From 67b1fd17e6b1dd373cd42ee8dd0ef8480f5eb6b1 Mon Sep 17 00:00:00 2001 From: xaxtix Date: Sat, 5 Nov 2022 16:34:47 +0400 Subject: [PATCH] update to 9.1.0 --- TMessagesProj/build.gradle | 10 +- .../release/AndroidManifest_standalone.xml | 80 + TMessagesProj/jni/sqlite/sqlite3.c | 29210 ++++++++++------ TMessagesProj/jni/sqlite/sqlite3.h | 696 +- TMessagesProj/jni/tgnet/ApiScheme.cpp | 43 + TMessagesProj/jni/tgnet/ApiScheme.h | 19 +- .../jni/tgnet/ConnectionsManager.cpp | 2 +- TMessagesProj/jni/tgnet/Defines.h | 3 +- TMessagesProj/src/main/assets/currencies.json | 2 +- .../recyclerview/widget/RecyclerView.java | 8 +- .../telegram/messenger/AndroidUtilities.java | 221 +- .../messenger/BotWebViewVibrationEffect.java | 11 +- .../org/telegram/messenger/BuildVars.java | 4 +- .../org/telegram/messenger/ChatObject.java | 98 +- .../messenger/ChatsWidgetService.java | 6 +- .../messenger/DownloadController.java | 3 + .../telegram/messenger/FeedWidgetService.java | 4 +- .../telegram/messenger/FileLoadOperation.java | 47 +- .../org/telegram/messenger/FileLoader.java | 3 + .../java/org/telegram/messenger/FileLog.java | 14 +- .../telegram/messenger/FilePathDatabase.java | 38 +- .../org/telegram/messenger/ImageLoader.java | 23 +- .../org/telegram/messenger/ImageLocation.java | 11 + .../org/telegram/messenger/ImageReceiver.java | 55 +- .../telegram/messenger/LocaleController.java | 6 +- .../telegram/messenger/MediaController.java | 54 +- .../messenger/MediaDataController.java | 359 +- .../messenger/MessageCustomParamsHelper.java | 5 + .../org/telegram/messenger/MessageObject.java | 431 +- .../messenger/MessagesController.java | 1718 +- .../telegram/messenger/MessagesStorage.java | 3193 +- .../messenger/NotificationCenter.java | 3 +- .../messenger/NotificationsController.java | 332 +- .../NotificationsDisabledReceiver.java | 14 +- .../NotificationsSettingsFacade.java | 258 + .../messenger/PushListenerController.java | 12 +- .../telegram/messenger/SecretChatHelper.java | 6 +- .../messenger/SendMessagesHelper.java | 156 +- .../org/telegram/messenger/SharedConfig.java | 14 +- .../org/telegram/messenger/SvgHelper.java | 16 +- .../telegram/messenger/TopicsController.java | 886 + .../org/telegram/messenger/UserConfig.java | 4 +- .../org/telegram/messenger/Utilities.java | 59 +- .../telegram/messenger/WearReplyReceiver.java | 27 +- .../telegram/messenger/browser/Browser.java | 27 +- .../telegram/messenger/camera/CameraView.java | 22 +- .../messenger/utils/BitmapsCache.java | 35 +- .../telegram/tgnet/ConnectionsManager.java | 5 +- .../org/telegram/tgnet/NativeByteBuffer.java | 8 + .../main/java/org/telegram/tgnet/TLRPC.java | 1575 +- .../ui/ActionBar/ActionBarLayout.java | 271 +- .../ui/ActionBar/ActionBarMenuItem.java | 88 +- .../telegram/ui/ActionBar/AlertDialog.java | 116 +- .../telegram/ui/ActionBar/BaseFragment.java | 101 +- .../telegram/ui/ActionBar/BottomSheet.java | 3 +- .../ui/ActionBar/DrawerLayoutContainer.java | 16 +- .../ui/ActionBar/INavigationLayout.java | 379 + .../telegram/ui/ActionBar/SimpleTextView.java | 62 +- .../java/org/telegram/ui/ActionBar/Theme.java | 132 +- .../org/telegram/ui/ActionIntroActivity.java | 7 +- .../telegram/ui/Adapters/DialogsAdapter.java | 4 +- .../ui/Adapters/DialogsSearchAdapter.java | 26 +- .../telegram/ui/Adapters/MentionsAdapter.java | 2 +- .../ui/Adapters/MessagesSearchAdapter.java | 6 +- .../telegram/ui/Adapters/SearchAdapter.java | 3 +- .../telegram/ui/ArchivedStickersActivity.java | 4 +- .../java/org/telegram/ui/ArticleViewer.java | 16 +- .../java/org/telegram/ui/BubbleActivity.java | 53 +- .../org/telegram/ui/CacheControlActivity.java | 8 +- .../org/telegram/ui/CalendarActivity.java | 35 +- .../java/org/telegram/ui/CallLogActivity.java | 6 +- .../org/telegram/ui/CameraScanActivity.java | 122 +- .../org/telegram/ui/Cells/AboutLinkCell.java | 150 +- .../telegram/ui/Cells/AdminedChannelCell.java | 3 +- .../java/org/telegram/ui/Cells/BaseCell.java | 6 + .../org/telegram/ui/Cells/ChatActionCell.java | 81 +- .../telegram/ui/Cells/ChatMessageCell.java | 1648 +- .../telegram/ui/Cells/ContextLinkCell.java | 2 +- .../org/telegram/ui/Cells/DialogCell.java | 718 +- .../telegram/ui/Cells/DrawerProfileCell.java | 44 +- .../org/telegram/ui/Cells/DrawerUserCell.java | 13 +- .../ui/Cells/GroupCreateUserCell.java | 2 +- .../telegram/ui/Cells/ManageChatUserCell.java | 2 +- .../org/telegram/ui/Cells/MentionCell.java | 6 +- .../telegram/ui/Cells/ProfileSearchCell.java | 24 +- .../telegram/ui/Cells/ShareDialogCell.java | 78 +- .../org/telegram/ui/Cells/ShareTopicCell.java | 109 + .../org/telegram/ui/Cells/SharedLinkCell.java | 2 +- .../telegram/ui/Cells/StickerEmojiCell.java | 2 +- .../java/org/telegram/ui/Cells/TextCell.java | 147 +- .../org/telegram/ui/Cells/TextCheckCell.java | 17 +- .../org/telegram/ui/Cells/TextCheckCell2.java | 8 +- .../org/telegram/ui/Cells/TextDetailCell.java | 48 +- .../ui/Cells/ThemePreviewMessagesCell.java | 6 +- .../telegram/ui/Cells/TopicExceptionCell.java | 64 + .../telegram/ui/Cells/TopicSearchCell.java | 78 + .../java/org/telegram/ui/Cells/UserCell2.java | 4 +- .../org/telegram/ui/ChangeBioActivity.java | 2 +- .../telegram/ui/ChangeUsernameActivity.java | 1128 +- .../telegram/ui/ChannelAdminLogActivity.java | 44 +- .../telegram/ui/ChannelCreateActivity.java | 10 +- .../java/org/telegram/ui/ChatActivity.java | 2259 +- .../org/telegram/ui/ChatEditActivity.java | 269 +- .../org/telegram/ui/ChatEditTypeActivity.java | 608 +- .../org/telegram/ui/ChatLinkActivity.java | 24 +- .../telegram/ui/ChatRightsEditActivity.java | 71 +- .../org/telegram/ui/ChatUsersActivity.java | 60 +- .../ui/ChatsWidgetConfigActivity.java | 4 +- .../java/org/telegram/ui/CodeNumberField.java | 2 +- .../telegram/ui/Components/AlertsCreator.java | 115 +- .../ui/Components/AnimatedEmojiDrawable.java | 65 +- .../ui/Components/AnimatedEmojiSpan.java | 23 +- .../ui/Components/AnimatedTextView.java | 51 +- .../ui/Components/AudioPlayerAlert.java | 10 +- .../Components/AudioVisualizerDrawable.java | 11 + .../ui/Components/AvatarDrawable.java | 21 +- .../ui/Components/AvatarsDarawable.java | 11 + .../ui/Components/BackButtonMenu.java | 49 +- .../ui/Components/BackupImageView.java | 20 + .../ui/Components/BotWebViewContainer.java | 18 +- .../org/telegram/ui/Components/Bulletin.java | 362 +- .../ui/Components/BulletinFactory.java | 80 +- .../ui/Components/ChatActivityEnterView.java | 20 +- .../ui/Components/ChatAttachAlert.java | 2 +- .../ChatAttachAlertDocumentLayout.java | 5 +- .../ui/Components/ChatAvatarContainer.java | 41 +- .../ChatNotificationsPopupWrapper.java | 84 +- .../ui/Components/ClearHistoryAlert.java | 2 +- .../ui/Components/ColoredImageSpan.java | 2 +- .../ui/Components/CombinedDrawable.java | 4 + .../telegram/ui/Components/EditTextEmoji.java | 2 +- .../ui/Components/EmbedBottomSheet.java | 2 +- .../ui/Components/EmojiPacksAlert.java | 5 +- .../ui/Components/EmojiTabsStrip.java | 10 +- .../org/telegram/ui/Components/EmojiView.java | 2 +- .../ui/Components/FilterTabsView.java | 81 +- .../ui/Components/FiltersListBottomSheet.java | 16 +- .../ui/Components/FireworksOverlay.java | 29 +- .../telegram/ui/Components/FlatCheckBox.java | 2 +- .../ui/Components/FlickerLoadingView.java | 38 + .../FloatingDebugController.java | 69 + .../FloatingDebug/FloatingDebugProvider.java | 7 + .../FloatingDebug/FloatingDebugView.java | 514 + .../Components/Forum/ForumBubbleDrawable.java | 197 + .../ui/Components/Forum/ForumUtilities.java | 279 + .../ui/Components/FragmentContextView.java | 175 +- .../FragmentContextViewWavesDrawable.java | 2 +- .../ui/Components/GroupVoipInviteAlert.java | 10 +- .../ui/Components/InstantCameraView.java | 5 +- .../ui/Components/InviteLinkBottomSheet.java | 16 +- .../Components/InviteMembersBottomSheet.java | 46 +- .../ui/Components/LetterDrawable.java | 52 +- .../ui/Components/LinkActionView.java | 5 +- .../ui/Components/LinkSpanDrawable.java | 73 +- .../Components/LoadingAnimatedTextView.java | 87 + .../telegram/ui/Components/MediaActivity.java | 16 + .../Components/MemberRequestsBottomSheet.java | 2 +- .../ui/Components/MentionsContainerView.java | 5 + .../Components/MotionBackgroundDrawable.java | 4 +- .../OverlayActionBarLayoutDialog.java | 45 +- .../telegram/ui/Components/PasscodeView.java | 2 +- .../Components/PermanentLinkBottomSheet.java | 5 +- .../PhotoViewerCaptionEnterView.java | 6 +- .../ui/Components/PollVotesAlert.java | 123 +- .../Premium/GiftPremiumBottomSheet.java | 6 +- .../Premium/LimitReachedBottomSheet.java | 7 +- .../Premium/PremiumFeatureBottomSheet.java | 4 +- .../Premium/PremiumPreviewBottomSheet.java | 138 +- .../ui/Components/RLottieDrawable.java | 3 +- .../ui/Components/RadialProgress2.java | 11 +- .../ui/Components/ReactedUsersListView.java | 14 +- .../Reactions/CustomEmojiReactionsWindow.java | 86 +- .../Reactions/ReactionsLayoutInBubble.java | 23 +- .../Components/ReactionsContainerLayout.java | 37 +- .../ui/Components/RecyclerListView.java | 86 +- .../Components/ReplaceableIconDrawable.java | 21 +- .../Components/RoundVideoPlayingDrawable.java | 6 +- .../Components/SearchDownloadsContainer.java | 2 +- .../ui/Components/SearchViewPager.java | 10 +- .../org/telegram/ui/Components/SeekBar.java | 24 +- .../ui/Components/SeekBarWaveform.java | 16 +- .../ui/Components/SenderSelectPopup.java | 6 +- .../telegram/ui/Components/ShareAlert.java | 449 +- .../ui/Components/SharedMediaLayout.java | 112 +- .../Components/SizeNotifierFrameLayout.java | 6 +- .../ui/Components/StickerEmptyView.java | 4 +- .../telegram/ui/Components/StickersAlert.java | 10 +- .../org/telegram/ui/Components/Switch.java | 1 + .../ui/Components/ThemeEditorView.java | 27 +- .../ui/Components/ThemeSmallPreviewView.java | 6 +- .../ui/Components/TranscribeButton.java | 261 +- .../org/telegram/ui/Components/UndoView.java | 66 +- .../ui/Components/UnreadCounterTextView.java | 301 + .../ui/Components/UsersAlertBase.java | 20 +- .../telegram/ui/Components/VideoPlayer.java | 1 - .../ui/Components/voip/VoIPToggleButton.java | 4 +- .../org/telegram/ui/ContactAddActivity.java | 29 +- .../org/telegram/ui/ContactsActivity.java | 12 +- .../ui/ContactsWidgetConfigActivity.java | 4 +- .../org/telegram/ui/CreateTopicEmptyView.java | 93 + .../org/telegram/ui/DataSettingsActivity.java | 6 +- .../telegram/ui/DefaultThemesPreviewCell.java | 5 +- .../ChatActivityMemberRequestsDelegate.java | 8 +- .../ui/Delegates/MemberRequestsDelegate.java | 2 +- .../ui/DialogOrContactPickerActivity.java | 8 +- .../java/org/telegram/ui/DialogsActivity.java | 290 +- .../org/telegram/ui/EditWidgetActivity.java | 17 +- .../telegram/ui/ExternalActionActivity.java | 78 +- .../telegram/ui/FeedWidgetConfigActivity.java | 6 +- .../org/telegram/ui/FilterCreateActivity.java | 2 +- .../org/telegram/ui/FilterUsersActivity.java | 3 +- .../org/telegram/ui/FilteredSearchView.java | 10 +- .../org/telegram/ui/FiltersSetupActivity.java | 58 +- .../org/telegram/ui/GroupCallActivity.java | 49 +- .../org/telegram/ui/GroupCreateActivity.java | 8 +- .../telegram/ui/GroupCreateFinalActivity.java | 2 +- .../org/telegram/ui/IdenticonActivity.java | 2 +- .../java/org/telegram/ui/IntroActivity.java | 2 +- .../org/telegram/ui/KeyboardHideHelper.java | 2 +- .../telegram/ui/LNavigation/LNavigation.java | 2258 ++ .../java/org/telegram/ui/LaunchActivity.java | 1064 +- .../org/telegram/ui/LinkEditActivity.java | 73 +- .../org/telegram/ui/LocationActivity.java | 2 +- .../java/org/telegram/ui/LoginActivity.java | 15 +- .../org/telegram/ui/ManageLinksActivity.java | 8 +- .../java/org/telegram/ui/MessageSeenView.java | 44 +- .../telegram/ui/MessageStatisticActivity.java | 20 +- .../org/telegram/ui/NewContactActivity.java | 6 +- .../NotificationsCustomSettingsActivity.java | 35 +- .../ui/NotificationsSettingsActivity.java | 32 +- .../ui/NotificationsSoundActivity.java | 31 +- .../org/telegram/ui/PassportActivity.java | 10 +- .../org/telegram/ui/PaymentFormActivity.java | 28 +- .../org/telegram/ui/PeopleNearbyActivity.java | 4 +- .../org/telegram/ui/PhotoPickerActivity.java | 6 +- .../java/org/telegram/ui/PhotoViewer.java | 361 +- .../ui/PopupNotificationActivity.java | 10 +- .../telegram/ui/PremiumPreviewFragment.java | 7 + .../telegram/ui/PrivacySettingsActivity.java | 15 +- .../org/telegram/ui/PrivacyUsersActivity.java | 3 +- .../java/org/telegram/ui/ProfileActivity.java | 877 +- .../ui/ProfileNotificationsActivity.java | 137 +- .../telegram/ui/ProxySettingsActivity.java | 2 +- .../main/java/org/telegram/ui/QrActivity.java | 3 +- .../org/telegram/ui/ReadAllMentionsMenu.java | 8 +- .../ui/RestrictedLanguagesSelectActivity.java | 2 +- .../ui/SelectAnimatedEmojiDialog.java | 403 +- .../org/telegram/ui/SessionsActivity.java | 7 +- .../org/telegram/ui/StatisticActivity.java | 6 +- .../org/telegram/ui/StickersActivity.java | 12 +- .../ui/TextMessageEnterTransition.java | 16 +- .../java/org/telegram/ui/ThemeActivity.java | 23 +- .../org/telegram/ui/ThemePreviewActivity.java | 20 +- .../ui/TooManyCommunitiesActivity.java | 2 +- .../org/telegram/ui/TopicCreateFragment.java | 527 + .../java/org/telegram/ui/TopicsFragment.java | 2338 ++ .../ui/TopicsNotifySettingsFragments.java | 311 + .../ui/TwoStepVerificationActivity.java | 10 +- .../ui/TwoStepVerificationSetupActivity.java | 10 +- .../telegram/ui/WallpapersListActivity.java | 6 +- .../java/org/telegram/ui/WebviewActivity.java | 2 +- .../res/drawable-hdpi/ic_chatlist_add_2.png | Bin 0 -> 1834 bytes .../main/res/drawable-hdpi/list_unmute.png | Bin 0 -> 314 bytes .../main/res/drawable-hdpi/msg_discuss.png | Bin 0 -> 1154 bytes .../res/drawable-hdpi/msg_members_list.png | Bin 0 -> 994 bytes .../res/drawable-hdpi/msg_mini_forumarrow.png | Bin 0 -> 374 bytes .../src/main/res/drawable-hdpi/msg_signed.png | Bin 0 -> 763 bytes .../res/drawable-hdpi/msg_topic_close.png | Bin 0 -> 762 bytes .../res/drawable-hdpi/msg_topic_create.png | Bin 0 -> 837 bytes .../res/drawable-hdpi/msg_topic_restart.png | Bin 0 -> 991 bytes .../src/main/res/drawable-hdpi/msg_topics.png | Bin 0 -> 449 bytes .../res/drawable-hdpi/msg_viewintopic.png | Bin 0 -> 1170 bytes .../drawable-hdpi/popup_fixed_alert3.9.png | Bin 0 -> 2425 bytes .../res/drawable-mdpi/ic_chatlist_add_2.png | Bin 0 -> 1136 bytes .../main/res/drawable-mdpi/list_unmute.png | Bin 0 -> 278 bytes .../main/res/drawable-mdpi/msg_discuss.png | Bin 0 -> 730 bytes .../res/drawable-mdpi/msg_members_list.png | Bin 0 -> 665 bytes .../res/drawable-mdpi/msg_mini_forumarrow.png | Bin 0 -> 310 bytes .../src/main/res/drawable-mdpi/msg_signed.png | Bin 0 -> 526 bytes .../res/drawable-mdpi/msg_topic_close.png | Bin 0 -> 542 bytes .../res/drawable-mdpi/msg_topic_create.png | Bin 0 -> 599 bytes .../res/drawable-mdpi/msg_topic_restart.png | Bin 0 -> 720 bytes .../src/main/res/drawable-mdpi/msg_topics.png | Bin 0 -> 363 bytes .../res/drawable-mdpi/msg_viewintopic.png | Bin 0 -> 781 bytes .../drawable-mdpi/popup_fixed_alert3.9.png | Bin 0 -> 1520 bytes .../res/drawable-xhdpi/ic_chatlist_add_2.png | Bin 0 -> 2341 bytes .../main/res/drawable-xhdpi/list_unmute.png | Bin 0 -> 403 bytes .../main/res/drawable-xhdpi/msg_discuss.png | Bin 0 -> 1451 bytes .../res/drawable-xhdpi/msg_members_list.png | Bin 0 -> 1382 bytes .../drawable-xhdpi/msg_mini_forumarrow.png | Bin 0 -> 417 bytes .../main/res/drawable-xhdpi/msg_signed.png | Bin 0 -> 1010 bytes .../res/drawable-xhdpi/msg_topic_close.png | Bin 0 -> 1179 bytes .../res/drawable-xhdpi/msg_topic_create.png | Bin 0 -> 989 bytes .../res/drawable-xhdpi/msg_topic_restart.png | Bin 0 -> 1486 bytes .../main/res/drawable-xhdpi/msg_topics.png | Bin 0 -> 625 bytes .../res/drawable-xhdpi/msg_viewintopic.png | Bin 0 -> 1523 bytes .../drawable-xhdpi/popup_fixed_alert3.9.png | Bin 0 -> 3809 bytes .../res/drawable-xxhdpi/ic_chatlist_add_2.png | Bin 0 -> 3776 bytes .../main/res/drawable-xxhdpi/list_unmute.png | Bin 0 -> 487 bytes .../main/res/drawable-xxhdpi/msg_discuss.png | Bin 0 -> 2077 bytes .../res/drawable-xxhdpi/msg_members_list.png | Bin 0 -> 1787 bytes .../drawable-xxhdpi/msg_mini_forumarrow.png | Bin 0 -> 633 bytes .../main/res/drawable-xxhdpi/msg_signed.png | Bin 0 -> 1473 bytes .../res/drawable-xxhdpi/msg_topic_close.png | Bin 0 -> 1474 bytes .../res/drawable-xxhdpi/msg_topic_create.png | Bin 0 -> 1435 bytes .../res/drawable-xxhdpi/msg_topic_restart.png | Bin 0 -> 2163 bytes .../main/res/drawable-xxhdpi/msg_topics.png | Bin 0 -> 714 bytes .../res/drawable-xxhdpi/msg_viewintopic.png | Bin 0 -> 2206 bytes .../drawable-xxhdpi/popup_fixed_alert3.9.png | Bin 0 -> 7189 bytes .../src/main/res/raw/filter_reorder.json | 1 + .../src/main/res/raw/hint_swipe_reply.json | 1 + .../src/main/res/raw/not_available.json | 1 + .../src/main/res/raw/topic_bubble.svg | 7 + TMessagesProj/src/main/res/raw/topics.json | 1 + .../src/main/res/raw/voice_to_text.json | 1 + TMessagesProj/src/main/res/values/ids.xml | 3 + TMessagesProj/src/main/res/values/strings.xml | 156 +- gradle.properties | 4 +- 318 files changed, 46849 insertions(+), 16511 deletions(-) create mode 100644 TMessagesProj/src/main/java/org/telegram/messenger/NotificationsSettingsFacade.java create mode 100644 TMessagesProj/src/main/java/org/telegram/messenger/TopicsController.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/ActionBar/INavigationLayout.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareTopicCell.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Cells/TopicExceptionCell.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Cells/TopicSearchCell.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/FloatingDebug/FloatingDebugController.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/FloatingDebug/FloatingDebugProvider.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/FloatingDebug/FloatingDebugView.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/Forum/ForumBubbleDrawable.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/Forum/ForumUtilities.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/LoadingAnimatedTextView.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/UnreadCounterTextView.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/CreateTopicEmptyView.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/LNavigation/LNavigation.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/TopicCreateFragment.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/TopicsFragment.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/TopicsNotifySettingsFragments.java create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/ic_chatlist_add_2.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/list_unmute.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/msg_discuss.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/msg_members_list.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/msg_mini_forumarrow.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/msg_signed.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/msg_topic_close.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/msg_topic_create.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/msg_topic_restart.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/msg_topics.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/msg_viewintopic.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/popup_fixed_alert3.9.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/ic_chatlist_add_2.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/list_unmute.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/msg_discuss.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/msg_members_list.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/msg_mini_forumarrow.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/msg_signed.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/msg_topic_close.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/msg_topic_create.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/msg_topic_restart.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/msg_topics.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/msg_viewintopic.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/popup_fixed_alert3.9.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/ic_chatlist_add_2.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/list_unmute.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/msg_discuss.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/msg_members_list.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/msg_mini_forumarrow.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/msg_signed.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/msg_topic_close.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/msg_topic_create.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/msg_topic_restart.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/msg_topics.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/msg_viewintopic.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/popup_fixed_alert3.9.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/ic_chatlist_add_2.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/list_unmute.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/msg_discuss.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/msg_members_list.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/msg_mini_forumarrow.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/msg_signed.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/msg_topic_close.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/msg_topic_create.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/msg_topic_restart.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/msg_topics.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/msg_viewintopic.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/popup_fixed_alert3.9.png create mode 100644 TMessagesProj/src/main/res/raw/filter_reorder.json create mode 100644 TMessagesProj/src/main/res/raw/hint_swipe_reply.json create mode 100644 TMessagesProj/src/main/res/raw/not_available.json create mode 100644 TMessagesProj/src/main/res/raw/topic_bubble.svg create mode 100644 TMessagesProj/src/main/res/raw/topics.json create mode 100644 TMessagesProj/src/main/res/raw/voice_to_text.json diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle index f5ef40b64..cd456e8c0 100644 --- a/TMessagesProj/build.gradle +++ b/TMessagesProj/build.gradle @@ -1,3 +1,5 @@ +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform + apply plugin: 'com.android.library' repositories { @@ -42,6 +44,7 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' } +def isWindows = String.valueOf(DefaultNativePlatform.currentOperatingSystem.toFamilyName() == OperatingSystemFamily.WINDOWS) android { compileSdkVersion 31 buildToolsVersion '31.0.0' @@ -102,7 +105,8 @@ android { ndk.debugSymbolLevel = 'FULL' buildConfigField "String", "APP_CENTER_HASH", "\"\"" buildConfigField "boolean", "DEBUG_VERSION", "true" - buildConfigField "boolean", "DEBUG_PRIVATE_VERSION", "false" + buildConfigField "boolean", "DEBUG_PRIVATE_VERSION", "true" + buildConfigField "boolean", "BUILD_HOST_IS_WINDOWS", isWindows } HA_private { @@ -115,6 +119,7 @@ android { buildConfigField "String", "APP_CENTER_HASH", "\"" + getProps("APP_CENTER_HASH_PRIVATE") + "\"" buildConfigField "boolean", "DEBUG_VERSION", "true" buildConfigField "boolean", "DEBUG_PRIVATE_VERSION", "true" + buildConfigField "boolean", "BUILD_HOST_IS_WINDOWS", isWindows } HA_public { @@ -127,6 +132,7 @@ android { buildConfigField "String", "APP_CENTER_HASH", "\"" + getProps("APP_CENTER_HASH_PUBLIC") + "\"" buildConfigField "boolean", "DEBUG_VERSION", "true" buildConfigField "boolean", "DEBUG_PRIVATE_VERSION", "false" + buildConfigField "boolean", "BUILD_HOST_IS_WINDOWS", isWindows } standalone { @@ -139,6 +145,7 @@ android { buildConfigField "String", "APP_CENTER_HASH", "\"\"" buildConfigField "boolean", "DEBUG_VERSION", "false" buildConfigField "boolean", "DEBUG_PRIVATE_VERSION", "false" + buildConfigField "boolean", "BUILD_HOST_IS_WINDOWS", isWindows } release { @@ -152,6 +159,7 @@ android { buildConfigField "String", "APP_CENTER_HASH", "\"\"" buildConfigField "boolean", "DEBUG_VERSION", "false" buildConfigField "boolean", "DEBUG_PRIVATE_VERSION", "false" + buildConfigField "boolean", "BUILD_HOST_IS_WINDOWS", isWindows } } } diff --git a/TMessagesProj/config/release/AndroidManifest_standalone.xml b/TMessagesProj/config/release/AndroidManifest_standalone.xml index 7cb61014d..8849bb3d9 100644 --- a/TMessagesProj/config/release/AndroidManifest_standalone.xml +++ b/TMessagesProj/config/release/AndroidManifest_standalone.xml @@ -31,6 +31,86 @@ android:requestLegacyExternalStorage="true" tools:replace="android:supportsRtl"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +** +**
  • [[SQLITE_FCNTL_EXTERNAL_READER]] +** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect +** whether or not there is a database client in another process with a wal-mode +** transaction open on the database or not. It is only available on unix.The +** (void*) argument passed with this file-control should be a pointer to a +** value of type (int). The integer value is set to 1 if the database is a wal +** mode database and there exists at least one client in another process that +** currently has an SQL transaction open on the database. It is set to 0 if +** the database is not a wal-mode db, or if there is no such connection in any +** other process. This opcode cannot be used to detect transactions opened +** by clients within the current process, only within other processes. +** +** +**
  • [[SQLITE_FCNTL_CKSM_FILE]] +** Used by the cksmvfs VFS module only. +** */ #define SQLITE_FCNTL_LOCKSTATE 1 #define SQLITE_FCNTL_GET_LOCKPROXYFILE 2 @@ -2230,6 +1528,8 @@ struct sqlite3_io_methods { #define SQLITE_FCNTL_CKPT_DONE 37 #define SQLITE_FCNTL_RESERVE_BYTES 38 #define SQLITE_FCNTL_CKPT_START 39 +#define SQLITE_FCNTL_EXTERNAL_READER 40 +#define SQLITE_FCNTL_CKSM_FILE 41 /* deprecated names */ #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE @@ -3508,11 +2808,14 @@ SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64); ** CAPI3REF: Count The Number Of Rows Modified ** METHOD: sqlite3 ** -** ^This function returns the number of rows modified, inserted or +** ^These functions return the number of rows modified, inserted or ** deleted by the most recently completed INSERT, UPDATE or DELETE ** statement on the database connection specified by the only parameter. -** ^Executing any other type of SQL statement does not modify the value -** returned by this function. +** The two functions are identical except for the type of the return value +** and that if the number of rows modified by the most recent INSERT, UPDATE +** or DELETE is greater than the maximum value supported by type "int", then +** the return value of sqlite3_changes() is undefined. ^Executing any other +** type of SQL statement does not modify the value returned by these functions. ** ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are ** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], @@ -3561,16 +2864,21 @@ SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64); ** */ SQLITE_API int sqlite3_changes(sqlite3*); +SQLITE_API sqlite3_int64 sqlite3_changes64(sqlite3*); /* ** CAPI3REF: Total Number Of Rows Modified ** METHOD: sqlite3 ** -** ^This function returns the total number of rows inserted, modified or +** ^These functions return the total number of rows inserted, modified or ** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed ** since the database connection was opened, including those executed as -** part of trigger programs. ^Executing any other type of SQL statement -** does not affect the value returned by sqlite3_total_changes(). +** part of trigger programs. The two functions are identical except for the +** type of the return value and that if the number of rows modified by the +** connection exceeds the maximum value supported by type "int", then +** the return value of sqlite3_total_changes() is undefined. ^Executing +** any other type of SQL statement does not affect the value returned by +** sqlite3_total_changes(). ** ** ^Changes made as part of [foreign key actions] are included in the ** count, but those made as part of REPLACE constraint resolution are @@ -3598,6 +2906,7 @@ SQLITE_API int sqlite3_changes(sqlite3*); ** */ SQLITE_API int sqlite3_total_changes(sqlite3*); +SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3*); /* ** CAPI3REF: Interrupt A Long-Running Query @@ -4427,6 +3736,14 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** the default shared cache setting provided by ** [sqlite3_enable_shared_cache()].)^ ** +** [[OPEN_EXRESCODE]] ^(
    [SQLITE_OPEN_EXRESCODE]
    +**
    The database connection comes up in "extended result code mode". +** In other words, the database behaves has if +** [sqlite3_extended_result_codes(db,1)] where called on the database +** connection as soon as the connection is created. In addition to setting +** the extended result code mode, this flag also causes [sqlite3_open_v2()] +** to return an extended result code.
    +** ** [[OPEN_NOFOLLOW]] ^(
    [SQLITE_OPEN_NOFOLLOW]
    **
    The database filename is not allowed to be a symbolic link
    ** )^ @@ -4434,7 +3751,15 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** If the 3rd parameter to sqlite3_open_v2() is not one of the ** required combinations shown above optionally combined with other ** [SQLITE_OPEN_READONLY | SQLITE_OPEN_* bits] -** then the behavior is undefined. +** then the behavior is undefined. Historic versions of SQLite +** have silently ignored surplus bits in the flags parameter to +** sqlite3_open_v2(), however that behavior might not be carried through +** into future versions of SQLite and so applications should not rely +** upon it. Note in particular that the SQLITE_OPEN_EXCLUSIVE flag is a no-op +** for sqlite3_open_v2(). The SQLITE_OPEN_EXCLUSIVE does *not* cause +** the open to fail if the database already exists. The SQLITE_OPEN_EXCLUSIVE +** flag is intended for use by the [sqlite3_vfs|VFS interface] only, and not +** by sqlite3_open_v2(). ** ** ^The fourth parameter to sqlite3_open_v2() is the name of the ** [sqlite3_vfs] object that defines the operating system interface that @@ -4805,13 +4130,14 @@ SQLITE_API void sqlite3_free_filename(char*); ** sqlite3_extended_errcode() might change with each API call. ** Except, there are some interfaces that are guaranteed to never ** change the value of the error code. The error-code preserving -** interfaces are: +** interfaces include the following: ** **
      **
    • sqlite3_errcode() **
    • sqlite3_extended_errcode() **
    • sqlite3_errmsg() **
    • sqlite3_errmsg16() +**
    • sqlite3_error_offset() **
    ** ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language @@ -4826,6 +4152,13 @@ SQLITE_API void sqlite3_free_filename(char*); ** ^(Memory to hold the error message string is managed internally ** and must not be freed by the application)^. ** +** ^If the most recent error references a specific token in the input +** SQL, the sqlite3_error_offset() interface returns the byte offset +** of the start of that token. ^The byte offset returned by +** sqlite3_error_offset() assumes that the input SQL is UTF8. +** ^If the most recent error does not reference a specific token in the input +** SQL, then the sqlite3_error_offset() function returns -1. +** ** When the serialized [threading mode] is in use, it might be the ** case that a second error occurs on a separate thread in between ** the time of the first error and the call to these interfaces. @@ -4845,6 +4178,7 @@ SQLITE_API int sqlite3_extended_errcode(sqlite3 *db); SQLITE_API const char *sqlite3_errmsg(sqlite3*); SQLITE_API const void *sqlite3_errmsg16(sqlite3*); SQLITE_API const char *sqlite3_errstr(int); +SQLITE_API int sqlite3_error_offset(sqlite3 *db); /* ** CAPI3REF: Prepared Statement Object @@ -5202,12 +4536,17 @@ SQLITE_API int sqlite3_prepare16_v3( ** are managed by SQLite and are automatically freed when the prepared ** statement is finalized. ** ^The string returned by sqlite3_expanded_sql(P), on the other hand, -** is obtained from [sqlite3_malloc()] and must be free by the application +** is obtained from [sqlite3_malloc()] and must be freed by the application ** by passing it to [sqlite3_free()]. +** +** ^The sqlite3_normalized_sql() interface is only available if +** the [SQLITE_ENABLE_NORMALIZE] compile-time option is defined. */ SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt); SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt); +#ifdef SQLITE_ENABLE_NORMALIZE SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt); +#endif /* ** CAPI3REF: Determine If An SQL Statement Writes The Database @@ -5242,6 +4581,19 @@ SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt); ** [BEGIN] merely sets internal flags, but the [BEGIN|BEGIN IMMEDIATE] and ** [BEGIN|BEGIN EXCLUSIVE] commands do touch the database and so ** sqlite3_stmt_readonly() returns false for those commands. +** +** ^This routine returns false if there is any possibility that the +** statement might change the database file. ^A false return does +** not guarantee that the statement will change the database file. +** ^For example, an UPDATE statement might have a WHERE clause that +** makes it a no-op, but the sqlite3_stmt_readonly() result would still +** be false. ^Similarly, a CREATE TABLE IF NOT EXISTS statement is a +** read-only no-op if the table already exists, but +** sqlite3_stmt_readonly() still returns false for such a statement. +** +** ^If prepared statement X is an [EXPLAIN] or [EXPLAIN QUERY PLAN] +** statement, then sqlite3_stmt_readonly(X) returns the same value as +** if the EXPLAIN or EXPLAIN QUERY PLAN prefix were omitted. */ SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); @@ -5310,6 +4662,8 @@ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*); ** ** ^The sqlite3_value objects that are passed as parameters into the ** implementation of [application-defined SQL functions] are protected. +** ^The sqlite3_value objects returned by [sqlite3_vtab_rhs_value()] +** are protected. ** ^The sqlite3_value object returned by ** [sqlite3_column_value()] is unprotected. ** Unprotected sqlite3_value objects may only be used as arguments @@ -5411,18 +4765,22 @@ typedef struct sqlite3_context sqlite3_context; ** contain embedded NULs. The result of expressions involving strings ** with embedded NULs is undefined. ** -** ^The fifth argument to the BLOB and string binding interfaces -** is a destructor used to dispose of the BLOB or -** string after SQLite has finished with it. ^The destructor is called -** to dispose of the BLOB or string even if the call to the bind API fails, -** except the destructor is not called if the third parameter is a NULL -** pointer or the fourth parameter is negative. -** ^If the fifth argument is -** the special value [SQLITE_STATIC], then SQLite assumes that the -** information is in static, unmanaged space and does not need to be freed. -** ^If the fifth argument has the value [SQLITE_TRANSIENT], then -** SQLite makes its own private copy of the data immediately, before -** the sqlite3_bind_*() routine returns. +** ^The fifth argument to the BLOB and string binding interfaces controls +** or indicates the lifetime of the object referenced by the third parameter. +** These three options exist: +** ^ (1) A destructor to dispose of the BLOB or string after SQLite has finished +** with it may be passed. ^It is called to dispose of the BLOB or string even +** if the call to the bind API fails, except the destructor is not called if +** the third parameter is a NULL pointer or the fourth parameter is negative. +** ^ (2) The special constant, [SQLITE_STATIC], may be passsed to indicate that +** the application remains responsible for disposing of the object. ^In this +** case, the object and the provided pointer to it must remain valid until +** either the prepared statement is finalized or the same SQL parameter is +** bound to something else, whichever occurs sooner. +** ^ (3) The constant, [SQLITE_TRANSIENT], may be passed to indicate that the +** object is to be copied prior to the return from sqlite3_bind_*(). ^The +** object and pointer to it must remain valid until then. ^SQLite will then +** manage the lifetime of its private copy. ** ** ^The sixth argument to sqlite3_bind_text64() must be one of ** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE] @@ -5927,6 +5285,10 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** even empty strings, are always zero-terminated. ^The return ** value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer. ** +** ^Strings returned by sqlite3_column_text16() always have the endianness +** which is native to the platform, regardless of the text encoding set +** for the database. +** ** Warning: ^The object returned by [sqlite3_column_value()] is an ** [unprotected sqlite3_value] object. In a multithreaded environment, ** an unprotected sqlite3_value object may only be used safely with @@ -5940,7 +5302,7 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** [application-defined SQL functions] or [virtual tables], not within ** top-level application code. ** -** The these routines may attempt to convert the datatype of the result. +** These routines may attempt to convert the datatype of the result. ** ^For example, if the internal representation is FLOAT and a text result ** is requested, [sqlite3_snprintf()] is used internally to perform the ** conversion automatically. ^(The following table details the conversions @@ -5965,7 +5327,7 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** TEXT BLOB No change ** BLOB INTEGER [CAST] to INTEGER ** BLOB FLOAT [CAST] to REAL -** BLOB TEXT Add a zero terminator if needed +** BLOB TEXT [CAST] to TEXT, ensure zero terminator ** ** )^ ** @@ -6164,7 +5526,6 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); ** within VIEWs, TRIGGERs, CHECK constraints, generated column expressions, ** index expressions, or the WHERE clause of partial indexes. ** -** ** For best security, the [SQLITE_DIRECTONLY] flag is recommended for ** all application-defined SQL functions that do not need to be ** used inside of triggers, view, CHECK constraints, or other elements of @@ -6174,7 +5535,6 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); ** a database file to include invocations of the function with parameters ** chosen by the attacker, which the application will then execute when ** the database file is opened and read. -** ** ** ^(The fifth parameter is an arbitrary pointer. The implementation of the ** function can gain access to this pointer using [sqlite3_user_data()].)^ @@ -6539,7 +5899,8 @@ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*); ** object D and returns a pointer to that copy. ^The [sqlite3_value] returned ** is a [protected sqlite3_value] object even if the input is not. ** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a -** memory allocation fails. +** memory allocation fails. ^If V is a [pointer value], then the result +** of sqlite3_value_dup(V) is a NULL value. ** ** ^The sqlite3_value_free(V) interface frees an [sqlite3_value] object ** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer @@ -7221,6 +6582,28 @@ SQLITE_API int sqlite3_get_autocommit(sqlite3*); */ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); +/* +** CAPI3REF: Return The Schema Name For A Database Connection +** METHOD: sqlite3 +** +** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name +** for the N-th database on database connection D, or a NULL pointer of N is +** out of range. An N value of 0 means the main database file. An N of 1 is +** the "temp" schema. Larger values of N correspond to various ATTACH-ed +** databases. +** +** Space to hold the string that is returned by sqlite3_db_name() is managed +** by SQLite itself. The string might be deallocated by any operation that +** changes the schema, including [ATTACH] or [DETACH] or calls to +** [sqlite3_serialize()] or [sqlite3_deserialize()], even operations that +** occur on a different thread. Applications that need to +** remember the string long-term should make their own copy. Applications that +** are accessing the same database connection simultaneously on multiple +** threads should mutex-protect calls to this API and should make their own +** private copy of the result prior to releasing the mutex. +*/ +SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N); + /* ** CAPI3REF: Return The Filename For A Database Connection ** METHOD: sqlite3 @@ -7380,6 +6763,72 @@ SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); +/* +** CAPI3REF: Autovacuum Compaction Amount Callback +** METHOD: sqlite3 +** +** ^The sqlite3_autovacuum_pages(D,C,P,X) interface registers a callback +** function C that is invoked prior to each autovacuum of the database +** file. ^The callback is passed a copy of the generic data pointer (P), +** the schema-name of the attached database that is being autovacuumed, +** the the size of the database file in pages, the number of free pages, +** and the number of bytes per page, respectively. The callback should +** return the number of free pages that should be removed by the +** autovacuum. ^If the callback returns zero, then no autovacuum happens. +** ^If the value returned is greater than or equal to the number of +** free pages, then a complete autovacuum happens. +** +**

    ^If there are multiple ATTACH-ed database files that are being +** modified as part of a transaction commit, then the autovacuum pages +** callback is invoked separately for each file. +** +**

    The callback is not reentrant. The callback function should +** not attempt to invoke any other SQLite interface. If it does, bad +** things may happen, including segmentation faults and corrupt database +** files. The callback function should be a simple function that +** does some arithmetic on its input parameters and returns a result. +** +** ^The X parameter to sqlite3_autovacuum_pages(D,C,P,X) is an optional +** destructor for the P parameter. ^If X is not NULL, then X(P) is +** invoked whenever the database connection closes or when the callback +** is overwritten by another invocation of sqlite3_autovacuum_pages(). +** +**

    ^There is only one autovacuum pages callback per database connection. +** ^Each call to the sqlite3_autovacuum_pages() interface overrides all +** previous invocations for that database connection. ^If the callback +** argument (C) to sqlite3_autovacuum_pages(D,C,P,X) is a NULL pointer, +** then the autovacuum steps callback is cancelled. The return value +** from sqlite3_autovacuum_pages() is normally SQLITE_OK, but might +** be some other error code if something goes wrong. The current +** implementation will only return SQLITE_OK or SQLITE_MISUSE, but other +** return codes might be added in future releases. +** +**

    If no autovacuum pages callback is specified (the usual case) or +** a NULL pointer is provided for the callback, +** then the default behavior is to vacuum all free pages. So, in other +** words, the default behavior is the same as if the callback function +** were something like this: +** +**

    +**     unsigned int demonstration_autovac_pages_callback(
    +**       void *pClientData,
    +**       const char *zSchema,
    +**       unsigned int nDbPage,
    +**       unsigned int nFreePage,
    +**       unsigned int nBytePerPage
    +**     ){
    +**       return nFreePage;
    +**     }
    +** 
    +*/ +SQLITE_API int sqlite3_autovacuum_pages( + sqlite3 *db, + unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), + void*, + void(*)(void*) +); + + /* ** CAPI3REF: Data Change Notification Callbacks ** METHOD: sqlite3 @@ -8021,24 +7470,56 @@ struct sqlite3_index_info { ** ** These macros define the allowed values for the ** [sqlite3_index_info].aConstraint[].op field. Each value represents -** an operator that is part of a constraint term in the wHERE clause of +** an operator that is part of a constraint term in the WHERE clause of ** a query that uses a [virtual table]. +** +** ^The left-hand operand of the operator is given by the corresponding +** aConstraint[].iColumn field. ^An iColumn of -1 indicates the left-hand +** operand is the rowid. +** The SQLITE_INDEX_CONSTRAINT_LIMIT and SQLITE_INDEX_CONSTRAINT_OFFSET +** operators have no left-hand operand, and so for those operators the +** corresponding aConstraint[].iColumn is meaningless and should not be +** used. +** +** All operator values from SQLITE_INDEX_CONSTRAINT_FUNCTION through +** value 255 are reserved to represent functions that are overloaded +** by the [xFindFunction|xFindFunction method] of the virtual table +** implementation. +** +** The right-hand operands for each constraint might be accessible using +** the [sqlite3_vtab_rhs_value()] interface. Usually the right-hand +** operand is only available if it appears as a single constant literal +** in the input SQL. If the right-hand operand is another column or an +** expression (even a constant expression) or a parameter, then the +** sqlite3_vtab_rhs_value() probably will not be able to extract it. +** ^The SQLITE_INDEX_CONSTRAINT_ISNULL and +** SQLITE_INDEX_CONSTRAINT_ISNOTNULL operators have no right-hand operand +** and hence calls to sqlite3_vtab_rhs_value() for those operators will +** always return SQLITE_NOTFOUND. +** +** The collating sequence to be used for comparison can be found using +** the [sqlite3_vtab_collation()] interface. For most real-world virtual +** tables, the collating sequence of constraints does not matter (for example +** because the constraints are numeric) and so the sqlite3_vtab_collation() +** interface is no commonly needed. */ -#define SQLITE_INDEX_CONSTRAINT_EQ 2 -#define SQLITE_INDEX_CONSTRAINT_GT 4 -#define SQLITE_INDEX_CONSTRAINT_LE 8 -#define SQLITE_INDEX_CONSTRAINT_LT 16 -#define SQLITE_INDEX_CONSTRAINT_GE 32 -#define SQLITE_INDEX_CONSTRAINT_MATCH 64 -#define SQLITE_INDEX_CONSTRAINT_LIKE 65 -#define SQLITE_INDEX_CONSTRAINT_GLOB 66 -#define SQLITE_INDEX_CONSTRAINT_REGEXP 67 -#define SQLITE_INDEX_CONSTRAINT_NE 68 -#define SQLITE_INDEX_CONSTRAINT_ISNOT 69 -#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 -#define SQLITE_INDEX_CONSTRAINT_ISNULL 71 -#define SQLITE_INDEX_CONSTRAINT_IS 72 -#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150 +#define SQLITE_INDEX_CONSTRAINT_EQ 2 +#define SQLITE_INDEX_CONSTRAINT_GT 4 +#define SQLITE_INDEX_CONSTRAINT_LE 8 +#define SQLITE_INDEX_CONSTRAINT_LT 16 +#define SQLITE_INDEX_CONSTRAINT_GE 32 +#define SQLITE_INDEX_CONSTRAINT_MATCH 64 +#define SQLITE_INDEX_CONSTRAINT_LIKE 65 +#define SQLITE_INDEX_CONSTRAINT_GLOB 66 +#define SQLITE_INDEX_CONSTRAINT_REGEXP 67 +#define SQLITE_INDEX_CONSTRAINT_NE 68 +#define SQLITE_INDEX_CONSTRAINT_ISNOT 69 +#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 +#define SQLITE_INDEX_CONSTRAINT_ISNULL 71 +#define SQLITE_INDEX_CONSTRAINT_IS 72 +#define SQLITE_INDEX_CONSTRAINT_LIMIT 73 +#define SQLITE_INDEX_CONSTRAINT_OFFSET 74 +#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150 /* ** CAPI3REF: Register A Virtual Table Implementation @@ -8067,7 +7548,7 @@ struct sqlite3_index_info { ** destructor. ** ** ^If the third parameter (the pointer to the sqlite3_module object) is -** NULL then no new module is create and any existing modules with the +** NULL then no new module is created and any existing modules with the ** same name are dropped. ** ** See also: [sqlite3_drop_modules()] @@ -8842,7 +8323,9 @@ SQLITE_API int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS 29 #define SQLITE_TESTCTRL_SEEK_COUNT 30 #define SQLITE_TESTCTRL_TRACEFLAGS 31 -#define SQLITE_TESTCTRL_LAST 31 /* Largest TESTCTRL */ +#define SQLITE_TESTCTRL_TUNE 32 +#define SQLITE_TESTCTRL_LOGEST 33 +#define SQLITE_TESTCTRL_LAST 33 /* Largest TESTCTRL */ /* ** CAPI3REF: SQL Keyword Checking @@ -9365,6 +8848,16 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); ** The counter is incremented on the first [sqlite3_step()] call of each ** cycle. ** +** [[SQLITE_STMTSTATUS_FILTER_MISS]] +** [[SQLITE_STMTSTATUS_FILTER HIT]] +**
    SQLITE_STMTSTATUS_FILTER_HIT
    +** SQLITE_STMTSTATUS_FILTER_MISS
    +**
    ^SQLITE_STMTSTATUS_FILTER_HIT is the number of times that a join +** step was bypassed because a Bloom filter returned not-found. The +** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of +** times that the Bloom filter returned a find, and thus the join step +** had to be processed as normal. +** ** [[SQLITE_STMTSTATUS_MEMUSED]]
    SQLITE_STMTSTATUS_MEMUSED
    **
    ^This is the approximate number of bytes of heap memory ** used to store the prepared statement. ^This value is not actually @@ -9379,6 +8872,8 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); #define SQLITE_STMTSTATUS_VM_STEP 4 #define SQLITE_STMTSTATUS_REPREPARE 5 #define SQLITE_STMTSTATUS_RUN 6 +#define SQLITE_STMTSTATUS_FILTER_MISS 7 +#define SQLITE_STMTSTATUS_FILTER_HIT 8 #define SQLITE_STMTSTATUS_MEMUSED 99 /* @@ -10042,8 +9537,9 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); ** ** A single database handle may have at most a single write-ahead log callback ** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any -** previously registered write-ahead log callback. ^Note that the -** [sqlite3_wal_autocheckpoint()] interface and the +** previously registered write-ahead log callback. ^The return value is +** a copy of the third parameter from the previous call, if any, or 0. +** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the ** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will ** overwrite any prior [sqlite3_wal_hook()] settings. */ @@ -10346,19 +9842,276 @@ SQLITE_API int sqlite3_vtab_nochange(sqlite3_context*); /* ** CAPI3REF: Determine The Collation For a Virtual Table Constraint +** METHOD: sqlite3_index_info ** ** This function may only be called from within a call to the [xBestIndex] -** method of a [virtual table]. +** method of a [virtual table]. This function returns a pointer to a string +** that is the name of the appropriate collation sequence to use for text +** comparisons on the constraint identified by its arguments. ** -** The first argument must be the sqlite3_index_info object that is the -** first parameter to the xBestIndex() method. The second argument must be -** an index into the aConstraint[] array belonging to the sqlite3_index_info -** structure passed to xBestIndex. This function returns a pointer to a buffer -** containing the name of the collation sequence for the corresponding -** constraint. +** The first argument must be the pointer to the [sqlite3_index_info] object +** that is the first parameter to the xBestIndex() method. The second argument +** must be an index into the aConstraint[] array belonging to the +** sqlite3_index_info structure passed to xBestIndex. +** +** Important: +** The first parameter must be the same pointer that is passed into the +** xBestMethod() method. The first parameter may not be a pointer to a +** different [sqlite3_index_info] object, even an exact copy. +** +** The return value is computed as follows: +** +**
      +**
    1. If the constraint comes from a WHERE clause expression that contains +** a [COLLATE operator], then the name of the collation specified by +** that COLLATE operator is returned. +**

    2. If there is no COLLATE operator, but the column that is the subject +** of the constraint specifies an alternative collating sequence via +** a [COLLATE clause] on the column definition within the CREATE TABLE +** statement that was passed into [sqlite3_declare_vtab()], then the +** name of that alternative collating sequence is returned. +**

    3. Otherwise, "BINARY" is returned. +**

    */ SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_info*,int); +/* +** CAPI3REF: Determine if a virtual table query is DISTINCT +** METHOD: sqlite3_index_info +** +** This API may only be used from within an [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this +** interface from outside of xBestIndex() is undefined and probably harmful. +** +** ^The sqlite3_vtab_distinct() interface returns an integer between 0 and +** 3. The integer returned by sqlite3_vtab_distinct() +** gives the virtual table additional information about how the query +** planner wants the output to be ordered. As long as the virtual table +** can meet the ordering requirements of the query planner, it may set +** the "orderByConsumed" flag. +** +**
    1. +** ^If the sqlite3_vtab_distinct() interface returns 0, that means +** that the query planner needs the virtual table to return all rows in the +** sort order defined by the "nOrderBy" and "aOrderBy" fields of the +** [sqlite3_index_info] object. This is the default expectation. If the +** virtual table outputs all rows in sorted order, then it is always safe for +** the xBestIndex method to set the "orderByConsumed" flag, regardless of +** the return value from sqlite3_vtab_distinct(). +**

    2. +** ^(If the sqlite3_vtab_distinct() interface returns 1, that means +** that the query planner does not need the rows to be returned in sorted order +** as long as all rows with the same values in all columns identified by the +** "aOrderBy" field are adjacent.)^ This mode is used when the query planner +** is doing a GROUP BY. +**

    3. +** ^(If the sqlite3_vtab_distinct() interface returns 2, that means +** that the query planner does not need the rows returned in any particular +** order, as long as rows with the same values in all "aOrderBy" columns +** are adjacent.)^ ^(Furthermore, only a single row for each particular +** combination of values in the columns identified by the "aOrderBy" field +** needs to be returned.)^ ^It is always ok for two or more rows with the same +** values in all "aOrderBy" columns to be returned, as long as all such rows +** are adjacent. ^The virtual table may, if it chooses, omit extra rows +** that have the same value for all columns identified by "aOrderBy". +** ^However omitting the extra rows is optional. +** This mode is used for a DISTINCT query. +**

    4. +** ^(If the sqlite3_vtab_distinct() interface returns 3, that means +** that the query planner needs only distinct rows but it does need the +** rows to be sorted.)^ ^The virtual table implementation is free to omit +** rows that are identical in all aOrderBy columns, if it wants to, but +** it is not required to omit any rows. This mode is used for queries +** that have both DISTINCT and ORDER BY clauses. +**

    +** +** ^For the purposes of comparing virtual table output values to see if the +** values are same value for sorting purposes, two NULL values are considered +** to be the same. In other words, the comparison operator is "IS" +** (or "IS NOT DISTINCT FROM") and not "==". +** +** If a virtual table implementation is unable to meet the requirements +** specified above, then it must not set the "orderByConsumed" flag in the +** [sqlite3_index_info] object or an incorrect answer may result. +** +** ^A virtual table implementation is always free to return rows in any order +** it wants, as long as the "orderByConsumed" flag is not set. ^When the +** the "orderByConsumed" flag is unset, the query planner will add extra +** [bytecode] to ensure that the final results returned by the SQL query are +** ordered correctly. The use of the "orderByConsumed" flag and the +** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful +** use of the sqlite3_vtab_distinct() interface and the "orderByConsumed" +** flag might help queries against a virtual table to run faster. Being +** overly aggressive and setting the "orderByConsumed" flag when it is not +** valid to do so, on the other hand, might cause SQLite to return incorrect +** results. +*/ +SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info*); + +/* +** CAPI3REF: Identify and handle IN constraints in xBestIndex +** +** This interface may only be used from within an +** [xBestIndex|xBestIndex() method] of a [virtual table] implementation. +** The result of invoking this interface from any other context is +** undefined and probably harmful. +** +** ^(A constraint on a virtual table of the form +** "[IN operator|column IN (...)]" is +** communicated to the xBestIndex method as a +** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use +** this constraint, it must set the corresponding +** aConstraintUsage[].argvIndex to a postive integer. ^(Then, under +** the usual mode of handling IN operators, SQLite generates [bytecode] +** that invokes the [xFilter|xFilter() method] once for each value +** on the right-hand side of the IN operator.)^ Thus the virtual table +** only sees a single value from the right-hand side of the IN operator +** at a time. +** +** In some cases, however, it would be advantageous for the virtual +** table to see all values on the right-hand of the IN operator all at +** once. The sqlite3_vtab_in() interfaces facilitates this in two ways: +** +**
      +**
    1. +** ^A call to sqlite3_vtab_in(P,N,-1) will return true (non-zero) +** if and only if the [sqlite3_index_info|P->aConstraint][N] constraint +** is an [IN operator] that can be processed all at once. ^In other words, +** sqlite3_vtab_in() with -1 in the third argument is a mechanism +** by which the virtual table can ask SQLite if all-at-once processing +** of the IN operator is even possible. +** +**

    2. +** ^A call to sqlite3_vtab_in(P,N,F) with F==1 or F==0 indicates +** to SQLite that the virtual table does or does not want to process +** the IN operator all-at-once, respectively. ^Thus when the third +** parameter (F) is non-negative, this interface is the mechanism by +** which the virtual table tells SQLite how it wants to process the +** IN operator. +**

    +** +** ^The sqlite3_vtab_in(P,N,F) interface can be invoked multiple times +** within the same xBestIndex method call. ^For any given P,N pair, +** the return value from sqlite3_vtab_in(P,N,F) will always be the same +** within the same xBestIndex call. ^If the interface returns true +** (non-zero), that means that the constraint is an IN operator +** that can be processed all-at-once. ^If the constraint is not an IN +** operator or cannot be processed all-at-once, then the interface returns +** false. +** +** ^(All-at-once processing of the IN operator is selected if both of the +** following conditions are met: +** +**
      +**
    1. The P->aConstraintUsage[N].argvIndex value is set to a positive +** integer. This is how the virtual table tells SQLite that it wants to +** use the N-th constraint. +** +**

    2. The last call to sqlite3_vtab_in(P,N,F) for which F was +** non-negative had F>=1. +**

    )^ +** +** ^If either or both of the conditions above are false, then SQLite uses +** the traditional one-at-a-time processing strategy for the IN constraint. +** ^If both conditions are true, then the argvIndex-th parameter to the +** xFilter method will be an [sqlite3_value] that appears to be NULL, +** but which can be passed to [sqlite3_vtab_in_first()] and +** [sqlite3_vtab_in_next()] to find all values on the right-hand side +** of the IN constraint. +*/ +SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle); + +/* +** CAPI3REF: Find all elements on the right-hand side of an IN constraint. +** +** These interfaces are only useful from within the +** [xFilter|xFilter() method] of a [virtual table] implementation. +** The result of invoking these interfaces from any other context +** is undefined and probably harmful. +** +** The X parameter in a call to sqlite3_vtab_in_first(X,P) or +** sqlite3_vtab_in_next(X,P) must be one of the parameters to the +** xFilter method which invokes these routines, and specifically +** a parameter that was previously selected for all-at-once IN constraint +** processing use the [sqlite3_vtab_in()] interface in the +** [xBestIndex|xBestIndex method]. ^(If the X parameter is not +** an xFilter argument that was selected for all-at-once IN constraint +** processing, then these routines return [SQLITE_MISUSE])^ or perhaps +** exhibit some other undefined or harmful behavior. +** +** ^(Use these routines to access all values on the right-hand side +** of the IN constraint using code like the following: +** +**
    +**    for(rc=sqlite3_vtab_in_first(pList, &pVal);
    +**        rc==SQLITE_OK && pVal
    +**        rc=sqlite3_vtab_in_next(pList, &pVal)
    +**    ){
    +**      // do something with pVal
    +**    }
    +**    if( rc!=SQLITE_OK ){
    +**      // an error has occurred
    +**    }
    +** 
    )^ +** +** ^On success, the sqlite3_vtab_in_first(X,P) and sqlite3_vtab_in_next(X,P) +** routines return SQLITE_OK and set *P to point to the first or next value +** on the RHS of the IN constraint. ^If there are no more values on the +** right hand side of the IN constraint, then *P is set to NULL and these +** routines return [SQLITE_DONE]. ^The return value might be +** some other value, such as SQLITE_NOMEM, in the event of a malfunction. +** +** The *ppOut values returned by these routines are only valid until the +** next call to either of these routines or until the end of the xFilter +** method from which these routines were called. If the virtual table +** implementation needs to retain the *ppOut values for longer, it must make +** copies. The *ppOut values are [protected sqlite3_value|protected]. +*/ +SQLITE_API int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut); +SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut); + +/* +** CAPI3REF: Constraint values in xBestIndex() +** METHOD: sqlite3_index_info +** +** This API may only be used from within the [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this interface +** from outside of an xBestIndex method are undefined and probably harmful. +** +** ^When the sqlite3_vtab_rhs_value(P,J,V) interface is invoked from within +** the [xBestIndex] method of a [virtual table] implementation, with P being +** a copy of the [sqlite3_index_info] object pointer passed into xBestIndex and +** J being a 0-based index into P->aConstraint[], then this routine +** attempts to set *V to the value of the right-hand operand of +** that constraint if the right-hand operand is known. ^If the +** right-hand operand is not known, then *V is set to a NULL pointer. +** ^The sqlite3_vtab_rhs_value(P,J,V) interface returns SQLITE_OK if +** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V) +** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th +** constraint is not available. ^The sqlite3_vtab_rhs_value() interface +** can return an result code other than SQLITE_OK or SQLITE_NOTFOUND if +** something goes wrong. +** +** The sqlite3_vtab_rhs_value() interface is usually only successful if +** the right-hand operand of a constraint is a literal value in the original +** SQL statement. If the right-hand operand is an expression or a reference +** to some other column or a [host parameter], then sqlite3_vtab_rhs_value() +** will probably return [SQLITE_NOTFOUND]. +** +** ^(Some constraints, such as [SQLITE_INDEX_CONSTRAINT_ISNULL] and +** [SQLITE_INDEX_CONSTRAINT_ISNOTNULL], have no right-hand operand. For such +** constraints, sqlite3_vtab_rhs_value() always returns SQLITE_NOTFOUND.)^ +** +** ^The [sqlite3_value] object returned in *V is a protected sqlite3_value +** and remains valid for the duration of the xBestIndex method call. +** ^When xBestIndex returns, the sqlite3_value object returned by +** sqlite3_vtab_rhs_value() is automatically deallocated. +** +** The "_rhs_" in the name of this routine is an abbreviation for +** "Right-Hand Side". +*/ +SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); + /* ** CAPI3REF: Conflict resolution modes ** KEYWORDS: {conflict resolution mode} @@ -10594,6 +10347,15 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** triggers; or 2 for changes resulting from triggers called by top-level ** triggers; and so forth. ** +** When the [sqlite3_blob_write()] API is used to update a blob column, +** the pre-update hook is invoked with SQLITE_DELETE. This is because the +** in this case the new values are not available. In this case, when a +** callback made with op==SQLITE_DELETE is actuall a write using the +** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns +** the index of the column being written. In other cases, where the +** pre-update hook is being invoked for some other reason, including a +** regular DELETE, sqlite3_preupdate_blobwrite() returns -1. +** ** See also: [sqlite3_update_hook()] */ #if defined(SQLITE_ENABLE_PREUPDATE_HOOK) @@ -10614,6 +10376,7 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); SQLITE_API int sqlite3_preupdate_count(sqlite3 *); SQLITE_API int sqlite3_preupdate_depth(sqlite3 *); SQLITE_API int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); +SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *); #endif /* @@ -10852,8 +10615,8 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c ** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory ** allocation error occurs. ** -** This interface is only available if SQLite is compiled with the -** [SQLITE_ENABLE_DESERIALIZE] option. +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. */ SQLITE_API unsigned char *sqlite3_serialize( sqlite3 *db, /* The database connection */ @@ -10900,12 +10663,16 @@ SQLITE_API unsigned char *sqlite3_serialize( ** database is currently in a read transaction or is involved in a backup ** operation. ** +** It is not possible to deserialized into the TEMP database. If the +** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the +** function returns SQLITE_ERROR. +** ** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the ** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then ** [sqlite3_free()] is invoked on argument P prior to returning. ** -** This interface is only available if SQLite is compiled with the -** [SQLITE_ENABLE_DESERIALIZE] option. +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. */ SQLITE_API int sqlite3_deserialize( sqlite3 *db, /* The database connection */ @@ -11154,6 +10921,38 @@ SQLITE_API int sqlite3session_create( */ SQLITE_API void sqlite3session_delete(sqlite3_session *pSession); +/* +** CAPIREF: Conigure a Session Object +** METHOD: sqlite3_session +** +** This method is used to configure a session object after it has been +** created. At present the only valid value for the second parameter is +** [SQLITE_SESSION_OBJCONFIG_SIZE]. +** +** Arguments for sqlite3session_object_config() +** +** The following values may passed as the the 4th parameter to +** sqlite3session_object_config(). +** +**
    SQLITE_SESSION_OBJCONFIG_SIZE
    +** This option is used to set, clear or query the flag that enables +** the [sqlite3session_changeset_size()] API. Because it imposes some +** computational overhead, this API is disabled by default. Argument +** pArg must point to a value of type (int). If the value is initially +** 0, then the sqlite3session_changeset_size() API is disabled. If it +** is greater than 0, then the same API is enabled. Or, if the initial +** value is less than zero, no change is made. In all cases the (int) +** variable is set to 1 if the sqlite3session_changeset_size() API is +** enabled following the current call, or 0 otherwise. +** +** It is an error (SQLITE_MISUSE) to attempt to modify this setting after +** the first table has been attached to the session object. +*/ +SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg); + +/* +*/ +#define SQLITE_SESSION_OBJCONFIG_SIZE 1 /* ** CAPI3REF: Enable Or Disable A Session Object @@ -11398,6 +11197,22 @@ SQLITE_API int sqlite3session_changeset( void **ppChangeset /* OUT: Buffer containing changeset */ ); +/* +** CAPI3REF: Return An Upper-limit For The Size Of The Changeset +** METHOD: sqlite3_session +** +** By default, this function always returns 0. For it to return +** a useful result, the sqlite3_session object must have been configured +** to enable this API using sqlite3session_object_config() with the +** SQLITE_SESSION_OBJCONFIG_SIZE verb. +** +** When enabled, this function returns an upper limit, in bytes, for the size +** of the changeset that might be produced if sqlite3session_changeset() were +** called. The final changeset size might be equal to or smaller than the +** size in bytes returned by this function. +*/ +SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession); + /* ** CAPI3REF: Load The Difference Between Tables Into A Session ** METHOD: sqlite3_session @@ -13329,12 +13144,17 @@ struct fts5_api { /************** End of sqlite3.h *********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ +/* +** Reuse the STATIC_LRU for mutex access to sqlite3_temp_directory. +*/ +#define SQLITE_MUTEX_STATIC_TEMPDIR SQLITE_MUTEX_STATIC_VFS1 + /* ** Include the configuration header output by 'configure' if we're using the ** autoconf-based build */ #if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H) -/* #include "config.h" */ +#include "config.h" #define SQLITECONFIG_H 1 #endif @@ -13570,11 +13390,12 @@ struct fts5_api { #ifndef __has_extension # define __has_extension(x) 0 /* compatibility with non-clang compilers */ #endif -#if GCC_VERSION>=4007000 || \ - (__has_extension(c_atomic) && __has_extension(c_atomic_store_n)) +#if GCC_VERSION>=4007000 || __has_extension(c_atomic) +# define SQLITE_ATOMIC_INTRINSICS 1 # define AtomicLoad(PTR) __atomic_load_n((PTR),__ATOMIC_RELAXED) # define AtomicStore(PTR,VAL) __atomic_store_n((PTR),(VAL),__ATOMIC_RELAXED) #else +# define SQLITE_ATOMIC_INTRINSICS 0 # define AtomicLoad(PTR) (*(PTR)) # define AtomicStore(PTR,VAL) (*(PTR) = (VAL)) #endif @@ -13779,11 +13600,12 @@ struct fts5_api { ** is significant and used at least once. On switch statements ** where multiple cases go to the same block of code, testcase() ** can insure that all cases are evaluated. -** */ -#ifdef SQLITE_COVERAGE_TEST -SQLITE_PRIVATE void sqlite3Coverage(int); -# define testcase(X) if( X ){ sqlite3Coverage(__LINE__); } +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG) +# ifndef SQLITE_AMALGAMATION + extern unsigned int sqlite3CoverageCounter; +# endif +# define testcase(X) if( X ){ sqlite3CoverageCounter += (unsigned)__LINE__; } #else # define testcase(X) #endif @@ -13813,6 +13635,14 @@ SQLITE_PRIVATE void sqlite3Coverage(int); # define VVA_ONLY(X) #endif +/* +** Disable ALWAYS() and NEVER() (make them pass-throughs) for coverage +** and mutation testing +*/ +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif + /* ** The ALWAYS and NEVER macros surround boolean expressions which ** are intended to always be true or false, respectively. Such @@ -13828,7 +13658,7 @@ SQLITE_PRIVATE void sqlite3Coverage(int); ** be true and false so that the unreachable code they specify will ** not be counted as untested code. */ -#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) # define ALWAYS(X) (1) # define NEVER(X) (0) #elif !defined(NDEBUG) @@ -13839,26 +13669,6 @@ SQLITE_PRIVATE void sqlite3Coverage(int); # define NEVER(X) (X) #endif -/* -** The harmless(X) macro indicates that expression X is usually false -** but can be true without causing any problems, but we don't know of -** any way to cause X to be true. -** -** In debugging and testing builds, this macro will abort if X is ever -** true. In this way, developers are alerted to a possible test case -** that causes X to be true. If a harmless macro ever fails, that is -** an opportunity to change the macro into a testcase() and add a new -** test case to the test suite. -** -** For normal production builds, harmless(X) is a no-op, since it does -** not matter whether expression X is true or false. -*/ -#ifdef SQLITE_DEBUG -# define harmless(X) assert(!(X)); -#else -# define harmless(X) -#endif - /* ** Some conditionals are optimizations only. In other words, if the ** conditionals are replaced with a constant 1 (true) or 0 (false) then @@ -13922,6 +13732,13 @@ SQLITE_PRIVATE void sqlite3Coverage(int); # undef SQLITE_ENABLE_EXPLAIN_COMMENTS #endif +/* +** SQLITE_OMIT_VIRTUALTABLE implies SQLITE_OMIT_ALTERTABLE +*/ +#if defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_ALTERTABLE) +# define SQLITE_OMIT_ALTERTABLE +#endif + /* ** Return true (non-zero) if the input is an integer that is too large ** to fit in 32-bits. This macro is used inside of various testcase() @@ -14034,7 +13851,7 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); /* ** Number of entries in a hash table */ -/* #define sqliteHashCount(H) ((H)->count) // NOT USED */ +#define sqliteHashCount(H) ((H)->count) #endif /* SQLITE_HASH_H */ @@ -14066,8 +13883,8 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); #define TK_LP 22 #define TK_RP 23 #define TK_AS 24 -#define TK_WITHOUT 25 -#define TK_COMMA 26 +#define TK_COMMA 25 +#define TK_WITHOUT 26 #define TK_ABORT 27 #define TK_ACTION 28 #define TK_AFTER 29 @@ -14153,77 +13970,79 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); #define TK_SLASH 109 #define TK_REM 110 #define TK_CONCAT 111 -#define TK_COLLATE 112 -#define TK_BITNOT 113 -#define TK_ON 114 -#define TK_INDEXED 115 -#define TK_STRING 116 -#define TK_JOIN_KW 117 -#define TK_CONSTRAINT 118 -#define TK_DEFAULT 119 -#define TK_NULL 120 -#define TK_PRIMARY 121 -#define TK_UNIQUE 122 -#define TK_CHECK 123 -#define TK_REFERENCES 124 -#define TK_AUTOINCR 125 -#define TK_INSERT 126 -#define TK_DELETE 127 -#define TK_UPDATE 128 -#define TK_SET 129 -#define TK_DEFERRABLE 130 -#define TK_FOREIGN 131 -#define TK_DROP 132 -#define TK_UNION 133 -#define TK_ALL 134 -#define TK_EXCEPT 135 -#define TK_INTERSECT 136 -#define TK_SELECT 137 -#define TK_VALUES 138 -#define TK_DISTINCT 139 -#define TK_DOT 140 -#define TK_FROM 141 -#define TK_JOIN 142 -#define TK_USING 143 -#define TK_ORDER 144 -#define TK_GROUP 145 -#define TK_HAVING 146 -#define TK_LIMIT 147 -#define TK_WHERE 148 -#define TK_RETURNING 149 -#define TK_INTO 150 -#define TK_NOTHING 151 -#define TK_FLOAT 152 -#define TK_BLOB 153 -#define TK_INTEGER 154 -#define TK_VARIABLE 155 -#define TK_CASE 156 -#define TK_WHEN 157 -#define TK_THEN 158 -#define TK_ELSE 159 -#define TK_INDEX 160 -#define TK_ALTER 161 -#define TK_ADD 162 -#define TK_WINDOW 163 -#define TK_OVER 164 -#define TK_FILTER 165 -#define TK_COLUMN 166 -#define TK_AGG_FUNCTION 167 -#define TK_AGG_COLUMN 168 -#define TK_TRUEFALSE 169 -#define TK_ISNOT 170 -#define TK_FUNCTION 171 -#define TK_UMINUS 172 -#define TK_UPLUS 173 -#define TK_TRUTH 174 -#define TK_REGISTER 175 -#define TK_VECTOR 176 -#define TK_SELECT_COLUMN 177 -#define TK_IF_NULL_ROW 178 -#define TK_ASTERISK 179 -#define TK_SPAN 180 -#define TK_SPACE 181 -#define TK_ILLEGAL 182 +#define TK_PTR 112 +#define TK_COLLATE 113 +#define TK_BITNOT 114 +#define TK_ON 115 +#define TK_INDEXED 116 +#define TK_STRING 117 +#define TK_JOIN_KW 118 +#define TK_CONSTRAINT 119 +#define TK_DEFAULT 120 +#define TK_NULL 121 +#define TK_PRIMARY 122 +#define TK_UNIQUE 123 +#define TK_CHECK 124 +#define TK_REFERENCES 125 +#define TK_AUTOINCR 126 +#define TK_INSERT 127 +#define TK_DELETE 128 +#define TK_UPDATE 129 +#define TK_SET 130 +#define TK_DEFERRABLE 131 +#define TK_FOREIGN 132 +#define TK_DROP 133 +#define TK_UNION 134 +#define TK_ALL 135 +#define TK_EXCEPT 136 +#define TK_INTERSECT 137 +#define TK_SELECT 138 +#define TK_VALUES 139 +#define TK_DISTINCT 140 +#define TK_DOT 141 +#define TK_FROM 142 +#define TK_JOIN 143 +#define TK_USING 144 +#define TK_ORDER 145 +#define TK_GROUP 146 +#define TK_HAVING 147 +#define TK_LIMIT 148 +#define TK_WHERE 149 +#define TK_RETURNING 150 +#define TK_INTO 151 +#define TK_NOTHING 152 +#define TK_FLOAT 153 +#define TK_BLOB 154 +#define TK_INTEGER 155 +#define TK_VARIABLE 156 +#define TK_CASE 157 +#define TK_WHEN 158 +#define TK_THEN 159 +#define TK_ELSE 160 +#define TK_INDEX 161 +#define TK_ALTER 162 +#define TK_ADD 163 +#define TK_WINDOW 164 +#define TK_OVER 165 +#define TK_FILTER 166 +#define TK_COLUMN 167 +#define TK_AGG_FUNCTION 168 +#define TK_AGG_COLUMN 169 +#define TK_TRUEFALSE 170 +#define TK_ISNOT 171 +#define TK_FUNCTION 172 +#define TK_UMINUS 173 +#define TK_UPLUS 174 +#define TK_TRUTH 175 +#define TK_REGISTER 176 +#define TK_VECTOR 177 +#define TK_SELECT_COLUMN 178 +#define TK_IF_NULL_ROW 179 +#define TK_ASTERISK 180 +#define TK_SPAN 181 +#define TK_ERROR 182 +#define TK_SPACE 183 +#define TK_ILLEGAL 184 /************** End of parse.h ***********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ @@ -14329,7 +14148,7 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); ** number of pages. A negative number N translations means that a buffer ** of -1024*N bytes is allocated and used for as many pages as it will hold. ** -** The default value of "20" was choosen to minimize the run-time of the +** The default value of "20" was chosen to minimize the run-time of the ** speedtest1 test program with options: --shrink-memory --reprepare */ #ifndef SQLITE_DEFAULT_PCACHE_INITSZ @@ -14491,6 +14310,7 @@ typedef INT16_TYPE LogEst; # define SQLITE_PTRSIZE __SIZEOF_POINTER__ # elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \ defined(_M_ARM) || defined(__arm__) || defined(__x86) || \ + (defined(__APPLE__) && defined(__POWERPC__)) || \ (defined(__TOS_AIX__) && !defined(__64BIT__)) # define SQLITE_PTRSIZE 4 # else @@ -14572,8 +14392,19 @@ typedef INT16_TYPE LogEst; /* ** Round up a number to the next larger multiple of 8. This is used ** to force 8-byte alignment on 64-bit architectures. +** +** ROUND8() always does the rounding, for any argument. +** +** ROUND8P() assumes that the argument is already an integer number of +** pointers in size, and so it is a no-op on systems where the pointer +** size is 8. */ #define ROUND8(x) (((x)+7)&~7) +#if SQLITE_PTRSIZE==8 +# define ROUND8P(x) (x) +#else +# define ROUND8P(x) (((x)+7)&~7) +#endif /* ** Round down to the nearest multiple of 8 @@ -14636,22 +14467,23 @@ typedef INT16_TYPE LogEst; #endif /* -** SELECTTRACE_ENABLED will be either 1 or 0 depending on whether or not -** the Select query generator tracing logic is turned on. +** TREETRACE_ENABLED will be either 1 or 0 depending on whether or not +** the Abstract Syntax Tree tracing logic is turned on. */ #if !defined(SQLITE_AMALGAMATION) -SQLITE_PRIVATE u32 sqlite3SelectTrace; +SQLITE_PRIVATE u32 sqlite3TreeTrace; #endif #if defined(SQLITE_DEBUG) \ - && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SELECTTRACE)) -# define SELECTTRACE_ENABLED 1 + && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SELECTTRACE) \ + || defined(SQLITE_ENABLE_TREETRACE)) +# define TREETRACE_ENABLED 1 # define SELECTTRACE(K,P,S,X) \ - if(sqlite3SelectTrace&(K)) \ + if(sqlite3TreeTrace&(K)) \ sqlite3DebugPrintf("%u/%d/%p: ",(S)->selId,(P)->addrExplain,(S)),\ sqlite3DebugPrintf X #else # define SELECTTRACE(K,P,S,X) -# define SELECTTRACE_ENABLED 0 +# define TREETRACE_ENABLED 0 #endif /* @@ -14685,11 +14517,25 @@ struct BusyHandler { /* ** Name of table that holds the database schema. +** +** The PREFERRED names are used whereever possible. But LEGACY is also +** used for backwards compatibility. +** +** 1. Queries can use either the PREFERRED or the LEGACY names +** 2. The sqlite3_set_authorizer() callback uses the LEGACY name +** 3. The PRAGMA table_list statement uses the PREFERRED name +** +** The LEGACY names are stored in the internal symbol hash table +** in support of (2). Names are translated using sqlite3PreferredTableName() +** for (3). The sqlite3FindTable() function takes care of translating +** names for (1). +** +** Note that "sqlite_temp_schema" can also be called "temp.sqlite_schema". */ -#define DFLT_SCHEMA_TABLE "sqlite_master" -#define DFLT_TEMP_SCHEMA_TABLE "sqlite_temp_master" -#define ALT_SCHEMA_TABLE "sqlite_schema" -#define ALT_TEMP_SCHEMA_TABLE "sqlite_temp_schema" +#define LEGACY_SCHEMA_TABLE "sqlite_master" +#define LEGACY_TEMP_SCHEMA_TABLE "sqlite_temp_master" +#define PREFERRED_SCHEMA_TABLE "sqlite_schema" +#define PREFERRED_TEMP_SCHEMA_TABLE "sqlite_temp_schema" /* @@ -14701,7 +14547,7 @@ struct BusyHandler { ** The name of the schema table. The name is different for TEMP. */ #define SCHEMA_TABLE(x) \ - ((!OMIT_TEMPDB)&&(x==1)?DFLT_TEMP_SCHEMA_TABLE:DFLT_SCHEMA_TABLE) + ((!OMIT_TEMPDB)&&(x==1)?LEGACY_TEMP_SCHEMA_TABLE:LEGACY_SCHEMA_TABLE) /* ** A convenience macro that returns the number of elements in @@ -14722,7 +14568,7 @@ struct BusyHandler { ** pointer will work here as long as it is distinct from SQLITE_STATIC ** and SQLITE_TRANSIENT. */ -#define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3OomFault) +#define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3OomClear) /* ** When SQLITE_OMIT_WSD is defined, it means that the target platform does @@ -14798,6 +14644,7 @@ typedef struct Lookaside Lookaside; typedef struct LookasideSlot LookasideSlot; typedef struct Module Module; typedef struct NameContext NameContext; +typedef struct OnOrUsing OnOrUsing; typedef struct Parse Parse; typedef struct ParseCleanup ParseCleanup; typedef struct PreUpdate PreUpdate; @@ -14850,10 +14697,11 @@ typedef struct With With; /* ** A bit in a Bitmask */ -#define MASKBIT(n) (((Bitmask)1)<<(n)) -#define MASKBIT64(n) (((u64)1)<<(n)) -#define MASKBIT32(n) (((unsigned int)1)<<(n)) -#define ALLBITS ((Bitmask)-1) +#define MASKBIT(n) (((Bitmask)1)<<(n)) +#define MASKBIT64(n) (((u64)1)<<(n)) +#define MASKBIT32(n) (((unsigned int)1)<<(n)) +#define SMASKBIT32(n) ((n)<=31?((unsigned int)1)<<(n):0) +#define ALLBITS ((Bitmask)-1) /* A VList object records a mapping between parameters/variables/wildcards ** in the SQL statement (such as $abc, @pqr, or :xyz) and the integer @@ -14915,14 +14763,15 @@ typedef struct Pager Pager; typedef struct PgHdr DbPage; /* -** Page number PAGER_MJ_PGNO is never used in an SQLite database (it is +** Page number PAGER_SJ_PGNO is never used in an SQLite database (it is ** reserved for working around a windows/posix incompatibility). It is ** used in the journal to signify that the remainder of the journal file ** is devoted to storing a super-journal name - there are no more pages to ** roll back. See comments for function writeSuperJournal() in pager.c ** for details. */ -#define PAGER_MJ_PGNO(x) ((Pgno)((PENDING_BYTE/((x)->pageSize))+1)) +#define PAGER_SJ_PGNO_COMPUTED(x) ((Pgno)((PENDING_BYTE/((x)->pageSize))+1)) +#define PAGER_SJ_PGNO(x) ((x)->lckPgno) /* ** Allowed values for the flags parameter to sqlite3PagerOpen(). @@ -15242,7 +15091,7 @@ SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *); #define BTREE_BLOBKEY 2 /* Table has keys only - no data */ SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree*, int, int*); -SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree*, int, int*); +SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree*, int, i64*); SQLITE_PRIVATE int sqlite3BtreeClearTableOfCursor(BtCursor*); SQLITE_PRIVATE int sqlite3BtreeTripAllCursors(Btree*, int, int); @@ -15366,13 +15215,17 @@ SQLITE_PRIVATE void sqlite3BtreeCursorHint(BtCursor*, int, ...); #endif SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor*); -SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( +SQLITE_PRIVATE int sqlite3BtreeTableMoveto( BtCursor*, - UnpackedRecord *pUnKey, i64 intKey, int bias, int *pRes ); +SQLITE_PRIVATE int sqlite3BtreeIndexMoveto( + BtCursor*, + UnpackedRecord *pUnKey, + int *pRes +); SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor*); SQLITE_PRIVATE int sqlite3BtreeCursorRestore(BtCursor*, int*); SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*, u8 flags); @@ -15595,7 +15448,6 @@ struct VdbeOp { #ifdef SQLITE_ENABLE_CURSOR_HINTS Expr *pExpr; /* Used when p4type is P4_EXPR */ #endif - int (*xAdvance)(BtCursor *, int); } p4; #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS char *zComment; /* Comment to improve readability */ @@ -15646,21 +15498,19 @@ typedef struct VdbeOpList VdbeOpList; #define P4_COLLSEQ (-2) /* P4 is a pointer to a CollSeq structure */ #define P4_INT32 (-3) /* P4 is a 32-bit signed integer */ #define P4_SUBPROGRAM (-4) /* P4 is a pointer to a SubProgram structure */ -#define P4_ADVANCE (-5) /* P4 is a pointer to BtreeNext() or BtreePrev() */ -#define P4_TABLE (-6) /* P4 is a pointer to a Table structure */ +#define P4_TABLE (-5) /* P4 is a pointer to a Table structure */ /* Above do not own any resources. Must free those below */ -#define P4_FREE_IF_LE (-7) -#define P4_DYNAMIC (-7) /* Pointer to memory from sqliteMalloc() */ -#define P4_FUNCDEF (-8) /* P4 is a pointer to a FuncDef structure */ -#define P4_KEYINFO (-9) /* P4 is a pointer to a KeyInfo structure */ -#define P4_EXPR (-10) /* P4 is a pointer to an Expr tree */ -#define P4_MEM (-11) /* P4 is a pointer to a Mem* structure */ -#define P4_VTAB (-12) /* P4 is a pointer to an sqlite3_vtab structure */ -#define P4_REAL (-13) /* P4 is a 64-bit floating point value */ -#define P4_INT64 (-14) /* P4 is a 64-bit signed integer */ -#define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */ -#define P4_FUNCCTX (-16) /* P4 is a pointer to an sqlite3_context object */ -#define P4_DYNBLOB (-17) /* Pointer to memory from sqliteMalloc() */ +#define P4_FREE_IF_LE (-6) +#define P4_DYNAMIC (-6) /* Pointer to memory from sqliteMalloc() */ +#define P4_FUNCDEF (-7) /* P4 is a pointer to a FuncDef structure */ +#define P4_KEYINFO (-8) /* P4 is a pointer to a KeyInfo structure */ +#define P4_EXPR (-9) /* P4 is a pointer to an Expr tree */ +#define P4_MEM (-10) /* P4 is a pointer to a Mem* structure */ +#define P4_VTAB (-11) /* P4 is a pointer to an sqlite3_vtab structure */ +#define P4_REAL (-12) /* P4 is a 64-bit floating point value */ +#define P4_INT64 (-13) /* P4 is a 64-bit signed integer */ +#define P4_INTARRAY (-14) /* P4 is a vector of 32-bit integers */ +#define P4_FUNCCTX (-15) /* P4 is a pointer to an sqlite3_context object */ /* Error message codes for OP_Halt */ #define P5_ConstraintNotNull 1 @@ -15705,53 +15555,53 @@ typedef struct VdbeOpList VdbeOpList; #define OP_Savepoint 0 #define OP_AutoCommit 1 #define OP_Transaction 2 -#define OP_SorterNext 3 /* jump */ -#define OP_Prev 4 /* jump */ -#define OP_Next 5 /* jump */ -#define OP_Checkpoint 6 -#define OP_JournalMode 7 -#define OP_Vacuum 8 -#define OP_VFilter 9 /* jump, synopsis: iplan=r[P3] zplan='P4' */ -#define OP_VUpdate 10 /* synopsis: data=r[P3@P2] */ -#define OP_Goto 11 /* jump */ -#define OP_Gosub 12 /* jump */ -#define OP_InitCoroutine 13 /* jump */ -#define OP_Yield 14 /* jump */ -#define OP_MustBeInt 15 /* jump */ -#define OP_Jump 16 /* jump */ -#define OP_Once 17 /* jump */ -#define OP_If 18 /* jump */ +#define OP_Checkpoint 3 +#define OP_JournalMode 4 +#define OP_Vacuum 5 +#define OP_VFilter 6 /* jump, synopsis: iplan=r[P3] zplan='P4' */ +#define OP_VUpdate 7 /* synopsis: data=r[P3@P2] */ +#define OP_Goto 8 /* jump */ +#define OP_Gosub 9 /* jump */ +#define OP_InitCoroutine 10 /* jump */ +#define OP_Yield 11 /* jump */ +#define OP_MustBeInt 12 /* jump */ +#define OP_Jump 13 /* jump */ +#define OP_Once 14 /* jump */ +#define OP_If 15 /* jump */ +#define OP_IfNot 16 /* jump */ +#define OP_IsNullOrType 17 /* jump, synopsis: if typeof(r[P1]) IN (P3,5) goto P2 */ +#define OP_IfNullRow 18 /* jump, synopsis: if P1.nullRow then r[P3]=NULL, goto P2 */ #define OP_Not 19 /* same as TK_NOT, synopsis: r[P2]= !r[P1] */ -#define OP_IfNot 20 /* jump */ -#define OP_IfNullRow 21 /* jump, synopsis: if P1.nullRow then r[P3]=NULL, goto P2 */ -#define OP_SeekLT 22 /* jump, synopsis: key=r[P3@P4] */ -#define OP_SeekLE 23 /* jump, synopsis: key=r[P3@P4] */ -#define OP_SeekGE 24 /* jump, synopsis: key=r[P3@P4] */ -#define OP_SeekGT 25 /* jump, synopsis: key=r[P3@P4] */ -#define OP_IfNotOpen 26 /* jump, synopsis: if( !csr[P1] ) goto P2 */ -#define OP_IfNoHope 27 /* jump, synopsis: key=r[P3@P4] */ -#define OP_NoConflict 28 /* jump, synopsis: key=r[P3@P4] */ -#define OP_NotFound 29 /* jump, synopsis: key=r[P3@P4] */ -#define OP_Found 30 /* jump, synopsis: key=r[P3@P4] */ -#define OP_SeekRowid 31 /* jump, synopsis: intkey=r[P3] */ -#define OP_NotExists 32 /* jump, synopsis: intkey=r[P3] */ -#define OP_Last 33 /* jump */ -#define OP_IfSmaller 34 /* jump */ -#define OP_SorterSort 35 /* jump */ -#define OP_Sort 36 /* jump */ -#define OP_Rewind 37 /* jump */ -#define OP_IdxLE 38 /* jump, synopsis: key=r[P3@P4] */ -#define OP_IdxGT 39 /* jump, synopsis: key=r[P3@P4] */ -#define OP_IdxLT 40 /* jump, synopsis: key=r[P3@P4] */ -#define OP_IdxGE 41 /* jump, synopsis: key=r[P3@P4] */ -#define OP_RowSetRead 42 /* jump, synopsis: r[P3]=rowset(P1) */ +#define OP_SeekLT 20 /* jump, synopsis: key=r[P3@P4] */ +#define OP_SeekLE 21 /* jump, synopsis: key=r[P3@P4] */ +#define OP_SeekGE 22 /* jump, synopsis: key=r[P3@P4] */ +#define OP_SeekGT 23 /* jump, synopsis: key=r[P3@P4] */ +#define OP_IfNotOpen 24 /* jump, synopsis: if( !csr[P1] ) goto P2 */ +#define OP_IfNoHope 25 /* jump, synopsis: key=r[P3@P4] */ +#define OP_NoConflict 26 /* jump, synopsis: key=r[P3@P4] */ +#define OP_NotFound 27 /* jump, synopsis: key=r[P3@P4] */ +#define OP_Found 28 /* jump, synopsis: key=r[P3@P4] */ +#define OP_SeekRowid 29 /* jump, synopsis: intkey=r[P3] */ +#define OP_NotExists 30 /* jump, synopsis: intkey=r[P3] */ +#define OP_Last 31 /* jump */ +#define OP_IfSmaller 32 /* jump */ +#define OP_SorterSort 33 /* jump */ +#define OP_Sort 34 /* jump */ +#define OP_Rewind 35 /* jump */ +#define OP_SorterNext 36 /* jump */ +#define OP_Prev 37 /* jump */ +#define OP_Next 38 /* jump */ +#define OP_IdxLE 39 /* jump, synopsis: key=r[P3@P4] */ +#define OP_IdxGT 40 /* jump, synopsis: key=r[P3@P4] */ +#define OP_IdxLT 41 /* jump, synopsis: key=r[P3@P4] */ +#define OP_IdxGE 42 /* jump, synopsis: key=r[P3@P4] */ #define OP_Or 43 /* same as TK_OR, synopsis: r[P3]=(r[P1] || r[P2]) */ #define OP_And 44 /* same as TK_AND, synopsis: r[P3]=(r[P1] && r[P2]) */ -#define OP_RowSetTest 45 /* jump, synopsis: if r[P3] in rowset(P1) goto P2 */ -#define OP_Program 46 /* jump */ -#define OP_FkIfZero 47 /* jump, synopsis: if fkctr[P1]==0 goto P2 */ -#define OP_IfPos 48 /* jump, synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */ -#define OP_IfNotZero 49 /* jump, synopsis: if r[P1]!=0 then r[P1]--, goto P2 */ +#define OP_RowSetRead 45 /* jump, synopsis: r[P3]=rowset(P1) */ +#define OP_RowSetTest 46 /* jump, synopsis: if r[P3] in rowset(P1) goto P2 */ +#define OP_Program 47 /* jump */ +#define OP_FkIfZero 48 /* jump, synopsis: if fkctr[P1]==0 goto P2 */ +#define OP_IfPos 49 /* jump, synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */ #define OP_IsNull 50 /* jump, same as TK_ISNULL, synopsis: if r[P1]==NULL goto P2 */ #define OP_NotNull 51 /* jump, same as TK_NOTNULL, synopsis: if r[P1]!=NULL goto P2 */ #define OP_Ne 52 /* jump, same as TK_NE, synopsis: IF r[P3]!=r[P1] */ @@ -15760,50 +15610,50 @@ typedef struct VdbeOpList VdbeOpList; #define OP_Le 55 /* jump, same as TK_LE, synopsis: IF r[P3]<=r[P1] */ #define OP_Lt 56 /* jump, same as TK_LT, synopsis: IF r[P3]=r[P1] */ -#define OP_ElseNotEq 58 /* jump, same as TK_ESCAPE */ -#define OP_DecrJumpZero 59 /* jump, synopsis: if (--r[P1])==0 goto P2 */ -#define OP_IncrVacuum 60 /* jump */ -#define OP_VNext 61 /* jump */ -#define OP_Init 62 /* jump, synopsis: Start at P2 */ -#define OP_PureFunc 63 /* synopsis: r[P3]=func(r[P2@NP]) */ -#define OP_Function 64 /* synopsis: r[P3]=func(r[P2@NP]) */ -#define OP_Return 65 -#define OP_EndCoroutine 66 -#define OP_HaltIfNull 67 /* synopsis: if r[P3]=null halt */ -#define OP_Halt 68 -#define OP_Integer 69 /* synopsis: r[P2]=P1 */ -#define OP_Int64 70 /* synopsis: r[P2]=P4 */ -#define OP_String 71 /* synopsis: r[P2]='P4' (len=P1) */ -#define OP_Null 72 /* synopsis: r[P2..P3]=NULL */ -#define OP_SoftNull 73 /* synopsis: r[P1]=NULL */ -#define OP_Blob 74 /* synopsis: r[P2]=P4 (len=P1) */ -#define OP_Variable 75 /* synopsis: r[P2]=parameter(P1,P4) */ -#define OP_Move 76 /* synopsis: r[P2@P3]=r[P1@P3] */ -#define OP_Copy 77 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ -#define OP_SCopy 78 /* synopsis: r[P2]=r[P1] */ -#define OP_IntCopy 79 /* synopsis: r[P2]=r[P1] */ -#define OP_ChngCntRow 80 /* synopsis: output=r[P1] */ -#define OP_ResultRow 81 /* synopsis: output=r[P1@P2] */ -#define OP_CollSeq 82 -#define OP_AddImm 83 /* synopsis: r[P1]=r[P1]+P2 */ -#define OP_RealAffinity 84 -#define OP_Cast 85 /* synopsis: affinity(r[P1]) */ -#define OP_Permutation 86 -#define OP_Compare 87 /* synopsis: r[P1@P3] <-> r[P2@P3] */ -#define OP_IsTrue 88 /* synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 */ -#define OP_Offset 89 /* synopsis: r[P3] = sqlite_offset(P1) */ -#define OP_Column 90 /* synopsis: r[P3]=PX */ -#define OP_Affinity 91 /* synopsis: affinity(r[P1@P2]) */ -#define OP_MakeRecord 92 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ -#define OP_Count 93 /* synopsis: r[P2]=count() */ -#define OP_ReadCookie 94 -#define OP_SetCookie 95 -#define OP_ReopenIdx 96 /* synopsis: root=P2 iDb=P3 */ -#define OP_OpenRead 97 /* synopsis: root=P2 iDb=P3 */ -#define OP_OpenWrite 98 /* synopsis: root=P2 iDb=P3 */ -#define OP_OpenDup 99 -#define OP_OpenAutoindex 100 /* synopsis: nColumn=P2 */ -#define OP_OpenEphemeral 101 /* synopsis: nColumn=P2 */ +#define OP_ElseEq 58 /* jump, same as TK_ESCAPE */ +#define OP_IfNotZero 59 /* jump, synopsis: if r[P1]!=0 then r[P1]--, goto P2 */ +#define OP_DecrJumpZero 60 /* jump, synopsis: if (--r[P1])==0 goto P2 */ +#define OP_IncrVacuum 61 /* jump */ +#define OP_VNext 62 /* jump */ +#define OP_Filter 63 /* jump, synopsis: if key(P3@P4) not in filter(P1) goto P2 */ +#define OP_Init 64 /* jump, synopsis: Start at P2 */ +#define OP_PureFunc 65 /* synopsis: r[P3]=func(r[P2@NP]) */ +#define OP_Function 66 /* synopsis: r[P3]=func(r[P2@NP]) */ +#define OP_Return 67 +#define OP_EndCoroutine 68 +#define OP_HaltIfNull 69 /* synopsis: if r[P3]=null halt */ +#define OP_Halt 70 +#define OP_Integer 71 /* synopsis: r[P2]=P1 */ +#define OP_Int64 72 /* synopsis: r[P2]=P4 */ +#define OP_String 73 /* synopsis: r[P2]='P4' (len=P1) */ +#define OP_BeginSubrtn 74 /* synopsis: r[P2]=NULL */ +#define OP_Null 75 /* synopsis: r[P2..P3]=NULL */ +#define OP_SoftNull 76 /* synopsis: r[P1]=NULL */ +#define OP_Blob 77 /* synopsis: r[P2]=P4 (len=P1) */ +#define OP_Variable 78 /* synopsis: r[P2]=parameter(P1,P4) */ +#define OP_Move 79 /* synopsis: r[P2@P3]=r[P1@P3] */ +#define OP_Copy 80 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ +#define OP_SCopy 81 /* synopsis: r[P2]=r[P1] */ +#define OP_IntCopy 82 /* synopsis: r[P2]=r[P1] */ +#define OP_FkCheck 83 +#define OP_ResultRow 84 /* synopsis: output=r[P1@P2] */ +#define OP_CollSeq 85 +#define OP_AddImm 86 /* synopsis: r[P1]=r[P1]+P2 */ +#define OP_RealAffinity 87 +#define OP_Cast 88 /* synopsis: affinity(r[P1]) */ +#define OP_Permutation 89 +#define OP_Compare 90 /* synopsis: r[P1@P3] <-> r[P2@P3] */ +#define OP_IsTrue 91 /* synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 */ +#define OP_ZeroOrNull 92 /* synopsis: r[P2] = 0 OR NULL */ +#define OP_Offset 93 /* synopsis: r[P3] = sqlite_offset(P1) */ +#define OP_Column 94 /* synopsis: r[P3]=PX cursor P1 column P2 */ +#define OP_TypeCheck 95 /* synopsis: typecheck(r[P1@P2]) */ +#define OP_Affinity 96 /* synopsis: affinity(r[P1@P2]) */ +#define OP_MakeRecord 97 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ +#define OP_Count 98 /* synopsis: r[P2]=count() */ +#define OP_ReadCookie 99 +#define OP_SetCookie 100 +#define OP_ReopenIdx 101 /* synopsis: root=P2 iDb=P3 */ #define OP_BitAnd 102 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */ #define OP_BitOr 103 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */ #define OP_ShiftLeft 104 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */ -#define OP_AggInverse 156 /* synopsis: accum=r[P3] inverse(r[P2@P5]) */ -#define OP_AggStep 157 /* synopsis: accum=r[P3] step(r[P2@P5]) */ -#define OP_AggStep1 158 /* synopsis: accum=r[P3] step(r[P2@P5]) */ -#define OP_AggValue 159 /* synopsis: r[P3]=value N=P2 */ -#define OP_AggFinal 160 /* synopsis: accum=r[P1] N=P2 */ -#define OP_Expire 161 -#define OP_CursorLock 162 -#define OP_CursorUnlock 163 -#define OP_TableLock 164 /* synopsis: iDb=P1 root=P2 write=P3 */ -#define OP_VBegin 165 -#define OP_VCreate 166 -#define OP_VDestroy 167 -#define OP_VOpen 168 -#define OP_VColumn 169 /* synopsis: r[P3]=vcolumn(P2) */ -#define OP_VRename 170 -#define OP_Pagecount 171 -#define OP_MaxPgcnt 172 -#define OP_Trace 173 -#define OP_CursorHint 174 -#define OP_ReleaseReg 175 /* synopsis: release r[P1@P2] mask P3 */ -#define OP_Noop 176 -#define OP_Explain 177 -#define OP_Abortable 178 +#define OP_OpenRead 112 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenWrite 113 /* synopsis: root=P2 iDb=P3 */ +#define OP_BitNot 114 /* same as TK_BITNOT, synopsis: r[P2]= ~r[P1] */ +#define OP_OpenDup 115 +#define OP_OpenAutoindex 116 /* synopsis: nColumn=P2 */ +#define OP_String8 117 /* same as TK_STRING, synopsis: r[P2]='P4' */ +#define OP_OpenEphemeral 118 /* synopsis: nColumn=P2 */ +#define OP_SorterOpen 119 +#define OP_SequenceTest 120 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */ +#define OP_OpenPseudo 121 /* synopsis: P3 columns in r[P2] */ +#define OP_Close 122 +#define OP_ColumnsUsed 123 +#define OP_SeekScan 124 /* synopsis: Scan-ahead up to P1 rows */ +#define OP_SeekHit 125 /* synopsis: set P2<=seekHit<=P3 */ +#define OP_Sequence 126 /* synopsis: r[P2]=cursor[P1].ctr++ */ +#define OP_NewRowid 127 /* synopsis: r[P2]=rowid */ +#define OP_Insert 128 /* synopsis: intkey=r[P3] data=r[P2] */ +#define OP_RowCell 129 +#define OP_Delete 130 +#define OP_ResetCount 131 +#define OP_SorterCompare 132 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ +#define OP_SorterData 133 /* synopsis: r[P2]=data */ +#define OP_RowData 134 /* synopsis: r[P2]=data */ +#define OP_Rowid 135 /* synopsis: r[P2]=PX rowid of P1 */ +#define OP_NullRow 136 +#define OP_SeekEnd 137 +#define OP_IdxInsert 138 /* synopsis: key=r[P2] */ +#define OP_SorterInsert 139 /* synopsis: key=r[P2] */ +#define OP_IdxDelete 140 /* synopsis: key=r[P2@P3] */ +#define OP_DeferredSeek 141 /* synopsis: Move P3 to P1.rowid if needed */ +#define OP_IdxRowid 142 /* synopsis: r[P2]=rowid */ +#define OP_FinishSeek 143 +#define OP_Destroy 144 +#define OP_Clear 145 +#define OP_ResetSorter 146 +#define OP_CreateBtree 147 /* synopsis: r[P2]=root iDb=P1 flags=P3 */ +#define OP_SqlExec 148 +#define OP_ParseSchema 149 +#define OP_LoadAnalysis 150 +#define OP_DropTable 151 +#define OP_DropIndex 152 +#define OP_Real 153 /* same as TK_FLOAT, synopsis: r[P2]=P4 */ +#define OP_DropTrigger 154 +#define OP_IntegrityCk 155 +#define OP_RowSetAdd 156 /* synopsis: rowset(P1)=r[P2] */ +#define OP_Param 157 +#define OP_FkCounter 158 /* synopsis: fkctr[P1]+=P2 */ +#define OP_MemMax 159 /* synopsis: r[P1]=max(r[P1],r[P2]) */ +#define OP_OffsetLimit 160 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */ +#define OP_AggInverse 161 /* synopsis: accum=r[P3] inverse(r[P2@P5]) */ +#define OP_AggStep 162 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggStep1 163 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggValue 164 /* synopsis: r[P3]=value N=P2 */ +#define OP_AggFinal 165 /* synopsis: accum=r[P1] N=P2 */ +#define OP_Expire 166 +#define OP_CursorLock 167 +#define OP_CursorUnlock 168 +#define OP_TableLock 169 /* synopsis: iDb=P1 root=P2 write=P3 */ +#define OP_VBegin 170 +#define OP_VCreate 171 +#define OP_VDestroy 172 +#define OP_VOpen 173 +#define OP_VInitIn 174 /* synopsis: r[P2]=ValueList(P1,P3) */ +#define OP_VColumn 175 /* synopsis: r[P3]=vcolumn(P2) */ +#define OP_VRename 176 +#define OP_Pagecount 177 +#define OP_MaxPgcnt 178 +#define OP_ClrSubtype 179 /* synopsis: r[P1].subtype = 0 */ +#define OP_FilterAdd 180 /* synopsis: filter(P1) += key(P3@P4) */ +#define OP_Trace 181 +#define OP_CursorHint 182 +#define OP_ReleaseReg 183 /* synopsis: release r[P1@P2] mask P3 */ +#define OP_Noop 184 +#define OP_Explain 185 +#define OP_Abortable 186 /* Properties such as "out2" or "jump" that are specified in ** comments following the "case" for each opcode in the vdbe.c @@ -15893,37 +15751,38 @@ typedef struct VdbeOpList VdbeOpList; #define OPFLG_OUT2 0x10 /* out2: P2 is an output */ #define OPFLG_OUT3 0x20 /* out3: P3 is an output */ #define OPFLG_INITIALIZER {\ -/* 0 */ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x10,\ -/* 8 */ 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x03, 0x03,\ -/* 16 */ 0x01, 0x01, 0x03, 0x12, 0x03, 0x01, 0x09, 0x09,\ -/* 24 */ 0x09, 0x09, 0x01, 0x09, 0x09, 0x09, 0x09, 0x09,\ -/* 32 */ 0x09, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\ -/* 40 */ 0x01, 0x01, 0x23, 0x26, 0x26, 0x0b, 0x01, 0x01,\ -/* 48 */ 0x03, 0x03, 0x03, 0x03, 0x0b, 0x0b, 0x0b, 0x0b,\ -/* 56 */ 0x0b, 0x0b, 0x01, 0x03, 0x01, 0x01, 0x01, 0x00,\ -/* 64 */ 0x00, 0x02, 0x02, 0x08, 0x00, 0x10, 0x10, 0x10,\ -/* 72 */ 0x10, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10,\ -/* 80 */ 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00,\ -/* 88 */ 0x12, 0x20, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00,\ -/* 96 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x26,\ +/* 0 */ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,\ +/* 8 */ 0x01, 0x01, 0x01, 0x03, 0x03, 0x01, 0x01, 0x03,\ +/* 16 */ 0x03, 0x03, 0x01, 0x12, 0x09, 0x09, 0x09, 0x09,\ +/* 24 */ 0x01, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x01,\ +/* 32 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\ +/* 40 */ 0x01, 0x01, 0x01, 0x26, 0x26, 0x23, 0x0b, 0x01,\ +/* 48 */ 0x01, 0x03, 0x03, 0x03, 0x0b, 0x0b, 0x0b, 0x0b,\ +/* 56 */ 0x0b, 0x0b, 0x01, 0x03, 0x03, 0x01, 0x01, 0x01,\ +/* 64 */ 0x01, 0x00, 0x00, 0x02, 0x02, 0x08, 0x00, 0x10,\ +/* 72 */ 0x10, 0x10, 0x00, 0x10, 0x00, 0x10, 0x10, 0x00,\ +/* 80 */ 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x02, 0x02,\ +/* 88 */ 0x02, 0x00, 0x00, 0x12, 0x1e, 0x20, 0x00, 0x00,\ +/* 96 */ 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x26, 0x26,\ /* 104 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,\ -/* 112 */ 0x00, 0x12, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,\ -/* 120 */ 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 128 */ 0x00, 0x00, 0x10, 0x00, 0x00, 0x04, 0x04, 0x00,\ -/* 136 */ 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00,\ -/* 144 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x10,\ -/* 152 */ 0x10, 0x00, 0x04, 0x1a, 0x00, 0x00, 0x00, 0x00,\ -/* 160 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 168 */ 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00,\ -/* 176 */ 0x00, 0x00, 0x00,} +/* 112 */ 0x00, 0x00, 0x12, 0x00, 0x00, 0x10, 0x00, 0x00,\ +/* 120 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10,\ +/* 128 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,\ +/* 136 */ 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x10, 0x00,\ +/* 144 */ 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,\ +/* 152 */ 0x00, 0x10, 0x00, 0x00, 0x06, 0x10, 0x00, 0x04,\ +/* 160 */ 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ +/* 168 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,\ +/* 176 */ 0x00, 0x10, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00,\ +/* 184 */ 0x00, 0x00, 0x00,} -/* The sqlite3P2Values() routine is able to run faster if it knows +/* The resolve3P2Values() routine is able to run faster if it knows ** the value of the largest JUMP opcode. The smaller the maximum ** JUMP opcode the better, so the mkopcodeh.tcl script that ** generated this include file strives to group all JUMP opcodes ** together near the beginning of the list. */ -#define SQLITE_MX_JUMP_OPCODE 62 /* Maximum JUMP opcode */ +#define SQLITE_MX_JUMP_OPCODE 64 /* Maximum JUMP opcode */ /************** End of opcodes.h *********************************************/ /************** Continuing where we left off in vdbe.h ***********************/ @@ -15961,8 +15820,10 @@ SQLITE_PRIVATE void sqlite3VdbeVerifyNoResultRow(Vdbe *p); #endif #if defined(SQLITE_DEBUG) SQLITE_PRIVATE void sqlite3VdbeVerifyAbortable(Vdbe *p, int); +SQLITE_PRIVATE void sqlite3VdbeNoJumpsOutsideSubrtn(Vdbe*,int,int,int); #else # define sqlite3VdbeVerifyAbortable(A,B) +# define sqlite3VdbeNoJumpsOutsideSubrtn(A,B,C,D) #endif SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp,int iLineno); #ifndef SQLITE_OMIT_EXPLAIN @@ -16007,7 +15868,6 @@ SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Parse*); SQLITE_PRIVATE void sqlite3VdbeRunOnlyOnce(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeReusable(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe*); -SQLITE_PRIVATE void sqlite3VdbeClearObject(sqlite3*,Vdbe*); SQLITE_PRIVATE void sqlite3VdbeMakeReady(Vdbe*,Parse*); SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe*, int); @@ -16450,6 +16310,19 @@ SQLITE_PRIVATE int sqlite3PCacheIsDirty(PCache *pCache); # define SET_FULLSYNC(x,y) #endif +/* Maximum pathname length. Note: FILENAME_MAX defined by stdio.h +*/ +#ifndef SQLITE_MAX_PATHLEN +# define SQLITE_MAX_PATHLEN FILENAME_MAX +#endif + +/* Maximum number of symlinks that will be resolved while trying to +** expand a filename in xFullPathname() in the VFS. +*/ +#ifndef SQLITE_MAX_SYMLINK +# define SQLITE_MAX_SYMLINK 200 +#endif + /* ** The default size of a disk sector */ @@ -16978,6 +16851,7 @@ struct sqlite3 { u32 nSchemaLock; /* Do not reset the schema when non-zero */ unsigned int openFlags; /* Flags passed to sqlite3_vfs.xOpen() */ int errCode; /* Most recent error code (SQLITE_*) */ + int errByteOffset; /* Byte offset of error in SQL statement */ int errMask; /* & result codes with this before returning */ int iSysErrno; /* Errno value from last system error */ u32 dbOptFlags; /* Flags to enable/disable optimizations */ @@ -16994,10 +16868,10 @@ struct sqlite3 { u8 mTrace; /* zero or more SQLITE_TRACE flags */ u8 noSharedCache; /* True if no shared-cache backends */ u8 nSqlExec; /* Number of pending OP_SqlExec opcodes */ + u8 eOpenState; /* Current condition of the connection */ int nextPagesize; /* Pagesize after VACUUM if >0 */ - u32 magic; /* Magic number for detect library misuse */ - int nChange; /* Value returned by sqlite3_changes() */ - int nTotalChange; /* Value returned by sqlite3_total_changes() */ + i64 nChange; /* Value returned by sqlite3_changes() */ + i64 nTotalChange; /* Value returned by sqlite3_total_changes() */ int aLimit[SQLITE_N_LIMIT]; /* Limits */ int nMaxSorterMmap; /* Maximum size of regions mapped by sorter */ struct sqlite3InitInfo { /* Information used during initialization */ @@ -17007,10 +16881,7 @@ struct sqlite3 { unsigned orphanTrigger : 1; /* Last statement is orphaned TEMP trigger */ unsigned imposterTable : 1; /* Building an imposter table */ unsigned reopenMemdb : 1; /* ATTACH is really a reopen using MemDB */ - unsigned bDropColumn : 1; /* Doing schema check after DROP COLUMN */ - char **azInit; /* "type", "name", and "tbl_name" columns */ - /* or if bDropColumn, then azInit[0] is the */ - /* name of the column being dropped */ + const char **azInit; /* "type", "name", and "tbl_name" columns */ } init; int nVdbeActive; /* Number of VDBEs currently running */ int nVdbeRead; /* Number of active VDBEs that read or write */ @@ -17020,10 +16891,10 @@ struct sqlite3 { int nExtension; /* Number of loaded extensions */ void **aExtension; /* Array of shared library handles */ union { - void (*xLegacy)(void*,const char*); /* Legacy trace function */ - int (*xV2)(u32,void*,void*,void*); /* V2 Trace function */ + void (*xLegacy)(void*,const char*); /* mTrace==SQLITE_TRACE_LEGACY */ + int (*xV2)(u32,void*,void*,void*); /* All other mTrace values */ } trace; - void *pTraceArg; /* Argument to the trace function */ + void *pTraceArg; /* Argument to the trace function */ #ifndef SQLITE_OMIT_DEPRECATED void (*xProfile)(void*,const char*,u64); /* Profiling function */ void *pProfileArg; /* Argument to profile function */ @@ -17034,6 +16905,9 @@ struct sqlite3 { void (*xRollbackCallback)(void*); /* Invoked at every commit. */ void *pUpdateArg; void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64); + void *pAutovacPagesArg; /* Client argument to autovac_pages */ + void (*xAutovacDestr)(void*); /* Destructor for pAutovacPAgesArg */ + unsigned int (*xAutovacPages)(void*,const char*,u32,u32,u32); Parse *pParse; /* Current parse */ #ifdef SQLITE_ENABLE_PREUPDATE_HOOK void *pPreUpdateArg; /* First argument to xPreUpdateCallback */ @@ -17163,6 +17037,7 @@ struct sqlite3 { #define SQLITE_CountRows HI(0x00001) /* Count rows changed by INSERT, */ /* DELETE, or UPDATE and return */ /* the count using a callback. */ +#define SQLITE_CorruptRdOnly HI(0x00002) /* Prohibit writes due to error */ /* Flags used only if debugging */ #ifdef SQLITE_DEBUG @@ -17208,7 +17083,15 @@ struct sqlite3 { #define SQLITE_SkipScan 0x00004000 /* Skip-scans */ #define SQLITE_PropagateConst 0x00008000 /* The constant propagation opt */ #define SQLITE_MinMaxOpt 0x00010000 /* The min/max optimization */ -#define SQLITE_ExistsToIN 0x00020000 /* The EXISTS-to-IN optimization */ +#define SQLITE_SeekScan 0x00020000 /* The OP_SeekScan optimization */ +#define SQLITE_OmitOrderBy 0x00040000 /* Omit pointless ORDER BY */ + /* TH3 expects this value ^^^^^^^^^^ to be 0x40000. Coordinate any change */ +#define SQLITE_BloomFilter 0x00080000 /* Use a Bloom filter on searches */ +#define SQLITE_BloomPulldown 0x00100000 /* Run Bloom filters early */ +#define SQLITE_BalancedMerge 0x00200000 /* Balance multi-way merges */ +#define SQLITE_ReleaseReg 0x00400000 /* Use OP_ReleaseReg for testing */ +#define SQLITE_FlttnUnionAll 0x00800000 /* Disable the UNION ALL flattener */ + /* TH3 expects this value ^^^^^^^^^^ See flatten04.test */ #define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* @@ -17223,17 +17106,16 @@ struct sqlite3 { */ #define ConstFactorOk(P) ((P)->okConstFactor) -/* -** Possible values for the sqlite.magic field. -** The numbers are obtained at random and have no special meaning, other -** than being distinct from one another. +/* Possible values for the sqlite3.eOpenState field. +** The numbers are randomly selected such that a minimum of three bits must +** change to convert any number to another or to zero */ -#define SQLITE_MAGIC_OPEN 0xa029a697 /* Database is open */ -#define SQLITE_MAGIC_CLOSED 0x9f3c2d33 /* Database is closed */ -#define SQLITE_MAGIC_SICK 0x4b771290 /* Error and awaiting close */ -#define SQLITE_MAGIC_BUSY 0xf03b7906 /* Database currently in use */ -#define SQLITE_MAGIC_ERROR 0xb5357930 /* An SQLITE_MISUSE error occurred */ -#define SQLITE_MAGIC_ZOMBIE 0x64cffc7f /* Close with last statement close */ +#define SQLITE_STATE_OPEN 0x76 /* Database is open */ +#define SQLITE_STATE_CLOSED 0xce /* Database is closed */ +#define SQLITE_STATE_SICK 0xba /* Error and awaiting close */ +#define SQLITE_STATE_BUSY 0x6d /* Database currently in use */ +#define SQLITE_STATE_ERROR 0xd5 /* An SQLITE_MISUSE error occurred */ +#define SQLITE_STATE_ZOMBIE 0xa7 /* Close with last statement close */ /* ** Each SQL function is defined by an instance of the following @@ -17258,7 +17140,7 @@ struct FuncDef { union { FuncDef *pHash; /* Next with a different name but the same hash */ FuncDestructor *pDestructor; /* Reference counted destructor function */ - } u; + } u; /* pHash if SQLITE_FUNC_BUILTIN, pDestructor otherwise */ }; /* @@ -17288,12 +17170,13 @@ struct FuncDestructor { ** are assert() statements in the code to verify this. ** ** Value constraints (enforced via assert()): -** SQLITE_FUNC_MINMAX == NC_MinMaxAgg == SF_MinMaxAgg -** SQLITE_FUNC_LENGTH == OPFLAG_LENGTHARG -** SQLITE_FUNC_TYPEOF == OPFLAG_TYPEOFARG -** SQLITE_FUNC_CONSTANT == SQLITE_DETERMINISTIC from the API -** SQLITE_FUNC_DIRECT == SQLITE_DIRECTONLY from the API -** SQLITE_FUNC_UNSAFE == SQLITE_INNOCUOUS +** SQLITE_FUNC_MINMAX == NC_MinMaxAgg == SF_MinMaxAgg +** SQLITE_FUNC_ANYORDER == NC_OrderAgg == SF_OrderByReqd +** SQLITE_FUNC_LENGTH == OPFLAG_LENGTHARG +** SQLITE_FUNC_TYPEOF == OPFLAG_TYPEOFARG +** SQLITE_FUNC_CONSTANT == SQLITE_DETERMINISTIC from the API +** SQLITE_FUNC_DIRECT == SQLITE_DIRECTONLY from the API +** SQLITE_FUNC_UNSAFE == SQLITE_INNOCUOUS ** SQLITE_FUNC_ENCMASK depends on SQLITE_UTF* macros in the API */ #define SQLITE_FUNC_ENCMASK 0x0003 /* SQLITE_UTF8, SQLITE_UTF16BE or UTF16LE */ @@ -17311,13 +17194,15 @@ struct FuncDestructor { #define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a ** single query - might change over time */ #define SQLITE_FUNC_TEST 0x4000 /* Built-in testing functions */ -#define SQLITE_FUNC_OFFSET 0x8000 /* Built-in sqlite_offset() function */ +/* 0x8000 -- available for reuse */ #define SQLITE_FUNC_WINDOW 0x00010000 /* Built-in window-only function */ #define SQLITE_FUNC_INTERNAL 0x00040000 /* For use by NestedParse() only */ #define SQLITE_FUNC_DIRECT 0x00080000 /* Not for use in TRIGGERs or VIEWs */ #define SQLITE_FUNC_SUBTYPE 0x00100000 /* Result likely to have sub-type */ #define SQLITE_FUNC_UNSAFE 0x00200000 /* Function has side effects */ #define SQLITE_FUNC_INLINE 0x00400000 /* Functions implemented in-line */ +#define SQLITE_FUNC_BUILTIN 0x00800000 /* This is a built-in function */ +#define SQLITE_FUNC_ANYORDER 0x08000000 /* count/min/max aggregate */ /* Identifier numbers for each in-line function */ #define INLINEFUNC_coalesce 0 @@ -17326,6 +17211,7 @@ struct FuncDestructor { #define INLINEFUNC_expr_compare 3 #define INLINEFUNC_affinity 4 #define INLINEFUNC_iif 5 +#define INLINEFUNC_sqlite_offset 6 #define INLINEFUNC_unlikely 99 /* Default case */ /* @@ -17380,7 +17266,7 @@ struct FuncDestructor { ** are interpreted in the same way as the first 4 parameters to ** FUNCTION(). ** -** WFUNCTION(zName, nArg, iArg, xStep, xFinal, xValue, xInverse) +** WAGGREGATE(zName, nArg, iArg, xStep, xFinal, xValue, xInverse) ** Used to create an aggregate function definition implemented by ** the C functions xStep and xFinal. The first four parameters ** are interpreted in the same way as the first 4 parameters to @@ -17395,44 +17281,55 @@ struct FuncDestructor { ** parameter. */ #define FUNCTION(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define VFUNCTION(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define SFUNCTION(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_UTF8|SQLITE_DIRECTONLY|SQLITE_FUNC_UNSAFE, \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_DIRECTONLY|SQLITE_FUNC_UNSAFE, \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define MFUNCTION(zName, nArg, xPtr, xFunc) \ - {nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ xPtr, 0, xFunc, 0, 0, 0, #zName, {0} } +#define JFUNCTION(zName, nArg, iArg, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|\ + SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ + SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define INLINE_FUNC(zName, nArg, iArg, mFlags) \ - {nArg, SQLITE_UTF8|SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_UTF8|SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \ SQLITE_INT_TO_PTR(iArg), 0, noopFunc, 0, 0, 0, #zName, {0} } #define TEST_FUNC(zName, nArg, iArg, mFlags) \ - {nArg, SQLITE_UTF8|SQLITE_FUNC_INTERNAL|SQLITE_FUNC_TEST| \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_UTF8|SQLITE_FUNC_INTERNAL|SQLITE_FUNC_TEST| \ SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \ SQLITE_INT_TO_PTR(iArg), 0, noopFunc, 0, 0, 0, #zName, {0} } #define DFUNCTION(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8, \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_SLOCHNG|SQLITE_UTF8, \ 0, 0, xFunc, 0, 0, 0, #zName, {0} } #define PURE_DATE(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \ (void*)&sqlite3Config, 0, xFunc, 0, 0, 0, #zName, {0} } #define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags) \ - {nArg,SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags,\ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags,\ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \ - {nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ pArg, 0, xFunc, 0, 0, 0, #zName, } #define LIKEFUNC(zName, nArg, arg, flags) \ - {nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|flags, \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8|flags, \ (void *)arg, 0, likeFunc, 0, 0, 0, #zName, {0} } #define WAGGREGATE(zName, nArg, arg, nc, xStep, xFinal, xValue, xInverse, f) \ - {nArg, SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL)|f, \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL)|f, \ SQLITE_INT_TO_PTR(arg), 0, xStep,xFinal,xValue,xInverse,#zName, {0}} #define INTERNAL_FUNCTION(zName, nArg, xFunc) \ - {nArg, SQLITE_FUNC_INTERNAL|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_INTERNAL|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \ 0, 0, xFunc, 0, 0, 0, #zName, {0} } @@ -17488,18 +17385,42 @@ struct Module { ** or equal to the table column index. It is ** equal if and only if there are no VIRTUAL ** columns to the left. +** +** Notes on zCnName: +** The zCnName field stores the name of the column, the datatype of the +** column, and the collating sequence for the column, in that order, all in +** a single allocation. Each string is 0x00 terminated. The datatype +** is only included if the COLFLAG_HASTYPE bit of colFlags is set and the +** collating sequence name is only included if the COLFLAG_HASCOLL bit is +** set. */ struct Column { - char *zName; /* Name of this column, \000, then the type */ - Expr *pDflt; /* Default value or GENERATED ALWAYS AS value */ - char *zColl; /* Collating sequence. If NULL, use the default */ - u8 notNull; /* An OE_ code for handling a NOT NULL constraint */ - char affinity; /* One of the SQLITE_AFF_... values */ - u8 szEst; /* Estimated size of value in this column. sizeof(INT)==1 */ - u8 hName; /* Column name hash for faster lookup */ - u16 colFlags; /* Boolean properties. See COLFLAG_ defines below */ + char *zCnName; /* Name of this column */ + unsigned notNull :4; /* An OE_ code for handling a NOT NULL constraint */ + unsigned eCType :4; /* One of the standard types */ + char affinity; /* One of the SQLITE_AFF_... values */ + u8 szEst; /* Est size of value in this column. sizeof(INT)==1 */ + u8 hName; /* Column name hash for faster lookup */ + u16 iDflt; /* 1-based index of DEFAULT. 0 means "none" */ + u16 colFlags; /* Boolean properties. See COLFLAG_ defines below */ }; +/* Allowed values for Column.eCType. +** +** Values must match entries in the global constant arrays +** sqlite3StdTypeLen[] and sqlite3StdType[]. Each value is one more +** than the offset into these arrays for the corresponding name. +** Adjust the SQLITE_N_STDTYPE value if adding or removing entries. +*/ +#define COLTYPE_CUSTOM 0 /* Type appended to zName */ +#define COLTYPE_ANY 1 +#define COLTYPE_BLOB 2 +#define COLTYPE_INT 3 +#define COLTYPE_INTEGER 4 +#define COLTYPE_REAL 5 +#define COLTYPE_TEXT 6 +#define SQLITE_N_STDTYPE 6 /* Number of standard types */ + /* Allowed values for Column.colFlags. ** ** Constraints: @@ -17516,6 +17437,8 @@ struct Column { #define COLFLAG_STORED 0x0040 /* GENERATED ALWAYS AS ... STORED */ #define COLFLAG_NOTAVAIL 0x0080 /* STORED column not yet calculated */ #define COLFLAG_BUSY 0x0100 /* Blocks recursion on GENERATED columns */ +#define COLFLAG_HASCOLL 0x0200 /* Has collating sequence name in zCnName */ +#define COLFLAG_NOEXPAND 0x0400 /* Omit this column when expanding "*" */ #define COLFLAG_GENERATED 0x0060 /* Combo: _STORED, _VIRTUAL */ #define COLFLAG_NOINSERT 0x0062 /* Combo: _HIDDEN, _STORED, _VIRTUAL */ @@ -17581,9 +17504,7 @@ struct CollSeq { ** operator is NULL. It is added to certain comparison operators to ** prove that the operands are always NOT NULL. */ -#define SQLITE_KEEPNULL 0x08 /* Used by vector == or <> */ #define SQLITE_JUMPIFNULL 0x10 /* jumps if either operand is NULL */ -#define SQLITE_STOREP2 0x20 /* Store result in reg[P2] rather than jump */ #define SQLITE_NULLEQ 0x80 /* NULL=NULL */ #define SQLITE_NOTNULL 0x90 /* Assert that operands are never NULL */ @@ -17647,15 +17568,13 @@ struct VTable { #define SQLITE_VTABRISK_High 2 /* -** The schema for each SQL table and view is represented in memory -** by an instance of the following structure. +** The schema for each SQL table, virtual table, and view is represented +** in memory by an instance of the following structure. */ struct Table { char *zName; /* Name of the table or view */ Column *aCol; /* Information about each column */ Index *pIndex; /* List of SQL indexes on this table. */ - Select *pSelect; /* NULL for tables. Points to definition if a view. */ - FKey *pFKey; /* Linked list of all foreign keys in this table */ char *zColAff; /* String defining the affinity of each column */ ExprList *pCheck; /* All CHECK constraints */ /* ... also used as column name list in a VIEW */ @@ -17671,15 +17590,24 @@ struct Table { LogEst costMult; /* Cost multiplier for using this table */ #endif u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */ -#ifndef SQLITE_OMIT_ALTERTABLE - int addColOffset; /* Offset in CREATE TABLE stmt to add a new column */ -#endif -#ifndef SQLITE_OMIT_VIRTUALTABLE - int nModuleArg; /* Number of arguments to the module */ - char **azModuleArg; /* 0: module 1: schema 2: vtab name 3...: args */ - VTable *pVTable; /* List of VTable objects. */ -#endif - Trigger *pTrigger; /* List of triggers stored in pSchema */ + u8 eTabType; /* 0: normal, 1: virtual, 2: view */ + union { + struct { /* Used by ordinary tables: */ + int addColOffset; /* Offset in CREATE TABLE stmt to add a new column */ + FKey *pFKey; /* Linked list of all foreign keys in this table */ + ExprList *pDfltList; /* DEFAULT clauses on various columns. + ** Or the AS clause for generated columns. */ + } tab; + struct { /* Used by views: */ + Select *pSelect; /* View definition */ + } view; + struct { /* Used by virtual tables only: */ + int nArg; /* Number of arguments to the module */ + char **azArg; /* 0: module 1: schema 2: vtab name 3...: args */ + VTable *p; /* List of VTable objects. */ + } vtab; + } u; + Trigger *pTrigger; /* List of triggers on this object */ Schema *pSchema; /* Schema that contains this table */ }; @@ -17698,23 +17626,35 @@ struct Table { ** TF_HasStored == COLFLAG_STORED ** TF_HasHidden == COLFLAG_HIDDEN */ -#define TF_Readonly 0x0001 /* Read-only system table */ -#define TF_HasHidden 0x0002 /* Has one or more hidden columns */ -#define TF_HasPrimaryKey 0x0004 /* Table has a primary key */ -#define TF_Autoincrement 0x0008 /* Integer primary key is autoincrement */ -#define TF_HasStat1 0x0010 /* nRowLogEst set from sqlite_stat1 */ -#define TF_HasVirtual 0x0020 /* Has one or more VIRTUAL columns */ -#define TF_HasStored 0x0040 /* Has one or more STORED columns */ -#define TF_HasGenerated 0x0060 /* Combo: HasVirtual + HasStored */ -#define TF_WithoutRowid 0x0080 /* No rowid. PRIMARY KEY is the key */ -#define TF_StatsUsed 0x0100 /* Query planner decisions affected by +#define TF_Readonly 0x00000001 /* Read-only system table */ +#define TF_HasHidden 0x00000002 /* Has one or more hidden columns */ +#define TF_HasPrimaryKey 0x00000004 /* Table has a primary key */ +#define TF_Autoincrement 0x00000008 /* Integer primary key is autoincrement */ +#define TF_HasStat1 0x00000010 /* nRowLogEst set from sqlite_stat1 */ +#define TF_HasVirtual 0x00000020 /* Has one or more VIRTUAL columns */ +#define TF_HasStored 0x00000040 /* Has one or more STORED columns */ +#define TF_HasGenerated 0x00000060 /* Combo: HasVirtual + HasStored */ +#define TF_WithoutRowid 0x00000080 /* No rowid. PRIMARY KEY is the key */ +#define TF_StatsUsed 0x00000100 /* Query planner decisions affected by ** Index.aiRowLogEst[] values */ -#define TF_NoVisibleRowid 0x0200 /* No user-visible "rowid" column */ -#define TF_OOOHidden 0x0400 /* Out-of-Order hidden columns */ -#define TF_HasNotNull 0x0800 /* Contains NOT NULL constraints */ -#define TF_Shadow 0x1000 /* True for a shadow table */ -#define TF_HasStat4 0x2000 /* STAT4 info available for this table */ -#define TF_Ephemeral 0x4000 /* An ephemeral table */ +#define TF_NoVisibleRowid 0x00000200 /* No user-visible "rowid" column */ +#define TF_OOOHidden 0x00000400 /* Out-of-Order hidden columns */ +#define TF_HasNotNull 0x00000800 /* Contains NOT NULL constraints */ +#define TF_Shadow 0x00001000 /* True for a shadow table */ +#define TF_HasStat4 0x00002000 /* STAT4 info available for this table */ +#define TF_Ephemeral 0x00004000 /* An ephemeral table */ +#define TF_Eponymous 0x00008000 /* An eponymous virtual table */ +#define TF_Strict 0x00010000 /* STRICT mode */ + +/* +** Allowed values for Table.eTabType +*/ +#define TABTYP_NORM 0 /* Ordinary table */ +#define TABTYP_VTAB 1 /* Virtual table */ +#define TABTYP_VIEW 2 /* A view */ + +#define IsView(X) ((X)->eTabType==TABTYP_VIEW) +#define IsOrdinaryTable(X) ((X)->eTabType==TABTYP_NORM) /* ** Test to see whether or not a table is a virtual table. This is @@ -17722,9 +17662,9 @@ struct Table { ** table support is omitted from the build. */ #ifndef SQLITE_OMIT_VIRTUALTABLE -# define IsVirtual(X) ((X)->nModuleArg) +# define IsVirtual(X) ((X)->eTabType==TABTYP_VTAB) # define ExprIsVtab(X) \ - ((X)->op==TK_COLUMN && (X)->y.pTab!=0 && (X)->y.pTab->nModuleArg) + ((X)->op==TK_COLUMN && (X)->y.pTab!=0 && (X)->y.pTab->eTabType==TABTYP_VTAB) #else # define IsVirtual(X) 0 # define ExprIsVtab(X) 0 @@ -17905,6 +17845,11 @@ struct KeyInfo { struct UnpackedRecord { KeyInfo *pKeyInfo; /* Collation and sort-order information */ Mem *aMem; /* Values */ + union { + char *z; /* Cache of aMem[0].z for vdbeRecordCompareString() */ + i64 i; /* Cache of aMem[0].u.i for vdbeRecordCompareInt() */ + } u; + int n; /* Cache of aMem[0].n used by vdbeRecordCompareString() */ u16 nField; /* Number of entries in apMem[] */ i8 default_rc; /* Comparison result if keys are equal */ u8 errCode; /* Error detected by xRecordCompare (CORRUPT or NOMEM) */ @@ -18080,6 +18025,7 @@ struct AggInfo { FuncDef *pFunc; /* The aggregate function implementation */ int iMem; /* Memory location that acts as accumulator */ int iDistinct; /* Ephemeral table used to enforce DISTINCT */ + int iDistAddr; /* Address of OP_OpenEphemeral */ } *aFunc; int nFunc; /* Number of entries in aFunc[] */ u32 selId; /* Select to which this AggInfo belongs */ @@ -18112,10 +18058,10 @@ typedef int ynVar; ** tree. ** ** If the expression is an SQL literal (TK_INTEGER, TK_FLOAT, TK_BLOB, -** or TK_STRING), then Expr.token contains the text of the SQL literal. If -** the expression is a variable (TK_VARIABLE), then Expr.token contains the +** or TK_STRING), then Expr.u.zToken contains the text of the SQL literal. If +** the expression is a variable (TK_VARIABLE), then Expr.u.zToken contains the ** variable name. Finally, if the expression is an SQL function (TK_FUNCTION), -** then Expr.token contains the name of the function. +** then Expr.u.zToken contains the name of the function. ** ** Expr.pRight and Expr.pLeft are the left and right subexpressions of a ** binary operator. Either or both may be NULL. @@ -18155,7 +18101,7 @@ typedef int ynVar; ** help reduce memory requirements, sometimes an Expr object will be ** truncated. And to reduce the number of memory allocations, sometimes ** two or more Expr objects will be stored in a single memory allocation, -** together with Expr.zToken strings. +** together with Expr.u.zToken strings. ** ** If the EP_Reduced and EP_TokenOnly flags are set when ** an Expr object is truncated. When EP_Reduced is set, then all @@ -18211,7 +18157,10 @@ struct Expr { ** TK_VARIABLE: variable number (always >= 1). ** TK_SELECT_COLUMN: column of the result vector */ i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */ - int iRightJoinTable; /* If EP_FromJoin, the right table of the join */ + union { + int iJoin; /* If EP_OuterON or EP_InnerON, the right table */ + int iOfst; /* else: start of token from start of statement */ + } w; AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */ union { Table *pTab; /* TK_COLUMN: Table containing column. Can be NULL @@ -18224,36 +18173,35 @@ struct Expr { } y; }; -/* -** The following are the meanings of bits in the Expr.flags field. +/* The following are the meanings of bits in the Expr.flags field. ** Value restrictions: ** ** EP_Agg == NC_HasAgg == SF_HasAgg ** EP_Win == NC_HasWin */ -#define EP_FromJoin 0x000001 /* Originates in ON/USING clause of outer join */ -#define EP_Distinct 0x000002 /* Aggregate function with DISTINCT keyword */ -#define EP_HasFunc 0x000004 /* Contains one or more functions of any kind */ -#define EP_FixedCol 0x000008 /* TK_Column with a known fixed value */ +#define EP_OuterON 0x000001 /* Originates in ON/USING clause of outer join */ +#define EP_InnerON 0x000002 /* Originates in ON/USING of an inner join */ +#define EP_Distinct 0x000004 /* Aggregate function with DISTINCT keyword */ +#define EP_HasFunc 0x000008 /* Contains one or more functions of any kind */ #define EP_Agg 0x000010 /* Contains one or more aggregate functions */ -#define EP_VarSelect 0x000020 /* pSelect is correlated, not constant */ -#define EP_DblQuoted 0x000040 /* token.z was originally in "..." */ -#define EP_InfixFunc 0x000080 /* True for an infix function: LIKE, GLOB, etc */ -#define EP_Collate 0x000100 /* Tree contains a TK_COLLATE operator */ -#define EP_Commuted 0x000200 /* Comparison operator has been commuted */ -#define EP_IntValue 0x000400 /* Integer value contained in u.iValue */ -#define EP_xIsSelect 0x000800 /* x.pSelect is valid (otherwise x.pList is) */ -#define EP_Skip 0x001000 /* Operator does not contribute to affinity */ -#define EP_Reduced 0x002000 /* Expr struct EXPR_REDUCEDSIZE bytes only */ -#define EP_TokenOnly 0x004000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */ +#define EP_FixedCol 0x000020 /* TK_Column with a known fixed value */ +#define EP_VarSelect 0x000040 /* pSelect is correlated, not constant */ +#define EP_DblQuoted 0x000080 /* token.z was originally in "..." */ +#define EP_InfixFunc 0x000100 /* True for an infix function: LIKE, GLOB, etc */ +#define EP_Collate 0x000200 /* Tree contains a TK_COLLATE operator */ +#define EP_Commuted 0x000400 /* Comparison operator has been commuted */ +#define EP_IntValue 0x000800 /* Integer value contained in u.iValue */ +#define EP_xIsSelect 0x001000 /* x.pSelect is valid (otherwise x.pList is) */ +#define EP_Skip 0x002000 /* Operator does not contribute to affinity */ +#define EP_Reduced 0x004000 /* Expr struct EXPR_REDUCEDSIZE bytes only */ #define EP_Win 0x008000 /* Contains window functions */ -#define EP_MemToken 0x010000 /* Need to sqlite3DbFree() Expr.zToken */ -#define EP_IfNullRow 0x020000 /* The TK_IF_NULL_ROW opcode */ -#define EP_Unlikely 0x040000 /* unlikely() or likelihood() function */ -#define EP_ConstFunc 0x080000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */ -#define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */ -#define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */ - /* 0x400000 // Available */ +#define EP_TokenOnly 0x010000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */ +#define EP_MemToken 0x020000 /* Need to sqlite3DbFree() Expr.zToken */ +#define EP_IfNullRow 0x040000 /* The TK_IF_NULL_ROW opcode */ +#define EP_Unlikely 0x080000 /* unlikely() or likelihood() function */ +#define EP_ConstFunc 0x100000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */ +#define EP_CanBeNull 0x200000 /* Can be null despite NOT NULL constraint */ +#define EP_Subquery 0x400000 /* Tree contains a TK_SELECT operator */ #define EP_Leaf 0x800000 /* Expr.pLeft, .pRight, .u.pSelect all NULL */ #define EP_WinFunc 0x1000000 /* TK_FUNCTION with Expr.y.pWin set */ #define EP_Subrtn 0x2000000 /* Uses Expr.y.sub. TK_IN, _SELECT, or _EXISTS */ @@ -18264,23 +18212,31 @@ struct Expr { #define EP_FromDDL 0x40000000 /* Originates from sqlite_schema */ /* 0x80000000 // Available */ -/* -** The EP_Propagate mask is a set of properties that automatically propagate +/* The EP_Propagate mask is a set of properties that automatically propagate ** upwards into parent nodes. */ #define EP_Propagate (EP_Collate|EP_Subquery|EP_HasFunc) -/* -** These macros can be used to test, set, or clear bits in the +/* Macros can be used to test, set, or clear bits in the ** Expr.flags field. */ #define ExprHasProperty(E,P) (((E)->flags&(P))!=0) #define ExprHasAllProperty(E,P) (((E)->flags&(P))==(P)) #define ExprSetProperty(E,P) (E)->flags|=(P) #define ExprClearProperty(E,P) (E)->flags&=~(P) -#define ExprAlwaysTrue(E) (((E)->flags&(EP_FromJoin|EP_IsTrue))==EP_IsTrue) -#define ExprAlwaysFalse(E) (((E)->flags&(EP_FromJoin|EP_IsFalse))==EP_IsFalse) +#define ExprAlwaysTrue(E) (((E)->flags&(EP_OuterON|EP_IsTrue))==EP_IsTrue) +#define ExprAlwaysFalse(E) (((E)->flags&(EP_OuterON|EP_IsFalse))==EP_IsFalse) +/* Macros used to ensure that the correct members of unions are accessed +** in Expr. +*/ +#define ExprUseUToken(E) (((E)->flags&EP_IntValue)==0) +#define ExprUseUValue(E) (((E)->flags&EP_IntValue)!=0) +#define ExprUseXList(E) (((E)->flags&EP_xIsSelect)==0) +#define ExprUseXSelect(E) (((E)->flags&EP_xIsSelect)!=0) +#define ExprUseYTab(E) (((E)->flags&(EP_WinFunc|EP_Subrtn))==0) +#define ExprUseYWin(E) (((E)->flags&EP_WinFunc)!=0) +#define ExprUseYSub(E) (((E)->flags&EP_Subrtn)!=0) /* Flags for use with Expr.vvaFlags */ @@ -18352,21 +18308,29 @@ struct Expr { */ struct ExprList { int nExpr; /* Number of expressions on the list */ + int nAlloc; /* Number of a[] slots allocated */ struct ExprList_item { /* For each expression in the list */ Expr *pExpr; /* The parse tree for this expression */ char *zEName; /* Token associated with this expression */ - u8 sortFlags; /* Mask of KEYINFO_ORDER_* flags */ - unsigned eEName :2; /* Meaning of zEName */ - unsigned done :1; /* A flag to indicate when processing is finished */ - unsigned reusable :1; /* Constant expression is reusable */ - unsigned bSorterRef :1; /* Defer evaluation until after sorting */ - unsigned bNulls: 1; /* True if explicit "NULLS FIRST/LAST" */ + struct { + u8 sortFlags; /* Mask of KEYINFO_ORDER_* flags */ + unsigned eEName :2; /* Meaning of zEName */ + unsigned done :1; /* Indicates when processing is finished */ + unsigned reusable :1; /* Constant expression is reusable */ + unsigned bSorterRef :1; /* Defer evaluation until after sorting */ + unsigned bNulls :1; /* True if explicit "NULLS FIRST/LAST" */ + unsigned bUsed :1; /* This column used in a SF_NestedFrom subquery */ + unsigned bUsingTerm:1; /* Term from the USING clause of a NestedFrom */ + unsigned bNoExpand: 1; /* Term is an auxiliary in NestedFrom and should + ** not be expanded by "*" in parent queries */ + } fg; union { - struct { + struct { /* Used by any ExprList other than Parse.pConsExpr */ u16 iOrderByCol; /* For ORDER BY, column number in result set */ u16 iAlias; /* Index into Parse.aAlias[] for zName */ } x; - int iConstExprReg; /* Register in which Expr value is cached */ + int iConstExprReg; /* Register in which Expr value is cached. Used only + ** by Parse.pConstExpr */ } u; } a[1]; /* One slot for each expression in the list */ }; @@ -18394,16 +18358,35 @@ struct ExprList { ** If "a" is the k-th column of table "t", then IdList.a[0].idx==k. */ struct IdList { + int nId; /* Number of identifiers on the list */ + u8 eU4; /* Which element of a.u4 is valid */ struct IdList_item { char *zName; /* Name of the identifier */ - int idx; /* Index in some Table.aCol[] of a column named zName */ - } *a; - int nId; /* Number of identifiers on the list */ + union { + int idx; /* Index in some Table.aCol[] of a column named zName */ + Expr *pExpr; /* Expr to implement a USING variable -- NOT USED */ + } u4; + } a[1]; }; +/* +** Allowed values for IdList.eType, which determines which value of the a.u4 +** is valid. +*/ +#define EU4_NONE 0 /* Does not use IdList.a.u4 */ +#define EU4_IDX 1 /* Uses IdList.a.u4.idx */ +#define EU4_EXPR 2 /* Uses IdList.a.u4.pExpr -- NOT CURRENTLY USED */ + /* ** The SrcItem object represents a single term in the FROM clause of a query. ** The SrcList object is mostly an array of SrcItems. +** +** Union member validity: +** +** u1.zIndexedBy fg.isIndexedBy && !fg.isTabFunc +** u1.pFuncArg fg.isTabFunc && !fg.isIndexedBy +** u2.pIBIndex fg.isIndexedBy && !fg.isCte +** u2.pCteUse fg.isCte && !fg.isIndexedBy */ struct SrcItem { Schema *pSchema; /* Schema to which this item is fixed */ @@ -18421,14 +18404,22 @@ struct SrcItem { unsigned isIndexedBy :1; /* True if there is an INDEXED BY clause */ unsigned isTabFunc :1; /* True if table-valued-function syntax */ unsigned isCorrelated :1; /* True if sub-query is correlated */ + unsigned isMaterialized:1; /* This is a materialized view */ unsigned viaCoroutine :1; /* Implemented as a co-routine */ unsigned isRecursive :1; /* True for recursive reference in WITH */ unsigned fromDDL :1; /* Comes from sqlite_schema */ unsigned isCte :1; /* This is a CTE */ + unsigned notCte :1; /* This item may not match a CTE */ + unsigned isUsing :1; /* u3.pUsing is valid */ + unsigned isOn :1; /* u3.pOn was once valid and non-NULL */ + unsigned isSynthUsing :1; /* u3.pUsing is synthensized from NATURAL */ + unsigned isNestedFrom :1; /* pSelect is a SF_NestedFrom subquery */ } fg; int iCursor; /* The VDBE cursor number used to access this table */ - Expr *pOn; /* The ON clause of a join */ - IdList *pUsing; /* The USING clause of a join */ + union { + Expr *pOn; /* fg.isUsing==0 => The ON clause of a join */ + IdList *pUsing; /* fg.isUsing==1 => The USING clause of a join */ + } u3; Bitmask colUsed; /* Bit N (1<" clause */ @@ -18440,6 +18431,15 @@ struct SrcItem { } u2; }; +/* +** The OnOrUsing object represents either an ON clause or a USING clause. +** It can never be both at the same time, but it can be neither. +*/ +struct OnOrUsing { + Expr *pOn; /* The ON clause of a join */ + IdList *pUsing; /* The USING clause of a join */ +}; + /* ** The following structure describes the FROM clause of a SELECT statement. ** Each table or subquery in the FROM clause is a separate element of @@ -18468,14 +18468,15 @@ struct SrcList { /* ** Permitted values of the SrcList.a.jointype field */ -#define JT_INNER 0x0001 /* Any kind of inner or cross join */ -#define JT_CROSS 0x0002 /* Explicit use of the CROSS keyword */ -#define JT_NATURAL 0x0004 /* True for a "natural" join */ -#define JT_LEFT 0x0008 /* Left outer join */ -#define JT_RIGHT 0x0010 /* Right outer join */ -#define JT_OUTER 0x0020 /* The "OUTER" keyword is present */ -#define JT_ERROR 0x0040 /* unknown or unsupported join type */ - +#define JT_INNER 0x01 /* Any kind of inner or cross join */ +#define JT_CROSS 0x02 /* Explicit use of the CROSS keyword */ +#define JT_NATURAL 0x04 /* True for a "natural" join */ +#define JT_LEFT 0x08 /* Left outer join */ +#define JT_RIGHT 0x10 /* Right outer join */ +#define JT_OUTER 0x20 /* The "OUTER" keyword is present */ +#define JT_LTORJ 0x40 /* One of the LEFT operands of a RIGHT JOIN + ** Mnemonic: Left Table Of Right Join */ +#define JT_ERROR 0x80 /* unknown or unsupported join type */ /* ** Flags appropriate for the wctrlFlags parameter of sqlite3WhereBegin() @@ -18496,9 +18497,9 @@ struct SrcList { #define WHERE_DISTINCTBY 0x0080 /* pOrderby is really a DISTINCT clause */ #define WHERE_WANT_DISTINCT 0x0100 /* All output needs to be distinct */ #define WHERE_SORTBYGROUP 0x0200 /* Support sqlite3WhereIsSorted() */ - /* 0x0400 not currently used */ +#define WHERE_AGG_DISTINCT 0x0400 /* Query is "SELECT agg(DISTINCT ...)" */ #define WHERE_ORDERBY_LIMIT 0x0800 /* ORDERBY+LIMIT on the inner loop */ - /* 0x1000 not currently used */ +#define WHERE_RIGHT_JOIN 0x1000 /* Processing a RIGHT JOIN */ /* 0x2000 not currently used */ #define WHERE_USE_LIMIT 0x4000 /* Use the LIMIT in cost estimates */ /* 0x8000 not currently used */ @@ -18542,7 +18543,7 @@ struct NameContext { } uNC; NameContext *pNext; /* Next outer name context. NULL for outermost */ int nRef; /* Number of names resolved by this context */ - int nErr; /* Number of errors encountered while resolving names */ + int nNcErr; /* Number of errors encountered while resolving names */ int ncFlags; /* Zero or more NC_* flags defined below */ Select *pWinSelect; /* SELECT statement for any window functions */ }; @@ -18551,30 +18552,33 @@ struct NameContext { ** Allowed values for the NameContext, ncFlags field. ** ** Value constraints (all checked via assert()): -** NC_HasAgg == SF_HasAgg == EP_Agg -** NC_MinMaxAgg == SF_MinMaxAgg == SQLITE_FUNC_MINMAX +** NC_HasAgg == SF_HasAgg == EP_Agg +** NC_MinMaxAgg == SF_MinMaxAgg == SQLITE_FUNC_MINMAX +** NC_OrderAgg == SF_OrderByReqd == SQLITE_FUNC_ANYORDER ** NC_HasWin == EP_Win ** */ -#define NC_AllowAgg 0x00001 /* Aggregate functions are allowed here */ -#define NC_PartIdx 0x00002 /* True if resolving a partial index WHERE */ -#define NC_IsCheck 0x00004 /* True if resolving a CHECK constraint */ -#define NC_GenCol 0x00008 /* True for a GENERATED ALWAYS AS clause */ -#define NC_HasAgg 0x00010 /* One or more aggregate functions seen */ -#define NC_IdxExpr 0x00020 /* True if resolving columns of CREATE INDEX */ -#define NC_SelfRef 0x0002e /* Combo: PartIdx, isCheck, GenCol, and IdxExpr */ -#define NC_VarSelect 0x00040 /* A correlated subquery has been seen */ -#define NC_UEList 0x00080 /* True if uNC.pEList is used */ -#define NC_UAggInfo 0x00100 /* True if uNC.pAggInfo is used */ -#define NC_UUpsert 0x00200 /* True if uNC.pUpsert is used */ -#define NC_UBaseReg 0x00400 /* True if uNC.iBaseReg is used */ -#define NC_MinMaxAgg 0x01000 /* min/max aggregates seen. See note above */ -#define NC_Complex 0x02000 /* True if a function or subquery seen */ -#define NC_AllowWin 0x04000 /* Window functions are allowed here */ -#define NC_HasWin 0x08000 /* One or more window functions seen */ -#define NC_IsDDL 0x10000 /* Resolving names in a CREATE statement */ -#define NC_InAggFunc 0x20000 /* True if analyzing arguments to an agg func */ -#define NC_FromDDL 0x40000 /* SQL text comes from sqlite_schema */ +#define NC_AllowAgg 0x000001 /* Aggregate functions are allowed here */ +#define NC_PartIdx 0x000002 /* True if resolving a partial index WHERE */ +#define NC_IsCheck 0x000004 /* True if resolving a CHECK constraint */ +#define NC_GenCol 0x000008 /* True for a GENERATED ALWAYS AS clause */ +#define NC_HasAgg 0x000010 /* One or more aggregate functions seen */ +#define NC_IdxExpr 0x000020 /* True if resolving columns of CREATE INDEX */ +#define NC_SelfRef 0x00002e /* Combo: PartIdx, isCheck, GenCol, and IdxExpr */ +#define NC_VarSelect 0x000040 /* A correlated subquery has been seen */ +#define NC_UEList 0x000080 /* True if uNC.pEList is used */ +#define NC_UAggInfo 0x000100 /* True if uNC.pAggInfo is used */ +#define NC_UUpsert 0x000200 /* True if uNC.pUpsert is used */ +#define NC_UBaseReg 0x000400 /* True if uNC.iBaseReg is used */ +#define NC_MinMaxAgg 0x001000 /* min/max aggregates seen. See note above */ +#define NC_Complex 0x002000 /* True if a function or subquery seen */ +#define NC_AllowWin 0x004000 /* Window functions are allowed here */ +#define NC_HasWin 0x008000 /* One or more window functions seen */ +#define NC_IsDDL 0x010000 /* Resolving names in a CREATE statement */ +#define NC_InAggFunc 0x020000 /* True if analyzing arguments to an agg func */ +#define NC_FromDDL 0x040000 /* SQL text comes from sqlite_schema */ +#define NC_NoSelect 0x080000 /* Do not descend into sub-selects */ +#define NC_OrderAgg 0x8000000 /* Has an aggregate other than count/min/max */ /* ** An instance of the following object describes a single ON CONFLICT @@ -18657,9 +18661,10 @@ struct Select { ** "Select Flag". ** ** Value constraints (all checked via assert()) -** SF_HasAgg == NC_HasAgg -** SF_MinMaxAgg == NC_MinMaxAgg == SQLITE_FUNC_MINMAX -** SF_FixedLimit == WHERE_USE_LIMIT +** SF_HasAgg == NC_HasAgg +** SF_MinMaxAgg == NC_MinMaxAgg == SQLITE_FUNC_MINMAX +** SF_OrderByReqd == NC_OrderAgg == SQLITE_FUNC_ANYORDER +** SF_FixedLimit == WHERE_USE_LIMIT */ #define SF_Distinct 0x0000001 /* Output should be DISTINCT */ #define SF_All 0x0000002 /* Includes the ALL keyword */ @@ -18684,9 +18689,14 @@ struct Select { #define SF_WinRewrite 0x0100000 /* Window function rewrite accomplished */ #define SF_View 0x0200000 /* SELECT statement is a view */ #define SF_NoopOrderBy 0x0400000 /* ORDER BY is ignored for this query */ -#define SF_UpdateFrom 0x0800000 /* Statement is an UPDATE...FROM */ +#define SF_UFSrcCheck 0x0800000 /* Check pSrc as required by UPDATE...FROM */ #define SF_PushDown 0x1000000 /* SELECT has be modified by push-down opt */ #define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ +#define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */ +#define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */ + +/* True if S exists and has SF_NestedFrom */ +#define IsNestedFrom(S) ((S)!=0 && ((S)->selFlags&SF_NestedFrom)!=0) /* ** The results of a SELECT can be distributed in several ways, as defined @@ -18899,6 +18909,7 @@ struct Parse { u8 okConstFactor; /* OK to factor out constants */ u8 disableLookaside; /* Number of times lookaside has been disabled */ u8 disableVtab; /* Disable all virtual tables for this parse */ + u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */ #if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */ #endif @@ -18928,7 +18939,8 @@ struct Parse { AutoincInfo *pAinc; /* Information about AUTOINCREMENT counters */ Parse *pToplevel; /* Parse structure for main program (or NULL) */ Table *pTriggerTab; /* Table triggers are being coded for */ - Parse *pParentParse; /* Parent parser if this parser is nested */ + TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */ + ParseCleanup *pCleanup; /* List of cleanup operations to run after parse */ union { int addrCrTab; /* Address of OP_CreateBtree on CREATE TABLE */ Returning *pReturning; /* The RETURNING clause */ @@ -18949,6 +18961,7 @@ struct Parse { **************************************************************************/ int aTempReg[8]; /* Holding area for temporary registers */ + Parse *pOuterParse; /* Outer Parse object when nested */ Token sNameToken; /* Token with unqualified schema object name */ /************************************************************************ @@ -18983,14 +18996,14 @@ struct Parse { Token sArg; /* Complete text of a module argument */ Table **apVtabLock; /* Pointer to virtual tables needing locking */ #endif - TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */ With *pWith; /* Current WITH clause, or NULL */ - ParseCleanup *pCleanup; /* List of cleanup operations to run after parse */ #ifndef SQLITE_OMIT_ALTERTABLE RenameToken *pRename; /* Tokens subject to renaming by ALTER TABLE */ #endif }; +/* Allowed values for Parse.eParseMode +*/ #define PARSE_MODE_NORMAL 0 #define PARSE_MODE_DECLARE_VTAB 1 #define PARSE_MODE_RENAME 2 @@ -18999,7 +19012,8 @@ struct Parse { /* ** Sizes and pointers of various parts of the Parse object. */ -#define PARSE_HDR_SZ offsetof(Parse,aTempReg) /* Recursive part w/o aColCache*/ +#define PARSE_HDR(X) (((char*)(X))+offsetof(Parse,zErrMsg)) +#define PARSE_HDR_SZ (offsetof(Parse,aTempReg)-offsetof(Parse,zErrMsg)) /* Recursive part w/o aColCache*/ #define PARSE_RECURSE_SZ offsetof(Parse,sLastToken) /* Recursive part */ #define PARSE_TAIL_SZ (sizeof(Parse)-PARSE_RECURSE_SZ) /* Non-recursive part */ #define PARSE_TAIL(X) (((char*)(X))+PARSE_RECURSE_SZ) /* Pointer to tail */ @@ -19068,20 +19082,20 @@ struct AuthContext { #define OPFLAG_PREFORMAT 0x80 /* OP_Insert uses preformatted cell */ /* - * Each trigger present in the database schema is stored as an instance of - * struct Trigger. - * - * Pointers to instances of struct Trigger are stored in two ways. - * 1. In the "trigHash" hash table (part of the sqlite3* that represents the - * database). This allows Trigger structures to be retrieved by name. - * 2. All triggers associated with a single table form a linked list, using the - * pNext member of struct Trigger. A pointer to the first element of the - * linked list is stored as the "pTrigger" member of the associated - * struct Table. - * - * The "step_list" member points to the first element of a linked list - * containing the SQL statements specified as the trigger program. - */ +** Each trigger present in the database schema is stored as an instance of +** struct Trigger. +** +** Pointers to instances of struct Trigger are stored in two ways. +** 1. In the "trigHash" hash table (part of the sqlite3* that represents the +** database). This allows Trigger structures to be retrieved by name. +** 2. All triggers associated with a single table form a linked list, using the +** pNext member of struct Trigger. A pointer to the first element of the +** linked list is stored as the "pTrigger" member of the associated +** struct Table. +** +** The "step_list" member points to the first element of a linked list +** containing the SQL statements specified as the trigger program. +*/ struct Trigger { char *zName; /* The name of the trigger */ char *table; /* The table or view to which the trigger applies */ @@ -19108,43 +19122,48 @@ struct Trigger { #define TRIGGER_AFTER 2 /* - * An instance of struct TriggerStep is used to store a single SQL statement - * that is a part of a trigger-program. - * - * Instances of struct TriggerStep are stored in a singly linked list (linked - * using the "pNext" member) referenced by the "step_list" member of the - * associated struct Trigger instance. The first element of the linked list is - * the first step of the trigger-program. - * - * The "op" member indicates whether this is a "DELETE", "INSERT", "UPDATE" or - * "SELECT" statement. The meanings of the other members is determined by the - * value of "op" as follows: - * - * (op == TK_INSERT) - * orconf -> stores the ON CONFLICT algorithm - * pSelect -> If this is an INSERT INTO ... SELECT ... statement, then - * this stores a pointer to the SELECT statement. Otherwise NULL. - * zTarget -> Dequoted name of the table to insert into. - * pExprList -> If this is an INSERT INTO ... VALUES ... statement, then - * this stores values to be inserted. Otherwise NULL. - * pIdList -> If this is an INSERT INTO ... () VALUES ... - * statement, then this stores the column-names to be - * inserted into. - * - * (op == TK_DELETE) - * zTarget -> Dequoted name of the table to delete from. - * pWhere -> The WHERE clause of the DELETE statement if one is specified. - * Otherwise NULL. - * - * (op == TK_UPDATE) - * zTarget -> Dequoted name of the table to update. - * pWhere -> The WHERE clause of the UPDATE statement if one is specified. - * Otherwise NULL. - * pExprList -> A list of the columns to update and the expressions to update - * them to. See sqlite3Update() documentation of "pChanges" - * argument. - * - */ +** An instance of struct TriggerStep is used to store a single SQL statement +** that is a part of a trigger-program. +** +** Instances of struct TriggerStep are stored in a singly linked list (linked +** using the "pNext" member) referenced by the "step_list" member of the +** associated struct Trigger instance. The first element of the linked list is +** the first step of the trigger-program. +** +** The "op" member indicates whether this is a "DELETE", "INSERT", "UPDATE" or +** "SELECT" statement. The meanings of the other members is determined by the +** value of "op" as follows: +** +** (op == TK_INSERT) +** orconf -> stores the ON CONFLICT algorithm +** pSelect -> The content to be inserted - either a SELECT statement or +** a VALUES clause. +** zTarget -> Dequoted name of the table to insert into. +** pIdList -> If this is an INSERT INTO ... () VALUES ... +** statement, then this stores the column-names to be +** inserted into. +** pUpsert -> The ON CONFLICT clauses for an Upsert +** +** (op == TK_DELETE) +** zTarget -> Dequoted name of the table to delete from. +** pWhere -> The WHERE clause of the DELETE statement if one is specified. +** Otherwise NULL. +** +** (op == TK_UPDATE) +** zTarget -> Dequoted name of the table to update. +** pWhere -> The WHERE clause of the UPDATE statement if one is specified. +** Otherwise NULL. +** pExprList -> A list of the columns to update and the expressions to update +** them to. See sqlite3Update() documentation of "pChanges" +** argument. +** +** (op == TK_SELECT) +** pSelect -> The SELECT statement +** +** (op == TK_RETURNING) +** pExprList -> The list of expressions that follow the RETURNING keyword. +** +*/ struct TriggerStep { u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT, ** or TK_RETURNING */ @@ -19212,8 +19231,26 @@ typedef struct { /* ** Allowed values for mInitFlags */ +#define INITFLAG_AlterMask 0x0003 /* Types of ALTER */ #define INITFLAG_AlterRename 0x0001 /* Reparse after a RENAME */ #define INITFLAG_AlterDrop 0x0002 /* Reparse after a DROP COLUMN */ +#define INITFLAG_AlterAdd 0x0003 /* Reparse after an ADD COLUMN */ + +/* Tuning parameters are set using SQLITE_TESTCTRL_TUNE and are controlled +** on debug-builds of the CLI using ".testctrl tune ID VALUE". Tuning +** parameters are for temporary use during development, to help find +** optimial values for parameters in the query planner. The should not +** be used on trunk check-ins. They are a temporary mechanism available +** for transient development builds only. +** +** Tuning parameters are numbered starting with 1. +*/ +#define SQLITE_NTUNE 6 /* Should be zero for all trunk check-ins */ +#ifdef SQLITE_DEBUG +# define Tuning(X) (sqlite3Config.aTune[(X)-1]) +#else +# define Tuning(X) 0 +#endif /* ** Structure containing global configuration data for the SQLite library. @@ -19269,16 +19306,21 @@ struct Sqlite3Config { void (*xVdbeBranch)(void*,unsigned iSrcLine,u8 eThis,u8 eMx); /* Callback */ void *pVdbeBranchArg; /* 1st argument */ #endif -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE sqlite3_int64 mxMemdbSize; /* Default max memdb size */ #endif #ifndef SQLITE_UNTESTABLE int (*xTestCallback)(int); /* Invoked by sqlite3FaultSim() */ #endif int bLocaltimeFault; /* True to fail localtime() calls */ + int (*xAltLocaltime)(const void*,void*); /* Alternative localtime() routine */ int iOnceResetThreshold; /* When to reset OP_Once counters */ u32 szSorterRef; /* Min size in bytes to use sorter-refs */ unsigned int iPrngSeed; /* Alternative fixed seed for the PRNG */ + /* vvvv--- must be last ---vvv */ +#ifdef SQLITE_DEBUG + sqlite3_int64 aTune[SQLITE_NTUNE]; /* Tuning parameters */ +#endif }; /* @@ -19314,8 +19356,8 @@ struct Walker { int n; /* A counter */ int iCur; /* A cursor number */ SrcList *pSrcList; /* FROM clause */ - struct SrcCount *pSrcCount; /* Counting column references */ struct CCurHint *pCCurHint; /* Used by codeCursorHint() */ + struct RefSrcList *pRefSrcList; /* sqlite3ReferencesSrcList() */ int *aiCol; /* array of column indexes */ struct IdxCover *pIdxCover; /* Check for index coverage */ struct IdxExprTrans *pIdxTrans; /* Convert idxed expr to column */ @@ -19356,11 +19398,18 @@ SQLITE_PRIVATE int sqlite3SelectWalkNoop(Walker*, Select*); SQLITE_PRIVATE int sqlite3SelectWalkFail(Walker*, Select*); SQLITE_PRIVATE int sqlite3WalkerDepthIncrease(Walker*,Select*); SQLITE_PRIVATE void sqlite3WalkerDepthDecrease(Walker*,Select*); +SQLITE_PRIVATE void sqlite3WalkWinDefnDummyCallback(Walker*,Select*); #ifdef SQLITE_DEBUG SQLITE_PRIVATE void sqlite3SelectWalkAssert2(Walker*, Select*); #endif +#ifndef SQLITE_OMIT_CTE +SQLITE_PRIVATE void sqlite3SelectPopWith(Walker*, Select*); +#else +# define sqlite3SelectPopWith 0 +#endif + /* ** Return code from the parse-tree walking primitives and their ** callbacks. @@ -19394,6 +19443,7 @@ struct Cte { */ struct With { int nCte; /* Number of CTEs in the WITH clause */ + int bView; /* Belongs to the outermost Select of a view */ With *pOuter; /* Containing WITH clause, or NULL */ Cte a[1]; /* For each CTE in the WITH clause.... */ }; @@ -19468,7 +19518,7 @@ struct Window { Window **ppThis; /* Pointer to this object in Select.pWin list */ Window *pNextWin; /* Next window function belonging to this SELECT */ Expr *pFilter; /* The FILTER expression */ - FuncDef *pFunc; /* The function */ + FuncDef *pWFunc; /* The function */ int iEphCsr; /* Partition buffer or Peer buffer */ int regAccum; /* Accumulator */ int regResult; /* Interim result */ @@ -19492,7 +19542,7 @@ SQLITE_PRIVATE void sqlite3WindowListDelete(sqlite3 *db, Window *p); SQLITE_PRIVATE Window *sqlite3WindowAlloc(Parse*, int, int, Expr*, int , Expr*, u8); SQLITE_PRIVATE void sqlite3WindowAttach(Parse*, Expr*, Window*); SQLITE_PRIVATE void sqlite3WindowLink(Select *pSel, Window *pWin); -SQLITE_PRIVATE int sqlite3WindowCompare(Parse*, Window*, Window*, int); +SQLITE_PRIVATE int sqlite3WindowCompare(const Parse*, const Window*, const Window*, int); SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse*, Select*); SQLITE_PRIVATE void sqlite3WindowCodeStep(Parse*, Select*, WhereInfo*, int, int); SQLITE_PRIVATE int sqlite3WindowRewrite(Parse*, Select*); @@ -19624,8 +19674,8 @@ SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *, void *, u64); SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *, void *, u64); SQLITE_PRIVATE void sqlite3DbFree(sqlite3*, void*); SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3*, void*); -SQLITE_PRIVATE int sqlite3MallocSize(void*); -SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3*, void*); +SQLITE_PRIVATE int sqlite3MallocSize(const void*); +SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3*, const void*); SQLITE_PRIVATE void *sqlite3PageMalloc(int); SQLITE_PRIVATE void sqlite3PageFree(void*); SQLITE_PRIVATE void sqlite3MemSetDefault(void); @@ -19723,27 +19773,63 @@ SQLITE_PRIVATE void *sqlite3TestTextToPtr(const char*); #endif #if defined(SQLITE_DEBUG) +SQLITE_PRIVATE void sqlite3TreeViewLine(TreeView*, const char *zFormat, ...); SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView*, const Expr*, u8); SQLITE_PRIVATE void sqlite3TreeViewBareExprList(TreeView*, const ExprList*, const char*); SQLITE_PRIVATE void sqlite3TreeViewExprList(TreeView*, const ExprList*, u8, const char*); +SQLITE_PRIVATE void sqlite3TreeViewBareIdList(TreeView*, const IdList*, const char*); +SQLITE_PRIVATE void sqlite3TreeViewIdList(TreeView*, const IdList*, u8, const char*); +SQLITE_PRIVATE void sqlite3TreeViewColumnList(TreeView*, const Column*, int, u8); SQLITE_PRIVATE void sqlite3TreeViewSrcList(TreeView*, const SrcList*); SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView*, const Select*, u8); SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView*, const With*, u8); +SQLITE_PRIVATE void sqlite3TreeViewUpsert(TreeView*, const Upsert*, u8); +#if TREETRACE_ENABLED +SQLITE_PRIVATE void sqlite3TreeViewDelete(const With*, const SrcList*, const Expr*, + const ExprList*,const Expr*, const Trigger*); +SQLITE_PRIVATE void sqlite3TreeViewInsert(const With*, const SrcList*, + const IdList*, const Select*, const ExprList*, + int, const Upsert*, const Trigger*); +SQLITE_PRIVATE void sqlite3TreeViewUpdate(const With*, const SrcList*, const ExprList*, + const Expr*, int, const ExprList*, const Expr*, + const Upsert*, const Trigger*); +#endif +#ifndef SQLITE_OMIT_TRIGGER +SQLITE_PRIVATE void sqlite3TreeViewTriggerStep(TreeView*, const TriggerStep*, u8, u8); +SQLITE_PRIVATE void sqlite3TreeViewTrigger(TreeView*, const Trigger*, u8, u8); +#endif #ifndef SQLITE_OMIT_WINDOWFUNC SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView*, const Window*, u8); SQLITE_PRIVATE void sqlite3TreeViewWinFunc(TreeView*, const Window*, u8); #endif +SQLITE_PRIVATE void sqlite3ShowExpr(const Expr*); +SQLITE_PRIVATE void sqlite3ShowExprList(const ExprList*); +SQLITE_PRIVATE void sqlite3ShowIdList(const IdList*); +SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList*); +SQLITE_PRIVATE void sqlite3ShowSelect(const Select*); +SQLITE_PRIVATE void sqlite3ShowWith(const With*); +SQLITE_PRIVATE void sqlite3ShowUpsert(const Upsert*); +#ifndef SQLITE_OMIT_TRIGGER +SQLITE_PRIVATE void sqlite3ShowTriggerStep(const TriggerStep*); +SQLITE_PRIVATE void sqlite3ShowTriggerStepList(const TriggerStep*); +SQLITE_PRIVATE void sqlite3ShowTrigger(const Trigger*); +SQLITE_PRIVATE void sqlite3ShowTriggerList(const Trigger*); +#endif +#ifndef SQLITE_OMIT_WINDOWFUNC +SQLITE_PRIVATE void sqlite3ShowWindow(const Window*); +SQLITE_PRIVATE void sqlite3ShowWinFunc(const Window*); +#endif #endif - SQLITE_PRIVATE void sqlite3SetString(char **, sqlite3*, const char*); SQLITE_PRIVATE void sqlite3ErrorMsg(Parse*, const char*, ...); SQLITE_PRIVATE int sqlite3ErrorToParser(sqlite3*,int); SQLITE_PRIVATE void sqlite3Dequote(char*); SQLITE_PRIVATE void sqlite3DequoteExpr(Expr*); +SQLITE_PRIVATE void sqlite3DequoteToken(Token*); SQLITE_PRIVATE void sqlite3TokenInit(Token*,char*); SQLITE_PRIVATE int sqlite3KeywordCode(const unsigned char*, int); -SQLITE_PRIVATE int sqlite3RunParser(Parse*, const char*, char **); +SQLITE_PRIVATE int sqlite3RunParser(Parse*, const char*); SQLITE_PRIVATE void sqlite3FinishCoding(Parse*); SQLITE_PRIVATE int sqlite3GetTempReg(Parse*); SQLITE_PRIVATE void sqlite3ReleaseTempReg(Parse*,int); @@ -19760,16 +19846,17 @@ SQLITE_PRIVATE Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*); SQLITE_PRIVATE void sqlite3PExprAddSelect(Parse*, Expr*, Select*); SQLITE_PRIVATE Expr *sqlite3ExprAnd(Parse*,Expr*, Expr*); SQLITE_PRIVATE Expr *sqlite3ExprSimplifiedAndOr(Expr*); -SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse*,ExprList*, Token*, int); -SQLITE_PRIVATE void sqlite3ExprFunctionUsable(Parse*,Expr*,FuncDef*); +SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse*,ExprList*, const Token*, int); +SQLITE_PRIVATE void sqlite3ExprFunctionUsable(Parse*,const Expr*,const FuncDef*); SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32); SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3*, Expr*); SQLITE_PRIVATE void sqlite3ExprDeferredDelete(Parse*, Expr*); SQLITE_PRIVATE void sqlite3ExprUnmapAndDelete(Parse*, Expr*); SQLITE_PRIVATE ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*); SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*); +SQLITE_PRIVATE Select *sqlite3ExprListToValues(Parse*, int, ExprList*); SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList*,int,int); -SQLITE_PRIVATE void sqlite3ExprListSetName(Parse*,ExprList*,Token*,int); +SQLITE_PRIVATE void sqlite3ExprListSetName(Parse*,ExprList*,const Token*,int); SQLITE_PRIVATE void sqlite3ExprListSetSpan(Parse*,ExprList*,const char*,const char*); SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3*, ExprList*); SQLITE_PRIVATE u32 sqlite3ExprListFlags(const ExprList*); @@ -19785,7 +19872,12 @@ SQLITE_PRIVATE void sqlite3ResetAllSchemasOfConnection(sqlite3*); SQLITE_PRIVATE void sqlite3ResetOneSchema(sqlite3*,int); SQLITE_PRIVATE void sqlite3CollapseDatabaseArray(sqlite3*); SQLITE_PRIVATE void sqlite3CommitInternalChanges(sqlite3*); +SQLITE_PRIVATE void sqlite3ColumnSetExpr(Parse*,Table*,Column*,Expr*); +SQLITE_PRIVATE Expr *sqlite3ColumnExpr(Table*,Column*); +SQLITE_PRIVATE void sqlite3ColumnSetColl(sqlite3*,Column*,const char*zColl); +SQLITE_PRIVATE const char *sqlite3ColumnColl(Column*); SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3*,Table*); +SQLITE_PRIVATE void sqlite3GenerateColumnNames(Parse *pParse, Select *pSelect); SQLITE_PRIVATE int sqlite3ColumnsFromExprList(Parse*,ExprList*,i16*,Column**); SQLITE_PRIVATE void sqlite3SelectAddColumnTypeAndCollation(Parse*,Table*,Select*,char); SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse*,Select*,char); @@ -19805,14 +19897,14 @@ SQLITE_PRIVATE void sqlite3ColumnPropertiesFromName(Table*, Column*); #else # define sqlite3ColumnPropertiesFromName(T,C) /* no-op */ #endif -SQLITE_PRIVATE void sqlite3AddColumn(Parse*,Token*,Token*); +SQLITE_PRIVATE void sqlite3AddColumn(Parse*,Token,Token); SQLITE_PRIVATE void sqlite3AddNotNull(Parse*, int); SQLITE_PRIVATE void sqlite3AddPrimaryKey(Parse*, ExprList*, int, int, int); SQLITE_PRIVATE void sqlite3AddCheckConstraint(Parse*, Expr*, const char*, const char*); SQLITE_PRIVATE void sqlite3AddDefaultValue(Parse*,Expr*,const char*,const char*); SQLITE_PRIVATE void sqlite3AddCollateType(Parse*, Token*); SQLITE_PRIVATE void sqlite3AddGenerated(Parse*,Expr*,Token*); -SQLITE_PRIVATE void sqlite3EndTable(Parse*,Token*,Token*,u8,Select*); +SQLITE_PRIVATE void sqlite3EndTable(Parse*,Token*,Token*,u32,Select*); SQLITE_PRIVATE void sqlite3AddReturning(Parse*,ExprList*); SQLITE_PRIVATE int sqlite3ParseUri(const char*,const char*,unsigned int*, sqlite3_vfs**,char**,char **); @@ -19876,13 +19968,14 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListEnlarge(Parse*, SrcList*, int, int); SQLITE_PRIVATE SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2); SQLITE_PRIVATE SrcList *sqlite3SrcListAppend(Parse*, SrcList*, Token*, Token*); SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(Parse*, SrcList*, Token*, Token*, - Token*, Select*, Expr*, IdList*); + Token*, Select*, OnOrUsing*); SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *, SrcList *, Token *); SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse*, SrcList*, ExprList*); SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *, SrcItem *); -SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(SrcList*); +SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(Parse*,SrcList*); SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse*, SrcList*); SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3*, IdList*); +SQLITE_PRIVATE void sqlite3ClearOnOrUsing(sqlite3*, OnOrUsing*); SQLITE_PRIVATE void sqlite3SrcListDelete(sqlite3*, SrcList*); SQLITE_PRIVATE Index *sqlite3AllocateIndexObject(sqlite3*,i16,int,char**); SQLITE_PRIVATE void sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*, @@ -19898,10 +19991,12 @@ SQLITE_PRIVATE void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int); #if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) SQLITE_PRIVATE Expr *sqlite3LimitWhere(Parse*,SrcList*,Expr*,ExprList*,Expr*,char*); #endif +SQLITE_PRIVATE void sqlite3CodeChangeCount(Vdbe*,int,const char*); SQLITE_PRIVATE void sqlite3DeleteFrom(Parse*, SrcList*, Expr*, ExprList*, Expr*); SQLITE_PRIVATE void sqlite3Update(Parse*, SrcList*, ExprList*,Expr*,int,ExprList*,Expr*, Upsert*); -SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*,ExprList*,u16,int); +SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*, + ExprList*,Select*,u16,int); SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo*); SQLITE_PRIVATE LogEst sqlite3WhereOutputRowCount(WhereInfo*); SQLITE_PRIVATE int sqlite3WhereIsDistinct(WhereInfo*); @@ -19922,7 +20017,7 @@ SQLITE_PRIVATE void sqlite3ExprCodeGetColumnOfTable(Vdbe*, Table*, int, int, int SQLITE_PRIVATE void sqlite3ExprCodeMove(Parse*, int, int, int); SQLITE_PRIVATE void sqlite3ExprCode(Parse*, Expr*, int); #ifndef SQLITE_OMIT_GENERATED_COLUMNS -SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(Parse*, Column*, int); +SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(Parse*, Table*, Column*, int); #endif SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse*, Expr*, int); SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int); @@ -19941,23 +20036,24 @@ SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3*,const char*, const char*); #define LOCATE_VIEW 0x01 #define LOCATE_NOERR 0x02 SQLITE_PRIVATE Table *sqlite3LocateTable(Parse*,u32 flags,const char*, const char*); +SQLITE_PRIVATE const char *sqlite3PreferredTableName(const char*); SQLITE_PRIVATE Table *sqlite3LocateTableItem(Parse*,u32 flags,SrcItem *); SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3*,const char*, const char*); SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3*,int,const char*); SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3*,int,const char*); SQLITE_PRIVATE void sqlite3Vacuum(Parse*,Token*,Expr*); SQLITE_PRIVATE int sqlite3RunVacuum(char**, sqlite3*, int, sqlite3_value*); -SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3*, Token*); -SQLITE_PRIVATE int sqlite3ExprCompare(Parse*,Expr*, Expr*, int); -SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr*, Expr*, int); -SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList*, ExprList*, int); -SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Parse*,Expr*, Expr*, int); +SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3*, const Token*); +SQLITE_PRIVATE int sqlite3ExprCompare(const Parse*,const Expr*,const Expr*, int); +SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr*,Expr*,int); +SQLITE_PRIVATE int sqlite3ExprListCompare(const ExprList*,const ExprList*, int); +SQLITE_PRIVATE int sqlite3ExprImpliesExpr(const Parse*,const Expr*,const Expr*, int); SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr*,int); SQLITE_PRIVATE void sqlite3AggInfoPersistWalkerInit(Walker*,Parse*); SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*); SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*); SQLITE_PRIVATE int sqlite3ExprCoveredByIndex(Expr*, int iCur, Index *pIdx); -SQLITE_PRIVATE int sqlite3FunctionUsesThisSrc(Expr*, SrcList*); +SQLITE_PRIVATE int sqlite3ReferencesSrcList(Parse*, Expr*, SrcList*); SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse*); #ifndef SQLITE_UNTESTABLE SQLITE_PRIVATE void sqlite3PrngSaveState(void); @@ -19979,10 +20075,11 @@ SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr*); SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr*, u8); SQLITE_PRIVATE int sqlite3ExprIsConstantOrGroupBy(Parse*, Expr*, ExprList*); SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr*,int); +SQLITE_PRIVATE int sqlite3ExprIsTableConstraint(Expr*,const SrcItem*); #ifdef SQLITE_ENABLE_CURSOR_HINTS SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr*); #endif -SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr*, int*); +SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr*, int*); SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr*); SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr*, char); SQLITE_PRIVATE int sqlite3IsRowid(const char*); @@ -20007,20 +20104,26 @@ SQLITE_PRIVATE void sqlite3MayAbort(Parse*); SQLITE_PRIVATE void sqlite3HaltConstraint(Parse*, int, int, char*, i8, u8); SQLITE_PRIVATE void sqlite3UniqueConstraint(Parse*, int, Index*); SQLITE_PRIVATE void sqlite3RowidConstraint(Parse*, int, Table*); -SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3*,Expr*,int); -SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3*,ExprList*,int); -SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3*,SrcList*,int); -SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3*,IdList*); -SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3*,Select*,int); +SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3*,const Expr*,int); +SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3*,const ExprList*,int); +SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3*,const SrcList*,int); +SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3*,const IdList*); +SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3*,const Select*,int); SQLITE_PRIVATE FuncDef *sqlite3FunctionSearch(int,const char*); SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs(FuncDef*,int); SQLITE_PRIVATE FuncDef *sqlite3FindFunction(sqlite3*,const char*,int,u8,u8); +SQLITE_PRIVATE void sqlite3QuoteValue(StrAccum*,sqlite3_value*); SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void); SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void); +SQLITE_PRIVATE void sqlite3RegisterJsonFunctions(void); SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3*); +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) +SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3*); +#endif SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3*); SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3*); SQLITE_PRIVATE void sqlite3ChangeCookie(Parse*, int); +SQLITE_PRIVATE With *sqlite3WithDup(sqlite3 *db, With *p); #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) SQLITE_PRIVATE void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); @@ -20070,7 +20173,8 @@ SQLITE_PRIVATE SrcList *sqlite3TriggerStepSrc(Parse*, TriggerStep*); SQLITE_PRIVATE int sqlite3JoinType(Parse*, Token*, Token*, Token*); SQLITE_PRIVATE int sqlite3ColumnIndex(Table *pTab, const char *zCol); -SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr*,int); +SQLITE_PRIVATE void sqlite3SrcItemColumnUsed(SrcItem*,int); +SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr*,int,u32); SQLITE_PRIVATE void sqlite3CreateForeignKey(Parse*, ExprList*, Token*, ExprList*, int); SQLITE_PRIVATE void sqlite3DeferForeignKey(Parse*, int); #ifndef SQLITE_OMIT_AUTHORIZATION @@ -20106,14 +20210,8 @@ SQLITE_PRIVATE int sqlite3Utf8CharLen(const char *pData, int nByte); SQLITE_PRIVATE u32 sqlite3Utf8Read(const u8**); SQLITE_PRIVATE LogEst sqlite3LogEst(u64); SQLITE_PRIVATE LogEst sqlite3LogEstAdd(LogEst,LogEst); -#ifndef SQLITE_OMIT_VIRTUALTABLE SQLITE_PRIVATE LogEst sqlite3LogEstFromDouble(double); -#endif -#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || \ - defined(SQLITE_ENABLE_STAT4) || \ - defined(SQLITE_EXPLAIN_ESTIMATED_ROWS) SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst); -#endif SQLITE_PRIVATE VList *sqlite3VListAdd(sqlite3*,VList*,const char*,int,int); SQLITE_PRIVATE const char *sqlite3VListNumToName(VList*,int); SQLITE_PRIVATE int sqlite3VListNameToNum(VList*,const char*,int); @@ -20148,7 +20246,7 @@ SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3*, Index*); SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe*, Table*, int); SQLITE_PRIVATE char sqlite3CompareAffinity(const Expr *pExpr, char aff2); SQLITE_PRIVATE int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity); -SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table*,int); +SQLITE_PRIVATE char sqlite3TableColumnAffinity(const Table*,int); SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr); SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*, int, u8); SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char*, i64*); @@ -20164,7 +20262,7 @@ SQLITE_PRIVATE int sqlite3TwoPartName(Parse *, Token *, Token *, Token **); SQLITE_PRIVATE const char *sqlite3ErrName(int); #endif -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE SQLITE_PRIVATE int sqlite3MemdbInit(void); #endif @@ -20177,14 +20275,14 @@ SQLITE_PRIVATE void sqlite3SetTextEncoding(sqlite3 *db, u8); SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr); SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, const Expr *pExpr); SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse*,const Expr*,const Expr*); -SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr*, const Token*, int); -SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse*,Expr*,const char*); +SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(const Parse *pParse, Expr*, const Token*, int); +SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(const Parse*,Expr*,const char*); SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr*); SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr*); SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *, CollSeq *); SQLITE_PRIVATE int sqlite3WritableSchema(sqlite3*); SQLITE_PRIVATE int sqlite3CheckObjectName(Parse*, const char*,const char*,const char*); -SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *, int); +SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *, i64); SQLITE_PRIVATE int sqlite3AddInt64(i64*,i64); SQLITE_PRIVATE int sqlite3SubInt64(i64*,i64); SQLITE_PRIVATE int sqlite3MulInt64(i64*,i64); @@ -20209,12 +20307,19 @@ SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *); #ifndef SQLITE_OMIT_UTF16 SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *, const void*, int, u8); #endif -SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, Expr *, u8, u8, sqlite3_value **); +SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, const Expr *, u8, u8, sqlite3_value **); SQLITE_PRIVATE void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8); #ifndef SQLITE_AMALGAMATION SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[]; SQLITE_PRIVATE const char sqlite3StrBINARY[]; +SQLITE_PRIVATE const unsigned char sqlite3StdTypeLen[]; +SQLITE_PRIVATE const char sqlite3StdTypeAffinity[]; +SQLITE_PRIVATE const char sqlite3StdTypeMap[]; +SQLITE_PRIVATE const char *sqlite3StdType[]; SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[]; +SQLITE_PRIVATE const unsigned char *sqlite3aLTb; +SQLITE_PRIVATE const unsigned char *sqlite3aEQb; +SQLITE_PRIVATE const unsigned char *sqlite3aGTb; SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[]; SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config; SQLITE_PRIVATE FuncDefHash sqlite3BuiltinFunctions; @@ -20254,9 +20359,9 @@ SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *, Table *, int, int); SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *, Token *); SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *, SrcList *); -SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse*, SrcList*, Token*); -SQLITE_PRIVATE void *sqlite3RenameTokenMap(Parse*, void*, Token*); -SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse*, void *pTo, void *pFrom); +SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse*, SrcList*, const Token*); +SQLITE_PRIVATE const void *sqlite3RenameTokenMap(Parse*, const void*, const Token*); +SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse*, const void *pTo, const void *pFrom); SQLITE_PRIVATE void sqlite3RenameExprUnmap(Parse*, Expr*); SQLITE_PRIVATE void sqlite3RenameExprlistUnmap(Parse*, ExprList*); SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq(Parse*, u8, CollSeq *, const char*); @@ -20293,15 +20398,20 @@ SQLITE_PRIVATE int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *, FuncDestructor *pDestructor ); SQLITE_PRIVATE void sqlite3NoopDestructor(void*); -SQLITE_PRIVATE void sqlite3OomFault(sqlite3*); +SQLITE_PRIVATE void *sqlite3OomFault(sqlite3*); SQLITE_PRIVATE void sqlite3OomClear(sqlite3*); SQLITE_PRIVATE int sqlite3ApiExit(sqlite3 *db, int); SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *); SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int); +SQLITE_PRIVATE int sqlite3StrAccumEnlarge(StrAccum*, int); SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum*); +SQLITE_PRIVATE void sqlite3StrAccumSetError(StrAccum*, u8); +SQLITE_PRIVATE void sqlite3ResultStrAccum(sqlite3_context*,StrAccum*); SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest*,int,int); SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int); +SQLITE_PRIVATE void sqlite3RecordErrorByteOffset(sqlite3*,const char*); +SQLITE_PRIVATE void sqlite3RecordErrorOffsetOfExpr(sqlite3*,const Expr*); SQLITE_PRIVATE void sqlite3BackupRestart(sqlite3_backup *); SQLITE_PRIVATE void sqlite3BackupUpdate(sqlite3_backup *, Pgno, const u8 *); @@ -20352,7 +20462,7 @@ SQLITE_PRIVATE int sqlite3Utf8To8(unsigned char*); #endif #ifdef SQLITE_OMIT_VIRTUALTABLE -# define sqlite3VtabClear(Y) +# define sqlite3VtabClear(D,T) # define sqlite3VtabSync(X,Y) SQLITE_OK # define sqlite3VtabRollback(X) # define sqlite3VtabCommit(X) @@ -20389,9 +20499,11 @@ SQLITE_PRIVATE int sqlite3ReadOnlyShadowTables(sqlite3 *db); #ifndef SQLITE_OMIT_VIRTUALTABLE SQLITE_PRIVATE int sqlite3ShadowTableName(sqlite3 *db, const char *zName); SQLITE_PRIVATE int sqlite3IsShadowTableOf(sqlite3*,Table*,const char*); +SQLITE_PRIVATE void sqlite3MarkAllShadowTablesOf(sqlite3*, Table*); #else # define sqlite3ShadowTableName(A,B) 0 # define sqlite3IsShadowTableOf(A,B,C) 0 +# define sqlite3MarkAllShadowTablesOf(A,B) #endif SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse*,Module*); SQLITE_PRIVATE void sqlite3VtabEponymousTableClear(sqlite3*,Module*); @@ -20404,11 +20516,17 @@ SQLITE_PRIVATE int sqlite3VtabCallCreate(sqlite3*, int, const char *, char **); SQLITE_PRIVATE int sqlite3VtabCallConnect(Parse*, Table*); SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3*, int, const char *); SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *, VTable *); + SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction(sqlite3 *,FuncDef*, int nArg, Expr*); +#if (defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)) \ + && !defined(SQLITE_OMIT_VIRTUALTABLE) +SQLITE_PRIVATE void sqlite3VtabUsesAllSchemas(sqlite3_index_info*); +#endif SQLITE_PRIVATE sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*); SQLITE_PRIVATE int sqlite3VdbeParameterIndex(Vdbe*, const char*, int); SQLITE_PRIVATE int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *); -SQLITE_PRIVATE void sqlite3ParserReset(Parse*); +SQLITE_PRIVATE void sqlite3ParseObjectInit(Parse*,sqlite3*); +SQLITE_PRIVATE void sqlite3ParseObjectReset(Parse*); SQLITE_PRIVATE void *sqlite3ParserAddCleanup(Parse*,void(*)(sqlite3*,void*),void*); #ifdef SQLITE_ENABLE_NORMALIZE SQLITE_PRIVATE char *sqlite3Normalize(Vdbe*, const char*); @@ -20428,13 +20546,13 @@ SQLITE_PRIVATE Cte *sqlite3CteNew(Parse*,Token*,ExprList*,Select*,u8); SQLITE_PRIVATE void sqlite3CteDelete(sqlite3*,Cte*); SQLITE_PRIVATE With *sqlite3WithAdd(Parse*,With*,Cte*); SQLITE_PRIVATE void sqlite3WithDelete(sqlite3*,With*); -SQLITE_PRIVATE void sqlite3WithPush(Parse*, With*, u8); +SQLITE_PRIVATE With *sqlite3WithPush(Parse*, With*, u8); #else # define sqlite3CteNew(P,T,E,S) ((void*)0) # define sqlite3CteDelete(D,C) # define sqlite3CteWithAdd(P,W,C) ((void*)0) # define sqlite3WithDelete(x,y) -# define sqlite3WithPush(x,y,z) +# define sqlite3WithPush(x,y,z) ((void*)0) #endif #ifndef SQLITE_OMIT_UPSERT SQLITE_PRIVATE Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*,Upsert*); @@ -20467,6 +20585,7 @@ SQLITE_PRIVATE void sqlite3FkActions(Parse*, Table*, ExprList*, int, int*, int SQLITE_PRIVATE int sqlite3FkRequired(Parse*, Table*, int*, int); SQLITE_PRIVATE u32 sqlite3FkOldmask(Parse*, Table*); SQLITE_PRIVATE FKey *sqlite3FkReferences(Table *); +SQLITE_PRIVATE void sqlite3FkClearTriggerCache(sqlite3*,int); #else #define sqlite3FkActions(a,b,c,d,e,f) #define sqlite3FkCheck(a,b,c,d,e,f) @@ -20474,6 +20593,7 @@ SQLITE_PRIVATE FKey *sqlite3FkReferences(Table *); #define sqlite3FkOldmask(a,b) 0 #define sqlite3FkRequired(a,b,c,d) 0 #define sqlite3FkReferences(a) 0 + #define sqlite3FkClearTriggerCache(a,b) #endif #ifndef SQLITE_OMIT_FOREIGN_KEY SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *, Table*); @@ -20531,7 +20651,7 @@ SQLITE_PRIVATE void sqlite3MemJournalOpen(sqlite3_file *); SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p); #if SQLITE_MAX_EXPR_DEPTH>0 -SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *); +SQLITE_PRIVATE int sqlite3SelectExprHeight(const Select *); SQLITE_PRIVATE int sqlite3ExprCheckHeight(Parse*, int); #else #define sqlite3SelectExprHeight(x) 0 @@ -20602,8 +20722,8 @@ SQLITE_API SQLITE_EXTERN void (SQLITE_CDECL *sqlite3IoTrace)(const char*,...); */ #ifdef SQLITE_MEMDEBUG SQLITE_PRIVATE void sqlite3MemdebugSetType(void*,u8); -SQLITE_PRIVATE int sqlite3MemdebugHasType(void*,u8); -SQLITE_PRIVATE int sqlite3MemdebugNoType(void*,u8); +SQLITE_PRIVATE int sqlite3MemdebugHasType(const void*,u8); +SQLITE_PRIVATE int sqlite3MemdebugNoType(const void*,u8); #else # define sqlite3MemdebugSetType(X,Y) /* no-op */ # define sqlite3MemdebugHasType(X,Y) 1 @@ -20628,10 +20748,10 @@ SQLITE_PRIVATE int sqlite3DbpageRegister(sqlite3*); SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3*); #endif -SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr); -SQLITE_PRIVATE int sqlite3ExprIsVector(Expr *pExpr); +SQLITE_PRIVATE int sqlite3ExprVectorSize(const Expr *pExpr); +SQLITE_PRIVATE int sqlite3ExprIsVector(const Expr *pExpr); SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr*, int); -SQLITE_PRIVATE Expr *sqlite3ExprForVectorField(Parse*,Expr*,int); +SQLITE_PRIVATE Expr *sqlite3ExprForVectorField(Parse*,Expr*,int,int); SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse*, Expr*); #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS @@ -20641,6 +20761,993 @@ SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt); #endif /* SQLITEINT_H */ /************** End of sqliteInt.h *******************************************/ +/************** Begin file os_common.h ***************************************/ +/* +** 2004 May 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains macros and a little bit of code that is common to +** all of the platform-specific files (os_*.c) and is #included into those +** files. +** +** This file should be #included by the os_*.c files only. It is not a +** general purpose header file. +*/ +#ifndef _OS_COMMON_H_ +#define _OS_COMMON_H_ + +/* +** At least two bugs have slipped in because we changed the MEMORY_DEBUG +** macro to SQLITE_DEBUG and some older makefiles have not yet made the +** switch. The following code should catch this problem at compile-time. +*/ +#ifdef MEMORY_DEBUG +# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead." +#endif + +/* +** Macros for performance tracing. Normally turned off. Only works +** on i486 hardware. +*/ +#ifdef SQLITE_PERFORMANCE_TRACE + +/* +** hwtime.h contains inline assembler code for implementing +** high-performance timing routines. +*/ +/************** Include hwtime.h in the middle of os_common.h ****************/ +/************** Begin file hwtime.h ******************************************/ +/* +** 2008 May 27 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains inline asm code for retrieving "high-performance" +** counters for x86 and x86_64 class CPUs. +*/ +#ifndef SQLITE_HWTIME_H +#define SQLITE_HWTIME_H + +/* +** The following routine only works on pentium-class (or newer) processors. +** It uses the RDTSC opcode to read the cycle count value out of the +** processor and returns that value. This can be used for high-res +** profiling. +*/ +#if !defined(__STRICT_ANSI__) && \ + (defined(__GNUC__) || defined(_MSC_VER)) && \ + (defined(i386) || defined(__i386__) || defined(_M_IX86)) + + #if defined(__GNUC__) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + unsigned int lo, hi; + __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); + return (sqlite_uint64)hi << 32 | lo; + } + + #elif defined(_MSC_VER) + + __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){ + __asm { + rdtsc + ret ; return value at EDX:EAX + } + } + + #endif + +#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__)) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + unsigned long val; + __asm__ __volatile__ ("rdtsc" : "=A" (val)); + return val; + } + +#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__)) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + unsigned long long retval; + unsigned long junk; + __asm__ __volatile__ ("\n\ + 1: mftbu %1\n\ + mftb %L0\n\ + mftbu %0\n\ + cmpw %0,%1\n\ + bne 1b" + : "=r" (retval), "=r" (junk)); + return retval; + } + +#else + + /* + ** asm() is needed for hardware timing support. Without asm(), + ** disable the sqlite3Hwtime() routine. + ** + ** sqlite3Hwtime() is only used for some obscure debugging + ** and analysis configurations, not in any deliverable, so this + ** should not be a great loss. + */ +SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); } + +#endif + +#endif /* !defined(SQLITE_HWTIME_H) */ + +/************** End of hwtime.h **********************************************/ +/************** Continuing where we left off in os_common.h ******************/ + +static sqlite_uint64 g_start; +static sqlite_uint64 g_elapsed; +#define TIMER_START g_start=sqlite3Hwtime() +#define TIMER_END g_elapsed=sqlite3Hwtime()-g_start +#define TIMER_ELAPSED g_elapsed +#else +#define TIMER_START +#define TIMER_END +#define TIMER_ELAPSED ((sqlite_uint64)0) +#endif + +/* +** If we compile with the SQLITE_TEST macro set, then the following block +** of code will give us the ability to simulate a disk I/O error. This +** is used for testing the I/O recovery logic. +*/ +#if defined(SQLITE_TEST) +SQLITE_API extern int sqlite3_io_error_hit; +SQLITE_API extern int sqlite3_io_error_hardhit; +SQLITE_API extern int sqlite3_io_error_pending; +SQLITE_API extern int sqlite3_io_error_persist; +SQLITE_API extern int sqlite3_io_error_benign; +SQLITE_API extern int sqlite3_diskfull_pending; +SQLITE_API extern int sqlite3_diskfull; +#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X) +#define SimulateIOError(CODE) \ + if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \ + || sqlite3_io_error_pending-- == 1 ) \ + { local_ioerr(); CODE; } +static void local_ioerr(){ + IOTRACE(("IOERR\n")); + sqlite3_io_error_hit++; + if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++; +} +#define SimulateDiskfullError(CODE) \ + if( sqlite3_diskfull_pending ){ \ + if( sqlite3_diskfull_pending == 1 ){ \ + local_ioerr(); \ + sqlite3_diskfull = 1; \ + sqlite3_io_error_hit = 1; \ + CODE; \ + }else{ \ + sqlite3_diskfull_pending--; \ + } \ + } +#else +#define SimulateIOErrorBenign(X) +#define SimulateIOError(A) +#define SimulateDiskfullError(A) +#endif /* defined(SQLITE_TEST) */ + +/* +** When testing, keep a count of the number of open files. +*/ +#if defined(SQLITE_TEST) +SQLITE_API extern int sqlite3_open_file_count; +#define OpenCounter(X) sqlite3_open_file_count+=(X) +#else +#define OpenCounter(X) +#endif /* defined(SQLITE_TEST) */ + +#endif /* !defined(_OS_COMMON_H_) */ + +/************** End of os_common.h *******************************************/ +/************** Begin file ctime.c *******************************************/ +/* DO NOT EDIT! +** This file is automatically generated by the script in the canonical +** SQLite source tree at tool/mkctimec.tcl. +** +** To modify this header, edit any of the various lists in that script +** which specify categories of generated conditionals in this file. +*/ + +/* +** 2010 February 23 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements routines used to report what compile-time options +** SQLite was built with. +*/ +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS /* IMP: R-16824-07538 */ + +/* +** Include the configuration header output by 'configure' if we're using the +** autoconf-based build +*/ +#if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H) +/* #include "config.h" */ +#define SQLITECONFIG_H 1 +#endif + +/* These macros are provided to "stringify" the value of the define +** for those options in which the value is meaningful. */ +#define CTIMEOPT_VAL_(opt) #opt +#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) + +/* Like CTIMEOPT_VAL, but especially for SQLITE_DEFAULT_LOOKASIDE. This +** option requires a separate macro because legal values contain a single +** comma. e.g. (-DSQLITE_DEFAULT_LOOKASIDE="100,100") */ +#define CTIMEOPT_VAL2_(opt1,opt2) #opt1 "," #opt2 +#define CTIMEOPT_VAL2(opt) CTIMEOPT_VAL2_(opt) +/* #include "sqliteInt.h" */ + +/* +** An array of names of all compile-time options. This array should +** be sorted A-Z. +** +** This array looks large, but in a typical installation actually uses +** only a handful of compile-time options, so most times this array is usually +** rather short and uses little memory space. +*/ +static const char * const sqlite3azCompileOpt[] = { + +#ifdef SQLITE_32BIT_ROWID + "32BIT_ROWID", +#endif +#ifdef SQLITE_4_BYTE_ALIGNED_MALLOC + "4_BYTE_ALIGNED_MALLOC", +#endif +#ifdef SQLITE_64BIT_STATS + "64BIT_STATS", +#endif +#ifdef SQLITE_ALLOW_COVERING_INDEX_SCAN +# if SQLITE_ALLOW_COVERING_INDEX_SCAN != 1 + "ALLOW_COVERING_INDEX_SCAN=" CTIMEOPT_VAL(SQLITE_ALLOW_COVERING_INDEX_SCAN), +# endif +#endif +#ifdef SQLITE_ALLOW_URI_AUTHORITY + "ALLOW_URI_AUTHORITY", +#endif +#ifdef SQLITE_ATOMIC_INTRINSICS + "ATOMIC_INTRINSICS=" CTIMEOPT_VAL(SQLITE_ATOMIC_INTRINSICS), +#endif +#ifdef SQLITE_BITMASK_TYPE + "BITMASK_TYPE=" CTIMEOPT_VAL(SQLITE_BITMASK_TYPE), +#endif +#ifdef SQLITE_BUG_COMPATIBLE_20160819 + "BUG_COMPATIBLE_20160819", +#endif +#ifdef SQLITE_CASE_SENSITIVE_LIKE + "CASE_SENSITIVE_LIKE", +#endif +#ifdef SQLITE_CHECK_PAGES + "CHECK_PAGES", +#endif +#if defined(__clang__) && defined(__clang_major__) + "COMPILER=clang-" CTIMEOPT_VAL(__clang_major__) "." + CTIMEOPT_VAL(__clang_minor__) "." + CTIMEOPT_VAL(__clang_patchlevel__), +#elif defined(_MSC_VER) + "COMPILER=msvc-" CTIMEOPT_VAL(_MSC_VER), +#elif defined(__GNUC__) && defined(__VERSION__) + "COMPILER=gcc-" __VERSION__, +#endif +#ifdef SQLITE_COVERAGE_TEST + "COVERAGE_TEST", +#endif +#ifdef SQLITE_DEBUG + "DEBUG", +#endif +#ifdef SQLITE_DEFAULT_AUTOMATIC_INDEX + "DEFAULT_AUTOMATIC_INDEX", +#endif +#ifdef SQLITE_DEFAULT_AUTOVACUUM + "DEFAULT_AUTOVACUUM", +#endif +#ifdef SQLITE_DEFAULT_CACHE_SIZE + "DEFAULT_CACHE_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_CACHE_SIZE), +#endif +#ifdef SQLITE_DEFAULT_CKPTFULLFSYNC + "DEFAULT_CKPTFULLFSYNC", +#endif +#ifdef SQLITE_DEFAULT_FILE_FORMAT + "DEFAULT_FILE_FORMAT=" CTIMEOPT_VAL(SQLITE_DEFAULT_FILE_FORMAT), +#endif +#ifdef SQLITE_DEFAULT_FILE_PERMISSIONS + "DEFAULT_FILE_PERMISSIONS=" CTIMEOPT_VAL(SQLITE_DEFAULT_FILE_PERMISSIONS), +#endif +#ifdef SQLITE_DEFAULT_FOREIGN_KEYS + "DEFAULT_FOREIGN_KEYS", +#endif +#ifdef SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT + "DEFAULT_JOURNAL_SIZE_LIMIT=" CTIMEOPT_VAL(SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT), +#endif +#ifdef SQLITE_DEFAULT_LOCKING_MODE + "DEFAULT_LOCKING_MODE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOCKING_MODE), +#endif +#ifdef SQLITE_DEFAULT_LOOKASIDE + "DEFAULT_LOOKASIDE=" CTIMEOPT_VAL2(SQLITE_DEFAULT_LOOKASIDE), +#endif +#ifdef SQLITE_DEFAULT_MEMSTATUS +# if SQLITE_DEFAULT_MEMSTATUS != 1 + "DEFAULT_MEMSTATUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_MEMSTATUS), +# endif +#endif +#ifdef SQLITE_DEFAULT_MMAP_SIZE + "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE), +#endif +#ifdef SQLITE_DEFAULT_PAGE_SIZE + "DEFAULT_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_PAGE_SIZE), +#endif +#ifdef SQLITE_DEFAULT_PCACHE_INITSZ + "DEFAULT_PCACHE_INITSZ=" CTIMEOPT_VAL(SQLITE_DEFAULT_PCACHE_INITSZ), +#endif +#ifdef SQLITE_DEFAULT_PROXYDIR_PERMISSIONS + "DEFAULT_PROXYDIR_PERMISSIONS=" CTIMEOPT_VAL(SQLITE_DEFAULT_PROXYDIR_PERMISSIONS), +#endif +#ifdef SQLITE_DEFAULT_RECURSIVE_TRIGGERS + "DEFAULT_RECURSIVE_TRIGGERS", +#endif +#ifdef SQLITE_DEFAULT_ROWEST + "DEFAULT_ROWEST=" CTIMEOPT_VAL(SQLITE_DEFAULT_ROWEST), +#endif +#ifdef SQLITE_DEFAULT_SECTOR_SIZE + "DEFAULT_SECTOR_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_SECTOR_SIZE), +#endif +#ifdef SQLITE_DEFAULT_SYNCHRONOUS + "DEFAULT_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_SYNCHRONOUS), +#endif +#ifdef SQLITE_DEFAULT_WAL_AUTOCHECKPOINT + "DEFAULT_WAL_AUTOCHECKPOINT=" CTIMEOPT_VAL(SQLITE_DEFAULT_WAL_AUTOCHECKPOINT), +#endif +#ifdef SQLITE_DEFAULT_WAL_SYNCHRONOUS + "DEFAULT_WAL_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_WAL_SYNCHRONOUS), +#endif +#ifdef SQLITE_DEFAULT_WORKER_THREADS + "DEFAULT_WORKER_THREADS=" CTIMEOPT_VAL(SQLITE_DEFAULT_WORKER_THREADS), +#endif +#ifdef SQLITE_DIRECT_OVERFLOW_READ + "DIRECT_OVERFLOW_READ", +#endif +#ifdef SQLITE_DISABLE_DIRSYNC + "DISABLE_DIRSYNC", +#endif +#ifdef SQLITE_DISABLE_FTS3_UNICODE + "DISABLE_FTS3_UNICODE", +#endif +#ifdef SQLITE_DISABLE_FTS4_DEFERRED + "DISABLE_FTS4_DEFERRED", +#endif +#ifdef SQLITE_DISABLE_INTRINSIC + "DISABLE_INTRINSIC", +#endif +#ifdef SQLITE_DISABLE_LFS + "DISABLE_LFS", +#endif +#ifdef SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS + "DISABLE_PAGECACHE_OVERFLOW_STATS", +#endif +#ifdef SQLITE_DISABLE_SKIPAHEAD_DISTINCT + "DISABLE_SKIPAHEAD_DISTINCT", +#endif +#ifdef SQLITE_ENABLE_8_3_NAMES + "ENABLE_8_3_NAMES=" CTIMEOPT_VAL(SQLITE_ENABLE_8_3_NAMES), +#endif +#ifdef SQLITE_ENABLE_API_ARMOR + "ENABLE_API_ARMOR", +#endif +#ifdef SQLITE_ENABLE_ATOMIC_WRITE + "ENABLE_ATOMIC_WRITE", +#endif +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + "ENABLE_BATCH_ATOMIC_WRITE", +#endif +#ifdef SQLITE_ENABLE_BYTECODE_VTAB + "ENABLE_BYTECODE_VTAB", +#endif +#ifdef SQLITE_ENABLE_CEROD + "ENABLE_CEROD=" CTIMEOPT_VAL(SQLITE_ENABLE_CEROD), +#endif +#ifdef SQLITE_ENABLE_COLUMN_METADATA + "ENABLE_COLUMN_METADATA", +#endif +#ifdef SQLITE_ENABLE_COLUMN_USED_MASK + "ENABLE_COLUMN_USED_MASK", +#endif +#ifdef SQLITE_ENABLE_COSTMULT + "ENABLE_COSTMULT", +#endif +#ifdef SQLITE_ENABLE_CURSOR_HINTS + "ENABLE_CURSOR_HINTS", +#endif +#ifdef SQLITE_ENABLE_DBPAGE_VTAB + "ENABLE_DBPAGE_VTAB", +#endif +#ifdef SQLITE_ENABLE_DBSTAT_VTAB + "ENABLE_DBSTAT_VTAB", +#endif +#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT + "ENABLE_EXPENSIVE_ASSERT", +#endif +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + "ENABLE_EXPLAIN_COMMENTS", +#endif +#ifdef SQLITE_ENABLE_FTS3 + "ENABLE_FTS3", +#endif +#ifdef SQLITE_ENABLE_FTS3_PARENTHESIS + "ENABLE_FTS3_PARENTHESIS", +#endif +#ifdef SQLITE_ENABLE_FTS3_TOKENIZER + "ENABLE_FTS3_TOKENIZER", +#endif +#ifdef SQLITE_ENABLE_FTS4 + "ENABLE_FTS4", +#endif +#ifdef SQLITE_ENABLE_FTS5 + "ENABLE_FTS5", +#endif +#ifdef SQLITE_ENABLE_GEOPOLY + "ENABLE_GEOPOLY", +#endif +#ifdef SQLITE_ENABLE_HIDDEN_COLUMNS + "ENABLE_HIDDEN_COLUMNS", +#endif +#ifdef SQLITE_ENABLE_ICU + "ENABLE_ICU", +#endif +#ifdef SQLITE_ENABLE_IOTRACE + "ENABLE_IOTRACE", +#endif +#ifdef SQLITE_ENABLE_LOAD_EXTENSION + "ENABLE_LOAD_EXTENSION", +#endif +#ifdef SQLITE_ENABLE_LOCKING_STYLE + "ENABLE_LOCKING_STYLE=" CTIMEOPT_VAL(SQLITE_ENABLE_LOCKING_STYLE), +#endif +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS + "ENABLE_MATH_FUNCTIONS", +#endif +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT + "ENABLE_MEMORY_MANAGEMENT", +#endif +#ifdef SQLITE_ENABLE_MEMSYS3 + "ENABLE_MEMSYS3", +#endif +#ifdef SQLITE_ENABLE_MEMSYS5 + "ENABLE_MEMSYS5", +#endif +#ifdef SQLITE_ENABLE_MULTIPLEX + "ENABLE_MULTIPLEX", +#endif +#ifdef SQLITE_ENABLE_NORMALIZE + "ENABLE_NORMALIZE", +#endif +#ifdef SQLITE_ENABLE_NULL_TRIM + "ENABLE_NULL_TRIM", +#endif +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + "ENABLE_OFFSET_SQL_FUNC", +#endif +#ifdef SQLITE_ENABLE_OVERSIZE_CELL_CHECK + "ENABLE_OVERSIZE_CELL_CHECK", +#endif +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + "ENABLE_PREUPDATE_HOOK", +#endif +#ifdef SQLITE_ENABLE_QPSG + "ENABLE_QPSG", +#endif +#ifdef SQLITE_ENABLE_RBU + "ENABLE_RBU", +#endif +#ifdef SQLITE_ENABLE_RTREE + "ENABLE_RTREE", +#endif +#ifdef SQLITE_ENABLE_SESSION + "ENABLE_SESSION", +#endif +#ifdef SQLITE_ENABLE_SNAPSHOT + "ENABLE_SNAPSHOT", +#endif +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + "ENABLE_SORTER_REFERENCES", +#endif +#ifdef SQLITE_ENABLE_SQLLOG + "ENABLE_SQLLOG", +#endif +#ifdef SQLITE_ENABLE_STAT4 + "ENABLE_STAT4", +#endif +#ifdef SQLITE_ENABLE_STMTVTAB + "ENABLE_STMTVTAB", +#endif +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + "ENABLE_STMT_SCANSTATUS", +#endif +#ifdef SQLITE_ENABLE_TREETRACE + "ENABLE_TREETRACE", +#endif +#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION + "ENABLE_UNKNOWN_SQL_FUNCTION", +#endif +#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY + "ENABLE_UNLOCK_NOTIFY", +#endif +#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT + "ENABLE_UPDATE_DELETE_LIMIT", +#endif +#ifdef SQLITE_ENABLE_URI_00_ERROR + "ENABLE_URI_00_ERROR", +#endif +#ifdef SQLITE_ENABLE_VFSTRACE + "ENABLE_VFSTRACE", +#endif +#ifdef SQLITE_ENABLE_WHERETRACE + "ENABLE_WHERETRACE", +#endif +#ifdef SQLITE_ENABLE_ZIPVFS + "ENABLE_ZIPVFS", +#endif +#ifdef SQLITE_EXPLAIN_ESTIMATED_ROWS + "EXPLAIN_ESTIMATED_ROWS", +#endif +#ifdef SQLITE_EXTRA_IFNULLROW + "EXTRA_IFNULLROW", +#endif +#ifdef SQLITE_EXTRA_INIT + "EXTRA_INIT=" CTIMEOPT_VAL(SQLITE_EXTRA_INIT), +#endif +#ifdef SQLITE_EXTRA_SHUTDOWN + "EXTRA_SHUTDOWN=" CTIMEOPT_VAL(SQLITE_EXTRA_SHUTDOWN), +#endif +#ifdef SQLITE_FTS3_MAX_EXPR_DEPTH + "FTS3_MAX_EXPR_DEPTH=" CTIMEOPT_VAL(SQLITE_FTS3_MAX_EXPR_DEPTH), +#endif +#ifdef SQLITE_FTS5_ENABLE_TEST_MI + "FTS5_ENABLE_TEST_MI", +#endif +#ifdef SQLITE_FTS5_NO_WITHOUT_ROWID + "FTS5_NO_WITHOUT_ROWID", +#endif +#if HAVE_ISNAN || SQLITE_HAVE_ISNAN + "HAVE_ISNAN", +#endif +#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX +# if SQLITE_HOMEGROWN_RECURSIVE_MUTEX != 1 + "HOMEGROWN_RECURSIVE_MUTEX=" CTIMEOPT_VAL(SQLITE_HOMEGROWN_RECURSIVE_MUTEX), +# endif +#endif +#ifdef SQLITE_IGNORE_AFP_LOCK_ERRORS + "IGNORE_AFP_LOCK_ERRORS", +#endif +#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS + "IGNORE_FLOCK_LOCK_ERRORS", +#endif +#ifdef SQLITE_INLINE_MEMCPY + "INLINE_MEMCPY", +#endif +#ifdef SQLITE_INT64_TYPE + "INT64_TYPE", +#endif +#ifdef SQLITE_INTEGRITY_CHECK_ERROR_MAX + "INTEGRITY_CHECK_ERROR_MAX=" CTIMEOPT_VAL(SQLITE_INTEGRITY_CHECK_ERROR_MAX), +#endif +#ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS + "LIKE_DOESNT_MATCH_BLOBS", +#endif +#ifdef SQLITE_LOCK_TRACE + "LOCK_TRACE", +#endif +#ifdef SQLITE_LOG_CACHE_SPILL + "LOG_CACHE_SPILL", +#endif +#ifdef SQLITE_MALLOC_SOFT_LIMIT + "MALLOC_SOFT_LIMIT=" CTIMEOPT_VAL(SQLITE_MALLOC_SOFT_LIMIT), +#endif +#ifdef SQLITE_MAX_ATTACHED + "MAX_ATTACHED=" CTIMEOPT_VAL(SQLITE_MAX_ATTACHED), +#endif +#ifdef SQLITE_MAX_COLUMN + "MAX_COLUMN=" CTIMEOPT_VAL(SQLITE_MAX_COLUMN), +#endif +#ifdef SQLITE_MAX_COMPOUND_SELECT + "MAX_COMPOUND_SELECT=" CTIMEOPT_VAL(SQLITE_MAX_COMPOUND_SELECT), +#endif +#ifdef SQLITE_MAX_DEFAULT_PAGE_SIZE + "MAX_DEFAULT_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_DEFAULT_PAGE_SIZE), +#endif +#ifdef SQLITE_MAX_EXPR_DEPTH + "MAX_EXPR_DEPTH=" CTIMEOPT_VAL(SQLITE_MAX_EXPR_DEPTH), +#endif +#ifdef SQLITE_MAX_FUNCTION_ARG + "MAX_FUNCTION_ARG=" CTIMEOPT_VAL(SQLITE_MAX_FUNCTION_ARG), +#endif +#ifdef SQLITE_MAX_LENGTH + "MAX_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_LENGTH), +#endif +#ifdef SQLITE_MAX_LIKE_PATTERN_LENGTH + "MAX_LIKE_PATTERN_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_LIKE_PATTERN_LENGTH), +#endif +#ifdef SQLITE_MAX_MEMORY + "MAX_MEMORY=" CTIMEOPT_VAL(SQLITE_MAX_MEMORY), +#endif +#ifdef SQLITE_MAX_MMAP_SIZE + "MAX_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE), +#endif +#ifdef SQLITE_MAX_MMAP_SIZE_ + "MAX_MMAP_SIZE_=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE_), +#endif +#ifdef SQLITE_MAX_PAGE_COUNT + "MAX_PAGE_COUNT=" CTIMEOPT_VAL(SQLITE_MAX_PAGE_COUNT), +#endif +#ifdef SQLITE_MAX_PAGE_SIZE + "MAX_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_PAGE_SIZE), +#endif +#ifdef SQLITE_MAX_SCHEMA_RETRY + "MAX_SCHEMA_RETRY=" CTIMEOPT_VAL(SQLITE_MAX_SCHEMA_RETRY), +#endif +#ifdef SQLITE_MAX_SQL_LENGTH + "MAX_SQL_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_SQL_LENGTH), +#endif +#ifdef SQLITE_MAX_TRIGGER_DEPTH + "MAX_TRIGGER_DEPTH=" CTIMEOPT_VAL(SQLITE_MAX_TRIGGER_DEPTH), +#endif +#ifdef SQLITE_MAX_VARIABLE_NUMBER + "MAX_VARIABLE_NUMBER=" CTIMEOPT_VAL(SQLITE_MAX_VARIABLE_NUMBER), +#endif +#ifdef SQLITE_MAX_VDBE_OP + "MAX_VDBE_OP=" CTIMEOPT_VAL(SQLITE_MAX_VDBE_OP), +#endif +#ifdef SQLITE_MAX_WORKER_THREADS + "MAX_WORKER_THREADS=" CTIMEOPT_VAL(SQLITE_MAX_WORKER_THREADS), +#endif +#ifdef SQLITE_MEMDEBUG + "MEMDEBUG", +#endif +#ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT + "MIXED_ENDIAN_64BIT_FLOAT", +#endif +#ifdef SQLITE_MMAP_READWRITE + "MMAP_READWRITE", +#endif +#ifdef SQLITE_MUTEX_NOOP + "MUTEX_NOOP", +#endif +#ifdef SQLITE_MUTEX_OMIT + "MUTEX_OMIT", +#endif +#ifdef SQLITE_MUTEX_PTHREADS + "MUTEX_PTHREADS", +#endif +#ifdef SQLITE_MUTEX_W32 + "MUTEX_W32", +#endif +#ifdef SQLITE_NEED_ERR_NAME + "NEED_ERR_NAME", +#endif +#ifdef SQLITE_NO_SYNC + "NO_SYNC", +#endif +#ifdef SQLITE_OMIT_ALTERTABLE + "OMIT_ALTERTABLE", +#endif +#ifdef SQLITE_OMIT_ANALYZE + "OMIT_ANALYZE", +#endif +#ifdef SQLITE_OMIT_ATTACH + "OMIT_ATTACH", +#endif +#ifdef SQLITE_OMIT_AUTHORIZATION + "OMIT_AUTHORIZATION", +#endif +#ifdef SQLITE_OMIT_AUTOINCREMENT + "OMIT_AUTOINCREMENT", +#endif +#ifdef SQLITE_OMIT_AUTOINIT + "OMIT_AUTOINIT", +#endif +#ifdef SQLITE_OMIT_AUTOMATIC_INDEX + "OMIT_AUTOMATIC_INDEX", +#endif +#ifdef SQLITE_OMIT_AUTORESET + "OMIT_AUTORESET", +#endif +#ifdef SQLITE_OMIT_AUTOVACUUM + "OMIT_AUTOVACUUM", +#endif +#ifdef SQLITE_OMIT_BETWEEN_OPTIMIZATION + "OMIT_BETWEEN_OPTIMIZATION", +#endif +#ifdef SQLITE_OMIT_BLOB_LITERAL + "OMIT_BLOB_LITERAL", +#endif +#ifdef SQLITE_OMIT_CAST + "OMIT_CAST", +#endif +#ifdef SQLITE_OMIT_CHECK + "OMIT_CHECK", +#endif +#ifdef SQLITE_OMIT_COMPLETE + "OMIT_COMPLETE", +#endif +#ifdef SQLITE_OMIT_COMPOUND_SELECT + "OMIT_COMPOUND_SELECT", +#endif +#ifdef SQLITE_OMIT_CONFLICT_CLAUSE + "OMIT_CONFLICT_CLAUSE", +#endif +#ifdef SQLITE_OMIT_CTE + "OMIT_CTE", +#endif +#if defined(SQLITE_OMIT_DATETIME_FUNCS) || defined(SQLITE_OMIT_FLOATING_POINT) + "OMIT_DATETIME_FUNCS", +#endif +#ifdef SQLITE_OMIT_DECLTYPE + "OMIT_DECLTYPE", +#endif +#ifdef SQLITE_OMIT_DEPRECATED + "OMIT_DEPRECATED", +#endif +#ifdef SQLITE_OMIT_DESERIALIZE + "OMIT_DESERIALIZE", +#endif +#ifdef SQLITE_OMIT_DISKIO + "OMIT_DISKIO", +#endif +#ifdef SQLITE_OMIT_EXPLAIN + "OMIT_EXPLAIN", +#endif +#ifdef SQLITE_OMIT_FLAG_PRAGMAS + "OMIT_FLAG_PRAGMAS", +#endif +#ifdef SQLITE_OMIT_FLOATING_POINT + "OMIT_FLOATING_POINT", +#endif +#ifdef SQLITE_OMIT_FOREIGN_KEY + "OMIT_FOREIGN_KEY", +#endif +#ifdef SQLITE_OMIT_GET_TABLE + "OMIT_GET_TABLE", +#endif +#ifdef SQLITE_OMIT_HEX_INTEGER + "OMIT_HEX_INTEGER", +#endif +#ifdef SQLITE_OMIT_INCRBLOB + "OMIT_INCRBLOB", +#endif +#ifdef SQLITE_OMIT_INTEGRITY_CHECK + "OMIT_INTEGRITY_CHECK", +#endif +#ifdef SQLITE_OMIT_INTROSPECTION_PRAGMAS + "OMIT_INTROSPECTION_PRAGMAS", +#endif +#ifdef SQLITE_OMIT_JSON + "OMIT_JSON", +#endif +#ifdef SQLITE_OMIT_LIKE_OPTIMIZATION + "OMIT_LIKE_OPTIMIZATION", +#endif +#ifdef SQLITE_OMIT_LOAD_EXTENSION + "OMIT_LOAD_EXTENSION", +#endif +#ifdef SQLITE_OMIT_LOCALTIME + "OMIT_LOCALTIME", +#endif +#ifdef SQLITE_OMIT_LOOKASIDE + "OMIT_LOOKASIDE", +#endif +#ifdef SQLITE_OMIT_MEMORYDB + "OMIT_MEMORYDB", +#endif +#ifdef SQLITE_OMIT_OR_OPTIMIZATION + "OMIT_OR_OPTIMIZATION", +#endif +#ifdef SQLITE_OMIT_PAGER_PRAGMAS + "OMIT_PAGER_PRAGMAS", +#endif +#ifdef SQLITE_OMIT_PARSER_TRACE + "OMIT_PARSER_TRACE", +#endif +#ifdef SQLITE_OMIT_POPEN + "OMIT_POPEN", +#endif +#ifdef SQLITE_OMIT_PRAGMA + "OMIT_PRAGMA", +#endif +#ifdef SQLITE_OMIT_PROGRESS_CALLBACK + "OMIT_PROGRESS_CALLBACK", +#endif +#ifdef SQLITE_OMIT_QUICKBALANCE + "OMIT_QUICKBALANCE", +#endif +#ifdef SQLITE_OMIT_REINDEX + "OMIT_REINDEX", +#endif +#ifdef SQLITE_OMIT_SCHEMA_PRAGMAS + "OMIT_SCHEMA_PRAGMAS", +#endif +#ifdef SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS + "OMIT_SCHEMA_VERSION_PRAGMAS", +#endif +#ifdef SQLITE_OMIT_SHARED_CACHE + "OMIT_SHARED_CACHE", +#endif +#ifdef SQLITE_OMIT_SHUTDOWN_DIRECTORIES + "OMIT_SHUTDOWN_DIRECTORIES", +#endif +#ifdef SQLITE_OMIT_SUBQUERY + "OMIT_SUBQUERY", +#endif +#ifdef SQLITE_OMIT_TCL_VARIABLE + "OMIT_TCL_VARIABLE", +#endif +#ifdef SQLITE_OMIT_TEMPDB + "OMIT_TEMPDB", +#endif +#ifdef SQLITE_OMIT_TEST_CONTROL + "OMIT_TEST_CONTROL", +#endif +#ifdef SQLITE_OMIT_TRACE +# if SQLITE_OMIT_TRACE != 1 + "OMIT_TRACE=" CTIMEOPT_VAL(SQLITE_OMIT_TRACE), +# endif +#endif +#ifdef SQLITE_OMIT_TRIGGER + "OMIT_TRIGGER", +#endif +#ifdef SQLITE_OMIT_TRUNCATE_OPTIMIZATION + "OMIT_TRUNCATE_OPTIMIZATION", +#endif +#ifdef SQLITE_OMIT_UTF16 + "OMIT_UTF16", +#endif +#ifdef SQLITE_OMIT_VACUUM + "OMIT_VACUUM", +#endif +#ifdef SQLITE_OMIT_VIEW + "OMIT_VIEW", +#endif +#ifdef SQLITE_OMIT_VIRTUALTABLE + "OMIT_VIRTUALTABLE", +#endif +#ifdef SQLITE_OMIT_WAL + "OMIT_WAL", +#endif +#ifdef SQLITE_OMIT_WSD + "OMIT_WSD", +#endif +#ifdef SQLITE_OMIT_XFER_OPT + "OMIT_XFER_OPT", +#endif +#ifdef SQLITE_PCACHE_SEPARATE_HEADER + "PCACHE_SEPARATE_HEADER", +#endif +#ifdef SQLITE_PERFORMANCE_TRACE + "PERFORMANCE_TRACE", +#endif +#ifdef SQLITE_POWERSAFE_OVERWRITE +# if SQLITE_POWERSAFE_OVERWRITE != 1 + "POWERSAFE_OVERWRITE=" CTIMEOPT_VAL(SQLITE_POWERSAFE_OVERWRITE), +# endif +#endif +#ifdef SQLITE_PREFER_PROXY_LOCKING + "PREFER_PROXY_LOCKING", +#endif +#ifdef SQLITE_PROXY_DEBUG + "PROXY_DEBUG", +#endif +#ifdef SQLITE_REVERSE_UNORDERED_SELECTS + "REVERSE_UNORDERED_SELECTS", +#endif +#ifdef SQLITE_RTREE_INT_ONLY + "RTREE_INT_ONLY", +#endif +#ifdef SQLITE_SECURE_DELETE + "SECURE_DELETE", +#endif +#ifdef SQLITE_SMALL_STACK + "SMALL_STACK", +#endif +#ifdef SQLITE_SORTER_PMASZ + "SORTER_PMASZ=" CTIMEOPT_VAL(SQLITE_SORTER_PMASZ), +#endif +#ifdef SQLITE_SOUNDEX + "SOUNDEX", +#endif +#ifdef SQLITE_STAT4_SAMPLES + "STAT4_SAMPLES=" CTIMEOPT_VAL(SQLITE_STAT4_SAMPLES), +#endif +#ifdef SQLITE_STMTJRNL_SPILL + "STMTJRNL_SPILL=" CTIMEOPT_VAL(SQLITE_STMTJRNL_SPILL), +#endif +#ifdef SQLITE_SUBSTR_COMPATIBILITY + "SUBSTR_COMPATIBILITY", +#endif +#if (!defined(SQLITE_WIN32_MALLOC) \ + && !defined(SQLITE_ZERO_MALLOC) \ + && !defined(SQLITE_MEMDEBUG) \ + ) || defined(SQLITE_SYSTEM_MALLOC) + "SYSTEM_MALLOC", +#endif +#ifdef SQLITE_TCL + "TCL", +#endif +#ifdef SQLITE_TEMP_STORE + "TEMP_STORE=" CTIMEOPT_VAL(SQLITE_TEMP_STORE), +#endif +#ifdef SQLITE_TEST + "TEST", +#endif +#if defined(SQLITE_THREADSAFE) + "THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE), +#elif defined(THREADSAFE) + "THREADSAFE=" CTIMEOPT_VAL(THREADSAFE), +#else + "THREADSAFE=1", +#endif +#ifdef SQLITE_UNLINK_AFTER_CLOSE + "UNLINK_AFTER_CLOSE", +#endif +#ifdef SQLITE_UNTESTABLE + "UNTESTABLE", +#endif +#ifdef SQLITE_USER_AUTHENTICATION + "USER_AUTHENTICATION", +#endif +#ifdef SQLITE_USE_ALLOCA + "USE_ALLOCA", +#endif +#ifdef SQLITE_USE_FCNTL_TRACE + "USE_FCNTL_TRACE", +#endif +#ifdef SQLITE_USE_URI + "USE_URI", +#endif +#ifdef SQLITE_VDBE_COVERAGE + "VDBE_COVERAGE", +#endif +#ifdef SQLITE_WIN32_MALLOC + "WIN32_MALLOC", +#endif +#ifdef SQLITE_ZERO_MALLOC + "ZERO_MALLOC", +#endif + +} ; + +SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt){ + *pnOpt = sizeof(sqlite3azCompileOpt) / sizeof(sqlite3azCompileOpt[0]); + return (const char**)sqlite3azCompileOpt; +} + +#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ + +/************** End of ctime.c ***********************************************/ /************** Begin file global.c ******************************************/ /* ** 2008 June 13 @@ -20681,7 +21788,7 @@ SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[] = { 198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215, 216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233, 234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251, - 252,253,254,255 + 252,253,254,255, #endif #ifdef SQLITE_EBCDIC 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 0x */ @@ -20701,7 +21808,35 @@ SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[] = { 224,225,162,163,164,165,166,167,168,169,234,235,236,237,238,239, /* Ex */ 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255, /* Fx */ #endif +/* All of the upper-to-lower conversion data is above. The following +** 18 integers are completely unrelated. They are appended to the +** sqlite3UpperToLower[] array to avoid UBSAN warnings. Here's what is +** going on: +** +** The SQL comparison operators (<>, =, >, <=, <, and >=) are implemented +** by invoking sqlite3MemCompare(A,B) which compares values A and B and +** returns negative, zero, or positive if A is less then, equal to, or +** greater than B, respectively. Then the true false results is found by +** consulting sqlite3aLTb[opcode], sqlite3aEQb[opcode], or +** sqlite3aGTb[opcode] depending on whether the result of compare(A,B) +** is negative, zero, or positive, where opcode is the specific opcode. +** The only works because the comparison opcodes are consecutive and in +** this order: NE EQ GT LE LT GE. Various assert()s throughout the code +** ensure that is the case. +** +** These elements must be appended to another array. Otherwise the +** index (here shown as [256-OP_Ne]) would be out-of-bounds and thus +** be undefined behavior. That's goofy, but the C-standards people thought +** it was a good idea, so here we are. +*/ +/* NE EQ GT LE LT GE */ + 1, 0, 0, 1, 1, 0, /* aLTb[]: Use when compare(A,B) less than zero */ + 0, 1, 0, 1, 0, 1, /* aEQb[]: Use when compare(A,B) equals zero */ + 1, 0, 1, 0, 0, 1 /* aGTb[]: Use when compare(A,B) greater than zero*/ }; +SQLITE_PRIVATE const unsigned char *sqlite3aLTb = &sqlite3UpperToLower[256-OP_Ne]; +SQLITE_PRIVATE const unsigned char *sqlite3aEQb = &sqlite3UpperToLower[256+6-OP_Ne]; +SQLITE_PRIVATE const unsigned char *sqlite3aGTb = &sqlite3UpperToLower[256+12-OP_Ne]; /* ** The following 256 byte lookup table is used to support SQLites built-in @@ -20895,16 +22030,20 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = { 0, /* xVdbeBranch */ 0, /* pVbeBranchArg */ #endif -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE SQLITE_MEMDB_DEFAULT_MAXSIZE, /* mxMemdbSize */ #endif #ifndef SQLITE_UNTESTABLE 0, /* xTestCallback */ #endif 0, /* bLocaltimeFault */ + 0, /* xAltLocaltime */ 0x7ffffffe, /* iOnceResetThreshold */ SQLITE_DEFAULT_SORTERREF_SIZE, /* szSorterRef */ 0, /* iPrngSeed */ +#ifdef SQLITE_DEBUG + {0,0,0,0,0,0} /* aTune */ +#endif }; /* @@ -20914,6 +22053,18 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = { */ SQLITE_PRIVATE FuncDefHash sqlite3BuiltinFunctions; +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG) +/* +** Counter used for coverage testing. Does not come into play for +** release builds. +** +** Access to this global variable is not mutex protected. This might +** result in TSAN warnings. But as the variable does not exist in +** release builds, that should not be a concern. +*/ +SQLITE_PRIVATE unsigned int sqlite3CoverageCounter; +#endif /* SQLITE_COVERAGE_TEST || SQLITE_DEBUG */ + #ifdef VDBE_PROFILE /* ** The following performance counter can be used in place of @@ -20947,7 +22098,7 @@ SQLITE_PRIVATE int sqlite3PendingByte = 0x40000000; /* ** Tracing flags set by SQLITE_TESTCTRL_TRACEFLAGS. */ -SQLITE_PRIVATE u32 sqlite3SelectTrace = 0; +SQLITE_PRIVATE u32 sqlite3TreeTrace = 0; SQLITE_PRIVATE u32 sqlite3WhereTrace = 0; /* #include "opcodes.h" */ @@ -20964,6 +22115,48 @@ SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[] = OPFLG_INITIALIZER; */ SQLITE_PRIVATE const char sqlite3StrBINARY[] = "BINARY"; +/* +** Standard typenames. These names must match the COLTYPE_* definitions. +** Adjust the SQLITE_N_STDTYPE value if adding or removing entries. +** +** sqlite3StdType[] The actual names of the datatypes. +** +** sqlite3StdTypeLen[] The length (in bytes) of each entry +** in sqlite3StdType[]. +** +** sqlite3StdTypeAffinity[] The affinity associated with each entry +** in sqlite3StdType[]. +** +** sqlite3StdTypeMap[] The type value (as returned from +** sqlite3_column_type() or sqlite3_value_type()) +** for each entry in sqlite3StdType[]. +*/ +SQLITE_PRIVATE const unsigned char sqlite3StdTypeLen[] = { 3, 4, 3, 7, 4, 4 }; +SQLITE_PRIVATE const char sqlite3StdTypeAffinity[] = { + SQLITE_AFF_NUMERIC, + SQLITE_AFF_BLOB, + SQLITE_AFF_INTEGER, + SQLITE_AFF_INTEGER, + SQLITE_AFF_REAL, + SQLITE_AFF_TEXT +}; +SQLITE_PRIVATE const char sqlite3StdTypeMap[] = { + 0, + SQLITE_BLOB, + SQLITE_INTEGER, + SQLITE_INTEGER, + SQLITE_FLOAT, + SQLITE_TEXT +}; +SQLITE_PRIVATE const char *sqlite3StdType[] = { + "ANY", + "BLOB", + "INT", + "INTEGER", + "REAL", + "TEXT" +}; + /************** End of global.c **********************************************/ /************** Begin file status.c ******************************************/ /* @@ -21061,7 +22254,7 @@ typedef struct AuxData AuxData; typedef struct VdbeCursor VdbeCursor; struct VdbeCursor { u8 eCurType; /* One of the CURTYPE_* values above */ - i8 iDb; /* Index of cursor database in db->aDb[] (or -1) */ + i8 iDb; /* Index of cursor database in db->aDb[] */ u8 nullRow; /* True if pointing to a row with no data */ u8 deferredMoveto; /* A call to sqlite3BtreeMoveto() is needed */ u8 isTable; /* True for rowid tables. False for indexes */ @@ -21072,11 +22265,13 @@ struct VdbeCursor { Bool isEphemeral:1; /* True for an ephemeral table */ Bool useRandomRowid:1; /* Generate new record numbers semi-randomly */ Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */ - Bool hasBeenDuped:1; /* This cursor was source or target of OP_OpenDup */ + Bool noReuse:1; /* OpenEphemeral may not reuse this cursor */ u16 seekHit; /* See the OP_SeekHit and OP_IfNoHope opcodes */ - Btree *pBtx; /* Separate file holding temporary table */ + union { /* pBtx for isEphermeral. pAltMap otherwise */ + Btree *pBtx; /* Separate file holding temporary table */ + u32 *aAltMap; /* Mapping from table to index column numbers */ + } ub; i64 seqCount; /* Sequence counter */ - u32 *aAltMap; /* Mapping from table to index column numbers */ /* Cached OP_Column parse information is only valid if cacheStatus matches ** Vdbe.cacheCtr. Vdbe.cacheCtr will never take on the value of @@ -21118,6 +22313,11 @@ struct VdbeCursor { u32 aType[1]; /* Type values record decode. MUST BE LAST */ }; +/* Return true if P is a null-only cursor +*/ +#define IsNullCursor(P) \ + ((P)->eCurType==CURTYPE_PSEUDO && (P)->nullRow && (P)->seekResult==0) + /* ** A value for VdbeCursor.cacheStatus that means the cache is always invalid. @@ -21166,8 +22366,8 @@ struct VdbeFrame { int nMem; /* Number of entries in aMem */ int nChildMem; /* Number of memory cells for child frame */ int nChildCsr; /* Number of cursors for child frame */ - int nChange; /* Statement changes (Vdbe.nChange) */ - int nDbChange; /* Value of db->nChange */ + i64 nChange; /* Statement changes (Vdbe.nChange) */ + i64 nDbChange; /* Value of db->nChange */ }; /* Magic number for sanity checking on VdbeFrame objects */ @@ -21192,16 +22392,16 @@ struct sqlite3_value { const char *zPType; /* Pointer type when MEM_Term|MEM_Subtype|MEM_Null */ FuncDef *pDef; /* Used only when flags==MEM_Agg */ } u; + char *z; /* String or BLOB value */ + int n; /* Number of characters in string value, excluding '\0' */ u16 flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */ u8 enc; /* SQLITE_UTF8, SQLITE_UTF16BE, SQLITE_UTF16LE */ u8 eSubtype; /* Subtype for this value */ - int n; /* Number of characters in string value, excluding '\0' */ - char *z; /* String or BLOB value */ /* ShallowCopy only needs to copy the information above */ - char *zMalloc; /* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */ + sqlite3 *db; /* The associated database connection */ int szMalloc; /* Size of the zMalloc allocation */ u32 uTemp; /* Transient storage for serial_type in OP_MakeRecord */ - sqlite3 *db; /* The associated database connection */ + char *zMalloc; /* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */ void (*xDel)(void*);/* Destructor for Mem.z - only valid if MEM_Dyn */ #ifdef SQLITE_DEBUG Mem *pScopyFrom; /* This Mem is a shallow copy of pScopyFrom */ @@ -21213,11 +22413,43 @@ struct sqlite3_value { ** Size of struct Mem not including the Mem.zMalloc member or anything that ** follows. */ -#define MEMCELLSIZE offsetof(Mem,zMalloc) +#define MEMCELLSIZE offsetof(Mem,db) -/* One or more of the following flags are set to indicate the validOK +/* One or more of the following flags are set to indicate the ** representations of the value stored in the Mem struct. ** +** * MEM_Null An SQL NULL value +** +** * MEM_Null|MEM_Zero An SQL NULL with the virtual table +** UPDATE no-change flag set +** +** * MEM_Null|MEM_Term| An SQL NULL, but also contains a +** MEM_Subtype pointer accessible using +** sqlite3_value_pointer(). +** +** * MEM_Null|MEM_Cleared Special SQL NULL that compares non-equal +** to other NULLs even using the IS operator. +** +** * MEM_Str A string, stored in Mem.z with +** length Mem.n. Zero-terminated if +** MEM_Term is set. This flag is +** incompatible with MEM_Blob and +** MEM_Null, but can appear with MEM_Int, +** MEM_Real, and MEM_IntReal. +** +** * MEM_Blob A blob, stored in Mem.z length Mem.n. +** Incompatible with MEM_Str, MEM_Null, +** MEM_Int, MEM_Real, and MEM_IntReal. +** +** * MEM_Blob|MEM_Zero A blob in Mem.z of length Mem.n plus +** MEM.u.i extra 0x00 bytes at the end. +** +** * MEM_Int Integer stored in Mem.u.i. +** +** * MEM_Real Real stored in Mem.u.r. +** +** * MEM_IntReal Real stored as an integer in Mem.u.i. +** ** If the MEM_Null flag is set, then the value is an SQL NULL value. ** For a pointer type created using sqlite3_bind_pointer() or ** sqlite3_result_pointer() the MEM_Term and MEM_Subtype flags are also set. @@ -21228,6 +22460,7 @@ struct sqlite3_value { ** set, then the string is nul terminated. The MEM_Int and MEM_Real ** flags may coexist with the MEM_Str flag. */ +#define MEM_Undefined 0x0000 /* Value is undefined */ #define MEM_Null 0x0001 /* Value is NULL (or a pointer) */ #define MEM_Str 0x0002 /* Value is a string */ #define MEM_Int 0x0004 /* Value is an integer */ @@ -21235,28 +22468,24 @@ struct sqlite3_value { #define MEM_Blob 0x0010 /* Value is a BLOB */ #define MEM_IntReal 0x0020 /* MEM_Int that stringifies like MEM_Real */ #define MEM_AffMask 0x003f /* Mask of affinity bits */ -#define MEM_FromBind 0x0040 /* Value originates from sqlite3_bind() */ -#define MEM_Undefined 0x0080 /* Value is undefined */ -#define MEM_Cleared 0x0100 /* NULL set by OP_Null, not from data */ -#define MEM_TypeMask 0xc1bf /* Mask of type bits */ - -/* Whenever Mem contains a valid string or blob representation, one of -** the following flags must be set to determine the memory management -** policy for Mem.z. The MEM_Term flag tells us whether or not the -** string is \000 or \u0000 terminated +/* Extra bits that modify the meanings of the core datatypes above */ +#define MEM_FromBind 0x0040 /* Value originates from sqlite3_bind() */ + /* 0x0080 // Available */ +#define MEM_Cleared 0x0100 /* NULL set by OP_Null, not from data */ #define MEM_Term 0x0200 /* String in Mem.z is zero terminated */ -#define MEM_Dyn 0x0400 /* Need to call Mem.xDel() on Mem.z */ -#define MEM_Static 0x0800 /* Mem.z points to a static string */ -#define MEM_Ephem 0x1000 /* Mem.z points to an ephemeral string */ -#define MEM_Agg 0x2000 /* Mem.z points to an agg function context */ -#define MEM_Zero 0x4000 /* Mem.i contains count of 0s appended to blob */ -#define MEM_Subtype 0x8000 /* Mem.eSubtype is valid */ -#ifdef SQLITE_OMIT_INCRBLOB - #undef MEM_Zero - #define MEM_Zero 0x0000 -#endif +#define MEM_Zero 0x0400 /* Mem.i contains count of 0s appended to blob */ +#define MEM_Subtype 0x0800 /* Mem.eSubtype is valid */ +#define MEM_TypeMask 0x0dbf /* Mask of type bits */ + +/* Bits that determine the storage for Mem.z for a string or blob or +** aggregate accumulator. +*/ +#define MEM_Dyn 0x1000 /* Need to call Mem.xDel() on Mem.z */ +#define MEM_Static 0x2000 /* Mem.z points to a static string */ +#define MEM_Ephem 0x4000 /* Mem.z points to an ephemeral string */ +#define MEM_Agg 0x8000 /* Mem.z points to an agg function context */ /* Return TRUE if Mem X contains dynamically allocated content - anything ** that needs to be deallocated to avoid a leak. @@ -21278,11 +22507,15 @@ struct sqlite3_value { && (X)->n==0 && (X)->u.nZero==0) /* -** Return true if a memory cell is not marked as invalid. This macro +** Return true if a memory cell has been initialized and is valid. ** is for use inside assert() statements only. +** +** A Memory cell is initialized if at least one of the +** MEM_Null, MEM_Str, MEM_Int, MEM_Real, MEM_Blob, or MEM_IntReal bits +** is set. It is "undefined" if all those bits are zero. */ #ifdef SQLITE_DEBUG -#define memIsValid(M) ((M)->flags & MEM_Undefined)==0 +#define memIsValid(M) ((M)->flags & MEM_AffMask)!=0 #endif /* @@ -21320,6 +22553,7 @@ struct sqlite3_context { Vdbe *pVdbe; /* The VM that owns this context */ int iOp; /* Instruction number of OP_Function */ int isError; /* Error code returned by the function. */ + u8 enc; /* Encoding to use for results */ u8 skipFlag; /* Skip accumulator loading if true */ u8 argc; /* Number of arguments */ sqlite3_value *argv[1]; /* Argument set */ @@ -21368,13 +22602,12 @@ struct Vdbe { Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */ Parse *pParse; /* Parsing context used to create this Vdbe */ ynVar nVar; /* Number of entries in aVar[] */ - u32 iVdbeMagic; /* Magic number defining state of the SQL statement */ int nMem; /* Number of memory locations currently allocated */ int nCursor; /* Number of slots in apCsr[] */ u32 cacheCtr; /* VdbeCursor row cache generation counter */ int pc; /* The program counter */ int rc; /* Value to return */ - int nChange; /* Number of db changes made since last reset */ + i64 nChange; /* Number of db changes made since last reset */ int iStatement; /* Statement number (or 0 if has no opened stmt) */ i64 iCurrentTime; /* Value of julianday('now') for this statement */ i64 nFkConstraint; /* Number of imm. FK constraints this VM */ @@ -21406,17 +22639,16 @@ struct Vdbe { u8 errorAction; /* Recovery action to do in case of an error */ u8 minWriteFileFormat; /* Minimum file format for writable database files */ u8 prepFlags; /* SQLITE_PREPARE_* flags */ - u8 doingRerun; /* True if rerunning after an auto-reprepare */ + u8 eVdbeState; /* On of the VDBE_*_STATE values */ bft expired:2; /* 1: recompile VM immediately 2: when convenient */ bft explain:2; /* True if EXPLAIN present on SQL command */ bft changeCntOn:1; /* True to update the change-counter */ - bft runOnlyOnce:1; /* Automatically expire on reset */ bft usesStmtJournal:1; /* True if uses a statement journal */ bft readOnly:1; /* True for statements that do not write */ bft bIsReader:1; /* True for statements that read */ yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ yDbMask lockMask; /* Subset of btreeMask that requires a lock */ - u32 aCounter[7]; /* Counters used by sqlite3_stmt_status() */ + u32 aCounter[9]; /* Counters used by sqlite3_stmt_status() */ char *zSql; /* Text of the SQL statement that generated this */ #ifdef SQLITE_ENABLE_NORMALIZE char *zNormSql; /* Normalization of the associated SQL statement */ @@ -21437,13 +22669,12 @@ struct Vdbe { }; /* -** The following are allowed values for Vdbe.magic +** The following are allowed values for Vdbe.eVdbeState */ -#define VDBE_MAGIC_INIT 0x16bceaa5 /* Building a VDBE program */ -#define VDBE_MAGIC_RUN 0x2df20da3 /* VDBE is ready to execute */ -#define VDBE_MAGIC_HALT 0x319c2973 /* VDBE has completed execution */ -#define VDBE_MAGIC_RESET 0x48fa9f76 /* Reset and ready to run again */ -#define VDBE_MAGIC_DEAD 0x5606c3c8 /* The VDBE has been deallocated */ +#define VDBE_INIT_STATE 0 /* Prepared statement under construction */ +#define VDBE_READY_STATE 1 /* Ready to run but not yet started */ +#define VDBE_RUN_STATE 2 /* Run in progress */ +#define VDBE_HALT_STATE 3 /* Finished. Need reset() or finalize() */ /* ** Structure used to store the context required by the @@ -21458,6 +22689,7 @@ struct PreUpdate { UnpackedRecord *pUnpacked; /* Unpacked version of aRecord[] */ UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */ int iNewReg; /* Register for new.* values */ + int iBlobWrite; /* Value returned by preupdate_blobwrite() */ i64 iKey1; /* First key value passed to hook */ i64 iKey2; /* Second key value passed to hook */ Mem *aNew; /* Array of new.* values */ @@ -21465,19 +22697,50 @@ struct PreUpdate { Index *pPk; /* PK index if pTab is WITHOUT ROWID */ }; +/* +** An instance of this object is used to pass an vector of values into +** OP_VFilter, the xFilter method of a virtual table. The vector is the +** set of values on the right-hand side of an IN constraint. +** +** The value as passed into xFilter is an sqlite3_value with a "pointer" +** type, such as is generated by sqlite3_result_pointer() and read by +** sqlite3_value_pointer. Such values have MEM_Term|MEM_Subtype|MEM_Null +** and a subtype of 'p'. The sqlite3_vtab_in_first() and _next() interfaces +** know how to use this object to step through all the values in the +** right operand of the IN constraint. +*/ +typedef struct ValueList ValueList; +struct ValueList { + BtCursor *pCsr; /* An ephemeral table holding all values */ + sqlite3_value *pOut; /* Register to hold each decoded output value */ +}; + +/* Size of content associated with serial types that fit into a +** single-byte varint. +*/ +#ifndef SQLITE_AMALGAMATION +SQLITE_PRIVATE const u8 sqlite3SmallTypeSizes[]; +#endif + /* ** Function prototypes */ SQLITE_PRIVATE void sqlite3VdbeError(Vdbe*, const char *, ...); SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *, VdbeCursor*); +SQLITE_PRIVATE void sqlite3VdbeFreeCursorNN(Vdbe*,VdbeCursor*); void sqliteVdbePopStack(Vdbe*,int); +SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeHandleMovedCursor(VdbeCursor *p); SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeFinishMoveto(VdbeCursor*); -SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor**, u32*); SQLITE_PRIVATE int sqlite3VdbeCursorRestore(VdbeCursor*); SQLITE_PRIVATE u32 sqlite3VdbeSerialTypeLen(u32); SQLITE_PRIVATE u8 sqlite3VdbeOneByteSerialTypeLen(u8); -SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(unsigned char*, Mem*, u32); -SQLITE_PRIVATE u32 sqlite3VdbeSerialGet(const unsigned char*, u32, Mem*); +#ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT +SQLITE_PRIVATE u64 sqlite3FloatSwap(u64 in); +# define swapMixedEndianFloat(X) X = sqlite3FloatSwap(X) +#else +# define swapMixedEndianFloat(X) +#endif +SQLITE_PRIVATE void sqlite3VdbeSerialGet(const unsigned char*, u32, Mem*); SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(sqlite3*, AuxData**, int, int); int sqlite2BtreeKeyCompare(BtCursor *, const void *, int, int, int *); @@ -21501,7 +22764,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemCopy(Mem*, const Mem*); SQLITE_PRIVATE void sqlite3VdbeMemShallowCopy(Mem*, const Mem*, int); SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem*, Mem*); SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem*); -SQLITE_PRIVATE int sqlite3VdbeMemSetStr(Mem*, const char*, int, u8, void(*)(void*)); +SQLITE_PRIVATE int sqlite3VdbeMemSetStr(Mem*, const char*, i64, u8, void(*)(void*)); SQLITE_PRIVATE void sqlite3VdbeMemSetInt64(Mem*, i64); #ifdef SQLITE_OMIT_FLOATING_POINT # define sqlite3VdbeMemSetDouble sqlite3VdbeMemSetInt64 @@ -21511,14 +22774,19 @@ SQLITE_PRIVATE void sqlite3VdbeMemSetDouble(Mem*, double); SQLITE_PRIVATE void sqlite3VdbeMemSetPointer(Mem*, void*, const char*, void(*)(void*)); SQLITE_PRIVATE void sqlite3VdbeMemInit(Mem*,sqlite3*,u16); SQLITE_PRIVATE void sqlite3VdbeMemSetNull(Mem*); +#ifndef SQLITE_OMIT_INCRBLOB SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem*,int); +#else +SQLITE_PRIVATE int sqlite3VdbeMemSetZeroBlob(Mem*,int); +#endif #ifdef SQLITE_DEBUG SQLITE_PRIVATE int sqlite3VdbeMemIsRowSet(const Mem*); #endif SQLITE_PRIVATE int sqlite3VdbeMemSetRowSet(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem*, u8, u8); -SQLITE_PRIVATE i64 sqlite3VdbeIntValue(Mem*); +SQLITE_PRIVATE int sqlite3IntFloatCompare(i64,double); +SQLITE_PRIVATE i64 sqlite3VdbeIntValue(const Mem*); SQLITE_PRIVATE int sqlite3VdbeMemIntegerify(Mem*); SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem*); SQLITE_PRIVATE int sqlite3VdbeBooleanValue(Mem*, int ifNull); @@ -21529,6 +22797,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemCast(Mem*,u8,u8); SQLITE_PRIVATE int sqlite3VdbeMemFromBtree(BtCursor*,u32,u32,Mem*); SQLITE_PRIVATE int sqlite3VdbeMemFromBtreeZeroOffset(BtCursor*,u32,Mem*); SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p); +SQLITE_PRIVATE void sqlite3VdbeMemReleaseMalloc(Mem*p); SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem*, FuncDef*); #ifndef SQLITE_OMIT_WINDOWFUNC SQLITE_PRIVATE int sqlite3VdbeMemAggValue(Mem*, Mem*, FuncDef*); @@ -21546,7 +22815,8 @@ SQLITE_PRIVATE void sqlite3VdbeFrameMemDel(void*); /* Destructor on Mem */ SQLITE_PRIVATE void sqlite3VdbeFrameDelete(VdbeFrame*); /* Actually deletes the Frame */ SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *); #ifdef SQLITE_ENABLE_PREUPDATE_HOOK -SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int); +SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( + Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int,int); #endif SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p); @@ -21930,8 +23200,7 @@ SQLITE_API int sqlite3_db_status( db->pnBytesFreed = &nByte; for(pVdbe=db->pVdbe; pVdbe; pVdbe=pVdbe->pNext){ - sqlite3VdbeClearObject(db, pVdbe); - sqlite3DbFree(db, pVdbe); + sqlite3VdbeDelete(pVdbe); } db->pnBytesFreed = 0; @@ -22495,8 +23764,10 @@ static void clearYMD_HMS_TZ(DateTime *p){ ** is available. This routine returns 0 on success and ** non-zero on any kind of error. ** -** If the sqlite3GlobalConfig.bLocaltimeFault variable is true then this -** routine will always fail. +** If the sqlite3GlobalConfig.bLocaltimeFault variable is non-zero then this +** routine will always fail. If bLocaltimeFault is nonzero and +** sqlite3GlobalConfig.xAltLocaltime is not NULL, then xAltLocaltime() is +** invoked in place of the OS-defined localtime() function. ** ** EVIDENCE-OF: R-62172-00036 In this implementation, the standard C ** library function localtime_r() is used to assist in the calculation of @@ -22512,14 +23783,30 @@ static int osLocaltime(time_t *t, struct tm *pTm){ sqlite3_mutex_enter(mutex); pX = localtime(t); #ifndef SQLITE_UNTESTABLE - if( sqlite3GlobalConfig.bLocaltimeFault ) pX = 0; + if( sqlite3GlobalConfig.bLocaltimeFault ){ + if( sqlite3GlobalConfig.xAltLocaltime!=0 + && 0==sqlite3GlobalConfig.xAltLocaltime((const void*)t,(void*)pTm) + ){ + pX = pTm; + }else{ + pX = 0; + } + } #endif if( pX ) *pTm = *pX; +#if SQLITE_THREADSAFE>0 sqlite3_mutex_leave(mutex); +#endif rc = pX==0; #else #ifndef SQLITE_UNTESTABLE - if( sqlite3GlobalConfig.bLocaltimeFault ) return 1; + if( sqlite3GlobalConfig.bLocaltimeFault ){ + if( sqlite3GlobalConfig.xAltLocaltime!=0 ){ + return sqlite3GlobalConfig.xAltLocaltime((const void*)t,(void*)pTm); + }else{ + return 1; + } + } #endif #if HAVE_LOCALTIME_R rc = localtime_r(t, pTm)==0; @@ -22534,67 +23821,56 @@ static int osLocaltime(time_t *t, struct tm *pTm){ #ifndef SQLITE_OMIT_LOCALTIME /* -** Compute the difference (in milliseconds) between localtime and UTC -** (a.k.a. GMT) for the time value p where p is in UTC. If no error occurs, -** return this value and set *pRc to SQLITE_OK. -** -** Or, if an error does occur, set *pRc to SQLITE_ERROR. The returned value -** is undefined in this case. +** Assuming the input DateTime is UTC, move it to its localtime equivalent. */ -static sqlite3_int64 localtimeOffset( - DateTime *p, /* Date at which to calculate offset */ - sqlite3_context *pCtx, /* Write error here if one occurs */ - int *pRc /* OUT: Error code. SQLITE_OK or ERROR */ +static int toLocaltime( + DateTime *p, /* Date at which to calculate offset */ + sqlite3_context *pCtx /* Write error here if one occurs */ ){ - DateTime x, y; time_t t; struct tm sLocal; + int iYearDiff; /* Initialize the contents of sLocal to avoid a compiler warning. */ memset(&sLocal, 0, sizeof(sLocal)); - x = *p; - computeYMD_HMS(&x); - if( x.Y<1971 || x.Y>=2038 ){ + computeJD(p); + if( p->iJD<2108667600*(i64)100000 /* 1970-01-01 */ + || p->iJD>2130141456*(i64)100000 /* 2038-01-18 */ + ){ /* EVIDENCE-OF: R-55269-29598 The localtime_r() C function normally only ** works for years between 1970 and 2037. For dates outside this range, ** SQLite attempts to map the year into an equivalent year within this ** range, do the calculation, then map the year back. */ - x.Y = 2000; - x.M = 1; - x.D = 1; - x.h = 0; - x.m = 0; - x.s = 0.0; - } else { - int s = (int)(x.s + 0.5); - x.s = s; + DateTime x = *p; + computeYMD_HMS(&x); + iYearDiff = (2000 + x.Y%4) - x.Y; + x.Y += iYearDiff; + x.validJD = 0; + computeJD(&x); + t = (time_t)(x.iJD/1000 - 21086676*(i64)10000); + }else{ + iYearDiff = 0; + t = (time_t)(p->iJD/1000 - 21086676*(i64)10000); } - x.tz = 0; - x.validJD = 0; - computeJD(&x); - t = (time_t)(x.iJD/1000 - 21086676*(i64)10000); if( osLocaltime(&t, &sLocal) ){ sqlite3_result_error(pCtx, "local time unavailable", -1); - *pRc = SQLITE_ERROR; - return 0; + return SQLITE_ERROR; } - y.Y = sLocal.tm_year + 1900; - y.M = sLocal.tm_mon + 1; - y.D = sLocal.tm_mday; - y.h = sLocal.tm_hour; - y.m = sLocal.tm_min; - y.s = sLocal.tm_sec; - y.validYMD = 1; - y.validHMS = 1; - y.validJD = 0; - y.rawS = 0; - y.validTZ = 0; - y.isError = 0; - computeJD(&y); - *pRc = SQLITE_OK; - return y.iJD - x.iJD; + p->Y = sLocal.tm_year + 1900 - iYearDiff; + p->M = sLocal.tm_mon + 1; + p->D = sLocal.tm_mday; + p->h = sLocal.tm_hour; + p->m = sLocal.tm_min; + p->s = sLocal.tm_sec + (p->iJD%1000)*0.001; + p->validYMD = 1; + p->validHMS = 1; + p->validJD = 0; + p->rawS = 0; + p->validTZ = 0; + p->isError = 0; + return SQLITE_OK; } #endif /* SQLITE_OMIT_LOCALTIME */ @@ -22607,18 +23883,17 @@ static sqlite3_int64 localtimeOffset( ** of several units of time. */ static const struct { - u8 eType; /* Transformation type code */ - u8 nName; /* Length of th name */ - char *zName; /* Name of the transformation */ - double rLimit; /* Maximum NNN value for this transform */ - double rXform; /* Constant used for this transform */ + u8 nName; /* Length of the name */ + char zName[7]; /* Name of the transformation */ + float rLimit; /* Maximum NNN value for this transform */ + float rXform; /* Constant used for this transform */ } aXformType[] = { - { 0, 6, "second", 464269060800.0, 1000.0 }, - { 0, 6, "minute", 7737817680.0, 60000.0 }, - { 0, 4, "hour", 128963628.0, 3600000.0 }, - { 0, 3, "day", 5373485.0, 86400000.0 }, - { 1, 5, "month", 176546.0, 2592000000.0 }, - { 2, 4, "year", 14713.0, 31536000000.0 }, + { 6, "second", 4.6427e+14, 1.0 }, + { 6, "minute", 7.7379e+12, 60.0 }, + { 4, "hour", 1.2897e+11, 3600.0 }, + { 3, "day", 5373485.0, 86400.0 }, + { 5, "month", 176546.0, 2592000.0 }, + { 4, "year", 14713.0, 31536000.0 }, }; /* @@ -22649,11 +23924,55 @@ static int parseModifier( sqlite3_context *pCtx, /* Function context */ const char *z, /* The text of the modifier */ int n, /* Length of zMod in bytes */ - DateTime *p /* The date/time value to be modified */ + DateTime *p, /* The date/time value to be modified */ + int idx /* Parameter index of the modifier */ ){ int rc = 1; double r; switch(sqlite3UpperToLower[(u8)z[0]] ){ + case 'a': { + /* + ** auto + ** + ** If rawS is available, then interpret as a julian day number, or + ** a unix timestamp, depending on its magnitude. + */ + if( sqlite3_stricmp(z, "auto")==0 ){ + if( idx>1 ) return 1; /* IMP: R-33611-57934 */ + if( !p->rawS || p->validJD ){ + rc = 0; + p->rawS = 0; + }else if( p->s>=-21086676*(i64)10000 /* -4713-11-24 12:00:00 */ + && p->s<=(25340230*(i64)10000)+799 /* 9999-12-31 23:59:59 */ + ){ + r = p->s*1000.0 + 210866760000000.0; + clearYMD_HMS_TZ(p); + p->iJD = (sqlite3_int64)(r + 0.5); + p->validJD = 1; + p->rawS = 0; + rc = 0; + } + } + break; + } + case 'j': { + /* + ** julianday + ** + ** Always interpret the prior number as a julian-day value. If this + ** is not the first modifier, or if the prior argument is not a numeric + ** value in the allowed range of julian day numbers understood by + ** SQLite (0..5373484.5) then the result will be NULL. + */ + if( sqlite3_stricmp(z, "julianday")==0 ){ + if( idx>1 ) return 1; /* IMP: R-31176-64601 */ + if( p->validJD && p->rawS ){ + rc = 0; + p->rawS = 0; + } + } + break; + } #ifndef SQLITE_OMIT_LOCALTIME case 'l': { /* localtime @@ -22662,9 +23981,7 @@ static int parseModifier( ** show local time. */ if( sqlite3_stricmp(z, "localtime")==0 && sqlite3NotPureFunc(pCtx) ){ - computeJD(p); - p->iJD += localtimeOffset(p, pCtx, &rc); - clearYMD_HMS_TZ(p); + rc = toLocaltime(p, pCtx); } break; } @@ -22677,6 +23994,7 @@ static int parseModifier( ** seconds since 1970. Convert to a real julian day number. */ if( sqlite3_stricmp(z, "unixepoch")==0 && p->rawS ){ + if( idx>1 ) return 1; /* IMP: R-49255-55373 */ r = p->s*1000.0 + 210866760000000.0; if( r>=0.0 && r<464269060800000.0 ){ clearYMD_HMS_TZ(p); @@ -22689,18 +24007,31 @@ static int parseModifier( #ifndef SQLITE_OMIT_LOCALTIME else if( sqlite3_stricmp(z, "utc")==0 && sqlite3NotPureFunc(pCtx) ){ if( p->tzSet==0 ){ - sqlite3_int64 c1; + i64 iOrigJD; /* Original localtime */ + i64 iGuess; /* Guess at the corresponding utc time */ + int cnt = 0; /* Safety to prevent infinite loop */ + int iErr; /* Guess is off by this much */ + computeJD(p); - c1 = localtimeOffset(p, pCtx, &rc); - if( rc==SQLITE_OK ){ - p->iJD -= c1; - clearYMD_HMS_TZ(p); - p->iJD += c1 - localtimeOffset(p, pCtx, &rc); - } + iGuess = iOrigJD = p->iJD; + iErr = 0; + do{ + DateTime new; + memset(&new, 0, sizeof(new)); + iGuess -= iErr; + new.iJD = iGuess; + new.validJD = 1; + rc = toLocaltime(&new, pCtx); + if( rc ) return rc; + computeJD(&new); + iErr = new.iJD - iOrigJD; + }while( iErr && cnt++<3 ); + memset(p, 0, sizeof(*p)); + p->iJD = iGuess; + p->validJD = 1; p->tzSet = 1; - }else{ - rc = SQLITE_OK; } + rc = SQLITE_OK; } #endif break; @@ -22816,9 +24147,10 @@ static int parseModifier( && sqlite3_strnicmp(aXformType[i].zName, z, n)==0 && r>-aXformType[i].rLimit && rM += (int)r; x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12; @@ -22828,8 +24160,9 @@ static int parseModifier( r -= (int)r; break; } - case 2: { /* Special processing to add years */ + case 5: { /* Special processing to add years */ int y = (int)r; + assert( strcmp(aXformType[i].zName,"year")==0 ); computeYMD_HMS(p); p->Y += y; p->validJD = 0; @@ -22838,7 +24171,7 @@ static int parseModifier( } } computeJD(p); - p->iJD += (sqlite3_int64)(r*aXformType[i].rXform + rRounder); + p->iJD += (sqlite3_int64)(r*1000.0*aXformType[i].rXform + rRounder); rc = 0; break; } @@ -22888,7 +24221,7 @@ static int isDate( for(i=1; iisError || !validJulianDay(p->iJD) ) return 1; @@ -22918,6 +24251,24 @@ static void juliandayFunc( } } +/* +** unixepoch( TIMESTRING, MOD, MOD, ...) +** +** Return the number of seconds (including fractional seconds) since +** the unix epoch of 1970-01-01 00:00:00 GMT. +*/ +static void unixepochFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + DateTime x; + if( isDate(context, argc, argv, &x)==0 ){ + computeJD(&x); + sqlite3_result_int64(context, x.iJD/1000 - 21086676*(i64)10000); + } +} + /* ** datetime( TIMESTRING, MOD, MOD, ...) ** @@ -22930,11 +24281,38 @@ static void datetimeFunc( ){ DateTime x; if( isDate(context, argc, argv, &x)==0 ){ - char zBuf[100]; + int Y, s; + char zBuf[24]; computeYMD_HMS(&x); - sqlite3_snprintf(sizeof(zBuf), zBuf, "%04d-%02d-%02d %02d:%02d:%02d", - x.Y, x.M, x.D, x.h, x.m, (int)(x.s)); - sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + Y = x.Y; + if( Y<0 ) Y = -Y; + zBuf[1] = '0' + (Y/1000)%10; + zBuf[2] = '0' + (Y/100)%10; + zBuf[3] = '0' + (Y/10)%10; + zBuf[4] = '0' + (Y)%10; + zBuf[5] = '-'; + zBuf[6] = '0' + (x.M/10)%10; + zBuf[7] = '0' + (x.M)%10; + zBuf[8] = '-'; + zBuf[9] = '0' + (x.D/10)%10; + zBuf[10] = '0' + (x.D)%10; + zBuf[11] = ' '; + zBuf[12] = '0' + (x.h/10)%10; + zBuf[13] = '0' + (x.h)%10; + zBuf[14] = ':'; + zBuf[15] = '0' + (x.m/10)%10; + zBuf[16] = '0' + (x.m)%10; + zBuf[17] = ':'; + s = (int)x.s; + zBuf[18] = '0' + (s/10)%10; + zBuf[19] = '0' + (s)%10; + zBuf[20] = 0; + if( x.Y<0 ){ + zBuf[0] = '-'; + sqlite3_result_text(context, zBuf, 20, SQLITE_TRANSIENT); + }else{ + sqlite3_result_text(context, &zBuf[1], 19, SQLITE_TRANSIENT); + } } } @@ -22950,10 +24328,20 @@ static void timeFunc( ){ DateTime x; if( isDate(context, argc, argv, &x)==0 ){ - char zBuf[100]; + int s; + char zBuf[16]; computeHMS(&x); - sqlite3_snprintf(sizeof(zBuf), zBuf, "%02d:%02d:%02d", x.h, x.m, (int)x.s); - sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + zBuf[0] = '0' + (x.h/10)%10; + zBuf[1] = '0' + (x.h)%10; + zBuf[2] = ':'; + zBuf[3] = '0' + (x.m/10)%10; + zBuf[4] = '0' + (x.m)%10; + zBuf[5] = ':'; + s = (int)x.s; + zBuf[6] = '0' + (s/10)%10; + zBuf[7] = '0' + (s)%10; + zBuf[8] = 0; + sqlite3_result_text(context, zBuf, 8, SQLITE_TRANSIENT); } } @@ -22969,10 +24357,28 @@ static void dateFunc( ){ DateTime x; if( isDate(context, argc, argv, &x)==0 ){ - char zBuf[100]; + int Y; + char zBuf[16]; computeYMD(&x); - sqlite3_snprintf(sizeof(zBuf), zBuf, "%04d-%02d-%02d", x.Y, x.M, x.D); - sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + Y = x.Y; + if( Y<0 ) Y = -Y; + zBuf[1] = '0' + (Y/1000)%10; + zBuf[2] = '0' + (Y/100)%10; + zBuf[3] = '0' + (Y/10)%10; + zBuf[4] = '0' + (Y)%10; + zBuf[5] = '-'; + zBuf[6] = '0' + (x.M/10)%10; + zBuf[7] = '0' + (x.M)%10; + zBuf[8] = '-'; + zBuf[9] = '0' + (x.D/10)%10; + zBuf[10] = '0' + (x.D)%10; + zBuf[11] = 0; + if( x.Y<0 ){ + zBuf[0] = '-'; + sqlite3_result_text(context, zBuf, 11, SQLITE_TRANSIENT); + }else{ + sqlite3_result_text(context, &zBuf[1], 10, SQLITE_TRANSIENT); + } } } @@ -23001,131 +24407,100 @@ static void strftimeFunc( sqlite3_value **argv ){ DateTime x; - u64 n; size_t i,j; - char *z; sqlite3 *db; const char *zFmt; - char zBuf[100]; + sqlite3_str sRes; + + if( argc==0 ) return; zFmt = (const char*)sqlite3_value_text(argv[0]); if( zFmt==0 || isDate(context, argc-1, argv+1, &x) ) return; db = sqlite3_context_db_handle(context); - for(i=0, n=1; zFmt[i]; i++, n++){ - if( zFmt[i]=='%' ){ - switch( zFmt[i+1] ){ - case 'd': - case 'H': - case 'm': - case 'M': - case 'S': - case 'W': - n++; - /* fall thru */ - case 'w': - case '%': - break; - case 'f': - n += 8; - break; - case 'j': - n += 3; - break; - case 'Y': - n += 8; - break; - case 's': - case 'J': - n += 50; - break; - default: - return; /* ERROR. return a NULL */ - } - i++; - } - } - testcase( n==sizeof(zBuf)-1 ); - testcase( n==sizeof(zBuf) ); - testcase( n==(u64)db->aLimit[SQLITE_LIMIT_LENGTH]+1 ); - testcase( n==(u64)db->aLimit[SQLITE_LIMIT_LENGTH] ); - if( n(u64)db->aLimit[SQLITE_LIMIT_LENGTH] ){ - sqlite3_result_error_toobig(context); - return; - }else{ - z = sqlite3DbMallocRawNN(db, (int)n); - if( z==0 ){ - sqlite3_result_error_nomem(context); - return; - } - } + sqlite3StrAccumInit(&sRes, 0, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]); + computeJD(&x); computeYMD_HMS(&x); for(i=j=0; zFmt[i]; i++){ - if( zFmt[i]!='%' ){ - z[j++] = zFmt[i]; - }else{ - i++; - switch( zFmt[i] ){ - case 'd': sqlite3_snprintf(3, &z[j],"%02d",x.D); j+=2; break; - case 'f': { - double s = x.s; - if( s>59.999 ) s = 59.999; - sqlite3_snprintf(7, &z[j],"%06.3f", s); - j += sqlite3Strlen30(&z[j]); - break; + if( zFmt[i]!='%' ) continue; + if( j59.999 ) s = 59.999; + sqlite3_str_appendf(&sRes, "%06.3f", s); + break; + } + case 'H': { + sqlite3_str_appendf(&sRes, "%02d", x.h); + break; + } + case 'W': /* Fall thru */ + case 'j': { + int nDay; /* Number of days since 1st day of year */ + DateTime y = x; + y.validJD = 0; + y.M = 1; + y.D = 1; + computeJD(&y); + nDay = (int)((x.iJD-y.iJD+43200000)/86400000); + if( zFmt[i]=='W' ){ + int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */ + wd = (int)(((x.iJD+43200000)/86400000)%7); + sqlite3_str_appendf(&sRes,"%02d",(nDay+7-wd)/7); + }else{ + sqlite3_str_appendf(&sRes,"%03d",nDay+1); } - case 'H': sqlite3_snprintf(3, &z[j],"%02d",x.h); j+=2; break; - case 'W': /* Fall thru */ - case 'j': { - int nDay; /* Number of days since 1st day of year */ - DateTime y = x; - y.validJD = 0; - y.M = 1; - y.D = 1; - computeJD(&y); - nDay = (int)((x.iJD-y.iJD+43200000)/86400000); - if( zFmt[i]=='W' ){ - int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */ - wd = (int)(((x.iJD+43200000)/86400000)%7); - sqlite3_snprintf(3, &z[j],"%02d",(nDay+7-wd)/7); - j += 2; - }else{ - sqlite3_snprintf(4, &z[j],"%03d",nDay+1); - j += 3; - } - break; - } - case 'J': { - sqlite3_snprintf(20, &z[j],"%.16g",x.iJD/86400000.0); - j+=sqlite3Strlen30(&z[j]); - break; - } - case 'm': sqlite3_snprintf(3, &z[j],"%02d",x.M); j+=2; break; - case 'M': sqlite3_snprintf(3, &z[j],"%02d",x.m); j+=2; break; - case 's': { - i64 iS = (i64)(x.iJD/1000 - 21086676*(i64)10000); - sqlite3Int64ToText(iS, &z[j]); - j += sqlite3Strlen30(&z[j]); - break; - } - case 'S': sqlite3_snprintf(3,&z[j],"%02d",(int)x.s); j+=2; break; - case 'w': { - z[j++] = (char)(((x.iJD+129600000)/86400000) % 7) + '0'; - break; - } - case 'Y': { - sqlite3_snprintf(5,&z[j],"%04d",x.Y); j+=sqlite3Strlen30(&z[j]); - break; - } - default: z[j++] = '%'; break; + break; + } + case 'J': { + sqlite3_str_appendf(&sRes,"%.16g",x.iJD/86400000.0); + break; + } + case 'm': { + sqlite3_str_appendf(&sRes,"%02d",x.M); + break; + } + case 'M': { + sqlite3_str_appendf(&sRes,"%02d",x.m); + break; + } + case 's': { + i64 iS = (i64)(x.iJD/1000 - 21086676*(i64)10000); + sqlite3_str_appendf(&sRes,"%lld",iS); + break; + } + case 'S': { + sqlite3_str_appendf(&sRes,"%02d",(int)x.s); + break; + } + case 'w': { + sqlite3_str_appendchar(&sRes, 1, + (char)(((x.iJD+129600000)/86400000) % 7) + '0'); + break; + } + case 'Y': { + sqlite3_str_appendf(&sRes,"%04d",x.Y); + break; + } + case '%': { + sqlite3_str_appendchar(&sRes, 1, '%'); + break; + } + default: { + sqlite3_str_reset(&sRes); + return; } } } - z[j] = 0; - sqlite3_result_text(context, z, -1, - z==zBuf ? SQLITE_TRANSIENT : SQLITE_DYNAMIC); + if( jpMethods==0) ) return 0; return id->pMethods->xDeviceCharacteristics(id); } #ifndef SQLITE_OMIT_WAL @@ -23474,7 +24851,7 @@ SQLITE_PRIVATE int sqlite3OsOpen( SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ DO_OS_MALLOC_TEST(0); assert( dirSync==0 || dirSync==1 ); - return pVfs->xDelete(pVfs, zPath, dirSync); + return pVfs->xDelete!=0 ? pVfs->xDelete(pVfs, zPath, dirSync) : SQLITE_OK; } SQLITE_PRIVATE int sqlite3OsAccess( sqlite3_vfs *pVfs, @@ -23497,6 +24874,8 @@ SQLITE_PRIVATE int sqlite3OsFullPathname( } #ifndef SQLITE_OMIT_LOAD_EXTENSION SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *pVfs, const char *zPath){ + assert( zPath!=0 ); + assert( strlen(zPath)<=SQLITE_MAX_PATHLEN ); /* tag-20210611-1 */ return pVfs->xDlOpen(pVfs, zPath); } SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ @@ -23558,12 +24937,15 @@ SQLITE_PRIVATE int sqlite3OsOpenMalloc( rc = sqlite3OsOpen(pVfs, zFile, pFile, flags, pOutFlags); if( rc!=SQLITE_OK ){ sqlite3_free(pFile); + *ppFile = 0; }else{ *ppFile = pFile; } }else{ + *ppFile = 0; rc = SQLITE_NOMEM_BKPT; } + assert( *ppFile!=0 || rc!=SQLITE_OK ); return rc; } SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *pFile){ @@ -24281,7 +25663,7 @@ static void adjustStats(int iSize, int increment){ ** This routine checks the guards at either end of the allocation and ** if they are incorrect it asserts. */ -static struct MemBlockHdr *sqlite3MemsysGetHeader(void *pAllocation){ +static struct MemBlockHdr *sqlite3MemsysGetHeader(const void *pAllocation){ struct MemBlockHdr *p; int *pInt; u8 *pU8; @@ -24528,7 +25910,7 @@ SQLITE_PRIVATE void sqlite3MemdebugSetType(void *p, u8 eType){ ** ** assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); */ -SQLITE_PRIVATE int sqlite3MemdebugHasType(void *p, u8 eType){ +SQLITE_PRIVATE int sqlite3MemdebugHasType(const void *p, u8 eType){ int rc = 1; if( p && sqlite3GlobalConfig.m.xFree==sqlite3MemFree ){ struct MemBlockHdr *pHdr; @@ -24550,7 +25932,7 @@ SQLITE_PRIVATE int sqlite3MemdebugHasType(void *p, u8 eType){ ** ** assert( sqlite3MemdebugNoType(p, MEMTYPE_LOOKASIDE) ); */ -SQLITE_PRIVATE int sqlite3MemdebugNoType(void *p, u8 eType){ +SQLITE_PRIVATE int sqlite3MemdebugNoType(const void *p, u8 eType){ int rc = 1; if( p && sqlite3GlobalConfig.m.xFree==sqlite3MemFree ){ struct MemBlockHdr *pHdr; @@ -25773,8 +27155,13 @@ static void *memsys5Realloc(void *pPrior, int nBytes){ */ static int memsys5Roundup(int n){ int iFullSz; - if( n > 0x40000000 ) return 0; - for(iFullSz=mem5.szAtom; iFullSz0x40000000 ) return 0; + for(iFullSz=mem5.szAtom*8; iFullSz=n ) return iFullSz/2; return iFullSz; } @@ -26928,205 +28315,7 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){ /* ** Include code that is common to all os_*.c files */ -/************** Include os_common.h in the middle of mutex_w32.c *************/ -/************** Begin file os_common.h ***************************************/ -/* -** 2004 May 22 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This file contains macros and a little bit of code that is common to -** all of the platform-specific files (os_*.c) and is #included into those -** files. -** -** This file should be #included by the os_*.c files only. It is not a -** general purpose header file. -*/ -#ifndef _OS_COMMON_H_ -#define _OS_COMMON_H_ - -/* -** At least two bugs have slipped in because we changed the MEMORY_DEBUG -** macro to SQLITE_DEBUG and some older makefiles have not yet made the -** switch. The following code should catch this problem at compile-time. -*/ -#ifdef MEMORY_DEBUG -# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead." -#endif - -/* -** Macros for performance tracing. Normally turned off. Only works -** on i486 hardware. -*/ -#ifdef SQLITE_PERFORMANCE_TRACE - -/* -** hwtime.h contains inline assembler code for implementing -** high-performance timing routines. -*/ -/************** Include hwtime.h in the middle of os_common.h ****************/ -/************** Begin file hwtime.h ******************************************/ -/* -** 2008 May 27 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This file contains inline asm code for retrieving "high-performance" -** counters for x86 and x86_64 class CPUs. -*/ -#ifndef SQLITE_HWTIME_H -#define SQLITE_HWTIME_H - -/* -** The following routine only works on pentium-class (or newer) processors. -** It uses the RDTSC opcode to read the cycle count value out of the -** processor and returns that value. This can be used for high-res -** profiling. -*/ -#if !defined(__STRICT_ANSI__) && \ - (defined(__GNUC__) || defined(_MSC_VER)) && \ - (defined(i386) || defined(__i386__) || defined(_M_IX86)) - - #if defined(__GNUC__) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned int lo, hi; - __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); - return (sqlite_uint64)hi << 32 | lo; - } - - #elif defined(_MSC_VER) - - __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){ - __asm { - rdtsc - ret ; return value at EDX:EAX - } - } - - #endif - -#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__)) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long val; - __asm__ __volatile__ ("rdtsc" : "=A" (val)); - return val; - } - -#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__)) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long long retval; - unsigned long junk; - __asm__ __volatile__ ("\n\ - 1: mftbu %1\n\ - mftb %L0\n\ - mftbu %0\n\ - cmpw %0,%1\n\ - bne 1b" - : "=r" (retval), "=r" (junk)); - return retval; - } - -#else - - /* - ** asm() is needed for hardware timing support. Without asm(), - ** disable the sqlite3Hwtime() routine. - ** - ** sqlite3Hwtime() is only used for some obscure debugging - ** and analysis configurations, not in any deliverable, so this - ** should not be a great loss. - */ -SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); } - -#endif - -#endif /* !defined(SQLITE_HWTIME_H) */ - -/************** End of hwtime.h **********************************************/ -/************** Continuing where we left off in os_common.h ******************/ - -static sqlite_uint64 g_start; -static sqlite_uint64 g_elapsed; -#define TIMER_START g_start=sqlite3Hwtime() -#define TIMER_END g_elapsed=sqlite3Hwtime()-g_start -#define TIMER_ELAPSED g_elapsed -#else -#define TIMER_START -#define TIMER_END -#define TIMER_ELAPSED ((sqlite_uint64)0) -#endif - -/* -** If we compile with the SQLITE_TEST macro set, then the following block -** of code will give us the ability to simulate a disk I/O error. This -** is used for testing the I/O recovery logic. -*/ -#if defined(SQLITE_TEST) -SQLITE_API extern int sqlite3_io_error_hit; -SQLITE_API extern int sqlite3_io_error_hardhit; -SQLITE_API extern int sqlite3_io_error_pending; -SQLITE_API extern int sqlite3_io_error_persist; -SQLITE_API extern int sqlite3_io_error_benign; -SQLITE_API extern int sqlite3_diskfull_pending; -SQLITE_API extern int sqlite3_diskfull; -#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X) -#define SimulateIOError(CODE) \ - if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \ - || sqlite3_io_error_pending-- == 1 ) \ - { local_ioerr(); CODE; } -static void local_ioerr(){ - IOTRACE(("IOERR\n")); - sqlite3_io_error_hit++; - if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++; -} -#define SimulateDiskfullError(CODE) \ - if( sqlite3_diskfull_pending ){ \ - if( sqlite3_diskfull_pending == 1 ){ \ - local_ioerr(); \ - sqlite3_diskfull = 1; \ - sqlite3_io_error_hit = 1; \ - CODE; \ - }else{ \ - sqlite3_diskfull_pending--; \ - } \ - } -#else -#define SimulateIOErrorBenign(X) -#define SimulateIOError(A) -#define SimulateDiskfullError(A) -#endif /* defined(SQLITE_TEST) */ - -/* -** When testing, keep a count of the number of open files. -*/ -#if defined(SQLITE_TEST) -SQLITE_API extern int sqlite3_open_file_count; -#define OpenCounter(X) sqlite3_open_file_count+=(X) -#else -#define OpenCounter(X) -#endif /* defined(SQLITE_TEST) */ - -#endif /* !defined(_OS_COMMON_H_) */ - -/************** End of os_common.h *******************************************/ -/************** Continuing where we left off in mutex_w32.c ******************/ +/* #include "os_common.h" */ /* ** Include the header file for the Windows VFS. @@ -27764,7 +28953,6 @@ SQLITE_PRIVATE int sqlite3MallocInit(void){ if( sqlite3GlobalConfig.m.xMalloc==0 ){ sqlite3MemSetDefault(); } - memset(&mem0, 0, sizeof(mem0)); mem0.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); if( sqlite3GlobalConfig.pPage==0 || sqlite3GlobalConfig.szPage<512 || sqlite3GlobalConfig.nPage<=0 ){ @@ -27920,7 +29108,7 @@ SQLITE_API void *sqlite3_malloc64(sqlite3_uint64 n){ ** TRUE if p is a lookaside memory allocation from db */ #ifndef SQLITE_OMIT_LOOKASIDE -static int isLookaside(sqlite3 *db, void *p){ +static int isLookaside(sqlite3 *db, const void *p){ return SQLITE_WITHIN(p, db->lookaside.pStart, db->lookaside.pEnd); } #else @@ -27931,18 +29119,18 @@ static int isLookaside(sqlite3 *db, void *p){ ** Return the size of a memory allocation previously obtained from ** sqlite3Malloc() or sqlite3_malloc(). */ -SQLITE_PRIVATE int sqlite3MallocSize(void *p){ +SQLITE_PRIVATE int sqlite3MallocSize(const void *p){ assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); - return sqlite3GlobalConfig.m.xSize(p); + return sqlite3GlobalConfig.m.xSize((void*)p); } -static int lookasideMallocSize(sqlite3 *db, void *p){ +static int lookasideMallocSize(sqlite3 *db, const void *p){ #ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE return plookaside.pMiddle ? db->lookaside.szTrue : LOOKASIDE_SMALL; #else return db->lookaside.szTrue; #endif } -SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, void *p){ +SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, const void *p){ assert( p!=0 ); #ifdef SQLITE_DEBUG if( db==0 || !isLookaside(db,p) ){ @@ -27969,7 +29157,7 @@ SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, void *p){ } } } - return sqlite3GlobalConfig.m.xSize(p); + return sqlite3GlobalConfig.m.xSize((void*)p); } SQLITE_API sqlite3_uint64 sqlite3_msize(void *p){ assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); @@ -28354,8 +29542,9 @@ SQLITE_PRIVATE char *sqlite3DbSpanDup(sqlite3 *db, const char *zStart, const cha ** Free any prior content in *pz and replace it with a copy of zNew. */ SQLITE_PRIVATE void sqlite3SetString(char **pz, sqlite3 *db, const char *zNew){ + char *z = sqlite3DbStrDup(db, zNew); sqlite3DbFree(db, *pz); - *pz = sqlite3DbStrDup(db, zNew); + *pz = z; } /* @@ -28363,8 +29552,15 @@ SQLITE_PRIVATE void sqlite3SetString(char **pz, sqlite3 *db, const char *zNew){ ** has happened. This routine will set db->mallocFailed, and also ** temporarily disable the lookaside memory allocator and interrupt ** any running VDBEs. +** +** Always return a NULL pointer so that this routine can be invoked using +** +** return sqlite3OomFault(db); +** +** and thereby avoid unnecessary stack frame allocations for the overwhelmingly +** common case where no OOM occurs. */ -SQLITE_PRIVATE void sqlite3OomFault(sqlite3 *db){ +SQLITE_PRIVATE void *sqlite3OomFault(sqlite3 *db){ if( db->mallocFailed==0 && db->bBenignMalloc==0 ){ db->mallocFailed = 1; if( db->nVdbeExec>0 ){ @@ -28372,9 +29568,16 @@ SQLITE_PRIVATE void sqlite3OomFault(sqlite3 *db){ } DisableLookaside; if( db->pParse ){ + Parse *pParse; + sqlite3ErrorMsg(db->pParse, "out of memory"); db->pParse->rc = SQLITE_NOMEM_BKPT; + for(pParse=db->pParse->pOuterParse; pParse; pParse = pParse->pOuterParse){ + pParse->nErr++; + pParse->rc = SQLITE_NOMEM; + } } } + return 0; } /* @@ -28463,7 +29666,7 @@ SQLITE_PRIVATE int sqlite3ApiExit(sqlite3* db, int rc){ #define etSQLESCAPE2 10 /* Strings with '\'' doubled and enclosed in '', NULL pointers replaced by SQL NULL. %Q */ #define etTOKEN 11 /* a pointer to a Token structure */ -#define etSRCLIST 12 /* a pointer to a SrcList */ +#define etSRCITEM 12 /* a pointer to a SrcItem */ #define etPOINTER 13 /* The %p conversion */ #define etSQLESCAPE3 14 /* %w -> Strings with '\"' doubled */ #define etORDINAL 15 /* %r -> 1st, 2nd, 3rd, 4th, etc. English only */ @@ -28529,10 +29732,16 @@ static const et_info fmtinfo[] = { /* All the rest are undocumented and are for internal use only */ { 'T', 0, 0, etTOKEN, 0, 0 }, - { 'S', 0, 0, etSRCLIST, 0, 0 }, + { 'S', 0, 0, etSRCITEM, 0, 0 }, { 'r', 10, 1, etORDINAL, 0, 0 }, }; +/* Notes: +** +** %S Takes a pointer to SrcItem. Shows name or database.name +** %!S Like %S but prefer the zName over the zAlias +*/ + /* Floating point constants used for rounding */ static const double arRound[] = { 5.0e-01, 5.0e-02, 5.0e-03, 5.0e-04, 5.0e-05, @@ -28573,7 +29782,7 @@ static char et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){ /* ** Set the StrAccum object to an error mode. */ -static void setStrAccumError(StrAccum *p, u8 eError){ +SQLITE_PRIVATE void sqlite3StrAccumSetError(StrAccum *p, u8 eError){ assert( eError==SQLITE_NOMEM || eError==SQLITE_TOOBIG ); p->accError = eError; if( p->mxAlloc ) sqlite3_str_reset(p); @@ -28609,12 +29818,12 @@ static char *printfTempBuf(sqlite3_str *pAccum, sqlite3_int64 n){ char *z; if( pAccum->accError ) return 0; if( n>pAccum->nAlloc && n>pAccum->mxAlloc ){ - setStrAccumError(pAccum, SQLITE_TOOBIG); + sqlite3StrAccumSetError(pAccum, SQLITE_TOOBIG); return 0; } z = sqlite3DbMallocRaw(pAccum->db, n); if( z==0 ){ - setStrAccumError(pAccum, SQLITE_NOMEM); + sqlite3StrAccumSetError(pAccum, SQLITE_NOMEM); } return z; } @@ -29231,8 +30440,8 @@ SQLITE_API void sqlite3_str_vappendf( case etSQLESCAPE: /* %q: Escape ' characters */ case etSQLESCAPE2: /* %Q: Escape ' and enclose in '...' */ case etSQLESCAPE3: { /* %w: Escape " characters */ - int i, j, k, n, isnull; - int needQuote; + i64 i, j, k, n; + int needQuote, isnull; char ch; char q = ((xtype==etSQLESCAPE3)?'"':'\''); /* Quote character */ char *escarg; @@ -29277,31 +30486,50 @@ SQLITE_API void sqlite3_str_vappendf( goto adjust_width_for_utf8; } case etTOKEN: { - Token *pToken; if( (pAccum->printfFlags & SQLITE_PRINTF_INTERNAL)==0 ) return; - pToken = va_arg(ap, Token*); - assert( bArgList==0 ); - if( pToken && pToken->n ){ - sqlite3_str_append(pAccum, (const char*)pToken->z, pToken->n); + if( flag_alternateform ){ + /* %#T means an Expr pointer that uses Expr.u.zToken */ + Expr *pExpr = va_arg(ap,Expr*); + if( ALWAYS(pExpr) && ALWAYS(!ExprHasProperty(pExpr,EP_IntValue)) ){ + sqlite3_str_appendall(pAccum, (const char*)pExpr->u.zToken); + sqlite3RecordErrorOffsetOfExpr(pAccum->db, pExpr); + } + }else{ + /* %T means a Token pointer */ + Token *pToken = va_arg(ap, Token*); + assert( bArgList==0 ); + if( pToken && pToken->n ){ + sqlite3_str_append(pAccum, (const char*)pToken->z, pToken->n); + sqlite3RecordErrorByteOffset(pAccum->db, pToken->z); + } } length = width = 0; break; } - case etSRCLIST: { - SrcList *pSrc; - int k; + case etSRCITEM: { SrcItem *pItem; if( (pAccum->printfFlags & SQLITE_PRINTF_INTERNAL)==0 ) return; - pSrc = va_arg(ap, SrcList*); - k = va_arg(ap, int); - pItem = &pSrc->a[k]; + pItem = va_arg(ap, SrcItem*); assert( bArgList==0 ); - assert( k>=0 && knSrc ); - if( pItem->zDatabase ){ - sqlite3_str_appendall(pAccum, pItem->zDatabase); - sqlite3_str_append(pAccum, ".", 1); + if( pItem->zAlias && !flag_altform2 ){ + sqlite3_str_appendall(pAccum, pItem->zAlias); + }else if( pItem->zName ){ + if( pItem->zDatabase ){ + sqlite3_str_appendall(pAccum, pItem->zDatabase); + sqlite3_str_append(pAccum, ".", 1); + } + sqlite3_str_appendall(pAccum, pItem->zName); + }else if( pItem->zAlias ){ + sqlite3_str_appendall(pAccum, pItem->zAlias); + }else{ + Select *pSel = pItem->pSelect; + assert( pSel!=0 ); + if( pSel->selFlags & SF_NestedFrom ){ + sqlite3_str_appendf(pAccum, "(join-%u)", pSel->selId); + }else{ + sqlite3_str_appendf(pAccum, "(subquery-%u)", pSel->selId); + } } - sqlite3_str_appendall(pAccum, pItem->zName); length = width = 0; break; } @@ -29334,6 +30562,44 @@ SQLITE_API void sqlite3_str_vappendf( }/* End for loop over the format string */ } /* End of function */ + +/* +** The z string points to the first character of a token that is +** associated with an error. If db does not already have an error +** byte offset recorded, try to compute the error byte offset for +** z and set the error byte offset in db. +*/ +SQLITE_PRIVATE void sqlite3RecordErrorByteOffset(sqlite3 *db, const char *z){ + const Parse *pParse; + const char *zText; + const char *zEnd; + assert( z!=0 ); + if( NEVER(db==0) ) return; + if( db->errByteOffset!=(-2) ) return; + pParse = db->pParse; + if( NEVER(pParse==0) ) return; + zText =pParse->zTail; + if( NEVER(zText==0) ) return; + zEnd = &zText[strlen(zText)]; + if( SQLITE_WITHIN(z,zText,zEnd) ){ + db->errByteOffset = (int)(z-zText); + } +} + +/* +** If pExpr has a byte offset for the start of a token, record that as +** as the error offset. +*/ +SQLITE_PRIVATE void sqlite3RecordErrorOffsetOfExpr(sqlite3 *db, const Expr *pExpr){ + while( pExpr + && (ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) || pExpr->w.iOfst<=0) + ){ + pExpr = pExpr->pLeft; + } + if( pExpr==0 ) return; + db->errByteOffset = pExpr->w.iOfst; +} + /* ** Enlarge the memory allocation on a StrAccum object so that it is ** able to accept at least N more bytes of text. @@ -29341,7 +30607,7 @@ SQLITE_API void sqlite3_str_vappendf( ** Return the number of bytes of text that StrAccum is able to accept ** after the attempted enlargement. The value returned might be zero. */ -static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ +SQLITE_PRIVATE int sqlite3StrAccumEnlarge(StrAccum *p, int N){ char *zNew; assert( p->nChar+(i64)N >= p->nAlloc ); /* Only called if really needed */ if( p->accError ){ @@ -29350,7 +30616,7 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ return 0; } if( p->mxAlloc==0 ){ - setStrAccumError(p, SQLITE_TOOBIG); + sqlite3StrAccumSetError(p, SQLITE_TOOBIG); return p->nAlloc - p->nChar - 1; }else{ char *zOld = isMalloced(p) ? p->zText : 0; @@ -29363,7 +30629,7 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ } if( szNew > p->mxAlloc ){ sqlite3_str_reset(p); - setStrAccumError(p, SQLITE_TOOBIG); + sqlite3StrAccumSetError(p, SQLITE_TOOBIG); return 0; }else{ p->nAlloc = (int)szNew; @@ -29381,7 +30647,7 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ p->printfFlags |= SQLITE_PRINTF_MALLOCED; }else{ sqlite3_str_reset(p); - setStrAccumError(p, SQLITE_NOMEM); + sqlite3StrAccumSetError(p, SQLITE_NOMEM); return 0; } } @@ -29454,7 +30720,7 @@ static SQLITE_NOINLINE char *strAccumFinishRealloc(StrAccum *p){ memcpy(zText, p->zText, p->nChar+1); p->printfFlags |= SQLITE_PRINTF_MALLOCED; }else{ - setStrAccumError(p, SQLITE_NOMEM); + sqlite3StrAccumSetError(p, SQLITE_NOMEM); } p->zText = zText; return zText; @@ -29469,6 +30735,22 @@ SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum *p){ return p->zText; } +/* +** Use the content of the StrAccum passed as the second argument +** as the result of an SQL function. +*/ +SQLITE_PRIVATE void sqlite3ResultStrAccum(sqlite3_context *pCtx, StrAccum *p){ + if( p->accError ){ + sqlite3_result_error_code(pCtx, p->accError); + sqlite3_str_reset(p); + }else if( isMalloced(p) ){ + sqlite3_result_text(pCtx, p->zText, p->nChar, SQLITE_DYNAMIC); + }else{ + sqlite3_result_text(pCtx, "", 0, SQLITE_STATIC); + sqlite3_str_reset(p); + } +} + /* ** This singleton is an sqlite3_str object that is returned if ** sqlite3_malloc() fails to provide space for a real one. This @@ -29771,40 +31053,44 @@ SQLITE_API void sqlite3_str_appendf(StrAccum *p, const char *zFormat, ...){ ** Add a new subitem to the tree. The moreToFollow flag indicates that this ** is not the last item in the tree. */ -static TreeView *sqlite3TreeViewPush(TreeView *p, u8 moreToFollow){ +static void sqlite3TreeViewPush(TreeView **pp, u8 moreToFollow){ + TreeView *p = *pp; if( p==0 ){ - p = sqlite3_malloc64( sizeof(*p) ); - if( p==0 ) return 0; + *pp = p = sqlite3_malloc64( sizeof(*p) ); + if( p==0 ) return; memset(p, 0, sizeof(*p)); }else{ p->iLevel++; } assert( moreToFollow==0 || moreToFollow==1 ); - if( p->iLevelbLine) ) p->bLine[p->iLevel] = moreToFollow; - return p; + if( p->iLevel<(int)sizeof(p->bLine) ) p->bLine[p->iLevel] = moreToFollow; } /* ** Finished with one layer of the tree */ -static void sqlite3TreeViewPop(TreeView *p){ +static void sqlite3TreeViewPop(TreeView **pp){ + TreeView *p = *pp; if( p==0 ) return; p->iLevel--; - if( p->iLevel<0 ) sqlite3_free(p); + if( p->iLevel<0 ){ + sqlite3_free(p); + *pp = 0; + } } /* ** Generate a single line of output for the tree, with a prefix that contains ** all the appropriate tree lines */ -static void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){ +SQLITE_PRIVATE void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){ va_list ap; int i; StrAccum acc; - char zBuf[500]; + char zBuf[1000]; sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0); if( p ){ - for(i=0; iiLevel && ibLine)-1; i++){ + for(i=0; iiLevel && i<(int)sizeof(p->bLine)-1; i++){ sqlite3_str_append(&acc, p->bLine[i] ? "| " : " ", 4); } sqlite3_str_append(&acc, p->bLine[i] ? "|-- " : "'-- ", 4); @@ -29825,10 +31111,57 @@ static void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){ ** Shorthand for starting a new tree item that consists of a single label */ static void sqlite3TreeViewItem(TreeView *p, const char *zLabel,u8 moreFollows){ - p = sqlite3TreeViewPush(p, moreFollows); + sqlite3TreeViewPush(&p, moreFollows); sqlite3TreeViewLine(p, "%s", zLabel); } +/* +** Show a list of Column objects in tree format. +*/ +SQLITE_PRIVATE void sqlite3TreeViewColumnList( + TreeView *pView, + const Column *aCol, + int nCol, + u8 moreToFollow +){ + int i; + sqlite3TreeViewPush(&pView, moreToFollow); + sqlite3TreeViewLine(pView, "COLUMNS"); + for(i=0; inCte>0 ){ - pView = sqlite3TreeViewPush(pView, 1); + sqlite3TreeViewPush(&pView, moreToFollow); for(i=0; inCte; i++){ StrAccum x; char zLine[1000]; @@ -29858,6 +31191,10 @@ SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 m } sqlite3_str_appendf(&x, ")"); } + if( pCte->eM10d!=M10d_Any ){ + sqlite3_str_appendf(&x, " %sMATERIALIZED", + pCte->eM10d==M10d_No ? "NOT " : ""); + } if( pCte->pUse ){ sqlite3_str_appendf(&x, " (pUse=0x%p, nUse=%d)", pCte->pUse, pCte->pUse->nUse); @@ -29865,9 +31202,9 @@ SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 m sqlite3StrAccumFinish(&x); sqlite3TreeViewItem(pView, zLine, inCte-1); sqlite3TreeViewSelect(pView, pCte->pSelect, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } } @@ -29876,26 +31213,30 @@ SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 m */ SQLITE_PRIVATE void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){ int i; + if( pSrc==0 ) return; for(i=0; inSrc; i++){ const SrcItem *pItem = &pSrc->a[i]; StrAccum x; - char zLine[100]; + int n = 0; + char zLine[1000]; sqlite3StrAccumInit(&x, 0, zLine, sizeof(zLine), 0); - sqlite3_str_appendf(&x, "{%d:*}", pItem->iCursor); - if( pItem->zDatabase ){ - sqlite3_str_appendf(&x, " %s.%s", pItem->zDatabase, pItem->zName); - }else if( pItem->zName ){ - sqlite3_str_appendf(&x, " %s", pItem->zName); - } + x.printfFlags |= SQLITE_PRINTF_INTERNAL; + sqlite3_str_appendf(&x, "{%d:*} %!S", pItem->iCursor, pItem); if( pItem->pTab ){ sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p used=%llx", pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab, pItem->colUsed); } - if( pItem->zAlias ){ - sqlite3_str_appendf(&x, " (AS %s)", pItem->zAlias); - } - if( pItem->fg.jointype & JT_LEFT ){ + if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))==(JT_LEFT|JT_RIGHT) ){ + sqlite3_str_appendf(&x, " FULL-OUTER-JOIN"); + }else if( pItem->fg.jointype & JT_LEFT ){ sqlite3_str_appendf(&x, " LEFT-JOIN"); + }else if( pItem->fg.jointype & JT_RIGHT ){ + sqlite3_str_appendf(&x, " RIGHT-JOIN"); + }else if( pItem->fg.jointype & JT_CROSS ){ + sqlite3_str_appendf(&x, " CROSS-JOIN"); + } + if( pItem->fg.jointype & JT_LTORJ ){ + sqlite3_str_appendf(&x, " LTORJ"); } if( pItem->fg.fromDDL ){ sqlite3_str_appendf(&x, " DDL"); @@ -29903,15 +31244,30 @@ SQLITE_PRIVATE void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc) if( pItem->fg.isCte ){ sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse); } + if( pItem->fg.isOn || (pItem->fg.isUsing==0 && pItem->u3.pOn!=0) ){ + sqlite3_str_appendf(&x, " ON"); + } sqlite3StrAccumFinish(&x); sqlite3TreeViewItem(pView, zLine, inSrc-1); + n = 0; + if( pItem->pSelect ) n++; + if( pItem->fg.isTabFunc ) n++; + if( pItem->fg.isUsing ) n++; + if( pItem->fg.isUsing ){ + sqlite3TreeViewIdList(pView, pItem->u3.pUsing, (--n)>0, "USING"); + } if( pItem->pSelect ){ - sqlite3TreeViewSelect(pView, pItem->pSelect, 0); + if( pItem->pTab ){ + Table *pTab = pItem->pTab; + sqlite3TreeViewColumnList(pView, pTab->aCol, pTab->nCol, 1); + } + assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) ); + sqlite3TreeViewSelect(pView, pItem->pSelect, (--n)>0); } if( pItem->fg.isTabFunc ){ sqlite3TreeViewExprList(pView, pItem->u1.pFuncArg, 0, "func-args:"); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } } @@ -29925,11 +31281,11 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m sqlite3TreeViewLine(pView, "nil-SELECT"); return; } - pView = sqlite3TreeViewPush(pView, moreToFollow); + sqlite3TreeViewPush(&pView, moreToFollow); if( p->pWith ){ sqlite3TreeViewWith(pView, p->pWith, 1); cnt = 1; - sqlite3TreeViewPush(pView, 1); + sqlite3TreeViewPush(&pView, 1); } do{ if( p->selFlags & SF_WhereBegin ){ @@ -29943,7 +31299,7 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m (int)p->nSelectRow ); } - if( cnt++ ) sqlite3TreeViewPop(pView); + if( cnt++ ) sqlite3TreeViewPop(&pView); if( p->pPrior ){ n = 1000; }else{ @@ -29966,24 +31322,24 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m #ifndef SQLITE_OMIT_WINDOWFUNC if( p->pWin ){ Window *pX; - pView = sqlite3TreeViewPush(pView, (n--)>0); + sqlite3TreeViewPush(&pView, (n--)>0); sqlite3TreeViewLine(pView, "window-functions"); for(pX=p->pWin; pX; pX=pX->pNextWin){ sqlite3TreeViewWinFunc(pView, pX, pX->pNextWin!=0); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #endif if( p->pSrc && p->pSrc->nSrc ){ - pView = sqlite3TreeViewPush(pView, (n--)>0); + sqlite3TreeViewPush(&pView, (n--)>0); sqlite3TreeViewLine(pView, "FROM"); sqlite3TreeViewSrcList(pView, p->pSrc); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } if( p->pWhere ){ sqlite3TreeViewItem(pView, "WHERE", (n--)>0); sqlite3TreeViewExpr(pView, p->pWhere, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } if( p->pGroupBy ){ sqlite3TreeViewExprList(pView, p->pGroupBy, (n--)>0, "GROUPBY"); @@ -29991,7 +31347,7 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m if( p->pHaving ){ sqlite3TreeViewItem(pView, "HAVING", (n--)>0); sqlite3TreeViewExpr(pView, p->pHaving, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #ifndef SQLITE_OMIT_WINDOWFUNC if( p->pWinDefn ){ @@ -30000,7 +31356,7 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m for(pX=p->pWinDefn; pX; pX=pX->pNextWin){ sqlite3TreeViewWindow(pView, pX, pX->pNextWin!=0); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #endif if( p->pOrderBy ){ @@ -30012,9 +31368,9 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m if( p->pLimit->pRight ){ sqlite3TreeViewItem(pView, "OFFSET", (n--)>0); sqlite3TreeViewExpr(pView, p->pLimit->pRight, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } if( p->pPrior ){ const char *zOp = "UNION"; @@ -30027,7 +31383,7 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m } p = p->pPrior; }while( p!=0 ); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #ifndef SQLITE_OMIT_WINDOWFUNC @@ -30043,24 +31399,24 @@ SQLITE_PRIVATE void sqlite3TreeViewBound( switch( eBound ){ case TK_UNBOUNDED: { sqlite3TreeViewItem(pView, "UNBOUNDED", moreToFollow); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); break; } case TK_CURRENT: { sqlite3TreeViewItem(pView, "CURRENT", moreToFollow); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); break; } case TK_PRECEDING: { sqlite3TreeViewItem(pView, "PRECEDING", moreToFollow); sqlite3TreeViewExpr(pView, pExpr, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); break; } case TK_FOLLOWING: { sqlite3TreeViewItem(pView, "FOLLOWING", moreToFollow); sqlite3TreeViewExpr(pView, pExpr, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); break; } } @@ -30073,12 +31429,13 @@ SQLITE_PRIVATE void sqlite3TreeViewBound( */ SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u8 more){ int nElement = 0; + if( pWin==0 ) return; if( pWin->pFilter ){ sqlite3TreeViewItem(pView, "FILTER", 1); sqlite3TreeViewExpr(pView, pWin->pFilter, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } - pView = sqlite3TreeViewPush(pView, more); + sqlite3TreeViewPush(&pView, more); if( pWin->zName ){ sqlite3TreeViewLine(pView, "OVER %s (%p)", pWin->zName, pWin); }else{ @@ -30089,9 +31446,9 @@ SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u if( pWin->eFrmType ) nElement++; if( pWin->eExclude ) nElement++; if( pWin->zBase ){ - sqlite3TreeViewPush(pView, (--nElement)>0); + sqlite3TreeViewPush(&pView, (--nElement)>0); sqlite3TreeViewLine(pView, "window: %s", pWin->zBase); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } if( pWin->pPartition ){ sqlite3TreeViewExprList(pView, pWin->pPartition, nElement>0,"PARTITION-BY"); @@ -30109,7 +31466,7 @@ SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u sqlite3TreeViewItem(pView, zBuf, (--nElement)>0); sqlite3TreeViewBound(pView, pWin->eStart, pWin->pStart, 1); sqlite3TreeViewBound(pView, pWin->eEnd, pWin->pEnd, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } if( pWin->eExclude ){ char zBuf[30]; @@ -30124,11 +31481,11 @@ SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u zExclude = zBuf; break; } - sqlite3TreeViewPush(pView, 0); + sqlite3TreeViewPush(&pView, 0); sqlite3TreeViewLine(pView, "EXCLUDE %s", zExclude); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #endif /* SQLITE_OMIT_WINDOWFUNC */ @@ -30137,11 +31494,12 @@ SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u ** Generate a human-readable explanation for a Window Function object */ SQLITE_PRIVATE void sqlite3TreeViewWinFunc(TreeView *pView, const Window *pWin, u8 more){ - pView = sqlite3TreeViewPush(pView, more); + if( pWin==0 ) return; + sqlite3TreeViewPush(&pView, more); sqlite3TreeViewLine(pView, "WINFUNC %s(%d)", - pWin->pFunc->zName, pWin->pFunc->nArg); + pWin->pWFunc->zName, pWin->pWFunc->nArg); sqlite3TreeViewWindow(pView, pWin, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #endif /* SQLITE_OMIT_WINDOWFUNC */ @@ -30152,10 +31510,10 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m const char *zBinOp = 0; /* Binary operator */ const char *zUniOp = 0; /* Unary operator */ char zFlgs[200]; - pView = sqlite3TreeViewPush(pView, moreToFollow); + sqlite3TreeViewPush(&pView, moreToFollow); if( pExpr==0 ){ sqlite3TreeViewLine(pView, "nil"); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); return; } if( pExpr->flags || pExpr->affExpr || pExpr->vvaFlags ){ @@ -30163,8 +31521,11 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m sqlite3StrAccumInit(&x, 0, zFlgs, sizeof(zFlgs), 0); sqlite3_str_appendf(&x, " fg.af=%x.%c", pExpr->flags, pExpr->affExpr ? pExpr->affExpr : 'n'); - if( ExprHasProperty(pExpr, EP_FromJoin) ){ - sqlite3_str_appendf(&x, " iRJT=%d", pExpr->iRightJoinTable); + if( ExprHasProperty(pExpr, EP_OuterON) ){ + sqlite3_str_appendf(&x, " outer.iJoin=%d", pExpr->w.iJoin); + } + if( ExprHasProperty(pExpr, EP_InnerON) ){ + sqlite3_str_appendf(&x, " inner.iJoin=%d", pExpr->w.iJoin); } if( ExprHasProperty(pExpr, EP_FromDDL) ){ sqlite3_str_appendf(&x, " DDL"); @@ -30194,6 +31555,7 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m sqlite3TreeViewLine(pView, "COLUMN(%d)%s%s", pExpr->iColumn, zFlgs, zOp2); }else{ + assert( ExprUseYTab(pExpr) ); sqlite3TreeViewLine(pView, "{%d:%d} pTab=%p%s", pExpr->iTable, pExpr->iColumn, pExpr->y.pTab, zFlgs); @@ -30213,11 +31575,13 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m } #ifndef SQLITE_OMIT_FLOATING_POINT case TK_FLOAT: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"%s", pExpr->u.zToken); break; } #endif case TK_STRING: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"%Q", pExpr->u.zToken); break; } @@ -30226,17 +31590,19 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m break; } case TK_TRUEFALSE: { - sqlite3TreeViewLine(pView, - sqlite3ExprTruthValue(pExpr) ? "TRUE" : "FALSE"); + sqlite3TreeViewLine(pView,"%s%s", + sqlite3ExprTruthValue(pExpr) ? "TRUE" : "FALSE", zFlgs); break; } #ifndef SQLITE_OMIT_BLOB_LITERAL case TK_BLOB: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"%s", pExpr->u.zToken); break; } #endif case TK_VARIABLE: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"VARIABLE(%s,%d)", pExpr->u.zToken, pExpr->iColumn); break; @@ -30246,12 +31612,14 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m break; } case TK_ID: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"ID \"%w\"", pExpr->u.zToken); break; } #ifndef SQLITE_OMIT_CAST case TK_CAST: { /* Expressions of the form: CAST(pLeft AS token) */ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"CAST %Q", pExpr->u.zToken); sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); break; @@ -30301,6 +31669,7 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m } case TK_SPAN: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView, "SPAN %Q", pExpr->u.zToken); sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); break; @@ -30312,6 +31681,7 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m ** up in the treeview output as "SOFT-COLLATE". Explicit COLLATE ** operators that appear in the original SQL always have the ** EP_Collate bit set and appear in treeview output as just "COLLATE" */ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView, "%sCOLLATE %Q%s", !ExprHasProperty(pExpr, EP_Collate) ? "SOFT-" : "", pExpr->u.zToken, zFlgs); @@ -30327,6 +31697,7 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m pFarg = 0; pWin = 0; }else{ + assert( ExprUseXList(pExpr) ); pFarg = pExpr->x.pList; #ifndef SQLITE_OMIT_WINDOWFUNC pWin = ExprHasProperty(pExpr, EP_WinFunc) ? pExpr->y.pWin : 0; @@ -30334,6 +31705,7 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m pWin = 0; #endif } + assert( !ExprHasProperty(pExpr, EP_IntValue) ); if( pExpr->op==TK_AGG_FUNCTION ){ sqlite3TreeViewLine(pView, "AGG_FUNCTION%d %Q%s agg=%d[%d]/%p", pExpr->op2, pExpr->u.zToken, zFlgs, @@ -30365,19 +31737,31 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m } #ifndef SQLITE_OMIT_SUBQUERY case TK_EXISTS: { + assert( ExprUseXSelect(pExpr) ); sqlite3TreeViewLine(pView, "EXISTS-expr flags=0x%x", pExpr->flags); sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); break; } case TK_SELECT: { + assert( ExprUseXSelect(pExpr) ); sqlite3TreeViewLine(pView, "subquery-expr flags=0x%x", pExpr->flags); sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); break; } case TK_IN: { - sqlite3TreeViewLine(pView, "IN flags=0x%x", pExpr->flags); + sqlite3_str *pStr = sqlite3_str_new(0); + char *z; + sqlite3_str_appendf(pStr, "IN flags=0x%x", pExpr->flags); + if( pExpr->iTable ) sqlite3_str_appendf(pStr, " iTable=%d",pExpr->iTable); + if( ExprHasProperty(pExpr, EP_Subrtn) ){ + sqlite3_str_appendf(pStr, " subrtn(%d,%d)", + pExpr->y.sub.regReturn, pExpr->y.sub.iAddr); + } + z = sqlite3_str_finish(pStr); + sqlite3TreeViewLine(pView, z); + sqlite3_free(z); sqlite3TreeViewExpr(pView, pExpr->pLeft, 1); - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); }else{ sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, 0); @@ -30398,9 +31782,12 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m ** Z is stored in pExpr->pList->a[1].pExpr. */ case TK_BETWEEN: { - Expr *pX = pExpr->pLeft; - Expr *pY = pExpr->x.pList->a[0].pExpr; - Expr *pZ = pExpr->x.pList->a[1].pExpr; + const Expr *pX, *pY, *pZ; + pX = pExpr->pLeft; + assert( ExprUseXList(pExpr) ); + assert( pExpr->x.pList->nExpr==2 ); + pY = pExpr->x.pList->a[0].pExpr; + pZ = pExpr->x.pList->a[1].pExpr; sqlite3TreeViewLine(pView, "BETWEEN"); sqlite3TreeViewExpr(pView, pX, 1); sqlite3TreeViewExpr(pView, pY, 1); @@ -30422,6 +31809,7 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m case TK_CASE: { sqlite3TreeViewLine(pView, "CASE"); sqlite3TreeViewExpr(pView, pExpr->pLeft, 1); + assert( ExprUseXList(pExpr) ); sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, 0); break; } @@ -30434,6 +31822,7 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m case OE_Fail: zType = "fail"; break; case OE_Ignore: zType = "ignore"; break; } + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView, "RAISE %s(%Q)", zType, pExpr->u.zToken); break; } @@ -30446,12 +31835,16 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m } case TK_VECTOR: { char *z = sqlite3_mprintf("VECTOR%s",zFlgs); + assert( ExprUseXList(pExpr) ); sqlite3TreeViewBareExprList(pView, pExpr->x.pList, z); sqlite3_free(z); break; } case TK_SELECT_COLUMN: { - sqlite3TreeViewLine(pView, "SELECT-COLUMN %d", pExpr->iColumn); + sqlite3TreeViewLine(pView, "SELECT-COLUMN %d of [0..%d]%s", + pExpr->iColumn, pExpr->iTable-1, + pExpr->pRight==pExpr->pLeft ? " (SELECT-owner)" : ""); + assert( ExprUseXSelect(pExpr->pLeft) ); sqlite3TreeViewSelect(pView, pExpr->pLeft->x.pSelect, 0); break; } @@ -30460,6 +31853,23 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); break; } + case TK_ERROR: { + Expr tmp; + sqlite3TreeViewLine(pView, "ERROR"); + tmp = *pExpr; + tmp.op = pExpr->op2; + sqlite3TreeViewExpr(pView, &tmp, 0); + break; + } + case TK_ROW: { + if( pExpr->iColumn<=0 ){ + sqlite3TreeViewLine(pView, "First FROM table rowid"); + }else{ + sqlite3TreeViewLine(pView, "First FROM table column %d", + pExpr->iColumn-1); + } + break; + } default: { sqlite3TreeViewLine(pView, "op=%d", pExpr->op); break; @@ -30473,7 +31883,7 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m sqlite3TreeViewLine(pView, "%s%s", zUniOp, zFlgs); sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } @@ -30495,13 +31905,25 @@ SQLITE_PRIVATE void sqlite3TreeViewBareExprList( int j = pList->a[i].u.x.iOrderByCol; char *zName = pList->a[i].zEName; int moreToFollow = inExpr - 1; - if( pList->a[i].eEName!=ENAME_NAME ) zName = 0; if( j || zName ){ - sqlite3TreeViewPush(pView, moreToFollow); + sqlite3TreeViewPush(&pView, moreToFollow); moreToFollow = 0; sqlite3TreeViewLine(pView, 0); if( zName ){ - fprintf(stdout, "AS %s ", zName); + switch( pList->a[i].fg.eEName ){ + default: + fprintf(stdout, "AS %s ", zName); + break; + case ENAME_TAB: + fprintf(stdout, "TABLE-ALIAS-NAME(\"%s\") ", zName); + if( pList->a[i].fg.bUsed ) fprintf(stdout, "(used) "); + if( pList->a[i].fg.bUsingTerm ) fprintf(stdout, "(USING-term) "); + if( pList->a[i].fg.bNoExpand ) fprintf(stdout, "(NoExpand) "); + break; + case ENAME_SPAN: + fprintf(stdout, "SPAN(\"%s\") ", zName); + break; + } } if( j ){ fprintf(stdout, "iOrderByCol=%d", j); @@ -30511,7 +31933,7 @@ SQLITE_PRIVATE void sqlite3TreeViewBareExprList( } sqlite3TreeViewExpr(pView, pList->a[i].pExpr, moreToFollow); if( j || zName ){ - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } } } @@ -30522,11 +31944,378 @@ SQLITE_PRIVATE void sqlite3TreeViewExprList( u8 moreToFollow, const char *zLabel ){ - pView = sqlite3TreeViewPush(pView, moreToFollow); + sqlite3TreeViewPush(&pView, moreToFollow); sqlite3TreeViewBareExprList(pView, pList, zLabel); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } +/* +** Generate a human-readable explanation of an id-list. +*/ +SQLITE_PRIVATE void sqlite3TreeViewBareIdList( + TreeView *pView, + const IdList *pList, + const char *zLabel +){ + if( zLabel==0 || zLabel[0]==0 ) zLabel = "LIST"; + if( pList==0 ){ + sqlite3TreeViewLine(pView, "%s (empty)", zLabel); + }else{ + int i; + sqlite3TreeViewLine(pView, "%s", zLabel); + for(i=0; inId; i++){ + char *zName = pList->a[i].zName; + int moreToFollow = inId - 1; + if( zName==0 ) zName = "(null)"; + sqlite3TreeViewPush(&pView, moreToFollow); + sqlite3TreeViewLine(pView, 0); + if( pList->eU4==EU4_NONE ){ + fprintf(stdout, "%s\n", zName); + }else if( pList->eU4==EU4_IDX ){ + fprintf(stdout, "%s (%d)\n", zName, pList->a[i].u4.idx); + }else{ + assert( pList->eU4==EU4_EXPR ); + if( pList->a[i].u4.pExpr==0 ){ + fprintf(stdout, "%s (pExpr=NULL)\n", zName); + }else{ + fprintf(stdout, "%s\n", zName); + sqlite3TreeViewPush(&pView, inId-1); + sqlite3TreeViewExpr(pView, pList->a[i].u4.pExpr, 0); + sqlite3TreeViewPop(&pView); + } + } + sqlite3TreeViewPop(&pView); + } + } +} +SQLITE_PRIVATE void sqlite3TreeViewIdList( + TreeView *pView, + const IdList *pList, + u8 moreToFollow, + const char *zLabel +){ + sqlite3TreeViewPush(&pView, moreToFollow); + sqlite3TreeViewBareIdList(pView, pList, zLabel); + sqlite3TreeViewPop(&pView); +} + +/* +** Generate a human-readable explanation of a list of Upsert objects +*/ +SQLITE_PRIVATE void sqlite3TreeViewUpsert( + TreeView *pView, + const Upsert *pUpsert, + u8 moreToFollow +){ + if( pUpsert==0 ) return; + sqlite3TreeViewPush(&pView, moreToFollow); + while( pUpsert ){ + int n; + sqlite3TreeViewPush(&pView, pUpsert->pNextUpsert!=0 || moreToFollow); + sqlite3TreeViewLine(pView, "ON CONFLICT DO %s", + pUpsert->isDoUpdate ? "UPDATE" : "NOTHING"); + n = (pUpsert->pUpsertSet!=0) + (pUpsert->pUpsertWhere!=0); + sqlite3TreeViewExprList(pView, pUpsert->pUpsertTarget, (n--)>0, "TARGET"); + sqlite3TreeViewExprList(pView, pUpsert->pUpsertSet, (n--)>0, "SET"); + if( pUpsert->pUpsertWhere ){ + sqlite3TreeViewItem(pView, "WHERE", (n--)>0); + sqlite3TreeViewExpr(pView, pUpsert->pUpsertWhere, 0); + sqlite3TreeViewPop(&pView); + } + sqlite3TreeViewPop(&pView); + pUpsert = pUpsert->pNextUpsert; + } + sqlite3TreeViewPop(&pView); +} + +#if TREETRACE_ENABLED +/* +** Generate a human-readable diagram of the data structure that go +** into generating an DELETE statement. +*/ +SQLITE_PRIVATE void sqlite3TreeViewDelete( + const With *pWith, + const SrcList *pTabList, + const Expr *pWhere, + const ExprList *pOrderBy, + const Expr *pLimit, + const Trigger *pTrigger +){ + int n = 0; + TreeView *pView = 0; + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewLine(pView, "DELETE"); + if( pWith ) n++; + if( pTabList ) n++; + if( pWhere ) n++; + if( pOrderBy ) n++; + if( pLimit ) n++; + if( pTrigger ) n++; + if( pWith ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewWith(pView, pWith, 0); + sqlite3TreeViewPop(&pView); + } + if( pTabList ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "FROM"); + sqlite3TreeViewSrcList(pView, pTabList); + sqlite3TreeViewPop(&pView); + } + if( pWhere ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "WHERE"); + sqlite3TreeViewExpr(pView, pWhere, 0); + sqlite3TreeViewPop(&pView); + } + if( pOrderBy ){ + sqlite3TreeViewExprList(pView, pOrderBy, (--n)>0, "ORDER-BY"); + } + if( pLimit ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "LIMIT"); + sqlite3TreeViewExpr(pView, pLimit, 0); + sqlite3TreeViewPop(&pView); + } + if( pTrigger ){ + sqlite3TreeViewTrigger(pView, pTrigger, (--n)>0, 1); + } + sqlite3TreeViewPop(&pView); +} +#endif /* TREETRACE_ENABLED */ + +#if TREETRACE_ENABLED +/* +** Generate a human-readable diagram of the data structure that go +** into generating an INSERT statement. +*/ +SQLITE_PRIVATE void sqlite3TreeViewInsert( + const With *pWith, + const SrcList *pTabList, + const IdList *pColumnList, + const Select *pSelect, + const ExprList *pExprList, + int onError, + const Upsert *pUpsert, + const Trigger *pTrigger +){ + TreeView *pView = 0; + int n = 0; + const char *zLabel = "INSERT"; + switch( onError ){ + case OE_Replace: zLabel = "REPLACE"; break; + case OE_Ignore: zLabel = "INSERT OR IGNORE"; break; + case OE_Rollback: zLabel = "INSERT OR ROLLBACK"; break; + case OE_Abort: zLabel = "INSERT OR ABORT"; break; + case OE_Fail: zLabel = "INSERT OR FAIL"; break; + } + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewLine(pView, zLabel); + if( pWith ) n++; + if( pTabList ) n++; + if( pColumnList ) n++; + if( pSelect ) n++; + if( pExprList ) n++; + if( pUpsert ) n++; + if( pTrigger ) n++; + if( pWith ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewWith(pView, pWith, 0); + sqlite3TreeViewPop(&pView); + } + if( pTabList ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "INTO"); + sqlite3TreeViewSrcList(pView, pTabList); + sqlite3TreeViewPop(&pView); + } + if( pColumnList ){ + sqlite3TreeViewIdList(pView, pColumnList, (--n)>0, "COLUMNS"); + } + if( pSelect ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "DATA-SOURCE"); + sqlite3TreeViewSelect(pView, pSelect, 0); + sqlite3TreeViewPop(&pView); + } + if( pExprList ){ + sqlite3TreeViewExprList(pView, pExprList, (--n)>0, "VALUES"); + } + if( pUpsert ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "UPSERT"); + sqlite3TreeViewUpsert(pView, pUpsert, 0); + sqlite3TreeViewPop(&pView); + } + if( pTrigger ){ + sqlite3TreeViewTrigger(pView, pTrigger, (--n)>0, 1); + } + sqlite3TreeViewPop(&pView); +} +#endif /* TREETRACE_ENABLED */ + +#if TREETRACE_ENABLED +/* +** Generate a human-readable diagram of the data structure that go +** into generating an UPDATE statement. +*/ +SQLITE_PRIVATE void sqlite3TreeViewUpdate( + const With *pWith, + const SrcList *pTabList, + const ExprList *pChanges, + const Expr *pWhere, + int onError, + const ExprList *pOrderBy, + const Expr *pLimit, + const Upsert *pUpsert, + const Trigger *pTrigger +){ + int n = 0; + TreeView *pView = 0; + const char *zLabel = "UPDATE"; + switch( onError ){ + case OE_Replace: zLabel = "UPDATE OR REPLACE"; break; + case OE_Ignore: zLabel = "UPDATE OR IGNORE"; break; + case OE_Rollback: zLabel = "UPDATE OR ROLLBACK"; break; + case OE_Abort: zLabel = "UPDATE OR ABORT"; break; + case OE_Fail: zLabel = "UPDATE OR FAIL"; break; + } + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewLine(pView, zLabel); + if( pWith ) n++; + if( pTabList ) n++; + if( pChanges ) n++; + if( pWhere ) n++; + if( pOrderBy ) n++; + if( pLimit ) n++; + if( pUpsert ) n++; + if( pTrigger ) n++; + if( pWith ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewWith(pView, pWith, 0); + sqlite3TreeViewPop(&pView); + } + if( pTabList ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "FROM"); + sqlite3TreeViewSrcList(pView, pTabList); + sqlite3TreeViewPop(&pView); + } + if( pChanges ){ + sqlite3TreeViewExprList(pView, pChanges, (--n)>0, "SET"); + } + if( pWhere ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "WHERE"); + sqlite3TreeViewExpr(pView, pWhere, 0); + sqlite3TreeViewPop(&pView); + } + if( pOrderBy ){ + sqlite3TreeViewExprList(pView, pOrderBy, (--n)>0, "ORDER-BY"); + } + if( pLimit ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "LIMIT"); + sqlite3TreeViewExpr(pView, pLimit, 0); + sqlite3TreeViewPop(&pView); + } + if( pUpsert ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "UPSERT"); + sqlite3TreeViewUpsert(pView, pUpsert, 0); + sqlite3TreeViewPop(&pView); + } + if( pTrigger ){ + sqlite3TreeViewTrigger(pView, pTrigger, (--n)>0, 1); + } + sqlite3TreeViewPop(&pView); +} +#endif /* TREETRACE_ENABLED */ + +#ifndef SQLITE_OMIT_TRIGGER +/* +** Show a human-readable graph of a TriggerStep +*/ +SQLITE_PRIVATE void sqlite3TreeViewTriggerStep( + TreeView *pView, + const TriggerStep *pStep, + u8 moreToFollow, + u8 showFullList +){ + int cnt = 0; + if( pStep==0 ) return; + sqlite3TreeViewPush(&pView, + moreToFollow || (showFullList && pStep->pNext!=0)); + do{ + if( cnt++ && pStep->pNext==0 ){ + sqlite3TreeViewPop(&pView); + sqlite3TreeViewPush(&pView, 0); + } + sqlite3TreeViewLine(pView, "%s", pStep->zSpan ? pStep->zSpan : "RETURNING"); + }while( showFullList && (pStep = pStep->pNext)!=0 ); + sqlite3TreeViewPop(&pView); +} + +/* +** Show a human-readable graph of a Trigger +*/ +SQLITE_PRIVATE void sqlite3TreeViewTrigger( + TreeView *pView, + const Trigger *pTrigger, + u8 moreToFollow, + u8 showFullList +){ + int cnt = 0; + if( pTrigger==0 ) return; + sqlite3TreeViewPush(&pView, + moreToFollow || (showFullList && pTrigger->pNext!=0)); + do{ + if( cnt++ && pTrigger->pNext==0 ){ + sqlite3TreeViewPop(&pView); + sqlite3TreeViewPush(&pView, 0); + } + sqlite3TreeViewLine(pView, "TRIGGER %s", pTrigger->zName); + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewTriggerStep(pView, pTrigger->step_list, 0, 1); + sqlite3TreeViewPop(&pView); + }while( showFullList && (pTrigger = pTrigger->pNext)!=0 ); + sqlite3TreeViewPop(&pView); +} +#endif /* SQLITE_OMIT_TRIGGER */ + + +/* +** These simplified versions of the tree-view routines omit unnecessary +** parameters. These variants are intended to be used from a symbolic +** debugger, such as "gdb", during interactive debugging sessions. +** +** This routines are given external linkage so that they will always be +** accessible to the debugging, and to avoid warnings about unused +** functions. But these routines only exist in debugging builds, so they +** do not contaminate the interface. +*/ +SQLITE_PRIVATE void sqlite3ShowExpr(const Expr *p){ sqlite3TreeViewExpr(0,p,0); } +SQLITE_PRIVATE void sqlite3ShowExprList(const ExprList *p){ sqlite3TreeViewExprList(0,p,0,0);} +SQLITE_PRIVATE void sqlite3ShowIdList(const IdList *p){ sqlite3TreeViewIdList(0,p,0,0); } +SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList *p){ sqlite3TreeViewSrcList(0,p); } +SQLITE_PRIVATE void sqlite3ShowSelect(const Select *p){ sqlite3TreeViewSelect(0,p,0); } +SQLITE_PRIVATE void sqlite3ShowWith(const With *p){ sqlite3TreeViewWith(0,p,0); } +SQLITE_PRIVATE void sqlite3ShowUpsert(const Upsert *p){ sqlite3TreeViewUpsert(0,p,0); } +#ifndef SQLITE_OMIT_TRIGGER +SQLITE_PRIVATE void sqlite3ShowTriggerStep(const TriggerStep *p){ + sqlite3TreeViewTriggerStep(0,p,0,0); +} +SQLITE_PRIVATE void sqlite3ShowTriggerStepList(const TriggerStep *p){ + sqlite3TreeViewTriggerStep(0,p,0,1); +} +SQLITE_PRIVATE void sqlite3ShowTrigger(const Trigger *p){ sqlite3TreeViewTrigger(0,p,0,0); } +SQLITE_PRIVATE void sqlite3ShowTriggerList(const Trigger *p){ sqlite3TreeViewTrigger(0,p,0,1);} +#endif +#ifndef SQLITE_OMIT_WINDOWFUNC +SQLITE_PRIVATE void sqlite3ShowWindow(const Window *p){ sqlite3TreeViewWindow(0,p,0); } +SQLITE_PRIVATE void sqlite3ShowWinFunc(const Window *p){ sqlite3TreeViewWinFunc(0,p,0); } +#endif + #endif /* SQLITE_DEBUG */ /************** End of treeview.c ********************************************/ @@ -30609,11 +32398,16 @@ SQLITE_API void sqlite3_randomness(int N, void *pBuf){ ** number generator) not as an encryption device. */ if( !wsdPrng.isInit ){ + sqlite3_vfs *pVfs = sqlite3_vfs_find(0); int i; char k[256]; wsdPrng.j = 0; wsdPrng.i = 0; - sqlite3OsRandomness(sqlite3_vfs_find(0), 256, k); + if( NEVER(pVfs==0) ){ + memset(k, 0, sizeof(k)); + }else{ + sqlite3OsRandomness(pVfs, 256, k); + } for(i=0; i<256; i++){ wsdPrng.s[i] = (u8)i; } @@ -31506,16 +33300,6 @@ SQLITE_PRIVATE void sqlite3UtfSelfTest(void){ #include #endif -/* -** Routine needed to support the testcase() macro. -*/ -#ifdef SQLITE_COVERAGE_TEST -SQLITE_PRIVATE void sqlite3Coverage(int x){ - static unsigned dummy = 0; - dummy += (unsigned)x; -} -#endif - /* ** Calls to sqlite3FaultSim() are used to simulate a failure during testing, ** or to bypass normal error detection during testing in order to let @@ -31545,11 +33329,21 @@ SQLITE_PRIVATE int sqlite3FaultSim(int iTest){ #ifndef SQLITE_OMIT_FLOATING_POINT /* ** Return true if the floating point value is Not a Number (NaN). +** +** Use the math library isnan() function if compiled with SQLITE_HAVE_ISNAN. +** Otherwise, we have our own implementation that works on most systems. */ SQLITE_PRIVATE int sqlite3IsNaN(double x){ + int rc; /* The value return */ +#if !SQLITE_HAVE_ISNAN && !HAVE_ISNAN u64 y; memcpy(&y,&x,sizeof(y)); - return IsNaN(y); + rc = IsNaN(y); +#else + rc = isnan(x); +#endif /* HAVE_ISNAN */ + testcase( rc ); + return rc; } #endif /* SQLITE_OMIT_FLOATING_POINT */ @@ -31574,8 +33368,14 @@ SQLITE_PRIVATE int sqlite3Strlen30(const char *z){ ** the column name if and only if the COLFLAG_HASTYPE flag is set. */ SQLITE_PRIVATE char *sqlite3ColumnType(Column *pCol, char *zDflt){ - if( (pCol->colFlags & COLFLAG_HASTYPE)==0 ) return zDflt; - return pCol->zName + strlen(pCol->zName) + 1; + if( pCol->colFlags & COLFLAG_HASTYPE ){ + return pCol->zCnName + strlen(pCol->zCnName) + 1; + }else if( pCol->eCType ){ + assert( pCol->eCType<=SQLITE_N_STDTYPE ); + return (char*)sqlite3StdType[pCol->eCType-1]; + }else{ + return zDflt; + } } /* @@ -31596,7 +33396,11 @@ static SQLITE_NOINLINE void sqlite3ErrorFinish(sqlite3 *db, int err_code){ SQLITE_PRIVATE void sqlite3Error(sqlite3 *db, int err_code){ assert( db!=0 ); db->errCode = err_code; - if( err_code || db->pErr ) sqlite3ErrorFinish(db, err_code); + if( err_code || db->pErr ){ + sqlite3ErrorFinish(db, err_code); + }else{ + db->errByteOffset = -1; + } } /* @@ -31606,6 +33410,7 @@ SQLITE_PRIVATE void sqlite3Error(sqlite3 *db, int err_code){ SQLITE_PRIVATE void sqlite3ErrorClear(sqlite3 *db){ assert( db!=0 ); db->errCode = SQLITE_OK; + db->errByteOffset = -1; if( db->pErr ) sqlite3ValueSetNull(db->pErr); } @@ -31626,17 +33431,8 @@ SQLITE_PRIVATE void sqlite3SystemError(sqlite3 *db, int rc){ ** handle "db". The error code is set to "err_code". ** ** If it is not NULL, string zFormat specifies the format of the -** error string in the style of the printf functions: The following -** format characters are allowed: -** -** %s Insert a string -** %z A string that should be freed after use -** %d Insert an integer -** %T Insert a token -** %S Insert the first element of a SrcList -** -** zFormat and any string tokens that follow it are assumed to be -** encoded in UTF-8. +** error string. zFormat and any string tokens that follow it are +** assumed to be encoded in UTF-8. ** ** To clear the most recent error for sqlite handle "db", sqlite3Error ** should be called with err_code set to SQLITE_OK and zFormat set @@ -31660,13 +33456,6 @@ SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3 *db, int err_code, const char *z /* ** Add an error message to pParse->zErrMsg and increment pParse->nErr. -** The following formatting characters are allowed: -** -** %s Insert a string -** %z A string that should be freed after use -** %d Insert an integer -** %T Insert a token -** %S Insert the first element of a SrcList ** ** This function should be used to report any error that occurs while ** compiling an SQL statement (i.e. within sqlite3_prepare()). The @@ -31679,11 +33468,19 @@ SQLITE_PRIVATE void sqlite3ErrorMsg(Parse *pParse, const char *zFormat, ...){ char *zMsg; va_list ap; sqlite3 *db = pParse->db; + assert( db!=0 ); + assert( db->pParse==pParse || db->pParse->pToplevel==pParse ); + db->errByteOffset = -2; va_start(ap, zFormat); zMsg = sqlite3VMPrintf(db, zFormat, ap); va_end(ap); + if( db->errByteOffset<-1 ) db->errByteOffset = -1; if( db->suppressErr ){ sqlite3DbFree(db, zMsg); + if( db->mallocFailed ){ + pParse->nErr++; + pParse->rc = SQLITE_NOMEM; + } }else{ pParse->nErr++; sqlite3DbFree(db, pParse->zErrMsg); @@ -31746,11 +33543,34 @@ SQLITE_PRIVATE void sqlite3Dequote(char *z){ z[j] = 0; } SQLITE_PRIVATE void sqlite3DequoteExpr(Expr *p){ + assert( !ExprHasProperty(p, EP_IntValue) ); assert( sqlite3Isquote(p->u.zToken[0]) ); p->flags |= p->u.zToken[0]=='"' ? EP_Quoted|EP_DblQuoted : EP_Quoted; sqlite3Dequote(p->u.zToken); } +/* +** If the input token p is quoted, try to adjust the token to remove +** the quotes. This is not always possible: +** +** "abc" -> abc +** "ab""cd" -> (not possible because of the interior "") +** +** Remove the quotes if possible. This is a optimization. The overall +** system should still return the correct answer even if this routine +** is always a no-op. +*/ +SQLITE_PRIVATE void sqlite3DequoteToken(Token *p){ + unsigned int i; + if( p->n<2 ) return; + if( !sqlite3Isquote(p->z[0]) ) return; + for(i=1; in-1; i++){ + if( sqlite3Isquote(p->z[i]) ) return; + } + p->n -= 2; + p->z++; +} + /* ** Generate a Token object from a string */ @@ -32856,13 +34676,13 @@ static void logBadConnection(const char *zType){ ** used as an argument to sqlite3_errmsg() or sqlite3_close(). */ SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3 *db){ - u32 magic; + u8 eOpenState; if( db==0 ){ logBadConnection("NULL"); return 0; } - magic = db->magic; - if( magic!=SQLITE_MAGIC_OPEN ){ + eOpenState = db->eOpenState; + if( eOpenState!=SQLITE_STATE_OPEN ){ if( sqlite3SafetyCheckSickOrOk(db) ){ testcase( sqlite3GlobalConfig.xLog!=0 ); logBadConnection("unopened"); @@ -32873,11 +34693,11 @@ SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3 *db){ } } SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3 *db){ - u32 magic; - magic = db->magic; - if( magic!=SQLITE_MAGIC_SICK && - magic!=SQLITE_MAGIC_OPEN && - magic!=SQLITE_MAGIC_BUSY ){ + u8 eOpenState; + eOpenState = db->eOpenState; + if( eOpenState!=SQLITE_STATE_SICK && + eOpenState!=SQLITE_STATE_OPEN && + eOpenState!=SQLITE_STATE_BUSY ){ testcase( sqlite3GlobalConfig.xLog!=0 ); logBadConnection("invalid"); return 0; @@ -33042,7 +34862,6 @@ SQLITE_PRIVATE LogEst sqlite3LogEst(u64 x){ return a[x&7] + y - 10; } -#ifndef SQLITE_OMIT_VIRTUALTABLE /* ** Convert a double into a LogEst ** In other words, compute an approximation for 10*log2(x). @@ -33057,16 +34876,9 @@ SQLITE_PRIVATE LogEst sqlite3LogEstFromDouble(double x){ e = (a>>52) - 1022; return e*10; } -#endif /* SQLITE_OMIT_VIRTUALTABLE */ -#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || \ - defined(SQLITE_ENABLE_STAT4) || \ - defined(SQLITE_EXPLAIN_ESTIMATED_ROWS) /* ** Convert a LogEst into an integer. -** -** Note that this routine is only used when one or more of various -** non-standard compile-time options is enabled. */ SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst x){ u64 n; @@ -33074,17 +34886,9 @@ SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst x){ x /= 10; if( n>=5 ) n -= 2; else if( n>=1 ) n -= 1; -#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || \ - defined(SQLITE_EXPLAIN_ESTIMATED_ROWS) if( x>60 ) return (u64)LARGEST_INT64; -#else - /* If only SQLITE_ENABLE_STAT4 is on, then the largest input - ** possible to this routine is 310, resulting in a maximum x of 31 */ - assert( x<=60 ); -#endif return x>=3 ? (n+8)<<(x-3) : (n+8)>>(3-x); } -#endif /* defined SCANSTAT or STAT4 or ESTIMATED_ROWS */ /* ** Add a new name/number pair to a VList. This might require that the @@ -33478,53 +35282,53 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ /* 0 */ "Savepoint" OpHelp(""), /* 1 */ "AutoCommit" OpHelp(""), /* 2 */ "Transaction" OpHelp(""), - /* 3 */ "SorterNext" OpHelp(""), - /* 4 */ "Prev" OpHelp(""), - /* 5 */ "Next" OpHelp(""), - /* 6 */ "Checkpoint" OpHelp(""), - /* 7 */ "JournalMode" OpHelp(""), - /* 8 */ "Vacuum" OpHelp(""), - /* 9 */ "VFilter" OpHelp("iplan=r[P3] zplan='P4'"), - /* 10 */ "VUpdate" OpHelp("data=r[P3@P2]"), - /* 11 */ "Goto" OpHelp(""), - /* 12 */ "Gosub" OpHelp(""), - /* 13 */ "InitCoroutine" OpHelp(""), - /* 14 */ "Yield" OpHelp(""), - /* 15 */ "MustBeInt" OpHelp(""), - /* 16 */ "Jump" OpHelp(""), - /* 17 */ "Once" OpHelp(""), - /* 18 */ "If" OpHelp(""), + /* 3 */ "Checkpoint" OpHelp(""), + /* 4 */ "JournalMode" OpHelp(""), + /* 5 */ "Vacuum" OpHelp(""), + /* 6 */ "VFilter" OpHelp("iplan=r[P3] zplan='P4'"), + /* 7 */ "VUpdate" OpHelp("data=r[P3@P2]"), + /* 8 */ "Goto" OpHelp(""), + /* 9 */ "Gosub" OpHelp(""), + /* 10 */ "InitCoroutine" OpHelp(""), + /* 11 */ "Yield" OpHelp(""), + /* 12 */ "MustBeInt" OpHelp(""), + /* 13 */ "Jump" OpHelp(""), + /* 14 */ "Once" OpHelp(""), + /* 15 */ "If" OpHelp(""), + /* 16 */ "IfNot" OpHelp(""), + /* 17 */ "IsNullOrType" OpHelp("if typeof(r[P1]) IN (P3,5) goto P2"), + /* 18 */ "IfNullRow" OpHelp("if P1.nullRow then r[P3]=NULL, goto P2"), /* 19 */ "Not" OpHelp("r[P2]= !r[P1]"), - /* 20 */ "IfNot" OpHelp(""), - /* 21 */ "IfNullRow" OpHelp("if P1.nullRow then r[P3]=NULL, goto P2"), - /* 22 */ "SeekLT" OpHelp("key=r[P3@P4]"), - /* 23 */ "SeekLE" OpHelp("key=r[P3@P4]"), - /* 24 */ "SeekGE" OpHelp("key=r[P3@P4]"), - /* 25 */ "SeekGT" OpHelp("key=r[P3@P4]"), - /* 26 */ "IfNotOpen" OpHelp("if( !csr[P1] ) goto P2"), - /* 27 */ "IfNoHope" OpHelp("key=r[P3@P4]"), - /* 28 */ "NoConflict" OpHelp("key=r[P3@P4]"), - /* 29 */ "NotFound" OpHelp("key=r[P3@P4]"), - /* 30 */ "Found" OpHelp("key=r[P3@P4]"), - /* 31 */ "SeekRowid" OpHelp("intkey=r[P3]"), - /* 32 */ "NotExists" OpHelp("intkey=r[P3]"), - /* 33 */ "Last" OpHelp(""), - /* 34 */ "IfSmaller" OpHelp(""), - /* 35 */ "SorterSort" OpHelp(""), - /* 36 */ "Sort" OpHelp(""), - /* 37 */ "Rewind" OpHelp(""), - /* 38 */ "IdxLE" OpHelp("key=r[P3@P4]"), - /* 39 */ "IdxGT" OpHelp("key=r[P3@P4]"), - /* 40 */ "IdxLT" OpHelp("key=r[P3@P4]"), - /* 41 */ "IdxGE" OpHelp("key=r[P3@P4]"), - /* 42 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"), + /* 20 */ "SeekLT" OpHelp("key=r[P3@P4]"), + /* 21 */ "SeekLE" OpHelp("key=r[P3@P4]"), + /* 22 */ "SeekGE" OpHelp("key=r[P3@P4]"), + /* 23 */ "SeekGT" OpHelp("key=r[P3@P4]"), + /* 24 */ "IfNotOpen" OpHelp("if( !csr[P1] ) goto P2"), + /* 25 */ "IfNoHope" OpHelp("key=r[P3@P4]"), + /* 26 */ "NoConflict" OpHelp("key=r[P3@P4]"), + /* 27 */ "NotFound" OpHelp("key=r[P3@P4]"), + /* 28 */ "Found" OpHelp("key=r[P3@P4]"), + /* 29 */ "SeekRowid" OpHelp("intkey=r[P3]"), + /* 30 */ "NotExists" OpHelp("intkey=r[P3]"), + /* 31 */ "Last" OpHelp(""), + /* 32 */ "IfSmaller" OpHelp(""), + /* 33 */ "SorterSort" OpHelp(""), + /* 34 */ "Sort" OpHelp(""), + /* 35 */ "Rewind" OpHelp(""), + /* 36 */ "SorterNext" OpHelp(""), + /* 37 */ "Prev" OpHelp(""), + /* 38 */ "Next" OpHelp(""), + /* 39 */ "IdxLE" OpHelp("key=r[P3@P4]"), + /* 40 */ "IdxGT" OpHelp("key=r[P3@P4]"), + /* 41 */ "IdxLT" OpHelp("key=r[P3@P4]"), + /* 42 */ "IdxGE" OpHelp("key=r[P3@P4]"), /* 43 */ "Or" OpHelp("r[P3]=(r[P1] || r[P2])"), /* 44 */ "And" OpHelp("r[P3]=(r[P1] && r[P2])"), - /* 45 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"), - /* 46 */ "Program" OpHelp(""), - /* 47 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"), - /* 48 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"), - /* 49 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]--, goto P2"), + /* 45 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"), + /* 46 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"), + /* 47 */ "Program" OpHelp(""), + /* 48 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"), + /* 49 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"), /* 50 */ "IsNull" OpHelp("if r[P1]==NULL goto P2"), /* 51 */ "NotNull" OpHelp("if r[P1]!=NULL goto P2"), /* 52 */ "Ne" OpHelp("IF r[P3]!=r[P1]"), @@ -33533,50 +35337,50 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ /* 55 */ "Le" OpHelp("IF r[P3]<=r[P1]"), /* 56 */ "Lt" OpHelp("IF r[P3]=r[P1]"), - /* 58 */ "ElseNotEq" OpHelp(""), - /* 59 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"), - /* 60 */ "IncrVacuum" OpHelp(""), - /* 61 */ "VNext" OpHelp(""), - /* 62 */ "Init" OpHelp("Start at P2"), - /* 63 */ "PureFunc" OpHelp("r[P3]=func(r[P2@NP])"), - /* 64 */ "Function" OpHelp("r[P3]=func(r[P2@NP])"), - /* 65 */ "Return" OpHelp(""), - /* 66 */ "EndCoroutine" OpHelp(""), - /* 67 */ "HaltIfNull" OpHelp("if r[P3]=null halt"), - /* 68 */ "Halt" OpHelp(""), - /* 69 */ "Integer" OpHelp("r[P2]=P1"), - /* 70 */ "Int64" OpHelp("r[P2]=P4"), - /* 71 */ "String" OpHelp("r[P2]='P4' (len=P1)"), - /* 72 */ "Null" OpHelp("r[P2..P3]=NULL"), - /* 73 */ "SoftNull" OpHelp("r[P1]=NULL"), - /* 74 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"), - /* 75 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"), - /* 76 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), - /* 77 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), - /* 78 */ "SCopy" OpHelp("r[P2]=r[P1]"), - /* 79 */ "IntCopy" OpHelp("r[P2]=r[P1]"), - /* 80 */ "ChngCntRow" OpHelp("output=r[P1]"), - /* 81 */ "ResultRow" OpHelp("output=r[P1@P2]"), - /* 82 */ "CollSeq" OpHelp(""), - /* 83 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), - /* 84 */ "RealAffinity" OpHelp(""), - /* 85 */ "Cast" OpHelp("affinity(r[P1])"), - /* 86 */ "Permutation" OpHelp(""), - /* 87 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), - /* 88 */ "IsTrue" OpHelp("r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4"), - /* 89 */ "Offset" OpHelp("r[P3] = sqlite_offset(P1)"), - /* 90 */ "Column" OpHelp("r[P3]=PX"), - /* 91 */ "Affinity" OpHelp("affinity(r[P1@P2])"), - /* 92 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), - /* 93 */ "Count" OpHelp("r[P2]=count()"), - /* 94 */ "ReadCookie" OpHelp(""), - /* 95 */ "SetCookie" OpHelp(""), - /* 96 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"), - /* 97 */ "OpenRead" OpHelp("root=P2 iDb=P3"), - /* 98 */ "OpenWrite" OpHelp("root=P2 iDb=P3"), - /* 99 */ "OpenDup" OpHelp(""), - /* 100 */ "OpenAutoindex" OpHelp("nColumn=P2"), - /* 101 */ "OpenEphemeral" OpHelp("nColumn=P2"), + /* 58 */ "ElseEq" OpHelp(""), + /* 59 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]--, goto P2"), + /* 60 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"), + /* 61 */ "IncrVacuum" OpHelp(""), + /* 62 */ "VNext" OpHelp(""), + /* 63 */ "Filter" OpHelp("if key(P3@P4) not in filter(P1) goto P2"), + /* 64 */ "Init" OpHelp("Start at P2"), + /* 65 */ "PureFunc" OpHelp("r[P3]=func(r[P2@NP])"), + /* 66 */ "Function" OpHelp("r[P3]=func(r[P2@NP])"), + /* 67 */ "Return" OpHelp(""), + /* 68 */ "EndCoroutine" OpHelp(""), + /* 69 */ "HaltIfNull" OpHelp("if r[P3]=null halt"), + /* 70 */ "Halt" OpHelp(""), + /* 71 */ "Integer" OpHelp("r[P2]=P1"), + /* 72 */ "Int64" OpHelp("r[P2]=P4"), + /* 73 */ "String" OpHelp("r[P2]='P4' (len=P1)"), + /* 74 */ "BeginSubrtn" OpHelp("r[P2]=NULL"), + /* 75 */ "Null" OpHelp("r[P2..P3]=NULL"), + /* 76 */ "SoftNull" OpHelp("r[P1]=NULL"), + /* 77 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"), + /* 78 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"), + /* 79 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), + /* 80 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), + /* 81 */ "SCopy" OpHelp("r[P2]=r[P1]"), + /* 82 */ "IntCopy" OpHelp("r[P2]=r[P1]"), + /* 83 */ "FkCheck" OpHelp(""), + /* 84 */ "ResultRow" OpHelp("output=r[P1@P2]"), + /* 85 */ "CollSeq" OpHelp(""), + /* 86 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), + /* 87 */ "RealAffinity" OpHelp(""), + /* 88 */ "Cast" OpHelp("affinity(r[P1])"), + /* 89 */ "Permutation" OpHelp(""), + /* 90 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), + /* 91 */ "IsTrue" OpHelp("r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4"), + /* 92 */ "ZeroOrNull" OpHelp("r[P2] = 0 OR NULL"), + /* 93 */ "Offset" OpHelp("r[P3] = sqlite_offset(P1)"), + /* 94 */ "Column" OpHelp("r[P3]=PX cursor P1 column P2"), + /* 95 */ "TypeCheck" OpHelp("typecheck(r[P1@P2])"), + /* 96 */ "Affinity" OpHelp("affinity(r[P1@P2])"), + /* 97 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), + /* 98 */ "Count" OpHelp("r[P2]=count()"), + /* 99 */ "ReadCookie" OpHelp(""), + /* 100 */ "SetCookie" OpHelp(""), + /* 101 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"), /* 102 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"), /* 103 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"), /* 104 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"), - /* 156 */ "AggInverse" OpHelp("accum=r[P3] inverse(r[P2@P5])"), - /* 157 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), - /* 158 */ "AggStep1" OpHelp("accum=r[P3] step(r[P2@P5])"), - /* 159 */ "AggValue" OpHelp("r[P3]=value N=P2"), - /* 160 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), - /* 161 */ "Expire" OpHelp(""), - /* 162 */ "CursorLock" OpHelp(""), - /* 163 */ "CursorUnlock" OpHelp(""), - /* 164 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"), - /* 165 */ "VBegin" OpHelp(""), - /* 166 */ "VCreate" OpHelp(""), - /* 167 */ "VDestroy" OpHelp(""), - /* 168 */ "VOpen" OpHelp(""), - /* 169 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"), - /* 170 */ "VRename" OpHelp(""), - /* 171 */ "Pagecount" OpHelp(""), - /* 172 */ "MaxPgcnt" OpHelp(""), - /* 173 */ "Trace" OpHelp(""), - /* 174 */ "CursorHint" OpHelp(""), - /* 175 */ "ReleaseReg" OpHelp("release r[P1@P2] mask P3"), - /* 176 */ "Noop" OpHelp(""), - /* 177 */ "Explain" OpHelp(""), - /* 178 */ "Abortable" OpHelp(""), + /* 112 */ "OpenRead" OpHelp("root=P2 iDb=P3"), + /* 113 */ "OpenWrite" OpHelp("root=P2 iDb=P3"), + /* 114 */ "BitNot" OpHelp("r[P2]= ~r[P1]"), + /* 115 */ "OpenDup" OpHelp(""), + /* 116 */ "OpenAutoindex" OpHelp("nColumn=P2"), + /* 117 */ "String8" OpHelp("r[P2]='P4'"), + /* 118 */ "OpenEphemeral" OpHelp("nColumn=P2"), + /* 119 */ "SorterOpen" OpHelp(""), + /* 120 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"), + /* 121 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"), + /* 122 */ "Close" OpHelp(""), + /* 123 */ "ColumnsUsed" OpHelp(""), + /* 124 */ "SeekScan" OpHelp("Scan-ahead up to P1 rows"), + /* 125 */ "SeekHit" OpHelp("set P2<=seekHit<=P3"), + /* 126 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), + /* 127 */ "NewRowid" OpHelp("r[P2]=rowid"), + /* 128 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"), + /* 129 */ "RowCell" OpHelp(""), + /* 130 */ "Delete" OpHelp(""), + /* 131 */ "ResetCount" OpHelp(""), + /* 132 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), + /* 133 */ "SorterData" OpHelp("r[P2]=data"), + /* 134 */ "RowData" OpHelp("r[P2]=data"), + /* 135 */ "Rowid" OpHelp("r[P2]=PX rowid of P1"), + /* 136 */ "NullRow" OpHelp(""), + /* 137 */ "SeekEnd" OpHelp(""), + /* 138 */ "IdxInsert" OpHelp("key=r[P2]"), + /* 139 */ "SorterInsert" OpHelp("key=r[P2]"), + /* 140 */ "IdxDelete" OpHelp("key=r[P2@P3]"), + /* 141 */ "DeferredSeek" OpHelp("Move P3 to P1.rowid if needed"), + /* 142 */ "IdxRowid" OpHelp("r[P2]=rowid"), + /* 143 */ "FinishSeek" OpHelp(""), + /* 144 */ "Destroy" OpHelp(""), + /* 145 */ "Clear" OpHelp(""), + /* 146 */ "ResetSorter" OpHelp(""), + /* 147 */ "CreateBtree" OpHelp("r[P2]=root iDb=P1 flags=P3"), + /* 148 */ "SqlExec" OpHelp(""), + /* 149 */ "ParseSchema" OpHelp(""), + /* 150 */ "LoadAnalysis" OpHelp(""), + /* 151 */ "DropTable" OpHelp(""), + /* 152 */ "DropIndex" OpHelp(""), + /* 153 */ "Real" OpHelp("r[P2]=P4"), + /* 154 */ "DropTrigger" OpHelp(""), + /* 155 */ "IntegrityCk" OpHelp(""), + /* 156 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"), + /* 157 */ "Param" OpHelp(""), + /* 158 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), + /* 159 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"), + /* 160 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"), + /* 161 */ "AggInverse" OpHelp("accum=r[P3] inverse(r[P2@P5])"), + /* 162 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 163 */ "AggStep1" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 164 */ "AggValue" OpHelp("r[P3]=value N=P2"), + /* 165 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), + /* 166 */ "Expire" OpHelp(""), + /* 167 */ "CursorLock" OpHelp(""), + /* 168 */ "CursorUnlock" OpHelp(""), + /* 169 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"), + /* 170 */ "VBegin" OpHelp(""), + /* 171 */ "VCreate" OpHelp(""), + /* 172 */ "VDestroy" OpHelp(""), + /* 173 */ "VOpen" OpHelp(""), + /* 174 */ "VInitIn" OpHelp("r[P2]=ValueList(P1,P3)"), + /* 175 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"), + /* 176 */ "VRename" OpHelp(""), + /* 177 */ "Pagecount" OpHelp(""), + /* 178 */ "MaxPgcnt" OpHelp(""), + /* 179 */ "ClrSubtype" OpHelp("r[P1].subtype = 0"), + /* 180 */ "FilterAdd" OpHelp("filter(P1) += key(P3@P4)"), + /* 181 */ "Trace" OpHelp(""), + /* 182 */ "CursorHint" OpHelp(""), + /* 183 */ "ReleaseReg" OpHelp("release r[P1@P2] mask P3"), + /* 184 */ "Noop" OpHelp(""), + /* 185 */ "Explain" OpHelp(""), + /* 186 */ "Abortable" OpHelp(""), }; return azName[i]; } @@ -33960,205 +35772,7 @@ static pid_t randomnessPid = 0; /* ** Include code that is common to all os_*.c files */ -/************** Include os_common.h in the middle of os_unix.c ***************/ -/************** Begin file os_common.h ***************************************/ -/* -** 2004 May 22 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This file contains macros and a little bit of code that is common to -** all of the platform-specific files (os_*.c) and is #included into those -** files. -** -** This file should be #included by the os_*.c files only. It is not a -** general purpose header file. -*/ -#ifndef _OS_COMMON_H_ -#define _OS_COMMON_H_ - -/* -** At least two bugs have slipped in because we changed the MEMORY_DEBUG -** macro to SQLITE_DEBUG and some older makefiles have not yet made the -** switch. The following code should catch this problem at compile-time. -*/ -#ifdef MEMORY_DEBUG -# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead." -#endif - -/* -** Macros for performance tracing. Normally turned off. Only works -** on i486 hardware. -*/ -#ifdef SQLITE_PERFORMANCE_TRACE - -/* -** hwtime.h contains inline assembler code for implementing -** high-performance timing routines. -*/ -/************** Include hwtime.h in the middle of os_common.h ****************/ -/************** Begin file hwtime.h ******************************************/ -/* -** 2008 May 27 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This file contains inline asm code for retrieving "high-performance" -** counters for x86 and x86_64 class CPUs. -*/ -#ifndef SQLITE_HWTIME_H -#define SQLITE_HWTIME_H - -/* -** The following routine only works on pentium-class (or newer) processors. -** It uses the RDTSC opcode to read the cycle count value out of the -** processor and returns that value. This can be used for high-res -** profiling. -*/ -#if !defined(__STRICT_ANSI__) && \ - (defined(__GNUC__) || defined(_MSC_VER)) && \ - (defined(i386) || defined(__i386__) || defined(_M_IX86)) - - #if defined(__GNUC__) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned int lo, hi; - __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); - return (sqlite_uint64)hi << 32 | lo; - } - - #elif defined(_MSC_VER) - - __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){ - __asm { - rdtsc - ret ; return value at EDX:EAX - } - } - - #endif - -#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__)) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long val; - __asm__ __volatile__ ("rdtsc" : "=A" (val)); - return val; - } - -#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__)) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long long retval; - unsigned long junk; - __asm__ __volatile__ ("\n\ - 1: mftbu %1\n\ - mftb %L0\n\ - mftbu %0\n\ - cmpw %0,%1\n\ - bne 1b" - : "=r" (retval), "=r" (junk)); - return retval; - } - -#else - - /* - ** asm() is needed for hardware timing support. Without asm(), - ** disable the sqlite3Hwtime() routine. - ** - ** sqlite3Hwtime() is only used for some obscure debugging - ** and analysis configurations, not in any deliverable, so this - ** should not be a great loss. - */ -SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); } - -#endif - -#endif /* !defined(SQLITE_HWTIME_H) */ - -/************** End of hwtime.h **********************************************/ -/************** Continuing where we left off in os_common.h ******************/ - -static sqlite_uint64 g_start; -static sqlite_uint64 g_elapsed; -#define TIMER_START g_start=sqlite3Hwtime() -#define TIMER_END g_elapsed=sqlite3Hwtime()-g_start -#define TIMER_ELAPSED g_elapsed -#else -#define TIMER_START -#define TIMER_END -#define TIMER_ELAPSED ((sqlite_uint64)0) -#endif - -/* -** If we compile with the SQLITE_TEST macro set, then the following block -** of code will give us the ability to simulate a disk I/O error. This -** is used for testing the I/O recovery logic. -*/ -#if defined(SQLITE_TEST) -SQLITE_API extern int sqlite3_io_error_hit; -SQLITE_API extern int sqlite3_io_error_hardhit; -SQLITE_API extern int sqlite3_io_error_pending; -SQLITE_API extern int sqlite3_io_error_persist; -SQLITE_API extern int sqlite3_io_error_benign; -SQLITE_API extern int sqlite3_diskfull_pending; -SQLITE_API extern int sqlite3_diskfull; -#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X) -#define SimulateIOError(CODE) \ - if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \ - || sqlite3_io_error_pending-- == 1 ) \ - { local_ioerr(); CODE; } -static void local_ioerr(){ - IOTRACE(("IOERR\n")); - sqlite3_io_error_hit++; - if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++; -} -#define SimulateDiskfullError(CODE) \ - if( sqlite3_diskfull_pending ){ \ - if( sqlite3_diskfull_pending == 1 ){ \ - local_ioerr(); \ - sqlite3_diskfull = 1; \ - sqlite3_io_error_hit = 1; \ - CODE; \ - }else{ \ - sqlite3_diskfull_pending--; \ - } \ - } -#else -#define SimulateIOErrorBenign(X) -#define SimulateIOError(A) -#define SimulateDiskfullError(A) -#endif /* defined(SQLITE_TEST) */ - -/* -** When testing, keep a count of the number of open files. -*/ -#if defined(SQLITE_TEST) -SQLITE_API extern int sqlite3_open_file_count; -#define OpenCounter(X) sqlite3_open_file_count+=(X) -#else -#define OpenCounter(X) -#endif /* defined(SQLITE_TEST) */ - -#endif /* !defined(_OS_COMMON_H_) */ - -/************** End of os_common.h *******************************************/ -/************** Continuing where we left off in os_unix.c ********************/ +/* #include "os_common.h" */ /* ** Define various macros that are missing from some systems. @@ -37812,6 +39426,9 @@ static void unixModeBit(unixFile *pFile, unsigned char mask, int *pArg){ /* Forward declaration */ static int unixGetTempname(int nBuf, char *zBuf); +#ifndef SQLITE_OMIT_WAL + static int unixFcntlExternalReader(unixFile*, int*); +#endif /* ** Information and control of an open file handle. @@ -37928,6 +39545,15 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){ return proxyFileControl(id,op,pArg); } #endif /* SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) */ + + case SQLITE_FCNTL_EXTERNAL_READER: { +#ifndef SQLITE_OMIT_WAL + return unixFcntlExternalReader((unixFile*)id, (int*)pArg); +#else + *(int*)pArg = 0; + return SQLITE_OK; +#endif + } } return SQLITE_NOTFOUND; } @@ -38173,6 +39799,40 @@ struct unixShm { #define UNIX_SHM_BASE ((22+SQLITE_SHM_NLOCK)*4) /* first lock byte */ #define UNIX_SHM_DMS (UNIX_SHM_BASE+SQLITE_SHM_NLOCK) /* deadman switch */ +/* +** Use F_GETLK to check whether or not there are any readers with open +** wal-mode transactions in other processes on database file pFile. If +** no error occurs, return SQLITE_OK and set (*piOut) to 1 if there are +** such transactions, or 0 otherwise. If an error occurs, return an +** SQLite error code. The final value of *piOut is undefined in this +** case. +*/ +static int unixFcntlExternalReader(unixFile *pFile, int *piOut){ + int rc = SQLITE_OK; + *piOut = 0; + if( pFile->pShm){ + unixShmNode *pShmNode = pFile->pShm->pShmNode; + struct flock f; + + memset(&f, 0, sizeof(f)); + f.l_type = F_WRLCK; + f.l_whence = SEEK_SET; + f.l_start = UNIX_SHM_BASE + 3; + f.l_len = SQLITE_SHM_NLOCK - 3; + + sqlite3_mutex_enter(pShmNode->pShmMutex); + if( osFcntl(pShmNode->hShm, F_GETLK, &f)<0 ){ + rc = SQLITE_IOERR_LOCK; + }else{ + *piOut = (f.l_type!=F_UNLCK); + } + sqlite3_mutex_leave(pShmNode->pShmMutex); + } + + return rc; +} + + /* ** Apply posix advisory locks for all bytes from ofst through ofst+n-1. ** @@ -38351,7 +40011,7 @@ static int unixLockSharedMemory(unixFile *pDbFd, unixShmNode *pShmNode){ ** -shm header size) rather than 0 as a system debugging aid, to ** help detect if a -shm file truncation is legitimate or is the work ** or a rogue process. */ - if( rc==SQLITE_OK && robust_ftruncate(pShmNode->hShm, 0) ){ + if( rc==SQLITE_OK && robust_ftruncate(pShmNode->hShm, 3) ){ rc = unixLogError(SQLITE_IOERR_SHMOPEN,"ftruncate",pShmNode->zFilename); } } @@ -38725,11 +40385,17 @@ static int unixShmLock( int flags /* What to do with the lock */ ){ unixFile *pDbFd = (unixFile*)fd; /* Connection holding shared memory */ - unixShm *p = pDbFd->pShm; /* The shared memory being locked */ - unixShmNode *pShmNode = p->pShmNode; /* The underlying file iNode */ + unixShm *p; /* The shared memory being locked */ + unixShmNode *pShmNode; /* The underlying file iNode */ int rc = SQLITE_OK; /* Result code */ u16 mask; /* Mask of locks to take or release */ - int *aLock = pShmNode->aLock; + int *aLock; + + p = pDbFd->pShm; + if( p==0 ) return SQLITE_IOERR_SHMLOCK; + pShmNode = p->pShmNode; + if( NEVER(pShmNode==0) ) return SQLITE_IOERR_SHMLOCK; + aLock = pShmNode->aLock; assert( pShmNode==pDbFd->pInode->pShmNode ); assert( pShmNode->pInode==pDbFd->pInode ); @@ -39613,25 +41279,35 @@ static int fillInUnixFile( return rc; } +/* +** Directories to consider for temp files. +*/ +static const char *azTempDirs[] = { + 0, + 0, + "/var/tmp", + "/usr/tmp", + "/tmp", + "." +}; + +/* +** Initialize first two members of azTempDirs[] array. +*/ +static void unixTempFileInit(void){ + azTempDirs[0] = getenv("SQLITE_TMPDIR"); + azTempDirs[1] = getenv("TMPDIR"); +} + /* ** Return the name of a directory in which to put temporary files. ** If no suitable temporary file directory can be found, return NULL. */ static const char *unixTempFileDir(void){ - static const char *azDirs[] = { - 0, - 0, - "/var/tmp", - "/usr/tmp", - "/tmp", - "." - }; unsigned int i = 0; struct stat buf; const char *zDir = sqlite3_temp_directory; - if( !azDirs[0] ) azDirs[0] = getenv("SQLITE_TMPDIR"); - if( !azDirs[1] ) azDirs[1] = getenv("TMPDIR"); while(1){ if( zDir!=0 && osStat(zDir, &buf)==0 @@ -39640,8 +41316,8 @@ static const char *unixTempFileDir(void){ ){ return zDir; } - if( i>=sizeof(azDirs)/sizeof(azDirs[0]) ) break; - zDir = azDirs[i++]; + if( i>=sizeof(azTempDirs)/sizeof(azTempDirs[0]) ) break; + zDir = azTempDirs[i++]; } return 0; } @@ -39654,6 +41330,7 @@ static const char *unixTempFileDir(void){ static int unixGetTempname(int nBuf, char *zBuf){ const char *zDir; int iLimit = 0; + int rc = SQLITE_OK; /* It's odd to simulate an io-error here, but really this is just ** using the io-error infrastructure to test that SQLite handles this @@ -39662,18 +41339,26 @@ static int unixGetTempname(int nBuf, char *zBuf){ zBuf[0] = 0; SimulateIOError( return SQLITE_IOERR ); + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); zDir = unixTempFileDir(); - if( zDir==0 ) return SQLITE_IOERR_GETTEMPPATH; - do{ - u64 r; - sqlite3_randomness(sizeof(r), &r); - assert( nBuf>2 ); - zBuf[nBuf-2] = 0; - sqlite3_snprintf(nBuf, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX"%llx%c", - zDir, r, 0); - if( zBuf[nBuf-2]!=0 || (iLimit++)>10 ) return SQLITE_ERROR; - }while( osAccess(zBuf,0)==0 ); - return SQLITE_OK; + if( zDir==0 ){ + rc = SQLITE_IOERR_GETTEMPPATH; + }else{ + do{ + u64 r; + sqlite3_randomness(sizeof(r), &r); + assert( nBuf>2 ); + zBuf[nBuf-2] = 0; + sqlite3_snprintf(nBuf, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX"%llx%c", + zDir, r, 0); + if( zBuf[nBuf-2]!=0 || (iLimit++)>10 ){ + rc = SQLITE_ERROR; + break; + } + }while( osAccess(zBuf,0)==0 ); + } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + return rc; } #if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) @@ -39816,20 +41501,23 @@ static int findCreateFileMode( ** ** where NN is a decimal number. The NN naming schemes are ** used by the test_multiplex.c module. + ** + ** In normal operation, the journal file name will always contain + ** a '-' character. However in 8+3 filename mode, or if a corrupt + ** rollback journal specifies a super-journal with a goofy name, then + ** the '-' might be missing or the '-' might be the first character in + ** the filename. In that case, just return SQLITE_OK with *pMode==0. */ nDb = sqlite3Strlen30(zPath) - 1; - while( zPath[nDb]!='-' ){ - /* In normal operation, the journal file name will always contain - ** a '-' character. However in 8+3 filename mode, or if a corrupt - ** rollback journal specifies a super-journal with a goofy name, then - ** the '-' might be missing. */ - if( nDb==0 || zPath[nDb]=='.' ) return SQLITE_OK; + while( nDb>0 && zPath[nDb]!='.' ){ + if( zPath[nDb]=='-' ){ + memcpy(zDb, zPath, nDb); + zDb[nDb] = '\0'; + rc = getFileMode(zDb, pMode, pUid, pGid); + break; + } nDb--; } - memcpy(zDb, zPath, nDb); - zDb[nDb] = '\0'; - - rc = getFileMode(zDb, pMode, pUid, pGid); }else if( flags & SQLITE_OPEN_DELETEONCLOSE ){ *pMode = 0600; }else if( flags & SQLITE_OPEN_URI ){ @@ -39947,6 +41635,11 @@ static int unixOpen( } memset(p, 0, sizeof(unixFile)); +#ifdef SQLITE_ASSERT_NO_FILES + /* Applications that never read or write a persistent disk files */ + assert( zName==0 ); +#endif + if( eType==SQLITE_OPEN_MAIN_DB ){ UnixUnusedFd *pUnused; pUnused = findReusableFd(zName, flags); @@ -40214,86 +41907,99 @@ static int unixAccess( } /* -** If the last component of the pathname in z[0]..z[j-1] is something -** other than ".." then back it out and return true. If the last -** component is empty or if it is ".." then return false. +** A pathname under construction */ -static int unixBackupDir(const char *z, int *pJ){ - int j = *pJ; - int i; - if( j<=0 ) return 0; - for(i=j-1; i>0 && z[i-1]!='/'; i--){} - if( i==0 ) return 0; - if( z[i]=='.' && i==j-2 && z[i+1]=='.' ) return 0; - *pJ = i-1; - return 1; +typedef struct DbPath DbPath; +struct DbPath { + int rc; /* Non-zero following any error */ + int nSymlink; /* Number of symlinks resolved */ + char *zOut; /* Write the pathname here */ + int nOut; /* Bytes of space available to zOut[] */ + int nUsed; /* Bytes of zOut[] currently being used */ +}; + +/* Forward reference */ +static void appendAllPathElements(DbPath*,const char*); + +/* +** Append a single path element to the DbPath under construction +*/ +static void appendOnePathElement( + DbPath *pPath, /* Path under construction, to which to append zName */ + const char *zName, /* Name to append to pPath. Not zero-terminated */ + int nName /* Number of significant bytes in zName */ +){ + assert( nName>0 ); + assert( zName!=0 ); + if( zName[0]=='.' ){ + if( nName==1 ) return; + if( zName[1]=='.' && nName==2 ){ + if( pPath->nUsed<=1 ){ + pPath->rc = SQLITE_ERROR; + return; + } + assert( pPath->zOut[0]=='/' ); + while( pPath->zOut[--pPath->nUsed]!='/' ){} + return; + } + } + if( pPath->nUsed + nName + 2 >= pPath->nOut ){ + pPath->rc = SQLITE_ERROR; + return; + } + pPath->zOut[pPath->nUsed++] = '/'; + memcpy(&pPath->zOut[pPath->nUsed], zName, nName); + pPath->nUsed += nName; +#if defined(HAVE_READLINK) && defined(HAVE_LSTAT) + if( pPath->rc==SQLITE_OK ){ + const char *zIn; + struct stat buf; + pPath->zOut[pPath->nUsed] = 0; + zIn = pPath->zOut; + if( osLstat(zIn, &buf)!=0 ){ + if( errno!=ENOENT ){ + pPath->rc = unixLogError(SQLITE_CANTOPEN_BKPT, "lstat", zIn); + } + }else if( S_ISLNK(buf.st_mode) ){ + ssize_t got; + char zLnk[SQLITE_MAX_PATHLEN+2]; + if( pPath->nSymlink++ > SQLITE_MAX_SYMLINK ){ + pPath->rc = SQLITE_CANTOPEN_BKPT; + return; + } + got = osReadlink(zIn, zLnk, sizeof(zLnk)-2); + if( got<=0 || got>=(ssize_t)sizeof(zLnk)-2 ){ + pPath->rc = unixLogError(SQLITE_CANTOPEN_BKPT, "readlink", zIn); + return; + } + zLnk[got] = 0; + if( zLnk[0]=='/' ){ + pPath->nUsed = 0; + }else{ + pPath->nUsed -= nName + 1; + } + appendAllPathElements(pPath, zLnk); + } + } +#endif } /* -** Convert a relative pathname into a full pathname. Also -** simplify the pathname as follows: -** -** Remove all instances of /./ -** Remove all isntances of /X/../ for any X +** Append all path elements in zPath to the DbPath under construction. */ -static int mkFullPathname( - const char *zPath, /* Input path */ - char *zOut, /* Output buffer */ - int nOut /* Allocated size of buffer zOut */ +static void appendAllPathElements( + DbPath *pPath, /* Path under construction, to which to append zName */ + const char *zPath /* Path to append to pPath. Is zero-terminated */ ){ - int nPath = sqlite3Strlen30(zPath); - int iOff = 0; - int i, j; - if( zPath[0]!='/' ){ - if( osGetcwd(zOut, nOut-2)==0 ){ - return unixLogError(SQLITE_CANTOPEN_BKPT, "getcwd", zPath); + int i = 0; + int j = 0; + do{ + while( zPath[i] && zPath[i]!='/' ){ i++; } + if( i>j ){ + appendOnePathElement(pPath, &zPath[j], i-j); } - iOff = sqlite3Strlen30(zOut); - zOut[iOff++] = '/'; - } - if( (iOff+nPath+1)>nOut ){ - /* SQLite assumes that xFullPathname() nul-terminates the output buffer - ** even if it returns an error. */ - zOut[iOff] = '\0'; - return SQLITE_CANTOPEN_BKPT; - } - sqlite3_snprintf(nOut-iOff, &zOut[iOff], "%s", zPath); - - /* Remove duplicate '/' characters. Except, two // at the beginning - ** of a pathname is allowed since this is important on windows. */ - for(i=j=1; zOut[i]; i++){ - zOut[j++] = zOut[i]; - while( zOut[i]=='/' && zOut[i+1]=='/' ) i++; - } - zOut[j] = 0; - - assert( zOut[0]=='/' ); - for(i=j=0; zOut[i]; i++){ - if( zOut[i]=='/' ){ - /* Skip over internal "/." directory components */ - if( zOut[i+1]=='.' && zOut[i+2]=='/' ){ - i += 1; - continue; - } - - /* If this is a "/.." directory component then back out the - ** previous term of the directory if it is something other than "..". - */ - if( zOut[i+1]=='.' - && zOut[i+2]=='.' - && zOut[i+3]=='/' - && unixBackupDir(zOut, &j) - ){ - i += 2; - continue; - } - } - if( ALWAYS(j>=0) ) zOut[j] = zOut[i]; - j++; - } - if( NEVER(j==0) ) zOut[j++] = '/'; - zOut[j] = 0; - return SQLITE_OK; + j = i+1; + }while( zPath[i++] ); } /* @@ -40311,86 +42017,27 @@ static int unixFullPathname( int nOut, /* Size of output buffer in bytes */ char *zOut /* Output buffer */ ){ -#if !defined(HAVE_READLINK) || !defined(HAVE_LSTAT) - return mkFullPathname(zPath, zOut, nOut); -#else - int rc = SQLITE_OK; - int nByte; - int nLink = 0; /* Number of symbolic links followed so far */ - const char *zIn = zPath; /* Input path for each iteration of loop */ - char *zDel = 0; - - assert( pVfs->mxPathname==MAX_PATHNAME ); + DbPath path; UNUSED_PARAMETER(pVfs); - - /* It's odd to simulate an io-error here, but really this is just - ** using the io-error infrastructure to test that SQLite handles this - ** function failing. This function could fail if, for example, the - ** current working directory has been unlinked. - */ - SimulateIOError( return SQLITE_ERROR ); - - do { - - /* Call stat() on path zIn. Set bLink to true if the path is a symbolic - ** link, or false otherwise. */ - int bLink = 0; - struct stat buf; - if( osLstat(zIn, &buf)!=0 ){ - if( errno!=ENOENT ){ - rc = unixLogError(SQLITE_CANTOPEN_BKPT, "lstat", zIn); - } - }else{ - bLink = S_ISLNK(buf.st_mode); + path.rc = 0; + path.nUsed = 0; + path.nSymlink = 0; + path.nOut = nOut; + path.zOut = zOut; + if( zPath[0]!='/' ){ + char zPwd[SQLITE_MAX_PATHLEN+2]; + if( osGetcwd(zPwd, sizeof(zPwd)-2)==0 ){ + return unixLogError(SQLITE_CANTOPEN_BKPT, "getcwd", zPath); } - - if( bLink ){ - nLink++; - if( zDel==0 ){ - zDel = sqlite3_malloc(nOut); - if( zDel==0 ) rc = SQLITE_NOMEM_BKPT; - }else if( nLink>=SQLITE_MAX_SYMLINKS ){ - rc = SQLITE_CANTOPEN_BKPT; - } - - if( rc==SQLITE_OK ){ - nByte = osReadlink(zIn, zDel, nOut-1); - if( nByte<0 ){ - rc = unixLogError(SQLITE_CANTOPEN_BKPT, "readlink", zIn); - }else{ - if( zDel[0]!='/' ){ - int n; - for(n = sqlite3Strlen30(zIn); n>0 && zIn[n-1]!='/'; n--); - if( nByte+n+1>nOut ){ - rc = SQLITE_CANTOPEN_BKPT; - }else{ - memmove(&zDel[n], zDel, nByte+1); - memcpy(zDel, zIn, n); - nByte += n; - } - } - zDel[nByte] = '\0'; - } - } - - zIn = zDel; - } - - assert( rc!=SQLITE_OK || zIn!=zOut || zIn[0]=='/' ); - if( rc==SQLITE_OK && zIn!=zOut ){ - rc = mkFullPathname(zIn, zOut, nOut); - } - if( bLink==0 ) break; - zIn = zOut; - }while( rc==SQLITE_OK ); - - sqlite3_free(zDel); - if( rc==SQLITE_OK && nLink ) rc = SQLITE_OK_SYMLINK; - return rc; -#endif /* HAVE_READLINK && HAVE_LSTAT */ + appendAllPathElements(&path, zPwd); + } + appendAllPathElements(&path, zPath); + zOut[path.nUsed] = 0; + if( path.rc || path.nUsed<2 ) return SQLITE_CANTOPEN_BKPT; + if( path.nSymlink ) return SQLITE_OK_SYMLINK; + return SQLITE_OK; } - #ifndef SQLITE_OMIT_LOAD_EXTENSION /* ** Interfaces for opening a shared library, finding entry points @@ -41889,6 +43536,28 @@ SQLITE_API int sqlite3_os_init(void){ sqlite3_vfs_register(&aVfs[i], i==0); } unixBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); + +#ifndef SQLITE_OMIT_WAL + /* Validate lock assumptions */ + assert( SQLITE_SHM_NLOCK==8 ); /* Number of available locks */ + assert( UNIX_SHM_BASE==120 ); /* Start of locking area */ + /* Locks: + ** WRITE UNIX_SHM_BASE 120 + ** CKPT UNIX_SHM_BASE+1 121 + ** RECOVER UNIX_SHM_BASE+2 122 + ** READ-0 UNIX_SHM_BASE+3 123 + ** READ-1 UNIX_SHM_BASE+4 124 + ** READ-2 UNIX_SHM_BASE+5 125 + ** READ-3 UNIX_SHM_BASE+6 126 + ** READ-4 UNIX_SHM_BASE+7 127 + ** DMS UNIX_SHM_BASE+8 128 + */ + assert( UNIX_SHM_DMS==128 ); /* Byte offset of the deadman-switch */ +#endif + + /* Initialize temp file dir array. */ + unixTempFileInit(); + return SQLITE_OK; } @@ -41928,205 +43597,7 @@ SQLITE_API int sqlite3_os_end(void){ /* ** Include code that is common to all os_*.c files */ -/************** Include os_common.h in the middle of os_win.c ****************/ -/************** Begin file os_common.h ***************************************/ -/* -** 2004 May 22 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This file contains macros and a little bit of code that is common to -** all of the platform-specific files (os_*.c) and is #included into those -** files. -** -** This file should be #included by the os_*.c files only. It is not a -** general purpose header file. -*/ -#ifndef _OS_COMMON_H_ -#define _OS_COMMON_H_ - -/* -** At least two bugs have slipped in because we changed the MEMORY_DEBUG -** macro to SQLITE_DEBUG and some older makefiles have not yet made the -** switch. The following code should catch this problem at compile-time. -*/ -#ifdef MEMORY_DEBUG -# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead." -#endif - -/* -** Macros for performance tracing. Normally turned off. Only works -** on i486 hardware. -*/ -#ifdef SQLITE_PERFORMANCE_TRACE - -/* -** hwtime.h contains inline assembler code for implementing -** high-performance timing routines. -*/ -/************** Include hwtime.h in the middle of os_common.h ****************/ -/************** Begin file hwtime.h ******************************************/ -/* -** 2008 May 27 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This file contains inline asm code for retrieving "high-performance" -** counters for x86 and x86_64 class CPUs. -*/ -#ifndef SQLITE_HWTIME_H -#define SQLITE_HWTIME_H - -/* -** The following routine only works on pentium-class (or newer) processors. -** It uses the RDTSC opcode to read the cycle count value out of the -** processor and returns that value. This can be used for high-res -** profiling. -*/ -#if !defined(__STRICT_ANSI__) && \ - (defined(__GNUC__) || defined(_MSC_VER)) && \ - (defined(i386) || defined(__i386__) || defined(_M_IX86)) - - #if defined(__GNUC__) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned int lo, hi; - __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); - return (sqlite_uint64)hi << 32 | lo; - } - - #elif defined(_MSC_VER) - - __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){ - __asm { - rdtsc - ret ; return value at EDX:EAX - } - } - - #endif - -#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__)) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long val; - __asm__ __volatile__ ("rdtsc" : "=A" (val)); - return val; - } - -#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__)) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long long retval; - unsigned long junk; - __asm__ __volatile__ ("\n\ - 1: mftbu %1\n\ - mftb %L0\n\ - mftbu %0\n\ - cmpw %0,%1\n\ - bne 1b" - : "=r" (retval), "=r" (junk)); - return retval; - } - -#else - - /* - ** asm() is needed for hardware timing support. Without asm(), - ** disable the sqlite3Hwtime() routine. - ** - ** sqlite3Hwtime() is only used for some obscure debugging - ** and analysis configurations, not in any deliverable, so this - ** should not be a great loss. - */ -SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); } - -#endif - -#endif /* !defined(SQLITE_HWTIME_H) */ - -/************** End of hwtime.h **********************************************/ -/************** Continuing where we left off in os_common.h ******************/ - -static sqlite_uint64 g_start; -static sqlite_uint64 g_elapsed; -#define TIMER_START g_start=sqlite3Hwtime() -#define TIMER_END g_elapsed=sqlite3Hwtime()-g_start -#define TIMER_ELAPSED g_elapsed -#else -#define TIMER_START -#define TIMER_END -#define TIMER_ELAPSED ((sqlite_uint64)0) -#endif - -/* -** If we compile with the SQLITE_TEST macro set, then the following block -** of code will give us the ability to simulate a disk I/O error. This -** is used for testing the I/O recovery logic. -*/ -#if defined(SQLITE_TEST) -SQLITE_API extern int sqlite3_io_error_hit; -SQLITE_API extern int sqlite3_io_error_hardhit; -SQLITE_API extern int sqlite3_io_error_pending; -SQLITE_API extern int sqlite3_io_error_persist; -SQLITE_API extern int sqlite3_io_error_benign; -SQLITE_API extern int sqlite3_diskfull_pending; -SQLITE_API extern int sqlite3_diskfull; -#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X) -#define SimulateIOError(CODE) \ - if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \ - || sqlite3_io_error_pending-- == 1 ) \ - { local_ioerr(); CODE; } -static void local_ioerr(){ - IOTRACE(("IOERR\n")); - sqlite3_io_error_hit++; - if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++; -} -#define SimulateDiskfullError(CODE) \ - if( sqlite3_diskfull_pending ){ \ - if( sqlite3_diskfull_pending == 1 ){ \ - local_ioerr(); \ - sqlite3_diskfull = 1; \ - sqlite3_io_error_hit = 1; \ - CODE; \ - }else{ \ - sqlite3_diskfull_pending--; \ - } \ - } -#else -#define SimulateIOErrorBenign(X) -#define SimulateIOError(A) -#define SimulateDiskfullError(A) -#endif /* defined(SQLITE_TEST) */ - -/* -** When testing, keep a count of the number of open files. -*/ -#if defined(SQLITE_TEST) -SQLITE_API extern int sqlite3_open_file_count; -#define OpenCounter(X) sqlite3_open_file_count+=(X) -#else -#define OpenCounter(X) -#endif /* defined(SQLITE_TEST) */ - -#endif /* !defined(_OS_COMMON_H_) */ - -/************** End of os_common.h *******************************************/ -/************** Continuing where we left off in os_win.c *********************/ +/* #include "os_common.h" */ /* ** Include the header file for the Windows VFS. @@ -44030,6 +45501,7 @@ SQLITE_API int sqlite3_win32_set_directory8( int rc = sqlite3_initialize(); if( rc ) return rc; #endif + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); if( type==SQLITE_WIN32_DATA_DIRECTORY_TYPE ){ ppDirectory = &sqlite3_data_directory; }else if( type==SQLITE_WIN32_TEMP_DIRECTORY_TYPE ){ @@ -44044,14 +45516,19 @@ SQLITE_API int sqlite3_win32_set_directory8( if( zValue && zValue[0] ){ zCopy = sqlite3_mprintf("%s", zValue); if ( zCopy==0 ){ - return SQLITE_NOMEM_BKPT; + rc = SQLITE_NOMEM_BKPT; + goto set_directory8_done; } } sqlite3_free(*ppDirectory); *ppDirectory = zCopy; - return SQLITE_OK; + rc = SQLITE_OK; + }else{ + rc = SQLITE_ERROR; } - return SQLITE_ERROR; +set_directory8_done: + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + return rc; } /* @@ -46178,10 +47655,14 @@ static int winShmLock( winFile *pDbFd = (winFile*)fd; /* Connection holding shared memory */ winShm *p = pDbFd->pShm; /* The shared memory being locked */ winShm *pX; /* For looping over all siblings */ - winShmNode *pShmNode = p->pShmNode; + winShmNode *pShmNode; int rc = SQLITE_OK; /* Result code */ u16 mask; /* Mask of locks to take or release */ + if( p==0 ) return SQLITE_IOERR_SHMLOCK; + pShmNode = p->pShmNode; + if( NEVER(pShmNode==0) ) return SQLITE_IOERR_SHMLOCK; + assert( ofst>=0 && ofst+n<=SQLITE_SHM_NLOCK ); assert( n>=1 ); assert( flags==(SQLITE_SHM_LOCK | SQLITE_SHM_SHARED) @@ -46821,6 +48302,18 @@ static int winMakeEndInDirSep(int nBuf, char *zBuf){ return 0; } +/* +** If sqlite3_temp_directory is not, take the mutex and return true. +** +** If sqlite3_temp_directory is NULL, omit the mutex and return false. +*/ +static int winTempDirDefined(void){ + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + if( sqlite3_temp_directory!=0 ) return 1; + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + return 0; +} + /* ** Create a temporary file name and store the resulting pointer into pzBuf. ** The pointer returned in pzBuf must be freed via sqlite3_free(). @@ -46857,20 +48350,23 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ */ nDir = nMax - (nPre + 15); assert( nDir>0 ); - if( sqlite3_temp_directory ){ + if( winTempDirDefined() ){ int nDirLen = sqlite3Strlen30(sqlite3_temp_directory); if( nDirLen>0 ){ if( !winIsDirSep(sqlite3_temp_directory[nDirLen-1]) ){ nDirLen++; } if( nDirLen>nDir ){ + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_ERROR\n")); return winLogError(SQLITE_ERROR, 0, "winGetTempname1", 0); } sqlite3_snprintf(nMax, zBuf, "%s", sqlite3_temp_directory); } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); } + #if defined(__CYGWIN__) else{ static const char *azDirs[] = { @@ -47659,7 +49155,7 @@ static BOOL winIsVerbatimPathname( ** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname ** bytes in size. */ -static int winFullPathname( +static int winFullPathnameNoMutex( sqlite3_vfs *pVfs, /* Pointer to vfs object */ const char *zRelative, /* Possibly relative input path */ int nFull, /* Size of output buffer in bytes */ @@ -47838,6 +49334,19 @@ static int winFullPathname( } #endif } +static int winFullPathname( + sqlite3_vfs *pVfs, /* Pointer to vfs object */ + const char *zRelative, /* Possibly relative input path */ + int nFull, /* Size of output buffer in bytes */ + char *zFull /* Output buffer */ +){ + int rc; + sqlite3_mutex *pMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR); + sqlite3_mutex_enter(pMutex); + rc = winFullPathnameNoMutex(pVfs, zRelative, nFull, zFull); + sqlite3_mutex_leave(pMutex); + return rc; +} #ifndef SQLITE_OMIT_LOAD_EXTENSION /* @@ -48282,31 +49791,88 @@ SQLITE_API int sqlite3_os_end(void){ ** sqlite3_deserialize(). */ /* #include "sqliteInt.h" */ -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE /* ** Forward declaration of objects used by this utility */ typedef struct sqlite3_vfs MemVfs; typedef struct MemFile MemFile; +typedef struct MemStore MemStore; /* Access to a lower-level VFS that (might) implement dynamic loading, ** access to randomness, etc. */ #define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData)) -/* An open file */ -struct MemFile { - sqlite3_file base; /* IO methods */ +/* Storage for a memdb file. +** +** An memdb object can be shared or separate. Shared memdb objects can be +** used by more than one database connection. Mutexes are used by shared +** memdb objects to coordinate access. Separate memdb objects are only +** connected to a single database connection and do not require additional +** mutexes. +** +** Shared memdb objects have .zFName!=0 and .pMutex!=0. They are created +** using "file:/name?vfs=memdb". The first character of the name must be +** "/" or else the object will be a separate memdb object. All shared +** memdb objects are stored in memdb_g.apMemStore[] in an arbitrary order. +** +** Separate memdb objects are created using a name that does not begin +** with "/" or using sqlite3_deserialize(). +** +** Access rules for shared MemStore objects: +** +** * .zFName is initialized when the object is created and afterwards +** is unchanged until the object is destroyed. So it can be accessed +** at any time as long as we know the object is not being destroyed, +** which means while either the SQLITE_MUTEX_STATIC_VFS1 or +** .pMutex is held or the object is not part of memdb_g.apMemStore[]. +** +** * Can .pMutex can only be changed while holding the +** SQLITE_MUTEX_STATIC_VFS1 mutex or while the object is not part +** of memdb_g.apMemStore[]. +** +** * Other fields can only be changed while holding the .pMutex mutex +** or when the .nRef is less than zero and the object is not part of +** memdb_g.apMemStore[]. +** +** * The .aData pointer has the added requirement that it can can only +** be changed (for resizing) when nMmap is zero. +** +*/ +struct MemStore { sqlite3_int64 sz; /* Size of the file */ sqlite3_int64 szAlloc; /* Space allocated to aData */ sqlite3_int64 szMax; /* Maximum allowed size of the file */ unsigned char *aData; /* content of the file */ + sqlite3_mutex *pMutex; /* Used by shared stores only */ int nMmap; /* Number of memory mapped pages */ unsigned mFlags; /* Flags */ + int nRdLock; /* Number of readers */ + int nWrLock; /* Number of writers. (Always 0 or 1) */ + int nRef; /* Number of users of this MemStore */ + char *zFName; /* The filename for shared stores */ +}; + +/* An open file */ +struct MemFile { + sqlite3_file base; /* IO methods */ + MemStore *pStore; /* The storage */ int eLock; /* Most recent lock against this file */ }; +/* +** File-scope variables for holding the memdb files that are accessible +** to multiple database connections in separate threads. +** +** Must hold SQLITE_MUTEX_STATIC_VFS1 to access any part of this object. +*/ +static struct MemFS { + int nMemStore; /* Number of shared MemStore objects */ + MemStore **apMemStore; /* Array of all shared MemStore objects */ +} memdb_g; + /* ** Methods for MemFile */ @@ -48360,7 +49926,10 @@ static sqlite3_vfs memdb_vfs = { memdbSleep, /* xSleep */ 0, /* memdbCurrentTime, */ /* xCurrentTime */ memdbGetLastError, /* xGetLastError */ - memdbCurrentTimeInt64 /* xCurrentTimeInt64 */ + memdbCurrentTimeInt64, /* xCurrentTimeInt64 */ + 0, /* xSetSystemCall */ + 0, /* xGetSystemCall */ + 0, /* xNextSystemCall */ }; static const sqlite3_io_methods memdb_io_methods = { @@ -48385,19 +49954,67 @@ static const sqlite3_io_methods memdb_io_methods = { memdbUnfetch /* xUnfetch */ }; +/* +** Enter/leave the mutex on a MemStore +*/ +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0 +static void memdbEnter(MemStore *p){ + UNUSED_PARAMETER(p); +} +static void memdbLeave(MemStore *p){ + UNUSED_PARAMETER(p); +} +#else +static void memdbEnter(MemStore *p){ + sqlite3_mutex_enter(p->pMutex); +} +static void memdbLeave(MemStore *p){ + sqlite3_mutex_leave(p->pMutex); +} +#endif + /* ** Close an memdb-file. -** -** The pData pointer is owned by the application, so there is nothing -** to free. Unless the SQLITE_DESERIALIZE_FREEONCLOSE flag is set, -** in which case we own the pData pointer and need to free it. +** Free the underlying MemStore object when its refcount drops to zero +** or less. */ static int memdbClose(sqlite3_file *pFile){ - MemFile *p = (MemFile *)pFile; - if( p->mFlags & SQLITE_DESERIALIZE_FREEONCLOSE ){ - sqlite3_free(p->aData); + MemStore *p = ((MemFile*)pFile)->pStore; + if( p->zFName ){ + int i; +#ifndef SQLITE_MUTEX_OMIT + sqlite3_mutex *pVfsMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); +#endif + sqlite3_mutex_enter(pVfsMutex); + for(i=0; ALWAYS(inRef==1 ){ + memdb_g.apMemStore[i] = memdb_g.apMemStore[--memdb_g.nMemStore]; + if( memdb_g.nMemStore==0 ){ + sqlite3_free(memdb_g.apMemStore); + memdb_g.apMemStore = 0; + } + } + break; + } + } + sqlite3_mutex_leave(pVfsMutex); + }else{ + memdbEnter(p); + } + p->nRef--; + if( p->nRef<=0 ){ + if( p->mFlags & SQLITE_DESERIALIZE_FREEONCLOSE ){ + sqlite3_free(p->aData); + } + memdbLeave(p); + sqlite3_mutex_free(p->pMutex); + sqlite3_free(p); + }else{ + memdbLeave(p); } return SQLITE_OK; } @@ -48411,22 +50028,25 @@ static int memdbRead( int iAmt, sqlite_int64 iOfst ){ - MemFile *p = (MemFile *)pFile; + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); if( iOfst+iAmt>p->sz ){ memset(zBuf, 0, iAmt); if( iOfstsz ) memcpy(zBuf, p->aData+iOfst, p->sz - iOfst); + memdbLeave(p); return SQLITE_IOERR_SHORT_READ; } memcpy(zBuf, p->aData+iOfst, iAmt); + memdbLeave(p); return SQLITE_OK; } /* ** Try to enlarge the memory allocation to hold at least sz bytes */ -static int memdbEnlarge(MemFile *p, sqlite3_int64 newSz){ +static int memdbEnlarge(MemStore *p, sqlite3_int64 newSz){ unsigned char *pNew; - if( (p->mFlags & SQLITE_DESERIALIZE_RESIZEABLE)==0 || p->nMmap>0 ){ + if( (p->mFlags & SQLITE_DESERIALIZE_RESIZEABLE)==0 || NEVER(p->nMmap>0) ){ return SQLITE_FULL; } if( newSz>p->szMax ){ @@ -48435,7 +50055,7 @@ static int memdbEnlarge(MemFile *p, sqlite3_int64 newSz){ newSz *= 2; if( newSz>p->szMax ) newSz = p->szMax; pNew = sqlite3Realloc(p->aData, newSz); - if( pNew==0 ) return SQLITE_NOMEM; + if( pNew==0 ) return SQLITE_IOERR_NOMEM; p->aData = pNew; p->szAlloc = newSz; return SQLITE_OK; @@ -48450,19 +50070,27 @@ static int memdbWrite( int iAmt, sqlite_int64 iOfst ){ - MemFile *p = (MemFile *)pFile; - if( NEVER(p->mFlags & SQLITE_DESERIALIZE_READONLY) ) return SQLITE_READONLY; + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); + if( NEVER(p->mFlags & SQLITE_DESERIALIZE_READONLY) ){ + /* Can't happen: memdbLock() will return SQLITE_READONLY before + ** reaching this point */ + memdbLeave(p); + return SQLITE_IOERR_WRITE; + } if( iOfst+iAmt>p->sz ){ int rc; if( iOfst+iAmt>p->szAlloc && (rc = memdbEnlarge(p, iOfst+iAmt))!=SQLITE_OK ){ + memdbLeave(p); return rc; } if( iOfst>p->sz ) memset(p->aData+p->sz, 0, iOfst-p->sz); p->sz = iOfst+iAmt; } memcpy(p->aData+iOfst, z, iAmt); + memdbLeave(p); return SQLITE_OK; } @@ -48474,16 +50102,25 @@ static int memdbWrite( ** the size of a file, never to increase the size. */ static int memdbTruncate(sqlite3_file *pFile, sqlite_int64 size){ - MemFile *p = (MemFile *)pFile; - if( NEVER(size>p->sz) ) return SQLITE_FULL; - p->sz = size; - return SQLITE_OK; + MemStore *p = ((MemFile*)pFile)->pStore; + int rc = SQLITE_OK; + memdbEnter(p); + if( size>p->sz ){ + /* This can only happen with a corrupt wal mode db */ + rc = SQLITE_CORRUPT; + }else{ + p->sz = size; + } + memdbLeave(p); + return rc; } /* ** Sync an memdb-file. */ static int memdbSync(sqlite3_file *pFile, int flags){ + UNUSED_PARAMETER(pFile); + UNUSED_PARAMETER(flags); return SQLITE_OK; } @@ -48491,8 +50128,10 @@ static int memdbSync(sqlite3_file *pFile, int flags){ ** Return the current file-size of an memdb-file. */ static int memdbFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ - MemFile *p = (MemFile *)pFile; + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); *pSize = p->sz; + memdbLeave(p); return SQLITE_OK; } @@ -48500,19 +50139,48 @@ static int memdbFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ ** Lock an memdb-file. */ static int memdbLock(sqlite3_file *pFile, int eLock){ - MemFile *p = (MemFile *)pFile; - if( eLock>SQLITE_LOCK_SHARED - && (p->mFlags & SQLITE_DESERIALIZE_READONLY)!=0 - ){ - return SQLITE_READONLY; + MemFile *pThis = (MemFile*)pFile; + MemStore *p = pThis->pStore; + int rc = SQLITE_OK; + if( eLock==pThis->eLock ) return SQLITE_OK; + memdbEnter(p); + if( eLock>SQLITE_LOCK_SHARED ){ + if( p->mFlags & SQLITE_DESERIALIZE_READONLY ){ + rc = SQLITE_READONLY; + }else if( pThis->eLock<=SQLITE_LOCK_SHARED ){ + if( p->nWrLock ){ + rc = SQLITE_BUSY; + }else{ + p->nWrLock = 1; + } + } + }else if( eLock==SQLITE_LOCK_SHARED ){ + if( pThis->eLock > SQLITE_LOCK_SHARED ){ + assert( p->nWrLock==1 ); + p->nWrLock = 0; + }else if( p->nWrLock ){ + rc = SQLITE_BUSY; + }else{ + p->nRdLock++; + } + }else{ + assert( eLock==SQLITE_LOCK_NONE ); + if( pThis->eLock>SQLITE_LOCK_SHARED ){ + assert( p->nWrLock==1 ); + p->nWrLock = 0; + } + assert( p->nRdLock>0 ); + p->nRdLock--; } - p->eLock = eLock; - return SQLITE_OK; + if( rc==SQLITE_OK ) pThis->eLock = eLock; + memdbLeave(p); + return rc; } -#if 0 /* Never used because memdbAccess() always returns false */ +#if 0 /* -** Check if another file-handle holds a RESERVED lock on an memdb-file. +** This interface is only used for crash recovery, which does not +** occur on an in-memory database. */ static int memdbCheckReservedLock(sqlite3_file *pFile, int *pResOut){ *pResOut = 0; @@ -48520,12 +50188,14 @@ static int memdbCheckReservedLock(sqlite3_file *pFile, int *pResOut){ } #endif + /* ** File control method. For custom operations on an memdb-file. */ static int memdbFileControl(sqlite3_file *pFile, int op, void *pArg){ - MemFile *p = (MemFile *)pFile; + MemStore *p = ((MemFile*)pFile)->pStore; int rc = SQLITE_NOTFOUND; + memdbEnter(p); if( op==SQLITE_FCNTL_VFSNAME ){ *(char**)pArg = sqlite3_mprintf("memdb(%p,%lld)", p->aData, p->sz); rc = SQLITE_OK; @@ -48543,6 +50213,7 @@ static int memdbFileControl(sqlite3_file *pFile, int op, void *pArg){ *(sqlite3_int64*)pArg = iLimit; rc = SQLITE_OK; } + memdbLeave(p); return rc; } @@ -48559,6 +50230,7 @@ static int memdbSectorSize(sqlite3_file *pFile){ ** Return the device characteristic flags supported by an memdb-file. */ static int memdbDeviceCharacteristics(sqlite3_file *pFile){ + UNUSED_PARAMETER(pFile); return SQLITE_IOCAP_ATOMIC | SQLITE_IOCAP_POWERSAFE_OVERWRITE | SQLITE_IOCAP_SAFE_APPEND | @@ -48572,20 +50244,26 @@ static int memdbFetch( int iAmt, void **pp ){ - MemFile *p = (MemFile *)pFile; - if( iOfst+iAmt>p->sz ){ + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); + if( iOfst+iAmt>p->sz || (p->mFlags & SQLITE_DESERIALIZE_RESIZEABLE)!=0 ){ *pp = 0; }else{ p->nMmap++; *pp = (void*)(p->aData + iOfst); } + memdbLeave(p); return SQLITE_OK; } /* Release a memory-mapped page */ static int memdbUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){ - MemFile *p = (MemFile *)pFile; + MemStore *p = ((MemFile*)pFile)->pStore; + UNUSED_PARAMETER(iOfst); + UNUSED_PARAMETER(pPage); + memdbEnter(p); p->nMmap--; + memdbLeave(p); return SQLITE_OK; } @@ -48595,20 +50273,79 @@ static int memdbUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){ static int memdbOpen( sqlite3_vfs *pVfs, const char *zName, - sqlite3_file *pFile, + sqlite3_file *pFd, int flags, int *pOutFlags ){ - MemFile *p = (MemFile*)pFile; - if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){ - return ORIGVFS(pVfs)->xOpen(ORIGVFS(pVfs), zName, pFile, flags, pOutFlags); + MemFile *pFile = (MemFile*)pFd; + MemStore *p = 0; + int szName; + UNUSED_PARAMETER(pVfs); + + memset(pFile, 0, sizeof(*pFile)); + szName = sqlite3Strlen30(zName); + if( szName>1 && zName[0]=='/' ){ + int i; +#ifndef SQLITE_MUTEX_OMIT + sqlite3_mutex *pVfsMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); +#endif + sqlite3_mutex_enter(pVfsMutex); + for(i=0; izFName,zName)==0 ){ + p = memdb_g.apMemStore[i]; + break; + } + } + if( p==0 ){ + MemStore **apNew; + p = sqlite3Malloc( sizeof(*p) + szName + 3 ); + if( p==0 ){ + sqlite3_mutex_leave(pVfsMutex); + return SQLITE_NOMEM; + } + apNew = sqlite3Realloc(memdb_g.apMemStore, + sizeof(apNew[0])*(memdb_g.nMemStore+1) ); + if( apNew==0 ){ + sqlite3_free(p); + sqlite3_mutex_leave(pVfsMutex); + return SQLITE_NOMEM; + } + apNew[memdb_g.nMemStore++] = p; + memdb_g.apMemStore = apNew; + memset(p, 0, sizeof(*p)); + p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE|SQLITE_DESERIALIZE_FREEONCLOSE; + p->szMax = sqlite3GlobalConfig.mxMemdbSize; + p->zFName = (char*)&p[1]; + memcpy(p->zFName, zName, szName+1); + p->pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + if( p->pMutex==0 ){ + memdb_g.nMemStore--; + sqlite3_free(p); + sqlite3_mutex_leave(pVfsMutex); + return SQLITE_NOMEM; + } + p->nRef = 1; + memdbEnter(p); + }else{ + memdbEnter(p); + p->nRef++; + } + sqlite3_mutex_leave(pVfsMutex); + }else{ + p = sqlite3Malloc( sizeof(*p) ); + if( p==0 ){ + return SQLITE_NOMEM; + } + memset(p, 0, sizeof(*p)); + p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE | SQLITE_DESERIALIZE_FREEONCLOSE; + p->szMax = sqlite3GlobalConfig.mxMemdbSize; } - memset(p, 0, sizeof(*p)); - p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE | SQLITE_DESERIALIZE_FREEONCLOSE; - assert( pOutFlags!=0 ); /* True because flags==SQLITE_OPEN_MAIN_DB */ - *pOutFlags = flags | SQLITE_OPEN_MEMORY; - pFile->pMethods = &memdb_io_methods; - p->szMax = sqlite3GlobalConfig.mxMemdbSize; + pFile->pStore = p; + if( pOutFlags!=0 ){ + *pOutFlags = flags | SQLITE_OPEN_MEMORY; + } + pFd->pMethods = &memdb_io_methods; + memdbLeave(p); return SQLITE_OK; } @@ -48636,6 +50373,9 @@ static int memdbAccess( int flags, int *pResOut ){ + UNUSED_PARAMETER(pVfs); + UNUSED_PARAMETER(zPath); + UNUSED_PARAMETER(flags); *pResOut = 0; return SQLITE_OK; } @@ -48651,6 +50391,7 @@ static int memdbFullPathname( int nOut, char *zOut ){ + UNUSED_PARAMETER(pVfs); sqlite3_snprintf(nOut, zOut, "%s", zPath); return SQLITE_OK; } @@ -48723,9 +50464,14 @@ static int memdbCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){ */ static MemFile *memdbFromDbSchema(sqlite3 *db, const char *zSchema){ MemFile *p = 0; + MemStore *pStore; int rc = sqlite3_file_control(db, zSchema, SQLITE_FCNTL_FILE_POINTER, &p); if( rc ) return 0; if( p->base.pMethods!=&memdb_io_methods ) return 0; + pStore = p->pStore; + memdbEnter(pStore); + if( pStore->zFName!=0 ) p = 0; + memdbLeave(pStore); return p; } @@ -48761,12 +50507,14 @@ SQLITE_API unsigned char *sqlite3_serialize( if( piSize ) *piSize = -1; if( iDb<0 ) return 0; if( p ){ - if( piSize ) *piSize = p->sz; + MemStore *pStore = p->pStore; + assert( pStore->pMutex==0 ); + if( piSize ) *piSize = pStore->sz; if( mFlags & SQLITE_SERIALIZE_NOCOPY ){ - pOut = p->aData; + pOut = pStore->aData; }else{ - pOut = sqlite3_malloc64( p->sz ); - if( pOut ) memcpy(pOut, p->aData, p->sz); + pOut = sqlite3_malloc64( pStore->sz ); + if( pOut ) memcpy(pOut, pStore->aData, pStore->sz); } return pOut; } @@ -48836,7 +50584,8 @@ SQLITE_API int sqlite3_deserialize( sqlite3_mutex_enter(db->mutex); if( zSchema==0 ) zSchema = db->aDb[0].zDbSName; iDb = sqlite3FindDbName(db, zSchema); - if( iDb<0 ){ + testcase( iDb==1 ); + if( iDb<2 && iDb!=0 ){ rc = SQLITE_ERROR; goto end_deserialize; } @@ -48860,15 +50609,16 @@ SQLITE_API int sqlite3_deserialize( if( p==0 ){ rc = SQLITE_ERROR; }else{ - p->aData = pData; + MemStore *pStore = p->pStore; + pStore->aData = pData; pData = 0; - p->sz = szDb; - p->szAlloc = szBuf; - p->szMax = szBuf; - if( p->szMaxszMax = sqlite3GlobalConfig.mxMemdbSize; + pStore->sz = szDb; + pStore->szAlloc = szBuf; + pStore->szMax = szBuf; + if( pStore->szMaxszMax = sqlite3GlobalConfig.mxMemdbSize; } - p->mFlags = mFlags; + pStore->mFlags = mFlags; rc = SQLITE_OK; } @@ -48887,7 +50637,9 @@ end_deserialize: */ SQLITE_PRIVATE int sqlite3MemdbInit(void){ sqlite3_vfs *pLower = sqlite3_vfs_find(0); - int sz = pLower->szOsFile; + unsigned int sz; + if( NEVER(pLower==0) ) return SQLITE_ERROR; + sz = pLower->szOsFile; memdb_vfs.pAppData = pLower; /* The following conditional can only be true when compiled for ** Windows x86 and SQLITE_MAX_MMAP_SIZE=0. We always leave @@ -48897,7 +50649,7 @@ SQLITE_PRIVATE int sqlite3MemdbInit(void){ memdb_vfs.szOsFile = sz; return sqlite3_vfs_register(&memdb_vfs, 0); } -#endif /* SQLITE_ENABLE_DESERIALIZE */ +#endif /* SQLITE_OMIT_DESERIALIZE */ /************** End of memdb.c ***********************************************/ /************** Begin file bitvec.c ******************************************/ @@ -49256,7 +51008,7 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){ sqlite3BitvecClear(0, 1, pTmpSpace); /* Run the program */ - pc = 0; + pc = i = 0; while( (op = aOp[pc])!=0 ){ switch( op ){ case 1: @@ -49560,11 +51312,14 @@ static int numberOfCachePages(PCache *p){ ** suggested cache size is set to N. */ return p->szCache; }else{ + i64 n; /* IMPLEMANTATION-OF: R-59858-46238 If the argument N is negative, then the ** number of cache pages is adjusted to be a number of pages that would ** use approximately abs(N*1024) bytes of memory based on the current ** page size. */ - return (int)((-1024*(i64)p->szCache)/(p->szPage+p->szExtra)); + n = ((-1024*(i64)p->szCache)/(p->szPage+p->szExtra)); + if( n>1000000000 ) n = 1000000000; + return (int)n; } } @@ -51019,12 +52774,18 @@ static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){ */ static void pcache1Cachesize(sqlite3_pcache *p, int nMax){ PCache1 *pCache = (PCache1 *)p; + u32 n; + assert( nMax>=0 ); if( pCache->bPurgeable ){ PGroup *pGroup = pCache->pGroup; pcache1EnterMutex(pGroup); - pGroup->nMaxPage += (nMax - pCache->nMax); + n = (u32)nMax; + if( n > 0x7fff0000 - pGroup->nMaxPage + pCache->nMax ){ + n = 0x7fff0000 - pGroup->nMaxPage + pCache->nMax; + } + pGroup->nMaxPage += (n - pCache->nMax); pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage; - pCache->nMax = nMax; + pCache->nMax = n; pCache->n90pct = pCache->nMax*9/10; pcache1EnforceMaxPage(pCache); pcache1LeaveMutex(pGroup); @@ -51040,7 +52801,7 @@ static void pcache1Shrink(sqlite3_pcache *p){ PCache1 *pCache = (PCache1*)p; if( pCache->bPurgeable ){ PGroup *pGroup = pCache->pGroup; - int savedMaxPage; + unsigned int savedMaxPage; pcache1EnterMutex(pGroup); savedMaxPage = pGroup->nMaxPage; pGroup->nMaxPage = 0; @@ -52777,6 +54538,7 @@ struct Pager { u8 noLock; /* Do not lock (except in WAL mode) */ u8 readOnly; /* True for a read-only database */ u8 memDb; /* True to inhibit all file I/O */ + u8 memVfs; /* VFS-implemented memory database */ /************************************************************************** ** The following block contains those class members that change during @@ -52826,8 +54588,9 @@ struct Pager { i16 nReserve; /* Number of unused bytes at end of each page */ u32 vfsFlags; /* Flags for sqlite3_vfs.xOpen() */ u32 sectorSize; /* Assumed sector size during rollback */ - int pageSize; /* Number of bytes in a page */ Pgno mxPgno; /* Maximum allowed size of the database */ + Pgno lckPgno; /* Page number for the locking page */ + i64 pageSize; /* Number of bytes in a page */ i64 journalSizeLimit; /* Size limit for persistent journal files */ char *zFilename; /* Name of the database file */ char *zJournal; /* Name of the journal file */ @@ -53813,7 +55576,7 @@ static int readJournalHdr( ** journal file descriptor is advanced to the next sector boundary before ** anything is written. The format is: ** -** + 4 bytes: PAGER_MJ_PGNO. +** + 4 bytes: PAGER_SJ_PGNO. ** + N bytes: super-journal filename in utf-8. ** + 4 bytes: N (length of super-journal name in bytes, no nul-terminator). ** + 4 bytes: super-journal name checksum. @@ -53861,7 +55624,7 @@ static int writeSuperJournal(Pager *pPager, const char *zSuper){ /* Write the super-journal data to the end of the journal file. If ** an error occurs, return the error code to the caller. */ - if( (0 != (rc = write32bits(pPager->jfd, iHdrOff, PAGER_MJ_PGNO(pPager)))) + if( (0 != (rc = write32bits(pPager->jfd, iHdrOff, PAGER_SJ_PGNO(pPager)))) || (0 != (rc = sqlite3OsWrite(pPager->jfd, zSuper, nSuper, iHdrOff+4))) || (0 != (rc = write32bits(pPager->jfd, iHdrOff+4+nSuper, nSuper))) || (0 != (rc = write32bits(pPager->jfd, iHdrOff+4+nSuper+4, cksum))) @@ -54371,7 +56134,7 @@ static u32 pager_cksum(Pager *pPager, const u8 *aData){ ** corrupted, SQLITE_DONE is returned. Data is considered corrupted in ** two circumstances: ** -** * If the record page-number is illegal (0 or PAGER_MJ_PGNO), or +** * If the record page-number is illegal (0 or PAGER_SJ_PGNO), or ** * If the record is being rolled back from the main journal file ** and the checksum field does not match the record content. ** @@ -54431,7 +56194,7 @@ static int pager_playback_one_page( ** it could cause invalid data to be written into the journal. We need to ** detect this invalid data (with high probability) and ignore it. */ - if( pgno==0 || pgno==PAGER_MJ_PGNO(pPager) ){ + if( pgno==0 || pgno==PAGER_SJ_PGNO(pPager) ){ assert( !isSavepnt ); return SQLITE_DONE; } @@ -54768,6 +56531,7 @@ static int pager_truncate(Pager *pPager, Pgno nPage){ memset(pTmp, 0, szPage); testcase( (newSize-szPage) == currentSize ); testcase( (newSize-szPage) > currentSize ); + sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_SIZE_HINT, &newSize); rc = sqlite3OsWrite(pPager->fd, pTmp, szPage, newSize-szPage); } if( rc==SQLITE_OK ){ @@ -54990,6 +56754,9 @@ static int pager_playback(Pager *pPager, int isHot){ goto end_playback; } pPager->dbSize = mxPg; + if( pPager->mxPgnomxPgno = mxPg; + } } /* Copy original pages out of the journal and back into the @@ -55171,6 +56938,7 @@ static int readDbPage(PgHdr *pPg){ */ static void pager_write_changecounter(PgHdr *pPg){ u32 change_counter; + if( NEVER(pPg==0) ) return; /* Increment the value just read and write it back to byte 24. */ change_counter = sqlite3Get4byte((u8*)pPg->pPager->dbFileVers)+1; @@ -55885,6 +57653,7 @@ SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nR pPager->pTmpSpace = pNew; pPager->dbSize = (Pgno)((nByte+pageSize-1)/pageSize); pPager->pageSize = pageSize; + pPager->lckPgno = (Pgno)(PENDING_BYTE/pageSize) + 1; }else{ sqlite3PageFree(pNew); } @@ -56045,8 +57814,7 @@ static int pager_wait_on_lock(Pager *pPager, int locktype){ ** current database image, in pages, OR ** ** b) if the page content were written at this time, it would not -** be necessary to write the current content out to the sub-journal -** (as determined by function subjRequiresPage()). +** be necessary to write the current content out to the sub-journal. ** ** If the condition asserted by this function were not true, and the ** dirty page were to be discarded from the cache via the pagerStress() @@ -56061,8 +57829,16 @@ static int pager_wait_on_lock(Pager *pPager, int locktype){ */ #if defined(SQLITE_DEBUG) static void assertTruncateConstraintCb(PgHdr *pPg){ + Pager *pPager = pPg->pPager; assert( pPg->flags&PGHDR_DIRTY ); - assert( !subjRequiresPage(pPg) || pPg->pgno<=pPg->pPager->dbSize ); + if( pPg->pgno>pPager->dbSize ){ /* if (a) is false */ + Pgno pgno = pPg->pgno; + int i; + for(i=0; ipPager->nSavepoint; i++){ + PagerSavepoint *p = &pPager->aSavepoint[i]; + assert( p->nOrigpInSavepoint,pgno) ); + } + } } static void assertTruncateConstraint(Pager *pPager){ sqlite3PcacheIterateDirty(pPager->pPCache, assertTruncateConstraintCb); @@ -56083,7 +57859,7 @@ static void assertTruncateConstraint(Pager *pPager){ ** then continue writing to the database. */ SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager *pPager, Pgno nPage){ - assert( pPager->dbSize>=nPage ); + assert( pPager->dbSize>=nPage || CORRUPT_DB ); assert( pPager->eState>=PAGER_WRITER_CACHEMOD ); pPager->dbSize = nPage; @@ -56811,7 +58587,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen( int rc = SQLITE_OK; /* Return code */ int tempFile = 0; /* True for temp files (incl. in-memory files) */ int memDb = 0; /* True if this is an in-memory file */ -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE int memJM = 0; /* Memory journal mode */ #else # define memJM 0 @@ -57004,6 +58780,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen( pPager->zWal = 0; } #endif + (void)pPtr; /* Suppress warning about unused pPtr value */ if( nPathname ) sqlite3DbFree(0, zPathname); pPager->pVfs = pVfs; @@ -57015,8 +58792,8 @@ SQLITE_PRIVATE int sqlite3PagerOpen( int fout = 0; /* VFS flags returned by xOpen() */ rc = sqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd, vfsFlags, &fout); assert( !memDb ); -#ifdef SQLITE_ENABLE_DESERIALIZE - memJM = (fout&SQLITE_OPEN_MEMORY)!=0; +#ifndef SQLITE_OMIT_DESERIALIZE + pPager->memVfs = memJM = (fout&SQLITE_OPEN_MEMORY)!=0; #endif readOnly = (fout&SQLITE_OPEN_READONLY)!=0; @@ -57401,7 +59178,7 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ ** may mean that the pager was in the error-state when this ** function was called and the journal file does not exist. */ - if( !isOpen(pPager->jfd) ){ + if( !isOpen(pPager->jfd) && pPager->journalMode!=PAGER_JOURNALMODE_OFF ){ sqlite3_vfs * const pVfs = pPager->pVfs; int bExists; /* True if journal file exists */ rc = sqlite3OsAccess( @@ -57646,7 +59423,7 @@ static int getPageNormal( if( pPg->pPager && !noContent ){ /* In this case the pcache already contains an initialized copy of ** the page. Return without further ado. */ - assert( pgno!=PAGER_MJ_PGNO(pPager) ); + assert( pgno!=PAGER_SJ_PGNO(pPager) ); pPager->aStat[PAGER_STAT_HIT]++; return SQLITE_OK; @@ -57657,7 +59434,7 @@ static int getPageNormal( ** (*) obsolete. Was: maximum page number is 2^31 ** (2) Never try to fetch the locking page */ - if( pgno==PAGER_MJ_PGNO(pPager) ){ + if( pgno==PAGER_SJ_PGNO(pPager) ){ rc = SQLITE_CORRUPT_BKPT; goto pager_acquire_err; } @@ -57803,6 +59580,7 @@ SQLITE_PRIVATE int sqlite3PagerGet( DbPage **ppPage, /* Write a pointer to the page here */ int flags /* PAGER_GET_XXX flags */ ){ + /* printf("PAGE %u\n", pgno); fflush(stdout); */ return pPager->xGet(pPager, pgno, ppPage, flags); } @@ -57951,6 +59729,7 @@ static int pager_open_journal(Pager *pPager){ if( rc!=SQLITE_OK ){ sqlite3BitvecDestroy(pPager->pInJournal); pPager->pInJournal = 0; + pPager->journalOff = 0; }else{ assert( pPager->eState==PAGER_WRITER_LOCKED ); pPager->eState = PAGER_WRITER_CACHEMOD; @@ -57983,7 +59762,7 @@ SQLITE_PRIVATE int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory assert( pPager->eState>=PAGER_READER && pPager->eStatesubjInMemory = (u8)subjInMemory; - if( ALWAYS(pPager->eState==PAGER_READER) ){ + if( pPager->eState==PAGER_READER ){ assert( pPager->pInJournal==0 ); if( pagerUseWal(pPager) ){ @@ -58055,7 +59834,7 @@ static SQLITE_NOINLINE int pagerAddPageToRollbackJournal(PgHdr *pPg){ /* We should never write to the journal file the page that ** contains the database locks. The following assert verifies ** that we do not. */ - assert( pPg->pgno!=PAGER_MJ_PGNO(pPager) ); + assert( pPg->pgno!=PAGER_SJ_PGNO(pPager) ); assert( pPager->journalHdr<=pPager->journalOff ); pData2 = pPg->pData; @@ -58234,7 +60013,7 @@ static SQLITE_NOINLINE int pagerWriteLargeSector(PgHdr *pPg){ Pgno pg = pg1+ii; PgHdr *pPage; if( pg==pPg->pgno || !sqlite3BitvecTest(pPager->pInJournal, pg) ){ - if( pg!=PAGER_MJ_PGNO(pPager) ){ + if( pg!=PAGER_SJ_PGNO(pPager) ){ rc = sqlite3PagerGet(pPager, pg, &pPage, 0); if( rc==SQLITE_OK ){ rc = pager_write(pPage); @@ -58712,7 +60491,7 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne( ** last page is never written out to disk, leaving the database file ** undersized. Fix this now if it is the case. */ if( pPager->dbSize>pPager->dbFileSize ){ - Pgno nNew = pPager->dbSize - (pPager->dbSize==PAGER_MJ_PGNO(pPager)); + Pgno nNew = pPager->dbSize - (pPager->dbSize==PAGER_SJ_PGNO(pPager)); assert( pPager->eState==PAGER_WRITER_DBMOD ); rc = pager_truncate(pPager, nNew); if( rc!=SQLITE_OK ) goto commit_phase_one_exit; @@ -58883,8 +60662,8 @@ SQLITE_PRIVATE int sqlite3PagerRefcount(Pager *pPager){ ** used by the pager and its associated cache. */ SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager *pPager){ - int perPageSize = pPager->pageSize + pPager->nExtra + sizeof(PgHdr) - + 5*sizeof(void*); + int perPageSize = pPager->pageSize + pPager->nExtra + + (int)(sizeof(PgHdr) + 5*sizeof(void*)); return perPageSize*sqlite3PcachePagecount(pPager->pPCache) + sqlite3MallocSize(pPager) + pPager->pageSize; @@ -58953,7 +60732,7 @@ SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, i ** Return true if this is an in-memory or temp-file backed pager. */ SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager *pPager){ - return pPager->tempFile; + return pPager->tempFile || pPager->memVfs; } /* @@ -59078,14 +60857,14 @@ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ } pPager->nSavepoint = nNew; - /* If this is a release of the outermost savepoint, truncate - ** the sub-journal to zero bytes in size. */ + /* Truncate the sub-journal so that it only includes the parts + ** that are still in use. */ if( op==SAVEPOINT_RELEASE ){ PagerSavepoint *pRel = &pPager->aSavepoint[nNew]; if( pRel->bTruncateOnRelease && isOpen(pPager->sjfd) ){ /* Only truncate if it is an in-memory sub-journal. */ if( sqlite3JournalIsInMemory(pPager->sjfd) ){ - i64 sz = (pPager->pageSize+4)*pRel->iSubRec; + i64 sz = (pPager->pageSize+4)*(i64)pRel->iSubRec; rc = sqlite3OsTruncate(pPager->sjfd, sz); assert( rc==SQLITE_OK ); } @@ -59273,7 +61052,7 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i pPgOld = sqlite3PagerLookup(pPager, pgno); assert( !pPgOld || pPgOld->nRef==1 || CORRUPT_DB ); if( pPgOld ){ - if( pPgOld->nRef>1 ){ + if( NEVER(pPgOld->nRef>1) ){ sqlite3PagerUnrefNotNull(pPgOld); return SQLITE_CORRUPT_BKPT; } @@ -59408,12 +61187,12 @@ SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){ u8 eOld = pPager->journalMode; /* Prior journalmode */ /* The eMode parameter is always valid */ - assert( eMode==PAGER_JOURNALMODE_DELETE - || eMode==PAGER_JOURNALMODE_TRUNCATE - || eMode==PAGER_JOURNALMODE_PERSIST - || eMode==PAGER_JOURNALMODE_OFF - || eMode==PAGER_JOURNALMODE_WAL - || eMode==PAGER_JOURNALMODE_MEMORY ); + assert( eMode==PAGER_JOURNALMODE_DELETE /* 0 */ + || eMode==PAGER_JOURNALMODE_PERSIST /* 1 */ + || eMode==PAGER_JOURNALMODE_OFF /* 2 */ + || eMode==PAGER_JOURNALMODE_TRUNCATE /* 3 */ + || eMode==PAGER_JOURNALMODE_MEMORY /* 4 */ + || eMode==PAGER_JOURNALMODE_WAL /* 5 */ ); /* This routine is only called from the OP_JournalMode opcode, and ** the logic there will never allow a temporary file to be changed @@ -59450,7 +61229,6 @@ SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){ assert( isOpen(pPager->fd) || pPager->exclusiveMode ); if( !pPager->exclusiveMode && (eOld & 5)==1 && (eMode & 1)==0 ){ - /* In this case we would like to delete the journal file. If it is ** not possible, then that is not a problem. Deleting the journal file ** here is an optimization only. @@ -59507,7 +61285,7 @@ SQLITE_PRIVATE int sqlite3PagerGetJournalMode(Pager *pPager){ SQLITE_PRIVATE int sqlite3PagerOkToChangeJournalMode(Pager *pPager){ assert( assert_pager_state(pPager) ); if( pPager->eState>=PAGER_WRITER_CACHEMOD ) return 0; - if( NEVER(isOpen(pPager->jfd) && pPager->journalOff>0) ) return 0; + if( isOpen(pPager->jfd) && pPager->journalOff>0 ) return 0; return 1; } @@ -59562,6 +61340,18 @@ SQLITE_PRIVATE int sqlite3PagerCheckpoint( int *pnCkpt /* OUT: Final number of checkpointed frames */ ){ int rc = SQLITE_OK; + if( pPager->pWal==0 && pPager->journalMode==PAGER_JOURNALMODE_WAL ){ + /* This only happens when a database file is zero bytes in size opened and + ** then "PRAGMA journal_mode=WAL" is run and then sqlite3_wal_checkpoint() + ** is invoked without any intervening transactions. We need to start + ** a transaction to initialize pWal. The PRAGMA table_list statement is + ** used for this since it starts transactions on every database file, + ** including all ATTACHed databases. This seems expensive for a single + ** sqlite3_wal_checkpoint() call, but it happens very rarely. + ** https://sqlite.org/forum/forumpost/fd0f19d229156939 + */ + sqlite3_exec(db, "PRAGMA table_list",0,0,0); + } if( pPager->pWal ){ rc = sqlite3WalCheckpoint(pPager->pWal, db, eMode, (eMode==SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler), @@ -60019,7 +61809,10 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ ** HASHTABLE_NPAGE_ONE frames. The values of HASHTABLE_NPAGE_ONE and ** HASHTABLE_NPAGE are selected so that together the wal-index header and ** first index block are the same size as all other index blocks in the -** wal-index. +** wal-index. The values are: +** +** HASHTABLE_NPAGE 4096 +** HASHTABLE_NPAGE_ONE 4062 ** ** Each index block contains two sections, a page-mapping that contains the ** database page number associated with each wal frame, and a hash-table @@ -60255,6 +62048,70 @@ struct WalCkptInfo { }; #define READMARK_NOT_USED 0xffffffff +/* +** This is a schematic view of the complete 136-byte header of the +** wal-index file (also known as the -shm file): +** +** +-----------------------------+ +** 0: | iVersion | \ +** +-----------------------------+ | +** 4: | (unused padding) | | +** +-----------------------------+ | +** 8: | iChange | | +** +-------+-------+-------------+ | +** 12: | bInit | bBig | szPage | | +** +-------+-------+-------------+ | +** 16: | mxFrame | | First copy of the +** +-----------------------------+ | WalIndexHdr object +** 20: | nPage | | +** +-----------------------------+ | +** 24: | aFrameCksum | | +** | | | +** +-----------------------------+ | +** 32: | aSalt | | +** | | | +** +-----------------------------+ | +** 40: | aCksum | | +** | | / +** +-----------------------------+ +** 48: | iVersion | \ +** +-----------------------------+ | +** 52: | (unused padding) | | +** +-----------------------------+ | +** 56: | iChange | | +** +-------+-------+-------------+ | +** 60: | bInit | bBig | szPage | | +** +-------+-------+-------------+ | Second copy of the +** 64: | mxFrame | | WalIndexHdr +** +-----------------------------+ | +** 68: | nPage | | +** +-----------------------------+ | +** 72: | aFrameCksum | | +** | | | +** +-----------------------------+ | +** 80: | aSalt | | +** | | | +** +-----------------------------+ | +** 88: | aCksum | | +** | | / +** +-----------------------------+ +** 96: | nBackfill | +** +-----------------------------+ +** 100: | 5 read marks | +** | | +** | | +** | | +** | | +** +-------+-------+------+------+ +** 120: | Write | Ckpt | Rcvr | Rd0 | \ +** +-------+-------+------+------+ ) 8 lock bytes +** | Read1 | Read2 | Rd3 | Rd4 | / +** +-------+-------+------+------+ +** 128: | nBackfillAttempted | +** +-----------------------------+ +** 132: | (unused padding) | +** +-----------------------------+ +*/ /* A block of WALINDEX_LOCK_RESERVED bytes beginning at ** WALINDEX_LOCK_OFFSET is reserved for locks. Since some systems @@ -60411,9 +62268,13 @@ struct WalIterator { ** so. It is safe to enlarge the wal-index if pWal->writeLock is true ** or pWal->exclusiveMode==WAL_HEAPMEMORY_MODE. ** -** If this call is successful, *ppPage is set to point to the wal-index -** page and SQLITE_OK is returned. If an error (an OOM or VFS error) occurs, -** then an SQLite error code is returned and *ppPage is set to 0. +** Three possible result scenarios: +** +** (1) rc==SQLITE_OK and *ppPage==Requested-Wal-Index-Page +** (2) rc>=SQLITE_ERROR and *ppPage==NULL +** (3) rc==SQLITE_OK and *ppPage==NULL // only if iPage==0 +** +** Scenario (3) can only occur when pWal->writeLock is false and iPage==0 */ static SQLITE_NOINLINE int walIndexPageRealloc( Wal *pWal, /* The WAL context */ @@ -60446,7 +62307,9 @@ static SQLITE_NOINLINE int walIndexPageRealloc( rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, pWal->writeLock, (void volatile **)&pWal->apWiData[iPage] ); - assert( pWal->apWiData[iPage]!=0 || rc!=SQLITE_OK || pWal->writeLock==0 ); + assert( pWal->apWiData[iPage]!=0 + || rc!=SQLITE_OK + || (pWal->writeLock==0 && iPage==0) ); testcase( pWal->apWiData[iPage]==0 && rc==SQLITE_OK ); if( rc==SQLITE_OK ){ if( iPage>0 && sqlite3FaultSim(600) ) rc = SQLITE_NOMEM; @@ -60785,8 +62648,8 @@ struct WalHashLoc { ** slot in the hash table is set to N, it refers to frame number ** (pLoc->iZero+N) in the log. ** -** Finally, set pLoc->aPgno so that pLoc->aPgno[1] is the page number of the -** first frame indexed by the hash table, frame (pLoc->iZero+1). +** Finally, set pLoc->aPgno so that pLoc->aPgno[0] is the page number of the +** first frame indexed by the hash table, frame (pLoc->iZero). */ static int walHashGet( Wal *pWal, /* WAL handle */ @@ -60798,7 +62661,7 @@ static int walHashGet( rc = walIndexPage(pWal, iHash, &pLoc->aPgno); assert( rc==SQLITE_OK || iHash>0 ); - if( rc==SQLITE_OK ){ + if( pLoc->aPgno ){ pLoc->aHash = (volatile ht_slot *)&pLoc->aPgno[HASHTABLE_NPAGE]; if( iHash==0 ){ pLoc->aPgno = &pLoc->aPgno[WALINDEX_HDR_SIZE/sizeof(u32)]; @@ -60806,7 +62669,8 @@ static int walHashGet( }else{ pLoc->iZero = HASHTABLE_NPAGE_ONE + (iHash-1)*HASHTABLE_NPAGE; } - pLoc->aPgno = &pLoc->aPgno[-1]; + }else if( NEVER(rc==SQLITE_OK) ){ + rc = SQLITE_ERROR; } return rc; } @@ -60857,7 +62721,6 @@ static void walCleanupHash(Wal *pWal){ int iLimit = 0; /* Zero values greater than this */ int nByte; /* Number of bytes to zero in aPgno[] */ int i; /* Used to iterate through aHash[] */ - int rc; /* Return code form walHashGet() */ assert( pWal->writeLock ); testcase( pWal->hdr.mxFrame==HASHTABLE_NPAGE_ONE-1 ); @@ -60872,8 +62735,8 @@ static void walCleanupHash(Wal *pWal){ */ assert( pWal->nWiData>walFramePage(pWal->hdr.mxFrame) ); assert( pWal->apWiData[walFramePage(pWal->hdr.mxFrame)] ); - rc = walHashGet(pWal, walFramePage(pWal->hdr.mxFrame), &sLoc); - if( NEVER(rc) ) return; /* Defense-in-depth, in case (1) above is wrong */ + i = walHashGet(pWal, walFramePage(pWal->hdr.mxFrame), &sLoc); + if( NEVER(i) ) return; /* Defense-in-depth, in case (1) above is wrong */ /* Zero all hash-table entries that correspond to frame numbers greater ** than pWal->hdr.mxFrame. @@ -60889,8 +62752,9 @@ static void walCleanupHash(Wal *pWal){ /* Zero the entries in the aPgno array that correspond to frames with ** frame numbers greater than pWal->hdr.mxFrame. */ - nByte = (int)((char *)sLoc.aHash - (char *)&sLoc.aPgno[iLimit+1]); - memset((void *)&sLoc.aPgno[iLimit+1], 0, nByte); + nByte = (int)((char *)sLoc.aHash - (char *)&sLoc.aPgno[iLimit]); + assert( nByte>=0 ); + memset((void *)&sLoc.aPgno[iLimit], 0, nByte); #ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT /* Verify that the every entry in the mapping region is still reachable @@ -60899,11 +62763,11 @@ static void walCleanupHash(Wal *pWal){ if( iLimit ){ int j; /* Loop counter */ int iKey; /* Hash key */ - for(j=1; j<=iLimit; j++){ + for(j=0; j=0 ); + memset((void*)sLoc.aPgno, 0, nByte); } /* If the entry in aPgno[] is already set, then the previous writer @@ -60946,9 +62810,9 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){ ** Remove the remnants of that writers uncommitted transaction from ** the hash-table before writing any new entries. */ - if( sLoc.aPgno[idx] ){ + if( sLoc.aPgno[idx-1] ){ walCleanupHash(pWal); - assert( !sLoc.aPgno[idx] ); + assert( !sLoc.aPgno[idx-1] ); } /* Write the aPgno[] array entry and the hash-table slot. */ @@ -60956,7 +62820,7 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){ for(iKey=walHash(iPage); sLoc.aHash[iKey]; iKey=walNextHash(iKey)){ if( (nCollide--)==0 ) return SQLITE_CORRUPT_BKPT; } - sLoc.aPgno[idx] = iPage; + sLoc.aPgno[idx-1] = iPage; AtomicStore(&sLoc.aHash[iKey], (ht_slot)idx); #ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT @@ -60977,19 +62841,18 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){ */ if( (idx&0x3ff)==0 ){ int i; /* Loop counter */ - for(i=1; i<=idx; i++){ + for(i=0; iapWiData[iPg] = aPrivate; for(iFrame=iFirst; iFrame<=iLast; iFrame++){ @@ -61269,14 +63133,43 @@ SQLITE_PRIVATE int sqlite3WalOpen( assert( zWalName && zWalName[0] ); assert( pDbFd ); + /* Verify the values of various constants. Any changes to the values + ** of these constants would result in an incompatible on-disk format + ** for the -shm file. Any change that causes one of these asserts to + ** fail is a backward compatibility problem, even if the change otherwise + ** works. + ** + ** This table also serves as a helpful cross-reference when trying to + ** interpret hex dumps of the -shm file. + */ + assert( 48 == sizeof(WalIndexHdr) ); + assert( 40 == sizeof(WalCkptInfo) ); + assert( 120 == WALINDEX_LOCK_OFFSET ); + assert( 136 == WALINDEX_HDR_SIZE ); + assert( 4096 == HASHTABLE_NPAGE ); + assert( 4062 == HASHTABLE_NPAGE_ONE ); + assert( 8192 == HASHTABLE_NSLOT ); + assert( 383 == HASHTABLE_HASH_1 ); + assert( 32768 == WALINDEX_PGSZ ); + assert( 8 == SQLITE_SHM_NLOCK ); + assert( 5 == WAL_NREADER ); + assert( 24 == WAL_FRAME_HDRSIZE ); + assert( 32 == WAL_HDRSIZE ); + assert( 120 == WALINDEX_LOCK_OFFSET + WAL_WRITE_LOCK ); + assert( 121 == WALINDEX_LOCK_OFFSET + WAL_CKPT_LOCK ); + assert( 122 == WALINDEX_LOCK_OFFSET + WAL_RECOVER_LOCK ); + assert( 123 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(0) ); + assert( 124 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(1) ); + assert( 125 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(2) ); + assert( 126 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(3) ); + assert( 127 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(4) ); + /* In the amalgamation, the os_unix.c and os_win.c source files come before ** this source file. Verify that the #defines of the locking byte offsets ** in os_unix.c and os_win.c agree with the WALINDEX_LOCK_OFFSET value. ** For that matter, if the lock offset ever changes from its initial design ** value of 120, we need to know that so there is an assert() to check it. */ - assert( 120==WALINDEX_LOCK_OFFSET ); - assert( 136==WALINDEX_HDR_SIZE ); #ifdef WIN_SHM_BASE assert( WIN_SHM_BASE==WALINDEX_LOCK_OFFSET ); #endif @@ -61578,7 +63471,6 @@ static int walIteratorInit(Wal *pWal, u32 nBackfill, WalIterator **pp){ int nEntry; /* Number of entries in this segment */ ht_slot *aIndex; /* Sorted index for this segment */ - sLoc.aPgno++; if( (i+1)==nSegment ){ nEntry = (int)(iLast - sLoc.iZero); }else{ @@ -62359,7 +64251,9 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ } /* Allocate a buffer to read frames into */ - szFrame = pWal->hdr.szPage + WAL_FRAME_HDRSIZE; + assert( (pWal->szPage & (pWal->szPage-1))==0 ); + assert( pWal->szPage>=512 && pWal->szPage<=65536 ); + szFrame = pWal->szPage + WAL_FRAME_HDRSIZE; aFrame = (u8 *)sqlite3_malloc64(szFrame); if( aFrame==0 ){ rc = SQLITE_NOMEM_BKPT; @@ -62373,7 +64267,7 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ ** the caller. */ aSaveCksum[0] = pWal->hdr.aFrameCksum[0]; aSaveCksum[1] = pWal->hdr.aFrameCksum[1]; - for(iOffset=walFrameOffset(pWal->hdr.mxFrame+1, pWal->hdr.szPage); + for(iOffset=walFrameOffset(pWal->hdr.mxFrame+1, pWal->szPage); iOffset+szFrame<=szWal; iOffset+=szFrame ){ @@ -62717,7 +64611,8 @@ SQLITE_PRIVATE int sqlite3WalSnapshotRecover(Wal *pWal){ rc = walHashGet(pWal, walFramePage(i), &sLoc); if( rc!=SQLITE_OK ) break; - pgno = sLoc.aPgno[i-sLoc.iZero]; + assert( i - sLoc.iZero - 1 >=0 ); + pgno = sLoc.aPgno[i-sLoc.iZero-1]; iDbOff = (i64)(pgno-1) * szPage; if( iDbOff+szPage<=szDb ){ @@ -62950,7 +64845,7 @@ SQLITE_PRIVATE int sqlite3WalFindFrame( iKey = walHash(pgno); while( (iH = AtomicLoad(&sLoc.aHash[iKey]))!=0 ){ u32 iFrame = iH + sLoc.iZero; - if( iFrame<=iLast && iFrame>=pWal->minFrame && sLoc.aPgno[iH]==pgno ){ + if( iFrame<=iLast && iFrame>=pWal->minFrame && sLoc.aPgno[iH-1]==pgno ){ assert( iFrame>iRead || CORRUPT_DB ); iRead = iFrame; } @@ -64202,7 +66097,6 @@ typedef struct CellInfo CellInfo; */ struct MemPage { u8 isInit; /* True if previously initialized. MUST BE FIRST! */ - u8 bBusy; /* Prevent endless loops on corrupt database files */ u8 intKey; /* True if table b-trees. False for index b-trees */ u8 intKeyLeaf; /* True if the leaf of an intKey table */ Pgno pgno; /* Page number for this page */ @@ -64224,7 +66118,9 @@ struct MemPage { u8 *apOvfl[4]; /* Pointers to the body of overflow cells */ BtShared *pBt; /* Pointer to BtShared that this page is part of */ u8 *aData; /* Pointer to disk image of the page data */ - u8 *aDataEnd; /* One byte past the end of usable data */ + u8 *aDataEnd; /* One byte past the end of the entire page - not just + ** the usable space, the entire page. Used to prevent + ** corruption-induced buffer overflow. */ u8 *aCellIdx; /* The cell index area */ u8 *aDataOfst; /* Same as aData for leaves. aData+4 for interior */ DbPage *pDbPage; /* Pager page handle */ @@ -64529,7 +66425,7 @@ struct BtCursor { /* ** The database page the PENDING_BYTE occupies. This page is never used. */ -# define PENDING_BYTE_PAGE(pBt) PAGER_MJ_PGNO(pBt) +#define PENDING_BYTE_PAGE(pBt) ((Pgno)((PENDING_BYTE/((pBt)->pageSize))+1)) /* ** These macros define the location of the pointer-map entry for a @@ -65170,7 +67066,7 @@ static int hasSharedCacheTableLock( int bSeen = 0; for(p=sqliteHashFirst(&pSchema->idxHash); p; p=sqliteHashNext(p)){ Index *pIdx = (Index *)sqliteHashData(p); - if( pIdx->tnum==(int)iRoot ){ + if( pIdx->tnum==iRoot ){ if( bSeen ){ /* Two or more indexes share the same root page. There must ** be imposter tables. So just return true. The assert is not @@ -65503,7 +67399,7 @@ static void invalidateIncrblobCursors( int isClearTable /* True if all rows are being deleted */ ){ BtCursor *p; - if( pBtree->hasIncrblobCur==0 ) return; + assert( pBtree->hasIncrblobCur ); assert( sqlite3BtreeHoldsMutex(pBtree) ); pBtree->hasIncrblobCur = 0; for(p=pBtree->pBt->pCursor; p; p=p->pNext){ @@ -65763,7 +67659,7 @@ SQLITE_PRIVATE void sqlite3BtreeClearCursor(BtCursor *pCur){ /* ** In this version of BtreeMoveto, pKey is a packed index record ** such as is generated by the OP_MakeRecord opcode. Unpack the -** record and then call BtreeMovetoUnpacked() to do the work. +** record and then call sqlite3BtreeIndexMoveto() to do the work. */ static int btreeMoveto( BtCursor *pCur, /* Cursor open on the btree to be searched */ @@ -65783,15 +67679,13 @@ static int btreeMoveto( sqlite3VdbeRecordUnpack(pKeyInfo, (int)nKey, pKey, pIdxKey); if( pIdxKey->nField==0 || pIdxKey->nField>pKeyInfo->nAllField ){ rc = SQLITE_CORRUPT_BKPT; - goto moveto_done; + }else{ + rc = sqlite3BtreeIndexMoveto(pCur, pIdxKey, pRes); } + sqlite3DbFree(pCur->pKeyInfo->db, pIdxKey); }else{ pIdxKey = 0; - } - rc = sqlite3BtreeMovetoUnpacked(pCur, pIdxKey, nKey, bias, pRes); -moveto_done: - if( pIdxKey ){ - sqlite3DbFree(pCur->pKeyInfo->db, pIdxKey); + rc = sqlite3BtreeTableMoveto(pCur, nKey, bias, pRes); } return rc; } @@ -66183,18 +68077,32 @@ static void btreeParseCellPtr( ** ** pIter += getVarint(pIter, (u64*)&pInfo->nKey); ** - ** The code is inlined to avoid a function call. + ** The code is inlined and the loop is unrolled for performance. + ** This routine is a high-runner. */ iKey = *pIter; if( iKey>=0x80 ){ - u8 *pEnd = &pIter[7]; - iKey &= 0x7f; - while(1){ - iKey = (iKey<<7) | (*++pIter & 0x7f); - if( (*pIter)<0x80 ) break; - if( pIter>=pEnd ){ - iKey = (iKey<<8) | *++pIter; - break; + u8 x; + iKey = ((iKey&0x7f)<<7) | ((x = *++pIter) & 0x7f); + if( x>=0x80 ){ + iKey = (iKey<<7) | ((x =*++pIter) & 0x7f); + if( x>=0x80 ){ + iKey = (iKey<<7) | ((x = *++pIter) & 0x7f); + if( x>=0x80 ){ + iKey = (iKey<<7) | ((x = *++pIter) & 0x7f); + if( x>=0x80 ){ + iKey = (iKey<<7) | ((x = *++pIter) & 0x7f); + if( x>=0x80 ){ + iKey = (iKey<<7) | ((x = *++pIter) & 0x7f); + if( x>=0x80 ){ + iKey = (iKey<<7) | ((x = *++pIter) & 0x7f); + if( x>=0x80 ){ + iKey = (iKey<<8) | (*++pIter); + } + } + } + } + } } } } @@ -66204,7 +68112,7 @@ static void btreeParseCellPtr( pInfo->nPayload = nPayload; pInfo->pPayload = pIter; testcase( nPayload==pPage->maxLocal ); - testcase( nPayload==pPage->maxLocal+1 ); + testcase( nPayload==(u32)pPage->maxLocal+1 ); if( nPayload<=pPage->maxLocal ){ /* This is the (easy) common case where the entire payload fits ** on the local page. No overflow is required. @@ -66241,7 +68149,7 @@ static void btreeParseCellPtrIndex( pInfo->nPayload = nPayload; pInfo->pPayload = pIter; testcase( nPayload==pPage->maxLocal ); - testcase( nPayload==pPage->maxLocal+1 ); + testcase( nPayload==(u32)pPage->maxLocal+1 ); if( nPayload<=pPage->maxLocal ){ /* This is the (easy) common case where the entire payload fits ** on the local page. No overflow is required. @@ -66271,6 +68179,7 @@ static void btreeParseCell( ** the space used by the cell pointer. ** ** cellSizePtrNoPayload() => table internal nodes +** cellSizePtrTableLeaf() => table leaf nodes ** cellSizePtr() => all index nodes & table leaf nodes */ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ @@ -66296,15 +68205,8 @@ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ }while( *(pIter)>=0x80 && pIterintKey ){ - /* pIter now points at the 64-bit integer key value, a variable length - ** integer. The following block moves pIter to point at the first byte - ** past the end of the key value. */ - pEnd = &pIter[9]; - while( (*pIter++)&0x80 && pItermaxLocal ); - testcase( nSize==pPage->maxLocal+1 ); + testcase( nSize==(u32)pPage->maxLocal+1 ); if( nSize<=pPage->maxLocal ){ nSize += (u32)(pIter - pCell); if( nSize<4 ) nSize = 4; @@ -66312,7 +68214,7 @@ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ int minLocal = pPage->minLocal; nSize = minLocal + (nSize - minLocal) % (pPage->pBt->usableSize - 4); testcase( nSize==pPage->maxLocal ); - testcase( nSize==pPage->maxLocal+1 ); + testcase( nSize==(u32)pPage->maxLocal+1 ); if( nSize>pPage->maxLocal ){ nSize = minLocal; } @@ -66342,6 +68244,58 @@ static u16 cellSizePtrNoPayload(MemPage *pPage, u8 *pCell){ assert( debuginfo.nSize==(u16)(pIter - pCell) || CORRUPT_DB ); return (u16)(pIter - pCell); } +static u16 cellSizePtrTableLeaf(MemPage *pPage, u8 *pCell){ + u8 *pIter = pCell; /* For looping over bytes of pCell */ + u8 *pEnd; /* End mark for a varint */ + u32 nSize; /* Size value to return */ + +#ifdef SQLITE_DEBUG + /* The value returned by this function should always be the same as + ** the (CellInfo.nSize) value found by doing a full parse of the + ** cell. If SQLITE_DEBUG is defined, an assert() at the bottom of + ** this function verifies that this invariant is not violated. */ + CellInfo debuginfo; + pPage->xParseCell(pPage, pCell, &debuginfo); +#endif + + nSize = *pIter; + if( nSize>=0x80 ){ + pEnd = &pIter[8]; + nSize &= 0x7f; + do{ + nSize = (nSize<<7) | (*++pIter & 0x7f); + }while( *(pIter)>=0x80 && pItermaxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize<=pPage->maxLocal ){ + nSize += (u32)(pIter - pCell); + if( nSize<4 ) nSize = 4; + }else{ + int minLocal = pPage->minLocal; + nSize = minLocal + (nSize - minLocal) % (pPage->pBt->usableSize - 4); + testcase( nSize==pPage->maxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize>pPage->maxLocal ){ + nSize = minLocal; + } + nSize += 4 + (u16)(pIter - pCell); + } + assert( nSize==debuginfo.nSize || CORRUPT_DB ); + return (u16)nSize; +} #ifdef SQLITE_DEBUG @@ -66355,7 +68309,7 @@ static u16 cellSize(MemPage *pPage, int iCell){ #ifndef SQLITE_OMIT_AUTOVACUUM /* ** The cell pCell is currently part of page pSrc but will ultimately be part -** of pPage. (pSrc and pPager are often the same.) If pCell contains a +** of pPage. (pSrc and pPage are often the same.) If pCell contains a ** pointer to an overflow page, insert an entry into the pointer-map for ** the overflow page that will be valid after pCell has been moved to pPage. */ @@ -66404,6 +68358,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ unsigned char *src; /* Source of content */ int iCellFirst; /* First allowable cell index */ int iCellLast; /* Last possible cell index */ + int iCellStart; /* First cell offset in input */ assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( pPage->pBt!=0 ); @@ -66464,6 +68419,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ cbrk = usableSize; iCellLast = usableSize - 4; + iCellStart = get2byte(&data[hdr+5]); for(i=0; iiCellLast ){ + if( pciCellLast ){ return SQLITE_CORRUPT_PAGE(pPage); } - assert( pc>=iCellFirst && pc<=iCellLast ); + assert( pc>=iCellStart && pc<=iCellLast ); size = pPage->xCellSize(pPage, &src[pc]); cbrk -= size; - if( cbrkusableSize ){ + if( cbrkusableSize ){ return SQLITE_CORRUPT_PAGE(pPage); } - assert( cbrk+size<=usableSize && cbrk>=iCellFirst ); + assert( cbrk+size<=usableSize && cbrk>=iCellStart ); testcase( cbrk+size==usableSize ); testcase( pc+size==usableSize ); put2byte(pAddr, cbrk); if( temp==0 ){ - int x; if( cbrk==pc ) continue; temp = sqlite3PagerTempSpace(pPage->pBt->pPager); - x = get2byte(&data[hdr+5]); - memcpy(&temp[x], &data[x], (cbrk+size) - x); + memcpy(&temp[iCellStart], &data[iCellStart], usableSize - iCellStart); src = temp; } memcpy(&data[cbrk], &src[pc], size); @@ -66530,7 +68484,8 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ const int hdr = pPg->hdrOffset; /* Offset to page header */ u8 * const aData = pPg->aData; /* Page data */ int iAddr = hdr + 1; /* Address of ptr to pc */ - int pc = get2byte(&aData[iAddr]); /* Address of a free slot */ + u8 *pTmp = &aData[iAddr]; /* Temporary ptr into aData[] */ + int pc = get2byte(pTmp); /* Address of a free slot */ int x; /* Excess size of the slot */ int maxPC = pPg->pBt->usableSize - nByte; /* Max address for a usable slot */ int size; /* Size of the free slot */ @@ -66540,7 +68495,8 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ /* EVIDENCE-OF: R-22710-53328 The third and fourth bytes of each ** freeblock form a big-endian integer which is the size of the freeblock ** in bytes, including the 4-byte header. */ - size = get2byte(&aData[pc+2]); + pTmp = &aData[pc+2]; + size = get2byte(pTmp); if( (x = size - nByte)>=0 ){ testcase( x==4 ); testcase( x==3 ); @@ -66553,6 +68509,8 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ ** fragmented bytes within the page. */ memcpy(&aData[iAddr], &aData[pc], 2); aData[hdr+7] += (u8)x; + testcase( pc+x>maxPC ); + return &aData[pc]; }else if( x+pc > maxPC ){ /* This slot extends off the end of the usable part of the page */ *pRc = SQLITE_CORRUPT_PAGE(pPg); @@ -66565,7 +68523,8 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ return &aData[pc + x]; } iAddr = pc; - pc = get2byte(&aData[pc]); + pTmp = &aData[pc]; + pc = get2byte(pTmp); if( pc<=iAddr+size ){ if( pc ){ /* The next slot in the chain is not past the end of the current slot */ @@ -66599,6 +68558,7 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ u8 * const data = pPage->aData; /* Local cache of pPage->aData */ int top; /* First byte of cell content area */ int rc = SQLITE_OK; /* Integer return code */ + u8 *pTmp; /* Temp ptr into data[] */ int gap; /* First byte of gap between cell pointers and cell content */ assert( sqlite3PagerIswriteable(pPage->pDbPage) ); @@ -66617,7 +68577,8 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ ** then the cell content offset of an empty page wants to be 65536. ** However, that integer is too large to be stored in a 2-byte unsigned ** integer, so a value of 0 is used in its place. */ - top = get2byte(&data[hdr+5]); + pTmp = &data[hdr+5]; + top = get2byte(pTmp); assert( top<=(int)pPage->pBt->usableSize ); /* by btreeComputeFreeSpace() */ if( gap>top ){ if( top==0 && pPage->pBt->usableSize==65536 ){ @@ -66640,7 +68601,7 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ int g2; assert( pSpace+nByte<=data+pPage->pBt->usableSize ); *pIdx = g2 = (int)(pSpace-data); - if( NEVER(g2<=gap) ){ + if( g2<=gap ){ return SQLITE_CORRUPT_PAGE(pPage); }else{ return SQLITE_OK; @@ -66699,6 +68660,7 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ u16 x; /* Offset to cell content area */ u32 iEnd = iStart + iSize; /* First byte past the iStart buffer */ unsigned char *data = pPage->aData; /* Page content */ + u8 *pTmp; /* Temporary ptr into data[] */ assert( pPage->pBt!=0 ); assert( sqlite3PagerIswriteable(pPage->pDbPage) ); @@ -66726,7 +68688,7 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ if( iFreeBlk>pPage->pBt->usableSize-4 ){ /* TH3: corrupt081.100 */ return SQLITE_CORRUPT_PAGE(pPage); } - assert( iFreeBlk>iPtr || iFreeBlk==0 ); + assert( iFreeBlk>iPtr || iFreeBlk==0 || CORRUPT_DB ); /* At this point: ** iFreeBlk: First freeblock after iStart, or zero if none @@ -66761,7 +68723,8 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ if( nFrag>data[hdr+7] ) return SQLITE_CORRUPT_PAGE(pPage); data[hdr+7] -= nFrag; } - x = get2byte(&data[hdr+5]); + pTmp = &data[hdr+5]; + x = get2byte(pTmp); if( iStart<=x ){ /* The new freeblock is at the beginning of the cell content area, ** so just extend the cell content area rather than create another @@ -66805,7 +68768,6 @@ static int decodeFlags(MemPage *pPage, int flagByte){ pPage->leaf = (u8)(flagByte>>3); assert( PTF_LEAF == 1<<3 ); flagByte &= ~PTF_LEAF; pPage->childPtrSize = 4-4*pPage->leaf; - pPage->xCellSize = cellSizePtr; pBt = pPage->pBt; if( flagByte==(PTF_LEAFDATA | PTF_INTKEY) ){ /* EVIDENCE-OF: R-07291-35328 A value of 5 (0x05) means the page is an @@ -66817,6 +68779,7 @@ static int decodeFlags(MemPage *pPage, int flagByte){ pPage->intKey = 1; if( pPage->leaf ){ pPage->intKeyLeaf = 1; + pPage->xCellSize = cellSizePtrTableLeaf; pPage->xParseCell = btreeParseCellPtr; }else{ pPage->intKeyLeaf = 0; @@ -66834,12 +68797,17 @@ static int decodeFlags(MemPage *pPage, int flagByte){ assert( (PTF_ZERODATA|PTF_LEAF)==10 ); pPage->intKey = 0; pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtr; pPage->xParseCell = btreeParseCellPtrIndex; pPage->maxLocal = pBt->maxLocal; pPage->minLocal = pBt->minLocal; }else{ /* EVIDENCE-OF: R-47608-56469 Any other value for the b-tree page type is ** an error. */ + pPage->intKey = 0; + pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtr; + pPage->xParseCell = btreeParseCellPtrIndex; return SQLITE_CORRUPT_PAGE(pPage); } pPage->max1bytePayload = pBt->max1bytePayload; @@ -66997,7 +68965,7 @@ static int btreeInitPage(MemPage *pPage){ pPage->nOverflow = 0; pPage->cellOffset = pPage->hdrOffset + 8 + pPage->childPtrSize; pPage->aCellIdx = data + pPage->childPtrSize + 8; - pPage->aDataEnd = pPage->aData + pBt->usableSize; + pPage->aDataEnd = pPage->aData + pBt->pageSize; pPage->aDataOfst = pPage->aData + pPage->childPtrSize; /* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the ** number of cells on the page. */ @@ -67032,7 +69000,7 @@ static void zeroPage(MemPage *pPage, int flags){ u8 hdr = pPage->hdrOffset; u16 first; - assert( sqlite3PagerPagenumber(pPage->pDbPage)==pPage->pgno ); + assert( sqlite3PagerPagenumber(pPage->pDbPage)==pPage->pgno || CORRUPT_DB ); assert( sqlite3PagerGetExtra(pPage->pDbPage) == (void*)pPage ); assert( sqlite3PagerGetData(pPage->pDbPage) == data ); assert( sqlite3PagerIswriteable(pPage->pDbPage) ); @@ -67048,7 +69016,7 @@ static void zeroPage(MemPage *pPage, int flags){ pPage->nFree = (u16)(pBt->usableSize - first); decodeFlags(pPage, flags); pPage->cellOffset = first; - pPage->aDataEnd = &data[pBt->usableSize]; + pPage->aDataEnd = &data[pBt->pageSize]; pPage->aCellIdx = &data[first]; pPage->aDataOfst = &data[pPage->childPtrSize]; pPage->nOverflow = 0; @@ -67174,7 +69142,7 @@ static int getAndInitPage( goto getAndInitPage_error2; } } - assert( (*ppPage)->pgno==pgno ); + assert( (*ppPage)->pgno==pgno || CORRUPT_DB ); assert( (*ppPage)->aData==sqlite3PagerGetData(pDbPage) ); /* If obtaining a child page for a cursor, we must verify that the page is @@ -67193,7 +69161,9 @@ getAndInitPage_error1: pCur->pPage = pCur->apPage[pCur->iPage]; } testcase( pgno==0 ); - assert( pgno!=0 || rc==SQLITE_CORRUPT ); + assert( pgno!=0 || rc==SQLITE_CORRUPT + || rc==SQLITE_IOERR_NOMEM + || rc==SQLITE_NOMEM ); return rc; } @@ -67651,30 +69621,38 @@ static int removeFromSharingList(BtShared *pBt){ ** MX_CELL_SIZE(pBt) bytes with a 4-byte prefix for a left-child ** pointer. */ -static void allocateTempSpace(BtShared *pBt){ - if( !pBt->pTmpSpace ){ - pBt->pTmpSpace = sqlite3PageMalloc( pBt->pageSize ); - - /* One of the uses of pBt->pTmpSpace is to format cells before - ** inserting them into a leaf page (function fillInCell()). If - ** a cell is less than 4 bytes in size, it is rounded up to 4 bytes - ** by the various routines that manipulate binary cells. Which - ** can mean that fillInCell() only initializes the first 2 or 3 - ** bytes of pTmpSpace, but that the first 4 bytes are copied from - ** it into a database page. This is not actually a problem, but it - ** does cause a valgrind error when the 1 or 2 bytes of unitialized - ** data is passed to system call write(). So to avoid this error, - ** zero the first 4 bytes of temp space here. - ** - ** Also: Provide four bytes of initialized space before the - ** beginning of pTmpSpace as an area available to prepend the - ** left-child pointer to the beginning of a cell. - */ - if( pBt->pTmpSpace ){ - memset(pBt->pTmpSpace, 0, 8); - pBt->pTmpSpace += 4; - } +static SQLITE_NOINLINE int allocateTempSpace(BtShared *pBt){ + assert( pBt!=0 ); + assert( pBt->pTmpSpace==0 ); + /* This routine is called only by btreeCursor() when allocating the + ** first write cursor for the BtShared object */ + assert( pBt->pCursor!=0 && (pBt->pCursor->curFlags & BTCF_WriteFlag)!=0 ); + pBt->pTmpSpace = sqlite3PageMalloc( pBt->pageSize ); + if( pBt->pTmpSpace==0 ){ + BtCursor *pCur = pBt->pCursor; + pBt->pCursor = pCur->pNext; /* Unlink the cursor */ + memset(pCur, 0, sizeof(*pCur)); + return SQLITE_NOMEM_BKPT; } + + /* One of the uses of pBt->pTmpSpace is to format cells before + ** inserting them into a leaf page (function fillInCell()). If + ** a cell is less than 4 bytes in size, it is rounded up to 4 bytes + ** by the various routines that manipulate binary cells. Which + ** can mean that fillInCell() only initializes the first 2 or 3 + ** bytes of pTmpSpace, but that the first 4 bytes are copied from + ** it into a database page. This is not actually a problem, but it + ** does cause a valgrind error when the 1 or 2 bytes of unitialized + ** data is passed to system call write(). So to avoid this error, + ** zero the first 4 bytes of temp space here. + ** + ** Also: Provide four bytes of initialized space before the + ** beginning of pTmpSpace as an area available to prepend the + ** left-child pointer to the beginning of a cell. + */ + memset(pBt->pTmpSpace, 0, 8); + pBt->pTmpSpace += 4; + return SQLITE_OK; } /* @@ -68053,7 +70031,6 @@ static int lockBtree(BtShared *pBt){ MemPage *pPage1; /* Page 1 of the database file */ u32 nPage; /* Number of pages in the database */ u32 nPageFile = 0; /* Number of pages in the database file */ - u32 nPageHeader; /* Number of pages in the database according to hdr */ assert( sqlite3_mutex_held(pBt->mutex) ); assert( pBt->pPage1==0 ); @@ -68065,7 +70042,7 @@ static int lockBtree(BtShared *pBt){ /* Do some checking to help insure the file we opened really is ** a valid database file. */ - nPage = nPageHeader = get4byte(28+(u8*)pPage1->aData); + nPage = get4byte(28+(u8*)pPage1->aData); sqlite3PagerPagecount(pBt->pPager, (int*)&nPageFile); if( nPage==0 || memcmp(24+(u8*)pPage1->aData, 92+(u8*)pPage1->aData,4)!=0 ){ nPage = nPageFile; @@ -68100,7 +70077,7 @@ static int lockBtree(BtShared *pBt){ goto page1_init_failed; } - /* If the write version is set to 2, this database should be accessed + /* If the read version is set to 2, this database should be accessed ** in WAL mode. If the log is not already open, open it now. Then ** return SQLITE_OK and return without populating BtShared.pPage1. ** The caller detects this and calls this function again. This is @@ -68172,9 +70149,13 @@ static int lockBtree(BtShared *pBt){ pageSize-usableSize); return rc; } - if( sqlite3WritableSchema(pBt->db)==0 && nPage>nPageFile ){ - rc = SQLITE_CORRUPT_BKPT; - goto page1_init_failed; + if( nPage>nPageFile ){ + if( sqlite3WritableSchema(pBt->db)==0 ){ + rc = SQLITE_CORRUPT_BKPT; + goto page1_init_failed; + }else{ + nPage = nPageFile; + } } /* EVIDENCE-OF: R-28312-64704 However, the usable size is not allowed to ** be less than 480. In other words, if the page size is 512, then the @@ -68804,12 +70785,17 @@ static int incrVacuumStep(BtShared *pBt, Pgno nFin, Pgno iLastPg, int bCommit){ } do { MemPage *pFreePg; + Pgno dbSize = btreePagecount(pBt); rc = allocateBtreePage(pBt, &pFreePg, &iFreePg, iNear, eMode); if( rc!=SQLITE_OK ){ releasePage(pLastPg); return rc; } releasePage(pFreePg); + if( iFreePg>dbSize ){ + releasePage(pLastPg); + return SQLITE_CORRUPT_BKPT; + } }while( bCommit && iFreePg>nFin ); assert( iFreePgpPager; - VVA_ONLY( int nRef = sqlite3PagerRefcount(pPager); ) + Pager *pPager; + BtShared *pBt; + sqlite3 *db; + VVA_ONLY( int nRef ); + + assert( p!=0 ); + pBt = p->pBt; + pPager = pBt->pPager; + VVA_ONLY( nRef = sqlite3PagerRefcount(pPager); ) assert( sqlite3_mutex_held(pBt->mutex) ); invalidateAllOverflowCache(pBt); @@ -68915,6 +70903,7 @@ static int autoVacuumCommit(BtShared *pBt){ if( !pBt->incrVacuum ){ Pgno nFin; /* Number of pages in database after autovacuuming */ Pgno nFree; /* Number of pages on the freelist initially */ + Pgno nVac; /* Number of pages to vacuum */ Pgno iFree; /* The next page to be freed */ Pgno nOrig; /* Database size before freeing */ @@ -68928,18 +70917,42 @@ static int autoVacuumCommit(BtShared *pBt){ } nFree = get4byte(&pBt->pPage1->aData[36]); - nFin = finalDbSize(pBt, nOrig, nFree); + db = p->db; + if( db->xAutovacPages ){ + int iDb; + for(iDb=0; ALWAYS(iDbnDb); iDb++){ + if( db->aDb[iDb].pBt==p ) break; + } + nVac = db->xAutovacPages( + db->pAutovacPagesArg, + db->aDb[iDb].zDbSName, + nOrig, + nFree, + pBt->pageSize + ); + if( nVac>nFree ){ + nVac = nFree; + } + if( nVac==0 ){ + return SQLITE_OK; + } + }else{ + nVac = nFree; + } + nFin = finalDbSize(pBt, nOrig, nVac); if( nFin>nOrig ) return SQLITE_CORRUPT_BKPT; if( nFinnFin && rc==SQLITE_OK; iFree--){ - rc = incrVacuumStep(pBt, nFin, iFree, 1); + rc = incrVacuumStep(pBt, nFin, iFree, nVac==nFree); } if( (rc==SQLITE_DONE || rc==SQLITE_OK) && nFree>0 ){ rc = sqlite3PagerWrite(pBt->pPage1->pDbPage); - put4byte(&pBt->pPage1->aData[32], 0); - put4byte(&pBt->pPage1->aData[36], 0); + if( nVac==nFree ){ + put4byte(&pBt->pPage1->aData[32], 0); + put4byte(&pBt->pPage1->aData[36], 0); + } put4byte(&pBt->pPage1->aData[28], nFin); pBt->bDoTruncate = 1; pBt->nPage = nFin; @@ -68990,7 +71003,7 @@ SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zSuperJrnl){ sqlite3BtreeEnter(p); #ifndef SQLITE_OMIT_AUTOVACUUM if( pBt->autoVacuum ){ - rc = autoVacuumCommit(pBt); + rc = autoVacuumCommit(p); if( rc!=SQLITE_OK ){ sqlite3BtreeLeave(p); return rc; @@ -69177,7 +71190,7 @@ static void btreeSetNPage(BtShared *pBt, MemPage *pPage1){ int nPage = get4byte(&pPage1->aData[28]); testcase( nPage==0 ); if( nPage==0 ) sqlite3PagerPagecount(pBt->pPager, &nPage); - testcase( pBt->nPage!=nPage ); + testcase( pBt->nPage!=(u32)nPage ); pBt->nPage = nPage; } @@ -69389,10 +71402,6 @@ static int btreeCursor( assert( pBt->pPage1 && pBt->pPage1->aData ); assert( wrFlag==0 || (pBt->btsFlags & BTS_READ_ONLY)==0 ); - if( wrFlag ){ - allocateTempSpace(pBt); - if( pBt->pTmpSpace==0 ) return SQLITE_NOMEM_BKPT; - } if( iTable<=1 ){ if( iTable<1 ){ return SQLITE_CORRUPT_BKPT; @@ -69409,19 +71418,25 @@ static int btreeCursor( pCur->pKeyInfo = pKeyInfo; pCur->pBtree = p; pCur->pBt = pBt; - pCur->curFlags = wrFlag ? BTCF_WriteFlag : 0; - pCur->curPagerFlags = wrFlag ? 0 : PAGER_GET_READONLY; + pCur->curFlags = 0; /* If there are two or more cursors on the same btree, then all such ** cursors *must* have the BTCF_Multiple flag set. */ for(pX=pBt->pCursor; pX; pX=pX->pNext){ if( pX->pgnoRoot==iTable ){ pX->curFlags |= BTCF_Multiple; - pCur->curFlags |= BTCF_Multiple; + pCur->curFlags = BTCF_Multiple; } } + pCur->eState = CURSOR_INVALID; pCur->pNext = pBt->pCursor; pBt->pCursor = pCur; - pCur->eState = CURSOR_INVALID; + if( wrFlag ){ + pCur->curFlags |= BTCF_WriteFlag; + pCur->curPagerFlags = 0; + if( pBt->pTmpSpace==0 ) return allocateTempSpace(pBt); + }else{ + pCur->curPagerFlags = PAGER_GET_READONLY; + } return SQLITE_OK; } static int btreeCursorWithLock( @@ -69795,7 +71810,9 @@ static int accessPayload( assert( pPage ); assert( eOp==0 || eOp==1 ); assert( pCur->eState==CURSOR_VALID ); - assert( pCur->ixnCell ); + if( pCur->ix>=pPage->nCell ){ + return SQLITE_CORRUPT_PAGE(pPage); + } assert( cursorHoldsMutex(pCur) ); getCellInfo(pCur); @@ -69982,7 +71999,6 @@ SQLITE_PRIVATE int sqlite3BtreePayload(BtCursor *pCur, u32 offset, u32 amt, void assert( cursorHoldsMutex(pCur) ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->iPage>=0 && pCur->pPage ); - assert( pCur->ixpPage->nCell ); return accessPayload(pCur, offset, amt, (unsigned char*)pBuf, 0); } @@ -70044,7 +72060,7 @@ static const void *fetchPayload( assert( pCur->eState==CURSOR_VALID ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); assert( cursorOwnsBtShared(pCur) ); - assert( pCur->ixpPage->nCell ); + assert( pCur->ixpPage->nCell || CORRUPT_DB ); assert( pCur->info.nSize>0 ); assert( pCur->info.pPayload>pCur->pPage->aData || CORRUPT_DB ); assert( pCur->info.pPayloadpPage->aDataEnd ||CORRUPT_DB); @@ -70195,7 +72211,7 @@ static int moveToRoot(BtCursor *pCur){ while( --pCur->iPage ){ releasePageNotNull(pCur->apPage[pCur->iPage]); } - pCur->pPage = pCur->apPage[0]; + pRoot = pCur->pPage = pCur->apPage[0]; goto skip_init; } }else if( pCur->pgnoRoot==0 ){ @@ -70220,7 +72236,7 @@ static int moveToRoot(BtCursor *pCur){ pCur->curIntKey = pCur->pPage->intKey; } pRoot = pCur->pPage; - assert( pRoot->pgno==pCur->pgnoRoot ); + assert( pRoot->pgno==pCur->pgnoRoot || CORRUPT_DB ); /* If pCur->pKeyInfo is not NULL, then the caller that opened this cursor ** expected to open it on an index b-tree. Otherwise, if pKeyInfo is @@ -70242,7 +72258,6 @@ skip_init: pCur->info.nSize = 0; pCur->curFlags &= ~(BTCF_AtLast|BTCF_ValidNKey|BTCF_ValidOvfl); - pRoot = pCur->pPage; if( pRoot->nCell>0 ){ pCur->eState = CURSOR_VALID; }else if( !pRoot->leaf ){ @@ -70350,7 +72365,9 @@ SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ for(ii=0; iiiPage; ii++){ assert( pCur->aiIdx[ii]==pCur->apPage[ii]->nCell ); } - assert( pCur->ix==pCur->pPage->nCell-1 ); + assert( pCur->ix==pCur->pPage->nCell-1 || CORRUPT_DB ); + testcase( pCur->ix!=pCur->pPage->nCell-1 ); + /* ^-- dbsqlfuzz b92b72e4de80b5140c30ab71372ca719b8feb618 */ assert( pCur->pPage->leaf ); #endif *pRes = 0; @@ -70375,12 +72392,8 @@ SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ return rc; } -/* Move the cursor so that it points to an entry near the key -** specified by pIdxKey or intKey. Return a success code. -** -** For INTKEY tables, the intKey parameter is used. pIdxKey -** must be NULL. For index tables, pIdxKey is used and intKey -** is ignored. +/* Move the cursor so that it points to an entry in a table (a.k.a INTKEY) +** table near the key intKey. Return a success code. ** ** If an exact match is not found, then the cursor is always ** left pointing at a leaf page which would hold the entry if it @@ -70393,39 +72406,32 @@ SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ ** *pRes is as follows: ** ** *pRes<0 The cursor is left pointing at an entry that -** is smaller than intKey/pIdxKey or if the table is empty +** is smaller than intKey or if the table is empty ** and the cursor is therefore left point to nothing. ** ** *pRes==0 The cursor is left pointing at an entry that -** exactly matches intKey/pIdxKey. +** exactly matches intKey. ** ** *pRes>0 The cursor is left pointing at an entry that -** is larger than intKey/pIdxKey. -** -** For index tables, the pIdxKey->eqSeen field is set to 1 if there -** exists an entry in the table that exactly matches pIdxKey. +** is larger than intKey. */ -SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( +SQLITE_PRIVATE int sqlite3BtreeTableMoveto( BtCursor *pCur, /* The cursor to be moved */ - UnpackedRecord *pIdxKey, /* Unpacked index key */ i64 intKey, /* The table key */ int biasRight, /* If true, bias the search to the high end */ int *pRes /* Write search results here */ ){ int rc; - RecordCompare xRecordCompare; assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); assert( pRes ); - assert( (pIdxKey==0)==(pCur->pKeyInfo==0) ); - assert( pCur->eState!=CURSOR_VALID || (pIdxKey==0)==(pCur->curIntKey!=0) ); + assert( pCur->pKeyInfo==0 ); + assert( pCur->eState!=CURSOR_VALID || pCur->curIntKey!=0 ); /* If the cursor is already positioned at the point we are trying ** to move to, then just return without doing any work */ - if( pIdxKey==0 - && pCur->eState==CURSOR_VALID && (pCur->curFlags & BTCF_ValidNKey)!=0 - ){ + if( pCur->eState==CURSOR_VALID && (pCur->curFlags & BTCF_ValidNKey)!=0 ){ if( pCur->info.nKey==intKey ){ *pRes = 0; return SQLITE_OK; @@ -70447,9 +72453,7 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( if( pCur->info.nKey==intKey ){ return SQLITE_OK; } - }else if( rc==SQLITE_DONE ){ - rc = SQLITE_OK; - }else{ + }else if( rc!=SQLITE_DONE ){ return rc; } } @@ -70460,17 +72464,6 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( pCur->pBtree->nSeek++; /* Performance measurement during testing */ #endif - if( pIdxKey ){ - xRecordCompare = sqlite3VdbeFindCompare(pIdxKey); - pIdxKey->errCode = 0; - assert( pIdxKey->default_rc==1 - || pIdxKey->default_rc==0 - || pIdxKey->default_rc==-1 - ); - }else{ - xRecordCompare = 0; /* All keys are integers */ - } - rc = moveToRoot(pCur); if( rc ){ if( rc==SQLITE_EMPTY ){ @@ -70485,7 +72478,8 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( assert( pCur->eState==CURSOR_VALID ); assert( pCur->pPage->nCell > 0 ); assert( pCur->iPage==0 || pCur->apPage[0]->intKey==pCur->curIntKey ); - assert( pCur->curIntKey || pIdxKey ); + assert( pCur->curIntKey ); + for(;;){ int lwr, upr, idx, c; Pgno chldPg; @@ -70499,144 +72493,55 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( ** be the right kind (index or table) of b-tree page. Otherwise ** a moveToChild() or moveToRoot() call would have detected corruption. */ assert( pPage->nCell>0 ); - assert( pPage->intKey==(pIdxKey==0) ); + assert( pPage->intKey ); lwr = 0; upr = pPage->nCell-1; assert( biasRight==0 || biasRight==1 ); idx = upr>>(1-biasRight); /* idx = biasRight ? upr : (lwr+upr)/2; */ - pCur->ix = (u16)idx; - if( xRecordCompare==0 ){ - for(;;){ - i64 nCellKey; - pCell = findCellPastPtr(pPage, idx); - if( pPage->intKeyLeaf ){ - while( 0x80 <= *(pCell++) ){ - if( pCell>=pPage->aDataEnd ){ - return SQLITE_CORRUPT_PAGE(pPage); - } + for(;;){ + i64 nCellKey; + pCell = findCellPastPtr(pPage, idx); + if( pPage->intKeyLeaf ){ + while( 0x80 <= *(pCell++) ){ + if( pCell>=pPage->aDataEnd ){ + return SQLITE_CORRUPT_PAGE(pPage); } } - getVarint(pCell, (u64*)&nCellKey); - if( nCellKeyupr ){ c = -1; break; } - }else if( nCellKey>intKey ){ - upr = idx-1; - if( lwr>upr ){ c = +1; break; } - }else{ - assert( nCellKey==intKey ); - pCur->ix = (u16)idx; - if( !pPage->leaf ){ - lwr = idx; - goto moveto_next_layer; - }else{ - pCur->curFlags |= BTCF_ValidNKey; - pCur->info.nKey = nCellKey; - pCur->info.nSize = 0; - *pRes = 0; - return SQLITE_OK; - } - } - assert( lwr+upr>=0 ); - idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2; */ } - }else{ - for(;;){ - int nCell; /* Size of the pCell cell in bytes */ - pCell = findCellPastPtr(pPage, idx); - - /* The maximum supported page-size is 65536 bytes. This means that - ** the maximum number of record bytes stored on an index B-Tree - ** page is less than 16384 bytes and may be stored as a 2-byte - ** varint. This information is used to attempt to avoid parsing - ** the entire cell by checking for the cases where the record is - ** stored entirely within the b-tree page by inspecting the first - ** 2 bytes of the cell. - */ - nCell = pCell[0]; - if( nCell<=pPage->max1bytePayload ){ - /* This branch runs if the record-size field of the cell is a - ** single byte varint and the record fits entirely on the main - ** b-tree page. */ - testcase( pCell+nCell+1==pPage->aDataEnd ); - c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); - }else if( !(pCell[1] & 0x80) - && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal - ){ - /* The record-size field is a 2 byte varint and the record - ** fits entirely on the main b-tree page. */ - testcase( pCell+nCell+2==pPage->aDataEnd ); - c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); + getVarint(pCell, (u64*)&nCellKey); + if( nCellKeyupr ){ c = -1; break; } + }else if( nCellKey>intKey ){ + upr = idx-1; + if( lwr>upr ){ c = +1; break; } + }else{ + assert( nCellKey==intKey ); + pCur->ix = (u16)idx; + if( !pPage->leaf ){ + lwr = idx; + goto moveto_table_next_layer; }else{ - /* The record flows over onto one or more overflow pages. In - ** this case the whole cell needs to be parsed, a buffer allocated - ** and accessPayload() used to retrieve the record into the - ** buffer before VdbeRecordCompare() can be called. - ** - ** If the record is corrupt, the xRecordCompare routine may read - ** up to two varints past the end of the buffer. An extra 18 - ** bytes of padding is allocated at the end of the buffer in - ** case this happens. */ - void *pCellKey; - u8 * const pCellBody = pCell - pPage->childPtrSize; - const int nOverrun = 18; /* Size of the overrun padding */ - pPage->xParseCell(pPage, pCellBody, &pCur->info); - nCell = (int)pCur->info.nKey; - testcase( nCell<0 ); /* True if key size is 2^32 or more */ - testcase( nCell==0 ); /* Invalid key size: 0x80 0x80 0x00 */ - testcase( nCell==1 ); /* Invalid key size: 0x80 0x80 0x01 */ - testcase( nCell==2 ); /* Minimum legal index key size */ - if( nCell<2 || nCell/pCur->pBt->usableSize>pCur->pBt->nPage ){ - rc = SQLITE_CORRUPT_PAGE(pPage); - goto moveto_finish; - } - pCellKey = sqlite3Malloc( nCell+nOverrun ); - if( pCellKey==0 ){ - rc = SQLITE_NOMEM_BKPT; - goto moveto_finish; - } - pCur->ix = (u16)idx; - rc = accessPayload(pCur, 0, nCell, (unsigned char*)pCellKey, 0); - memset(((u8*)pCellKey)+nCell,0,nOverrun); /* Fix uninit warnings */ - pCur->curFlags &= ~BTCF_ValidOvfl; - if( rc ){ - sqlite3_free(pCellKey); - goto moveto_finish; - } - c = sqlite3VdbeRecordCompare(nCell, pCellKey, pIdxKey); - sqlite3_free(pCellKey); - } - assert( - (pIdxKey->errCode!=SQLITE_CORRUPT || c==0) - && (pIdxKey->errCode!=SQLITE_NOMEM || pCur->pBtree->db->mallocFailed) - ); - if( c<0 ){ - lwr = idx+1; - }else if( c>0 ){ - upr = idx-1; - }else{ - assert( c==0 ); + pCur->curFlags |= BTCF_ValidNKey; + pCur->info.nKey = nCellKey; + pCur->info.nSize = 0; *pRes = 0; - rc = SQLITE_OK; - pCur->ix = (u16)idx; - if( pIdxKey->errCode ) rc = SQLITE_CORRUPT_BKPT; - goto moveto_finish; + return SQLITE_OK; } - if( lwr>upr ) break; - assert( lwr+upr>=0 ); - idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2 */ } + assert( lwr+upr>=0 ); + idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2; */ } - assert( lwr==upr+1 || (pPage->intKey && !pPage->leaf) ); + assert( lwr==upr+1 || !pPage->leaf ); assert( pPage->isInit ); if( pPage->leaf ){ assert( pCur->ixpPage->nCell ); pCur->ix = (u16)idx; *pRes = c; rc = SQLITE_OK; - goto moveto_finish; + goto moveto_table_finish; } -moveto_next_layer: +moveto_table_next_layer: if( lwr>=pPage->nCell ){ chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]); }else{ @@ -70646,7 +72551,300 @@ moveto_next_layer: rc = moveToChild(pCur, chldPg); if( rc ) break; } -moveto_finish: +moveto_table_finish: + pCur->info.nSize = 0; + assert( (pCur->curFlags & BTCF_ValidOvfl)==0 ); + return rc; +} + +/* +** Compare the "idx"-th cell on the page the cursor pCur is currently +** pointing to to pIdxKey using xRecordCompare. Return negative or +** zero if the cell is less than or equal pIdxKey. Return positive +** if unknown. +** +** Return value negative: Cell at pCur[idx] less than pIdxKey +** +** Return value is zero: Cell at pCur[idx] equals pIdxKey +** +** Return value positive: Nothing is known about the relationship +** of the cell at pCur[idx] and pIdxKey. +** +** This routine is part of an optimization. It is always safe to return +** a positive value as that will cause the optimization to be skipped. +*/ +static int indexCellCompare( + BtCursor *pCur, + int idx, + UnpackedRecord *pIdxKey, + RecordCompare xRecordCompare +){ + MemPage *pPage = pCur->pPage; + int c; + int nCell; /* Size of the pCell cell in bytes */ + u8 *pCell = findCellPastPtr(pPage, idx); + + nCell = pCell[0]; + if( nCell<=pPage->max1bytePayload ){ + /* This branch runs if the record-size field of the cell is a + ** single byte varint and the record fits entirely on the main + ** b-tree page. */ + testcase( pCell+nCell+1==pPage->aDataEnd ); + c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); + }else if( !(pCell[1] & 0x80) + && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal + ){ + /* The record-size field is a 2 byte varint and the record + ** fits entirely on the main b-tree page. */ + testcase( pCell+nCell+2==pPage->aDataEnd ); + c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); + }else{ + /* If the record extends into overflow pages, do not attempt + ** the optimization. */ + c = 99; + } + return c; +} + +/* +** Return true (non-zero) if pCur is current pointing to the last +** page of a table. +*/ +static int cursorOnLastPage(BtCursor *pCur){ + int i; + assert( pCur->eState==CURSOR_VALID ); + for(i=0; iiPage; i++){ + MemPage *pPage = pCur->apPage[i]; + if( pCur->aiIdx[i]nCell ) return 0; + } + return 1; +} + +/* Move the cursor so that it points to an entry in an index table +** near the key pIdxKey. Return a success code. +** +** If an exact match is not found, then the cursor is always +** left pointing at a leaf page which would hold the entry if it +** were present. The cursor might point to an entry that comes +** before or after the key. +** +** An integer is written into *pRes which is the result of +** comparing the key with the entry to which the cursor is +** pointing. The meaning of the integer written into +** *pRes is as follows: +** +** *pRes<0 The cursor is left pointing at an entry that +** is smaller than pIdxKey or if the table is empty +** and the cursor is therefore left point to nothing. +** +** *pRes==0 The cursor is left pointing at an entry that +** exactly matches pIdxKey. +** +** *pRes>0 The cursor is left pointing at an entry that +** is larger than pIdxKey. +** +** The pIdxKey->eqSeen field is set to 1 if there +** exists an entry in the table that exactly matches pIdxKey. +*/ +SQLITE_PRIVATE int sqlite3BtreeIndexMoveto( + BtCursor *pCur, /* The cursor to be moved */ + UnpackedRecord *pIdxKey, /* Unpacked index key */ + int *pRes /* Write search results here */ +){ + int rc; + RecordCompare xRecordCompare; + + assert( cursorOwnsBtShared(pCur) ); + assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); + assert( pRes ); + assert( pCur->pKeyInfo!=0 ); + +#ifdef SQLITE_DEBUG + pCur->pBtree->nSeek++; /* Performance measurement during testing */ +#endif + + xRecordCompare = sqlite3VdbeFindCompare(pIdxKey); + pIdxKey->errCode = 0; + assert( pIdxKey->default_rc==1 + || pIdxKey->default_rc==0 + || pIdxKey->default_rc==-1 + ); + + + /* Check to see if we can skip a lot of work. Two cases: + ** + ** (1) If the cursor is already pointing to the very last cell + ** in the table and the pIdxKey search key is greater than or + ** equal to that last cell, then no movement is required. + ** + ** (2) If the cursor is on the last page of the table and the first + ** cell on that last page is less than or equal to the pIdxKey + ** search key, then we can start the search on the current page + ** without needing to go back to root. + */ + if( pCur->eState==CURSOR_VALID + && pCur->pPage->leaf + && cursorOnLastPage(pCur) + ){ + int c; + if( pCur->ix==pCur->pPage->nCell-1 + && (c = indexCellCompare(pCur, pCur->ix, pIdxKey, xRecordCompare))<=0 + && pIdxKey->errCode==SQLITE_OK + ){ + *pRes = c; + return SQLITE_OK; /* Cursor already pointing at the correct spot */ + } + if( pCur->iPage>0 + && indexCellCompare(pCur, 0, pIdxKey, xRecordCompare)<=0 + && pIdxKey->errCode==SQLITE_OK + ){ + pCur->curFlags &= ~BTCF_ValidOvfl; + if( !pCur->pPage->isInit ){ + return SQLITE_CORRUPT_BKPT; + } + goto bypass_moveto_root; /* Start search on the current page */ + } + pIdxKey->errCode = SQLITE_OK; + } + + rc = moveToRoot(pCur); + if( rc ){ + if( rc==SQLITE_EMPTY ){ + assert( pCur->pgnoRoot==0 || pCur->pPage->nCell==0 ); + *pRes = -1; + return SQLITE_OK; + } + return rc; + } + +bypass_moveto_root: + assert( pCur->pPage ); + assert( pCur->pPage->isInit ); + assert( pCur->eState==CURSOR_VALID ); + assert( pCur->pPage->nCell > 0 ); + assert( pCur->curIntKey==0 ); + assert( pIdxKey!=0 ); + for(;;){ + int lwr, upr, idx, c; + Pgno chldPg; + MemPage *pPage = pCur->pPage; + u8 *pCell; /* Pointer to current cell in pPage */ + + /* pPage->nCell must be greater than zero. If this is the root-page + ** the cursor would have been INVALID above and this for(;;) loop + ** not run. If this is not the root-page, then the moveToChild() routine + ** would have already detected db corruption. Similarly, pPage must + ** be the right kind (index or table) of b-tree page. Otherwise + ** a moveToChild() or moveToRoot() call would have detected corruption. */ + assert( pPage->nCell>0 ); + assert( pPage->intKey==0 ); + lwr = 0; + upr = pPage->nCell-1; + idx = upr>>1; /* idx = (lwr+upr)/2; */ + for(;;){ + int nCell; /* Size of the pCell cell in bytes */ + pCell = findCellPastPtr(pPage, idx); + + /* The maximum supported page-size is 65536 bytes. This means that + ** the maximum number of record bytes stored on an index B-Tree + ** page is less than 16384 bytes and may be stored as a 2-byte + ** varint. This information is used to attempt to avoid parsing + ** the entire cell by checking for the cases where the record is + ** stored entirely within the b-tree page by inspecting the first + ** 2 bytes of the cell. + */ + nCell = pCell[0]; + if( nCell<=pPage->max1bytePayload ){ + /* This branch runs if the record-size field of the cell is a + ** single byte varint and the record fits entirely on the main + ** b-tree page. */ + testcase( pCell+nCell+1==pPage->aDataEnd ); + c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); + }else if( !(pCell[1] & 0x80) + && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal + ){ + /* The record-size field is a 2 byte varint and the record + ** fits entirely on the main b-tree page. */ + testcase( pCell+nCell+2==pPage->aDataEnd ); + c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); + }else{ + /* The record flows over onto one or more overflow pages. In + ** this case the whole cell needs to be parsed, a buffer allocated + ** and accessPayload() used to retrieve the record into the + ** buffer before VdbeRecordCompare() can be called. + ** + ** If the record is corrupt, the xRecordCompare routine may read + ** up to two varints past the end of the buffer. An extra 18 + ** bytes of padding is allocated at the end of the buffer in + ** case this happens. */ + void *pCellKey; + u8 * const pCellBody = pCell - pPage->childPtrSize; + const int nOverrun = 18; /* Size of the overrun padding */ + pPage->xParseCell(pPage, pCellBody, &pCur->info); + nCell = (int)pCur->info.nKey; + testcase( nCell<0 ); /* True if key size is 2^32 or more */ + testcase( nCell==0 ); /* Invalid key size: 0x80 0x80 0x00 */ + testcase( nCell==1 ); /* Invalid key size: 0x80 0x80 0x01 */ + testcase( nCell==2 ); /* Minimum legal index key size */ + if( nCell<2 || nCell/pCur->pBt->usableSize>pCur->pBt->nPage ){ + rc = SQLITE_CORRUPT_PAGE(pPage); + goto moveto_index_finish; + } + pCellKey = sqlite3Malloc( nCell+nOverrun ); + if( pCellKey==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto moveto_index_finish; + } + pCur->ix = (u16)idx; + rc = accessPayload(pCur, 0, nCell, (unsigned char*)pCellKey, 0); + memset(((u8*)pCellKey)+nCell,0,nOverrun); /* Fix uninit warnings */ + pCur->curFlags &= ~BTCF_ValidOvfl; + if( rc ){ + sqlite3_free(pCellKey); + goto moveto_index_finish; + } + c = sqlite3VdbeRecordCompare(nCell, pCellKey, pIdxKey); + sqlite3_free(pCellKey); + } + assert( + (pIdxKey->errCode!=SQLITE_CORRUPT || c==0) + && (pIdxKey->errCode!=SQLITE_NOMEM || pCur->pBtree->db->mallocFailed) + ); + if( c<0 ){ + lwr = idx+1; + }else if( c>0 ){ + upr = idx-1; + }else{ + assert( c==0 ); + *pRes = 0; + rc = SQLITE_OK; + pCur->ix = (u16)idx; + if( pIdxKey->errCode ) rc = SQLITE_CORRUPT_BKPT; + goto moveto_index_finish; + } + if( lwr>upr ) break; + assert( lwr+upr>=0 ); + idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2 */ + } + assert( lwr==upr+1 || (pPage->intKey && !pPage->leaf) ); + assert( pPage->isInit ); + if( pPage->leaf ){ + assert( pCur->ixpPage->nCell || CORRUPT_DB ); + pCur->ix = (u16)idx; + *pRes = c; + rc = SQLITE_OK; + goto moveto_index_finish; + } + if( lwr>=pPage->nCell ){ + chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]); + }else{ + chldPg = get4byte(findCell(pPage, lwr)); + } + pCur->ix = (u16)lwr; + rc = moveToChild(pCur, chldPg); + if( rc ) break; + } +moveto_index_finish: pCur->info.nSize = 0; assert( (pCur->curFlags & BTCF_ValidOvfl)==0 ); return rc; @@ -70747,16 +72945,6 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur){ return SQLITE_CORRUPT_BKPT; } - /* If the database file is corrupt, it is possible for the value of idx - ** to be invalid here. This can only occur if a second cursor modifies - ** the page while cursor pCur is holding a reference to it. Which can - ** only happen if the database is corrupt in such a way as to link the - ** page into more than one b-tree structure. - ** - ** Update 2019-12-23: appears to long longer be possible after the - ** addition of anotherValidCursor() condition on balance_deeper(). */ - harmless( idx>pPage->nCell ); - if( idx>=pPage->nCell ){ if( !pPage->leaf ){ rc = moveToChild(pCur, get4byte(&pPage->aData[pPage->hdrOffset+8])); @@ -71117,7 +73305,7 @@ static int allocateBtreePage( iPage = get4byte(&aData[8+closest*4]); testcase( iPage==mxPage ); - if( iPage>mxPage ){ + if( iPage>mxPage || iPage<2 ){ rc = SQLITE_CORRUPT_PGNO(iTrunk); goto end_allocate_page; } @@ -71373,10 +73561,9 @@ static void freePage(MemPage *pPage, int *pRC){ } /* -** Free any overflow pages associated with the given Cell. Store -** size information about the cell in pInfo. +** Free the overflow pages associated with the given Cell. */ -static int clearCell( +static SQLITE_NOINLINE int clearCellOverflow( MemPage *pPage, /* The page that contains the Cell */ unsigned char *pCell, /* First byte of the Cell */ CellInfo *pInfo /* Size information about the cell */ @@ -71388,10 +73575,7 @@ static int clearCell( u32 ovflPageSize; assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - pPage->xParseCell(pPage, pCell, pInfo); - if( pInfo->nLocal==pInfo->nPayload ){ - return SQLITE_OK; /* No overflow pages. Return without doing anything */ - } + assert( pInfo->nLocal!=pInfo->nPayload ); testcase( pCell + pInfo->nSize == pPage->aDataEnd ); testcase( pCell + (pInfo->nSize-1) == pPage->aDataEnd ); if( pCell + pInfo->nSize > pPage->aDataEnd ){ @@ -71447,6 +73631,21 @@ static int clearCell( return SQLITE_OK; } +/* Call xParseCell to compute the size of a cell. If the cell contains +** overflow, then invoke cellClearOverflow to clear out that overflow. +** STore the result code (SQLITE_OK or some error code) in rc. +** +** Implemented as macro to force inlining for performance. +*/ +#define BTREE_CLEAR_CELL(rc, pPage, pCell, sInfo) \ + pPage->xParseCell(pPage, pCell, &sInfo); \ + if( sInfo.nLocal!=sInfo.nPayload ){ \ + rc = clearCellOverflow(pPage, pCell, &sInfo); \ + }else{ \ + rc = SQLITE_OK; \ + } + + /* ** Create the byte sequence used to represent a cell on page pPage ** and write that byte sequence into pCell[]. Overflow pages are @@ -71657,16 +73856,24 @@ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){ int hdr; /* Beginning of the header. 0 most pages. 100 page 1 */ if( *pRC ) return; - assert( idx>=0 && idxnCell ); + assert( idx>=0 ); + assert( idxnCell ); assert( CORRUPT_DB || sz==cellSize(pPage, idx) ); assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( pPage->nFree>=0 ); data = pPage->aData; ptr = &pPage->aCellIdx[2*idx]; + assert( pPage->pBt->usableSize > (u32)(ptr-data) ); pc = get2byte(ptr); hdr = pPage->hdrOffset; - testcase( pc==get2byte(&data[hdr+5]) ); +#if 0 /* Not required. Omit for efficiency */ + if( pcnCell*2 ){ + *pRC = SQLITE_CORRUPT_BKPT; + return; + } +#endif + testcase( pc==(u32)get2byte(&data[hdr+5]) ); testcase( pc+sz==pPage->pBt->usableSize ); if( pc+sz > pPage->pBt->usableSize ){ *pRC = SQLITE_CORRUPT_BKPT; @@ -71958,7 +74165,7 @@ static int rebuildPage( assert( i(u32)usableSize) ){ j = 0; } + if( j>(u32)usableSize ){ j = 0; } memcpy(&pTmp[j], &aData[j], usableSize - j); for(k=0; pCArray->ixNx[k]<=i && ALWAYS(kapCell[i]; u16 sz = pCArray->szCell[i]; assert( sz>0 ); - if( SQLITE_WITHIN(pCell,aData,pEnd) ){ + if( SQLITE_WITHIN(pCell,aData+j,pEnd) ){ if( ((uptr)(pCell+sz))>(uptr)pEnd ) return SQLITE_CORRUPT_BKPT; pCell = &pTmp[pCell - aData]; }else if( (uptr)(pCell+sz)>(uptr)pSrcEnd @@ -71982,9 +74189,8 @@ static int rebuildPage( put2byte(pCellptr, (pData - aData)); pCellptr += 2; if( pData < pCellptr ) return SQLITE_CORRUPT_BKPT; - memcpy(pData, pCell, sz); + memmove(pData, pCell, sz); assert( sz==pPg->xCellSize(pPg, pCell) || CORRUPT_DB ); - testcase( sz!=pPg->xCellSize(pPg,pCell) ) i++; if( i>=iEnd ) break; if( pCArray->ixNx[k]<=i ){ @@ -72123,7 +74329,9 @@ static int pageFreeArray( } pFree = pCell; szFree = sz; - if( pFree+sz>pEnd ) return 0; + if( pFree+sz>pEnd ){ + return 0; + } }else{ pFree = pCell; szFree += sz; @@ -72188,6 +74396,7 @@ static int editPage( pData = &aData[get2byteNotZero(&aData[hdr+5])]; if( pDatapPg->aDataEnd ) goto editpage_fail; /* Add cells to the start of the page */ if( iNewpBt; assert( sqlite3_mutex_held(pBt->mutex) ); assert( sqlite3PagerIswriteable(pParent->pDbPage) ); @@ -72618,6 +74826,7 @@ static int balance_nonroot( goto balance_cleanup; } } + nMaxCells += apOld[i]->nCell + ArraySize(pParent->apOvfl); if( (i--)==0 ) break; if( pParent->nOverflow && i+nxDiv==pParent->aiOvfl[0] ){ @@ -72659,7 +74868,6 @@ static int balance_nonroot( /* Make nMaxCells a multiple of 4 in order to preserve 8-byte ** alignment */ - nMaxCells = nOld*(MX_CELL(pBt) + ArraySize(pParent->apOvfl)); nMaxCells = (nMaxCells + 3)&~3; /* @@ -72776,7 +74984,7 @@ static int balance_nonroot( b.szCell[b.nCell] = b.szCell[b.nCell] - leafCorrection; if( !pOld->leaf ){ assert( leafCorrection==0 ); - assert( pOld->hdrOffset==0 ); + assert( pOld->hdrOffset==0 || CORRUPT_DB ); /* The right pointer of the child page pOld becomes the left ** pointer of the divider cell */ memcpy(b.apCell[b.nCell], &pOld->aData[8], 4); @@ -72942,7 +75150,9 @@ static int balance_nonroot( apOld[i] = 0; rc = sqlite3PagerWrite(pNew->pDbPage); nNew++; - if( sqlite3PagerPageRefcount(pNew->pDbPage)!=1+(i==(iParentIdx-nxDiv)) ){ + if( sqlite3PagerPageRefcount(pNew->pDbPage)!=1+(i==(iParentIdx-nxDiv)) + && rc==SQLITE_OK + ){ rc = SQLITE_CORRUPT_BKPT; } if( rc ) goto balance_cleanup; @@ -73099,6 +75309,7 @@ static int balance_nonroot( u8 *pCell; u8 *pTemp; int sz; + u8 *pSrcEnd; MemPage *pNew = apNew[i]; j = cntNew[i]; @@ -73142,6 +75353,12 @@ static int balance_nonroot( iOvflSpace += sz; assert( sz<=pBt->maxLocal+23 ); assert( iOvflSpace <= (int)pBt->pageSize ); + for(k=0; b.ixNx[k]<=j && ALWAYS(kpgno, &rc); if( rc!=SQLITE_OK ) goto balance_cleanup; assert( sqlite3PagerIswriteable(pParent->pDbPage) ); @@ -73355,7 +75572,7 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){ ** Return SQLITE_CORRUPT if any cursor other than pCur is currently valid ** on the same B-tree as pCur. ** -** This can if a database is corrupt with two or more SQL tables +** This can occur if a database is corrupt with two or more SQL tables ** pointing to the same b-tree. If an insert occurs on one SQL table ** and causes a BEFORE TRIGGER to do a secondary insert on the other SQL ** table linked to the same b-tree. If the secondary insert causes a @@ -73387,7 +75604,6 @@ static int anotherValidCursor(BtCursor *pCur){ */ static int balance(BtCursor *pCur){ int rc = SQLITE_OK; - const int nMin = pCur->pBt->usableSize * 2 / 3; u8 aBalanceQuickSpace[13]; u8 *pFree = 0; @@ -73399,7 +75615,11 @@ static int balance(BtCursor *pCur){ MemPage *pPage = pCur->pPage; if( NEVER(pPage->nFree<0) && btreeComputeFreeSpace(pPage) ) break; - if( pPage->nOverflow==0 && pPage->nFree<=nMin ){ + if( pPage->nOverflow==0 && pPage->nFree*3<=(int)pCur->pBt->usableSize*2 ){ + /* No rebalance required as long as: + ** (1) There are no overflow cells + ** (2) The amount of free space on the page is less than 2/3rds of + ** the total usable space on the page. */ break; }else if( (iPage = pCur->iPage)==0 ){ if( pPage->nOverflow && (rc = anotherValidCursor(pCur))==SQLITE_OK ){ @@ -73584,7 +75804,7 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ do{ rc = btreeGetPage(pBt, ovflPgno, &pPage, 0); if( rc ) return rc; - if( sqlite3PagerPageRefcount(pPage->pDbPage)!=1 ){ + if( sqlite3PagerPageRefcount(pPage->pDbPage)!=1 || pPage->isInit ){ rc = SQLITE_CORRUPT_BKPT; }else{ if( iOffset+ovflPageSize<(u32)nTotal ){ @@ -73619,7 +75839,7 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ ** pX.pData,nData,nZero fields must be zero. ** ** If the seekResult parameter is non-zero, then a successful call to -** MovetoUnpacked() to seek cursor pCur to (pKey,nKey) has already +** sqlite3BtreeIndexMoveto() to seek cursor pCur to (pKey,nKey) has already ** been performed. In other words, if seekResult!=0 then the cursor ** is currently pointing to a cell that will be adjacent to the cell ** to be inserted. If seekResult<0 then pCur points to a cell that is @@ -73637,7 +75857,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( BtCursor *pCur, /* Insert data into the table of this cursor */ const BtreePayload *pX, /* Content of the row to be inserted */ int flags, /* True if this is likely an append */ - int seekResult /* Result of prior MovetoUnpacked() call */ + int seekResult /* Result of prior IndexMoveto() call */ ){ int rc; int loc = seekResult; /* -1: before desired location +1: after */ @@ -73652,24 +75872,6 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( assert( (flags & (BTREE_SAVEPOSITION|BTREE_APPEND|BTREE_PREFORMAT))==flags ); assert( (flags & BTREE_PREFORMAT)==0 || seekResult || pCur->pKeyInfo==0 ); - if( pCur->eState==CURSOR_FAULT ){ - assert( pCur->skipNext!=SQLITE_OK ); - return pCur->skipNext; - } - - assert( cursorOwnsBtShared(pCur) ); - assert( (pCur->curFlags & BTCF_WriteFlag)!=0 - && pBt->inTransaction==TRANS_WRITE - && (pBt->btsFlags & BTS_READ_ONLY)==0 ); - assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) ); - - /* Assert that the caller has been consistent. If this cursor was opened - ** expecting an index b-tree, then the caller should be inserting blob - ** keys with no associated data. If the cursor was opened expecting an - ** intkey table, the caller should be inserting integer keys with a - ** blob of associated data. */ - assert( (flags & BTREE_PREFORMAT) || (pX->pKey==0)==(pCur->pKeyInfo==0) ); - /* Save the positions of any other cursors open on this table. ** ** In some cases, the call to btreeMoveto() below is a no-op. For @@ -73684,13 +75886,46 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( if( pCur->curFlags & BTCF_Multiple ){ rc = saveAllCursors(pBt, pCur->pgnoRoot, pCur); if( rc ) return rc; + if( loc && pCur->iPage<0 ){ + /* This can only happen if the schema is corrupt such that there is more + ** than one table or index with the same root page as used by the cursor. + ** Which can only happen if the SQLITE_NoSchemaError flag was set when + ** the schema was loaded. This cannot be asserted though, as a user might + ** set the flag, load the schema, and then unset the flag. */ + return SQLITE_CORRUPT_BKPT; + } } + /* Ensure that the cursor is not in the CURSOR_FAULT state and that it + ** points to a valid cell. + */ + if( pCur->eState>=CURSOR_REQUIRESEEK ){ + testcase( pCur->eState==CURSOR_REQUIRESEEK ); + testcase( pCur->eState==CURSOR_FAULT ); + rc = moveToRoot(pCur); + if( rc && rc!=SQLITE_EMPTY ) return rc; + } + + assert( cursorOwnsBtShared(pCur) ); + assert( (pCur->curFlags & BTCF_WriteFlag)!=0 + && pBt->inTransaction==TRANS_WRITE + && (pBt->btsFlags & BTS_READ_ONLY)==0 ); + assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) ); + + /* Assert that the caller has been consistent. If this cursor was opened + ** expecting an index b-tree, then the caller should be inserting blob + ** keys with no associated data. If the cursor was opened expecting an + ** intkey table, the caller should be inserting integer keys with a + ** blob of associated data. */ + assert( (flags & BTREE_PREFORMAT) || (pX->pKey==0)==(pCur->pKeyInfo==0) ); + if( pCur->pKeyInfo==0 ){ assert( pX->pKey==0 ); /* If this is an insert into a table b-tree, invalidate any incrblob ** cursors open on the row being replaced */ - invalidateIncrblobCursors(p, pCur->pgnoRoot, pX->nKey, 0); + if( p->hasIncrblobCur ){ + invalidateIncrblobCursors(p, pCur->pgnoRoot, pX->nKey, 0); + } /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing ** to a row with the same key as the new entry being inserted. @@ -73723,7 +75958,8 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( ** to an adjacent cell. Move the cursor so that it is pointing either ** to the cell to be overwritten or an adjacent cell. */ - rc = sqlite3BtreeMovetoUnpacked(pCur, 0, pX->nKey, flags!=0, &loc); + rc = sqlite3BtreeTableMoveto(pCur, pX->nKey, + (flags & BTREE_APPEND)!=0, &loc); if( rc ) return rc; } }else{ @@ -73746,13 +75982,11 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( r.aMem = pX->aMem; r.nField = pX->nMem; r.default_rc = 0; - r.errCode = 0; - r.r1 = 0; - r.r2 = 0; r.eqSeen = 0; - rc = sqlite3BtreeMovetoUnpacked(pCur, &r, 0, flags!=0, &loc); + rc = sqlite3BtreeIndexMoveto(pCur, &r, &loc); }else{ - rc = btreeMoveto(pCur, pX->pKey, pX->nKey, flags!=0, &loc); + rc = btreeMoveto(pCur, pX->pKey, pX->nKey, + (flags & BTREE_APPEND)!=0, &loc); } if( rc ) return rc; } @@ -73771,17 +76005,16 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( return btreeOverwriteCell(pCur, &x2); } } - } assert( pCur->eState==CURSOR_VALID - || (pCur->eState==CURSOR_INVALID && loc) - || CORRUPT_DB ); + || (pCur->eState==CURSOR_INVALID && loc) ); pPage = pCur->pPage; assert( pPage->intKey || pX->nKey>=0 || (flags & BTREE_PREFORMAT) ); assert( pPage->leaf || !pPage->intKey ); if( pPage->nFree<0 ){ - if( pCur->eState>CURSOR_INVALID ){ + if( NEVER(pCur->eState>CURSOR_INVALID) ){ + /* ^^^^^--- due to the moveToRoot() call above */ rc = SQLITE_CORRUPT_BKPT; }else{ rc = btreeComputeFreeSpace(pPage); @@ -73792,7 +76025,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( TRACE(("INSERT: table=%d nkey=%lld ndata=%d page=%d %s\n", pCur->pgnoRoot, pX->nKey, pX->nData, pPage->pgno, loc==0 ? "overwrite" : "new entry")); - assert( pPage->isInit ); + assert( pPage->isInit || CORRUPT_DB ); newCell = pBt->pTmpSpace; assert( newCell!=0 ); if( flags & BTREE_PREFORMAT ){ @@ -73816,7 +76049,10 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( idx = pCur->ix; if( loc==0 ){ CellInfo info; - assert( idxnCell ); + assert( idx>=0 ); + if( idx>=pPage->nCell ){ + return SQLITE_CORRUPT_BKPT; + } rc = sqlite3PagerWrite(pPage->pDbPage); if( rc ){ goto end_insert; @@ -73825,7 +76061,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( if( !pPage->leaf ){ memcpy(newCell, oldCell, 4); } - rc = clearCell(pPage, oldCell, &info); + BTREE_CLEAR_CELL(rc, pPage, oldCell, info); testcase( pCur->curFlags & BTCF_ValidOvfl ); invalidateOverflowCache(pCur); if( info.nSize==szNew && info.nLocal==info.nPayload @@ -73940,7 +76176,11 @@ SQLITE_PRIVATE int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 u32 nRem; /* Bytes of data still to copy */ getCellInfo(pSrc); - aOut += putVarint32(aOut, pSrc->info.nPayload); + if( pSrc->info.nPayload<0x80 ){ + *(aOut++) = pSrc->info.nPayload; + }else{ + aOut += sqlite3PutVarint(aOut, pSrc->info.nPayload); + } if( pDest->pKeyInfo==0 ) aOut += putVarint(aOut, iKey); nIn = pSrc->info.nLocal; aIn = pSrc->info.pPayload; @@ -73998,7 +76238,7 @@ SQLITE_PRIVATE int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 } }while( rc==SQLITE_OK && nOut>0 ); - if( rc==SQLITE_OK && nRem>0 ){ + if( rc==SQLITE_OK && nRem>0 && ALWAYS(pPgnoOut) ){ Pgno pgnoNew; MemPage *pNew = 0; rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0); @@ -74044,14 +76284,13 @@ SQLITE_PRIVATE int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ Btree *p = pCur->pBtree; BtShared *pBt = p->pBt; - int rc; /* Return code */ - MemPage *pPage; /* Page to delete cell from */ - unsigned char *pCell; /* Pointer to cell to delete */ - int iCellIdx; /* Index of cell to delete */ - int iCellDepth; /* Depth of node containing pCell */ - CellInfo info; /* Size of the cell being deleted */ - int bSkipnext = 0; /* Leaf cursor in SKIPNEXT state */ - u8 bPreserve = flags & BTREE_SAVEPOSITION; /* Keep cursor valid */ + int rc; /* Return code */ + MemPage *pPage; /* Page to delete cell from */ + unsigned char *pCell; /* Pointer to cell to delete */ + int iCellIdx; /* Index of cell to delete */ + int iCellDepth; /* Depth of node containing pCell */ + CellInfo info; /* Size of the cell being deleted */ + u8 bPreserve; /* Keep cursor valid. 2 for CURSOR_SKIPNEXT */ assert( cursorOwnsBtShared(pCur) ); assert( pBt->inTransaction==TRANS_WRITE ); @@ -74060,30 +76299,49 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) ); assert( !hasReadConflicts(p, pCur->pgnoRoot) ); assert( (flags & ~(BTREE_SAVEPOSITION | BTREE_AUXDELETE))==0 ); - if( pCur->eState==CURSOR_REQUIRESEEK ){ - rc = btreeRestoreCursorPosition(pCur); - if( rc ) return rc; + if( pCur->eState!=CURSOR_VALID ){ + if( pCur->eState>=CURSOR_REQUIRESEEK ){ + rc = btreeRestoreCursorPosition(pCur); + assert( rc!=SQLITE_OK || CORRUPT_DB || pCur->eState==CURSOR_VALID ); + if( rc || pCur->eState!=CURSOR_VALID ) return rc; + }else{ + return SQLITE_CORRUPT_BKPT; + } } assert( pCur->eState==CURSOR_VALID ); iCellDepth = pCur->iPage; iCellIdx = pCur->ix; pPage = pCur->pPage; + if( pPage->nCell<=iCellIdx ){ + return SQLITE_CORRUPT_BKPT; + } pCell = findCell(pPage, iCellIdx); - if( pPage->nFree<0 && btreeComputeFreeSpace(pPage) ) return SQLITE_CORRUPT; + if( pPage->nFree<0 && btreeComputeFreeSpace(pPage) ){ + return SQLITE_CORRUPT_BKPT; + } - /* If the bPreserve flag is set to true, then the cursor position must + /* If the BTREE_SAVEPOSITION bit is on, then the cursor position must ** be preserved following this delete operation. If the current delete ** will cause a b-tree rebalance, then this is done by saving the cursor ** key and leaving the cursor in CURSOR_REQUIRESEEK state before ** returning. ** - ** Or, if the current delete will not cause a rebalance, then the cursor + ** If the current delete will not cause a rebalance, then the cursor ** will be left in CURSOR_SKIPNEXT state pointing to the entry immediately - ** before or after the deleted entry. In this case set bSkipnext to true. */ + ** before or after the deleted entry. + ** + ** The bPreserve value records which path is required: + ** + ** bPreserve==0 Not necessary to save the cursor position + ** bPreserve==1 Use CURSOR_REQUIRESEEK to save the cursor position + ** bPreserve==2 Cursor won't move. Set CURSOR_SKIPNEXT. + */ + bPreserve = (flags & BTREE_SAVEPOSITION)!=0; if( bPreserve ){ if( !pPage->leaf - || (pPage->nFree+cellSizePtr(pPage,pCell)+2)>(int)(pBt->usableSize*2/3) + || (pPage->nFree+pPage->xCellSize(pPage,pCell)+2) > + (int)(pBt->usableSize*2/3) || pPage->nCell==1 /* See dbfuzz001.test for a test case */ ){ /* A b-tree rebalance will be required after deleting this entry. @@ -74091,7 +76349,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ rc = saveCursorKey(pCur); if( rc ) return rc; }else{ - bSkipnext = 1; + bPreserve = 2; } } @@ -74117,7 +76375,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ /* If this is a delete operation to remove a row from a table b-tree, ** invalidate any incrblob cursors open on the row being deleted. */ - if( pCur->pKeyInfo==0 ){ + if( pCur->pKeyInfo==0 && p->hasIncrblobCur ){ invalidateIncrblobCursors(p, pCur->pgnoRoot, pCur->info.nKey, 0); } @@ -74126,7 +76384,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ ** itself from within the page. */ rc = sqlite3PagerWrite(pPage->pDbPage); if( rc ) return rc; - rc = clearCell(pPage, pCell, &info); + BTREE_CLEAR_CELL(rc, pPage, pCell, info); dropCell(pPage, iCellIdx, info.nSize, &rc); if( rc ) return rc; @@ -74179,7 +76437,15 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ ** been corrected, so be it. Otherwise, after balancing the leaf node, ** walk the cursor up the tree to the internal node and balance it as ** well. */ - rc = balance(pCur); + assert( pCur->pPage->nOverflow==0 ); + assert( pCur->pPage->nFree>=0 ); + if( pCur->pPage->nFree*3<=(int)pCur->pBt->usableSize*2 ){ + /* Optimization: If the free space is less than 2/3rds of the page, + ** then balance() will always be a no-op. No need to invoke it. */ + rc = SQLITE_OK; + }else{ + rc = balance(pCur); + } if( rc==SQLITE_OK && pCur->iPage>iCellDepth ){ releasePageNotNull(pCur->pPage); pCur->iPage--; @@ -74191,8 +76457,8 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ } if( rc==SQLITE_OK ){ - if( bSkipnext ){ - assert( bPreserve && (pCur->iPage==iCellDepth || CORRUPT_DB) ); + if( bPreserve>1 ){ + assert( (pCur->iPage==iCellDepth || CORRUPT_DB) ); assert( pPage==pCur->pPage || CORRUPT_DB ); assert( (pPage->nCell>0 || CORRUPT_DB) && iCellIdx<=pPage->nCell ); pCur->eState = CURSOR_SKIPNEXT; @@ -74386,7 +76652,7 @@ static int clearDatabasePage( BtShared *pBt, /* The BTree that contains the table */ Pgno pgno, /* Page number to clear */ int freePageFlag, /* Deallocate page if true */ - int *pnChange /* Add number of Cells freed to this counter */ + i64 *pnChange /* Add number of Cells freed to this counter */ ){ MemPage *pPage; int rc; @@ -74401,11 +76667,12 @@ static int clearDatabasePage( } rc = getAndInitPage(pBt, pgno, &pPage, 0, 0); if( rc ) return rc; - if( pPage->bBusy ){ + if( (pBt->openFlags & BTREE_SINGLE)==0 + && sqlite3PagerPageRefcount(pPage->pDbPage) != (1 + (pgno==1)) + ){ rc = SQLITE_CORRUPT_BKPT; goto cleardatabasepage_out; } - pPage->bBusy = 1; hdr = pPage->hdrOffset; for(i=0; inCell; i++){ pCell = findCell(pPage, i); @@ -74413,14 +76680,15 @@ static int clearDatabasePage( rc = clearDatabasePage(pBt, get4byte(pCell), 1, pnChange); if( rc ) goto cleardatabasepage_out; } - rc = clearCell(pPage, pCell, &info); + BTREE_CLEAR_CELL(rc, pPage, pCell, info); if( rc ) goto cleardatabasepage_out; } if( !pPage->leaf ){ rc = clearDatabasePage(pBt, get4byte(&pPage->aData[hdr+8]), 1, pnChange); if( rc ) goto cleardatabasepage_out; - }else if( pnChange ){ - assert( pPage->intKey || CORRUPT_DB ); + if( pPage->intKey ) pnChange = 0; + } + if( pnChange ){ testcase( !pPage->intKey ); *pnChange += pPage->nCell; } @@ -74431,7 +76699,6 @@ static int clearDatabasePage( } cleardatabasepage_out: - pPage->bBusy = 0; releasePage(pPage); return rc; } @@ -74445,11 +76712,10 @@ cleardatabasepage_out: ** read cursors on the table. Open write cursors are moved to the ** root of the table. ** -** If pnChange is not NULL, then table iTable must be an intkey table. The -** integer value pointed to by pnChange is incremented by the number of -** entries in the table. +** If pnChange is not NULL, then the integer value pointed to by pnChange +** is incremented by the number of entries in the table. */ -SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree *p, int iTable, int *pnChange){ +SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree *p, int iTable, i64 *pnChange){ int rc; BtShared *pBt = p->pBt; sqlite3BtreeEnter(p); @@ -74461,7 +76727,9 @@ SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree *p, int iTable, int *pnChange){ /* Invalidate all incrblob cursors open on table iTable (assuming iTable ** is the root of a table b-tree - if it is not, the following call is ** a no-op). */ - invalidateIncrblobCursors(p, (Pgno)iTable, 0, 1); + if( p->hasIncrblobCur ){ + invalidateIncrblobCursors(p, (Pgno)iTable, 0, 1); + } rc = clearDatabasePage(pBt, (Pgno)iTable, 0, pnChange); } sqlite3BtreeLeave(p); @@ -74509,10 +76777,10 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ return SQLITE_CORRUPT_BKPT; } - rc = btreeGetPage(pBt, (Pgno)iTable, &pPage, 0); - if( rc ) return rc; rc = sqlite3BtreeClearTable(p, iTable, 0); - if( rc ){ + if( rc ) return rc; + rc = btreeGetPage(pBt, (Pgno)iTable, &pPage, 0); + if( NEVER(rc) ){ releasePage(pPage); return rc; } @@ -75780,14 +78048,13 @@ static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){ if( i==1 ){ Parse sParse; int rc = 0; - memset(&sParse, 0, sizeof(sParse)); - sParse.db = pDb; + sqlite3ParseObjectInit(&sParse,pDb); if( sqlite3OpenTempDatabase(&sParse) ){ sqlite3ErrorWithMsg(pErrorDb, sParse.rc, "%s", sParse.zErrMsg); rc = SQLITE_ERROR; } sqlite3DbFree(pErrorDb, sParse.zErrMsg); - sqlite3ParserReset(&sParse); + sqlite3ParseObjectReset(&sParse); if( rc ){ return 0; } @@ -76544,7 +78811,9 @@ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){ /* The szMalloc field holds the correct memory allocation size */ assert( p->szMalloc==0 - || p->szMalloc==sqlite3DbMallocSize(p->db,p->zMalloc) ); + || (p->flags==MEM_Undefined + && p->szMalloc<=sqlite3DbMallocSize(p->db,p->zMalloc)) + || p->szMalloc==sqlite3DbMallocSize(p->db,p->zMalloc)); /* If p holds a string or blob, the Mem.z must point to exactly ** one of the following: @@ -76667,10 +78936,15 @@ SQLITE_PRIVATE int sqlite3VdbeChangeEncoding(Mem *pMem, int desiredEnc){ #ifndef SQLITE_OMIT_UTF16 int rc; #endif + assert( pMem!=0 ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); assert( desiredEnc==SQLITE_UTF8 || desiredEnc==SQLITE_UTF16LE || desiredEnc==SQLITE_UTF16BE ); - if( !(pMem->flags&MEM_Str) || pMem->enc==desiredEnc ){ + if( !(pMem->flags&MEM_Str) ){ + pMem->enc = desiredEnc; + return SQLITE_OK; + } + if( pMem->enc==desiredEnc ){ return SQLITE_OK; } assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); @@ -76708,7 +78982,9 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemGrow(Mem *pMem, int n, int bPre testcase( bPreserve && pMem->z==0 ); assert( pMem->szMalloc==0 - || pMem->szMalloc==sqlite3DbMallocSize(pMem->db, pMem->zMalloc) ); + || (pMem->flags==MEM_Undefined + && pMem->szMalloc<=sqlite3DbMallocSize(pMem->db,pMem->zMalloc)) + || pMem->szMalloc==sqlite3DbMallocSize(pMem->db,pMem->zMalloc)); if( pMem->szMalloc>0 && bPreserve && pMem->z==pMem->zMalloc ){ if( pMem->db ){ pMem->z = pMem->zMalloc = sqlite3DbReallocOrFree(pMem->db, pMem->z, n); @@ -76797,6 +79073,7 @@ static SQLITE_NOINLINE int vdbeMemAddTerminator(Mem *pMem){ ** Return SQLITE_OK on success or SQLITE_NOMEM if malloc fails. */ SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem *pMem){ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); if( (pMem->flags & (MEM_Str|MEM_Blob))!=0 ){ @@ -76821,6 +79098,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem *pMem){ #ifndef SQLITE_OMIT_INCRBLOB SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *pMem){ int nByte; + assert( pMem!=0 ); assert( pMem->flags & MEM_Zero ); assert( (pMem->flags&MEM_Blob)!=0 || MemNullNochng(pMem) ); testcase( sqlite3_value_nochange(pMem) ); @@ -76836,6 +79114,8 @@ SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *pMem){ if( sqlite3VdbeMemGrow(pMem, nByte, 1) ){ return SQLITE_NOMEM_BKPT; } + assert( pMem->z!=0 ); + assert( sqlite3DbMallocSize(pMem->db,pMem->z) >= nByte ); memset(&pMem->z[pMem->n], 0, pMem->u.nZero); pMem->n += pMem->u.nZero; @@ -76848,6 +79128,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *pMem){ ** Make sure the given Mem is \u0000 terminated. */ SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem *pMem){ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); testcase( (pMem->flags & (MEM_Term|MEM_Str))==(MEM_Term|MEM_Str) ); testcase( (pMem->flags & (MEM_Term|MEM_Str))==0 ); @@ -76875,6 +79156,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem *pMem){ SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem *pMem, u8 enc, u8 bForce){ const int nByte = 32; + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( !(pMem->flags&MEM_Zero) ); assert( !(pMem->flags&(MEM_Str|MEM_Blob)) ); @@ -76910,9 +79192,11 @@ SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){ sqlite3_context ctx; Mem t; assert( pFunc!=0 ); + assert( pMem!=0 ); + assert( pMem->db!=0 ); assert( pFunc->xFinalize!=0 ); assert( (pMem->flags & MEM_Null)!=0 || pFunc==pMem->u.pDef ); - assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + assert( sqlite3_mutex_held(pMem->db->mutex) ); memset(&ctx, 0, sizeof(ctx)); memset(&t, 0, sizeof(t)); t.flags = MEM_Null; @@ -76920,6 +79204,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){ ctx.pOut = &t; ctx.pMem = pMem; ctx.pFunc = pFunc; + ctx.enc = ENC(t.db); pFunc->xFinalize(&ctx); /* IMP: R-24505-23230 */ assert( (pMem->flags & MEM_Dyn)==0 ); if( pMem->szMalloc>0 ) sqlite3DbFreeNN(pMem->db, pMem->zMalloc); @@ -76941,12 +79226,14 @@ SQLITE_PRIVATE int sqlite3VdbeMemAggValue(Mem *pAccum, Mem *pOut, FuncDef *pFunc assert( pFunc!=0 ); assert( pFunc->xValue!=0 ); assert( (pAccum->flags & MEM_Null)!=0 || pFunc==pAccum->u.pDef ); - assert( pAccum->db==0 || sqlite3_mutex_held(pAccum->db->mutex) ); + assert( pAccum->db!=0 ); + assert( sqlite3_mutex_held(pAccum->db->mutex) ); memset(&ctx, 0, sizeof(ctx)); sqlite3VdbeMemSetNull(pOut); ctx.pOut = pOut; ctx.pMem = pAccum; ctx.pFunc = pFunc; + ctx.enc = ENC(pAccum->db); pFunc->xValue(&ctx); return ctx.isError; } @@ -77012,6 +79299,14 @@ SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p){ } } +/* Like sqlite3VdbeMemRelease() but faster for cases where we +** know in advance that the Mem is not MEM_Dyn or MEM_Agg. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemReleaseMalloc(Mem *p){ + assert( !VdbeMemDynamic(p) ); + if( p->szMalloc ) vdbeMemClear(p); +} + /* ** Convert a 64-bit IEEE double into a 64-bit signed integer. ** If the double is out of range of a 64-bit signed integer then @@ -77053,13 +79348,14 @@ static SQLITE_NOINLINE i64 doubleToInt64(double r){ ** ** If pMem represents a string value, its encoding might be changed. */ -static SQLITE_NOINLINE i64 memIntValue(Mem *pMem){ +static SQLITE_NOINLINE i64 memIntValue(const Mem *pMem){ i64 value = 0; sqlite3Atoi64(pMem->z, &value, pMem->n, pMem->enc); return value; } -SQLITE_PRIVATE i64 sqlite3VdbeIntValue(Mem *pMem){ +SQLITE_PRIVATE i64 sqlite3VdbeIntValue(const Mem *pMem){ int flags; + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); flags = pMem->flags; @@ -77088,6 +79384,7 @@ static SQLITE_NOINLINE double memRealValue(Mem *pMem){ return val; } SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem *pMem){ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); if( pMem->flags & MEM_Real ){ @@ -77120,6 +79417,7 @@ SQLITE_PRIVATE int sqlite3VdbeBooleanValue(Mem *pMem, int ifNull){ */ SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem *pMem){ i64 ix; + assert( pMem!=0 ); assert( pMem->flags & MEM_Real ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); @@ -77147,6 +79445,7 @@ SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem *pMem){ ** Convert pMem to type integer. Invalidate any prior representations. */ SQLITE_PRIVATE int sqlite3VdbeMemIntegerify(Mem *pMem){ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); @@ -77161,6 +79460,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemIntegerify(Mem *pMem){ ** Invalidate any prior representations. */ SQLITE_PRIVATE int sqlite3VdbeMemRealify(Mem *pMem){ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); @@ -77194,6 +79494,7 @@ SQLITE_PRIVATE int sqlite3RealSameAsInt(double r1, sqlite3_int64 i){ ** as much of the string as we can and ignore the rest. */ SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem *pMem){ + assert( pMem!=0 ); testcase( pMem->flags & MEM_Int ); testcase( pMem->flags & MEM_Real ); testcase( pMem->flags & MEM_IntReal ); @@ -77303,6 +79604,7 @@ SQLITE_PRIVATE void sqlite3ValueSetNull(sqlite3_value *p){ ** Delete any previous value and set the value to be a BLOB of length ** n containing all zeros. */ +#ifndef SQLITE_OMIT_INCRBLOB SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){ sqlite3VdbeMemRelease(pMem); pMem->flags = MEM_Blob|MEM_Zero; @@ -77312,6 +79614,21 @@ SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){ pMem->enc = SQLITE_UTF8; pMem->z = 0; } +#else +SQLITE_PRIVATE int sqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){ + int nByte = n>0?n:1; + if( sqlite3VdbeMemGrow(pMem, nByte, 0) ){ + return SQLITE_NOMEM_BKPT; + } + assert( pMem->z!=0 ); + assert( sqlite3DbMallocSize(pMem->db, pMem->z)>=nByte ); + memset(pMem->z, 0, nByte); + pMem->n = n>0?n:0; + pMem->flags = MEM_Blob; + pMem->enc = SQLITE_UTF8; + return SQLITE_OK; +} +#endif /* ** The pMem is known to contain content that needs to be destroyed prior @@ -77351,6 +79668,7 @@ SQLITE_PRIVATE void sqlite3VdbeMemSetPointer( void (*xDestructor)(void*) ){ assert( pMem->flags==MEM_Null ); + vdbeMemClear(pMem); pMem->u.zPType = zPType ? zPType : ""; pMem->z = pPtr; pMem->flags = MEM_Null|MEM_Dyn|MEM_Subtype|MEM_Term; @@ -77533,20 +79851,29 @@ SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem *pTo, Mem *pFrom){ ** stored without allocating memory, then it is. If a memory allocation ** is required to store the string, then value of pMem is unchanged. In ** either case, SQLITE_TOOBIG is returned. +** +** The "enc" parameter is the text encoding for the string, or zero +** to store a blob. +** +** If n is negative, then the string consists of all bytes up to but +** excluding the first zero character. The n parameter must be +** non-negative for blobs. */ SQLITE_PRIVATE int sqlite3VdbeMemSetStr( Mem *pMem, /* Memory cell to set to string value */ const char *z, /* String pointer */ - int n, /* Bytes in string, or negative */ + i64 n, /* Bytes in string, or negative */ u8 enc, /* Encoding of z. 0 for BLOBs */ void (*xDel)(void*) /* Destructor function */ ){ - int nByte = n; /* New value for pMem->n */ + i64 nByte = n; /* New value for pMem->n */ int iLimit; /* Maximum allowed string or blob size */ - u16 flags = 0; /* New value for pMem->flags */ + u16 flags; /* New value for pMem->flags */ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); + assert( enc!=0 || n>=0 ); /* If z is a NULL pointer, set pMem to contain an SQL NULL. */ if( !z ){ @@ -77559,15 +79886,30 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr( }else{ iLimit = SQLITE_MAX_LENGTH; } - flags = (enc==0?MEM_Blob:MEM_Str); if( nByte<0 ){ assert( enc!=0 ); if( enc==SQLITE_UTF8 ){ - nByte = 0x7fffffff & (int)strlen(z); + nByte = strlen(z); }else{ for(nByte=0; nByte<=iLimit && (z[nByte] | z[nByte+1]); nByte+=2){} } - flags |= MEM_Term; + flags= MEM_Str|MEM_Term; + }else if( enc==0 ){ + flags = MEM_Blob; + enc = SQLITE_UTF8; + }else{ + flags = MEM_Str; + } + if( nByte>iLimit ){ + if( xDel && xDel!=SQLITE_TRANSIENT ){ + if( xDel==SQLITE_DYNAMIC ){ + sqlite3DbFree(pMem->db, (void*)z); + }else{ + xDel((void*)z); + } + } + sqlite3VdbeMemSetNull(pMem); + return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG); } /* The following block sets the new values of Mem.z and Mem.xDel. It @@ -77575,13 +79917,10 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr( ** management (one of MEM_Dyn or MEM_Static). */ if( xDel==SQLITE_TRANSIENT ){ - u32 nAlloc = nByte; + i64 nAlloc = nByte; if( flags&MEM_Term ){ nAlloc += (enc==SQLITE_UTF8?1:2); } - if( nByte>iLimit ){ - return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG); - } testcase( nAlloc==0 ); testcase( nAlloc==31 ); testcase( nAlloc==32 ); @@ -77601,18 +79940,9 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr( } } - pMem->n = nByte; + pMem->n = (int)(nByte & 0x7fffffff); pMem->flags = flags; - if( enc ){ - pMem->enc = enc; -#ifdef SQLITE_ENABLE_SESSION - }else if( pMem->db==0 ){ - pMem->enc = SQLITE_UTF8; -#endif - }else{ - assert( pMem->db!=0 ); - pMem->enc = ENC(pMem->db); - } + pMem->enc = enc; #ifndef SQLITE_OMIT_UTF16 if( enc>SQLITE_UTF8 && sqlite3VdbeMemHandleBom(pMem) ){ @@ -77620,9 +79950,6 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr( } #endif - if( nByte>iLimit ){ - return SQLITE_TOOBIG; - } return SQLITE_OK; } @@ -77853,7 +80180,7 @@ static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){ #ifdef SQLITE_ENABLE_STAT4 static int valueFromFunction( sqlite3 *db, /* The database connection */ - Expr *p, /* The expression to evaluate */ + const Expr *p, /* The expression to evaluate */ u8 enc, /* Encoding to use */ u8 aff, /* Affinity to use */ sqlite3_value **ppVal, /* Write the new value here */ @@ -77870,8 +80197,10 @@ static int valueFromFunction( assert( pCtx!=0 ); assert( (p->flags & EP_TokenOnly)==0 ); + assert( ExprUseXList(p) ); pList = p->x.pList; if( pList ) nVal = pList->nExpr; + assert( !ExprHasProperty(p, EP_IntValue) ); pFunc = sqlite3FindFunction(db, p->u.zToken, nVal, enc, 0); assert( pFunc ); if( (pFunc->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0 @@ -77898,10 +80227,12 @@ static int valueFromFunction( goto value_from_function_out; } - assert( pCtx->pParse->rc==SQLITE_OK ); + testcase( pCtx->pParse->rc==SQLITE_ERROR ); + testcase( pCtx->pParse->rc==SQLITE_OK ); memset(&ctx, 0, sizeof(ctx)); ctx.pOut = pVal; ctx.pFunc = pFunc; + ctx.enc = ENC(db); pFunc->xSFunc(&ctx, nVal, apVal); if( ctx.isError ){ rc = ctx.isError; @@ -77947,7 +80278,7 @@ static int valueFromFunction( */ static int valueFromExpr( sqlite3 *db, /* The database connection */ - Expr *pExpr, /* The expression to evaluate */ + const Expr *pExpr, /* The expression to evaluate */ u8 enc, /* Encoding to use */ u8 affinity, /* Affinity to use */ sqlite3_value **ppVal, /* Write the new value here */ @@ -77962,11 +80293,7 @@ static int valueFromExpr( assert( pExpr!=0 ); while( (op = pExpr->op)==TK_UPLUS || op==TK_SPAN ) pExpr = pExpr->pLeft; -#if defined(SQLITE_ENABLE_STAT4) if( op==TK_REGISTER ) op = pExpr->op2; -#else - if( NEVER(op==TK_REGISTER) ) op = pExpr->op2; -#endif /* Compressed expressions only appear when parsing the DEFAULT clause ** on a table column definition, and hence only when pCtx==0. This @@ -77975,12 +80302,14 @@ static int valueFromExpr( assert( (pExpr->flags & EP_TokenOnly)==0 || pCtx==0 ); if( op==TK_CAST ){ - u8 aff = sqlite3AffinityType(pExpr->u.zToken,0); + u8 aff; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + aff = sqlite3AffinityType(pExpr->u.zToken,0); rc = valueFromExpr(db, pExpr->pLeft, enc, aff, ppVal, pCtx); testcase( rc!=SQLITE_OK ); if( *ppVal ){ - sqlite3VdbeMemCast(*ppVal, aff, SQLITE_UTF8); - sqlite3ValueApplyAffinity(*ppVal, affinity, SQLITE_UTF8); + sqlite3VdbeMemCast(*ppVal, aff, enc); + sqlite3ValueApplyAffinity(*ppVal, affinity, enc); } return rc; } @@ -78048,6 +80377,7 @@ static int valueFromExpr( #ifndef SQLITE_OMIT_BLOB_LITERAL else if( op==TK_BLOB ){ int nVal; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); assert( pExpr->u.zToken[0]=='x' || pExpr->u.zToken[0]=='X' ); assert( pExpr->u.zToken[1]=='\'' ); pVal = valueNew(db, pCtx); @@ -78065,6 +80395,7 @@ static int valueFromExpr( } #endif else if( op==TK_TRUEFALSE ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); pVal = valueNew(db, pCtx); if( pVal ){ pVal->flags = MEM_Int; @@ -78077,7 +80408,7 @@ static int valueFromExpr( no_mem: #ifdef SQLITE_ENABLE_STAT4 - if( pCtx==0 || pCtx->pParse->nErr==0 ) + if( pCtx==0 || NEVER(pCtx->pParse->nErr==0) ) #endif sqlite3OomFault(db); sqlite3DbFree(db, zVal); @@ -78102,7 +80433,7 @@ no_mem: */ SQLITE_PRIVATE int sqlite3ValueFromExpr( sqlite3 *db, /* The database connection */ - Expr *pExpr, /* The expression to evaluate */ + const Expr *pExpr, /* The expression to evaluate */ u8 enc, /* Encoding to use */ u8 affinity, /* Affinity to use */ sqlite3_value **ppVal /* Write the new value here */ @@ -78412,7 +80743,7 @@ SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(Parse *pParse){ p->pNext = db->pVdbe; p->pPrev = 0; db->pVdbe = p; - p->iVdbeMagic = VDBE_MAGIC_INIT; + assert( p->eVdbeState==VDBE_INIT_STATE ); p->pParse = pParse; pParse->pVdbe = p; assert( pParse->aLabel==0 ); @@ -78557,7 +80888,7 @@ static int growOpArray(Vdbe *v, int nOp){ return SQLITE_NOMEM; } - assert( nOp<=(1024/sizeof(Op)) ); + assert( nOp<=(int)(1024/sizeof(Op)) ); assert( nNew>=(v->nOpAlloc+nOp) ); pNew = sqlite3DbRealloc(p->db, v->aOp, nNew*sizeof(Op)); if( pNew ){ @@ -78613,13 +80944,15 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ VdbeOp *pOp; i = p->nOp; - assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); assert( op>=0 && op<0xff ); if( p->nOpAlloc<=i ){ return growOp3(p, op, p1, p2, p3); } + assert( p->aOp!=0 ); p->nOp++; pOp = &p->aOp[i]; + assert( pOp!=0 ); pOp->opcode = (u8)op; pOp->p5 = 0; pOp->p1 = p1; @@ -78756,6 +81089,7 @@ SQLITE_PRIVATE int sqlite3VdbeAddFunctionCall( addr = sqlite3VdbeAddOp4(v, eCallCtx ? OP_PureFunc : OP_Function, p1, p2, p3, (char*)pCtx, P4_FUNCCTX); sqlite3VdbeChangeP5(v, eCallCtx & NC_SelfRef); + sqlite3MayAbort(pParse); return addr; } @@ -78943,7 +81277,7 @@ static SQLITE_NOINLINE void resizeResolveLabel(Parse *p, Vdbe *v, int j){ SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe *v, int x){ Parse *p = v->pParse; int j = ADDR(x); - assert( v->iVdbeMagic==VDBE_MAGIC_INIT ); + assert( v->eVdbeState==VDBE_INIT_STATE ); assert( j<-p->nLabel ); assert( j>=0 ); #ifdef SQLITE_DEBUG @@ -78963,14 +81297,20 @@ SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe *v, int x){ ** Mark the VDBE as one that can only be run one time. */ SQLITE_PRIVATE void sqlite3VdbeRunOnlyOnce(Vdbe *p){ - p->runOnlyOnce = 1; + sqlite3VdbeAddOp2(p, OP_Expire, 1, 1); } /* -** Mark the VDBE as one that can only be run multiple times. +** Mark the VDBE as one that can be run multiple times. */ SQLITE_PRIVATE void sqlite3VdbeReusable(Vdbe *p){ - p->runOnlyOnce = 0; + int i; + for(i=1; ALWAYS(inOp); i++){ + if( ALWAYS(p->aOp[i].opcode==OP_Expire) ){ + p->aOp[1].opcode = OP_Noop; + break; + } + } } #ifdef SQLITE_DEBUG /* sqlite3AssertMayAbort() logic */ @@ -79074,6 +81414,8 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){ int hasInitCoroutine = 0; Op *pOp; VdbeOpIter sIter; + + if( v==0 ) return 0; memset(&sIter, 0, sizeof(sIter)); sIter.v = v; @@ -79083,6 +81425,7 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){ || opcode==OP_VDestroy || opcode==OP_VCreate || opcode==OP_ParseSchema + || opcode==OP_Function || opcode==OP_PureFunc || ((opcode==OP_Halt || opcode==OP_HaltIfNull) && ((pOp->p1)!=SQLITE_OK && pOp->p2==OE_Abort)) ){ @@ -79157,7 +81500,7 @@ SQLITE_PRIVATE void sqlite3VdbeAssertAbortable(Vdbe *p){ ** (3) Update the Vdbe.readOnly and Vdbe.bIsReader flags to accurately ** indicate what the prepared statement actually does. ** -** (4) Initialize the p4.xAdvance pointer on opcodes that use it. +** (4) (discontinued) ** ** (5) Reclaim the memory allocated for storing labels. ** @@ -79203,25 +81546,6 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ p->bIsReader = 1; break; } - case OP_Next: - case OP_SorterNext: { - pOp->p4.xAdvance = sqlite3BtreeNext; - pOp->p4type = P4_ADVANCE; - /* The code generator never codes any of these opcodes as a jump - ** to a label. They are always coded as a jump backwards to a - ** known address */ - assert( pOp->p2>=0 ); - break; - } - case OP_Prev: { - pOp->p4.xAdvance = sqlite3BtreePrevious; - pOp->p4type = P4_ADVANCE; - /* The code generator never codes any of these opcodes as a jump - ** to a label. They are always coded as a jump backwards to a - ** known address */ - assert( pOp->p2>=0 ); - break; - } #ifndef SQLITE_OMIT_VIRTUALTABLE case OP_VUpdate: { if( pOp->p2>nMaxArgs ) nMaxArgs = pOp->p2; @@ -79257,18 +81581,104 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ if( pOp==p->aOp ) break; pOp--; } - sqlite3DbFree(p->db, pParse->aLabel); - pParse->aLabel = 0; + if( aLabel ){ + sqlite3DbFreeNN(p->db, pParse->aLabel); + pParse->aLabel = 0; + } pParse->nLabel = 0; *pMaxFuncArgs = nMaxArgs; assert( p->bIsReader!=0 || DbMaskAllZero(p->btreeMask) ); } +#ifdef SQLITE_DEBUG +/* +** Check to see if a subroutine contains a jump to a location outside of +** the subroutine. If a jump outside the subroutine is detected, add code +** that will cause the program to halt with an error message. +** +** The subroutine consists of opcodes between iFirst and iLast. Jumps to +** locations within the subroutine are acceptable. iRetReg is a register +** that contains the return address. Jumps to outside the range of iFirst +** through iLast are also acceptable as long as the jump destination is +** an OP_Return to iReturnAddr. +** +** A jump to an unresolved label means that the jump destination will be +** beyond the current address. That is normally a jump to an early +** termination and is consider acceptable. +** +** This routine only runs during debug builds. The purpose is (of course) +** to detect invalid escapes out of a subroutine. The OP_Halt opcode +** is generated rather than an assert() or other error, so that ".eqp full" +** will still work to show the original bytecode, to aid in debugging. +*/ +SQLITE_PRIVATE void sqlite3VdbeNoJumpsOutsideSubrtn( + Vdbe *v, /* The byte-code program under construction */ + int iFirst, /* First opcode of the subroutine */ + int iLast, /* Last opcode of the subroutine */ + int iRetReg /* Subroutine return address register */ +){ + VdbeOp *pOp; + Parse *pParse; + int i; + sqlite3_str *pErr = 0; + assert( v!=0 ); + pParse = v->pParse; + assert( pParse!=0 ); + if( pParse->nErr ) return; + assert( iLast>=iFirst ); + assert( iLastnOp ); + pOp = &v->aOp[iFirst]; + for(i=iFirst; i<=iLast; i++, pOp++){ + if( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP)!=0 ){ + int iDest = pOp->p2; /* Jump destination */ + if( iDest==0 ) continue; + if( pOp->opcode==OP_Gosub ) continue; + if( iDest<0 ){ + int j = ADDR(iDest); + assert( j>=0 ); + if( j>=-pParse->nLabel || pParse->aLabel[j]<0 ){ + continue; + } + iDest = pParse->aLabel[j]; + } + if( iDestiLast ){ + int j = iDest; + for(; jnOp; j++){ + VdbeOp *pX = &v->aOp[j]; + if( pX->opcode==OP_Return ){ + if( pX->p1==iRetReg ) break; + continue; + } + if( pX->opcode==OP_Noop ) continue; + if( pX->opcode==OP_Explain ) continue; + if( pErr==0 ){ + pErr = sqlite3_str_new(0); + }else{ + sqlite3_str_appendchar(pErr, 1, '\n'); + } + sqlite3_str_appendf(pErr, + "Opcode at %d jumps to %d which is outside the " + "subroutine at %d..%d", + i, iDest, iFirst, iLast); + break; + } + } + } + } + if( pErr ){ + char *zErr = sqlite3_str_finish(pErr); + sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_INTERNAL, OE_Abort, 0, zErr, 0); + sqlite3_free(zErr); + sqlite3MayAbort(pParse); + } +} +#endif /* SQLITE_DEBUG */ + /* ** Return the address of the next instruction to be inserted. */ SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe *p){ - assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); return p->nOp; } @@ -79353,7 +81763,7 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList( int i; VdbeOp *pOut, *pFirst; assert( nOp>0 ); - assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); if( p->nOp + nOp > p->nOpAlloc && growOpArray(p, nOp) ){ return 0; } @@ -79505,7 +81915,6 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){ case P4_REAL: case P4_INT64: case P4_DYNAMIC: - case P4_DYNBLOB: case P4_INTARRAY: { sqlite3DbFree(db, p4); break; @@ -79545,13 +81954,16 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){ ** nOp entries. */ static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){ + assert( nOp>=0 ); if( aOp ){ - Op *pOp; - for(pOp=&aOp[nOp-1]; pOp>=aOp; pOp--){ + Op *pOp = &aOp[nOp-1]; + while(1){ /* Exit via break */ if( pOp->p4type <= P4_FREE_IF_LE ) freeP4(db, pOp->p4type, pOp->p4.p); #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS sqlite3DbFree(db, pOp->zComment); #endif + if( pOp==aOp ) break; + pOp--; } sqlite3DbFreeNN(db, aOp); } @@ -79613,7 +82025,7 @@ SQLITE_PRIVATE void sqlite3VdbeReleaseRegisters( u32 mask, /* Mask of registers to NOT release */ int bUndefine /* If true, mark registers as undefined */ ){ - if( N==0 ) return; + if( N==0 || OptimizationDisabled(pParse->db, SQLITE_ReleaseReg) ) return; assert( pParse->pVdbe ); assert( iFirst>=1 ); assert( iFirst+N-1<=pParse->nMem ); @@ -79677,7 +82089,7 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int sqlite3 *db; assert( p!=0 ); db = p->db; - assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); assert( p->aOp!=0 || db->mallocFailed ); if( db->mallocFailed ){ if( n!=P4_VTAB ) freeP4(db, n, (void*)*(char**)&zP4); @@ -79753,8 +82165,7 @@ SQLITE_PRIVATE void sqlite3VdbeSetP4KeyInfo(Parse *pParse, Index *pIdx){ */ static void vdbeVComment(Vdbe *p, const char *zFormat, va_list ap){ assert( p->nOp>0 || p->aOp==0 ); - assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed - || p->pParse->nErr>0 ); + assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->pParse->nErr>0 ); if( p->nOp ){ assert( p->aOp ); sqlite3DbFree(p->db, p->aOp[p->nOp-1].zComment); @@ -79806,7 +82217,7 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){ /* C89 specifies that the constant "dummy" will be initialized to all ** zeros, which is correct. MSVC generates a warning, nevertheless. */ static VdbeOp dummy; /* Ignore the MSVC warning about no initializer */ - assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); if( addr<0 ){ addr = p->nOp - 1; } @@ -79862,13 +82273,9 @@ SQLITE_PRIVATE char *sqlite3VdbeDisplayComment( if( zOpName[nOpName+1] ){ int seenCom = 0; char c; - zSynopsis = zOpName += nOpName + 1; + zSynopsis = zOpName + nOpName + 1; if( strncmp(zSynopsis,"IF ",3)==0 ){ - if( pOp->p5 & SQLITE_STOREP2 ){ - sqlite3_snprintf(sizeof(zAlt), zAlt, "r[P2] = (%s)", zSynopsis+3); - }else{ - sqlite3_snprintf(sizeof(zAlt), zAlt, "if %s goto P2", zSynopsis+3); - } + sqlite3_snprintf(sizeof(zAlt), zAlt, "if %s goto P2", zSynopsis+3); zSynopsis = zAlt; } for(ii=0; (c = zSynopsis[ii])!=0; ii++){ @@ -79877,8 +82284,11 @@ SQLITE_PRIVATE char *sqlite3VdbeDisplayComment( if( c=='4' ){ sqlite3_str_appendall(&x, zP4); }else if( c=='X' ){ - sqlite3_str_appendall(&x, pOp->zComment); - seenCom = 1; + if( pOp->zComment && pOp->zComment[0] ){ + sqlite3_str_appendall(&x, pOp->zComment); + seenCom = 1; + break; + } }else{ int v1 = translateP(c, pOp); int v2; @@ -79939,6 +82349,7 @@ static void displayP4Expr(StrAccum *p, Expr *pExpr){ const char *zOp = 0; switch( pExpr->op ){ case TK_STRING: + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3_str_appendf(p, "%Q", pExpr->u.zToken); break; case TK_INTEGER: @@ -80041,7 +82452,7 @@ SQLITE_PRIVATE char *sqlite3VdbeDisplayP4(sqlite3 *db, Op *pOp){ case P4_COLLSEQ: { static const char *const encnames[] = {"?", "8", "16LE", "16BE"}; CollSeq *pColl = pOp->p4.pColl; - assert( pColl->enc>=0 && pColl->enc<4 ); + assert( pColl->enc<4 ); sqlite3_str_appendf(&x, "%.18s-%s", pColl->zName, encnames[pColl->enc]); break; @@ -80106,10 +82517,6 @@ SQLITE_PRIVATE char *sqlite3VdbeDisplayP4(sqlite3 *db, Op *pOp){ zP4 = "program"; break; } - case P4_DYNBLOB: - case P4_ADVANCE: { - break; - } case P4_TABLE: { zP4 = pOp->p4.pTab->zName; break; @@ -80241,21 +82648,40 @@ SQLITE_PRIVATE void sqlite3VdbePrintOp(FILE *pOut, int pc, VdbeOp *pOp){ /* ** Initialize an array of N Mem element. +** +** This is a high-runner, so only those fields that really do need to +** be initialized are set. The Mem structure is organized so that +** the fields that get initialized are nearby and hopefully on the same +** cache line. +** +** Mem.flags = flags +** Mem.db = db +** Mem.szMalloc = 0 +** +** All other fields of Mem can safely remain uninitialized for now. They +** will be initialized before use. */ static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){ - while( (N--)>0 ){ - p->db = db; - p->flags = flags; - p->szMalloc = 0; + if( N>0 ){ + do{ + p->flags = flags; + p->db = db; + p->szMalloc = 0; #ifdef SQLITE_DEBUG - p->pScopyFrom = 0; + p->pScopyFrom = 0; #endif - p++; + p++; + }while( (--N)>0 ); } } /* -** Release an array of N Mem elements +** Release auxiliary memory held in an array of N Mem elements. +** +** After this routine returns, all Mem elements in the array will still +** be valid. Those Mem elements that were not holding auxiliary resources +** will be unchanged. Mem elements which had something freed will be +** set to MEM_Undefined. */ static void releaseMemArray(Mem *p, int N){ if( p && N ){ @@ -80285,15 +82711,20 @@ static void releaseMemArray(Mem *p, int N){ */ testcase( p->flags & MEM_Agg ); testcase( p->flags & MEM_Dyn ); - testcase( p->xDel==sqlite3VdbeFrameMemDel ); if( p->flags&(MEM_Agg|MEM_Dyn) ){ + testcase( (p->flags & MEM_Dyn)!=0 && p->xDel==sqlite3VdbeFrameMemDel ); sqlite3VdbeMemRelease(p); + p->flags = MEM_Undefined; }else if( p->szMalloc ){ sqlite3DbFreeNN(db, p->zMalloc); p->szMalloc = 0; + p->flags = MEM_Undefined; } - - p->flags = MEM_Undefined; +#ifdef SQLITE_DEBUG + else{ + p->flags = MEM_Undefined; + } +#endif }while( (++p)nChildMem]; assert( sqlite3VdbeFrameIsValid(p) ); for(i=0; inChildCsr; i++){ - sqlite3VdbeFreeCursor(p->v, apCsr[i]); + if( apCsr[i] ) sqlite3VdbeFreeCursorNN(p->v, apCsr[i]); } releaseMemArray(aMem, p->nChildMem); sqlite3VdbeDeleteAuxData(p->v->db, &p->pAuxData, -1, 0); @@ -80491,7 +82922,7 @@ SQLITE_PRIVATE int sqlite3VdbeList( Op *pOp; /* Current opcode */ assert( p->explain ); - assert( p->iVdbeMagic==VDBE_MAGIC_RUN ); + assert( p->eVdbeState==VDBE_RUN_STATE ); assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY || p->rc==SQLITE_NOMEM ); /* Even though this opcode does not use dynamic strings for @@ -80646,11 +83077,11 @@ struct ReusableSpace { static void *allocSpace( struct ReusableSpace *p, /* Bulk memory available for allocation */ void *pBuf, /* Pointer to a prior allocation */ - sqlite3_int64 nByte /* Bytes of memory needed */ + sqlite3_int64 nByte /* Bytes of memory needed. */ ){ assert( EIGHT_BYTE_ALIGNMENT(p->pSpace) ); if( pBuf==0 ){ - nByte = ROUND8(nByte); + nByte = ROUND8P(nByte); if( nByte <= p->nFree ){ p->nFree -= nByte; pBuf = &p->pSpace[p->nFree]; @@ -80671,14 +83102,15 @@ SQLITE_PRIVATE void sqlite3VdbeRewind(Vdbe *p){ int i; #endif assert( p!=0 ); - assert( p->iVdbeMagic==VDBE_MAGIC_INIT || p->iVdbeMagic==VDBE_MAGIC_RESET ); + assert( p->eVdbeState==VDBE_INIT_STATE + || p->eVdbeState==VDBE_READY_STATE + || p->eVdbeState==VDBE_HALT_STATE ); /* There should be at least one opcode. */ assert( p->nOp>0 ); - /* Set the magic to VDBE_MAGIC_RUN sooner rather than later. */ - p->iVdbeMagic = VDBE_MAGIC_RUN; + p->eVdbeState = VDBE_READY_STATE; #ifdef SQLITE_DEBUG for(i=0; inMem; i++){ @@ -80734,7 +83166,7 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( assert( p!=0 ); assert( p->nOp>0 ); assert( pParse!=0 ); - assert( p->iVdbeMagic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); assert( pParse==p->pParse ); p->pVList = pParse->pVList; pParse->pVList = 0; @@ -80757,7 +83189,7 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( ** opcode array. This extra memory will be reallocated for other elements ** of the prepared statement. */ - n = ROUND8(sizeof(Op)*p->nOp); /* Bytes of opcode memory used */ + n = ROUND8P(sizeof(Op)*p->nOp); /* Bytes of opcode memory used */ x.pSpace = &((u8*)p->aOp)[n]; /* Unused opcode memory */ assert( EIGHT_BYTE_ALIGNMENT(x.pSpace) ); x.nFree = ROUNDDOWN8(pParse->szOpAlloc - n); /* Bytes of unused memory */ @@ -80845,11 +83277,9 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( ** happens to hold. */ SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ - if( pCx==0 ){ - return; - } - assert( pCx->pBtx==0 || pCx->eCurType==CURTYPE_BTREE ); - assert( pCx->pBtx==0 || pCx->isEphemeral ); + if( pCx ) sqlite3VdbeFreeCursorNN(p,pCx); +} +SQLITE_PRIVATE void sqlite3VdbeFreeCursorNN(Vdbe *p, VdbeCursor *pCx){ switch( pCx->eCurType ){ case CURTYPE_SORTER: { sqlite3VdbeSorterClose(p->db, pCx); @@ -80877,14 +83307,12 @@ SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ ** Close all cursors in the current frame. */ static void closeCursorsInFrame(Vdbe *p){ - if( p->apCsr ){ - int i; - for(i=0; inCursor; i++){ - VdbeCursor *pC = p->apCsr[i]; - if( pC ){ - sqlite3VdbeFreeCursor(p, pC); - p->apCsr[i] = 0; - } + int i; + for(i=0; inCursor; i++){ + VdbeCursor *pC = p->apCsr[i]; + if( pC ){ + sqlite3VdbeFreeCursorNN(p, pC); + p->apCsr[i] = 0; } } } @@ -80933,9 +83361,7 @@ static void closeAllCursors(Vdbe *p){ } assert( p->nFrame==0 ); closeCursorsInFrame(p); - if( p->aMem ){ - releaseMemArray(p->aMem, p->nMem); - } + releaseMemArray(p->aMem, p->nMem); while( p->pDelFrame ){ VdbeFrame *pDel = p->pDelFrame; p->pDelFrame = pDel->pParent; @@ -81375,7 +83801,8 @@ SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *p, int deferred){ p->rc = SQLITE_CONSTRAINT_FOREIGNKEY; p->errorAction = OE_Abort; sqlite3VdbeError(p, "FOREIGN KEY constraint failed"); - return SQLITE_ERROR; + if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ) return SQLITE_ERROR; + return SQLITE_CONSTRAINT_FOREIGNKEY; } return SQLITE_OK; } @@ -81386,9 +83813,9 @@ SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *p, int deferred){ ** has made changes and is in autocommit mode, then commit those ** changes. If a rollback is needed, then do the rollback. ** -** This routine is the only way to move the state of a VM from -** SQLITE_MAGIC_RUN to SQLITE_MAGIC_HALT. It is harmless to -** call this on a VM that is in the SQLITE_MAGIC_HALT state. +** This routine is the only way to move the sqlite3eOpenState of a VM from +** SQLITE_STATE_RUN to SQLITE_STATE_HALT. It is harmless to +** call this on a VM that is in the SQLITE_STATE_HALT state. ** ** Return an error code. If the commit could not complete because of ** lock contention, return SQLITE_BUSY. If SQLITE_BUSY is returned, it @@ -81414,9 +83841,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ ** one, or the complete transaction if there is no statement transaction. */ - if( p->iVdbeMagic!=VDBE_MAGIC_RUN ){ - return SQLITE_OK; - } + assert( p->eVdbeState==VDBE_RUN_STATE ); if( db->mallocFailed ){ p->rc = SQLITE_NOMEM_BKPT; } @@ -81425,7 +83850,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ /* No commit or rollback needed if the program never started or if the ** SQL statement does not read or write a database file. */ - if( p->pc>=0 && p->bIsReader ){ + if( p->bIsReader ){ int mrc; /* Primary error code from p->rc */ int eStatementOp = 0; int isSpecialError; /* Set to true if a 'special' error */ @@ -81434,9 +83859,15 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ sqlite3VdbeEnter(p); /* Check for one of the special errors */ - mrc = p->rc & 0xff; - isSpecialError = mrc==SQLITE_NOMEM || mrc==SQLITE_IOERR - || mrc==SQLITE_INTERRUPT || mrc==SQLITE_FULL; + if( p->rc ){ + mrc = p->rc & 0xff; + isSpecialError = mrc==SQLITE_NOMEM + || mrc==SQLITE_IOERR + || mrc==SQLITE_INTERRUPT + || mrc==SQLITE_FULL; + }else{ + mrc = isSpecialError = 0; + } if( isSpecialError ){ /* If the query was read-only and the error code is SQLITE_INTERRUPT, ** no rollback is necessary. Otherwise, at least a savepoint @@ -81488,6 +83919,9 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ return SQLITE_ERROR; } rc = SQLITE_CONSTRAINT_FOREIGNKEY; + }else if( db->flags & SQLITE_CorruptRdOnly ){ + rc = SQLITE_CORRUPT; + db->flags &= ~SQLITE_CorruptRdOnly; }else{ /* The auto-commit flag is true, the vdbe program was successful ** or hit an 'OR FAIL' constraint and there are no deferred foreign @@ -81564,15 +83998,13 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ } /* We have successfully halted and closed the VM. Record this fact. */ - if( p->pc>=0 ){ - db->nVdbeActive--; - if( !p->readOnly ) db->nVdbeWrite--; - if( p->bIsReader ) db->nVdbeRead--; - assert( db->nVdbeActive>=db->nVdbeRead ); - assert( db->nVdbeRead>=db->nVdbeWrite ); - assert( db->nVdbeWrite>=0 ); - } - p->iVdbeMagic = VDBE_MAGIC_HALT; + db->nVdbeActive--; + if( !p->readOnly ) db->nVdbeWrite--; + if( p->bIsReader ) db->nVdbeRead--; + assert( db->nVdbeActive>=db->nVdbeRead ); + assert( db->nVdbeRead>=db->nVdbeWrite ); + assert( db->nVdbeWrite>=0 ); + p->eVdbeState = VDBE_HALT_STATE; checkActiveVdbeCnt(db); if( db->mallocFailed ){ p->rc = SQLITE_NOMEM_BKPT; @@ -81621,6 +84053,7 @@ SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p){ sqlite3ValueSetNull(db->pErr); } db->errCode = rc; + db->errByteOffset = -1; return rc; } @@ -81653,8 +84086,8 @@ static void vdbeInvokeSqllog(Vdbe *v){ ** again. ** ** To look at it another way, this routine resets the state of the -** virtual machine from VDBE_MAGIC_RUN or VDBE_MAGIC_HALT back to -** VDBE_MAGIC_INIT. +** virtual machine from VDBE_RUN_STATE or VDBE_HALT_STATE back to +** VDBE_READY_STATE. */ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){ #if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) @@ -81668,7 +84101,7 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){ ** error, then it might not have been halted properly. So halt ** it now. */ - sqlite3VdbeHalt(p); + if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p); /* If the VDBE has been run even partially, then transfer the error code ** and error message from the VDBE into the main database structure. But @@ -81682,13 +84115,6 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){ }else{ db->errCode = p->rc; } - if( p->runOnlyOnce ) p->expired = 1; - }else if( p->rc && p->expired ){ - /* The expired flag was set on the VDBE before the first call - ** to sqlite3_step(). For consistency (since sqlite3_step() was - ** called), set the database error in this case as well. - */ - sqlite3ErrorWithMsg(db, p->rc, p->zErrMsg ? "%s" : 0, p->zErrMsg); } /* Reset register contents and reclaim error message memory. @@ -81745,7 +84171,6 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){ } } #endif - p->iVdbeMagic = VDBE_MAGIC_RESET; return p->rc & db->errMask; } @@ -81755,7 +84180,10 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){ */ SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe *p){ int rc = SQLITE_OK; - if( p->iVdbeMagic==VDBE_MAGIC_RUN || p->iVdbeMagic==VDBE_MAGIC_HALT ){ + assert( VDBE_RUN_STATE>VDBE_READY_STATE ); + assert( VDBE_HALT_STATE>VDBE_READY_STATE ); + assert( VDBE_INIT_STATEeVdbeState>=VDBE_READY_STATE ){ rc = sqlite3VdbeReset(p); assert( (rc & p->db->errMask)==rc ); } @@ -81807,22 +84235,24 @@ SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(sqlite3 *db, AuxData **pp, int iOp, ** VdbeDelete() also unlinks the Vdbe from the list of VMs associated with ** the database connection and frees the object itself. */ -SQLITE_PRIVATE void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ +static void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ SubProgram *pSub, *pNext; assert( p->db==0 || p->db==db ); - releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); + if( p->aColName ){ + releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); + sqlite3DbFreeNN(db, p->aColName); + } for(pSub=p->pProgram; pSub; pSub=pNext){ pNext = pSub->pNext; vdbeFreeOpArray(db, pSub->aOp, pSub->nOp); sqlite3DbFree(db, pSub); } - if( p->iVdbeMagic!=VDBE_MAGIC_INIT ){ + if( p->eVdbeState!=VDBE_INIT_STATE ){ releaseMemArray(p->aVar, p->nVar); - sqlite3DbFree(db, p->pVList); - sqlite3DbFree(db, p->pFree); + if( p->pVList ) sqlite3DbFreeNN(db, p->pVList); + if( p->pFree ) sqlite3DbFreeNN(db, p->pFree); } vdbeFreeOpArray(db, p->aOp, p->nOp); - sqlite3DbFree(db, p->aColName); sqlite3DbFree(db, p->zSql); #ifdef SQLITE_ENABLE_NORMALIZE sqlite3DbFree(db, p->zNormSql); @@ -81855,17 +84285,17 @@ SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe *p){ db = p->db; assert( sqlite3_mutex_held(db->mutex) ); sqlite3VdbeClearObject(db, p); - if( p->pPrev ){ - p->pPrev->pNext = p->pNext; - }else{ - assert( db->pVdbe==p ); - db->pVdbe = p->pNext; + if( db->pnBytesFreed==0 ){ + if( p->pPrev ){ + p->pPrev->pNext = p->pNext; + }else{ + assert( db->pVdbe==p ); + db->pVdbe = p->pNext; + } + if( p->pNext ){ + p->pNext->pPrev = p->pPrev; + } } - if( p->pNext ){ - p->pNext->pPrev = p->pPrev; - } - p->iVdbeMagic = VDBE_MAGIC_DEAD; - p->db = 0; sqlite3DbFreeNN(db, p); } @@ -81882,7 +84312,7 @@ SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeFinishMoveto(VdbeCursor *p){ assert( p->deferredMoveto ); assert( p->isTable ); assert( p->eCurType==CURTYPE_BTREE ); - rc = sqlite3BtreeMovetoUnpacked(p->uc.pCursor, 0, p->movetoTarget, 0, &res); + rc = sqlite3BtreeTableMoveto(p->uc.pCursor, p->movetoTarget, 0, &res); if( rc ) return rc; if( res!=0 ) return SQLITE_CORRUPT_BKPT; #ifdef SQLITE_TEST @@ -81900,7 +84330,7 @@ SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeFinishMoveto(VdbeCursor *p){ ** is supposed to be pointing. If the row was deleted out from under the ** cursor, set the cursor to point to a NULL row. */ -static int SQLITE_NOINLINE handleMovedCursor(VdbeCursor *p){ +SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeHandleMovedCursor(VdbeCursor *p){ int isDifferentRow, rc; assert( p->eCurType==CURTYPE_BTREE ); assert( p->uc.pCursor!=0 ); @@ -81916,41 +84346,9 @@ static int SQLITE_NOINLINE handleMovedCursor(VdbeCursor *p){ ** if need be. Return any I/O error from the restore operation. */ SQLITE_PRIVATE int sqlite3VdbeCursorRestore(VdbeCursor *p){ - assert( p->eCurType==CURTYPE_BTREE ); + assert( p->eCurType==CURTYPE_BTREE || IsNullCursor(p) ); if( sqlite3BtreeCursorHasMoved(p->uc.pCursor) ){ - return handleMovedCursor(p); - } - return SQLITE_OK; -} - -/* -** Make sure the cursor p is ready to read or write the row to which it -** was last positioned. Return an error code if an OOM fault or I/O error -** prevents us from positioning the cursor to its correct position. -** -** If a MoveTo operation is pending on the given cursor, then do that -** MoveTo now. If no move is pending, check to see if the row has been -** deleted out from under the cursor and if it has, mark the row as -** a NULL row. -** -** If the cursor is already pointing to the correct row and that row has -** not been deleted out from under the cursor, then this routine is a no-op. -*/ -SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor **pp, u32 *piCol){ - VdbeCursor *p = *pp; - assert( p->eCurType==CURTYPE_BTREE || p->eCurType==CURTYPE_PSEUDO ); - if( p->deferredMoveto ){ - u32 iMap; - assert( !p->isEphemeral ); - if( p->aAltMap && (iMap = p->aAltMap[1+*piCol])>0 && !p->nullRow ){ - *pp = p->pAltCursor; - *piCol = iMap - 1; - return SQLITE_OK; - } - return sqlite3VdbeFinishMoveto(p); - } - if( sqlite3BtreeCursorHasMoved(p->uc.pCursor) ){ - return handleMovedCursor(p); + return sqlite3VdbeHandleMovedCursor(p); } return SQLITE_OK; } @@ -81961,7 +84359,7 @@ SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor **pp, u32 *piCol){ ** sqlite3VdbeSerialType() ** sqlite3VdbeSerialTypeLen() ** sqlite3VdbeSerialLen() -** sqlite3VdbeSerialPut() +** sqlite3VdbeSerialPut() <--- in-lined into OP_MakeRecord as of 2022-04-02 ** sqlite3VdbeSerialGet() ** ** encapsulate the code that serializes values for storage in SQLite @@ -82073,7 +84471,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format, u32 *pLen){ /* ** The sizes for serial types less than 128 */ -static const u8 sqlite3SmallTypeSizes[] = { +SQLITE_PRIVATE const u8 sqlite3SmallTypeSizes[128] = { /* 0 1 2 3 4 5 6 7 8 9 */ /* 0 */ 0, 1, 2, 3, 4, 6, 8, 8, 0, 0, /* 10 */ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, @@ -82142,7 +84540,7 @@ SQLITE_PRIVATE u8 sqlite3VdbeOneByteSerialTypeLen(u8 serial_type){ ** so we trust him. */ #ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT -static u64 floatSwap(u64 in){ +SQLITE_PRIVATE u64 sqlite3FloatSwap(u64 in){ union { u64 r; u32 i[2]; @@ -82155,59 +84553,8 @@ static u64 floatSwap(u64 in){ u.i[1] = t; return u.r; } -# define swapMixedEndianFloat(X) X = floatSwap(X) -#else -# define swapMixedEndianFloat(X) -#endif +#endif /* SQLITE_MIXED_ENDIAN_64BIT_FLOAT */ -/* -** Write the serialized data blob for the value stored in pMem into -** buf. It is assumed that the caller has allocated sufficient space. -** Return the number of bytes written. -** -** nBuf is the amount of space left in buf[]. The caller is responsible -** for allocating enough space to buf[] to hold the entire field, exclusive -** of the pMem->u.nZero bytes for a MEM_Zero value. -** -** Return the number of bytes actually written into buf[]. The number -** of bytes in the zero-filled tail is included in the return value only -** if those bytes were zeroed in buf[]. -*/ -SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(u8 *buf, Mem *pMem, u32 serial_type){ - u32 len; - - /* Integer and Real */ - if( serial_type<=7 && serial_type>0 ){ - u64 v; - u32 i; - if( serial_type==7 ){ - assert( sizeof(v)==sizeof(pMem->u.r) ); - memcpy(&v, &pMem->u.r, sizeof(v)); - swapMixedEndianFloat(v); - }else{ - v = pMem->u.i; - } - len = i = sqlite3SmallTypeSizes[serial_type]; - assert( i>0 ); - do{ - buf[--i] = (u8)(v&0xFF); - v >>= 8; - }while( i ); - return len; - } - - /* String or blob */ - if( serial_type>=12 ){ - assert( pMem->n + ((pMem->flags & MEM_Zero)?pMem->u.nZero:0) - == (int)sqlite3VdbeSerialTypeLen(serial_type) ); - len = pMem->n; - if( len>0 ) memcpy(buf, pMem->z, len); - return len; - } - - /* NULL or constants 0 or 1 */ - return 0; -} /* Input "x" is a sequence of unsigned characters that represent a ** big-endian integer. Return the equivalent native integer @@ -82220,14 +84567,14 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(u8 *buf, Mem *pMem, u32 serial_type){ /* ** Deserialize the data blob pointed to by buf as serial type serial_type -** and store the result in pMem. Return the number of bytes read. +** and store the result in pMem. ** ** This function is implemented as two separate routines for performance. ** The few cases that require local variables are broken out into a separate ** routine so that in most cases the overhead of moving the stack pointer ** is avoided. */ -static u32 serialGet( +static void serialGet( const unsigned char *buf, /* Buffer to deserialize from */ u32 serial_type, /* Serial type to deserialize */ Mem *pMem /* Memory cell to write value into */ @@ -82261,9 +84608,8 @@ static u32 serialGet( memcpy(&pMem->u.r, &x, sizeof(x)); pMem->flags = IsNaN(x) ? MEM_Null : MEM_Real; } - return 8; } -SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( +SQLITE_PRIVATE void sqlite3VdbeSerialGet( const unsigned char *buf, /* Buffer to deserialize from */ u32 serial_type, /* Serial type to deserialize */ Mem *pMem /* Memory cell to write value into */ @@ -82274,13 +84620,13 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( pMem->flags = MEM_Null|MEM_Zero; pMem->n = 0; pMem->u.nZero = 0; - break; + return; } case 11: /* Reserved for future use */ case 0: { /* Null */ /* EVIDENCE-OF: R-24078-09375 Value is a NULL. */ pMem->flags = MEM_Null; - break; + return; } case 1: { /* EVIDENCE-OF: R-44885-25196 Value is an 8-bit twos-complement @@ -82288,7 +84634,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( pMem->u.i = ONE_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); - return 1; + return; } case 2: { /* 2-byte signed integer */ /* EVIDENCE-OF: R-49794-35026 Value is a big-endian 16-bit @@ -82296,7 +84642,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( pMem->u.i = TWO_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); - return 2; + return; } case 3: { /* 3-byte signed integer */ /* EVIDENCE-OF: R-37839-54301 Value is a big-endian 24-bit @@ -82304,7 +84650,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( pMem->u.i = THREE_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); - return 3; + return; } case 4: { /* 4-byte signed integer */ /* EVIDENCE-OF: R-01849-26079 Value is a big-endian 32-bit @@ -82316,7 +84662,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( #endif pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); - return 4; + return; } case 5: { /* 6-byte signed integer */ /* EVIDENCE-OF: R-50385-09674 Value is a big-endian 48-bit @@ -82324,13 +84670,14 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( pMem->u.i = FOUR_BYTE_UINT(buf+2) + (((i64)1)<<32)*TWO_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); - return 6; + return; } case 6: /* 8-byte signed integer */ case 7: { /* IEEE floating point */ /* These use local variables, so do them in a separate routine ** to avoid having to move the frame pointer in the common case */ - return serialGet(buf,serial_type,pMem); + serialGet(buf,serial_type,pMem); + return; } case 8: /* Integer 0 */ case 9: { /* Integer 1 */ @@ -82338,7 +84685,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( /* EVIDENCE-OF: R-18143-12121 Value is the integer 1. */ pMem->u.i = serial_type-8; pMem->flags = MEM_Int; - return 0; + return; } default: { /* EVIDENCE-OF: R-14606-31564 Value is a BLOB that is (N-12)/2 bytes in @@ -82349,10 +84696,10 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( pMem->z = (char *)buf; pMem->n = (serial_type-12)/2; pMem->flags = aFlag[serial_type&1]; - return pMem->n; + return; } } - return 0; + return; } /* ** This routine is used to allocate sufficient space for an UnpackedRecord @@ -82373,10 +84720,10 @@ SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord( ){ UnpackedRecord *p; /* Unpacked record to return */ int nByte; /* Number of bytes required for *p */ - nByte = ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*(pKeyInfo->nKeyField+1); + nByte = ROUND8P(sizeof(UnpackedRecord)) + sizeof(Mem)*(pKeyInfo->nKeyField+1); p = (UnpackedRecord *)sqlite3DbMallocRaw(pKeyInfo->db, nByte); if( !p ) return 0; - p->aMem = (Mem*)&((char*)p)[ROUND8(sizeof(UnpackedRecord))]; + p->aMem = (Mem*)&((char*)p)[ROUND8P(sizeof(UnpackedRecord))]; assert( pKeyInfo->aSortFlags!=0 ); p->pKeyInfo = pKeyInfo; p->nField = pKeyInfo->nKeyField + 1; @@ -82415,7 +84762,8 @@ SQLITE_PRIVATE void sqlite3VdbeRecordUnpack( /* pMem->flags = 0; // sqlite3VdbeSerialGet() will set this for us */ pMem->szMalloc = 0; pMem->z = 0; - d += sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem); + sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem); + d += sqlite3VdbeSerialTypeLen(serial_type); pMem++; if( (++u)>=p->nField ) break; } @@ -82499,7 +84847,8 @@ static int vdbeRecordCompareDebug( /* Extract the values to be compared. */ - d1 += sqlite3VdbeSerialGet(&aKey1[d1], serial_type1, &mem1); + sqlite3VdbeSerialGet(&aKey1[d1], serial_type1, &mem1); + d1 += sqlite3VdbeSerialTypeLen(serial_type1); /* Do the comparison */ @@ -82610,8 +84959,8 @@ static int vdbeCompareMemString( }else{ rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2); } - sqlite3VdbeMemRelease(&c1); - sqlite3VdbeMemRelease(&c2); + sqlite3VdbeMemReleaseMalloc(&c1); + sqlite3VdbeMemReleaseMalloc(&c2); return rc; } } @@ -82666,7 +85015,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3BlobCompare(const Mem *pB1, const Mem ** number. Return negative, zero, or positive if the first (i64) is less than, ** equal to, or greater than the second (double). */ -static int sqlite3IntFloatCompare(i64 i, double r){ +SQLITE_PRIVATE int sqlite3IntFloatCompare(i64 i, double r){ if( sizeof(LONGDOUBLE_TYPE)>8 ){ LONGDOUBLE_TYPE x = (LONGDOUBLE_TYPE)i; testcase( xaMem[0].u.i; + assert( pPKey2->u.i == pPKey2->aMem[0].u.i ); + v = pPKey2->u.i; if( v>lhs ){ res = pPKey2->r1; }else if( vaMem[0].flags & MEM_Str ); + assert( pPKey2->aMem[0].n == pPKey2->n ); + assert( pPKey2->aMem[0].z == pPKey2->u.z ); vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo); - serial_type = (u8)(aKey1[1]); - if( serial_type >= 0x80 ){ - sqlite3GetVarint32(&aKey1[1], (u32*)&serial_type); - } + serial_type = (signed char)(aKey1[1]); + +vrcs_restart: if( serial_type<12 ){ + if( serial_type<0 ){ + sqlite3GetVarint32(&aKey1[1], (u32*)&serial_type); + if( serial_type>=12 ) goto vrcs_restart; + assert( CORRUPT_DB ); + } res = pPKey2->r1; /* (pKey1/nKey1) is a number or a null */ }else if( !(serial_type & 0x01) ){ res = pPKey2->r2; /* (pKey1/nKey1) is a blob */ @@ -83189,15 +85553,15 @@ static int vdbeRecordCompareString( pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; return 0; /* Corruption */ } - nCmp = MIN( pPKey2->aMem[0].n, nStr ); - res = memcmp(&aKey1[szHdr], pPKey2->aMem[0].z, nCmp); + nCmp = MIN( pPKey2->n, nStr ); + res = memcmp(&aKey1[szHdr], pPKey2->u.z, nCmp); if( res>0 ){ res = pPKey2->r2; }else if( res<0 ){ res = pPKey2->r1; }else{ - res = nStr - pPKey2->aMem[0].n; + res = nStr - pPKey2->n; if( res==0 ){ if( pPKey2->nField>1 ){ res = sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1); @@ -83252,6 +85616,7 @@ SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){ p->r2 = 1; } if( (flags & MEM_Int) ){ + p->u.i = p->aMem[0].u.i; return vdbeRecordCompareInt; } testcase( flags & MEM_Real ); @@ -83261,6 +85626,8 @@ SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){ && p->pKeyInfo->aColl[0]==0 ){ assert( flags & MEM_Str ); + p->u.z = p->aMem[0].z; + p->n = p->aMem[0].n; return vdbeRecordCompareString; } } @@ -83303,7 +85670,7 @@ SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){ /* The index entry must begin with a header size */ getVarint32NR((u8*)m.z, szHdr); testcase( szHdr==3 ); - testcase( szHdr==m.n ); + testcase( szHdr==(u32)m.n ); testcase( szHdr>0x7fffffff ); assert( m.n>=0 ); if( unlikely(szHdr<3 || szHdr>(unsigned)m.n) ){ @@ -83333,14 +85700,14 @@ SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){ /* Fetch the integer off the end of the index record */ sqlite3VdbeSerialGet((u8*)&m.z[m.n-lenRowid], typeRowid, &v); *rowid = v.u.i; - sqlite3VdbeMemRelease(&m); + sqlite3VdbeMemReleaseMalloc(&m); return SQLITE_OK; /* Jump here if database corruption is detected after m has been ** allocated. Free the m object and return SQLITE_CORRUPT. */ idx_rowid_corruption: testcase( m.szMalloc!=0 ); - sqlite3VdbeMemRelease(&m); + sqlite3VdbeMemReleaseMalloc(&m); return SQLITE_CORRUPT_BKPT; } @@ -83382,7 +85749,7 @@ SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare( return rc; } *res = sqlite3VdbeRecordCompareWithSkip(m.n, m.z, pUnpacked, 0); - sqlite3VdbeMemRelease(&m); + sqlite3VdbeMemReleaseMalloc(&m); return SQLITE_OK; } @@ -83390,7 +85757,7 @@ SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare( ** This routine sets the value to be returned by subsequent calls to ** sqlite3_changes() on the database handle 'db'. */ -SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *db, int nChange){ +SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *db, i64 nChange){ assert( sqlite3_mutex_held(db->mutex) ); db->nChange = nChange; db->nTotalChange += nChange; @@ -83549,7 +85916,7 @@ static void vdbeFreeUnpacked(sqlite3 *db, int nField, UnpackedRecord *p){ int i; for(i=0; iaMem[i]; - if( pMem->zMalloc ) sqlite3VdbeMemRelease(pMem); + if( pMem->zMalloc ) sqlite3VdbeMemReleaseMalloc(pMem); } sqlite3DbFreeNN(db, p); } @@ -83570,7 +85937,8 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( const char *zDb, /* Database name */ Table *pTab, /* Modified table */ i64 iKey1, /* Initial key value */ - int iReg /* Register for new.* record */ + int iReg, /* Register for new.* record */ + int iBlobWrite ){ sqlite3 *db = v->db; i64 iKey2; @@ -83591,6 +85959,8 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( } } + assert( pCsr!=0 ); + assert( pCsr->eCurType==CURTYPE_BTREE ); assert( pCsr->nField==pTab->nCol || (pCsr->nField==pTab->nCol+1 && op==SQLITE_DELETE && iReg==-1) ); @@ -83606,6 +85976,7 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( preupdate.iKey1 = iKey1; preupdate.iKey2 = iKey2; preupdate.pTab = pTab; + preupdate.iBlobWrite = iBlobWrite; db->pPreUpdate = &preupdate; db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2); @@ -83972,6 +86343,9 @@ SQLITE_API sqlite3_value *sqlite3_value_dup(const sqlite3_value *pOrig){ sqlite3ValueFree(pNew); pNew = 0; } + }else if( pNew->flags & MEM_Null ){ + /* Do not duplicate pointer values */ + pNew->flags &= ~(MEM_Term|MEM_Subtype); } return pNew; } @@ -83989,8 +86363,8 @@ SQLITE_API void sqlite3_value_free(sqlite3_value *pOld){ ** the function result. ** ** The setStrOrError() function calls sqlite3VdbeMemSetStr() to store the -** result as a string or blob but if the string or blob is too large, it -** then sets the error code to SQLITE_TOOBIG +** result as a string or blob. Appropriate errors are set if the string/blob +** is too big or if an OOM occurs. ** ** The invokeValueDestructor(P,X) routine invokes destructor function X() ** on value P is not going to be used and need to be destroyed. @@ -84002,7 +86376,21 @@ static void setResultStrOrError( u8 enc, /* Encoding of z. 0 for BLOBs */ void (*xDel)(void*) /* Destructor function */ ){ - if( sqlite3VdbeMemSetStr(pCtx->pOut, z, n, enc, xDel)==SQLITE_TOOBIG ){ + Mem *pOut = pCtx->pOut; + int rc = sqlite3VdbeMemSetStr(pOut, z, n, enc, xDel); + if( rc ){ + if( rc==SQLITE_TOOBIG ){ + sqlite3_result_error_toobig(pCtx); + }else{ + /* The only errors possible from sqlite3VdbeMemSetStr are + ** SQLITE_TOOBIG and SQLITE_NOMEM */ + assert( rc==SQLITE_NOMEM ); + sqlite3_result_error_nomem(pCtx); + } + return; + } + sqlite3VdbeChangeEncoding(pOut, pCtx->enc); + if( sqlite3VdbeMemTooBig(pOut) ){ sqlite3_result_error_toobig(pCtx); } } @@ -84019,7 +86407,7 @@ static int invokeValueDestructor( }else{ xDel((void*)p); } - if( pCtx ) sqlite3_result_error_toobig(pCtx); + sqlite3_result_error_toobig(pCtx); return SQLITE_TOOBIG; } SQLITE_API void sqlite3_result_blob( @@ -84147,21 +86535,30 @@ SQLITE_API void sqlite3_result_text16le( } #endif /* SQLITE_OMIT_UTF16 */ SQLITE_API void sqlite3_result_value(sqlite3_context *pCtx, sqlite3_value *pValue){ + Mem *pOut = pCtx->pOut; assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); - sqlite3VdbeMemCopy(pCtx->pOut, pValue); + sqlite3VdbeMemCopy(pOut, pValue); + sqlite3VdbeChangeEncoding(pOut, pCtx->enc); + if( sqlite3VdbeMemTooBig(pOut) ){ + sqlite3_result_error_toobig(pCtx); + } } SQLITE_API void sqlite3_result_zeroblob(sqlite3_context *pCtx, int n){ - assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); - sqlite3VdbeMemSetZeroBlob(pCtx->pOut, n); + sqlite3_result_zeroblob64(pCtx, n>0 ? n : 0); } SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context *pCtx, u64 n){ Mem *pOut = pCtx->pOut; assert( sqlite3_mutex_held(pOut->db->mutex) ); if( n>(u64)pOut->db->aLimit[SQLITE_LIMIT_LENGTH] ){ + sqlite3_result_error_toobig(pCtx); return SQLITE_TOOBIG; } +#ifndef SQLITE_OMIT_INCRBLOB sqlite3VdbeMemSetZeroBlob(pCtx->pOut, (int)n); return SQLITE_OK; +#else + return sqlite3VdbeMemSetZeroBlob(pCtx->pOut, (int)n); +#endif } SQLITE_API void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){ pCtx->isError = errCode ? errCode : -1; @@ -84169,8 +86566,8 @@ SQLITE_API void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){ if( pCtx->pVdbe ) pCtx->pVdbe->rcApp = errCode; #endif if( pCtx->pOut->flags & MEM_Null ){ - sqlite3VdbeMemSetStr(pCtx->pOut, sqlite3ErrStr(errCode), -1, - SQLITE_UTF8, SQLITE_STATIC); + setResultStrOrError(pCtx, sqlite3ErrStr(errCode), -1, SQLITE_UTF8, + SQLITE_STATIC); } } @@ -84244,80 +86641,83 @@ static int sqlite3Step(Vdbe *p){ int rc; assert(p); - if( p->iVdbeMagic!=VDBE_MAGIC_RUN ){ - /* We used to require that sqlite3_reset() be called before retrying - ** sqlite3_step() after any error or after SQLITE_DONE. But beginning - ** with version 3.7.0, we changed this so that sqlite3_reset() would - ** be called automatically instead of throwing the SQLITE_MISUSE error. - ** This "automatic-reset" change is not technically an incompatibility, - ** since any application that receives an SQLITE_MISUSE is broken by - ** definition. - ** - ** Nevertheless, some published applications that were originally written - ** for version 3.6.23 or earlier do in fact depend on SQLITE_MISUSE - ** returns, and those were broken by the automatic-reset change. As a - ** a work-around, the SQLITE_OMIT_AUTORESET compile-time restores the - ** legacy behavior of returning SQLITE_MISUSE for cases where the - ** previous sqlite3_step() returned something other than a SQLITE_LOCKED - ** or SQLITE_BUSY error. - */ -#ifdef SQLITE_OMIT_AUTORESET - if( (rc = p->rc&0xff)==SQLITE_BUSY || rc==SQLITE_LOCKED ){ - sqlite3_reset((sqlite3_stmt*)p); - }else{ - return SQLITE_MISUSE_BKPT; - } -#else - sqlite3_reset((sqlite3_stmt*)p); -#endif - } - - /* Check that malloc() has not failed. If it has, return early. */ db = p->db; - if( db->mallocFailed ){ - p->rc = SQLITE_NOMEM; - return SQLITE_NOMEM_BKPT; - } + if( p->eVdbeState!=VDBE_RUN_STATE ){ + restart_step: + if( p->eVdbeState==VDBE_READY_STATE ){ + if( p->expired ){ + p->rc = SQLITE_SCHEMA; + rc = SQLITE_ERROR; + if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 ){ + /* If this statement was prepared using saved SQL and an + ** error has occurred, then return the error code in p->rc to the + ** caller. Set the error code in the database handle to the same + ** value. + */ + rc = sqlite3VdbeTransferError(p); + } + goto end_of_step; + } - if( p->pc<0 && p->expired ){ - p->rc = SQLITE_SCHEMA; - rc = SQLITE_ERROR; - if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 ){ - /* If this statement was prepared using saved SQL and an - ** error has occurred, then return the error code in p->rc to the - ** caller. Set the error code in the database handle to the same value. + /* If there are no other statements currently running, then + ** reset the interrupt flag. This prevents a call to sqlite3_interrupt + ** from interrupting a statement that has not yet started. */ - rc = sqlite3VdbeTransferError(p); - } - goto end_of_step; - } - if( p->pc<0 ){ - /* If there are no other statements currently running, then - ** reset the interrupt flag. This prevents a call to sqlite3_interrupt - ** from interrupting a statement that has not yet started. - */ - if( db->nVdbeActive==0 ){ - AtomicStore(&db->u1.isInterrupted, 0); - } + if( db->nVdbeActive==0 ){ + AtomicStore(&db->u1.isInterrupted, 0); + } - assert( db->nVdbeWrite>0 || db->autoCommit==0 - || (db->nDeferredCons==0 && db->nDeferredImmCons==0) - ); + assert( db->nVdbeWrite>0 || db->autoCommit==0 + || (db->nDeferredCons==0 && db->nDeferredImmCons==0) + ); #ifndef SQLITE_OMIT_TRACE - if( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0 - && !db->init.busy && p->zSql ){ - sqlite3OsCurrentTimeInt64(db->pVfs, &p->startTime); - }else{ - assert( p->startTime==0 ); - } + if( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0 + && !db->init.busy && p->zSql ){ + sqlite3OsCurrentTimeInt64(db->pVfs, &p->startTime); + }else{ + assert( p->startTime==0 ); + } #endif - db->nVdbeActive++; - if( p->readOnly==0 ) db->nVdbeWrite++; - if( p->bIsReader ) db->nVdbeRead++; - p->pc = 0; + db->nVdbeActive++; + if( p->readOnly==0 ) db->nVdbeWrite++; + if( p->bIsReader ) db->nVdbeRead++; + p->pc = 0; + p->eVdbeState = VDBE_RUN_STATE; + }else + + if( ALWAYS(p->eVdbeState==VDBE_HALT_STATE) ){ + /* We used to require that sqlite3_reset() be called before retrying + ** sqlite3_step() after any error or after SQLITE_DONE. But beginning + ** with version 3.7.0, we changed this so that sqlite3_reset() would + ** be called automatically instead of throwing the SQLITE_MISUSE error. + ** This "automatic-reset" change is not technically an incompatibility, + ** since any application that receives an SQLITE_MISUSE is broken by + ** definition. + ** + ** Nevertheless, some published applications that were originally written + ** for version 3.6.23 or earlier do in fact depend on SQLITE_MISUSE + ** returns, and those were broken by the automatic-reset change. As a + ** a work-around, the SQLITE_OMIT_AUTORESET compile-time restores the + ** legacy behavior of returning SQLITE_MISUSE for cases where the + ** previous sqlite3_step() returned something other than a SQLITE_LOCKED + ** or SQLITE_BUSY error. + */ +#ifdef SQLITE_OMIT_AUTORESET + if( (rc = p->rc&0xff)==SQLITE_BUSY || rc==SQLITE_LOCKED ){ + sqlite3_reset((sqlite3_stmt*)p); + }else{ + return SQLITE_MISUSE_BKPT; + } +#else + sqlite3_reset((sqlite3_stmt*)p); +#endif + assert( p->eVdbeState==VDBE_READY_STATE ); + goto restart_step; + } } + #ifdef SQLITE_DEBUG p->rcApp = SQLITE_OK; #endif @@ -84332,7 +86732,12 @@ static int sqlite3Step(Vdbe *p){ db->nVdbeExec--; } - if( rc!=SQLITE_ROW ){ + if( rc==SQLITE_ROW ){ + assert( p->rc==SQLITE_OK ); + assert( db->mallocFailed==0 ); + db->errCode = SQLITE_ROW; + return SQLITE_ROW; + }else{ #ifndef SQLITE_OMIT_TRACE /* If the statement completed successfully, invoke the profile callback */ checkProfileCallback(db, p); @@ -84384,7 +86789,6 @@ SQLITE_API int sqlite3_step(sqlite3_stmt *pStmt){ } db = v->db; sqlite3_mutex_enter(db->mutex); - v->doingRerun = 0; while( (rc = sqlite3Step(v))==SQLITE_SCHEMA && cnt++ < SQLITE_MAX_SCHEMA_RETRY ){ int savedPc = v->pc; @@ -84410,7 +86814,13 @@ SQLITE_API int sqlite3_step(sqlite3_stmt *pStmt){ break; } sqlite3_reset(pStmt); - if( savedPc>=0 ) v->doingRerun = 1; + if( savedPc>=0 ){ + /* Setting minWriteFileFormat to 254 is a signal to the OP_Init and + ** OP_Trace opcodes to *not* perform SQLITE_TRACE_STMT because it has + ** already been done once on a prior invocation that failed due to + ** SQLITE_SCHEMA. tag-20220401a */ + v->minWriteFileFormat = 254; + } assert( v->expired==0 ); } sqlite3_mutex_leave(db->mutex); @@ -84461,6 +86871,70 @@ SQLITE_API int sqlite3_vtab_nochange(sqlite3_context *p){ return sqlite3_value_nochange(p->pOut); } +/* +** Implementation of sqlite3_vtab_in_first() (if bNext==0) and +** sqlite3_vtab_in_next() (if bNext!=0). +*/ +static int valueFromValueList( + sqlite3_value *pVal, /* Pointer to the ValueList object */ + sqlite3_value **ppOut, /* Store the next value from the list here */ + int bNext /* 1 for _next(). 0 for _first() */ +){ + int rc; + ValueList *pRhs; + + *ppOut = 0; + if( pVal==0 ) return SQLITE_MISUSE; + pRhs = (ValueList*)sqlite3_value_pointer(pVal, "ValueList"); + if( pRhs==0 ) return SQLITE_MISUSE; + if( bNext ){ + rc = sqlite3BtreeNext(pRhs->pCsr, 0); + }else{ + int dummy = 0; + rc = sqlite3BtreeFirst(pRhs->pCsr, &dummy); + assert( rc==SQLITE_OK || sqlite3BtreeEof(pRhs->pCsr) ); + if( sqlite3BtreeEof(pRhs->pCsr) ) rc = SQLITE_DONE; + } + if( rc==SQLITE_OK ){ + u32 sz; /* Size of current row in bytes */ + Mem sMem; /* Raw content of current row */ + memset(&sMem, 0, sizeof(sMem)); + sz = sqlite3BtreePayloadSize(pRhs->pCsr); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pRhs->pCsr,(int)sz,&sMem); + if( rc==SQLITE_OK ){ + u8 *zBuf = (u8*)sMem.z; + u32 iSerial; + sqlite3_value *pOut = pRhs->pOut; + int iOff = 1 + getVarint32(&zBuf[1], iSerial); + sqlite3VdbeSerialGet(&zBuf[iOff], iSerial, pOut); + pOut->enc = ENC(pOut->db); + if( (pOut->flags & MEM_Ephem)!=0 && sqlite3VdbeMemMakeWriteable(pOut) ){ + rc = SQLITE_NOMEM; + }else{ + *ppOut = pOut; + } + } + sqlite3VdbeMemRelease(&sMem); + } + return rc; +} + +/* +** Set the iterator value pVal to point to the first value in the set. +** Set (*ppOut) to point to this value before returning. +*/ +SQLITE_API int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut){ + return valueFromValueList(pVal, ppOut, 0); +} + +/* +** Set the iterator value pVal to point to the next value in the set. +** Set (*ppOut) to point to this value before returning. +*/ +SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut){ + return valueFromValueList(pVal, ppOut, 1); +} + /* ** Return the current time for a statement. If the current time ** is requested more than once within the same run of a single prepared @@ -84655,15 +87129,15 @@ static const Mem *columnNullValue(void){ #endif = { /* .u = */ {0}, + /* .z = */ (char*)0, + /* .n = */ (int)0, /* .flags = */ (u16)MEM_Null, /* .enc = */ (u8)0, /* .eSubtype = */ (u8)0, - /* .n = */ (int)0, - /* .z = */ (char*)0, - /* .zMalloc = */ (char*)0, + /* .db = */ (sqlite3*)0, /* .szMalloc = */ (int)0, /* .uTemp = */ (u32)0, - /* .db = */ (sqlite3*)0, + /* .zMalloc = */ (char*)0, /* .xDel = */ (void(*)(void*))0, #ifdef SQLITE_DEBUG /* .pScopyFrom = */ (Mem*)0, @@ -84960,7 +87434,7 @@ static int vdbeUnbind(Vdbe *p, int i){ return SQLITE_MISUSE_BKPT; } sqlite3_mutex_enter(p->db->mutex); - if( p->iVdbeMagic!=VDBE_MAGIC_RUN || p->pc>=0 ){ + if( p->eVdbeState!=VDBE_READY_STATE ){ sqlite3Error(p->db, SQLITE_MISUSE); sqlite3_mutex_leave(p->db->mutex); sqlite3_log(SQLITE_MISUSE, @@ -85001,7 +87475,7 @@ static int bindText( sqlite3_stmt *pStmt, /* The statement to bind against */ int i, /* Index of the parameter to bind */ const void *zData, /* Pointer to the data to be bound */ - int nData, /* Number of bytes of data to be bound */ + i64 nData, /* Number of bytes of data to be bound */ void (*xDel)(void*), /* Destructor for the data */ u8 encoding /* Encoding for the data */ ){ @@ -85053,11 +87527,7 @@ SQLITE_API int sqlite3_bind_blob64( void (*xDel)(void*) ){ assert( xDel!=SQLITE_DYNAMIC ); - if( nData>0x7fffffff ){ - return invokeValueDestructor(zData, xDel, 0); - }else{ - return bindText(pStmt, i, zData, (int)nData, xDel, 0); - } + return bindText(pStmt, i, zData, nData, xDel, 0); } SQLITE_API int sqlite3_bind_double(sqlite3_stmt *pStmt, int i, double rValue){ int rc; @@ -85127,12 +87597,8 @@ SQLITE_API int sqlite3_bind_text64( unsigned char enc ){ assert( xDel!=SQLITE_DYNAMIC ); - if( nData>0x7fffffff ){ - return invokeValueDestructor(zData, xDel, 0); - }else{ - if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; - return bindText(pStmt, i, zData, (int)nData, xDel, enc); - } + if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; + return bindText(pStmt, i, zData, nData, xDel, enc); } #ifndef SQLITE_OMIT_UTF16 SQLITE_API int sqlite3_bind_text16( @@ -85153,7 +87619,10 @@ SQLITE_API int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_valu break; } case SQLITE_FLOAT: { - rc = sqlite3_bind_double(pStmt, i, pValue->u.r); + assert( pValue->flags & (MEM_Real|MEM_IntReal) ); + rc = sqlite3_bind_double(pStmt, i, + (pValue->flags & MEM_Real) ? pValue->u.r : (double)pValue->u.i + ); break; } case SQLITE_BLOB: { @@ -85181,7 +87650,11 @@ SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt *pStmt, int i, int n){ Vdbe *p = (Vdbe *)pStmt; rc = vdbeUnbind(p, i); if( rc==SQLITE_OK ){ +#ifndef SQLITE_OMIT_INCRBLOB sqlite3VdbeMemSetZeroBlob(&p->aVar[i-1], n); +#else + rc = sqlite3VdbeMemSetZeroBlob(&p->aVar[i-1], n); +#endif sqlite3_mutex_leave(p->db->mutex); } return rc; @@ -85314,7 +87787,7 @@ SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt){ */ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt *pStmt){ Vdbe *v = (Vdbe*)pStmt; - return v!=0 && v->iVdbeMagic==VDBE_MAGIC_RUN && v->pc>=0; + return v!=0 && v->eVdbeState==VDBE_RUN_STATE; } /* @@ -85360,8 +87833,7 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){ sqlite3_mutex_enter(db->mutex); v = 0; db->pnBytesFreed = (int*)&v; - sqlite3VdbeClearObject(db, pVdbe); - sqlite3DbFree(db, pVdbe); + sqlite3VdbeDelete(pVdbe); db->pnBytesFreed = 0; sqlite3_mutex_leave(db->mutex); }else{ @@ -85469,6 +87941,7 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppVa u32 nRec; u8 *aRec; + assert( p->pCsr->eCurType==CURTYPE_BTREE ); nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor); aRec = sqlite3DbMallocRaw(db, nRec); if( !aRec ) goto preupdate_old_out; @@ -85532,6 +88005,17 @@ SQLITE_API int sqlite3_preupdate_depth(sqlite3 *db){ } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** This function is designed to be called from within a pre-update callback +** only. +*/ +SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *db){ + PreUpdate *p = db->pPreUpdate; + return (p ? p->iBlobWrite : -1); +} +#endif + #ifdef SQLITE_ENABLE_PREUPDATE_HOOK /* ** This function is called from within a pre-update callback to retrieve @@ -85765,11 +88249,9 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql( #ifndef SQLITE_OMIT_UTF16 Mem utf8; /* Used to convert UTF16 into UTF8 for display */ #endif - char zBase[100]; /* Initial working space */ db = p->db; - sqlite3StrAccumInit(&out, 0, zBase, sizeof(zBase), - db->aLimit[SQLITE_LIMIT_LENGTH]); + sqlite3StrAccumInit(&out, 0, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]); if( db->nVdbeExec>1 ){ while( *zRawSql ){ const char *zStart = zRawSql; @@ -86119,7 +88601,6 @@ static VdbeCursor *allocateCursor( Vdbe *p, /* The virtual machine */ int iCur, /* Index of the new VdbeCursor */ int nField, /* Number of fields in the table or index */ - int iDb, /* Database the cursor belongs to, or -1 */ u8 eCurType /* Type of the new cursor */ ){ /* Find the memory cell that will be used to store the blob of memory @@ -86145,26 +88626,43 @@ static VdbeCursor *allocateCursor( int nByte; VdbeCursor *pCx = 0; nByte = - ROUND8(sizeof(VdbeCursor)) + 2*sizeof(u32)*nField + + ROUND8P(sizeof(VdbeCursor)) + 2*sizeof(u32)*nField + (eCurType==CURTYPE_BTREE?sqlite3BtreeCursorSize():0); assert( iCur>=0 && iCurnCursor ); if( p->apCsr[iCur] ){ /*OPTIMIZATION-IF-FALSE*/ - sqlite3VdbeFreeCursor(p, p->apCsr[iCur]); + sqlite3VdbeFreeCursorNN(p, p->apCsr[iCur]); p->apCsr[iCur] = 0; } - if( SQLITE_OK==sqlite3VdbeMemClearAndResize(pMem, nByte) ){ - p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->z; - memset(pCx, 0, offsetof(VdbeCursor,pAltCursor)); - pCx->eCurType = eCurType; - pCx->iDb = iDb; - pCx->nField = nField; - pCx->aOffset = &pCx->aType[nField]; - if( eCurType==CURTYPE_BTREE ){ - pCx->uc.pCursor = (BtCursor*) - &pMem->z[ROUND8(sizeof(VdbeCursor))+2*sizeof(u32)*nField]; - sqlite3BtreeCursorZero(pCx->uc.pCursor); + + /* There used to be a call to sqlite3VdbeMemClearAndResize() to make sure + ** the pMem used to hold space for the cursor has enough storage available + ** in pMem->zMalloc. But for the special case of the aMem[] entries used + ** to hold cursors, it is faster to in-line the logic. */ + assert( pMem->flags==MEM_Undefined ); + assert( (pMem->flags & MEM_Dyn)==0 ); + assert( pMem->szMalloc==0 || pMem->z==pMem->zMalloc ); + if( pMem->szMallocszMalloc>0 ){ + sqlite3DbFreeNN(pMem->db, pMem->zMalloc); } + pMem->z = pMem->zMalloc = sqlite3DbMallocRaw(pMem->db, nByte); + if( pMem->zMalloc==0 ){ + pMem->szMalloc = 0; + return 0; + } + pMem->szMalloc = nByte; + } + + p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->zMalloc; + memset(pCx, 0, offsetof(VdbeCursor,pAltCursor)); + pCx->eCurType = eCurType; + pCx->nField = nField; + pCx->aOffset = &pCx->aType[nField]; + if( eCurType==CURTYPE_BTREE ){ + pCx->uc.pCursor = (BtCursor*) + &pMem->z[ROUND8P(sizeof(VdbeCursor))+2*sizeof(u32)*nField]; + sqlite3BtreeCursorZero(pCx->uc.pCursor); } return pCx; } @@ -86311,7 +88809,10 @@ static u16 SQLITE_NOINLINE computeNumericType(Mem *pMem){ sqlite3_int64 ix; assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal))==0 ); assert( (pMem->flags & (MEM_Str|MEM_Blob))!=0 ); - ExpandBlob(pMem); + if( ExpandBlob(pMem) ){ + pMem->u.i = 0; + return MEM_Int; + } rc = sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc); if( rc<=0 ){ if( rc==0 && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1 ){ @@ -86449,6 +88950,11 @@ static void registerTrace(int iReg, Mem *p){ printf("\n"); sqlite3VdbeCheckMemInvariants(p); } +/**/ void sqlite3PrintMem(Mem *pMem){ + memTracePrint(pMem); + printf("\n"); + fflush(stdout); +} #endif #ifdef SQLITE_DEBUG @@ -86476,96 +88982,7 @@ SQLITE_PRIVATE void sqlite3VdbeRegisterDump(Vdbe *v){ ** hwtime.h contains inline assembler code for implementing ** high-performance timing routines. */ -/************** Include hwtime.h in the middle of vdbe.c *********************/ -/************** Begin file hwtime.h ******************************************/ -/* -** 2008 May 27 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This file contains inline asm code for retrieving "high-performance" -** counters for x86 and x86_64 class CPUs. -*/ -#ifndef SQLITE_HWTIME_H -#define SQLITE_HWTIME_H - -/* -** The following routine only works on pentium-class (or newer) processors. -** It uses the RDTSC opcode to read the cycle count value out of the -** processor and returns that value. This can be used for high-res -** profiling. -*/ -#if !defined(__STRICT_ANSI__) && \ - (defined(__GNUC__) || defined(_MSC_VER)) && \ - (defined(i386) || defined(__i386__) || defined(_M_IX86)) - - #if defined(__GNUC__) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned int lo, hi; - __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); - return (sqlite_uint64)hi << 32 | lo; - } - - #elif defined(_MSC_VER) - - __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){ - __asm { - rdtsc - ret ; return value at EDX:EAX - } - } - - #endif - -#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__)) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long val; - __asm__ __volatile__ ("rdtsc" : "=A" (val)); - return val; - } - -#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__)) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long long retval; - unsigned long junk; - __asm__ __volatile__ ("\n\ - 1: mftbu %1\n\ - mftb %L0\n\ - mftbu %0\n\ - cmpw %0,%1\n\ - bne 1b" - : "=r" (retval), "=r" (junk)); - return retval; - } - -#else - - /* - ** asm() is needed for hardware timing support. Without asm(), - ** disable the sqlite3Hwtime() routine. - ** - ** sqlite3Hwtime() is only used for some obscure debugging - ** and analysis configurations, not in any deliverable, so this - ** should not be a great loss. - */ -SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); } - -#endif - -#endif /* !defined(SQLITE_HWTIME_H) */ - -/************** End of hwtime.h **********************************************/ -/************** Continuing where we left off in vdbe.c ***********************/ +/* #include "hwtime.h" */ #endif @@ -86612,6 +89029,42 @@ static Mem *out2Prerelease(Vdbe *p, VdbeOp *pOp){ } } +/* +** Compute a bloom filter hash using pOp->p4.i registers from aMem[] beginning +** with pOp->p3. Return the hash. +*/ +static u64 filterHash(const Mem *aMem, const Op *pOp){ + int i, mx; + u64 h = 0; + + assert( pOp->p4type==P4_INT32 ); + for(i=pOp->p3, mx=i+pOp->p4.i; iflags & (MEM_Int|MEM_IntReal) ){ + h += p->u.i; + }else if( p->flags & MEM_Real ){ + h += sqlite3VdbeIntValue(p); + }else if( p->flags & (MEM_Str|MEM_Blob) ){ + h += p->n; + if( p->flags & MEM_Zero ) h += p->u.nZero; + } + } + return h; +} + +/* +** Return the symbolic name for the data type of a pMem +*/ +static const char *vdbeMemTypeName(Mem *pMem){ + static const char *azTypes[] = { + /* SQLITE_INTEGER */ "INT", + /* SQLITE_FLOAT */ "REAL", + /* SQLITE_TEXT */ "TEXT", + /* SQLITE_BLOB */ "BLOB", + /* SQLITE_NULL */ "NULL" + }; + return azTypes[sqlite3_value_type(pMem)-1]; +} /* ** Execute as much of a VDBE program as we can. @@ -86647,7 +89100,7 @@ SQLITE_PRIVATE int sqlite3VdbeExec( #endif /*** INSERT STACK UNION HERE ***/ - assert( p->iVdbeMagic==VDBE_MAGIC_RUN ); /* sqlite3_step() verifies this */ + assert( p->eVdbeState==VDBE_RUN_STATE ); /* sqlite3_step() verifies this */ sqlite3VdbeEnter(p); #ifndef SQLITE_OMIT_PROGRESS_CALLBACK if( db->xProgress ){ @@ -86890,24 +89343,39 @@ case OP_Gosub: { /* jump */ pIn1->flags = MEM_Int; pIn1->u.i = (int)(pOp-aOp); REGISTER_TRACE(pOp->p1, pIn1); - - /* Most jump operations do a goto to this spot in order to update - ** the pOp pointer. */ -jump_to_p2: - pOp = &aOp[pOp->p2 - 1]; - break; + goto jump_to_p2_and_check_for_interrupt; } -/* Opcode: Return P1 * * * * +/* Opcode: Return P1 P2 P3 * * ** -** Jump to the next instruction after the address in register P1. After -** the jump, register P1 becomes undefined. +** Jump to the address stored in register P1. If P1 is a return address +** register, then this accomplishes a return from a subroutine. +** +** If P3 is 1, then the jump is only taken if register P1 holds an integer +** values, otherwise execution falls through to the next opcode, and the +** OP_Return becomes a no-op. If P3 is 0, then register P1 must hold an +** integer or else an assert() is raised. P3 should be set to 1 when +** this opcode is used in combination with OP_BeginSubrtn, and set to 0 +** otherwise. +** +** The value in register P1 is unchanged by this opcode. +** +** P2 is not used by the byte-code engine. However, if P2 is positive +** and also less than the current address, then the "EXPLAIN" output +** formatter in the CLI will indent all opcodes from the P2 opcode up +** to be not including the current Return. P2 should be the first opcode +** in the subroutine from which this opcode is returning. Thus the P2 +** value is a byte-code indentation hint. See tag-20220407a in +** wherecode.c and shell.c. */ case OP_Return: { /* in1 */ pIn1 = &aMem[pOp->p1]; - assert( pIn1->flags==MEM_Int ); - pOp = &aOp[pIn1->u.i]; - pIn1->flags = MEM_Undefined; + if( pIn1->flags & MEM_Int ){ + if( pOp->p3 ){ VdbeBranchTaken(1, 2); } + pOp = &aOp[pIn1->u.i]; + }else if( ALWAYS(pOp->p3) ){ + VdbeBranchTaken(0, 2); + } break; } @@ -86930,7 +89398,14 @@ case OP_InitCoroutine: { /* jump */ assert( !VdbeMemDynamic(pOut) ); pOut->u.i = pOp->p3 - 1; pOut->flags = MEM_Int; - if( pOp->p2 ) goto jump_to_p2; + if( pOp->p2==0 ) break; + + /* Most jump operations do a goto to this spot in order to update + ** the pOp pointer. */ +jump_to_p2: + assert( pOp->p2>0 ); /* There are never any jumps to instruction 0 */ + assert( pOp->p2nOp ); /* Jumps must be in range */ + pOp = &aOp[pOp->p2 - 1]; break; } @@ -87032,11 +89507,10 @@ case OP_Halt: { VdbeFrame *pFrame; int pcx; - pcx = (int)(pOp - aOp); #ifdef SQLITE_DEBUG if( pOp->p2==OE_Abort ){ sqlite3VdbeAssertAbortable(p); } #endif - if( pOp->p1==SQLITE_OK && p->pFrame ){ + if( p->pFrame && pOp->p1==SQLITE_OK ){ /* Halt the sub-program. Return control to the parent frame. */ pFrame = p->pFrame; p->pFrame = pFrame->pParent; @@ -87058,7 +89532,6 @@ case OP_Halt: { } p->rc = pOp->p1; p->errorAction = (u8)pOp->p2; - p->pc = pcx; assert( pOp->p5<=4 ); if( p->rc ){ if( pOp->p5 ){ @@ -87075,6 +89548,7 @@ case OP_Halt: { }else{ sqlite3VdbeError(p, "%s", pOp->p4.z); } + pcx = (int)(pOp - aOp); sqlite3_log(pOp->p1, "abort at %d in [%s]: %s", pcx, p->zSql, p->zErrMsg); } rc = sqlite3VdbeHalt(p); @@ -87200,6 +89674,28 @@ case OP_String: { /* out2 */ break; } +/* Opcode: BeginSubrtn * P2 * * * +** Synopsis: r[P2]=NULL +** +** Mark the beginning of a subroutine that can be entered in-line +** or that can be called using OP_Gosub. The subroutine should +** be terminated by an OP_Return instruction that has a P1 operand that +** is the same as the P2 operand to this opcode and that has P3 set to 1. +** If the subroutine is entered in-line, then the OP_Return will simply +** fall through. But if the subroutine is entered using OP_Gosub, then +** the OP_Return will jump back to the first instruction after the OP_Gosub. +** +** This routine works by loading a NULL into the P2 register. When the +** return address register contains a NULL, the OP_Return instruction is +** a no-op that simply falls through to the next instruction (assuming that +** the OP_Return opcode has a P3 value of 1). Thus if the subroutine is +** entered in-line, then the OP_Return will cause in-line execution to +** continue. But if the subroutine is entered via OP_Gosub, then the +** OP_Return will cause a return to the address following the OP_Gosub. +** +** This opcode is identical to OP_Null. It has a different name +** only to make the byte code easier to read and verify. +*/ /* Opcode: Null P1 P2 P3 * * ** Synopsis: r[P2..P3]=NULL ** @@ -87212,6 +89708,7 @@ case OP_String: { /* out2 */ ** NULL values will not compare equal even if SQLITE_NULLEQ is set on ** OP_Ne or OP_Eq. */ +case OP_BeginSubrtn: case OP_Null: { /* out2 */ int cnt; u16 nullFlag; @@ -87253,12 +89750,18 @@ case OP_SoftNull: { ** Synopsis: r[P2]=P4 (len=P1) ** ** P4 points to a blob of data P1 bytes long. Store this -** blob in register P2. +** blob in register P2. If P4 is a NULL pointer, then construct +** a zero-filled blob that is P1 bytes long in P2. */ case OP_Blob: { /* out2 */ assert( pOp->p1 <= SQLITE_MAX_LENGTH ); pOut = out2Prerelease(p, pOp); - sqlite3VdbeMemSetStr(pOut, pOp->p4.z, pOp->p1, 0, 0); + if( pOp->p4.z==0 ){ + sqlite3VdbeMemSetZeroBlob(pOut, pOp->p1); + if( sqlite3VdbeMemExpandBlob(pOut) ) goto no_mem; + }else{ + sqlite3VdbeMemSetStr(pOut, pOp->p4.z, pOp->p1, 0, 0); + } pOut->enc = encoding; UPDATE_MAX_BLOBSIZE(pOut); break; @@ -87336,11 +89839,16 @@ case OP_Move: { break; } -/* Opcode: Copy P1 P2 P3 * * +/* Opcode: Copy P1 P2 P3 * P5 ** Synopsis: r[P2@P3+1]=r[P1@P3+1] ** ** Make a copy of registers P1..P1+P3 into registers P2..P2+P3. ** +** If the 0x0002 bit of P5 is set then also clear the MEM_Subtype flag in the +** destination. The 0x0001 bit of P5 indicates that this Copy opcode cannot +** be merged. The 0x0001 bit is used by the query planner and does not +** come into play during query execution. +** ** This instruction makes a deep copy of the value. A duplicate ** is made of any string or blob constant. See also OP_SCopy. */ @@ -87355,6 +89863,9 @@ case OP_Copy: { memAboutToChange(p, pOut); sqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem); Deephemeralize(pOut); + if( (pOut->flags & MEM_Subtype)!=0 && (pOp->p5 & 0x0002)!=0 ){ + pOut->flags &= ~MEM_Subtype; + } #ifdef SQLITE_DEBUG pOut->pScopyFrom = 0; #endif @@ -87407,24 +89918,22 @@ case OP_IntCopy: { /* out2 */ break; } -/* Opcode: ChngCntRow P1 P2 * * * -** Synopsis: output=r[P1] +/* Opcode: FkCheck * * * * * ** -** Output value in register P1 as the chance count for a DML statement, -** due to the "PRAGMA count_changes=ON" setting. Or, if there was a -** foreign key error in the statement, trigger the error now. +** Halt with an SQLITE_CONSTRAINT error if there are any unresolved +** foreign key constraint violations. If there are no foreign key +** constraint violations, this is a no-op. ** -** This opcode is a variant of OP_ResultRow that checks the foreign key -** immediate constraint count and throws an error if the count is -** non-zero. The P2 opcode must be 1. +** FK constraint violations are also checked when the prepared statement +** exits. This opcode is used to raise foreign key constraint errors prior +** to returning results such as a row change count or the result of a +** RETURNING clause. */ -case OP_ChngCntRow: { - assert( pOp->p2==1 ); +case OP_FkCheck: { if( (rc = sqlite3VdbeCheckFk(p,0))!=SQLITE_OK ){ goto abort_due_to_error; } - /* Fall through to the next case, OP_ResultRow */ - /* no break */ deliberate_fall_through + break; } /* Opcode: ResultRow P1 P2 * * * @@ -87437,45 +89946,32 @@ case OP_ChngCntRow: { ** the result row. */ case OP_ResultRow: { - Mem *pMem; - int i; assert( p->nResColumn==pOp->p2 ); - assert( pOp->p1>0 ); + assert( pOp->p1>0 || CORRUPT_DB ); assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 ); - /* Invalidate all ephemeral cursor row caches */ p->cacheCtr = (p->cacheCtr + 2)|1; - - /* Make sure the results of the current row are \000 terminated - ** and have an assigned type. The results are de-ephemeralized as - ** a side effect. - */ - pMem = p->pResultSet = &aMem[pOp->p1]; - for(i=0; ip2; i++){ - assert( memIsValid(&pMem[i]) ); - Deephemeralize(&pMem[i]); - assert( (pMem[i].flags & MEM_Ephem)==0 - || (pMem[i].flags & (MEM_Str|MEM_Blob))==0 ); - sqlite3VdbeMemNulTerminate(&pMem[i]); - REGISTER_TRACE(pOp->p1+i, &pMem[i]); + p->pResultSet = &aMem[pOp->p1]; #ifdef SQLITE_DEBUG - /* The registers in the result will not be used again when the - ** prepared statement restarts. This is because sqlite3_column() - ** APIs might have caused type conversions of made other changes to - ** the register values. Therefore, we can go ahead and break any - ** OP_SCopy dependencies. */ - pMem[i].pScopyFrom = 0; -#endif + { + Mem *pMem = p->pResultSet; + int i; + for(i=0; ip2; i++){ + assert( memIsValid(&pMem[i]) ); + REGISTER_TRACE(pOp->p1+i, &pMem[i]); + /* The registers in the result will not be used again when the + ** prepared statement restarts. This is because sqlite3_column() + ** APIs might have caused type conversions of made other changes to + ** the register values. Therefore, we can go ahead and break any + ** OP_SCopy dependencies. */ + pMem[i].pScopyFrom = 0; + } } +#endif if( db->mallocFailed ) goto no_mem; - if( db->mTrace & SQLITE_TRACE_ROW ){ db->trace.xV2(SQLITE_TRACE_ROW, db->pTraceArg, p, 0); } - - - /* Return SQLITE_ROW - */ p->pc = (int)(pOp - aOp) + 1; rc = SQLITE_ROW; goto vdbe_return; @@ -87530,7 +90026,7 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ goto too_big; } - if( sqlite3VdbeMemGrow(pOut, (int)nByte+3, pOut==pIn2) ){ + if( sqlite3VdbeMemGrow(pOut, (int)nByte+2, pOut==pIn2) ){ goto no_mem; } MemSetTypeFlag(pOut, MEM_Str); @@ -87542,9 +90038,9 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ memcpy(&pOut->z[pIn2->n], pIn1->z, pIn1->n); assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) ); pIn1->flags = flags1; + if( encoding>SQLITE_UTF8 ) nByte &= ~1; pOut->z[nByte]=0; pOut->z[nByte+1] = 0; - pOut->z[nByte+2] = 0; pOut->flags |= MEM_Term; pOut->n = (int)nByte; pOut->enc = encoding; @@ -87882,8 +90378,7 @@ case OP_Cast: { /* in1 */ ** Synopsis: IF r[P3]==r[P1] ** ** Compare the values in register P1 and P3. If reg(P3)==reg(P1) then -** jump to address P2. Or if the SQLITE_STOREP2 flag is set in P5, then -** store the result of comparison in register P2. +** jump to address P2. ** ** The SQLITE_AFF_MASK portion of P5 must be an affinity character - ** SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth. An attempt is made @@ -87909,9 +90404,8 @@ case OP_Cast: { /* in1 */ ** If neither operand is NULL the result is the same as it would be if ** the SQLITE_NULLEQ flag were omitted from P5. ** -** If both SQLITE_STOREP2 and SQLITE_KEEPNULL flags are set then the -** content of r[P2] is only changed if the new value is NULL or 0 (false). -** In other words, a prior r[P2] value will not be overwritten by 1 (true). +** This opcode saves the result of comparison for use by the new +** OP_Jump opcode. */ /* Opcode: Ne P1 P2 P3 P4 P5 ** Synopsis: IF r[P3]!=r[P1] @@ -87919,17 +90413,12 @@ case OP_Cast: { /* in1 */ ** This works just like the Eq opcode except that the jump is taken if ** the operands in registers P1 and P3 are not equal. See the Eq opcode for ** additional information. -** -** If both SQLITE_STOREP2 and SQLITE_KEEPNULL flags are set then the -** content of r[P2] is only changed if the new value is NULL or 1 (true). -** In other words, a prior r[P2] value will not be overwritten by 0 (false). */ /* Opcode: Lt P1 P2 P3 P4 P5 ** Synopsis: IF r[P3]p3]; flags1 = pIn1->flags; flags3 = pIn3->flags; + if( (flags1 & flags3 & MEM_Int)!=0 ){ + assert( (pOp->p5 & SQLITE_AFF_MASK)!=SQLITE_AFF_TEXT || CORRUPT_DB ); + /* Common case of comparison of two integers */ + if( pIn3->u.i > pIn1->u.i ){ + if( sqlite3aGTb[pOp->opcode] ){ + VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); + goto jump_to_p2; + } + iCompare = +1; + }else if( pIn3->u.i < pIn1->u.i ){ + if( sqlite3aLTb[pOp->opcode] ){ + VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); + goto jump_to_p2; + } + iCompare = -1; + }else{ + if( sqlite3aEQb[pOp->opcode] ){ + VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); + goto jump_to_p2; + } + iCompare = 0; + } + VdbeBranchTaken(0, (pOp->p5 & SQLITE_NULLEQ)?2:3); + break; + } if( (flags1 | flags3)&MEM_Null ){ /* One or both operands are NULL */ if( pOp->p5 & SQLITE_NULLEQ ){ @@ -88011,22 +90528,16 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ ** then the result is always NULL. ** The jump is taken if the SQLITE_JUMPIFNULL bit is set. */ - if( pOp->p5 & SQLITE_STOREP2 ){ - pOut = &aMem[pOp->p2]; - iCompare = 1; /* Operands are not equal */ - memAboutToChange(p, pOut); - MemSetTypeFlag(pOut, MEM_Null); - REGISTER_TRACE(pOp->p2, pOut); - }else{ - VdbeBranchTaken(2,3); - if( pOp->p5 & SQLITE_JUMPIFNULL ){ - goto jump_to_p2; - } + VdbeBranchTaken(2,3); + if( pOp->p5 & SQLITE_JUMPIFNULL ){ + goto jump_to_p2; } + iCompare = 1; /* Operands are not equal */ break; } }else{ - /* Neither operand is NULL. Do a comparison. */ + /* Neither operand is NULL and we couldn't do the special high-speed + ** integer comparison case. So do a general-case comparison. */ affinity = pOp->p5 & SQLITE_AFF_MASK; if( affinity>=SQLITE_AFF_NUMERIC ){ if( (flags1 | flags3)&MEM_Str ){ @@ -88039,14 +90550,6 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ applyNumericAffinity(pIn3,0); } } - /* Handle the common case of integer comparison here, as an - ** optimization, to avoid a call to sqlite3MemCompare() */ - if( (pIn1->flags & pIn3->flags & MEM_Int)!=0 ){ - if( pIn3->u.i > pIn1->u.i ){ res = +1; goto compare_op; } - if( pIn3->u.i < pIn1->u.i ){ res = -1; goto compare_op; } - res = 0; - goto compare_op; - } }else if( affinity==SQLITE_AFF_TEXT ){ if( (flags1 & MEM_Str)==0 && (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ testcase( pIn1->flags & MEM_Int ); @@ -88055,7 +90558,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ sqlite3VdbeMemStringify(pIn1, encoding, 1); testcase( (flags1&MEM_Dyn) != (pIn1->flags&MEM_Dyn) ); flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask); - if( NEVER(pIn1==pIn3) ) flags3 = flags1 | MEM_Str; + if( pIn1==pIn3 ) flags3 = flags1 | MEM_Str; } if( (flags3 & MEM_Str)==0 && (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ testcase( pIn3->flags & MEM_Int ); @@ -88069,7 +90572,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ assert( pOp->p4type==P4_COLLSEQ || pOp->p4.pColl==0 ); res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl); } -compare_op: + /* At this point, res is negative, zero, or positive if reg[P1] is ** less than, equal to, or greater than reg[P3], respectively. Compute ** the answer to this operator in res2, depending on what the comparison @@ -88078,16 +90581,14 @@ compare_op: ** order: NE, EQ, GT, LE, LT, GE */ assert( OP_Eq==OP_Ne+1 ); assert( OP_Gt==OP_Ne+2 ); assert( OP_Le==OP_Ne+3 ); assert( OP_Lt==OP_Ne+4 ); assert( OP_Ge==OP_Ne+5 ); - if( res<0 ){ /* ne, eq, gt, le, lt, ge */ - static const unsigned char aLTb[] = { 1, 0, 0, 1, 1, 0 }; - res2 = aLTb[pOp->opcode - OP_Ne]; + if( res<0 ){ + res2 = sqlite3aLTb[pOp->opcode]; }else if( res==0 ){ - static const unsigned char aEQb[] = { 0, 1, 0, 1, 0, 1 }; - res2 = aEQb[pOp->opcode - OP_Ne]; + res2 = sqlite3aEQb[pOp->opcode]; }else{ - static const unsigned char aGTb[] = { 1, 0, 1, 0, 0, 1 }; - res2 = aGTb[pOp->opcode - OP_Ne]; + res2 = sqlite3aGTb[pOp->opcode]; } + iCompare = res; /* Undo any changes made by applyAffinity() to the input registers. */ assert( (pIn3->flags & MEM_Dyn) == (flags3 & MEM_Dyn) ); @@ -88095,67 +90596,39 @@ compare_op: assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) ); pIn1->flags = flags1; - if( pOp->p5 & SQLITE_STOREP2 ){ - pOut = &aMem[pOp->p2]; - iCompare = res; - if( (pOp->p5 & SQLITE_KEEPNULL)!=0 ){ - /* The KEEPNULL flag prevents OP_Eq from overwriting a NULL with 1 - ** and prevents OP_Ne from overwriting NULL with 0. This flag - ** is only used in contexts where either: - ** (1) op==OP_Eq && (r[P2]==NULL || r[P2]==0) - ** (2) op==OP_Ne && (r[P2]==NULL || r[P2]==1) - ** Therefore it is not necessary to check the content of r[P2] for - ** NULL. */ - assert( pOp->opcode==OP_Ne || pOp->opcode==OP_Eq ); - assert( res2==0 || res2==1 ); - testcase( res2==0 && pOp->opcode==OP_Eq ); - testcase( res2==1 && pOp->opcode==OP_Eq ); - testcase( res2==0 && pOp->opcode==OP_Ne ); - testcase( res2==1 && pOp->opcode==OP_Ne ); - if( (pOp->opcode==OP_Eq)==res2 ) break; - } - memAboutToChange(p, pOut); - MemSetTypeFlag(pOut, MEM_Int); - pOut->u.i = res2; - REGISTER_TRACE(pOp->p2, pOut); - }else{ - VdbeBranchTaken(res2!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3); - if( res2 ){ - goto jump_to_p2; - } + VdbeBranchTaken(res2!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3); + if( res2 ){ + goto jump_to_p2; } break; } -/* Opcode: ElseNotEq * P2 * * * +/* Opcode: ElseEq * P2 * * * ** ** This opcode must follow an OP_Lt or OP_Gt comparison operator. There ** can be zero or more OP_ReleaseReg opcodes intervening, but no other ** opcodes are allowed to occur between this instruction and the previous -** OP_Lt or OP_Gt. Furthermore, the prior OP_Lt or OP_Gt must have the -** SQLITE_STOREP2 bit set in the P5 field. +** OP_Lt or OP_Gt. ** ** If result of an OP_Eq comparison on the same two operands as the -** prior OP_Lt or OP_Gt would have been NULL or false (0), then then -** jump to P2. If the result of an OP_Eq comparison on the two previous -** operands would have been true (1), then fall through. +** prior OP_Lt or OP_Gt would have been true, then jump to P2. +** If the result of an OP_Eq comparison on the two previous +** operands would have been false or NULL, then fall through. */ -case OP_ElseNotEq: { /* same as TK_ESCAPE, jump */ +case OP_ElseEq: { /* same as TK_ESCAPE, jump */ #ifdef SQLITE_DEBUG /* Verify the preconditions of this opcode - that it follows an OP_Lt or - ** OP_Gt with the SQLITE_STOREP2 flag set, with zero or more intervening - ** OP_ReleaseReg opcodes */ + ** OP_Gt with zero or more intervening OP_ReleaseReg opcodes */ int iAddr; for(iAddr = (int)(pOp - aOp) - 1; ALWAYS(iAddr>=0); iAddr--){ if( aOp[iAddr].opcode==OP_ReleaseReg ) continue; assert( aOp[iAddr].opcode==OP_Lt || aOp[iAddr].opcode==OP_Gt ); - assert( aOp[iAddr].p5 & SQLITE_STOREP2 ); break; } #endif /* SQLITE_DEBUG */ - VdbeBranchTaken(iCompare!=0, 2); - if( iCompare!=0 ) goto jump_to_p2; + VdbeBranchTaken(iCompare==0, 2); + if( iCompare==0 ) goto jump_to_p2; break; } @@ -88165,9 +90638,8 @@ case OP_ElseNotEq: { /* same as TK_ESCAPE, jump */ ** Set the permutation used by the OP_Compare operator in the next ** instruction. The permutation is stored in the P4 operand. ** -** The permutation is only valid until the next OP_Compare that has -** the OPFLAG_PERMUTE bit set in P5. Typically the OP_Permutation should -** occur immediately prior to the OP_Compare. +** The permutation is only valid for the next opcode which must be +** an OP_Compare that has the OPFLAG_PERMUTE bit set in P5. ** ** The first integer in the P4 integer array is the length of the array ** and does not become part of the permutation. @@ -88199,6 +90671,8 @@ case OP_Permutation: { ** The comparison is a sort comparison, so NULLs compare equal, ** NULLs are less than numbers, numbers are less than strings, ** and strings are less than blobs. +** +** This opcode must be immediately followed by an OP_Jump opcode. */ case OP_Compare: { int n; @@ -88257,6 +90731,7 @@ case OP_Compare: { break; } } + assert( pOp[1].opcode==OP_Jump ); break; } @@ -88265,8 +90740,11 @@ case OP_Compare: { ** Jump to the instruction at address P1, P2, or P3 depending on whether ** in the most recent OP_Compare instruction the P1 vector was less than ** equal to, or greater than the P2 vector, respectively. +** +** This opcode must immediately follow an OP_Compare opcode. */ case OP_Jump: { /* jump */ + assert( pOp>aOp && pOp[-1].opcode==OP_Compare ); if( iCompare<0 ){ VdbeBranchTaken(0,4); pOp = &aOp[pOp->p1 - 1]; }else if( iCompare==0 ){ @@ -88466,6 +90944,40 @@ case OP_IsNull: { /* same as TK_ISNULL, jump, in1 */ break; } +/* Opcode: IsNullOrType P1 P2 P3 * * +** Synopsis: if typeof(r[P1]) IN (P3,5) goto P2 +** +** Jump to P2 if the value in register P1 is NULL or has a datatype P3. +** P3 is an integer which should be one of SQLITE_INTEGER, SQLITE_FLOAT, +** SQLITE_BLOB, SQLITE_NULL, or SQLITE_TEXT. +*/ +case OP_IsNullOrType: { /* jump, in1 */ + int doTheJump; + pIn1 = &aMem[pOp->p1]; + doTheJump = (pIn1->flags & MEM_Null)!=0 || sqlite3_value_type(pIn1)==pOp->p3; + VdbeBranchTaken( doTheJump, 2); + if( doTheJump ) goto jump_to_p2; + break; +} + +/* Opcode: ZeroOrNull P1 P2 P3 * * +** Synopsis: r[P2] = 0 OR NULL +** +** If all both registers P1 and P3 are NOT NULL, then store a zero in +** register P2. If either registers P1 or P3 are NULL then put +** a NULL in register P2. +*/ +case OP_ZeroOrNull: { /* in1, in2, out2, in3 */ + if( (aMem[pOp->p1].flags & MEM_Null)!=0 + || (aMem[pOp->p3].flags & MEM_Null)!=0 + ){ + sqlite3VdbeMemSetNull(aMem + pOp->p2); + }else{ + sqlite3VdbeMemSetInt64(aMem + pOp->p2, 0); + } + break; +} + /* Opcode: NotNull P1 P2 * * * ** Synopsis: if r[P1]!=NULL goto P2 ** @@ -88519,17 +91031,25 @@ case OP_Offset: { /* out3 */ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; pOut = &p->aMem[pOp->p3]; - if( NEVER(pC==0) || pC->eCurType!=CURTYPE_BTREE ){ + if( pC==0 || pC->eCurType!=CURTYPE_BTREE ){ sqlite3VdbeMemSetNull(pOut); }else{ - sqlite3VdbeMemSetInt64(pOut, sqlite3BtreeOffset(pC->uc.pCursor)); + if( pC->deferredMoveto ){ + rc = sqlite3VdbeFinishMoveto(pC); + if( rc ) goto abort_due_to_error; + } + if( sqlite3BtreeEof(pC->uc.pCursor) ){ + sqlite3VdbeMemSetNull(pOut); + }else{ + sqlite3VdbeMemSetInt64(pOut, sqlite3BtreeOffset(pC->uc.pCursor)); + } } break; } #endif /* SQLITE_ENABLE_OFFSET_SQL_FUNC */ /* Opcode: Column P1 P2 P3 P4 P5 -** Synopsis: r[P3]=PX +** Synopsis: r[P3]=PX cursor P1 column P2 ** ** Interpret the data that cursor P1 points to as a structure built using ** the MakeRecord instruction. (See the MakeRecord opcode for additional @@ -88551,7 +91071,7 @@ case OP_Offset: { /* out3 */ case OP_Column: { u32 p2; /* column number to retrieve */ VdbeCursor *pC; /* The VDBE cursor */ - BtCursor *pCrsr; /* The BTree cursor */ + BtCursor *pCrsr; /* The B-Tree cursor corresponding to pC */ u32 *aOffset; /* aOffset[i] is offset to start of data for i-th column */ int len; /* The length of the serialized data for the column */ int i; /* Loop counter */ @@ -88565,43 +91085,53 @@ case OP_Column: { Mem *pReg; /* PseudoTable input register */ assert( pOp->p1>=0 && pOp->p1nCursor ); + assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); pC = p->apCsr[pOp->p1]; - assert( pC!=0 ); p2 = (u32)pOp->p2; - /* If the cursor cache is stale (meaning it is not currently point at - ** the correct row) then bring it up-to-date by doing the necessary - ** B-Tree seek. */ - rc = sqlite3VdbeCursorMoveto(&pC, &p2); - if( rc ) goto abort_due_to_error; - - assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); - pDest = &aMem[pOp->p3]; - memAboutToChange(p, pDest); +op_column_restart: assert( pC!=0 ); - assert( p2<(u32)pC->nField ); + assert( p2<(u32)pC->nField + || (pC->eCurType==CURTYPE_PSEUDO && pC->seekResult==0) ); aOffset = pC->aOffset; + assert( aOffset==pC->aType+pC->nField ); assert( pC->eCurType!=CURTYPE_VTAB ); assert( pC->eCurType!=CURTYPE_PSEUDO || pC->nullRow ); assert( pC->eCurType!=CURTYPE_SORTER ); if( pC->cacheStatus!=p->cacheCtr ){ /*OPTIMIZATION-IF-FALSE*/ if( pC->nullRow ){ - if( pC->eCurType==CURTYPE_PSEUDO ){ + if( pC->eCurType==CURTYPE_PSEUDO && pC->seekResult>0 ){ /* For the special case of as pseudo-cursor, the seekResult field ** identifies the register that holds the record */ - assert( pC->seekResult>0 ); pReg = &aMem[pC->seekResult]; assert( pReg->flags & MEM_Blob ); assert( memIsValid(pReg) ); pC->payloadSize = pC->szRow = pReg->n; pC->aRow = (u8*)pReg->z; }else{ + pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); sqlite3VdbeMemSetNull(pDest); goto op_column_out; } }else{ pCrsr = pC->uc.pCursor; + if( pC->deferredMoveto ){ + u32 iMap; + assert( !pC->isEphemeral ); + if( pC->ub.aAltMap && (iMap = pC->ub.aAltMap[1+p2])>0 ){ + pC = pC->pAltCursor; + p2 = iMap - 1; + goto op_column_restart; + } + rc = sqlite3VdbeFinishMoveto(pC); + if( rc ) goto abort_due_to_error; + }else if( sqlite3BtreeCursorHasMoved(pCrsr) ){ + rc = sqlite3VdbeHandleMovedCursor(pC); + if( rc ) goto abort_due_to_error; + goto op_column_restart; + } assert( pC->eCurType==CURTYPE_BTREE ); assert( pCrsr ); assert( sqlite3BtreeCursorIsValid(pCrsr) ); @@ -88609,15 +91139,15 @@ case OP_Column: { pC->aRow = sqlite3BtreePayloadFetch(pCrsr, &pC->szRow); assert( pC->szRow<=pC->payloadSize ); assert( pC->szRow<=65536 ); /* Maximum page size is 64KiB */ - if( pC->payloadSize > (u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){ - goto too_big; - } } pC->cacheStatus = p->cacheCtr; - pC->iHdrOffset = getVarint32(pC->aRow, aOffset[0]); + if( (aOffset[0] = pC->aRow[0])<0x80 ){ + pC->iHdrOffset = 1; + }else{ + pC->iHdrOffset = sqlite3GetVarint32(pC->aRow, aOffset); + } pC->nHdrParsed = 0; - if( pC->szRowaRow does not have to hold the entire row, but it does at least ** need to cover the header of the record. If pC->aRow does not contain @@ -88657,6 +91187,10 @@ case OP_Column: { testcase( aOffset[0]==0 ); goto op_column_read_header; } + }else if( sqlite3BtreeCursorHasMoved(pC->uc.pCursor) ){ + rc = sqlite3VdbeHandleMovedCursor(pC); + if( rc ) goto abort_due_to_error; + goto op_column_restart; } /* Make sure at least the first p2+1 entries of the header have been @@ -88725,6 +91259,8 @@ case OP_Column: { ** columns. So the result will be either the default value or a NULL. */ if( pC->nHdrParsed<=p2 ){ + pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); if( pOp->p4type==P4_MEM ){ sqlite3VdbeMemShallowCopy(pDest, pOp->p4.pMem, MEM_Static); }else{ @@ -88742,6 +91278,8 @@ case OP_Column: { */ assert( p2nHdrParsed ); assert( rc==SQLITE_OK ); + pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); assert( sqlite3VdbeCheckMemInvariants(pDest) ); if( VdbeMemDynamic(pDest) ){ sqlite3VdbeMemSetNull(pDest); @@ -88762,6 +91300,7 @@ case OP_Column: { pDest->n = len = (t-12)/2; pDest->enc = encoding; if( pDest->szMalloc < len+2 ){ + if( len>db->aLimit[SQLITE_LIMIT_LENGTH] ) goto too_big; pDest->flags = MEM_Null; if( sqlite3VdbeMemGrow(pDest, len+2, 0) ) goto no_mem; }else{ @@ -88794,6 +91333,7 @@ case OP_Column: { */ sqlite3VdbeSerialGet((u8*)sqlite3CtypeMap, t, pDest); }else{ + if( len>db->aLimit[SQLITE_LIMIT_LENGTH] ) goto too_big; rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, aOffset[p2], len, pDest); if( rc!=SQLITE_OK ) goto abort_due_to_error; sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest); @@ -88816,6 +91356,110 @@ op_column_corrupt: } } +/* Opcode: TypeCheck P1 P2 P3 P4 * +** Synopsis: typecheck(r[P1@P2]) +** +** Apply affinities to the range of P2 registers beginning with P1. +** Take the affinities from the Table object in P4. If any value +** cannot be coerced into the correct type, then raise an error. +** +** This opcode is similar to OP_Affinity except that this opcode +** forces the register type to the Table column type. This is used +** to implement "strict affinity". +** +** GENERATED ALWAYS AS ... STATIC columns are only checked if P3 +** is zero. When P3 is non-zero, no type checking occurs for +** static generated columns. Virtual columns are computed at query time +** and so they are never checked. +** +** Preconditions: +** +**
      +**
    • P2 should be the number of non-virtual columns in the +** table of P4. +**
    • Table P4 should be a STRICT table. +**
    +** +** If any precondition is false, an assertion fault occurs. +*/ +case OP_TypeCheck: { + Table *pTab; + Column *aCol; + int i; + + assert( pOp->p4type==P4_TABLE ); + pTab = pOp->p4.pTab; + assert( pTab->tabFlags & TF_Strict ); + assert( pTab->nNVCol==pOp->p2 ); + aCol = pTab->aCol; + pIn1 = &aMem[pOp->p1]; + for(i=0; inCol; i++){ + if( aCol[i].colFlags & COLFLAG_GENERATED ){ + if( aCol[i].colFlags & COLFLAG_VIRTUAL ) continue; + if( pOp->p3 ){ pIn1++; continue; } + } + assert( pIn1 < &aMem[pOp->p1+pOp->p2] ); + applyAffinity(pIn1, aCol[i].affinity, encoding); + if( (pIn1->flags & MEM_Null)==0 ){ + switch( aCol[i].eCType ){ + case COLTYPE_BLOB: { + if( (pIn1->flags & MEM_Blob)==0 ) goto vdbe_type_error; + break; + } + case COLTYPE_INTEGER: + case COLTYPE_INT: { + if( (pIn1->flags & MEM_Int)==0 ) goto vdbe_type_error; + break; + } + case COLTYPE_TEXT: { + if( (pIn1->flags & MEM_Str)==0 ) goto vdbe_type_error; + break; + } + case COLTYPE_REAL: { + testcase( (pIn1->flags & (MEM_Real|MEM_IntReal))==MEM_Real ); + testcase( (pIn1->flags & (MEM_Real|MEM_IntReal))==MEM_IntReal ); + if( pIn1->flags & MEM_Int ){ + /* When applying REAL affinity, if the result is still an MEM_Int + ** that will fit in 6 bytes, then change the type to MEM_IntReal + ** so that we keep the high-resolution integer value but know that + ** the type really wants to be REAL. */ + testcase( pIn1->u.i==140737488355328LL ); + testcase( pIn1->u.i==140737488355327LL ); + testcase( pIn1->u.i==-140737488355328LL ); + testcase( pIn1->u.i==-140737488355329LL ); + if( pIn1->u.i<=140737488355327LL && pIn1->u.i>=-140737488355328LL){ + pIn1->flags |= MEM_IntReal; + pIn1->flags &= ~MEM_Int; + }else{ + pIn1->u.r = (double)pIn1->u.i; + pIn1->flags |= MEM_Real; + pIn1->flags &= ~MEM_Int; + } + }else if( (pIn1->flags & (MEM_Real|MEM_IntReal))==0 ){ + goto vdbe_type_error; + } + break; + } + default: { + /* COLTYPE_ANY. Accept anything. */ + break; + } + } + } + REGISTER_TRACE((int)(pIn1-aMem), pIn1); + pIn1++; + } + assert( pIn1 == &aMem[pOp->p1+pOp->p2] ); + break; + +vdbe_type_error: + sqlite3VdbeError(p, "cannot store %s value in %s column %s.%s", + vdbeMemTypeName(pIn1), sqlite3StdType[aCol[i].eCType-1], + pTab->zName, aCol[i].zCnName); + rc = SQLITE_CONSTRAINT_DATATYPE; + goto abort_due_to_error; +} + /* Opcode: Affinity P1 P2 * P4 * ** Synopsis: affinity(r[P1@P2]) ** @@ -88902,7 +91546,6 @@ case OP_MakeRecord: { Mem *pLast; /* Last field of the record */ int nField; /* Number of fields in the record */ char *zAffinity; /* The affinity string for the record */ - int file_format; /* File format to use for encoding */ u32 len; /* Length of a field */ u8 *zHdr; /* Where to write next byte of the header */ u8 *zPayload; /* Where to write next byte of the payload */ @@ -88931,7 +91574,6 @@ case OP_MakeRecord: { pData0 = &aMem[nField]; nField = pOp->p2; pLast = &pData0[nField-1]; - file_format = p->minWriteFileFormat; /* Identify the output register */ assert( pOp->p3p1 || pOp->p3>=pOp->p1+pOp->p2 ); @@ -89030,10 +91672,10 @@ case OP_MakeRecord: { testcase( uu==127 ); testcase( uu==128 ); testcase( uu==32767 ); testcase( uu==32768 ); testcase( uu==8388607 ); testcase( uu==8388608 ); - testcase( uu==2147483647 ); testcase( uu==2147483648 ); + testcase( uu==2147483647 ); testcase( uu==2147483648LL ); testcase( uu==140737488355327LL ); testcase( uu==140737488355328LL ); if( uu<=127 ){ - if( (i&1)==i && file_format>=4 ){ + if( (i&1)==i && p->minWriteFileFormat>=4 ){ pRec->uTemp = 8+(u32)uu; }else{ nData++; @@ -89138,18 +91780,60 @@ case OP_MakeRecord: { zPayload = zHdr + nHdr; /* Write the record */ - zHdr += putVarint32(zHdr, nHdr); + if( nHdr<0x80 ){ + *(zHdr++) = nHdr; + }else{ + zHdr += sqlite3PutVarint(zHdr,nHdr); + } assert( pData0<=pLast ); pRec = pData0; - do{ + while( 1 /*exit-by-break*/ ){ serial_type = pRec->uTemp; /* EVIDENCE-OF: R-06529-47362 Following the size varint are one or more - ** additional varints, one per column. */ - zHdr += putVarint32(zHdr, serial_type); /* serial type */ - /* EVIDENCE-OF: R-64536-51728 The values for each column in the record + ** additional varints, one per column. + ** EVIDENCE-OF: R-64536-51728 The values for each column in the record ** immediately follow the header. */ - zPayload += sqlite3VdbeSerialPut(zPayload, pRec, serial_type); /* content */ - }while( (++pRec)<=pLast ); + if( serial_type<=7 ){ + *(zHdr++) = serial_type; + if( serial_type==0 ){ + /* NULL value. No change in zPayload */ + }else{ + u64 v; + u32 i; + if( serial_type==7 ){ + assert( sizeof(v)==sizeof(pRec->u.r) ); + memcpy(&v, &pRec->u.r, sizeof(v)); + swapMixedEndianFloat(v); + }else{ + v = pRec->u.i; + } + len = i = sqlite3SmallTypeSizes[serial_type]; + assert( i>0 ); + while( 1 /*exit-by-break*/ ){ + zPayload[--i] = (u8)(v&0xFF); + if( i==0 ) break; + v >>= 8; + } + zPayload += len; + } + }else if( serial_type<0x80 ){ + *(zHdr++) = serial_type; + if( serial_type>=14 && pRec->n>0 ){ + assert( pRec->z!=0 ); + memcpy(zPayload, pRec->z, pRec->n); + zPayload += pRec->n; + } + }else{ + zHdr += sqlite3PutVarint(zHdr, serial_type); + if( pRec->n ){ + assert( pRec->z!=0 ); + memcpy(zPayload, pRec->z, pRec->n); + zPayload += pRec->n; + } + } + if( pRec==pLast ) break; + pRec++; + } assert( nHdr==(int)(zHdr - (u8*)pOut->z) ); assert( nByte==(int)(zPayload - (u8*)pOut->z) ); @@ -89158,7 +91842,7 @@ case OP_MakeRecord: { break; } -/* Opcode: Count P1 P2 p3 * * +/* Opcode: Count P1 P2 P3 * * ** Synopsis: r[P2]=count() ** ** Store the number of entries (an integer value) in the table or index @@ -89368,7 +92052,10 @@ case OP_Savepoint: { } } if( rc ) goto abort_due_to_error; - + if( p->eVdbeState==VDBE_HALT_STATE ){ + rc = SQLITE_DONE; + goto vdbe_return; + } break; } @@ -89472,6 +92159,7 @@ case OP_AutoCommit: { */ case OP_Transaction: { Btree *pBt; + Db *pDb; int iMeta = 0; assert( p->bIsReader ); @@ -89479,11 +92167,20 @@ case OP_Transaction: { assert( pOp->p2>=0 && pOp->p2<=2 ); assert( pOp->p1>=0 && pOp->p1nDb ); assert( DbMaskTest(p->btreeMask, pOp->p1) ); - if( pOp->p2 && (db->flags & SQLITE_QueryOnly)!=0 ){ - rc = SQLITE_READONLY; + assert( rc==SQLITE_OK ); + if( pOp->p2 && (db->flags & (SQLITE_QueryOnly|SQLITE_CorruptRdOnly))!=0 ){ + if( db->flags & SQLITE_QueryOnly ){ + /* Writes prohibited by the "PRAGMA query_only=TRUE" statement */ + rc = SQLITE_READONLY; + }else{ + /* Writes prohibited due to a prior SQLITE_CORRUPT in the current + ** transaction */ + rc = SQLITE_CORRUPT; + } goto abort_due_to_error; } - pBt = db->aDb[pOp->p1].pBt; + pDb = &db->aDb[pOp->p1]; + pBt = pDb->pBt; if( pBt ){ rc = sqlite3BtreeBeginTrans(pBt, pOp->p2, &iMeta); @@ -89522,9 +92219,9 @@ case OP_Transaction: { } } assert( pOp->p5==0 || pOp->p4type==P4_INT32 ); - if( pOp->p5 - && (iMeta!=pOp->p3 - || db->aDb[pOp->p1].pSchema->iGeneration!=pOp->p4.i) + if( rc==SQLITE_OK + && pOp->p5 + && (iMeta!=pOp->p3 || pDb->pSchema->iGeneration!=pOp->p4.i) ){ /* ** IMPLEMENTATION-OF: R-03189-51135 As each SQL statement runs, the schema @@ -89551,6 +92248,11 @@ case OP_Transaction: { } p->expired = 1; rc = SQLITE_SCHEMA; + + /* Set changeCntOn to 0 to prevent the value returned by sqlite3_changes() + ** from being modified in sqlite3VdbeHalt(). If this statement is + ** reprepared, changeCntOn will be set again. */ + p->changeCntOn = 0; } if( rc ) goto abort_due_to_error; break; @@ -89617,8 +92319,9 @@ case OP_SetCookie: { rc = sqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, pOp->p3); if( pOp->p2==BTREE_SCHEMA_VERSION ){ /* When the schema cookie changes, record the new cookie internally */ - pDb->pSchema->schema_cookie = pOp->p3 - pOp->p5; + *(u32*)&pDb->pSchema->schema_cookie = *(u32*)&pOp->p3 - pOp->p5; db->mDbFlags |= DBFLAG_SchemaChange; + sqlite3FkClearTriggerCache(db, pOp->p1); }else if( pOp->p2==BTREE_FILE_FORMAT ){ /* Record changes in the file format */ pDb->pSchema->file_format = pOp->p3; @@ -89732,6 +92435,8 @@ case OP_ReopenIdx: { pCur = p->apCsr[pOp->p1]; if( pCur && pCur->pgnoRoot==(u32)pOp->p2 ){ assert( pCur->iDb==pOp->p3 ); /* Guaranteed by the code generator */ + assert( pCur->eCurType==CURTYPE_BTREE ); + sqlite3BtreeClearCursor(pCur->uc.pCursor); goto open_cursor_set_hints; } /* If the cursor is not currently open or is open on a different @@ -89794,8 +92499,9 @@ case OP_OpenWrite: assert( pOp->p1>=0 ); assert( nField>=0 ); testcase( nField==0 ); /* Table with INTEGER PRIMARY KEY and nothing else */ - pCur = allocateCursor(p, pOp->p1, nField, iDb, CURTYPE_BTREE); + pCur = allocateCursor(p, pOp->p1, nField, CURTYPE_BTREE); if( pCur==0 ) goto no_mem; + pCur->iDb = iDb; pCur->nullRow = 1; pCur->isOrdered = 1; pCur->pgnoRoot = p2; @@ -89837,7 +92543,7 @@ case OP_OpenDup: { assert( pOrig ); assert( pOrig->isEphemeral ); /* Only ephemeral cursors can be duplicated */ - pCx = allocateCursor(p, pOp->p1, pOrig->nField, -1, CURTYPE_BTREE); + pCx = allocateCursor(p, pOp->p1, pOrig->nField, CURTYPE_BTREE); if( pCx==0 ) goto no_mem; pCx->nullRow = 1; pCx->isEphemeral = 1; @@ -89845,10 +92551,10 @@ case OP_OpenDup: { pCx->isTable = pOrig->isTable; pCx->pgnoRoot = pOrig->pgnoRoot; pCx->isOrdered = pOrig->isOrdered; - pCx->pBtx = pOrig->pBtx; - pCx->hasBeenDuped = 1; - pOrig->hasBeenDuped = 1; - rc = sqlite3BtreeCursor(pCx->pBtx, pCx->pgnoRoot, BTREE_WRCSR, + pCx->ub.pBtx = pOrig->ub.pBtx; + pCx->noReuse = 1; + pOrig->noReuse = 1; + rc = sqlite3BtreeCursor(pCx->ub.pBtx, pCx->pgnoRoot, BTREE_WRCSR, pCx->pKeyInfo, pCx->uc.pCursor); /* The sqlite3BtreeCursor() routine can only fail for the first cursor ** opened for a database. Since there is already an open cursor when this @@ -89914,23 +92620,23 @@ case OP_OpenEphemeral: { aMem[pOp->p3].z = ""; } pCx = p->apCsr[pOp->p1]; - if( pCx && !pCx->hasBeenDuped ){ + if( pCx && !pCx->noReuse && ALWAYS(pOp->p2<=pCx->nField) ){ /* If the ephermeral table is already open and has no duplicates from ** OP_OpenDup, then erase all existing content so that the table is ** empty again, rather than creating a new table. */ assert( pCx->isEphemeral ); pCx->seqCount = 0; pCx->cacheStatus = CACHE_STALE; - rc = sqlite3BtreeClearTable(pCx->pBtx, pCx->pgnoRoot, 0); + rc = sqlite3BtreeClearTable(pCx->ub.pBtx, pCx->pgnoRoot, 0); }else{ - pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, CURTYPE_BTREE); + pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_BTREE); if( pCx==0 ) goto no_mem; pCx->isEphemeral = 1; - rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBtx, + rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->ub.pBtx, BTREE_OMIT_JOURNAL | BTREE_SINGLE | pOp->p5, vfsFlags); if( rc==SQLITE_OK ){ - rc = sqlite3BtreeBeginTrans(pCx->pBtx, 1, 0); + rc = sqlite3BtreeBeginTrans(pCx->ub.pBtx, 1, 0); if( rc==SQLITE_OK ){ /* If a transient index is required, create it by calling ** sqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before @@ -89939,26 +92645,26 @@ case OP_OpenEphemeral: { */ if( (pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo)!=0 ){ assert( pOp->p4type==P4_KEYINFO ); - rc = sqlite3BtreeCreateTable(pCx->pBtx, &pCx->pgnoRoot, + rc = sqlite3BtreeCreateTable(pCx->ub.pBtx, &pCx->pgnoRoot, BTREE_BLOBKEY | pOp->p5); if( rc==SQLITE_OK ){ assert( pCx->pgnoRoot==SCHEMA_ROOT+1 ); assert( pKeyInfo->db==db ); assert( pKeyInfo->enc==ENC(db) ); - rc = sqlite3BtreeCursor(pCx->pBtx, pCx->pgnoRoot, BTREE_WRCSR, + rc = sqlite3BtreeCursor(pCx->ub.pBtx, pCx->pgnoRoot, BTREE_WRCSR, pKeyInfo, pCx->uc.pCursor); } pCx->isTable = 0; }else{ pCx->pgnoRoot = SCHEMA_ROOT; - rc = sqlite3BtreeCursor(pCx->pBtx, SCHEMA_ROOT, BTREE_WRCSR, + rc = sqlite3BtreeCursor(pCx->ub.pBtx, SCHEMA_ROOT, BTREE_WRCSR, 0, pCx->uc.pCursor); pCx->isTable = 1; } } pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED); if( rc ){ - sqlite3BtreeClose(pCx->pBtx); + sqlite3BtreeClose(pCx->ub.pBtx); } } } @@ -89982,7 +92688,7 @@ case OP_SorterOpen: { assert( pOp->p1>=0 ); assert( pOp->p2>=0 ); - pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, CURTYPE_SORTER); + pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_SORTER); if( pCx==0 ) goto no_mem; pCx->pKeyInfo = pOp->p4.pKeyInfo; assert( pCx->pKeyInfo->db==db ); @@ -90031,7 +92737,7 @@ case OP_OpenPseudo: { assert( pOp->p1>=0 ); assert( pOp->p3>=0 ); - pCx = allocateCursor(p, pOp->p1, pOp->p3, -1, CURTYPE_PSEUDO); + pCx = allocateCursor(p, pOp->p1, pOp->p3, CURTYPE_PSEUDO); if( pCx==0 ) goto no_mem; pCx->nullRow = 1; pCx->seekResult = pOp->p2; @@ -90219,6 +92925,7 @@ case OP_SeekGT: { /* jump, in3, group */ /* If the P3 value could not be converted into an integer without ** loss of information, then special processing is required... */ if( (newType & (MEM_Int|MEM_IntReal))==0 ){ + int c; if( (newType & MEM_Real)==0 ){ if( (newType & MEM_Null) || oc>=OP_SeekGE ){ VdbeBranchTaken(1,2); @@ -90228,7 +92935,8 @@ case OP_SeekGT: { /* jump, in3, group */ if( rc!=SQLITE_OK ) goto abort_due_to_error; goto seek_not_found; } - }else + } + c = sqlite3IntFloatCompare(iKey, pIn3->u.r); /* If the approximation iKey is larger than the actual real search ** term, substitute >= for > and < for <=. e.g. if the search term @@ -90237,7 +92945,7 @@ case OP_SeekGT: { /* jump, in3, group */ ** (x > 4.9) -> (x >= 5) ** (x <= 4.9) -> (x < 5) */ - if( pIn3->u.r<(double)iKey ){ + if( c>0 ){ assert( OP_SeekGE==(OP_SeekGT-1) ); assert( OP_SeekLT==(OP_SeekLE-1) ); assert( (OP_SeekLE & 0x0001)==(OP_SeekGT & 0x0001) ); @@ -90246,14 +92954,14 @@ case OP_SeekGT: { /* jump, in3, group */ /* If the approximation iKey is smaller than the actual real search ** term, substitute <= for < and > for >=. */ - else if( pIn3->u.r>(double)iKey ){ + else if( c<0 ){ assert( OP_SeekLE==(OP_SeekLT+1) ); assert( OP_SeekGT==(OP_SeekGE+1) ); assert( (OP_SeekLT & 0x0001)==(OP_SeekGE & 0x0001) ); if( (oc & 0x0001)==(OP_SeekLT & 0x0001) ) oc++; } } - rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, 0, (u64)iKey, 0, &res); + rc = sqlite3BtreeTableMoveto(pC->uc.pCursor, (u64)iKey, 0, &res); pC->movetoTarget = iKey; /* Used by OP_Delete */ if( rc!=SQLITE_OK ){ goto abort_due_to_error; @@ -90300,7 +93008,7 @@ case OP_SeekGT: { /* jump, in3, group */ { int i; for(i=0; iuc.pCursor, &r, 0, 0, &res); + rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, &r, &res); if( rc!=SQLITE_OK ){ goto abort_due_to_error; } @@ -90522,8 +93230,18 @@ case OP_SeekHit: { assert( pC!=0 ); assert( pOp->p3>=pOp->p2 ); if( pC->seekHitp2 ){ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("seekHit changes from %d to %d\n", pC->seekHit, pOp->p2); + } +#endif pC->seekHit = pOp->p2; }else if( pC->seekHit>pOp->p3 ){ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("seekHit changes from %d to %d\n", pC->seekHit, pOp->p3); + } +#endif pC->seekHit = pOp->p3; } break; @@ -90638,6 +93356,11 @@ case OP_IfNoHope: { /* jump, in3 */ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("seekHit is %d\n", pC->seekHit); + } +#endif if( pC->seekHit>=pOp->p4.i ) break; /* Fall through into OP_NotFound */ /* no break */ deliberate_fall_through @@ -90646,11 +93369,8 @@ case OP_NoConflict: /* jump, in3 */ case OP_NotFound: /* jump, in3 */ case OP_Found: { /* jump, in3 */ int alreadyExists; - int takeJump; int ii; VdbeCursor *pC; - int res; - UnpackedRecord *pFree; UnpackedRecord *pIdxKey; UnpackedRecord r; @@ -90665,14 +93385,15 @@ case OP_Found: { /* jump, in3 */ #ifdef SQLITE_DEBUG pC->seekOp = pOp->opcode; #endif - pIn3 = &aMem[pOp->p3]; + r.aMem = &aMem[pOp->p3]; assert( pC->eCurType==CURTYPE_BTREE ); assert( pC->uc.pCursor!=0 ); assert( pC->isTable==0 ); - if( pOp->p4.i>0 ){ + r.nField = (u16)pOp->p4.i; + if( r.nField>0 ){ + /* Key values in an array of registers */ r.pKeyInfo = pC->pKeyInfo; - r.nField = (u16)pOp->p4.i; - r.aMem = pIn3; + r.default_rc = 0; #ifdef SQLITE_DEBUG for(ii=0; iip3+ii, &r.aMem[ii]); } #endif - pIdxKey = &r; - pFree = 0; + rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, &r, &pC->seekResult); }else{ - assert( pIn3->flags & MEM_Blob ); - rc = ExpandBlob(pIn3); + /* Composite key generated by OP_MakeRecord */ + assert( r.aMem->flags & MEM_Blob ); + assert( pOp->opcode!=OP_NoConflict ); + rc = ExpandBlob(r.aMem); assert( rc==SQLITE_OK || rc==SQLITE_NOMEM ); if( rc ) goto no_mem; - pFree = pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo); + pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo); if( pIdxKey==0 ) goto no_mem; - sqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey); + sqlite3VdbeRecordUnpack(pC->pKeyInfo, r.aMem->n, r.aMem->z, pIdxKey); + pIdxKey->default_rc = 0; + rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, pIdxKey, &pC->seekResult); + sqlite3DbFreeNN(db, pIdxKey); } - pIdxKey->default_rc = 0; - takeJump = 0; - if( pOp->opcode==OP_NoConflict ){ - /* For the OP_NoConflict opcode, take the jump if any of the - ** input fields are NULL, since any key with a NULL will not - ** conflict */ - for(ii=0; iinField; ii++){ - if( pIdxKey->aMem[ii].flags & MEM_Null ){ - takeJump = 1; - break; - } - } - } - rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, pIdxKey, 0, 0, &res); - if( pFree ) sqlite3DbFreeNN(db, pFree); if( rc!=SQLITE_OK ){ goto abort_due_to_error; } - pC->seekResult = res; - alreadyExists = (res==0); + alreadyExists = (pC->seekResult==0); pC->nullRow = 1-alreadyExists; pC->deferredMoveto = 0; pC->cacheStatus = CACHE_STALE; @@ -90718,9 +93427,25 @@ case OP_Found: { /* jump, in3 */ VdbeBranchTaken(alreadyExists!=0,2); if( alreadyExists ) goto jump_to_p2; }else{ - VdbeBranchTaken(takeJump||alreadyExists==0,2); - if( takeJump || !alreadyExists ) goto jump_to_p2; - if( pOp->opcode==OP_IfNoHope ) pC->seekHit = pOp->p4.i; + if( !alreadyExists ){ + VdbeBranchTaken(1,2); + goto jump_to_p2; + } + if( pOp->opcode==OP_NoConflict ){ + /* For the OP_NoConflict opcode, take the jump if any of the + ** input fields are NULL, since any key with a NULL will not + ** conflict */ + for(ii=0; iiopcode==OP_IfNoHope ){ + pC->seekHit = pOp->p4.i; + } } break; } @@ -90813,7 +93538,7 @@ notExistsWithKey: pCrsr = pC->uc.pCursor; assert( pCrsr!=0 ); res = 0; - rc = sqlite3BtreeMovetoUnpacked(pCrsr, 0, iKey, 0, &res); + rc = sqlite3BtreeTableMoveto(pCrsr, iKey, 0, &res); assert( rc==SQLITE_OK || res==0 ); pC->movetoTarget = iKey; /* Used by OP_Delete */ pC->nullRow = 0; @@ -90970,7 +93695,7 @@ case OP_NewRowid: { /* out2 */ do{ sqlite3_randomness(sizeof(v), &v); v &= (MAX_ROWID>>1); v++; /* Ensure that v is greater than zero */ - }while( ((rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, 0, (u64)v, + }while( ((rc = sqlite3BtreeTableMoveto(pC->uc.pCursor, (u64)v, 0, &res))==SQLITE_OK) && (res==0) && (++cnt<100)); @@ -91060,14 +93785,14 @@ case OP_Insert: { assert( (pOp->p5 & OPFLAG_ISNOOP) || HasRowid(pTab) ); }else{ pTab = 0; - zDb = 0; /* Not needed. Silence a compiler warning. */ + zDb = 0; } #ifdef SQLITE_ENABLE_PREUPDATE_HOOK /* Invoke the pre-update hook, if any */ if( pTab ){ if( db->xPreUpdateCallback && !(pOp->p5 & OPFLAG_ISUPDATE) ){ - sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey,pOp->p2); + sqlite3VdbePreUpdateHook(p,pC,SQLITE_INSERT,zDb,pTab,x.nKey,pOp->p2,-1); } if( db->xUpdateCallback==0 || pTab->aCol==0 ){ /* Prevent post-update hook from running in cases when it should not */ @@ -91213,13 +93938,14 @@ case OP_Delete: { pC->movetoTarget = sqlite3BtreeIntegerKey(pC->uc.pCursor); } }else{ - zDb = 0; /* Not needed. Silence a compiler warning. */ - pTab = 0; /* Not needed. Silence a compiler warning. */ + zDb = 0; + pTab = 0; } #ifdef SQLITE_ENABLE_PREUPDATE_HOOK /* Invoke the pre-update-hook if required. */ - if( db->xPreUpdateCallback && pOp->p4.pTab ){ + assert( db->xPreUpdateCallback==0 || pTab==pOp->p4.pTab ); + if( db->xPreUpdateCallback && pTab ){ assert( !(opflags & OPFLAG_ISUPDATE) || HasRowid(pTab)==0 || (aMem[pOp->p3].flags & MEM_Int) @@ -91227,7 +93953,7 @@ case OP_Delete: { sqlite3VdbePreUpdateHook(p, pC, (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE, zDb, pTab, pC->movetoTarget, - pOp->p3 + pOp->p3, -1 ); } if( opflags & OPFLAG_ISNOOP ) break; @@ -91260,7 +93986,7 @@ case OP_Delete: { /* Invoke the update-hook if required. */ if( opflags & OPFLAG_NCHANGE ){ p->nChange++; - if( db->xUpdateCallback && HasRowid(pTab) ){ + if( db->xUpdateCallback && ALWAYS(pTab!=0) && HasRowid(pTab) ){ db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, pTab->zName, pC->movetoTarget); assert( pC->iDb>=0 ); @@ -91410,7 +94136,7 @@ case OP_RowData: { } /* Opcode: Rowid P1 P2 * * * -** Synopsis: r[P2]=rowid +** Synopsis: r[P2]=PX rowid of P1 ** ** Store in register P2 an integer which is the key of the table entry that ** P1 is currently point to. @@ -91465,13 +94191,25 @@ case OP_Rowid: { /* out2 */ ** Move the cursor P1 to a null row. Any OP_Column operations ** that occur while the cursor is on the null row will always ** write a NULL. +** +** If cursor P1 is not previously opened, open it now to a special +** pseudo-cursor that always returns NULL for every column. */ case OP_NullRow: { VdbeCursor *pC; assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; - assert( pC!=0 ); + if( pC==0 ){ + /* If the cursor is not already open, create a special kind of + ** pseudo-cursor that always gives null rows. */ + pC = allocateCursor(p, pOp->p1, 1, CURTYPE_PSEUDO); + if( pC==0 ) goto no_mem; + pC->seekResult = 0; + pC->isTable = 1; + pC->noReuse = 1; + pC->uc.pCursor = sqlite3BtreeFakeValidCursor(); + } pC->nullRow = 1; pC->cacheStatus = CACHE_STALE; if( pC->eCurType==CURTYPE_BTREE ){ @@ -91644,7 +94382,7 @@ case OP_Rewind: { /* jump */ break; } -/* Opcode: Next P1 P2 P3 P4 P5 +/* Opcode: Next P1 P2 P3 * P5 ** ** Advance cursor P1 so that it points to the next key/data pair in its ** table or index. If there are no more key/value pairs then fall through @@ -91663,15 +94401,12 @@ case OP_Rewind: { /* jump */ ** omitted if that index had been unique. P3 is usually 0. P3 is ** always either 0 or 1. ** -** P4 is always of type P4_ADVANCE. The function pointer points to -** sqlite3BtreeNext(). -** ** If P5 is positive and the jump is taken, then event counter ** number P5-1 in the prepared statement is incremented. ** ** See also: Prev */ -/* Opcode: Prev P1 P2 P3 P4 P5 +/* Opcode: Prev P1 P2 P3 * P5 ** ** Back up cursor P1 so that it points to the previous key/data pair in its ** table or index. If there is no previous key/value pairs then fall through @@ -91691,9 +94426,6 @@ case OP_Rewind: { /* jump */ ** omitted if that index had been unique. P3 is usually 0. P3 is ** always either 0 or 1. ** -** P4 is always of type P4_ADVANCE. The function pointer points to -** sqlite3BtreePrevious(). -** ** If P5 is positive and the jump is taken, then event counter ** number P5-1 in the prepared statement is incremented. */ @@ -91711,7 +94443,20 @@ case OP_SorterNext: { /* jump */ assert( isSorter(pC) ); rc = sqlite3VdbeSorterNext(db, pC); goto next_tail; + case OP_Prev: /* jump */ + assert( pOp->p1>=0 && pOp->p1nCursor ); + assert( pOp->p5aCounter) ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->deferredMoveto==0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->seekOp==OP_SeekLT || pC->seekOp==OP_SeekLE + || pC->seekOp==OP_Last || pC->seekOp==OP_IfNoHope + || pC->seekOp==OP_NullRow); + rc = sqlite3BtreePrevious(pC->uc.pCursor, pOp->p3); + goto next_tail; + case OP_Next: /* jump */ assert( pOp->p1>=0 && pOp->p1nCursor ); assert( pOp->p5aCounter) ); @@ -91719,22 +94464,12 @@ case OP_Next: /* jump */ assert( pC!=0 ); assert( pC->deferredMoveto==0 ); assert( pC->eCurType==CURTYPE_BTREE ); - assert( pOp->opcode!=OP_Next || pOp->p4.xAdvance==sqlite3BtreeNext ); - assert( pOp->opcode!=OP_Prev || pOp->p4.xAdvance==sqlite3BtreePrevious ); - - /* The Next opcode is only used after SeekGT, SeekGE, Rewind, and Found. - ** The Prev opcode is only used after SeekLT, SeekLE, and Last. */ - assert( pOp->opcode!=OP_Next - || pC->seekOp==OP_SeekGT || pC->seekOp==OP_SeekGE + assert( pC->seekOp==OP_SeekGT || pC->seekOp==OP_SeekGE || pC->seekOp==OP_Rewind || pC->seekOp==OP_Found || pC->seekOp==OP_NullRow|| pC->seekOp==OP_SeekRowid || pC->seekOp==OP_IfNoHope); - assert( pOp->opcode!=OP_Prev - || pC->seekOp==OP_SeekLT || pC->seekOp==OP_SeekLE - || pC->seekOp==OP_Last || pC->seekOp==OP_IfNoHope - || pC->seekOp==OP_NullRow); + rc = sqlite3BtreeNext(pC->uc.pCursor, pOp->p3); - rc = pOp->p4.xAdvance(pC->uc.pCursor, pOp->p3); next_tail: pC->cacheStatus = CACHE_STALE; VdbeBranchTaken(rc==SQLITE_OK,2); @@ -91847,7 +94582,8 @@ case OP_SorterInsert: { /* in2 */ ** an UPDATE or DELETE statement and the index entry to be updated ** or deleted is not found. For some uses of IdxDelete ** (example: the EXCEPT operator) it does not matter that no matching -** entry is found. For those cases, P5 is zero. +** entry is found. For those cases, P5 is zero. Also, do not raise +** this (self-correcting and non-critical) error if in writable_schema mode. */ case OP_IdxDelete: { VdbeCursor *pC; @@ -91868,12 +94604,12 @@ case OP_IdxDelete: { r.nField = (u16)pOp->p3; r.default_rc = 0; r.aMem = &aMem[pOp->p2]; - rc = sqlite3BtreeMovetoUnpacked(pCrsr, &r, 0, 0, &res); + rc = sqlite3BtreeIndexMoveto(pCrsr, &r, &res); if( rc ) goto abort_due_to_error; if( res==0 ){ rc = sqlite3BtreeDelete(pCrsr, BTREE_AUXDELETE); if( rc ) goto abort_due_to_error; - }else if( pOp->p5 ){ + }else if( pOp->p5 && !sqlite3WritableSchema(db) ){ rc = sqlite3ReportError(SQLITE_CORRUPT_INDEX, __LINE__, "index corruption"); goto abort_due_to_error; } @@ -91920,9 +94656,9 @@ case OP_IdxRowid: { /* out2 */ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); - assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->eCurType==CURTYPE_BTREE || IsNullCursor(pC) ); assert( pC->uc.pCursor!=0 ); - assert( pC->isTable==0 ); + assert( pC->isTable==0 || IsNullCursor(pC) ); assert( pC->deferredMoveto==0 ); assert( !pC->nullRow || pOp->opcode==OP_IdxRowid ); @@ -91951,10 +94687,11 @@ case OP_IdxRowid: { /* out2 */ pTabCur->nullRow = 0; pTabCur->movetoTarget = rowid; pTabCur->deferredMoveto = 1; + pTabCur->cacheStatus = CACHE_STALE; assert( pOp->p4type==P4_INTARRAY || pOp->p4.ai==0 ); - pTabCur->aAltMap = pOp->p4.ai; - assert( !pC->isEphemeral ); assert( !pTabCur->isEphemeral ); + pTabCur->ub.aAltMap = pOp->p4.ai; + assert( !pC->isEphemeral ); pTabCur->pAltCursor = pC; }else{ pOut = out2Prerelease(p, pOp); @@ -92085,7 +94822,7 @@ case OP_IdxGE: { /* jump */ rc = sqlite3VdbeMemFromBtreeZeroOffset(pCur, (u32)nCellKey, &m); if( rc ) goto abort_due_to_error; res = sqlite3VdbeRecordCompareWithSkip(m.n, m.z, &r, 0); - sqlite3VdbeMemRelease(&m); + sqlite3VdbeMemReleaseMalloc(&m); } /* End of inlined sqlite3VdbeIdxKeyCompare() */ @@ -92173,24 +94910,21 @@ case OP_Destroy: { /* out2 */ ** P2==1 then the table to be clear is in the auxiliary database file ** that is used to store tables create using CREATE TEMPORARY TABLE. ** -** If the P3 value is non-zero, then the table referred to must be an -** intkey table (an SQL table, not an index). In this case the row change -** count is incremented by the number of rows in the table being cleared. -** If P3 is greater than zero, then the value stored in register P3 is -** also incremented by the number of rows in the table being cleared. +** If the P3 value is non-zero, then the row change count is incremented +** by the number of rows in the table being cleared. If P3 is greater +** than zero, then the value stored in register P3 is also incremented +** by the number of rows in the table being cleared. ** ** See also: Destroy */ case OP_Clear: { - int nChange; + i64 nChange; sqlite3VdbeIncrWriteCounter(p, 0); nChange = 0; assert( p->readOnly==0 ); assert( DbMaskTest(p->btreeMask, pOp->p2) ); - rc = sqlite3BtreeClearTable( - db->aDb[pOp->p2].pBt, (u32)pOp->p1, (pOp->p3 ? &nChange : 0) - ); + rc = sqlite3BtreeClearTable(db->aDb[pOp->p2].pBt, (u32)pOp->p1, &nChange); if( pOp->p3 ){ p->nChange += nChange; if( pOp->p3>0 ){ @@ -92296,7 +95030,9 @@ case OP_ParseSchema: { iDb = pOp->p1; assert( iDb>=0 && iDbnDb ); - assert( DbHasProperty(db, iDb, DB_SchemaLoaded) ); + assert( DbHasProperty(db, iDb, DB_SchemaLoaded) + || db->mallocFailed + || (CORRUPT_DB && (db->flags & SQLITE_NoSchemaError)!=0) ); #ifndef SQLITE_OMIT_ALTERTABLE if( pOp->p4.z==0 ){ @@ -92308,7 +95044,7 @@ case OP_ParseSchema: { }else #endif { - zSchema = DFLT_SCHEMA_TABLE; + zSchema = LEGACY_SCHEMA_TABLE; initData.db = db; initData.iDb = iDb; initData.pzErrMsg = &p->zErrMsg; @@ -92967,6 +95703,7 @@ case OP_AggStep: { pCtx->pVdbe = p; pCtx->skipFlag = 0; pCtx->isError = 0; + pCtx->enc = encoding; pCtx->argc = n; pOp->p4type = P4_FUNCCTX; pOp->p4.pCtx = pCtx; @@ -93096,9 +95833,6 @@ case OP_AggFinal: { } sqlite3VdbeChangeEncoding(pMem, encoding); UPDATE_MAX_BLOBSIZE(pMem); - if( sqlite3VdbeMemTooBig(pMem) ){ - goto too_big; - } break; } @@ -93178,6 +95912,7 @@ case OP_JournalMode: { /* out2 */ pPager = sqlite3BtreePager(pBt); eOld = sqlite3PagerGetJournalMode(pPager); if( eNew==PAGER_JOURNALMODE_QUERY ) eNew = eOld; + assert( sqlite3BtreeHoldsMutex(pBt) ); if( !sqlite3PagerOkToChangeJournalMode(pPager) ) eNew = eOld; #ifndef SQLITE_OMIT_WAL @@ -93476,7 +96211,7 @@ case OP_VOpen: { pVCur->pVtab = pVtab; /* Initialize vdbe cursor object */ - pCur = allocateCursor(p, pOp->p1, 0, -1, CURTYPE_VTAB); + pCur = allocateCursor(p, pOp->p1, 0, CURTYPE_VTAB); if( pCur ){ pCur->uc.pVCur = pVCur; pVtab->nRef++; @@ -93489,6 +96224,34 @@ case OP_VOpen: { } #endif /* SQLITE_OMIT_VIRTUALTABLE */ +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VInitIn P1 P2 P3 * * +** Synopsis: r[P2]=ValueList(P1,P3) +** +** Set register P2 to be a pointer to a ValueList object for cursor P1 +** with cache register P3 and output register P3+1. This ValueList object +** can be used as the first argument to sqlite3_vtab_in_first() and +** sqlite3_vtab_in_next() to extract all of the values stored in the P1 +** cursor. Register P3 is used to hold the values returned by +** sqlite3_vtab_in_first() and sqlite3_vtab_in_next(). +*/ +case OP_VInitIn: { /* out2 */ + VdbeCursor *pC; /* The cursor containing the RHS values */ + ValueList *pRhs; /* New ValueList object to put in reg[P2] */ + + pC = p->apCsr[pOp->p1]; + pRhs = sqlite3_malloc64( sizeof(*pRhs) ); + if( pRhs==0 ) goto no_mem; + pRhs->pCsr = pC->uc.pCursor; + pRhs->pOut = &aMem[pOp->p3]; + pOut = out2Prerelease(p, pOp); + pOut->flags = MEM_Null; + sqlite3VdbeMemSetPointer(pOut, pRhs, "ValueList", sqlite3_free); + break; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + + #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VFilter P1 P2 P3 P4 * ** Synopsis: iplan=r[P3] zplan='P4' @@ -93527,6 +96290,7 @@ case OP_VFilter: { /* jump */ pCur = p->apCsr[pOp->p1]; assert( memIsValid(pQuery) ); REGISTER_TRACE(pOp->p3, pQuery); + assert( pCur!=0 ); assert( pCur->eCurType==CURTYPE_VTAB ); pVCur = pCur->uc.pVCur; pVtab = pVCur->pVtab; @@ -93538,7 +96302,6 @@ case OP_VFilter: { /* jump */ iQuery = (int)pQuery->u.i; /* Invoke the xFilter method */ - res = 0; apArg = p->apArg; for(i = 0; iapCsr[pOp->p1]; - assert( pCur->eCurType==CURTYPE_VTAB ); + assert( pCur!=0 ); assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); pDest = &aMem[pOp->p3]; memAboutToChange(p, pDest); @@ -93584,11 +96347,13 @@ case OP_VColumn: { sqlite3VdbeMemSetNull(pDest); break; } + assert( pCur->eCurType==CURTYPE_VTAB ); pVtab = pCur->uc.pVCur->pVtab; pModule = pVtab->pModule; assert( pModule->xColumn ); memset(&sContext, 0, sizeof(sContext)); sContext.pOut = pDest; + sContext.enc = encoding; assert( pOp->p5==OPFLAG_NOCHNG || pOp->p5==0 ); if( pOp->p5 & OPFLAG_NOCHNG ){ sqlite3VdbeMemSetNull(pDest); @@ -93607,9 +96372,6 @@ case OP_VColumn: { REGISTER_TRACE(pOp->p3, pDest); UPDATE_MAX_BLOBSIZE(pDest); - if( sqlite3VdbeMemTooBig(pDest) ){ - goto too_big; - } if( rc ) goto abort_due_to_error; break; } @@ -93628,8 +96390,8 @@ case OP_VNext: { /* jump */ int res; VdbeCursor *pCur; - res = 0; pCur = p->apCsr[pOp->p1]; + assert( pCur!=0 ); assert( pCur->eCurType==CURTYPE_VTAB ); if( pCur->nullRow ){ break; @@ -93725,7 +96487,7 @@ case OP_VUpdate: { const sqlite3_module *pModule; int nArg; int i; - sqlite_int64 rowid; + sqlite_int64 rowid = 0; Mem **apArg; Mem *pX; @@ -93876,6 +96638,7 @@ case OP_Function: { /* group */ if( pCtx->pOut != pOut ){ pCtx->pVdbe = p; pCtx->pOut = pOut; + pCtx->enc = encoding; for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i]; } assert( pCtx->pVdbe==p ); @@ -93902,17 +96665,98 @@ case OP_Function: { /* group */ if( rc ) goto abort_due_to_error; } - /* Copy the result of the function into register P3 */ - if( pOut->flags & (MEM_Str|MEM_Blob) ){ - sqlite3VdbeChangeEncoding(pOut, encoding); - if( sqlite3VdbeMemTooBig(pOut) ) goto too_big; - } + assert( (pOut->flags&MEM_Str)==0 + || pOut->enc==encoding + || db->mallocFailed ); + assert( !sqlite3VdbeMemTooBig(pOut) ); REGISTER_TRACE(pOp->p3, pOut); UPDATE_MAX_BLOBSIZE(pOut); break; } +/* Opcode: ClrSubtype P1 * * * * +** Synopsis: r[P1].subtype = 0 +** +** Clear the subtype from register P1. +*/ +case OP_ClrSubtype: { /* in1 */ + pIn1 = &aMem[pOp->p1]; + pIn1->flags &= ~MEM_Subtype; + break; +} + +/* Opcode: FilterAdd P1 * P3 P4 * +** Synopsis: filter(P1) += key(P3@P4) +** +** Compute a hash on the P4 registers starting with r[P3] and +** add that hash to the bloom filter contained in r[P1]. +*/ +case OP_FilterAdd: { + u64 h; + + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); + pIn1 = &aMem[pOp->p1]; + assert( pIn1->flags & MEM_Blob ); + assert( pIn1->n>0 ); + h = filterHash(aMem, pOp); +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + int ii; + for(ii=pOp->p3; iip3+pOp->p4.i; ii++){ + registerTrace(ii, &aMem[ii]); + } + printf("hash: %llu modulo %d -> %u\n", h, pIn1->n, (int)(h%pIn1->n)); + } +#endif + h %= pIn1->n; + pIn1->z[h/8] |= 1<<(h&7); + break; +} + +/* Opcode: Filter P1 P2 P3 P4 * +** Synopsis: if key(P3@P4) not in filter(P1) goto P2 +** +** Compute a hash on the key contained in the P4 registers starting +** with r[P3]. Check to see if that hash is found in the +** bloom filter hosted by register P1. If it is not present then +** maybe jump to P2. Otherwise fall through. +** +** False negatives are harmless. It is always safe to fall through, +** even if the value is in the bloom filter. A false negative causes +** more CPU cycles to be used, but it should still yield the correct +** answer. However, an incorrect answer may well arise from a +** false positive - if the jump is taken when it should fall through. +*/ +case OP_Filter: { /* jump */ + u64 h; + + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); + pIn1 = &aMem[pOp->p1]; + assert( (pIn1->flags & MEM_Blob)!=0 ); + assert( pIn1->n >= 1 ); + h = filterHash(aMem, pOp); +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + int ii; + for(ii=pOp->p3; iip3+pOp->p4.i; ii++){ + registerTrace(ii, &aMem[ii]); + } + printf("hash: %llu modulo %d -> %u\n", h, pIn1->n, (int)(h%pIn1->n)); + } +#endif + h %= pIn1->n; + if( (pIn1->z[h/8] & (1<<(h&7)))==0 ){ + VdbeBranchTaken(1, 2); + p->aCounter[SQLITE_STMTSTATUS_FILTER_HIT]++; + goto jump_to_p2; + }else{ + p->aCounter[SQLITE_STMTSTATUS_FILTER_MISS]++; + VdbeBranchTaken(0, 2); + } + break; +} + /* Opcode: Trace P1 P2 * P4 * ** ** Write P4 on the statement trace output if statement tracing is @@ -93961,7 +96805,7 @@ case OP_Init: { /* jump */ #ifndef SQLITE_OMIT_TRACE if( (db->mTrace & (SQLITE_TRACE_STMT|SQLITE_TRACE_LEGACY))!=0 - && !p->doingRerun + && p->minWriteFileFormat!=254 /* tag-20220401a */ && (zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql))!=0 ){ #ifndef SQLITE_OMIT_DEPRECATED @@ -94170,6 +97014,18 @@ abort_due_to_error: rc = SQLITE_CORRUPT_BKPT; } assert( rc ); +#ifdef SQLITE_DEBUG + if( db->flags & SQLITE_VdbeTrace ){ + const char *zTrace = p->zSql; + if( zTrace==0 ){ + if( aOp[0].opcode==OP_Trace ){ + zTrace = aOp[0].p4.z; + } + if( zTrace==0 ) zTrace = "???"; + } + printf("ABORT-due-to-error (rc=%d): %s\n", rc, zTrace); + } +#endif if( p->zErrMsg==0 && rc!=SQLITE_IOERR_NOMEM ){ sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc)); } @@ -94178,8 +97034,11 @@ abort_due_to_error: testcase( sqlite3GlobalConfig.xLog!=0 ); sqlite3_log(rc, "statement aborts at %d: [%s] %s", (int)(pOp - aOp), p->zSql, p->zErrMsg); - sqlite3VdbeHalt(p); + if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p); if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db); + if( rc==SQLITE_CORRUPT && db->autoCommit==0 ){ + db->flags |= SQLITE_CorruptRdOnly; + } rc = SQLITE_ERROR; if( resetSchemaOnFault>0 ){ sqlite3ResetOneSchema(db, resetSchemaOnFault-1); @@ -94311,7 +97170,10 @@ static int blobSeekToRow(Incrblob *p, sqlite3_int64 iRow, char **pzErr){ } if( rc==SQLITE_ROW ){ VdbeCursor *pC = v->apCsr[0]; - u32 type = pC->nHdrParsed>p->iCol ? pC->aType[p->iCol] : 0; + u32 type; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + type = pC->nHdrParsed>p->iCol ? pC->aType[p->iCol] : 0; testcase( pC->nHdrParsed==p->iCol ); testcase( pC->nHdrParsed==p->iCol+1 ); if( type<12 ){ @@ -94385,10 +97247,9 @@ SQLITE_API int sqlite3_blob_open( sqlite3_mutex_enter(db->mutex); pBlob = (Incrblob *)sqlite3DbMallocZero(db, sizeof(Incrblob)); - do { - memset(&sParse, 0, sizeof(Parse)); + while(1){ + sqlite3ParseObjectInit(&sParse,db); if( !pBlob ) goto blob_open_out; - sParse.db = db; sqlite3DbFree(db, zErr); zErr = 0; @@ -94403,7 +97264,7 @@ SQLITE_API int sqlite3_blob_open( sqlite3ErrorMsg(&sParse, "cannot open table without rowid: %s", zTable); } #ifndef SQLITE_OMIT_VIEW - if( pTab && pTab->pSelect ){ + if( pTab && IsView(pTab) ){ pTab = 0; sqlite3ErrorMsg(&sParse, "cannot open view: %s", zTable); } @@ -94423,7 +97284,7 @@ SQLITE_API int sqlite3_blob_open( /* Now search pTab for the exact column. */ for(iCol=0; iColnCol; iCol++) { - if( sqlite3StrICmp(pTab->aCol[iCol].zName, zColumn)==0 ){ + if( sqlite3StrICmp(pTab->aCol[iCol].zCnName, zColumn)==0 ){ break; } } @@ -94448,7 +97309,8 @@ SQLITE_API int sqlite3_blob_open( ** key columns must be indexed. The check below will pick up this ** case. */ FKey *pFKey; - for(pFKey=pTab->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + assert( IsOrdinaryTable(pTab) ); + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ int j; for(j=0; jnCol; j++){ if( pFKey->aCol[j].iFrom==iCol ){ @@ -94564,7 +97426,9 @@ SQLITE_API int sqlite3_blob_open( goto blob_open_out; } rc = blobSeekToRow(pBlob, iRow, &zErr); - } while( (++nAttempt)=SQLITE_MAX_SCHEMA_RETRY || rc!=SQLITE_SCHEMA ) break; + sqlite3ParseObjectReset(&sParse); + } blob_open_out: if( rc==SQLITE_OK && db->mallocFailed==0 ){ @@ -94575,7 +97439,7 @@ blob_open_out: } sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr); sqlite3DbFree(db, zErr); - sqlite3ParserReset(&sParse); + sqlite3ParseObjectReset(&sParse); rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); return rc; @@ -94655,8 +97519,10 @@ static int blobReadWrite( */ sqlite3_int64 iKey; iKey = sqlite3BtreeIntegerKey(p->pCsr); + assert( v->apCsr[0]!=0 ); + assert( v->apCsr[0]->eCurType==CURTYPE_BTREE ); sqlite3VdbePreUpdateHook( - v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1 + v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1, p->iCol ); } #endif @@ -94727,6 +97593,7 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ rc = SQLITE_ABORT; }else{ char *zErr; + ((Vdbe*)p->pStmt)->rc = SQLITE_OK; rc = blobSeekToRow(p, iRow, &zErr); if( rc!=SQLITE_OK ){ sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr); @@ -95707,7 +98574,8 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit( } #endif - assert( pCsr->pKeyInfo && pCsr->pBtx==0 ); + assert( pCsr->pKeyInfo ); + assert( !pCsr->isEphemeral ); assert( pCsr->eCurType==CURTYPE_SORTER ); szKeyInfo = sizeof(KeyInfo) + (pCsr->pKeyInfo->nKeyField-1)*sizeof(CollSeq*); sz = sizeof(VdbeSorter) + nWorker * sizeof(SortSubtask); @@ -95820,8 +98688,9 @@ static void vdbeSorterWorkDebug(SortSubtask *pTask, const char *zEvent){ fprintf(stderr, "%lld:%d %s\n", t, iTask, zEvent); } static void vdbeSorterRewindDebug(const char *zEvent){ - i64 t; - sqlite3OsCurrentTimeInt64(sqlite3_vfs_find(0), &t); + i64 t = 0; + sqlite3_vfs *pVfs = sqlite3_vfs_find(0); + if( ALWAYS(pVfs) ) sqlite3OsCurrentTimeInt64(pVfs, &t); fprintf(stderr, "%lld:X %s\n", t, zEvent); } static void vdbeSorterPopulateDebug( @@ -96035,7 +98904,7 @@ static void vdbeSorterExtendFile(sqlite3 *db, sqlite3_file *pFd, i64 nByte){ sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_CHUNK_SIZE, &chunksize); sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_SIZE_HINT, &nByte); sqlite3OsFetch(pFd, 0, (int)nByte, &p); - sqlite3OsUnfetch(pFd, 0, p); + if( p ) sqlite3OsUnfetch(pFd, 0, p); } } #else @@ -96753,6 +99622,7 @@ static int vdbeIncrMergerNew( vdbeMergeEngineFree(pMerger); rc = SQLITE_NOMEM_BKPT; } + assert( *ppOut!=0 || rc!=SQLITE_OK ); return rc; } @@ -98118,6 +100988,9 @@ static int memjrnlCreateFile(MemJournal *p){ } +/* Forward reference */ +static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size); + /* ** Write data to the file. */ @@ -98148,22 +101021,20 @@ static int memjrnlWrite( ** the in-memory journal is being used by a connection using the ** atomic-write optimization. In this case the first 28 bytes of the ** journal file may be written as part of committing the transaction. */ - assert( iOfst==p->endpoint.iOffset || iOfst==0 ); -#if defined(SQLITE_ENABLE_ATOMIC_WRITE) \ - || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) + assert( iOfst<=p->endpoint.iOffset ); + if( iOfst>0 && iOfst!=p->endpoint.iOffset ){ + memjrnlTruncate(pJfd, iOfst); + } if( iOfst==0 && p->pFirst ){ assert( p->nChunkSize>iAmt ); memcpy((u8*)p->pFirst->zChunk, zBuf, iAmt); - }else -#else - assert( iOfst>0 || p->pFirst==0 ); -#endif - { + }else{ while( nWrite>0 ){ FileChunk *pChunk = p->endpoint.pChunk; int iChunkOffset = (int)(p->endpoint.iOffset%p->nChunkSize); int iSpace = MIN(nWrite, p->nChunkSize - iChunkOffset); + assert( pChunk!=0 || iChunkOffset==0 ); if( iChunkOffset==0 ){ /* New chunk is required to extend the file. */ FileChunk *pNew = sqlite3_malloc(fileChunkSize(p->nChunkSize)); @@ -98178,10 +101049,11 @@ static int memjrnlWrite( assert( !p->pFirst ); p->pFirst = pNew; } - p->endpoint.pChunk = pNew; + pChunk = p->endpoint.pChunk = pNew; } - memcpy((u8*)p->endpoint.pChunk->zChunk + iChunkOffset, zWrite, iSpace); + assert( pChunk!=0 ); + memcpy((u8*)pChunk->zChunk + iChunkOffset, zWrite, iSpace); zWrite += iSpace; nWrite -= iSpace; p->endpoint.iOffset += iSpace; @@ -98197,26 +101069,28 @@ static int memjrnlWrite( */ static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){ MemJournal *p = (MemJournal *)pJfd; - FileChunk *pIter = 0; + assert( p->endpoint.pChunk==0 || p->endpoint.pChunk->pNext==0 ); + if( sizeendpoint.iOffset ){ + FileChunk *pIter = 0; + if( size==0 ){ + memjrnlFreeChunks(p->pFirst); + p->pFirst = 0; + }else{ + i64 iOff = p->nChunkSize; + for(pIter=p->pFirst; ALWAYS(pIter) && iOffpNext){ + iOff += p->nChunkSize; + } + if( ALWAYS(pIter) ){ + memjrnlFreeChunks(pIter->pNext); + pIter->pNext = 0; + } + } - if( size==0 ){ - memjrnlFreeChunks(p->pFirst); - p->pFirst = 0; - }else{ - i64 iOff = p->nChunkSize; - for(pIter=p->pFirst; ALWAYS(pIter) && iOff<=size; pIter=pIter->pNext){ - iOff += p->nChunkSize; - } - if( ALWAYS(pIter) ){ - memjrnlFreeChunks(pIter->pNext); - pIter->pNext = 0; - } + p->endpoint.pChunk = pIter; + p->endpoint.iOffset = size; + p->readpoint.pChunk = 0; + p->readpoint.iOffset = 0; } - - p->endpoint.pChunk = pIter; - p->endpoint.iOffset = size; - p->readpoint.pChunk = 0; - p->readpoint.iOffset = 0; return SQLITE_OK; } @@ -98409,15 +101283,10 @@ static int walkWindowList(Walker *pWalker, Window *pList, int bOneOnly){ if( rc ) return WRC_Abort; rc = sqlite3WalkExpr(pWalker, pWin->pFilter); if( rc ) return WRC_Abort; - - /* The next two are purely for calls to sqlite3RenameExprUnmap() - ** within sqlite3WindowOffsetExpr(). Because of constraints imposed - ** by sqlite3WindowOffsetExpr(), they can never fail. The results do - ** not matter anyhow. */ rc = sqlite3WalkExpr(pWalker, pWin->pStart); - if( NEVER(rc) ) return WRC_Abort; + if( rc ) return WRC_Abort; rc = sqlite3WalkExpr(pWalker, pWin->pEnd); - if( NEVER(rc) ) return WRC_Abort; + if( rc ) return WRC_Abort; if( bOneOnly ) break; } return WRC_Continue; @@ -98457,7 +101326,7 @@ static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){ assert( !ExprHasProperty(pExpr, EP_WinFunc) ); pExpr = pExpr->pRight; continue; - }else if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + }else if( ExprUseXSelect(pExpr) ){ assert( !ExprHasProperty(pExpr, EP_WinFunc) ); if( sqlite3WalkSelect(pWalker, pExpr->x.pSelect) ) return WRC_Abort; }else{ @@ -98494,6 +101363,16 @@ SQLITE_PRIVATE int sqlite3WalkExprList(Walker *pWalker, ExprList *p){ return WRC_Continue; } +/* +** This is a no-op callback for Walker->xSelectCallback2. If this +** callback is set, then the Select->pWinDefn list is traversed. +*/ +SQLITE_PRIVATE void sqlite3WalkWinDefnDummyCallback(Walker *pWalker, Select *p){ + UNUSED_PARAMETER(pWalker); + UNUSED_PARAMETER(p); + /* No-op */ +} + /* ** Walk all expressions associated with SELECT statement p. Do ** not invoke the SELECT callback on p, but do (of course) invoke @@ -98507,10 +101386,15 @@ SQLITE_PRIVATE int sqlite3WalkSelectExpr(Walker *pWalker, Select *p){ if( sqlite3WalkExpr(pWalker, p->pHaving) ) return WRC_Abort; if( sqlite3WalkExprList(pWalker, p->pOrderBy) ) return WRC_Abort; if( sqlite3WalkExpr(pWalker, p->pLimit) ) return WRC_Abort; -#if !defined(SQLITE_OMIT_WINDOWFUNC) && !defined(SQLITE_OMIT_ALTERTABLE) - { - Parse *pParse = pWalker->pParse; - if( pParse && IN_RENAME_OBJECT ){ +#if !defined(SQLITE_OMIT_WINDOWFUNC) + if( p->pWinDefn ){ + Parse *pParse; + if( pWalker->xSelectCallback2==sqlite3WalkWinDefnDummyCallback + || ((pParse = pWalker->pParse)!=0 && IN_RENAME_OBJECT) +#ifndef SQLITE_OMIT_CTE + || pWalker->xSelectCallback2==sqlite3SelectPopWith +#endif + ){ /* The following may return WRC_Abort if there are unresolvable ** symbols (e.g. a table that does not exist) in a window definition. */ int rc = walkWindowList(pWalker, p->pWinDefn, 0); @@ -98534,7 +101418,7 @@ SQLITE_PRIVATE int sqlite3WalkSelectFrom(Walker *pWalker, Select *p){ SrcItem *pItem; pSrc = p->pSrc; - if( pSrc ){ + if( ALWAYS(pSrc) ){ for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){ if( pItem->pSelect && sqlite3WalkSelect(pWalker, pItem->pSelect) ){ return WRC_Abort; @@ -98708,55 +101592,30 @@ static void resolveAlias( assert( pOrig!=0 ); db = pParse->db; pDup = sqlite3ExprDup(db, pOrig, 0); - if( pDup!=0 ){ + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pDup); + pDup = 0; + }else{ + Expr temp; incrAggFunctionDepth(pDup, nSubquery); if( pExpr->op==TK_COLLATE ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); pDup = sqlite3ExprAddCollateString(pParse, pDup, pExpr->u.zToken); } - - /* Before calling sqlite3ExprDelete(), set the EP_Static flag. This - ** prevents ExprDelete() from deleting the Expr structure itself, - ** allowing it to be repopulated by the memcpy() on the following line. - ** The pExpr->u.zToken might point into memory that will be freed by the - ** sqlite3DbFree(db, pDup) on the last line of this block, so be sure to - ** make a copy of the token before doing the sqlite3DbFree(). - */ - ExprSetProperty(pExpr, EP_Static); - sqlite3ExprDelete(db, pExpr); - memcpy(pExpr, pDup, sizeof(*pExpr)); - if( !ExprHasProperty(pExpr, EP_IntValue) && pExpr->u.zToken!=0 ){ - assert( (pExpr->flags & (EP_Reduced|EP_TokenOnly))==0 ); - pExpr->u.zToken = sqlite3DbStrDup(db, pExpr->u.zToken); - pExpr->flags |= EP_MemToken; - } + memcpy(&temp, pDup, sizeof(Expr)); + memcpy(pDup, pExpr, sizeof(Expr)); + memcpy(pExpr, &temp, sizeof(Expr)); if( ExprHasProperty(pExpr, EP_WinFunc) ){ - if( pExpr->y.pWin!=0 ){ + if( ALWAYS(pExpr->y.pWin!=0) ){ pExpr->y.pWin->pOwner = pExpr; - }else{ - assert( db->mallocFailed ); } } - sqlite3DbFree(db, pDup); + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3ExprDelete, + pDup); } } - -/* -** Return TRUE if the name zCol occurs anywhere in the USING clause. -** -** Return FALSE if the USING clause is NULL or if it does not contain -** zCol. -*/ -static int nameInUsingClause(IdList *pUsing, const char *zCol){ - if( pUsing ){ - int k; - for(k=0; knId; k++){ - if( sqlite3StrICmp(pUsing->a[k].zName, zCol)==0 ) return 1; - } - } - return 0; -} - /* ** Subqueries stores the original database, table and column names for their ** result sets in ExprList.a[].zSpan, in the form "DATABASE.TABLE.COLUMN". @@ -98772,7 +101631,7 @@ SQLITE_PRIVATE int sqlite3MatchEName( ){ int n; const char *zSpan; - if( pItem->eEName!=ENAME_TAB ) return 0; + if( pItem->fg.eEName!=ENAME_TAB ) return 0; zSpan = pItem->zEName; for(n=0; ALWAYS(zSpan[n]) && zSpan[n]!='.'; n++){} if( zDb && (sqlite3StrNICmp(zSpan, zDb, n)!=0 || zDb[n]!=0) ){ @@ -98816,6 +101675,7 @@ SQLITE_PRIVATE Bitmask sqlite3ExprColUsed(Expr *pExpr){ Table *pExTab; n = pExpr->iColumn; + assert( ExprUseYTab(pExpr) ); pExTab = pExpr->y.pTab; assert( pExTab!=0 ); if( (pExTab->tabFlags & TF_HasGenerated)!=0 @@ -98832,6 +101692,29 @@ SQLITE_PRIVATE Bitmask sqlite3ExprColUsed(Expr *pExpr){ } } +/* +** Create a new expression term for the column specified by pMatch and +** iColumn. Append this new expression term to the FULL JOIN Match set +** in *ppList. Create a new *ppList if this is the first term in the +** set. +*/ +static void extendFJMatch( + Parse *pParse, /* Parsing context */ + ExprList **ppList, /* ExprList to extend */ + SrcItem *pMatch, /* Source table containing the column */ + i16 iColumn /* The column number */ +){ + Expr *pNew = sqlite3ExprAlloc(pParse->db, TK_COLUMN, 0, 0); + if( pNew ){ + pNew->iTable = pMatch->iCursor; + pNew->iColumn = iColumn; + pNew->y.pTab = pMatch->pTab; + assert( (pMatch->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 ); + ExprSetProperty(pNew, EP_CanBeNull); + *ppList = sqlite3ExprListAppend(pParse, *ppList, pNew); + } +} + /* ** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up ** that name in the set of source tables in pSrcList and make the pExpr @@ -98877,11 +101760,13 @@ static int lookupName( NameContext *pTopNC = pNC; /* First namecontext in the list */ Schema *pSchema = 0; /* Schema of the expression */ int eNewExprOp = TK_COLUMN; /* New value for pExpr->op on success */ - Table *pTab = 0; /* Table hold the row */ + Table *pTab = 0; /* Table holding the row */ Column *pCol; /* A column of pTab */ + ExprList *pFJMatch = 0; /* Matches for FULL JOIN .. USING */ assert( pNC ); /* the name context cannot be NULL. */ assert( zCol ); /* The Z in X.Y.Z cannot be NULL */ + assert( zDb==0 || zTab!=0 ); assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); /* Initialize the node to no-match */ @@ -98929,62 +101814,124 @@ static int lookupName( u8 hCol; pTab = pItem->pTab; assert( pTab!=0 && pTab->zName!=0 ); - assert( pTab->nCol>0 ); - if( pItem->pSelect && (pItem->pSelect->selFlags & SF_NestedFrom)!=0 ){ + assert( pTab->nCol>0 || pParse->nErr ); + assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) ); + if( pItem->fg.isNestedFrom ){ + /* In this case, pItem is a subquery that has been formed from a + ** parenthesized subset of the FROM clause terms. Example: + ** .... FROM t1 LEFT JOIN (t2 RIGHT JOIN t3 USING(x)) USING(y) ... + ** \_________________________/ + ** This pItem -------------^ + */ int hit = 0; + assert( pItem->pSelect!=0 ); pEList = pItem->pSelect->pEList; + assert( pEList!=0 ); + assert( pEList->nExpr==pTab->nCol ); for(j=0; jnExpr; j++){ - if( sqlite3MatchEName(&pEList->a[j], zCol, zTab, zDb) ){ - cnt++; - cntTab = 2; - pMatch = pItem; - pExpr->iColumn = j; - hit = 1; + if( !sqlite3MatchEName(&pEList->a[j], zCol, zTab, zDb) ){ + continue; } + if( cnt>0 ){ + if( pItem->fg.isUsing==0 + || sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0 + ){ + /* Two or more tables have the same column name which is + ** not joined by USING. This is an error. Signal as much + ** by clearing pFJMatch and letting cnt go above 1. */ + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else + if( (pItem->fg.jointype & JT_RIGHT)==0 ){ + /* An INNER or LEFT JOIN. Use the left-most table */ + continue; + }else + if( (pItem->fg.jointype & JT_LEFT)==0 ){ + /* A RIGHT JOIN. Use the right-most table */ + cnt = 0; + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else{ + /* For a FULL JOIN, we must construct a coalesce() func */ + extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn); + } + } + cnt++; + cntTab = 2; + pMatch = pItem; + pExpr->iColumn = j; + pEList->a[j].fg.bUsed = 1; + hit = 1; + if( pEList->a[j].fg.bUsingTerm ) break; } if( hit || zTab==0 ) continue; } - if( zDb && pTab->pSchema!=pSchema ){ - continue; - } + assert( zDb==0 || zTab!=0 ); if( zTab ){ - const char *zTabName = pItem->zAlias ? pItem->zAlias : pTab->zName; + const char *zTabName; + if( zDb ){ + if( pTab->pSchema!=pSchema ) continue; + if( pSchema==0 && strcmp(zDb,"*")!=0 ) continue; + } + zTabName = pItem->zAlias ? pItem->zAlias : pTab->zName; assert( zTabName!=0 ); if( sqlite3StrICmp(zTabName, zTab)!=0 ){ continue; } + assert( ExprUseYTab(pExpr) ); if( IN_RENAME_OBJECT && pItem->zAlias ){ sqlite3RenameTokenRemap(pParse, 0, (void*)&pExpr->y.pTab); } } - if( 0==(cntTab++) ){ - pMatch = pItem; - } hCol = sqlite3StrIHash(zCol); for(j=0, pCol=pTab->aCol; jnCol; j++, pCol++){ - if( pCol->hName==hCol && sqlite3StrICmp(pCol->zName, zCol)==0 ){ - /* If there has been exactly one prior match and this match - ** is for the right-hand table of a NATURAL JOIN or is in a - ** USING clause, then skip this match. - */ - if( cnt==1 ){ - if( pItem->fg.jointype & JT_NATURAL ) continue; - if( nameInUsingClause(pItem->pUsing, zCol) ) continue; + if( pCol->hName==hCol + && sqlite3StrICmp(pCol->zCnName, zCol)==0 + ){ + if( cnt>0 ){ + if( pItem->fg.isUsing==0 + || sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0 + ){ + /* Two or more tables have the same column name which is + ** not joined by USING. This is an error. Signal as much + ** by clearing pFJMatch and letting cnt go above 1. */ + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else + if( (pItem->fg.jointype & JT_RIGHT)==0 ){ + /* An INNER or LEFT JOIN. Use the left-most table */ + continue; + }else + if( (pItem->fg.jointype & JT_LEFT)==0 ){ + /* A RIGHT JOIN. Use the right-most table */ + cnt = 0; + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else{ + /* For a FULL JOIN, we must construct a coalesce() func */ + extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn); + } } cnt++; pMatch = pItem; /* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */ pExpr->iColumn = j==pTab->iPKey ? -1 : (i16)j; + if( pItem->fg.isNestedFrom ){ + sqlite3SrcItemColumnUsed(pItem, j); + } break; } } + if( 0==cnt && VisibleRowid(pTab) ){ + cntTab++; + pMatch = pItem; + } } if( pMatch ){ pExpr->iTable = pMatch->iCursor; + assert( ExprUseYTab(pExpr) ); pExpr->y.pTab = pMatch->pTab; - /* RIGHT JOIN not (yet) supported */ - assert( (pMatch->fg.jointype & JT_RIGHT)==0 ); - if( (pMatch->fg.jointype & JT_LEFT)!=0 ){ + if( (pMatch->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 ){ ExprSetProperty(pExpr, EP_CanBeNull); } pSchema = pExpr->y.pTab->pSchema; @@ -99035,7 +101982,9 @@ static int lookupName( pSchema = pTab->pSchema; cntTab++; for(iCol=0, pCol=pTab->aCol; iColnCol; iCol++, pCol++){ - if( pCol->hName==hCol && sqlite3StrICmp(pCol->zName, zCol)==0 ){ + if( pCol->hName==hCol + && sqlite3StrICmp(pCol->zCnName, zCol)==0 + ){ if( iCol==pTab->iPKey ){ iCol = -1; } @@ -99052,6 +102001,7 @@ static int lookupName( #ifndef SQLITE_OMIT_UPSERT if( pExpr->iTable==EXCLUDED_TABLE_NUMBER ){ testcase( iCol==(-1) ); + assert( ExprUseYTab(pExpr) ); if( IN_RENAME_OBJECT ){ pExpr->iColumn = iCol; pExpr->y.pTab = pTab; @@ -99064,9 +102014,11 @@ static int lookupName( }else #endif /* SQLITE_OMIT_UPSERT */ { + assert( ExprUseYTab(pExpr) ); pExpr->y.pTab = pTab; if( pParse->bReturning ){ eNewExprOp = TK_REGISTER; + pExpr->op2 = TK_COLUMN; pExpr->iTable = pNC->uNC.iBaseReg + (pTab->nCol+1)*pExpr->iTable + sqlite3TableColumnToStorage(pTab, iCol) + 1; }else{ @@ -99100,7 +102052,7 @@ static int lookupName( && pMatch && (pNC->ncFlags & (NC_IdxExpr|NC_GenCol))==0 && sqlite3IsRowid(zCol) - && VisibleRowid(pMatch->pTab) + && ALWAYS(VisibleRowid(pMatch->pTab)) ){ cnt = 1; pExpr->iColumn = -1; @@ -99125,21 +102077,21 @@ static int lookupName( ** is supported for backwards compatibility only. Hence, we issue a warning ** on sqlite3_log() whenever the capability is used. */ - if( (pNC->ncFlags & NC_UEList)!=0 - && cnt==0 + if( cnt==0 + && (pNC->ncFlags & NC_UEList)!=0 && zTab==0 ){ pEList = pNC->uNC.pEList; assert( pEList!=0 ); for(j=0; jnExpr; j++){ char *zAs = pEList->a[j].zEName; - if( pEList->a[j].eEName==ENAME_NAME + if( pEList->a[j].fg.eEName==ENAME_NAME && sqlite3_stricmp(zAs, zCol)==0 ){ Expr *pOrig; assert( pExpr->pLeft==0 && pExpr->pRight==0 ); - assert( pExpr->x.pList==0 ); - assert( pExpr->x.pSelect==0 ); + assert( ExprUseXList(pExpr)==0 || pExpr->x.pList==0 ); + assert( ExprUseXSelect(pExpr)==0 || pExpr->x.pSelect==0 ); pOrig = pEList->a[j].pExpr; if( (pNC->ncFlags&NC_AllowAgg)==0 && ExprHasProperty(pOrig, EP_Agg) ){ sqlite3ErrorMsg(pParse, "misuse of aliased aggregate %s", zAs); @@ -99190,7 +102142,6 @@ static int lookupName( assert( pExpr->op==TK_ID ); if( ExprHasProperty(pExpr,EP_DblQuoted) && areDoubleQuotedStringsEnabled(db, pTopNC) - && (db->init.bDropColumn==0 || sqlite3StrICmp(zCol, db->init.azInit[0])!=0) ){ /* If a double-quoted identifier does not match any known column name, ** then treat it as a string. @@ -99205,11 +102156,6 @@ static int lookupName( ** Someday, I hope to get rid of this hack. Unfortunately there is ** a huge amount of legacy SQL that uses it. So for now, we just ** issue a warning. - ** - ** 2021-03-15: ticket 1c24a659e6d7f3a1 - ** Do not do the ID-to-STRING conversion when doing the schema - ** sanity check following a DROP COLUMN if the identifer name matches - ** the name of the column being dropped. */ sqlite3_log(SQLITE_WARNING, "double-quoted string literal: \"%w\"", zCol); @@ -99217,7 +102163,7 @@ static int lookupName( sqlite3VdbeAddDblquoteStr(db, pParse->pVdbe, zCol); #endif pExpr->op = TK_STRING; - pExpr->y.pTab = 0; + memset(&pExpr->y, 0, sizeof(pExpr->y)); return WRC_Prune; } if( sqlite3ExprIdToTrueFalse(pExpr) ){ @@ -99226,11 +102172,37 @@ static int lookupName( } /* - ** cnt==0 means there was not match. cnt>1 means there were two or - ** more matches. Either way, we have an error. + ** cnt==0 means there was not match. + ** cnt>1 means there were two or more matches. + ** + ** cnt==0 is always an error. cnt>1 is often an error, but might + ** be multiple matches for a NATURAL LEFT JOIN or a LEFT JOIN USING. */ + assert( pFJMatch==0 || cnt>0 ); + assert( !ExprHasProperty(pExpr, EP_xIsSelect|EP_IntValue) ); if( cnt!=1 ){ const char *zErr; + if( pFJMatch ){ + if( pFJMatch->nExpr==cnt-1 ){ + if( ExprHasProperty(pExpr,EP_Leaf) ){ + ExprClearProperty(pExpr,EP_Leaf); + }else{ + sqlite3ExprDelete(db, pExpr->pLeft); + pExpr->pLeft = 0; + sqlite3ExprDelete(db, pExpr->pRight); + pExpr->pRight = 0; + } + extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn); + pExpr->op = TK_FUNCTION; + pExpr->u.zToken = "coalesce"; + pExpr->x.pList = pFJMatch; + cnt = 1; + goto lookupname_end; + }else{ + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + } + } zErr = cnt==0 ? "no such column" : "ambiguous column name"; if( zDb ){ sqlite3ErrorMsg(pParse, "%s: %s.%s.%s", zErr, zDb, zTab, zCol); @@ -99239,8 +102211,19 @@ static int lookupName( }else{ sqlite3ErrorMsg(pParse, "%s: %s", zErr, zCol); } + sqlite3RecordErrorOffsetOfExpr(pParse->db, pExpr); pParse->checkSchema = 1; - pTopNC->nErr++; + pTopNC->nNcErr++; + } + assert( pFJMatch==0 ); + + /* Remove all substructure from pExpr */ + if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){ + sqlite3ExprDelete(db, pExpr->pLeft); + pExpr->pLeft = 0; + sqlite3ExprDelete(db, pExpr->pRight); + pExpr->pRight = 0; + ExprSetProperty(pExpr, EP_Leaf); } /* If a column from a table in pSrcList is referenced, then record @@ -99261,16 +102244,7 @@ static int lookupName( pMatch->colUsed |= sqlite3ExprColUsed(pExpr); } - /* Clean up and return - */ - if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){ - sqlite3ExprDelete(db, pExpr->pLeft); - pExpr->pLeft = 0; - sqlite3ExprDelete(db, pExpr->pRight); - pExpr->pRight = 0; - } pExpr->op = eNewExprOp; - ExprSetProperty(pExpr, EP_Leaf); lookupname_end: if( cnt==1 ){ assert( pNC!=0 ); @@ -99303,7 +102277,9 @@ SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *db, SrcList *pSrc, int iSr Expr *p = sqlite3ExprAlloc(db, TK_COLUMN, 0, 0); if( p ){ SrcItem *pItem = &pSrc->a[iSrc]; - Table *pTab = p->y.pTab = pItem->pTab; + Table *pTab; + assert( ExprUseYTab(p) ); + pTab = p->y.pTab = pItem->pTab; p->iTable = pItem->iCursor; if( p->y.pTab->iPKey==iCol ){ p->iColumn = -1; @@ -99345,7 +102321,8 @@ static void notValidImpl( Parse *pParse, /* Leave error message here */ NameContext *pNC, /* The name context */ const char *zMsg, /* Type of error */ - Expr *pExpr /* Invalidate this expression on error */ + Expr *pExpr, /* Invalidate this expression on error */ + Expr *pError /* Associate error with this expression */ ){ const char *zIn = "partial index WHERE clauses"; if( pNC->ncFlags & NC_IdxExpr ) zIn = "index expressions"; @@ -99357,10 +102334,11 @@ static void notValidImpl( #endif sqlite3ErrorMsg(pParse, "%s prohibited in %s", zMsg, zIn); if( pExpr ) pExpr->op = TK_NULL; + sqlite3RecordErrorOffsetOfExpr(pParse->db, pError); } -#define sqlite3ResolveNotValid(P,N,M,X,E) \ +#define sqlite3ResolveNotValid(P,N,M,X,E,R) \ assert( ((X)&~(NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol))==0 ); \ - if( ((N)->ncFlags & (X))!=0 ) notValidImpl(P,N,M,E); + if( ((N)->ncFlags & (X))!=0 ) notValidImpl(P,N,M,E,R); /* ** Expression p should encode a floating point value between 1.0 and 0.0. @@ -99370,6 +102348,7 @@ static void notValidImpl( static int exprProbability(Expr *p){ double r = -1.0; if( p->op!=TK_FLOAT ) return -1; + assert( !ExprHasProperty(p, EP_IntValue) ); sqlite3AtoF(p->u.zToken, &r, sqlite3Strlen30(p->u.zToken), SQLITE_UTF8); assert( r>=0.0 ); if( r>1.0 ) return -1; @@ -99418,6 +102397,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ assert( pSrcList && pSrcList->nSrc>=1 ); pItem = pSrcList->a; pExpr->op = TK_COLUMN; + assert( ExprUseYTab(pExpr) ); pExpr->y.pTab = pItem->pTab; pExpr->iTable = pItem->iCursor; pExpr->iColumn--; @@ -99449,6 +102429,8 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ } sqlite3WalkExpr(pWalker, pExpr->pLeft); if( 0==sqlite3ExprCanBeNull(pExpr->pLeft) && !IN_RENAME_OBJECT ){ + testcase( ExprHasProperty(pExpr, EP_OuterON) ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); if( pExpr->op==TK_NOTNULL ){ pExpr->u.zToken = "true"; ExprSetProperty(pExpr, EP_IsTrue); @@ -99484,24 +102466,28 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ if( pExpr->op==TK_ID ){ zDb = 0; zTable = 0; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); zColumn = pExpr->u.zToken; }else{ Expr *pLeft = pExpr->pLeft; testcase( pNC->ncFlags & NC_IdxExpr ); testcase( pNC->ncFlags & NC_GenCol ); sqlite3ResolveNotValid(pParse, pNC, "the \".\" operator", - NC_IdxExpr|NC_GenCol, 0); + NC_IdxExpr|NC_GenCol, 0, pExpr); pRight = pExpr->pRight; if( pRight->op==TK_ID ){ zDb = 0; }else{ assert( pRight->op==TK_DOT ); + assert( !ExprHasProperty(pRight, EP_IntValue) ); zDb = pLeft->u.zToken; pLeft = pRight->pLeft; pRight = pRight->pRight; } + assert( ExprUseUToken(pLeft) && ExprUseUToken(pRight) ); zTable = pLeft->u.zToken; zColumn = pRight->u.zToken; + assert( ExprUseYTab(pExpr) ); if( IN_RENAME_OBJECT ){ sqlite3RenameTokenRemap(pParse, (void*)pExpr, (void*)pRight); sqlite3RenameTokenRemap(pParse, (void*)&pExpr->y.pTab, (void*)pLeft); @@ -99518,7 +102504,6 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ int no_such_func = 0; /* True if no such function exists */ int wrong_num_args = 0; /* True if wrong number of arguments */ int is_agg = 0; /* True if is an aggregate function */ - int nId; /* Number of characters in function name */ const char *zId; /* The function name. */ FuncDef *pDef; /* Information about the function */ u8 enc = ENC(pParse->db); /* The database encoding */ @@ -99526,9 +102511,8 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ #ifndef SQLITE_OMIT_WINDOWFUNC Window *pWin = (IsWindowFunc(pExpr) ? pExpr->y.pWin : 0); #endif - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); + assert( !ExprHasProperty(pExpr, EP_xIsSelect|EP_IntValue) ); zId = pExpr->u.zToken; - nId = sqlite3Strlen30(zId); pDef = sqlite3FindFunction(pParse->db, zId, n, enc, 0); if( pDef==0 ){ pDef = sqlite3FindFunction(pParse->db, zId, -2, enc, 0); @@ -99545,9 +102529,9 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ pExpr->iTable = exprProbability(pList->a[1].pExpr); if( pExpr->iTable<0 ){ sqlite3ErrorMsg(pParse, - "second argument to likelihood() must be a " - "constant between 0.0 and 1.0"); - pNC->nErr++; + "second argument to %#T() must be a " + "constant between 0.0 and 1.0", pExpr); + pNC->nNcErr++; } }else{ /* EVIDENCE-OF: R-61304-29449 The unlikely(X) function is @@ -99567,9 +102551,9 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ int auth = sqlite3AuthCheck(pParse, SQLITE_FUNCTION, 0,pDef->zName,0); if( auth!=SQLITE_OK ){ if( auth==SQLITE_DENY ){ - sqlite3ErrorMsg(pParse, "not authorized to use function: %s", - pDef->zName); - pNC->nErr++; + sqlite3ErrorMsg(pParse, "not authorized to use function: %#T", + pExpr); + pNC->nNcErr++; } pExpr->op = TK_NULL; return WRC_Prune; @@ -99591,7 +102575,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ ** in a CHECK constraint. SQLServer, MySQL, and PostgreSQL all ** all this. */ sqlite3ResolveNotValid(pParse, pNC, "non-deterministic functions", - NC_IdxExpr|NC_PartIdx|NC_GenCol, 0); + NC_IdxExpr|NC_PartIdx|NC_GenCol, 0, pExpr); }else{ assert( (NC_SelfRef & 0xff)==NC_SelfRef ); /* Must fit in 8 bits */ pExpr->op2 = pNC->ncFlags & NC_SelfRef; @@ -99604,7 +102588,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ /* Internal-use-only functions are disallowed unless the ** SQL is being compiled using sqlite3NestedParse() or ** the SQLITE_TESTCTRL_INTERNAL_FUNCTIONS test-control has be - ** used to activate internal functionsn for testing purposes */ + ** used to activate internal functions for testing purposes */ no_such_func = 1; pDef = 0; }else @@ -99623,9 +102607,9 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ ); if( pDef && pDef->xValue==0 && pWin ){ sqlite3ErrorMsg(pParse, - "%.*s() may not be used as a window function", nId, zId + "%#T() may not be used as a window function", pExpr ); - pNC->nErr++; + pNC->nNcErr++; }else if( (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) || (is_agg && (pDef->funcFlags&SQLITE_FUNC_WINDOW) && !pWin) @@ -99637,14 +102621,14 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ }else{ zType = "aggregate"; } - sqlite3ErrorMsg(pParse, "misuse of %s function %.*s()",zType,nId,zId); - pNC->nErr++; + sqlite3ErrorMsg(pParse, "misuse of %s function %#T()",zType,pExpr); + pNC->nNcErr++; is_agg = 0; } #else if( (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) ){ - sqlite3ErrorMsg(pParse,"misuse of aggregate function %.*s()",nId,zId); - pNC->nErr++; + sqlite3ErrorMsg(pParse,"misuse of aggregate function %#T()",pExpr); + pNC->nNcErr++; is_agg = 0; } #endif @@ -99653,20 +102637,20 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ && pParse->explain==0 #endif ){ - sqlite3ErrorMsg(pParse, "no such function: %.*s", nId, zId); - pNC->nErr++; + sqlite3ErrorMsg(pParse, "no such function: %#T", pExpr); + pNC->nNcErr++; }else if( wrong_num_args ){ - sqlite3ErrorMsg(pParse,"wrong number of arguments to function %.*s()", - nId, zId); - pNC->nErr++; + sqlite3ErrorMsg(pParse,"wrong number of arguments to function %#T()", + pExpr); + pNC->nNcErr++; } #ifndef SQLITE_OMIT_WINDOWFUNC else if( is_agg==0 && ExprHasProperty(pExpr, EP_WinFunc) ){ sqlite3ErrorMsg(pParse, - "FILTER may not be used with non-aggregate %.*s()", - nId, zId + "FILTER may not be used with non-aggregate %#T()", + pExpr ); - pNC->nErr++; + pNC->nNcErr++; } #endif if( is_agg ){ @@ -99690,7 +102674,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ #ifndef SQLITE_OMIT_WINDOWFUNC if( pWin ){ Select *pSel = pNC->pWinSelect; - assert( pWin==pExpr->y.pWin ); + assert( pWin==0 || (ExprUseYWin(pExpr) && pWin==pExpr->y.pWin) ); if( IN_RENAME_OBJECT==0 ){ sqlite3WindowUpdate(pParse, pSel ? pSel->pWinDefn : 0, pWin, pDef); if( pParse->db->mallocFailed ) break; @@ -99703,7 +102687,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ }else #endif /* SQLITE_OMIT_WINDOWFUNC */ { - NameContext *pNC2 = pNC; + NameContext *pNC2; /* For looping up thru outer contexts */ pExpr->op = TK_AGG_FUNCTION; pExpr->op2 = 0; #ifndef SQLITE_OMIT_WINDOWFUNC @@ -99711,16 +102695,22 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ sqlite3WalkExpr(pWalker, pExpr->y.pWin->pFilter); } #endif - while( pNC2 && !sqlite3FunctionUsesThisSrc(pExpr, pNC2->pSrcList) ){ + pNC2 = pNC; + while( pNC2 + && sqlite3ReferencesSrcList(pParse, pExpr, pNC2->pSrcList)==0 + ){ pExpr->op2++; pNC2 = pNC2->pNext; } assert( pDef!=0 || IN_RENAME_OBJECT ); if( pNC2 && pDef ){ assert( SQLITE_FUNC_MINMAX==NC_MinMaxAgg ); + assert( SQLITE_FUNC_ANYORDER==NC_OrderAgg ); testcase( (pDef->funcFlags & SQLITE_FUNC_MINMAX)!=0 ); - pNC2->ncFlags |= NC_HasAgg | (pDef->funcFlags & SQLITE_FUNC_MINMAX); - + testcase( (pDef->funcFlags & SQLITE_FUNC_ANYORDER)!=0 ); + pNC2->ncFlags |= NC_HasAgg + | ((pDef->funcFlags^SQLITE_FUNC_ANYORDER) + & (SQLITE_FUNC_MINMAX|SQLITE_FUNC_ANYORDER)); } } pNC->ncFlags |= savedAllowFlags; @@ -99736,15 +102726,17 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ #endif case TK_IN: { testcase( pExpr->op==TK_IN ); - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ int nRef = pNC->nRef; testcase( pNC->ncFlags & NC_IsCheck ); testcase( pNC->ncFlags & NC_PartIdx ); testcase( pNC->ncFlags & NC_IdxExpr ); testcase( pNC->ncFlags & NC_GenCol ); - sqlite3ResolveNotValid(pParse, pNC, "subqueries", - NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol, pExpr); - sqlite3WalkSelect(pWalker, pExpr->x.pSelect); + if( pNC->ncFlags & NC_SelfRef ){ + notValidImpl(pParse, pNC, "subqueries", pExpr, pExpr); + }else{ + sqlite3WalkSelect(pWalker, pExpr->x.pSelect); + } assert( pNC->nRef>=nRef ); if( nRef!=pNC->nRef ){ ExprSetProperty(pExpr, EP_VarSelect); @@ -99759,7 +102751,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ testcase( pNC->ncFlags & NC_IdxExpr ); testcase( pNC->ncFlags & NC_GenCol ); sqlite3ResolveNotValid(pParse, pNC, "parameters", - NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol, pExpr); + NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol, pExpr, pExpr); break; } case TK_IS: @@ -99791,6 +102783,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ assert( pExpr->pLeft!=0 ); nLeft = sqlite3ExprVectorSize(pExpr->pLeft); if( pExpr->op==TK_BETWEEN ){ + assert( ExprUseXList(pExpr) ); nRight = sqlite3ExprVectorSize(pExpr->x.pList->a[0].pExpr); if( nRight==nLeft ){ nRight = sqlite3ExprVectorSize(pExpr->x.pList->a[1].pExpr); @@ -99810,11 +102803,13 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ testcase( pExpr->op==TK_ISNOT ); testcase( pExpr->op==TK_BETWEEN ); sqlite3ErrorMsg(pParse, "row value misused"); + sqlite3RecordErrorOffsetOfExpr(pParse->db, pExpr); } break; } } - return (pParse->nErr || pParse->db->mallocFailed) ? WRC_Abort : WRC_Continue; + assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); + return pParse->nErr ? WRC_Abort : WRC_Continue; } /* @@ -99839,9 +102834,11 @@ static int resolveAsName( UNUSED_PARAMETER(pParse); if( pE->op==TK_ID ){ - char *zCol = pE->u.zToken; + const char *zCol; + assert( !ExprHasProperty(pE, EP_IntValue) ); + zCol = pE->u.zToken; for(i=0; inExpr; i++){ - if( pEList->a[i].eEName==ENAME_NAME + if( pEList->a[i].fg.eEName==ENAME_NAME && sqlite3_stricmp(pEList->a[i].zEName, zCol)==0 ){ return i+1; @@ -99890,11 +102887,11 @@ static int resolveOrderByTermToExprList( nc.pParse = pParse; nc.pSrcList = pSelect->pSrc; nc.uNC.pEList = pEList; - nc.ncFlags = NC_AllowAgg|NC_UEList; - nc.nErr = 0; + nc.ncFlags = NC_AllowAgg|NC_UEList|NC_NoSelect; + nc.nNcErr = 0; db = pParse->db; savedSuppErr = db->suppressErr; - if( IN_RENAME_OBJECT==0 ) db->suppressErr = 1; + db->suppressErr = 1; rc = sqlite3ResolveExprNames(&nc, pE); db->suppressErr = savedSuppErr; if( rc ) return 0; @@ -99920,11 +102917,13 @@ static void resolveOutOfRangeError( Parse *pParse, /* The error context into which to write the error */ const char *zType, /* "ORDER" or "GROUP" */ int i, /* The index (1-based) of the term out of range */ - int mx /* Largest permissible value of i */ + int mx, /* Largest permissible value of i */ + Expr *pError /* Associate the error with the expression */ ){ sqlite3ErrorMsg(pParse, "%r %s BY term out of range - should be " "between 1 and %d", i, zType, mx); + sqlite3RecordErrorOffsetOfExpr(pParse->db, pError); } /* @@ -99960,7 +102959,7 @@ static int resolveCompoundOrderBy( return 1; } for(i=0; inExpr; i++){ - pOrderBy->a[i].done = 0; + pOrderBy->a[i].fg.done = 0; } pSelect->pNext = 0; while( pSelect->pPrior ){ @@ -99975,12 +102974,12 @@ static int resolveCompoundOrderBy( for(i=0, pItem=pOrderBy->a; inExpr; i++, pItem++){ int iCol = -1; Expr *pE, *pDup; - if( pItem->done ) continue; + if( pItem->fg.done ) continue; pE = sqlite3ExprSkipCollateAndLikely(pItem->pExpr); if( NEVER(pE==0) ) continue; if( sqlite3ExprIsInteger(pE, &iCol) ){ if( iCol<=0 || iCol>pEList->nExpr ){ - resolveOutOfRangeError(pParse, "ORDER", i+1, pEList->nExpr); + resolveOutOfRangeError(pParse, "ORDER", i+1, pEList->nExpr, pE); return 1; } }else{ @@ -99993,29 +102992,24 @@ static int resolveCompoundOrderBy( ** Once the comparisons are finished, the duplicate expression ** is deleted. ** - ** Or, if this is running as part of an ALTER TABLE operation, - ** resolve the symbols in the actual expression, not a duplicate. - ** And, if one of the comparisons is successful, leave the expression - ** as is instead of transforming it to an integer as in the usual - ** case. This allows the code in alter.c to modify column - ** refererences within the ORDER BY expression as required. */ - if( IN_RENAME_OBJECT ){ - pDup = pE; - }else{ - pDup = sqlite3ExprDup(db, pE, 0); - } + ** If this is running as part of an ALTER TABLE operation and + ** the symbols resolve successfully, also resolve the symbols in the + ** actual expression. This allows the code in alter.c to modify + ** column references within the ORDER BY expression as required. */ + pDup = sqlite3ExprDup(db, pE, 0); if( !db->mallocFailed ){ assert(pDup); iCol = resolveOrderByTermToExprList(pParse, pSelect, pDup); + if( IN_RENAME_OBJECT && iCol>0 ){ + resolveOrderByTermToExprList(pParse, pSelect, pE); + } } - if( !IN_RENAME_OBJECT ){ - sqlite3ExprDelete(db, pDup); - } + sqlite3ExprDelete(db, pDup); } } if( iCol>0 ){ /* Convert the ORDER BY term into an integer column number iCol, - ** taking care to preserve the COLLATE clause if it exists */ + ** taking care to preserve the COLLATE clause if it exists. */ if( !IN_RENAME_OBJECT ){ Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0); if( pNew==0 ) return 1; @@ -100033,7 +103027,7 @@ static int resolveCompoundOrderBy( sqlite3ExprDelete(db, pE); pItem->u.x.iOrderByCol = (u16)iCol; } - pItem->done = 1; + pItem->fg.done = 1; }else{ moreToDo = 1; } @@ -100041,7 +103035,7 @@ static int resolveCompoundOrderBy( pSelect = pSelect->pNext; } for(i=0; inExpr; i++){ - if( pOrderBy->a[i].done==0 ){ + if( pOrderBy->a[i].fg.done==0 ){ sqlite3ErrorMsg(pParse, "%r ORDER BY term does not match any " "column in the result set", i+1); return 1; @@ -100081,7 +103075,7 @@ SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy( for(i=0, pItem=pOrderBy->a; inExpr; i++, pItem++){ if( pItem->u.x.iOrderByCol ){ if( pItem->u.x.iOrderByCol>pEList->nExpr ){ - resolveOutOfRangeError(pParse, zType, i+1, pEList->nExpr); + resolveOutOfRangeError(pParse, zType, i+1, pEList->nExpr, 0); return 1; } resolveAlias(pParse, pEList, pItem->u.x.iOrderByCol-1, pItem->pExpr,0); @@ -100150,7 +103144,7 @@ static int resolveOrderGroupBy( Parse *pParse; /* Parsing context */ int nResult; /* Number of terms in the result set */ - if( pOrderBy==0 ) return 0; + assert( pOrderBy!=0 ); nResult = pSelect->pEList->nExpr; pParse = pNC->pParse; for(i=0, pItem=pOrderBy->a; inExpr; i++, pItem++){ @@ -100173,7 +103167,7 @@ static int resolveOrderGroupBy( ** number so that sqlite3ResolveOrderGroupBy() will convert the ** order-by term to a copy of the result-set expression */ if( iCol<1 || iCol>0xffff ){ - resolveOutOfRangeError(pParse, zType, i+1, nResult); + resolveOutOfRangeError(pParse, zType, i+1, nResult, pE2); return 1; } pItem->u.x.iOrderByCol = (u16)iCol; @@ -100231,7 +103225,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ */ if( (p->selFlags & SF_Expanded)==0 ){ sqlite3SelectPrep(pParse, p, pOuterNC); - return (pParse->nErr || db->mallocFailed) ? WRC_Abort : WRC_Prune; + return pParse->nErr ? WRC_Abort : WRC_Prune; } isCompound = p->pPrior!=0; @@ -100240,8 +103234,10 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ while( p ){ assert( (p->selFlags & SF_Expanded)!=0 ); assert( (p->selFlags & SF_Resolved)==0 ); + assert( db->suppressErr==0 ); /* SF_Resolved not set if errors suppressed */ p->selFlags |= SF_Resolved; + /* Resolve the expressions in the LIMIT and OFFSET clauses. These ** are not allowed to refer to any names, so pass an empty NameContext. */ @@ -100266,7 +103262,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ p->pOrderBy = 0; } - /* Recursively resolve names in all subqueries + /* Recursively resolve names in all subqueries in the FROM clause */ for(i=0; ipSrc->nSrc; i++){ SrcItem *pItem = &p->pSrc->a[i]; @@ -100277,7 +103273,8 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ if( pItem->zName ) pParse->zAuthContext = pItem->zName; sqlite3ResolveSelectNames(pParse, pItem->pSelect, pOuterNC); pParse->zAuthContext = zSavedContext; - if( pParse->nErr || db->mallocFailed ) return WRC_Abort; + if( pParse->nErr ) return WRC_Abort; + assert( db->mallocFailed==0 ); /* If the number of references to the outer context changed when ** expressions in the sub-select were resolved, the sub-select @@ -100310,18 +103307,12 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ pGroupBy = p->pGroupBy; if( pGroupBy || (sNC.ncFlags & NC_HasAgg)!=0 ){ assert( NC_MinMaxAgg==SF_MinMaxAgg ); - p->selFlags |= SF_Aggregate | (sNC.ncFlags&NC_MinMaxAgg); + assert( NC_OrderAgg==SF_OrderByReqd ); + p->selFlags |= SF_Aggregate | (sNC.ncFlags&(NC_MinMaxAgg|NC_OrderAgg)); }else{ sNC.ncFlags &= ~NC_AllowAgg; } - /* If a HAVING clause is present, then there must be a GROUP BY clause. - */ - if( p->pHaving && !pGroupBy ){ - sqlite3ErrorMsg(pParse, "a GROUP BY clause is required before HAVING"); - return WRC_Abort; - } - /* Add the output column list to the name-context before parsing the ** other expressions in the SELECT statement. This is so that ** expressions in the WHERE clause (etc.) can refer to expressions by @@ -100333,7 +103324,13 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ assert( (sNC.ncFlags & (NC_UAggInfo|NC_UUpsert|NC_UBaseReg))==0 ); sNC.uNC.pEList = p->pEList; sNC.ncFlags |= NC_UEList; - if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort; + if( p->pHaving ){ + if( (p->selFlags & SF_Aggregate)==0 ){ + sqlite3ErrorMsg(pParse, "HAVING clause on a non-aggregate query"); + return WRC_Abort; + } + if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort; + } if( sqlite3ResolveExprNames(&sNC, p->pWhere) ) return WRC_Abort; /* Resolve names in table-valued-function arguments */ @@ -100346,6 +103343,19 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ } } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( IN_RENAME_OBJECT ){ + Window *pWin; + for(pWin=p->pWinDefn; pWin; pWin=pWin->pNextWin){ + if( sqlite3ResolveExprListNames(&sNC, pWin->pOrderBy) + || sqlite3ResolveExprListNames(&sNC, pWin->pPartition) + ){ + return WRC_Abort; + } + } + } +#endif + /* The ORDER BY and GROUP BY clauses may not refer to terms in ** outer queries */ @@ -100373,7 +103383,8 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ** is not detected until much later, and so we need to go ahead and ** resolve those symbols on the incorrect ORDER BY for consistency. */ - if( isCompound<=nCompound /* Defer right-most ORDER BY of a compound */ + if( p->pOrderBy!=0 + && isCompound<=nCompound /* Defer right-most ORDER BY of a compound */ && resolveOrderGroupBy(&sNC, p, p->pOrderBy, "ORDER") ){ return WRC_Abort; @@ -100401,19 +103412,6 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ } } -#ifndef SQLITE_OMIT_WINDOWFUNC - if( IN_RENAME_OBJECT ){ - Window *pWin; - for(pWin=p->pWinDefn; pWin; pWin=pWin->pNextWin){ - if( sqlite3ResolveExprListNames(&sNC, pWin->pOrderBy) - || sqlite3ResolveExprListNames(&sNC, pWin->pPartition) - ){ - return WRC_Abort; - } - } - } -#endif - /* If this is part of a compound SELECT, check that it has the right ** number of expressions in the select list. */ if( p->pNext && p->pEList->nExpr!=p->pNext->pEList->nExpr ){ @@ -100493,11 +103491,11 @@ SQLITE_PRIVATE int sqlite3ResolveExprNames( Walker w; if( pExpr==0 ) return SQLITE_OK; - savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin); - pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin); + savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); w.pParse = pNC->pParse; w.xExprCallback = resolveExprStep; - w.xSelectCallback = resolveSelectStep; + w.xSelectCallback = (pNC->ncFlags & NC_NoSelect) ? 0 : resolveSelectStep; w.xSelectCallback2 = 0; w.u.pNC = pNC; #if SQLITE_MAX_EXPR_DEPTH>0 @@ -100516,7 +103514,7 @@ SQLITE_PRIVATE int sqlite3ResolveExprNames( testcase( pNC->ncFlags & NC_HasWin ); ExprSetProperty(pExpr, pNC->ncFlags & (NC_HasAgg|NC_HasWin) ); pNC->ncFlags |= savedHasAgg; - return pNC->nErr>0 || w.pParse->nErr>0; + return pNC->nNcErr>0 || w.pParse->nErr>0; } /* @@ -100537,8 +103535,8 @@ SQLITE_PRIVATE int sqlite3ResolveExprListNames( w.xSelectCallback = resolveSelectStep; w.xSelectCallback2 = 0; w.u.pNC = pNC; - savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin); - pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin); + savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); for(i=0; inExpr; i++){ Expr *pExpr = pList->a[i].pExpr; if( pExpr==0 ) continue; @@ -100556,12 +103554,13 @@ SQLITE_PRIVATE int sqlite3ResolveExprListNames( assert( EP_Win==NC_HasWin ); testcase( pNC->ncFlags & NC_HasAgg ); testcase( pNC->ncFlags & NC_HasWin ); - if( pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin) ){ + if( pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg) ){ ExprSetProperty(pExpr, pNC->ncFlags & (NC_HasAgg|NC_HasWin) ); - savedHasAgg |= pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin); - pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin); + savedHasAgg |= pNC->ncFlags & + (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); } - if( pNC->nErr>0 || w.pParse->nErr>0 ) return WRC_Abort; + if( w.pParse->nErr>0 ) return WRC_Abort; } pNC->ncFlags |= savedHasAgg; return WRC_Continue; @@ -100673,9 +103672,9 @@ static int exprCodeVector(Parse *pParse, Expr *p, int *piToFree); /* ** Return the affinity character for a single column of a table. */ -SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table *pTab, int iCol){ - assert( iColnCol ); - return iCol>=0 ? pTab->aCol[iCol].affinity : SQLITE_AFF_INTEGER; +SQLITE_PRIVATE char sqlite3TableColumnAffinity(const Table *pTab, int iCol){ + if( iCol<0 || NEVER(iCol>=pTab->nCol) ) return SQLITE_AFF_INTEGER; + return pTab->aCol[iCol].affinity; } /* @@ -100704,30 +103703,36 @@ SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr){ assert( pExpr!=0 ); } op = pExpr->op; + if( op==TK_REGISTER ) op = pExpr->op2; + if( op==TK_COLUMN || op==TK_AGG_COLUMN ){ + assert( ExprUseYTab(pExpr) ); + if( pExpr->y.pTab ){ + return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); + } + } if( op==TK_SELECT ){ - assert( pExpr->flags&EP_xIsSelect ); + assert( ExprUseXSelect(pExpr) ); assert( pExpr->x.pSelect!=0 ); assert( pExpr->x.pSelect->pEList!=0 ); assert( pExpr->x.pSelect->pEList->a[0].pExpr!=0 ); return sqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr); } - if( op==TK_REGISTER ) op = pExpr->op2; #ifndef SQLITE_OMIT_CAST if( op==TK_CAST ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); return sqlite3AffinityType(pExpr->u.zToken, 0); } #endif - if( (op==TK_AGG_COLUMN || op==TK_COLUMN) && pExpr->y.pTab ){ - return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); - } if( op==TK_SELECT_COLUMN ){ - assert( pExpr->pLeft->flags&EP_xIsSelect ); + assert( pExpr->pLeft!=0 && ExprUseXSelect(pExpr->pLeft) ); + assert( pExpr->iColumn < pExpr->iTable ); + assert( pExpr->iTable==pExpr->pLeft->x.pSelect->pEList->nExpr ); return sqlite3ExprAffinity( pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr ); } if( op==TK_VECTOR ){ + assert( ExprUseXList(pExpr) ); return sqlite3ExprAffinity(pExpr->x.pList->a[0].pExpr); } return pExpr->affExpr; @@ -100742,23 +103747,12 @@ SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr){ ** and the pExpr parameter is returned unchanged. */ SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken( - Parse *pParse, /* Parsing context */ + const Parse *pParse, /* Parsing context */ Expr *pExpr, /* Add the "COLLATE" clause to this expression */ const Token *pCollName, /* Name of collating sequence */ int dequote /* True to dequote pCollName */ ){ - assert( pExpr!=0 || pParse->db->mallocFailed ); - if( pExpr==0 ) return 0; - if( pExpr->op==TK_VECTOR ){ - ExprList *pList = pExpr->x.pList; - if( ALWAYS(pList!=0) ){ - int i; - for(i=0; inExpr; i++){ - pList->a[i].pExpr = sqlite3ExprAddCollateToken(pParse,pList->a[i].pExpr, - pCollName, dequote); - } - } - }else if( pCollName->n>0 ){ + if( pCollName->n>0 ){ Expr *pNew = sqlite3ExprAlloc(pParse->db, TK_COLLATE, pCollName, dequote); if( pNew ){ pNew->pLeft = pExpr; @@ -100768,7 +103762,11 @@ SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken( } return pExpr; } -SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse *pParse, Expr *pExpr, const char *zC){ +SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString( + const Parse *pParse, /* Parsing context */ + Expr *pExpr, /* Add the "COLLATE" clause to this expression */ + const char *zC /* The collating sequence name */ +){ Token s; assert( zC!=0 ); sqlite3TokenInit(&s, (char*)zC); @@ -100794,7 +103792,7 @@ SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr *pExpr){ SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr *pExpr){ while( pExpr && ExprHasProperty(pExpr, EP_Skip|EP_Unlikely) ){ if( ExprHasProperty(pExpr, EP_Unlikely) ){ - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); + assert( ExprUseXList(pExpr) ); assert( pExpr->x.pList->nExpr>0 ); assert( pExpr->op==TK_FUNCTION ); pExpr = pExpr->x.pList->a[0].pExpr; @@ -100827,27 +103825,30 @@ SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ while( p ){ int op = p->op; if( op==TK_REGISTER ) op = p->op2; - if( (op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_TRIGGER) - && p->y.pTab!=0 - ){ - /* op==TK_REGISTER && p->y.pTab!=0 happens when pExpr was originally - ** a TK_COLUMN but was previously evaluated and cached in a register */ - int j = p->iColumn; - if( j>=0 ){ - const char *zColl = p->y.pTab->aCol[j].zColl; - pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0); + if( op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_TRIGGER ){ + assert( ExprUseYTab(p) ); + if( p->y.pTab!=0 ){ + /* op==TK_REGISTER && p->y.pTab!=0 happens when pExpr was originally + ** a TK_COLUMN but was previously evaluated and cached in a register */ + int j = p->iColumn; + if( j>=0 ){ + const char *zColl = sqlite3ColumnColl(&p->y.pTab->aCol[j]); + pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0); + } + break; } - break; } if( op==TK_CAST || op==TK_UPLUS ){ p = p->pLeft; continue; } if( op==TK_VECTOR ){ + assert( ExprUseXList(p) ); p = p->x.pList->a[0].pExpr; continue; } if( op==TK_COLLATE ){ + assert( !ExprHasProperty(p, EP_IntValue) ); pColl = sqlite3GetCollSeq(pParse, ENC(db), 0, p->u.zToken); break; } @@ -100857,11 +103858,9 @@ SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ }else{ Expr *pNext = p->pRight; /* The Expr.x union is never used at the same time as Expr.pRight */ + assert( ExprUseXList(p) ); assert( p->x.pList==0 || p->pRight==0 ); - if( p->x.pList!=0 - && !db->mallocFailed - && ALWAYS(!ExprHasProperty(p, EP_xIsSelect)) - ){ + if( p->x.pList!=0 && !db->mallocFailed ){ int i; for(i=0; ALWAYS(ix.pList->nExpr); i++){ if( ExprHasProperty(p->x.pList->a[i].pExpr, EP_Collate) ){ @@ -100944,7 +103943,7 @@ static char comparisonAffinity(const Expr *pExpr){ aff = sqlite3ExprAffinity(pExpr->pLeft); if( pExpr->pRight ){ aff = sqlite3CompareAffinity(pExpr->pRight, aff); - }else if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + }else if( ExprUseXSelect(pExpr) ){ aff = sqlite3CompareAffinity(pExpr->x.pSelect->pEList->a[0].pExpr, aff); }else if( aff==0 ){ aff = SQLITE_AFF_BLOB; @@ -101070,7 +104069,7 @@ static int codeCompare( ** But a TK_SELECT might be either a vector or a scalar. It is only ** considered a vector if it has two or more result columns. */ -SQLITE_PRIVATE int sqlite3ExprIsVector(Expr *pExpr){ +SQLITE_PRIVATE int sqlite3ExprIsVector(const Expr *pExpr){ return sqlite3ExprVectorSize(pExpr)>1; } @@ -101080,12 +104079,14 @@ SQLITE_PRIVATE int sqlite3ExprIsVector(Expr *pExpr){ ** is a sub-select, return the number of columns in the sub-select. For ** any other type of expression, return 1. */ -SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr){ +SQLITE_PRIVATE int sqlite3ExprVectorSize(const Expr *pExpr){ u8 op = pExpr->op; if( op==TK_REGISTER ) op = pExpr->op2; if( op==TK_VECTOR ){ + assert( ExprUseXList(pExpr) ); return pExpr->x.pList->nExpr; }else if( op==TK_SELECT ){ + assert( ExprUseXSelect(pExpr) ); return pExpr->x.pSelect->pEList->nExpr; }else{ return 1; @@ -101108,12 +104109,14 @@ SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr){ ** been positioned. */ SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){ - assert( iop==TK_ERROR ); if( sqlite3ExprIsVector(pVector) ){ assert( pVector->op2==0 || pVector->op==TK_REGISTER ); if( pVector->op==TK_SELECT || pVector->op2==TK_SELECT ){ + assert( ExprUseXSelect(pVector) ); return pVector->x.pSelect->pEList->a[i].pExpr; }else{ + assert( ExprUseXList(pVector) ); return pVector->x.pList->a[i].pExpr; } } @@ -101144,11 +104147,12 @@ SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){ SQLITE_PRIVATE Expr *sqlite3ExprForVectorField( Parse *pParse, /* Parsing context */ Expr *pVector, /* The vector. List of expressions or a sub-SELECT */ - int iField /* Which column of the vector to return */ + int iField, /* Which column of the vector to return */ + int nField /* Total number of columns in the vector */ ){ Expr *pRet; if( pVector->op==TK_SELECT ){ - assert( pVector->flags & EP_xIsSelect ); + assert( ExprUseXSelect(pVector) ); /* The TK_SELECT_COLUMN Expr node: ** ** pLeft: pVector containing TK_SELECT. Not deleted. @@ -101167,14 +104171,23 @@ SQLITE_PRIVATE Expr *sqlite3ExprForVectorField( */ pRet = sqlite3PExpr(pParse, TK_SELECT_COLUMN, 0, 0); if( pRet ){ + pRet->iTable = nField; pRet->iColumn = iField; pRet->pLeft = pVector; } - assert( pRet==0 || pRet->iTable==0 ); }else{ - if( pVector->op==TK_VECTOR ) pVector = pVector->x.pList->a[iField].pExpr; + if( pVector->op==TK_VECTOR ){ + Expr **ppVector; + assert( ExprUseXList(pVector) ); + ppVector = &pVector->x.pList->a[iField].pExpr; + pVector = *ppVector; + if( IN_RENAME_OBJECT ){ + /* This must be a vector UPDATE inside a trigger */ + *ppVector = 0; + return pVector; + } + } pRet = sqlite3ExprDup(pParse->db, pVector, 0); - sqlite3RenameTokenRemap(pParse, pRet, pVector); } return pRet; } @@ -101224,17 +104237,22 @@ static int exprVectorRegister( int *pRegFree /* OUT: Temp register to free */ ){ u8 op = pVector->op; - assert( op==TK_VECTOR || op==TK_REGISTER || op==TK_SELECT ); + assert( op==TK_VECTOR || op==TK_REGISTER || op==TK_SELECT || op==TK_ERROR ); if( op==TK_REGISTER ){ *ppExpr = sqlite3VectorFieldSubexpr(pVector, iField); return pVector->iTable+iField; } if( op==TK_SELECT ){ + assert( ExprUseXSelect(pVector) ); *ppExpr = pVector->x.pSelect->pEList->a[iField].pExpr; return regSelect+iField; } - *ppExpr = pVector->x.pList->a[iField].pExpr; - return sqlite3ExprCodeTemp(pParse, *ppExpr, pRegFree); + if( op==TK_VECTOR ){ + assert( ExprUseXList(pVector) ); + *ppExpr = pVector->x.pList->a[iField].pExpr; + return sqlite3ExprCodeTemp(pParse, *ppExpr, pRegFree); + } + return 0; } /* @@ -101263,6 +104281,7 @@ static void codeVectorCompare( int regLeft = 0; int regRight = 0; u8 opx = op; + int addrCmp = 0; int addrDone = sqlite3VdbeMakeLabel(pParse); int isCommuted = ExprHasProperty(pExpr,EP_Commuted); @@ -101282,21 +104301,24 @@ static void codeVectorCompare( assert( p5==0 || pExpr->op!=op ); assert( p5==SQLITE_NULLEQ || pExpr->op==op ); - p5 |= SQLITE_STOREP2; - if( opx==TK_LE ) opx = TK_LT; - if( opx==TK_GE ) opx = TK_GT; + if( op==TK_LE ) opx = TK_LT; + if( op==TK_GE ) opx = TK_GT; + if( op==TK_NE ) opx = TK_EQ; regLeft = exprCodeSubselect(pParse, pLeft); regRight = exprCodeSubselect(pParse, pRight); + sqlite3VdbeAddOp2(v, OP_Integer, 1, dest); for(i=0; 1 /*Loop exits by "break"*/; i++){ int regFree1 = 0, regFree2 = 0; - Expr *pL, *pR; + Expr *pL = 0, *pR = 0; int r1, r2; assert( i>=0 && i0 @@ -101354,14 +104382,14 @@ SQLITE_PRIVATE int sqlite3ExprCheckHeight(Parse *pParse, int nHeight){ ** to by pnHeight, the second parameter, then set *pnHeight to that ** value. */ -static void heightOfExpr(Expr *p, int *pnHeight){ +static void heightOfExpr(const Expr *p, int *pnHeight){ if( p ){ if( p->nHeight>*pnHeight ){ *pnHeight = p->nHeight; } } } -static void heightOfExprList(ExprList *p, int *pnHeight){ +static void heightOfExprList(const ExprList *p, int *pnHeight){ if( p ){ int i; for(i=0; inExpr; i++){ @@ -101369,8 +104397,8 @@ static void heightOfExprList(ExprList *p, int *pnHeight){ } } } -static void heightOfSelect(Select *pSelect, int *pnHeight){ - Select *p; +static void heightOfSelect(const Select *pSelect, int *pnHeight){ + const Select *p; for(p=pSelect; p; p=p->pPrior){ heightOfExpr(p->pWhere, pnHeight); heightOfExpr(p->pHaving, pnHeight); @@ -101392,10 +104420,9 @@ static void heightOfSelect(Select *pSelect, int *pnHeight){ ** if appropriate. */ static void exprSetHeight(Expr *p){ - int nHeight = 0; - heightOfExpr(p->pLeft, &nHeight); - heightOfExpr(p->pRight, &nHeight); - if( ExprHasProperty(p, EP_xIsSelect) ){ + int nHeight = p->pLeft ? p->pLeft->nHeight : 0; + if( p->pRight && p->pRight->nHeight>nHeight ) nHeight = p->pRight->nHeight; + if( ExprUseXSelect(p) ){ heightOfSelect(p->x.pSelect, &nHeight); }else if( p->x.pList ){ heightOfExprList(p->x.pList, &nHeight); @@ -101422,7 +104449,7 @@ SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ ** Return the maximum height of any expression tree referenced ** by the select statement passed as an argument. */ -SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *p){ +SQLITE_PRIVATE int sqlite3SelectExprHeight(const Select *p){ int nHeight = 0; heightOfSelect(p, &nHeight); return nHeight; @@ -101434,7 +104461,7 @@ SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *p){ */ SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ if( pParse->nErr ) return; - if( p && p->x.pList && !ExprHasProperty(p, EP_xIsSelect) ){ + if( p && ExprUseXList(p) && p->x.pList ){ p->flags |= EP_Propagate & sqlite3ExprListFlags(p->x.pList); } } @@ -101592,6 +104619,63 @@ SQLITE_PRIVATE void sqlite3PExprAddSelect(Parse *pParse, Expr *pExpr, Select *pS } } +/* +** Expression list pEList is a list of vector values. This function +** converts the contents of pEList to a VALUES(...) Select statement +** returning 1 row for each element of the list. For example, the +** expression list: +** +** ( (1,2), (3,4) (5,6) ) +** +** is translated to the equivalent of: +** +** VALUES(1,2), (3,4), (5,6) +** +** Each of the vector values in pEList must contain exactly nElem terms. +** If a list element that is not a vector or does not contain nElem terms, +** an error message is left in pParse. +** +** This is used as part of processing IN(...) expressions with a list +** of vectors on the RHS. e.g. "... IN ((1,2), (3,4), (5,6))". +*/ +SQLITE_PRIVATE Select *sqlite3ExprListToValues(Parse *pParse, int nElem, ExprList *pEList){ + int ii; + Select *pRet = 0; + assert( nElem>1 ); + for(ii=0; iinExpr; ii++){ + Select *pSel; + Expr *pExpr = pEList->a[ii].pExpr; + int nExprElem; + if( pExpr->op==TK_VECTOR ){ + assert( ExprUseXList(pExpr) ); + nExprElem = pExpr->x.pList->nExpr; + }else{ + nExprElem = 1; + } + if( nExprElem!=nElem ){ + sqlite3ErrorMsg(pParse, "IN(...) element has %d term%s - expected %d", + nExprElem, nExprElem>1?"s":"", nElem + ); + break; + } + assert( ExprUseXList(pExpr) ); + pSel = sqlite3SelectNew(pParse, pExpr->x.pList, 0, 0, 0, 0, 0, SF_Values,0); + pExpr->x.pList = 0; + if( pSel ){ + if( pRet ){ + pSel->op = TK_ALL; + pSel->pPrior = pRet; + } + pRet = pSel; + } + } + + if( pRet && pRet->pPrior ){ + pRet->selFlags |= SF_MultiValue; + } + sqlite3ExprListDelete(pParse->db, pEList); + return pRet; +} /* ** Join two expressions using an AND operator. If either expression is @@ -101625,7 +104709,7 @@ SQLITE_PRIVATE Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){ SQLITE_PRIVATE Expr *sqlite3ExprFunction( Parse *pParse, /* Parsing context */ ExprList *pList, /* Argument list */ - Token *pToken, /* Name of the function */ + const Token *pToken, /* Name of the function */ int eDistinct /* SF_Distinct or SF_ALL or 0 */ ){ Expr *pNew; @@ -101636,12 +104720,17 @@ SQLITE_PRIVATE Expr *sqlite3ExprFunction( sqlite3ExprListDelete(db, pList); /* Avoid memory leak when malloc fails */ return 0; } - if( pList && pList->nExpr > pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] ){ + assert( !ExprHasProperty(pNew, EP_InnerON|EP_OuterON) ); + pNew->w.iOfst = (int)(pToken->z - pParse->zTail); + if( pList + && pList->nExpr > pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] + && !pParse->nested + ){ sqlite3ErrorMsg(pParse, "too many arguments on function %T", pToken); } pNew->x.pList = pList; ExprSetProperty(pNew, EP_HasFunc); - assert( !ExprHasProperty(pNew, EP_xIsSelect) ); + assert( ExprUseXList(pNew) ); sqlite3ExprSetHeightAndFlags(pParse, pNew); if( eDistinct==SF_Distinct ) ExprSetProperty(pNew, EP_Distinct); return pNew; @@ -101660,8 +104749,8 @@ SQLITE_PRIVATE Expr *sqlite3ExprFunction( */ SQLITE_PRIVATE void sqlite3ExprFunctionUsable( Parse *pParse, /* Parsing and code generating context */ - Expr *pExpr, /* The function invocation */ - FuncDef *pDef /* The function being invoked */ + const Expr *pExpr, /* The function invocation */ + const FuncDef *pDef /* The function being invoked */ ){ assert( !IN_RENAME_OBJECT ); assert( (pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE))!=0 ); @@ -101676,7 +104765,7 @@ SQLITE_PRIVATE void sqlite3ExprFunctionUsable( ** SQLITE_DBCONFIG_TRUSTED_SCHEMA is off (meaning ** that the schema is possibly tainted). */ - sqlite3ErrorMsg(pParse, "unsafe use of %s()", pDef->zName); + sqlite3ErrorMsg(pParse, "unsafe use of %#T()", pExpr); } } } @@ -101732,6 +104821,7 @@ SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n if( bOk==0 || i<1 || i>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){ sqlite3ErrorMsg(pParse, "variable number must be between ?1 and ?%d", db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]); + sqlite3RecordErrorOffsetOfExpr(pParse->db, pExpr); return; } x = (ynVar)i; @@ -101759,6 +104849,7 @@ SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n pExpr->iColumn = x; if( x>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){ sqlite3ErrorMsg(pParse, "too many SQL variables"); + sqlite3RecordErrorOffsetOfExpr(pParse->db, pExpr); } } @@ -101767,27 +104858,26 @@ SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n */ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ assert( p!=0 ); - /* Sanity check: Assert that the IntValue is non-negative if it exists */ - assert( !ExprHasProperty(p, EP_IntValue) || p->u.iValue>=0 ); - - assert( !ExprHasProperty(p, EP_WinFunc) || p->y.pWin!=0 || db->mallocFailed ); - assert( p->op!=TK_FUNCTION || ExprHasProperty(p, EP_TokenOnly|EP_Reduced) - || p->y.pWin==0 || ExprHasProperty(p, EP_WinFunc) ); + assert( !ExprUseUValue(p) || p->u.iValue>=0 ); + assert( !ExprUseYWin(p) || !ExprUseYSub(p) ); + assert( !ExprUseYWin(p) || p->y.pWin!=0 || db->mallocFailed ); + assert( p->op!=TK_FUNCTION || !ExprUseYSub(p) ); #ifdef SQLITE_DEBUG if( ExprHasProperty(p, EP_Leaf) && !ExprHasProperty(p, EP_TokenOnly) ){ assert( p->pLeft==0 ); assert( p->pRight==0 ); - assert( p->x.pSelect==0 ); + assert( !ExprUseXSelect(p) || p->x.pSelect==0 ); + assert( !ExprUseXList(p) || p->x.pList==0 ); } #endif if( !ExprHasProperty(p, (EP_TokenOnly|EP_Leaf)) ){ /* The Expr.x union is never used at the same time as Expr.pRight */ - assert( p->x.pList==0 || p->pRight==0 ); + assert( (ExprUseXList(p) && p->x.pList==0) || p->pRight==0 ); if( p->pLeft && p->op!=TK_SELECT_COLUMN ) sqlite3ExprDeleteNN(db, p->pLeft); if( p->pRight ){ assert( !ExprHasProperty(p, EP_WinFunc) ); sqlite3ExprDeleteNN(db, p->pRight); - }else if( ExprHasProperty(p, EP_xIsSelect) ){ + }else if( ExprUseXSelect(p) ){ assert( !ExprHasProperty(p, EP_WinFunc) ); sqlite3SelectDelete(db, p->x.pSelect); }else{ @@ -101799,7 +104889,10 @@ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ #endif } } - if( ExprHasProperty(p, EP_MemToken) ) sqlite3DbFree(db, p->u.zToken); + if( ExprHasProperty(p, EP_MemToken) ){ + assert( !ExprHasProperty(p, EP_IntValue) ); + sqlite3DbFree(db, p->u.zToken); + } if( !ExprHasProperty(p, EP_Static) ){ sqlite3DbFreeNN(db, p); } @@ -101808,6 +104901,18 @@ SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3 *db, Expr *p){ if( p ) sqlite3ExprDeleteNN(db, p); } +/* +** Clear both elements of an OnOrUsing object +*/ +SQLITE_PRIVATE void sqlite3ClearOnOrUsing(sqlite3 *db, OnOrUsing *p){ + if( p==0 ){ + /* Nothing to clear */ + }else if( p->pOn ){ + sqlite3ExprDeleteNN(db, p->pOn); + }else if( p->pUsing ){ + sqlite3IdListDelete(db, p->pUsing); + } +} /* ** Arrange to cause pExpr to be deleted when the pParse is deleted. @@ -101841,7 +104946,7 @@ SQLITE_PRIVATE void sqlite3ExprUnmapAndDelete(Parse *pParse, Expr *p){ ** passed as the first argument. This is always one of EXPR_FULLSIZE, ** EXPR_REDUCEDSIZE or EXPR_TOKENONLYSIZE. */ -static int exprStructSize(Expr *p){ +static int exprStructSize(const Expr *p){ if( ExprHasProperty(p, EP_TokenOnly) ) return EXPR_TOKENONLYSIZE; if( ExprHasProperty(p, EP_Reduced) ) return EXPR_REDUCEDSIZE; return EXPR_FULLSIZE; @@ -101881,7 +104986,7 @@ static int exprStructSize(Expr *p){ ** of dupedExprStructSize() contain multiple assert() statements that attempt ** to enforce this constraint. */ -static int dupedExprStructSize(Expr *p, int flags){ +static int dupedExprStructSize(const Expr *p, int flags){ int nSize; assert( flags==EXPRDUP_REDUCE || flags==0 ); /* Only one flag value allowed */ assert( EXPR_FULLSIZE<=0xfff ); @@ -101894,7 +104999,7 @@ static int dupedExprStructSize(Expr *p, int flags){ nSize = EXPR_FULLSIZE; }else{ assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); - assert( !ExprHasProperty(p, EP_FromJoin) ); + assert( !ExprHasProperty(p, EP_OuterON) ); assert( !ExprHasProperty(p, EP_MemToken) ); assert( !ExprHasVVAProperty(p, EP_NoReduce) ); if( p->pLeft || p->x.pList ){ @@ -101912,7 +105017,7 @@ static int dupedExprStructSize(Expr *p, int flags){ ** of the Expr structure and a copy of the Expr.u.zToken string (if that ** string is defined.) */ -static int dupedExprNodeSize(Expr *p, int flags){ +static int dupedExprNodeSize(const Expr *p, int flags){ int nByte = dupedExprStructSize(p, flags) & 0xfff; if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){ nByte += sqlite3Strlen30NN(p->u.zToken)+1; @@ -101933,7 +105038,7 @@ static int dupedExprNodeSize(Expr *p, int flags){ ** and Expr.pRight variables (but not for any structures pointed to or ** descended from the Expr.x.pList or Expr.x.pSelect variables). */ -static int dupedExprSize(Expr *p, int flags){ +static int dupedExprSize(const Expr *p, int flags){ int nByte = 0; if( p ){ nByte = dupedExprNodeSize(p, flags); @@ -101952,7 +105057,7 @@ static int dupedExprSize(Expr *p, int flags){ ** if any. Before returning, *pzBuffer is set to the first byte past the ** portion of the buffer copied into by this function. */ -static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ +static Expr *exprDup(sqlite3 *db, const Expr *p, int dupFlags, u8 **pzBuffer){ Expr *pNew; /* Value to return */ u8 *zAlloc; /* Memory space from which to build Expr object */ u32 staticFlag; /* EP_Static if space not obtained from malloc */ @@ -101966,6 +105071,7 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ if( pzBuffer ){ zAlloc = *pzBuffer; staticFlag = EP_Static; + assert( zAlloc!=0 ); }else{ zAlloc = sqlite3DbMallocRawNN(db, dupedExprSize(p, dupFlags)); staticFlag = 0; @@ -102014,7 +105120,7 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ if( 0==((p->flags|pNew->flags) & (EP_TokenOnly|EP_Leaf)) ){ /* Fill in the pNew->x.pSelect or pNew->x.pList member. */ - if( ExprHasProperty(p, EP_xIsSelect) ){ + if( ExprUseXSelect(p) ){ pNew->x.pSelect = sqlite3SelectDup(db, p->x.pSelect, dupFlags); }else{ pNew->x.pList = sqlite3ExprListDup(db, p->x.pList, dupFlags); @@ -102043,8 +105149,8 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ if( !ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){ if( pNew->op==TK_SELECT_COLUMN ){ pNew->pLeft = p->pLeft; - assert( p->iColumn==0 || p->pRight==0 ); - assert( p->pRight==0 || p->pRight==p->pLeft ); + assert( p->pRight==0 || p->pRight==p->pLeft + || ExprHasProperty(p->pLeft, EP_Subquery) ); }else{ pNew->pLeft = sqlite3ExprDup(db, p->pLeft, 0); } @@ -102061,7 +105167,7 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ ** and the db->mallocFailed flag set. */ #ifndef SQLITE_OMIT_CTE -static With *withDup(sqlite3 *db, With *p){ +SQLITE_PRIVATE With *sqlite3WithDup(sqlite3 *db, With *p){ With *pRet = 0; if( p ){ sqlite3_int64 nByte = sizeof(*p) + sizeof(p->a[0]) * (p->nCte-1); @@ -102073,13 +105179,14 @@ static With *withDup(sqlite3 *db, With *p){ pRet->a[i].pSelect = sqlite3SelectDup(db, p->a[i].pSelect, 0); pRet->a[i].pCols = sqlite3ExprListDup(db, p->a[i].pCols, 0); pRet->a[i].zName = sqlite3DbStrDup(db, p->a[i].zName); + pRet->a[i].eM10d = p->a[i].eM10d; } } } return pRet; } #else -# define withDup(x,y) 0 +# define sqlite3WithDup(x,y) 0 #endif #ifndef SQLITE_OMIT_WINDOWFUNC @@ -102132,20 +105239,23 @@ static void gatherSelectWindows(Select *p){ ** truncated version of the usual Expr structure that will be stored as ** part of the in-memory representation of the database schema. */ -SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3 *db, Expr *p, int flags){ +SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3 *db, const Expr *p, int flags){ assert( flags==0 || flags==EXPRDUP_REDUCE ); return p ? exprDup(db, p, flags, 0) : 0; } -SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags){ +SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, const ExprList *p, int flags){ ExprList *pNew; - struct ExprList_item *pItem, *pOldItem; + struct ExprList_item *pItem; + const struct ExprList_item *pOldItem; int i; - Expr *pPriorSelectCol = 0; + Expr *pPriorSelectColOld = 0; + Expr *pPriorSelectColNew = 0; assert( db!=0 ); if( p==0 ) return 0; pNew = sqlite3DbMallocRawNN(db, sqlite3DbMallocSize(db, p)); if( pNew==0 ) return 0; pNew->nExpr = p->nExpr; + pNew->nAlloc = p->nAlloc; pItem = pNew->a; pOldItem = p->a; for(i=0; inExpr; i++, pItem++, pOldItem++){ @@ -102156,24 +105266,22 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags) && pOldExpr->op==TK_SELECT_COLUMN && (pNewExpr = pItem->pExpr)!=0 ){ - assert( pNewExpr->iColumn==0 || i>0 ); - if( pNewExpr->iColumn==0 ){ - assert( pOldExpr->pLeft==pOldExpr->pRight ); - pPriorSelectCol = pNewExpr->pLeft = pNewExpr->pRight; + if( pNewExpr->pRight ){ + pPriorSelectColOld = pOldExpr->pRight; + pPriorSelectColNew = pNewExpr->pRight; + pNewExpr->pLeft = pNewExpr->pRight; }else{ - assert( i>0 ); - assert( pItem[-1].pExpr!=0 ); - assert( pNewExpr->iColumn==pItem[-1].pExpr->iColumn+1 ); - assert( pPriorSelectCol==pItem[-1].pExpr->pLeft ); - pNewExpr->pLeft = pPriorSelectCol; + if( pOldExpr->pLeft!=pPriorSelectColOld ){ + pPriorSelectColOld = pOldExpr->pLeft; + pPriorSelectColNew = sqlite3ExprDup(db, pPriorSelectColOld, flags); + pNewExpr->pRight = pPriorSelectColNew; + } + pNewExpr->pLeft = pPriorSelectColNew; } } pItem->zEName = sqlite3DbStrDup(db, pOldItem->zEName); - pItem->sortFlags = pOldItem->sortFlags; - pItem->eEName = pOldItem->eEName; - pItem->done = 0; - pItem->bNulls = pOldItem->bNulls; - pItem->bSorterRef = pOldItem->bSorterRef; + pItem->fg = pOldItem->fg; + pItem->fg.done = 0; pItem->u = pOldItem->u; } return pNew; @@ -102187,7 +105295,7 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags) */ #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) \ || !defined(SQLITE_OMIT_SUBQUERY) -SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){ +SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, const SrcList *p, int flags){ SrcList *pNew; int i; int nByte; @@ -102199,7 +105307,7 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){ pNew->nSrc = pNew->nAlloc = p->nSrc; for(i=0; inSrc; i++){ SrcItem *pNewItem = &pNew->a[i]; - SrcItem *pOldItem = &p->a[i]; + const SrcItem *pOldItem = &p->a[i]; Table *pTab; pNewItem->pSchema = pOldItem->pSchema; pNewItem->zDatabase = sqlite3DbStrDup(db, pOldItem->zDatabase); @@ -102225,41 +105333,39 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){ pTab->nTabRef++; } pNewItem->pSelect = sqlite3SelectDup(db, pOldItem->pSelect, flags); - pNewItem->pOn = sqlite3ExprDup(db, pOldItem->pOn, flags); - pNewItem->pUsing = sqlite3IdListDup(db, pOldItem->pUsing); + if( pOldItem->fg.isUsing ){ + assert( pNewItem->fg.isUsing ); + pNewItem->u3.pUsing = sqlite3IdListDup(db, pOldItem->u3.pUsing); + }else{ + pNewItem->u3.pOn = sqlite3ExprDup(db, pOldItem->u3.pOn, flags); + } pNewItem->colUsed = pOldItem->colUsed; } return pNew; } -SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, IdList *p){ +SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, const IdList *p){ IdList *pNew; int i; assert( db!=0 ); if( p==0 ) return 0; - pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) ); + assert( p->eU4!=EU4_EXPR ); + pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew)+(p->nId-1)*sizeof(p->a[0]) ); if( pNew==0 ) return 0; pNew->nId = p->nId; - pNew->a = sqlite3DbMallocRawNN(db, p->nId*sizeof(p->a[0]) ); - if( pNew->a==0 ){ - sqlite3DbFreeNN(db, pNew); - return 0; - } - /* Note that because the size of the allocation for p->a[] is not - ** necessarily a power of two, sqlite3IdListAppend() may not be called - ** on the duplicate created by this function. */ + pNew->eU4 = p->eU4; for(i=0; inId; i++){ struct IdList_item *pNewItem = &pNew->a[i]; - struct IdList_item *pOldItem = &p->a[i]; + const struct IdList_item *pOldItem = &p->a[i]; pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName); - pNewItem->idx = pOldItem->idx; + pNewItem->u4 = pOldItem->u4; } return pNew; } -SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *pDup, int flags){ +SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, const Select *pDup, int flags){ Select *pRet = 0; Select *pNext = 0; Select **pp = &pRet; - Select *p; + const Select *p; assert( db!=0 ); for(p=pDup; p; p=p->pPrior){ @@ -102281,13 +105387,21 @@ SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *pDup, int flags){ pNew->addrOpenEphm[0] = -1; pNew->addrOpenEphm[1] = -1; pNew->nSelectRow = p->nSelectRow; - pNew->pWith = withDup(db, p->pWith); + pNew->pWith = sqlite3WithDup(db, p->pWith); #ifndef SQLITE_OMIT_WINDOWFUNC pNew->pWin = 0; pNew->pWinDefn = sqlite3WindowListDup(db, p->pWinDefn); if( p->pWin && db->mallocFailed==0 ) gatherSelectWindows(pNew); #endif pNew->selId = p->selId; + if( db->mallocFailed ){ + /* Any prior OOM might have left the Select object incomplete. + ** Delete the whole thing rather than allow an incomplete Select + ** to be used by the code generator. */ + pNew->pNext = 0; + sqlite3SelectDelete(db, pNew); + break; + } *pp = pNew; pp = &pNew->pPrior; pNext = pNew; @@ -102296,7 +105410,7 @@ SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *pDup, int flags){ return pRet; } #else -SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *p, int flags){ +SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, const Select *p, int flags){ assert( p==0 ); return 0; } @@ -102318,41 +105432,64 @@ SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *p, int flags){ ** NULL is returned. If non-NULL is returned, then it is guaranteed ** that the new entry was successfully appended. */ +static const struct ExprList_item zeroItem = {0}; +SQLITE_PRIVATE SQLITE_NOINLINE ExprList *sqlite3ExprListAppendNew( + sqlite3 *db, /* Database handle. Used for memory allocation */ + Expr *pExpr /* Expression to be appended. Might be NULL */ +){ + struct ExprList_item *pItem; + ExprList *pList; + + pList = sqlite3DbMallocRawNN(db, sizeof(ExprList)+sizeof(pList->a[0])*4 ); + if( pList==0 ){ + sqlite3ExprDelete(db, pExpr); + return 0; + } + pList->nAlloc = 4; + pList->nExpr = 1; + pItem = &pList->a[0]; + *pItem = zeroItem; + pItem->pExpr = pExpr; + return pList; +} +SQLITE_PRIVATE SQLITE_NOINLINE ExprList *sqlite3ExprListAppendGrow( + sqlite3 *db, /* Database handle. Used for memory allocation */ + ExprList *pList, /* List to which to append. Might be NULL */ + Expr *pExpr /* Expression to be appended. Might be NULL */ +){ + struct ExprList_item *pItem; + ExprList *pNew; + pList->nAlloc *= 2; + pNew = sqlite3DbRealloc(db, pList, + sizeof(*pList)+(pList->nAlloc-1)*sizeof(pList->a[0])); + if( pNew==0 ){ + sqlite3ExprListDelete(db, pList); + sqlite3ExprDelete(db, pExpr); + return 0; + }else{ + pList = pNew; + } + pItem = &pList->a[pList->nExpr++]; + *pItem = zeroItem; + pItem->pExpr = pExpr; + return pList; +} SQLITE_PRIVATE ExprList *sqlite3ExprListAppend( Parse *pParse, /* Parsing context */ ExprList *pList, /* List to which to append. Might be NULL */ Expr *pExpr /* Expression to be appended. Might be NULL */ ){ struct ExprList_item *pItem; - sqlite3 *db = pParse->db; - assert( db!=0 ); if( pList==0 ){ - pList = sqlite3DbMallocRawNN(db, sizeof(ExprList) ); - if( pList==0 ){ - goto no_mem; - } - pList->nExpr = 0; - }else if( (pList->nExpr & (pList->nExpr-1))==0 ){ - ExprList *pNew; - pNew = sqlite3DbRealloc(db, pList, - sizeof(*pList)+(2*(sqlite3_int64)pList->nExpr-1)*sizeof(pList->a[0])); - if( pNew==0 ){ - goto no_mem; - } - pList = pNew; + return sqlite3ExprListAppendNew(pParse->db,pExpr); + } + if( pList->nAllocnExpr+1 ){ + return sqlite3ExprListAppendGrow(pParse->db,pList,pExpr); } pItem = &pList->a[pList->nExpr++]; - assert( offsetof(struct ExprList_item,zEName)==sizeof(pItem->pExpr) ); - assert( offsetof(struct ExprList_item,pExpr)==0 ); - memset(&pItem->zEName,0,sizeof(*pItem)-offsetof(struct ExprList_item,zEName)); + *pItem = zeroItem; pItem->pExpr = pExpr; return pList; - -no_mem: - /* Avoid leaking memory if malloc has failed. */ - sqlite3ExprDelete(db, pExpr); - sqlite3ExprListDelete(db, pList); - return 0; } /* @@ -102393,11 +105530,9 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector( } for(i=0; inId; i++){ - Expr *pSubExpr = sqlite3ExprForVectorField(pParse, pExpr, i); + Expr *pSubExpr = sqlite3ExprForVectorField(pParse, pExpr, i, pColumns->nId); assert( pSubExpr!=0 || db->mallocFailed ); - assert( pSubExpr==0 || pSubExpr->iTable==0 ); if( pSubExpr==0 ) continue; - pSubExpr->iTable = pColumns->nId; pList = sqlite3ExprListAppend(pParse, pList, pSubExpr); if( pList ){ assert( pList->nExpr==iFirst+i+1 ); @@ -102446,16 +105581,16 @@ SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList *p, int iSortOrder, int ); pItem = &p->a[p->nExpr-1]; - assert( pItem->bNulls==0 ); + assert( pItem->fg.bNulls==0 ); if( iSortOrder==SQLITE_SO_UNDEFINED ){ iSortOrder = SQLITE_SO_ASC; } - pItem->sortFlags = (u8)iSortOrder; + pItem->fg.sortFlags = (u8)iSortOrder; if( eNulls!=SQLITE_SO_UNDEFINED ){ - pItem->bNulls = 1; + pItem->fg.bNulls = 1; if( iSortOrder!=eNulls ){ - pItem->sortFlags |= KEYINFO_ORDER_BIGNULL; + pItem->fg.sortFlags |= KEYINFO_ORDER_BIGNULL; } } } @@ -102471,7 +105606,7 @@ SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList *p, int iSortOrder, int SQLITE_PRIVATE void sqlite3ExprListSetName( Parse *pParse, /* Parsing context */ ExprList *pList, /* List to which to add the span. */ - Token *pName, /* Name to be added */ + const Token *pName, /* Name to be added */ int dequote /* True to cause the name to be dequoted */ ){ assert( pList!=0 || pParse->db->mallocFailed!=0 ); @@ -102481,7 +105616,7 @@ SQLITE_PRIVATE void sqlite3ExprListSetName( assert( pList->nExpr>0 ); pItem = &pList->a[pList->nExpr-1]; assert( pItem->zEName==0 ); - assert( pItem->eEName==ENAME_NAME ); + assert( pItem->fg.eEName==ENAME_NAME ); pItem->zEName = sqlite3DbStrNDup(pParse->db, pName->z, pName->n); if( dequote ){ /* If dequote==0, then pName->z does not point to part of a DDL @@ -102489,7 +105624,7 @@ SQLITE_PRIVATE void sqlite3ExprListSetName( ** to the token-map. */ sqlite3Dequote(pItem->zEName); if( IN_RENAME_OBJECT ){ - sqlite3RenameTokenMap(pParse, (void*)pItem->zEName, pName); + sqlite3RenameTokenMap(pParse, (const void*)pItem->zEName, pName); } } } @@ -102516,7 +105651,7 @@ SQLITE_PRIVATE void sqlite3ExprListSetSpan( assert( pList->nExpr>0 ); if( pItem->zEName==0 ){ pItem->zEName = sqlite3DbSpanDup(db, zStart, zEnd); - pItem->eEName = ENAME_SPAN; + pItem->fg.eEName = ENAME_SPAN; } } } @@ -102608,7 +105743,7 @@ SQLITE_PRIVATE u32 sqlite3IsTrueOrFalse(const char *zIn){ SQLITE_PRIVATE int sqlite3ExprIdToTrueFalse(Expr *pExpr){ u32 v; assert( pExpr->op==TK_ID || pExpr->op==TK_STRING ); - if( !ExprHasProperty(pExpr, EP_Quoted) + if( !ExprHasProperty(pExpr, EP_Quoted|EP_IntValue) && (v = sqlite3IsTrueOrFalse(pExpr->u.zToken))!=0 ){ pExpr->op = TK_TRUEFALSE; @@ -102625,6 +105760,7 @@ SQLITE_PRIVATE int sqlite3ExprIdToTrueFalse(Expr *pExpr){ SQLITE_PRIVATE int sqlite3ExprTruthValue(const Expr *pExpr){ pExpr = sqlite3ExprSkipCollate((Expr*)pExpr); assert( pExpr->op==TK_TRUEFALSE ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); assert( sqlite3StrICmp(pExpr->u.zToken,"true")==0 || sqlite3StrICmp(pExpr->u.zToken,"false")==0 ); return pExpr->u.zToken[4]==0; @@ -102687,9 +105823,9 @@ SQLITE_PRIVATE Expr *sqlite3ExprSimplifiedAndOr(Expr *pExpr){ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ /* If pWalker->eCode is 2 then any term of the expression that comes from - ** the ON or USING clauses of a left join disqualifies the expression + ** the ON or USING clauses of an outer join disqualifies the expression ** from being considered constant. */ - if( pWalker->eCode==2 && ExprHasProperty(pExpr, EP_FromJoin) ){ + if( pWalker->eCode==2 && ExprHasProperty(pExpr, EP_OuterON) ){ pWalker->eCode = 0; return WRC_Abort; } @@ -102808,6 +105944,42 @@ SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr *p, int iCur){ return exprIsConst(p, 3, iCur); } +/* +** Check pExpr to see if it is an invariant constraint on data source pSrc. +** This is an optimization. False negatives will perhaps cause slower +** queries, but false positives will yield incorrect answers. So when in +** doubt, return 0. +** +** To be an invariant constraint, the following must be true: +** +** (1) pExpr cannot refer to any table other than pSrc->iCursor. +** +** (2) pExpr cannot use subqueries or non-deterministic functions. +** +** (3) pSrc cannot be part of the left operand for a RIGHT JOIN. +** (Is there some way to relax this constraint?) +** +** (4) If pSrc is the right operand of a LEFT JOIN, then... +** (4a) pExpr must come from an ON clause.. + (4b) and specifically the ON clause associated with the LEFT JOIN. +** +** (5) If pSrc is not the right operand of a LEFT JOIN or the left +** operand of a RIGHT JOIN, then pExpr must be from the WHERE +** clause, not an ON clause. +*/ +SQLITE_PRIVATE int sqlite3ExprIsTableConstraint(Expr *pExpr, const SrcItem *pSrc){ + if( pSrc->fg.jointype & JT_LTORJ ){ + return 0; /* rule (3) */ + } + if( pSrc->fg.jointype & JT_LEFT ){ + if( !ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* rule (4a) */ + if( pExpr->w.iJoin!=pSrc->iCursor ) return 0; /* rule (4b) */ + }else{ + if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* rule (5) */ + } + return sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor); /* rules (1), (2) */ +} + /* ** sqlite3WalkExpr() callback used by sqlite3ExprIsConstantOrGroupBy(). @@ -102829,7 +106001,7 @@ static int exprNodeIsConstantOrGroupBy(Walker *pWalker, Expr *pExpr){ } /* Check if pExpr is a sub-select. If so, consider it variable. */ - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ pWalker->eCode = 0; return WRC_Abort; } @@ -102917,7 +106089,7 @@ SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr *p){ ** in *pValue. If the expression is not an integer or if it is too big ** to fit in a signed 32-bit integer, return 0 and leave *pValue unchanged. */ -SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr *p, int *pValue){ +SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr *p, int *pValue){ int rc = 0; if( NEVER(p==0) ) return 0; /* Used to only happen following on OOM */ @@ -102936,9 +106108,9 @@ SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr *p, int *pValue){ break; } case TK_UMINUS: { - int v; + int v = 0; if( sqlite3ExprIsInteger(p->pLeft, &v) ){ - assert( v!=(-2147483647-1) ); + assert( ((unsigned int)v)!=0x80000000 ); *pValue = -v; rc = 1; } @@ -102965,8 +106137,10 @@ SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr *p, int *pValue){ */ SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr *p){ u8 op; + assert( p!=0 ); while( p->op==TK_UPLUS || p->op==TK_UMINUS ){ p = p->pLeft; + assert( p!=0 ); } op = p->op; if( op==TK_REGISTER ) op = p->op2; @@ -102977,10 +106151,11 @@ SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr *p){ case TK_BLOB: return 0; case TK_COLUMN: + assert( ExprUseYTab(p) ); return ExprHasProperty(p, EP_CanBeNull) || p->y.pTab==0 || /* Reference to column of index on expression */ (p->iColumn>=0 - && ALWAYS(p->y.pTab->aCol!=0) /* Defense against OOM problems */ + && p->y.pTab->aCol!=0 /* Possible due to prior error */ && p->y.pTab->aCol[p->iColumn].notNull==0); default: return 1; @@ -103048,13 +106223,13 @@ SQLITE_PRIVATE int sqlite3IsRowid(const char *z){ ** table, then return NULL. */ #ifndef SQLITE_OMIT_SUBQUERY -static Select *isCandidateForInOpt(Expr *pX){ +static Select *isCandidateForInOpt(const Expr *pX){ Select *p; SrcList *pSrc; ExprList *pEList; Table *pTab; int i; - if( !ExprHasProperty(pX, EP_xIsSelect) ) return 0; /* Not a subquery */ + if( !ExprUseXSelect(pX) ) return 0; /* Not a subquery */ if( ExprHasProperty(pX, EP_VarSelect) ) return 0; /* Correlated subq */ p = pX->x.pSelect; if( p->pPrior ) return 0; /* Not a compound SELECT */ @@ -103072,7 +106247,7 @@ static Select *isCandidateForInOpt(Expr *pX){ if( pSrc->a[0].pSelect ) return 0; /* FROM is not a subquery or view */ pTab = pSrc->a[0].pTab; assert( pTab!=0 ); - assert( pTab->pSelect==0 ); /* FROM clause is not a view */ + assert( !IsView(pTab) ); /* FROM clause is not a view */ if( IsVirtual(pTab) ) return 0; /* FROM clause not a virtual table */ pEList = p->pEList; assert( pEList!=0 ); @@ -103132,7 +106307,7 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** all members of the RHS set, skipping duplicates. ** ** A cursor is opened on the b-tree object that is the RHS of the IN operator -** and pX->iTable is set to the index of that cursor. +** and the *piTab parameter is set to the index of that cursor. ** ** The returned value of this function indicates the b-tree type, as follows: ** @@ -103152,7 +106327,10 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** If the RHS of the IN operator is a list or a more complex subquery, then ** an ephemeral table might need to be generated from the RHS and then ** pX->iTable made to point to the ephemeral table instead of an -** existing table. +** existing table. In this case, the creation and initialization of the +** ephmeral table might be put inside of a subroutine, the EP_Subrtn flag +** will be set on pX and the pX->y.sub fields will be set to show where +** the subroutine is coded. ** ** The inFlags parameter must contain, at a minimum, one of the bits ** IN_INDEX_MEMBERSHIP or IN_INDEX_LOOP but not both. If inFlags contains @@ -103213,19 +106391,20 @@ SQLITE_PRIVATE int sqlite3FindInIndex( ){ Select *p; /* SELECT to the right of IN operator */ int eType = 0; /* Type of RHS table. IN_INDEX_* */ - int iTab = pParse->nTab++; /* Cursor of the RHS table */ + int iTab; /* Cursor of the RHS table */ int mustBeUnique; /* True if RHS must be unique */ Vdbe *v = sqlite3GetVdbe(pParse); /* Virtual machine being coded */ assert( pX->op==TK_IN ); mustBeUnique = (inFlags & IN_INDEX_LOOP)!=0; + iTab = pParse->nTab++; /* If the RHS of this IN(...) operator is a SELECT, and if it matters ** whether or not the SELECT result contains NULL values, check whether ** or not NULL is actually possible (it may not be, for example, due ** to NOT NULL constraints in the schema). If no NULL values are possible, ** set prRhsHasNull to 0 before continuing. */ - if( prRhsHasNull && (pX->flags & EP_xIsSelect) ){ + if( prRhsHasNull && ExprUseXSelect(pX) ){ int i; ExprList *pEList = pX->x.pSelect->pEList; for(i=0; inExpr; i++){ @@ -103381,9 +106560,11 @@ SQLITE_PRIVATE int sqlite3FindInIndex( */ if( eType==0 && (inFlags & IN_INDEX_NOOP_OK) - && !ExprHasProperty(pX, EP_xIsSelect) + && ExprUseXList(pX) && (!sqlite3InRhsIsConstant(pX) || pX->x.pList->nExpr<=2) ){ + pParse->nTab--; /* Back out the allocation of the unused cursor */ + iTab = -1; /* Cursor is not allocated */ eType = IN_INDEX_NOOP; } @@ -103426,10 +106607,10 @@ SQLITE_PRIVATE int sqlite3FindInIndex( ** It is the responsibility of the caller to ensure that the returned ** string is eventually freed using sqlite3DbFree(). */ -static char *exprINAffinity(Parse *pParse, Expr *pExpr){ +static char *exprINAffinity(Parse *pParse, const Expr *pExpr){ Expr *pLeft = pExpr->pLeft; int nVal = sqlite3ExprVectorSize(pLeft); - Select *pSelect = (pExpr->flags & EP_xIsSelect) ? pExpr->x.pSelect : 0; + Select *pSelect = ExprUseXSelect(pExpr) ? pExpr->x.pSelect : 0; char *zRet; assert( pExpr->op==TK_IN ); @@ -103479,7 +106660,7 @@ SQLITE_PRIVATE void sqlite3SubselectError(Parse *pParse, int nActual, int nExpec */ SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse *pParse, Expr *pExpr){ #ifndef SQLITE_OMIT_SUBQUERY - if( pExpr->flags & EP_xIsSelect ){ + if( ExprUseXSelect(pExpr) ){ sqlite3SubselectError(pParse, pExpr->x.pSelect->pEList->nExpr, 1); }else #endif @@ -103543,24 +106724,26 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( */ if( ExprHasProperty(pExpr, EP_Subrtn) ){ addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ ExplainQueryPlan((pParse, 0, "REUSE LIST SUBQUERY %d", pExpr->x.pSelect->selId)); } + assert( ExprUseYSub(pExpr) ); sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn, pExpr->y.sub.iAddr); + assert( iTab!=pExpr->iTable ); sqlite3VdbeAddOp2(v, OP_OpenDup, iTab, pExpr->iTable); sqlite3VdbeJumpHere(v, addrOnce); return; } /* Begin coding the subroutine */ + assert( !ExprUseYWin(pExpr) ); ExprSetProperty(pExpr, EP_Subrtn); assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); pExpr->y.sub.regReturn = ++pParse->nMem; pExpr->y.sub.iAddr = - sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1; - VdbeComment((v, "return address")); + sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pExpr->y.sub.regReturn) + 1; addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } @@ -103575,7 +106758,7 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( pExpr->iTable = iTab; addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pExpr->iTable, nVal); #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ VdbeComment((v, "Result of SELECT %u", pExpr->x.pSelect->selId)); }else{ VdbeComment((v, "RHS of IN operator")); @@ -103583,7 +106766,7 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( #endif pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nVal, 1); - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ /* Case 1: expr IN (SELECT ...) ** ** Generate code to write the results of the select into the temporary @@ -103598,19 +106781,23 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( /* If the LHS and RHS of the IN operator do not match, that ** error will have been caught long before we reach this point. */ if( ALWAYS(pEList->nExpr==nVal) ){ + Select *pCopy; SelectDest dest; int i; + int rc; sqlite3SelectDestInit(&dest, SRT_Set, iTab); dest.zAffSdst = exprINAffinity(pParse, pExpr); pSelect->iLimit = 0; testcase( pSelect->selFlags & SF_Distinct ); testcase( pKeyInfo==0 ); /* Caused by OOM in sqlite3KeyInfoAlloc() */ - if( sqlite3Select(pParse, pSelect, &dest) ){ - sqlite3DbFree(pParse->db, dest.zAffSdst); + pCopy = sqlite3SelectDup(pParse->db, pSelect, 0); + rc = pParse->db->mallocFailed ? 1 :sqlite3Select(pParse, pCopy, &dest); + sqlite3SelectDelete(pParse->db, pCopy); + sqlite3DbFree(pParse->db, dest.zAffSdst); + if( rc ){ sqlite3KeyInfoUnref(pKeyInfo); return; } - sqlite3DbFree(pParse->db, dest.zAffSdst); assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */ assert( pEList!=0 ); assert( pEList->nExpr>0 ); @@ -103658,6 +106845,7 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( ** expression we need to rerun this code each time. */ if( addrOnce && !sqlite3ExprIsConstant(pE2) ){ + sqlite3VdbeChangeToNoop(v, addrOnce-1); sqlite3VdbeChangeToNoop(v, addrOnce); ExprClearProperty(pExpr, EP_Subrtn); addrOnce = 0; @@ -103677,8 +106865,12 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( if( addrOnce ){ sqlite3VdbeJumpHere(v, addrOnce); /* Subroutine return */ - sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn); - sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1); + assert( ExprUseYSub(pExpr) ); + assert( sqlite3VdbeGetOp(v,pExpr->y.sub.iAddr-1)->opcode==OP_BeginSubrtn + || pParse->nErr ); + sqlite3VdbeAddOp3(v, OP_Return, pExpr->y.sub.regReturn, + pExpr->y.sub.iAddr, 1); + VdbeCoverage(v); sqlite3ClearTempRegCache(pParse); } } @@ -103709,12 +106901,31 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ Vdbe *v = pParse->pVdbe; assert( v!=0 ); + if( pParse->nErr ) return 0; testcase( pExpr->op==TK_EXISTS ); testcase( pExpr->op==TK_SELECT ); assert( pExpr->op==TK_EXISTS || pExpr->op==TK_SELECT ); - assert( ExprHasProperty(pExpr, EP_xIsSelect) ); + assert( ExprUseXSelect(pExpr) ); pSel = pExpr->x.pSelect; + /* If this routine has already been coded, then invoke it as a + ** subroutine. */ + if( ExprHasProperty(pExpr, EP_Subrtn) ){ + ExplainQueryPlan((pParse, 0, "REUSE SUBQUERY %d", pSel->selId)); + assert( ExprUseYSub(pExpr) ); + sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn, + pExpr->y.sub.iAddr); + return pExpr->iTable; + } + + /* Begin coding the subroutine */ + assert( !ExprUseYWin(pExpr) ); + assert( !ExprHasProperty(pExpr, EP_Reduced|EP_TokenOnly) ); + ExprSetProperty(pExpr, EP_Subrtn); + pExpr->y.sub.regReturn = ++pParse->nMem; + pExpr->y.sub.iAddr = + sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pExpr->y.sub.regReturn) + 1; + /* The evaluation of the EXISTS/SELECT must be repeated every time it ** is encountered if any of the following is true: ** @@ -103726,22 +106937,6 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ ** save the results, and reuse the same result on subsequent invocations. */ if( !ExprHasProperty(pExpr, EP_VarSelect) ){ - /* If this routine has already been coded, then invoke it as a - ** subroutine. */ - if( ExprHasProperty(pExpr, EP_Subrtn) ){ - ExplainQueryPlan((pParse, 0, "REUSE SUBQUERY %d", pSel->selId)); - sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn, - pExpr->y.sub.iAddr); - return pExpr->iTable; - } - - /* Begin coding the subroutine */ - ExprSetProperty(pExpr, EP_Subrtn); - pExpr->y.sub.regReturn = ++pParse->nMem; - pExpr->y.sub.iAddr = - sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1; - VdbeComment((v, "return address")); - addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } @@ -103790,19 +106985,24 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ } pSel->iLimit = 0; if( sqlite3Select(pParse, pSel, &dest) ){ + pExpr->op2 = pExpr->op; + pExpr->op = TK_ERROR; return 0; } pExpr->iTable = rReg = dest.iSDParm; ExprSetVVAProperty(pExpr, EP_NoReduce); if( addrOnce ){ sqlite3VdbeJumpHere(v, addrOnce); - - /* Subroutine return */ - sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn); - sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1); - sqlite3ClearTempRegCache(pParse); } + /* Subroutine return */ + assert( ExprUseYSub(pExpr) ); + assert( sqlite3VdbeGetOp(v,pExpr->y.sub.iAddr-1)->opcode==OP_BeginSubrtn + || pParse->nErr ); + sqlite3VdbeAddOp3(v, OP_Return, pExpr->y.sub.regReturn, + pExpr->y.sub.iAddr, 1); + VdbeCoverage(v); + sqlite3ClearTempRegCache(pParse); return rReg; } #endif /* SQLITE_OMIT_SUBQUERY */ @@ -103816,7 +107016,7 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ */ SQLITE_PRIVATE int sqlite3ExprCheckIN(Parse *pParse, Expr *pIn){ int nVector = sqlite3ExprVectorSize(pIn->pLeft); - if( (pIn->flags & EP_xIsSelect) ){ + if( ExprUseXSelect(pIn) && !pParse->db->mallocFailed ){ if( nVector!=pIn->x.pSelect->pEList->nExpr ){ sqlite3SubselectError(pParse, pIn->x.pSelect->pEList->nExpr, nVector); return 1; @@ -103950,13 +107150,15 @@ static void sqlite3ExprCodeIN( ** This is step (1) in the in-operator.md optimized algorithm. */ if( eType==IN_INDEX_NOOP ){ - ExprList *pList = pExpr->x.pList; - CollSeq *pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft); + ExprList *pList; + CollSeq *pColl; int labelOk = sqlite3VdbeMakeLabel(pParse); int r2, regToFree; int regCkNull = 0; int ii; - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); + assert( ExprUseXList(pExpr) ); + pList = pExpr->x.pList; + pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft); if( destIfNull!=destIfFalse ){ regCkNull = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_BitAnd, rLhs, rLhs, regCkNull); @@ -104004,9 +107206,9 @@ static void sqlite3ExprCodeIN( }else{ destStep2 = destStep6 = sqlite3VdbeMakeLabel(pParse); } - if( pParse->nErr ) goto sqlite3ExprCodeIN_finished; for(i=0; ipLeft, i); + if( pParse->nErr ) goto sqlite3ExprCodeIN_oom_error; if( sqlite3ExprCanBeNull(p) ){ sqlite3VdbeAddOp2(v, OP_IsNull, rLhs+i, destStep2); VdbeCoverage(v); @@ -104144,11 +107346,12 @@ static void codeInteger(Parse *pParse, Expr *pExpr, int negFlag, int iMem){ c = sqlite3DecOrHexToI64(z, &value); if( (c==3 && !negFlag) || (c==2) || (negFlag && value==SMALLEST_INT64)){ #ifdef SQLITE_OMIT_FLOATING_POINT - sqlite3ErrorMsg(pParse, "oversized integer: %s%s", negFlag ? "-" : "", z); + sqlite3ErrorMsg(pParse, "oversized integer: %s%#T", negFlag?"-":"",pExpr); #else #ifndef SQLITE_OMIT_HEX_INTEGER if( sqlite3_strnicmp(z,"0x",2)==0 ){ - sqlite3ErrorMsg(pParse, "hex literal too big: %s%s", negFlag?"-":"",z); + sqlite3ErrorMsg(pParse, "hex literal too big: %s%#T", + negFlag?"-":"",pExpr); }else #endif { @@ -104192,9 +107395,10 @@ SQLITE_PRIVATE void sqlite3ExprCodeLoadIndexColumn( ** and store the result in register regOut */ SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn( - Parse *pParse, - Column *pCol, - int regOut + Parse *pParse, /* Parsing context */ + Table *pTab, /* Table containing the generated column */ + Column *pCol, /* The generated column */ + int regOut /* Put the result in this register */ ){ int iAddr; Vdbe *v = pParse->pVdbe; @@ -104205,7 +107409,7 @@ SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn( }else{ iAddr = 0; } - sqlite3ExprCodeCopy(pParse, pCol->pDflt, regOut); + sqlite3ExprCodeCopy(pParse, sqlite3ColumnExpr(pTab,pCol), regOut); if( pCol->affinity>=SQLITE_AFF_TEXT ){ sqlite3VdbeAddOp4(v, OP_Affinity, regOut, 1, 0, &pCol->affinity, 1); } @@ -104231,6 +107435,7 @@ SQLITE_PRIVATE void sqlite3ExprCodeGetColumnOfTable( } if( iCol<0 || iCol==pTab->iPKey ){ sqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut); + VdbeComment((v, "%s.rowid", pTab->zName)); }else{ int op; int x; @@ -104241,12 +107446,13 @@ SQLITE_PRIVATE void sqlite3ExprCodeGetColumnOfTable( }else if( (pCol = &pTab->aCol[iCol])->colFlags & COLFLAG_VIRTUAL ){ Parse *pParse = sqlite3VdbeParser(v); if( pCol->colFlags & COLFLAG_BUSY ){ - sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", pCol->zName); + sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", + pCol->zCnName); }else{ int savedSelfTab = pParse->iSelfTab; pCol->colFlags |= COLFLAG_BUSY; pParse->iSelfTab = iTabCur+1; - sqlite3ExprCodeGeneratedColumn(pParse, pCol, regOut); + sqlite3ExprCodeGeneratedColumn(pParse, pTab, pCol, regOut); pParse->iSelfTab = savedSelfTab; pCol->colFlags &= ~COLFLAG_BUSY; } @@ -104339,6 +107545,7 @@ static int exprCodeVector(Parse *pParse, Expr *p, int *piFreeable){ int i; iResult = pParse->nMem+1; pParse->nMem += nResult; + assert( ExprUseXList(p) ); for(i=0; ix.pList->a[i].pExpr, i+iResult); } @@ -104399,7 +107606,17 @@ static int exprCodeInlineFunction( caseExpr.x.pList = pFarg; return sqlite3ExprCodeTarget(pParse, &caseExpr, target); } - +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + case INLINEFUNC_sqlite_offset: { + Expr *pArg = pFarg->a[0].pExpr; + if( pArg->op==TK_COLUMN && pArg->iTable>=0 ){ + sqlite3VdbeAddOp3(v, OP_Offset, pArg->iTable, pArg->iColumn, target); + }else{ + sqlite3VdbeAddOp2(v, OP_Null, 0, target); + } + break; + } +#endif default: { /* The UNLIKELY() function is a no-op. The result is the value ** of the first argument. @@ -104413,6 +107630,7 @@ static int exprCodeInlineFunction( ** Test-only SQL functions that are only usable if enabled ** via SQLITE_TESTCTRL_INTERNAL_FUNCTIONS */ +#if !defined(SQLITE_UNTESTABLE) case INLINEFUNC_expr_compare: { /* Compare two expressions using sqlite3ExprCompare() */ assert( nFarg==2 ); @@ -104446,7 +107664,6 @@ static int exprCodeInlineFunction( break; } -#ifdef SQLITE_DEBUG case INLINEFUNC_affinity: { /* The AFFINITY() function evaluates to a string that describes ** the type affinity of the argument. This is used for testing of @@ -104460,7 +107677,7 @@ static int exprCodeInlineFunction( (aff<=SQLITE_AFF_NONE) ? "none" : azAff[aff-SQLITE_AFF_BLOB]); break; } -#endif +#endif /* !defined(SQLITE_UNTESTABLE) */ } return target; } @@ -104514,7 +107731,8 @@ expr_code_doover: if( pCol->iColumn<0 ){ VdbeComment((v,"%s.rowid",pTab->zName)); }else{ - VdbeComment((v,"%s.%s",pTab->zName,pTab->aCol[pCol->iColumn].zName)); + VdbeComment((v,"%s.%s", + pTab->zName, pTab->aCol[pCol->iColumn].zCnName)); if( pTab->aCol[pCol->iColumn].affinity==SQLITE_AFF_REAL ){ sqlite3VdbeAddOp1(v, OP_RealAffinity, target); } @@ -104536,6 +107754,7 @@ expr_code_doover: */ int aff; iReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft,target); + assert( ExprUseYTab(pExpr) ); if( pExpr->y.pTab ){ aff = sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); }else{ @@ -104559,9 +107778,11 @@ expr_code_doover: ** immediately prior to the first column. */ Column *pCol; - Table *pTab = pExpr->y.pTab; + Table *pTab; int iSrc; int iCol = pExpr->iColumn; + assert( ExprUseYTab(pExpr) ); + pTab = pExpr->y.pTab; assert( pTab!=0 ); assert( iCol>=XN_ROWID ); assert( iColnCol ); @@ -104575,12 +107796,12 @@ expr_code_doover: if( pCol->colFlags & COLFLAG_GENERATED ){ if( pCol->colFlags & COLFLAG_BUSY ){ sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", - pCol->zName); + pCol->zCnName); return 0; } pCol->colFlags |= COLFLAG_BUSY; if( pCol->colFlags & COLFLAG_NOTAVAIL ){ - sqlite3ExprCodeGeneratedColumn(pParse, pCol, iSrc); + sqlite3ExprCodeGeneratedColumn(pParse, pTab, pCol, iSrc); } pCol->colFlags &= ~(COLFLAG_BUSY|COLFLAG_NOTAVAIL); return iSrc; @@ -104599,6 +107820,7 @@ expr_code_doover: iTab = pParse->iSelfTab - 1; } } + assert( ExprUseYTab(pExpr) ); iReg = sqlite3ExprCodeGetColumn(pParse, pExpr->y.pTab, pExpr->iColumn, iTab, target, pExpr->op2); @@ -104632,7 +107854,7 @@ expr_code_doover: ** Expr node to be passed into this function, it will be handled ** sanely and not crash. But keep the assert() to bring the problem ** to the attention of the developers. */ - assert( op==TK_NULL ); + assert( op==TK_NULL || op==TK_ERROR || pParse->db->mallocFailed ); sqlite3VdbeAddOp2(v, OP_Null, 0, target); return target; } @@ -104676,6 +107898,7 @@ expr_code_doover: sqlite3VdbeAddOp2(v, OP_SCopy, inReg, target); inReg = target; } + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3VdbeAddOp2(v, OP_Cast, target, sqlite3AffinityType(pExpr->u.zToken, 0)); return inReg; @@ -104698,8 +107921,9 @@ expr_code_doover: }else{ r1 = sqlite3ExprCodeTemp(pParse, pLeft, ®Free1); r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); - codeCompare(pParse, pLeft, pExpr->pRight, op, - r1, r2, inReg, SQLITE_STOREP2 | p5, + sqlite3VdbeAddOp2(v, OP_Integer, 1, inReg); + codeCompare(pParse, pLeft, pExpr->pRight, op, r1, r2, + sqlite3VdbeCurrentAddr(v)+2, p5, ExprHasProperty(pExpr,EP_Commuted)); assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); @@ -104707,6 +107931,11 @@ expr_code_doover: assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); + if( p5==SQLITE_NULLEQ ){ + sqlite3VdbeAddOp2(v, OP_Integer, 0, inReg); + }else{ + sqlite3VdbeAddOp3(v, OP_ZeroOrNull, r1, inReg, r2); + } testcase( regFree1==0 ); testcase( regFree2==0 ); } @@ -104809,7 +108038,7 @@ expr_code_doover: || NEVER(pExpr->iAgg>=pInfo->nFunc) ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); - sqlite3ErrorMsg(pParse, "misuse of aggregate: %s()", pExpr->u.zToken); + sqlite3ErrorMsg(pParse, "misuse of aggregate: %#T()", pExpr); }else{ return pInfo->aFunc[pExpr->iAgg].iMem; } @@ -104837,8 +108066,8 @@ expr_code_doover: ** multiple times if we know they always give the same result */ return sqlite3ExprCodeRunJustOnce(pParse, pExpr, -1); } - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); assert( !ExprHasProperty(pExpr, EP_TokenOnly) ); + assert( ExprUseXList(pExpr) ); pFarg = pExpr->x.pList; nFarg = pFarg ? pFarg->nExpr : 0; assert( !ExprHasProperty(pExpr, EP_IntValue) ); @@ -104850,7 +108079,7 @@ expr_code_doover: } #endif if( pDef==0 || pDef->xFinalize!=0 ){ - sqlite3ErrorMsg(pParse, "unknown function: %s()", zId); + sqlite3ErrorMsg(pParse, "unknown function: %#T()", pExpr); break; } if( pDef->funcFlags & SQLITE_FUNC_INLINE ){ @@ -104926,20 +108155,8 @@ expr_code_doover: if( !pColl ) pColl = db->pDfltColl; sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ); } -#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC - if( pDef->funcFlags & SQLITE_FUNC_OFFSET ){ - Expr *pArg = pFarg->a[0].pExpr; - if( pArg->op==TK_COLUMN ){ - sqlite3VdbeAddOp3(v, OP_Offset, pArg->iTable, pArg->iColumn, target); - }else{ - sqlite3VdbeAddOp2(v, OP_Null, 0, target); - } - }else -#endif - { - sqlite3VdbeAddFunctionCall(pParse, constMask, r1, target, nFarg, - pDef, pExpr->op2); - } + sqlite3VdbeAddFunctionCall(pParse, constMask, r1, target, nFarg, + pDef, pExpr->op2); if( nFarg ){ if( constMask==0 ){ sqlite3ReleaseTempRange(pParse, r1, nFarg); @@ -104957,7 +108174,10 @@ expr_code_doover: testcase( op==TK_SELECT ); if( pParse->db->mallocFailed ){ return 0; - }else if( op==TK_SELECT && (nCol = pExpr->x.pSelect->pEList->nExpr)!=1 ){ + }else if( op==TK_SELECT + && ALWAYS( ExprUseXSelect(pExpr) ) + && (nCol = pExpr->x.pSelect->pEList->nExpr)!=1 + ){ sqlite3SubselectError(pParse, nCol, 1); }else{ return sqlite3CodeSubselect(pParse, pExpr); @@ -104966,17 +108186,18 @@ expr_code_doover: } case TK_SELECT_COLUMN: { int n; - if( pExpr->pLeft->iTable==0 ){ - pExpr->pLeft->iTable = sqlite3CodeSubselect(pParse, pExpr->pLeft); + Expr *pLeft = pExpr->pLeft; + if( pLeft->iTable==0 || pParse->withinRJSubrtn > pLeft->op2 ){ + pLeft->iTable = sqlite3CodeSubselect(pParse, pLeft); + pLeft->op2 = pParse->withinRJSubrtn; } - assert( pExpr->iTable==0 || pExpr->pLeft->op==TK_SELECT ); - if( pExpr->iTable!=0 - && pExpr->iTable!=(n = sqlite3ExprVectorSize(pExpr->pLeft)) - ){ + assert( pLeft->op==TK_SELECT || pLeft->op==TK_ERROR ); + n = sqlite3ExprVectorSize(pLeft); + if( pExpr->iTable!=n ){ sqlite3ErrorMsg(pParse, "%d columns assigned %d values", pExpr->iTable, n); } - return pExpr->pLeft->iTable + pExpr->iColumn; + return pLeft->iTable + pExpr->iColumn; } case TK_IN: { int destIfFalse = sqlite3VdbeMakeLabel(pParse); @@ -105007,8 +108228,24 @@ expr_code_doover: exprCodeBetween(pParse, pExpr, target, 0, 0); return target; } + case TK_COLLATE: { + if( !ExprHasProperty(pExpr, EP_Collate) + && ALWAYS(pExpr->pLeft) + && pExpr->pLeft->op==TK_FUNCTION + ){ + inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); + if( inReg!=target ){ + sqlite3VdbeAddOp2(v, OP_SCopy, inReg, target); + inReg = target; + } + sqlite3VdbeAddOp1(v, OP_ClrSubtype, inReg); + return inReg; + }else{ + pExpr = pExpr->pLeft; + goto expr_code_doover; /* 2018-04-28: Prevent deep recursion. */ + } + } case TK_SPAN: - case TK_COLLATE: case TK_UPLUS: { pExpr = pExpr->pLeft; goto expr_code_doover; /* 2018-04-28: Prevent deep recursion. OSSFuzz. */ @@ -105040,9 +108277,14 @@ expr_code_doover: ** p1==1 -> old.a p1==4 -> new.a ** p1==2 -> old.b p1==5 -> new.b */ - Table *pTab = pExpr->y.pTab; - int iCol = pExpr->iColumn; - int p1 = pExpr->iTable * (pTab->nCol+1) + 1 + Table *pTab; + int iCol; + int p1; + + assert( ExprUseYTab(pExpr) ); + pTab = pExpr->y.pTab; + iCol = pExpr->iColumn; + p1 = pExpr->iTable * (pTab->nCol+1) + 1 + sqlite3TableColumnToStorage(pTab, iCol); assert( pExpr->iTable==0 || pExpr->iTable==1 ); @@ -105053,7 +108295,7 @@ expr_code_doover: sqlite3VdbeAddOp2(v, OP_Param, p1, target); VdbeComment((v, "r[%d]=%s.%s", target, (pExpr->iTable ? "new" : "old"), - (pExpr->iColumn<0 ? "rowid" : pExpr->y.pTab->aCol[iCol].zName) + (pExpr->iColumn<0 ? "rowid" : pExpr->y.pTab->aCol[iCol].zCnName) )); #ifndef SQLITE_OMIT_FLOATING_POINT @@ -105130,7 +108372,7 @@ expr_code_doover: Expr *pDel = 0; sqlite3 *db = pParse->db; - assert( !ExprHasProperty(pExpr, EP_xIsSelect) && pExpr->x.pList ); + assert( ExprUseXList(pExpr) && pExpr->x.pList!=0 ); assert(pExpr->x.pList->nExpr > 0); pEList = pExpr->x.pList; aListelem = pEList->a; @@ -105244,7 +108486,9 @@ SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce( struct ExprList_item *pItem; int i; for(pItem=p->a, i=p->nExpr; i>0; pItem++, i--){ - if( pItem->reusable && sqlite3ExprCompare(0,pItem->pExpr,pExpr,-1)==0 ){ + if( pItem->fg.reusable + && sqlite3ExprCompare(0,pItem->pExpr,pExpr,-1)==0 + ){ return pItem->u.iConstExprReg; } } @@ -105267,7 +108511,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce( p = sqlite3ExprListAppend(pParse, p, pExpr); if( p ){ struct ExprList_item *pItem = &p->a[p->nExpr-1]; - pItem->reusable = regDest<0; + pItem->fg.reusable = regDest<0; if( regDest<0 ) regDest = ++pParse->nMem; pItem->u.iConstExprReg = regDest; } @@ -105327,7 +108571,7 @@ SQLITE_PRIVATE void sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){ inReg = sqlite3ExprCodeTarget(pParse, pExpr, target); if( inReg!=target ){ u8 op; - if( ExprHasProperty(pExpr,EP_Subquery) ){ + if( ALWAYS(pExpr) && ExprHasProperty(pExpr,EP_Subquery) ){ op = OP_Copy; }else{ op = OP_SCopy; @@ -105401,7 +108645,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeExprList( for(pItem=pList->a, i=0; ipExpr; #ifdef SQLITE_ENABLE_SORTER_REFERENCES - if( pItem->bSorterRef ){ + if( pItem->fg.bSorterRef ){ i--; n--; }else @@ -105475,7 +108719,7 @@ static void exprCodeBetween( memset(&compRight, 0, sizeof(Expr)); memset(&exprAnd, 0, sizeof(Expr)); - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); + assert( ExprUseXList(pExpr) ); pDel = sqlite3ExprDup(db, pExpr->pLeft, 0); if( db->mallocFailed==0 ){ exprAnd.op = TK_AND; @@ -105495,8 +108739,8 @@ static void exprCodeBetween( ** so that the sqlite3ExprCodeTarget() routine will not attempt to move ** it into the Parse.pConstExpr list. We should use a new bit for this, ** for clarity, but we are out of bits in the Expr.flags field so we - ** have to reuse the EP_FromJoin bit. Bummer. */ - pDel->flags |= EP_FromJoin; + ** have to reuse the EP_OuterON bit. Bummer. */ + pDel->flags |= EP_OuterON; sqlite3ExprCodeTarget(pParse, &exprAnd, dest); } sqlite3ReleaseTempReg(pParse, regFree1); @@ -105865,7 +109109,11 @@ SQLITE_PRIVATE void sqlite3ExprIfFalseDup(Parse *pParse, Expr *pExpr, int dest,i ** Otherwise, if the values are not the same or if pExpr is not a simple ** SQL value, zero is returned. */ -static int exprCompareVariable(Parse *pParse, Expr *pVar, Expr *pExpr){ +static int exprCompareVariable( + const Parse *pParse, + const Expr *pVar, + const Expr *pExpr +){ int res = 0; int iVar; sqlite3_value *pL, *pR = 0; @@ -105917,7 +109165,12 @@ static int exprCompareVariable(Parse *pParse, Expr *pVar, Expr *pExpr){ ** Argument pParse should normally be NULL. If it is not NULL and pA or ** pB causes a return value of 2. */ -SQLITE_PRIVATE int sqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTab){ +SQLITE_PRIVATE int sqlite3ExprCompare( + const Parse *pParse, + const Expr *pA, + const Expr *pB, + int iTab +){ u32 combinedFlags; if( pA==0 || pB==0 ){ return pB==pA ? 0 : 2; @@ -105941,7 +109194,9 @@ SQLITE_PRIVATE int sqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTa } return 2; } - if( pA->op!=TK_COLUMN && pA->op!=TK_AGG_COLUMN && pA->u.zToken ){ + assert( !ExprHasProperty(pA, EP_IntValue) ); + assert( !ExprHasProperty(pB, EP_IntValue) ); + if( pA->u.zToken ){ if( pA->op==TK_FUNCTION || pA->op==TK_AGG_FUNCTION ){ if( sqlite3StrICmp(pA->u.zToken,pB->u.zToken)!=0 ) return 2; #ifndef SQLITE_OMIT_WINDOWFUNC @@ -105959,7 +109214,12 @@ SQLITE_PRIVATE int sqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTa return 0; }else if( pA->op==TK_COLLATE ){ if( sqlite3_stricmp(pA->u.zToken,pB->u.zToken)!=0 ) return 2; - }else if( ALWAYS(pB->u.zToken!=0) && strcmp(pA->u.zToken,pB->u.zToken)!=0 ){ + }else + if( pB->u.zToken!=0 + && pA->op!=TK_COLUMN + && pA->op!=TK_AGG_COLUMN + && strcmp(pA->u.zToken,pB->u.zToken)!=0 + ){ return 2; } } @@ -106001,7 +109261,7 @@ SQLITE_PRIVATE int sqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTa ** Two NULL pointers are considered to be the same. But a NULL pointer ** always differs from a non-NULL pointer. */ -SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList *pA, ExprList *pB, int iTab){ +SQLITE_PRIVATE int sqlite3ExprListCompare(const ExprList *pA, const ExprList *pB, int iTab){ int i; if( pA==0 && pB==0 ) return 0; if( pA==0 || pB==0 ) return 1; @@ -106010,7 +109270,7 @@ SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList *pA, ExprList *pB, int iTab){ int res; Expr *pExprA = pA->a[i].pExpr; Expr *pExprB = pB->a[i].pExpr; - if( pA->a[i].sortFlags!=pB->a[i].sortFlags ) return 1; + if( pA->a[i].fg.sortFlags!=pB->a[i].fg.sortFlags ) return 1; if( (res = sqlite3ExprCompare(0, pExprA, pExprB, iTab)) ) return res; } return 0; @@ -106020,7 +109280,7 @@ SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList *pA, ExprList *pB, int iTab){ ** Like sqlite3ExprCompare() except COLLATE operators at the top-level ** are ignored. */ -SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr *pA, Expr *pB, int iTab){ +SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr *pA,Expr *pB, int iTab){ return sqlite3ExprCompare(0, sqlite3ExprSkipCollateAndLikely(pA), sqlite3ExprSkipCollateAndLikely(pB), @@ -106034,9 +109294,9 @@ SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr *pA, Expr *pB, int iTab){ ** non-NULL if pNN is not NULL */ static int exprImpliesNotNull( - Parse *pParse, /* Parsing context */ - Expr *p, /* The expression to be checked */ - Expr *pNN, /* The expression that is NOT NULL */ + const Parse *pParse,/* Parsing context */ + const Expr *p, /* The expression to be checked */ + const Expr *pNN, /* The expression that is NOT NULL */ int iTab, /* Table being evaluated */ int seenNot /* Return true only if p can be any non-NULL value */ ){ @@ -106048,12 +109308,13 @@ static int exprImpliesNotNull( switch( p->op ){ case TK_IN: { if( seenNot && ExprHasProperty(p, EP_xIsSelect) ) return 0; - assert( ExprHasProperty(p,EP_xIsSelect) - || (p->x.pList!=0 && p->x.pList->nExpr>0) ); + assert( ExprUseXSelect(p) || (p->x.pList!=0 && p->x.pList->nExpr>0) ); return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1); } case TK_BETWEEN: { - ExprList *pList = p->x.pList; + ExprList *pList; + assert( ExprUseXList(p) ); + pList = p->x.pList; assert( pList!=0 ); assert( pList->nExpr==2 ); if( seenNot ) return 0; @@ -106129,7 +109390,12 @@ static int exprImpliesNotNull( ** improvement. Returning false might cause a performance reduction, but ** it will always give the correct answer and is hence always safe. */ -SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Parse *pParse, Expr *pE1, Expr *pE2, int iTab){ +SQLITE_PRIVATE int sqlite3ExprImpliesExpr( + const Parse *pParse, + const Expr *pE1, + const Expr *pE2, + int iTab +){ if( sqlite3ExprCompare(pParse, pE1, pE2, iTab)==0 ){ return 1; } @@ -106159,7 +109425,7 @@ SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Parse *pParse, Expr *pE1, Expr *pE2, i static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ testcase( pExpr->op==TK_AGG_COLUMN ); testcase( pExpr->op==TK_AGG_FUNCTION ); - if( ExprHasProperty(pExpr, EP_FromJoin) ) return WRC_Prune; + if( ExprHasProperty(pExpr, EP_OuterON) ) return WRC_Prune; switch( pExpr->op ){ case TK_ISNOT: case TK_ISNULL: @@ -106225,10 +109491,14 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ testcase( pExpr->op==TK_GE ); /* The y.pTab=0 assignment in wherecode.c always happens after the ** impliesNotNullRow() test */ - if( (pLeft->op==TK_COLUMN && ALWAYS(pLeft->y.pTab!=0) - && IsVirtual(pLeft->y.pTab)) - || (pRight->op==TK_COLUMN && ALWAYS(pRight->y.pTab!=0) - && IsVirtual(pRight->y.pTab)) + assert( pLeft->op!=TK_COLUMN || ExprUseYTab(pLeft) ); + assert( pRight->op!=TK_COLUMN || ExprUseYTab(pRight) ); + if( (pLeft->op==TK_COLUMN + && pLeft->y.pTab!=0 + && IsVirtual(pLeft->y.pTab)) + || (pRight->op==TK_COLUMN + && pRight->y.pTab!=0 + && IsVirtual(pRight->y.pTab)) ){ return WRC_Prune; } @@ -106252,8 +109522,8 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ ** False positives are not allowed, however. A false positive may result ** in an incorrect answer. ** -** Terms of p that are marked with EP_FromJoin (and hence that come from -** the ON or USING clauses of LEFT JOINS) are excluded from the analysis. +** Terms of p that are marked with EP_OuterON (and hence that come from +** the ON or USING clauses of OUTER JOINS) are excluded from the analysis. ** ** This routine is used to check if a LEFT JOIN can be converted into ** an ordinary JOIN. The p argument is the WHERE clause. If the WHERE @@ -106337,88 +109607,125 @@ SQLITE_PRIVATE int sqlite3ExprCoveredByIndex( } -/* -** An instance of the following structure is used by the tree walker -** to count references to table columns in the arguments of an -** aggregate function, in order to implement the -** sqlite3FunctionThisSrc() routine. +/* Structure used to pass information throught the Walker in order to +** implement sqlite3ReferencesSrcList(). */ -struct SrcCount { - SrcList *pSrc; /* One particular FROM clause in a nested query */ - int iSrcInner; /* Smallest cursor number in this context */ - int nThis; /* Number of references to columns in pSrcList */ - int nOther; /* Number of references to columns in other FROM clauses */ +struct RefSrcList { + sqlite3 *db; /* Database connection used for sqlite3DbRealloc() */ + SrcList *pRef; /* Looking for references to these tables */ + i64 nExclude; /* Number of tables to exclude from the search */ + int *aiExclude; /* Cursor IDs for tables to exclude from the search */ }; /* -** xSelect callback for sqlite3FunctionUsesThisSrc(). If this is the first -** SELECT with a FROM clause encountered during this iteration, set -** SrcCount.iSrcInner to the cursor number of the leftmost object in -** the FROM cause. +** Walker SELECT callbacks for sqlite3ReferencesSrcList(). +** +** When entering a new subquery on the pExpr argument, add all FROM clause +** entries for that subquery to the exclude list. +** +** When leaving the subquery, remove those entries from the exclude list. */ -static int selectSrcCount(Walker *pWalker, Select *pSel){ - struct SrcCount *p = pWalker->u.pSrcCount; - if( p->iSrcInner==0x7FFFFFFF && ALWAYS(pSel->pSrc) && pSel->pSrc->nSrc ){ - pWalker->u.pSrcCount->iSrcInner = pSel->pSrc->a[0].iCursor; +static int selectRefEnter(Walker *pWalker, Select *pSelect){ + struct RefSrcList *p = pWalker->u.pRefSrcList; + SrcList *pSrc = pSelect->pSrc; + i64 i, j; + int *piNew; + if( pSrc->nSrc==0 ) return WRC_Continue; + j = p->nExclude; + p->nExclude += pSrc->nSrc; + piNew = sqlite3DbRealloc(p->db, p->aiExclude, p->nExclude*sizeof(int)); + if( piNew==0 ){ + p->nExclude = 0; + return WRC_Abort; + }else{ + p->aiExclude = piNew; + } + for(i=0; inSrc; i++, j++){ + p->aiExclude[j] = pSrc->a[i].iCursor; } return WRC_Continue; } +static void selectRefLeave(Walker *pWalker, Select *pSelect){ + struct RefSrcList *p = pWalker->u.pRefSrcList; + SrcList *pSrc = pSelect->pSrc; + if( p->nExclude ){ + assert( p->nExclude>=pSrc->nSrc ); + p->nExclude -= pSrc->nSrc; + } +} -/* -** Count the number of references to columns. +/* This is the Walker EXPR callback for sqlite3ReferencesSrcList(). +** +** Set the 0x01 bit of pWalker->eCode if there is a reference to any +** of the tables shown in RefSrcList.pRef. +** +** Set the 0x02 bit of pWalker->eCode if there is a reference to a +** table is in neither RefSrcList.pRef nor RefSrcList.aiExclude. */ -static int exprSrcCount(Walker *pWalker, Expr *pExpr){ - /* There was once a NEVER() on the second term on the grounds that - ** sqlite3FunctionUsesThisSrc() was always called before - ** sqlite3ExprAnalyzeAggregates() and so the TK_COLUMNs have not yet - ** been converted into TK_AGG_COLUMN. But this is no longer true due - ** to window functions - sqlite3WindowRewrite() may now indirectly call - ** FunctionUsesThisSrc() when creating a new sub-select. */ - if( pExpr->op==TK_COLUMN || pExpr->op==TK_AGG_COLUMN ){ +static int exprRefToSrcList(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_COLUMN + || pExpr->op==TK_AGG_COLUMN + ){ int i; - struct SrcCount *p = pWalker->u.pSrcCount; - SrcList *pSrc = p->pSrc; + struct RefSrcList *p = pWalker->u.pRefSrcList; + SrcList *pSrc = p->pRef; int nSrc = pSrc ? pSrc->nSrc : 0; for(i=0; iiTable==pSrc->a[i].iCursor ) break; + if( pExpr->iTable==pSrc->a[i].iCursor ){ + pWalker->eCode |= 1; + return WRC_Continue; + } } - if( inThis++; - }else if( pExpr->iTableiSrcInner ){ - /* In a well-formed parse tree (no name resolution errors), - ** TK_COLUMN nodes with smaller Expr.iTable values are in an - ** outer context. Those are the only ones to count as "other" */ - p->nOther++; + for(i=0; inExclude && p->aiExclude[i]!=pExpr->iTable; i++){} + if( i>=p->nExclude ){ + pWalker->eCode |= 2; } } return WRC_Continue; } /* -** Determine if any of the arguments to the pExpr Function reference -** pSrcList. Return true if they do. Also return true if the function -** has no arguments or has only constant arguments. Return false if pExpr -** references columns but not columns of tables found in pSrcList. +** Check to see if pExpr references any tables in pSrcList. +** Possible return values: +** +** 1 pExpr does references a table in pSrcList. +** +** 0 pExpr references some table that is not defined in either +** pSrcList or in subqueries of pExpr itself. +** +** -1 pExpr only references no tables at all, or it only +** references tables defined in subqueries of pExpr itself. +** +** As currently used, pExpr is always an aggregate function call. That +** fact is exploited for efficiency. */ -SQLITE_PRIVATE int sqlite3FunctionUsesThisSrc(Expr *pExpr, SrcList *pSrcList){ +SQLITE_PRIVATE int sqlite3ReferencesSrcList(Parse *pParse, Expr *pExpr, SrcList *pSrcList){ Walker w; - struct SrcCount cnt; - assert( pExpr->op==TK_AGG_FUNCTION ); + struct RefSrcList x; memset(&w, 0, sizeof(w)); - w.xExprCallback = exprSrcCount; - w.xSelectCallback = selectSrcCount; - w.u.pSrcCount = &cnt; - cnt.pSrc = pSrcList; - cnt.iSrcInner = (pSrcList&&pSrcList->nSrc)?pSrcList->a[0].iCursor:0x7FFFFFFF; - cnt.nThis = 0; - cnt.nOther = 0; + memset(&x, 0, sizeof(x)); + w.xExprCallback = exprRefToSrcList; + w.xSelectCallback = selectRefEnter; + w.xSelectCallback2 = selectRefLeave; + w.u.pRefSrcList = &x; + x.db = pParse->db; + x.pRef = pSrcList; + assert( pExpr->op==TK_AGG_FUNCTION ); + assert( ExprUseXList(pExpr) ); sqlite3WalkExprList(&w, pExpr->x.pList); #ifndef SQLITE_OMIT_WINDOWFUNC if( ExprHasProperty(pExpr, EP_WinFunc) ){ sqlite3WalkExpr(&w, pExpr->y.pWin->pFilter); } #endif - return cnt.nThis>0 || cnt.nOther==0; + sqlite3DbFree(pParse->db, x.aiExclude); + if( w.eCode & 0x01 ){ + return 1; + }else if( w.eCode ){ + return 0; + }else{ + return -1; + } } /* @@ -106553,6 +109860,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ && (k = addAggInfoColumn(pParse->db, pAggInfo))>=0 ){ pCol = &pAggInfo->aCol[k]; + assert( ExprUseYTab(pExpr) ); pCol->pTab = pExpr->y.pTab; pCol->iTable = pExpr->iTable; pCol->iColumn = pExpr->iColumn; @@ -106601,6 +109909,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ */ struct AggInfo_func *pItem = pAggInfo->aFunc; for(i=0; inFunc; i++, pItem++){ + if( pItem->pFExpr==pExpr ) break; if( sqlite3ExprCompare(0, pItem->pFExpr, pExpr, -1)==0 ){ break; } @@ -106615,7 +109924,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ pItem = &pAggInfo->aFunc[i]; pItem->pFExpr = pExpr; pItem->iMem = ++pParse->nMem; - assert( !ExprHasProperty(pExpr, EP_IntValue) ); + assert( ExprUseUToken(pExpr) ); pItem->pFunc = sqlite3FindFunction(pParse->db, pExpr->u.zToken, pExpr->x.pList ? pExpr->x.pList->nExpr : 0, enc, 0); @@ -106801,6 +110110,7 @@ SQLITE_PRIVATE int sqlite3NoTempsInRange(Parse *pParse, int iFirst, int iLast){ static int isAlterableTable(Parse *pParse, Table *pTab){ if( 0==sqlite3StrNICmp(pTab->zName, "sqlite_", 7) #ifndef SQLITE_OMIT_VIRTUALTABLE + || (pTab->tabFlags & TF_Eponymous)!=0 || ( (pTab->tabFlags & TF_Shadow)!=0 && sqlite3ReadOnlyShadowTables(pParse->db) ) @@ -106824,27 +110134,51 @@ static void renameTestSchema( const char *zDb, /* Name of db to verify schema of */ int bTemp, /* True if this is the temp db */ const char *zWhen, /* "when" part of error message */ - const char *zDropColumn /* Name of column being dropped */ + int bNoDQS /* Do not allow DQS in the schema */ ){ pParse->colNamesSet = 1; sqlite3NestedParse(pParse, "SELECT 1 " - "FROM \"%w\"." DFLT_SCHEMA_TABLE " " + "FROM \"%w\"." LEGACY_SCHEMA_TABLE " " "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" " AND sql NOT LIKE 'create virtual%%'" - " AND sqlite_rename_test(%Q, sql, type, name, %d, %Q, %Q)=NULL ", + " AND sqlite_rename_test(%Q, sql, type, name, %d, %Q, %d)=NULL ", zDb, - zDb, bTemp, zWhen, zDropColumn + zDb, bTemp, zWhen, bNoDQS ); if( bTemp==0 ){ sqlite3NestedParse(pParse, "SELECT 1 " - "FROM temp." DFLT_SCHEMA_TABLE " " + "FROM temp." LEGACY_SCHEMA_TABLE " " "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" " AND sql NOT LIKE 'create virtual%%'" - " AND sqlite_rename_test(%Q, sql, type, name, 1, %Q, %Q)=NULL ", - zDb, zWhen, zDropColumn + " AND sqlite_rename_test(%Q, sql, type, name, 1, %Q, %d)=NULL ", + zDb, zWhen, bNoDQS + ); + } +} + +/* +** Generate VM code to replace any double-quoted strings (but not double-quoted +** identifiers) within the "sql" column of the sqlite_schema table in +** database zDb with their single-quoted equivalents. If argument bTemp is +** not true, similarly update all SQL statements in the sqlite_schema table +** of the temp db. +*/ +static void renameFixQuotes(Parse *pParse, const char *zDb, int bTemp){ + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE + " SET sql = sqlite_rename_quotefix(%Q, sql)" + "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" + " AND sql NOT LIKE 'create virtual%%'" , zDb, zDb + ); + if( bTemp==0 ){ + sqlite3NestedParse(pParse, + "UPDATE temp." LEGACY_SCHEMA_TABLE + " SET sql = sqlite_rename_quotefix('temp', sql)" + "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" + " AND sql NOT LIKE 'create virtual%%'" ); } } @@ -106880,9 +110214,7 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( const char *zTabName; /* Original name of the table */ Vdbe *v; VTable *pVTab = 0; /* Non-zero if this is a v-tab with an xRename() */ - u32 savedDbFlags; /* Saved value of db->mDbFlags */ - savedDbFlags = db->mDbFlags; if( NEVER(db->mallocFailed) ) goto exit_rename_table; assert( pSrc->nSrc==1 ); assert( sqlite3BtreeHoldsAllMutexes(pParse->db) ); @@ -106891,7 +110223,6 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( if( !pTab ) goto exit_rename_table; iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); zDb = db->aDb[iDb].zDbSName; - db->mDbFlags |= DBFLAG_PreferBuiltin; /* Get a NULL terminated version of the new table name. */ zName = sqlite3NameFromToken(db, pName); @@ -106920,7 +110251,7 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( } #ifndef SQLITE_OMIT_VIEW - if( pTab->pSelect ){ + if( IsView(pTab) ){ sqlite3ErrorMsg(pParse, "view %s may not be altered", pTab->zName); goto exit_rename_table; } @@ -106962,7 +110293,7 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( /* Rewrite all CREATE TABLE, INDEX, TRIGGER or VIEW statements in ** the schema to use the new table name. */ sqlite3NestedParse(pParse, - "UPDATE \"%w\"." DFLT_SCHEMA_TABLE " SET " + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " "sql = sqlite_rename_table(%Q, type, name, sql, %Q, %Q, %d) " "WHERE (type!='index' OR tbl_name=%Q COLLATE nocase)" "AND name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" @@ -106972,7 +110303,7 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( /* Update the tbl_name and name columns of the sqlite_schema table ** as required. */ sqlite3NestedParse(pParse, - "UPDATE %Q." DFLT_SCHEMA_TABLE " SET " + "UPDATE %Q." LEGACY_SCHEMA_TABLE " SET " "tbl_name = %Q, " "name = CASE " "WHEN type='table' THEN %Q " @@ -107007,7 +110338,7 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( "sql = sqlite_rename_table(%Q, type, name, sql, %Q, %Q, 1), " "tbl_name = " "CASE WHEN tbl_name=%Q COLLATE nocase AND " - " sqlite_rename_test(%Q, sql, type, name, 1, 'after rename',0) " + " sqlite_rename_test(%Q, sql, type, name, 1, 'after rename', 0) " "THEN %Q ELSE tbl_name END " "WHERE type IN ('view', 'trigger')" , zDb, zTabName, zName, zTabName, zDb, zName); @@ -107032,7 +110363,6 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( exit_rename_table: sqlite3SrcListDelete(db, pSrc); sqlite3DbFree(db, zName); - db->mDbFlags = savedDbFlags; } /* @@ -107073,7 +110403,9 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ int r1; /* Temporary registers */ db = pParse->db; - if( pParse->nErr || db->mallocFailed ) return; + assert( db->pParse==pParse ); + if( pParse->nErr ) return; + assert( db->mallocFailed==0 ); pNew = pParse->pNewTable; assert( pNew ); @@ -107082,7 +110414,7 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ zDb = db->aDb[iDb].zDbSName; zTab = &pNew->zName[16]; /* Skip the "sqlite_altertab_" prefix on the name */ pCol = &pNew->aCol[pNew->nCol-1]; - pDflt = pCol->pDflt; + pDflt = sqlite3ColumnExpr(pNew, pCol); pTab = sqlite3FindTable(db, zTab, zDb); assert( pTab ); @@ -107116,7 +110448,8 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ if( pDflt && pDflt->pLeft->op==TK_NULL ){ pDflt = 0; } - if( (db->flags&SQLITE_ForeignKeys) && pNew->pFKey && pDflt ){ + assert( IsOrdinaryTable(pNew) ); + if( (db->flags&SQLITE_ForeignKeys) && pNew->u.tab.pFKey && pDflt ){ sqlite3ErrorIfNotEmpty(pParse, zDb, zTab, "Cannot add a REFERENCES column with non-NULL default value"); } @@ -107153,31 +110486,30 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ zCol = sqlite3DbStrNDup(db, (char*)pColDef->z, pColDef->n); if( zCol ){ char *zEnd = &zCol[pColDef->n-1]; - u32 savedDbFlags = db->mDbFlags; while( zEnd>zCol && (*zEnd==';' || sqlite3Isspace(*zEnd)) ){ *zEnd-- = '\0'; } - db->mDbFlags |= DBFLAG_PreferBuiltin; /* substr() operations on characters, but addColOffset is in bytes. So we ** have to use printf() to translate between these units: */ + assert( IsOrdinaryTable(pTab) ); + assert( IsOrdinaryTable(pNew) ); sqlite3NestedParse(pParse, - "UPDATE \"%w\"." DFLT_SCHEMA_TABLE " SET " + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " "sql = printf('%%.%ds, ',sql) || %Q" " || substr(sql,1+length(printf('%%.%ds',sql))) " "WHERE type = 'table' AND name = %Q", - zDb, pNew->addColOffset, zCol, pNew->addColOffset, + zDb, pNew->u.tab.addColOffset, zCol, pNew->u.tab.addColOffset, zTab ); sqlite3DbFree(db, zCol); - db->mDbFlags = savedDbFlags; } - /* Make sure the schema version is at least 3. But do not upgrade - ** from less than 3 to 4, as that will corrupt any preexisting DESC - ** index. - */ v = sqlite3GetVdbe(pParse); if( v ){ + /* Make sure the schema version is at least 3. But do not upgrade + ** from less than 3 to 4, as that will corrupt any preexisting DESC + ** index. + */ r1 = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, r1, BTREE_FILE_FORMAT); sqlite3VdbeUsesBtree(v, iDb); @@ -107186,10 +110518,25 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, 3); sqlite3ReleaseTempReg(pParse, r1); - } - /* Reload the table definition */ - renameReloadSchema(pParse, iDb, INITFLAG_AlterRename); + /* Reload the table definition */ + renameReloadSchema(pParse, iDb, INITFLAG_AlterAdd); + + /* Verify that constraints are still satisfied */ + if( pNew->pCheck!=0 + || (pCol->notNull && (pCol->colFlags & COLFLAG_GENERATED)!=0) + ){ + sqlite3NestedParse(pParse, + "SELECT CASE WHEN quick_check GLOB 'CHECK*'" + " THEN raise(ABORT,'CHECK constraint failed')" + " ELSE raise(ABORT,'NOT NULL constraint failed')" + " END" + " FROM pragma_quick_check(%Q,%Q)" + " WHERE quick_check GLOB 'CHECK*' OR quick_check GLOB 'NULL*'", + zTab, zDb + ); + } + } } /* @@ -107230,7 +110577,7 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ #endif /* Make sure this is not an attempt to ALTER a view. */ - if( pTab->pSelect ){ + if( IsView(pTab) ){ sqlite3ErrorMsg(pParse, "Cannot add a column to a view"); goto exit_begin_add_column; } @@ -107239,7 +110586,8 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ } sqlite3MayAbort(pParse); - assert( pTab->addColOffset>0 ); + assert( IsOrdinaryTable(pTab) ); + assert( pTab->u.tab.addColOffset>0 ); iDb = sqlite3SchemaToIndex(db, pTab->pSchema); /* Put a copy of the Table struct in Parse.pNewTable for the @@ -107266,13 +110614,13 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ memcpy(pNew->aCol, pTab->aCol, sizeof(Column)*pNew->nCol); for(i=0; inCol; i++){ Column *pCol = &pNew->aCol[i]; - pCol->zName = sqlite3DbStrDup(db, pCol->zName); - pCol->hName = sqlite3StrIHash(pCol->zName); - pCol->zColl = 0; - pCol->pDflt = 0; + pCol->zCnName = sqlite3DbStrDup(db, pCol->zCnName); + pCol->hName = sqlite3StrIHash(pCol->zCnName); } + assert( IsOrdinaryTable(pNew) ); + pNew->u.tab.pDfltList = sqlite3ExprListDup(db, pTab->u.tab.pDfltList, 0); pNew->pSchema = db->aDb[iDb].pSchema; - pNew->addColOffset = pTab->addColOffset; + pNew->u.tab.addColOffset = pTab->u.tab.addColOffset; pNew->nTabRef = 1; exit_begin_add_column: @@ -107292,7 +110640,7 @@ exit_begin_add_column: static int isRealTable(Parse *pParse, Table *pTab, int bDrop){ const char *zType = 0; #ifndef SQLITE_OMIT_VIEW - if( pTab->pSelect ){ + if( IsView(pTab) ){ zType = "view"; } #endif @@ -107359,13 +110707,17 @@ SQLITE_PRIVATE void sqlite3AlterRenameColumn( zOld = sqlite3NameFromToken(db, pOld); if( !zOld ) goto exit_rename_column; for(iCol=0; iColnCol; iCol++){ - if( 0==sqlite3StrICmp(pTab->aCol[iCol].zName, zOld) ) break; + if( 0==sqlite3StrICmp(pTab->aCol[iCol].zCnName, zOld) ) break; } if( iCol==pTab->nCol ){ - sqlite3ErrorMsg(pParse, "no such column: \"%s\"", zOld); + sqlite3ErrorMsg(pParse, "no such column: \"%T\"", pOld); goto exit_rename_column; } + /* Ensure the schema contains no double-quoted strings */ + renameTestSchema(pParse, zDb, iSchema==1, "", 0); + renameFixQuotes(pParse, zDb, iSchema==1); + /* Do the rename operation using a recursive UPDATE statement that ** uses the sqlite_rename_column() SQL function to compute the new ** CREATE statement text for the sqlite_schema table. @@ -107376,18 +110728,17 @@ SQLITE_PRIVATE void sqlite3AlterRenameColumn( assert( pNew->n>0 ); bQuote = sqlite3Isquote(pNew->z[0]); sqlite3NestedParse(pParse, - "UPDATE \"%w\"." DFLT_SCHEMA_TABLE " SET " + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, %d) " "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X' " - " AND (type != 'index' OR tbl_name = %Q)" - " AND sql NOT LIKE 'create virtual%%'", + " AND (type != 'index' OR tbl_name = %Q)", zDb, zDb, pTab->zName, iCol, zNew, bQuote, iSchema==1, pTab->zName ); sqlite3NestedParse(pParse, - "UPDATE temp." DFLT_SCHEMA_TABLE " SET " + "UPDATE temp." LEGACY_SCHEMA_TABLE " SET " "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, 1) " "WHERE type IN ('trigger', 'view')", zDb, pTab->zName, iCol, zNew, bQuote @@ -107395,7 +110746,7 @@ SQLITE_PRIVATE void sqlite3AlterRenameColumn( /* Drop and reload the database schema. */ renameReloadSchema(pParse, iSchema, INITFLAG_AlterRename); - renameTestSchema(pParse, zDb, iSchema==1, "after rename", 0); + renameTestSchema(pParse, zDb, iSchema==1, "after rename", 1); exit_rename_column: sqlite3SrcListDelete(db, pSrc); @@ -107422,7 +110773,7 @@ SQLITE_PRIVATE void sqlite3AlterRenameColumn( ** the parse tree. */ struct RenameToken { - void *p; /* Parse tree element created by token t */ + const void *p; /* Parse tree element created by token t */ Token t; /* The token that created parse tree element p */ RenameToken *pNext; /* Next is a list of all RenameToken objects */ }; @@ -107464,9 +110815,11 @@ struct RenameCtx { ** Technically, as x no longer points into a valid object or to the byte ** following a valid object, it may not be used in comparison operations. */ -static void renameTokenCheckAll(Parse *pParse, void *pPtr){ - if( pParse->nErr==0 && pParse->db->mallocFailed==0 ){ - RenameToken *p; +static void renameTokenCheckAll(Parse *pParse, const void *pPtr){ + assert( pParse==pParse->db->pParse ); + assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); + if( pParse->nErr==0 ){ + const RenameToken *p; u8 i = 0; for(p=pParse->pRename; p; p=p->pNext){ if( p->p ){ @@ -107492,7 +110845,11 @@ static void renameTokenCheckAll(Parse *pParse, void *pPtr){ ** with tail recursion in tokenExpr() routine, for a small performance ** improvement. */ -SQLITE_PRIVATE void *sqlite3RenameTokenMap(Parse *pParse, void *pPtr, Token *pToken){ +SQLITE_PRIVATE const void *sqlite3RenameTokenMap( + Parse *pParse, + const void *pPtr, + const Token *pToken +){ RenameToken *pNew; assert( pPtr || pParse->db->mallocFailed ); renameTokenCheckAll(pParse, pPtr); @@ -107514,7 +110871,7 @@ SQLITE_PRIVATE void *sqlite3RenameTokenMap(Parse *pParse, void *pPtr, Token *pTo ** with parse tree element pFrom. This function remaps the associated token ** to parse tree element pTo. */ -SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse *pParse, void *pTo, void *pFrom){ +SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse *pParse, const void *pTo, const void *pFrom){ RenameToken *p; renameTokenCheckAll(pParse, pTo); for(p=pParse->pRename; p; p=p->pNext){ @@ -107530,7 +110887,10 @@ SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse *pParse, void *pTo, void *pFro */ static int renameUnmapExprCb(Walker *pWalker, Expr *pExpr){ Parse *pParse = pWalker->pParse; - sqlite3RenameTokenRemap(pParse, 0, (void*)pExpr); + sqlite3RenameTokenRemap(pParse, 0, (const void*)pExpr); + if( ExprUseYTab(pExpr) ){ + sqlite3RenameTokenRemap(pParse, 0, (const void*)&pExpr->y.pTab); + } return WRC_Continue; } @@ -107541,15 +110901,31 @@ static int renameUnmapExprCb(Walker *pWalker, Expr *pExpr){ static void renameWalkWith(Walker *pWalker, Select *pSelect){ With *pWith = pSelect->pWith; if( pWith ){ + Parse *pParse = pWalker->pParse; int i; + With *pCopy = 0; + assert( pWith->nCte>0 ); + if( (pWith->a[0].pSelect->selFlags & SF_Expanded)==0 ){ + /* Push a copy of the With object onto the with-stack. We use a copy + ** here as the original will be expanded and resolved (flags SF_Expanded + ** and SF_Resolved) below. And the parser code that uses the with-stack + ** fails if the Select objects on it have already been expanded and + ** resolved. */ + pCopy = sqlite3WithDup(pParse->db, pWith); + pCopy = sqlite3WithPush(pParse, pCopy, 1); + } for(i=0; inCte; i++){ Select *p = pWith->a[i].pSelect; NameContext sNC; memset(&sNC, 0, sizeof(sNC)); - sNC.pParse = pWalker->pParse; - sqlite3SelectPrep(sNC.pParse, p, &sNC); + sNC.pParse = pParse; + if( pCopy ) sqlite3SelectPrep(sNC.pParse, p, &sNC); + if( sNC.pParse->db->mallocFailed ) return; sqlite3WalkSelect(pWalker, p); - sqlite3RenameExprlistUnmap(pWalker->pParse, pWith->a[i].pCols); + sqlite3RenameExprlistUnmap(pParse, pWith->a[i].pCols); + } + if( pCopy && pParse->pWith==pCopy ){ + pParse->pWith = pCopy->pOuter; } } } @@ -107559,13 +110935,12 @@ static void renameWalkWith(Walker *pWalker, Select *pSelect){ */ static void unmapColumnIdlistNames( Parse *pParse, - IdList *pIdList + const IdList *pIdList ){ - if( pIdList ){ - int ii; - for(ii=0; iinId; ii++){ - sqlite3RenameTokenRemap(pParse, 0, (void*)pIdList->a[ii].zName); - } + int ii; + assert( pIdList!=0 ); + for(ii=0; iinId; ii++){ + sqlite3RenameTokenRemap(pParse, 0, (const void*)pIdList->a[ii].zName); } } @@ -107576,11 +110951,15 @@ static int renameUnmapSelectCb(Walker *pWalker, Select *p){ Parse *pParse = pWalker->pParse; int i; if( pParse->nErr ) return WRC_Abort; - if( NEVER(p->selFlags & SF_View) ) return WRC_Prune; + testcase( p->selFlags & SF_View ); + testcase( p->selFlags & SF_CopyCte ); + if( p->selFlags & (SF_View|SF_CopyCte) ){ + return WRC_Prune; + } if( ALWAYS(p->pEList) ){ ExprList *pList = p->pEList; for(i=0; inExpr; i++){ - if( pList->a[i].zEName && pList->a[i].eEName==ENAME_NAME ){ + if( pList->a[i].zEName && pList->a[i].fg.eEName==ENAME_NAME ){ sqlite3RenameTokenRemap(pParse, 0, (void*)pList->a[i].zEName); } } @@ -107589,8 +110968,11 @@ static int renameUnmapSelectCb(Walker *pWalker, Select *p){ SrcList *pSrc = p->pSrc; for(i=0; inSrc; i++){ sqlite3RenameTokenRemap(pParse, 0, (void*)pSrc->a[i].zName); - if( sqlite3WalkExpr(pWalker, pSrc->a[i].pOn) ) return WRC_Abort; - unmapColumnIdlistNames(pParse, pSrc->a[i].pUsing); + if( pSrc->a[i].fg.isUsing==0 ){ + sqlite3WalkExpr(pWalker, pSrc->a[i].u3.pOn); + }else{ + unmapColumnIdlistNames(pParse, pSrc->a[i].u3.pUsing); + } } } @@ -107626,7 +111008,7 @@ SQLITE_PRIVATE void sqlite3RenameExprlistUnmap(Parse *pParse, ExprList *pEList){ sWalker.xExprCallback = renameUnmapExprCb; sqlite3WalkExprList(&sWalker, pEList); for(i=0; inExpr; i++){ - if( ALWAYS(pEList->a[i].eEName==ENAME_NAME) ){ + if( ALWAYS(pEList->a[i].fg.eEName==ENAME_NAME) ){ sqlite3RenameTokenRemap(pParse, 0, (void*)pEList->a[i].zEName); } } @@ -107657,10 +111039,12 @@ static void renameTokenFree(sqlite3 *db, RenameToken *pToken){ static RenameToken *renameTokenFind( Parse *pParse, struct RenameCtx *pCtx, - void *pPtr + const void *pPtr ){ RenameToken **pp; - assert( pPtr!=0 ); + if( NEVER(pPtr==0) ){ + return 0; + } for(pp=&pParse->pRename; (*pp); pp=&(*pp)->pNext){ if( (*pp)->p==pPtr ){ RenameToken *pToken = *pp; @@ -107682,7 +111066,11 @@ static RenameToken *renameTokenFind( ** descend into sub-select statements. */ static int renameColumnSelectCb(Walker *pWalker, Select *p){ - if( p->selFlags & SF_View ) return WRC_Prune; + if( p->selFlags & (SF_View|SF_CopyCte) ){ + testcase( p->selFlags & SF_View ); + testcase( p->selFlags & SF_CopyCte ); + return WRC_Prune; + } renameWalkWith(pWalker, p); return WRC_Continue; } @@ -107705,6 +111093,7 @@ static int renameColumnExprCb(Walker *pWalker, Expr *pExpr){ renameTokenFind(pWalker->pParse, p, (void*)pExpr); }else if( pExpr->op==TK_COLUMN && pExpr->iColumn==p->iCol + && ALWAYS(ExprUseYTab(pExpr)) && p->pTab==pExpr->y.pTab ){ renameTokenFind(pWalker->pParse, p, (void*)pExpr); @@ -107753,12 +111142,12 @@ static void renameColumnParseError( const char *zN = (const char*)sqlite3_value_text(pObject); char *zErr; - zErr = sqlite3_mprintf("error in %s %s%s%s: %s", + zErr = sqlite3MPrintf(pParse->db, "error in %s %s%s%s: %s", zT, zN, (zWhen[0] ? " " : ""), zWhen, pParse->zErrMsg ); sqlite3_result_error(pCtx, zErr, -1); - sqlite3_free(zErr); + sqlite3DbFree(pParse->db, zErr); } /* @@ -107770,18 +111159,18 @@ static void renameColumnParseError( static void renameColumnElistNames( Parse *pParse, RenameCtx *pCtx, - ExprList *pEList, + const ExprList *pEList, const char *zOld ){ if( pEList ){ int i; for(i=0; inExpr; i++){ - char *zName = pEList->a[i].zEName; - if( ALWAYS(pEList->a[i].eEName==ENAME_NAME) + const char *zName = pEList->a[i].zEName; + if( ALWAYS(pEList->a[i].fg.eEName==ENAME_NAME) && ALWAYS(zName!=0) && 0==sqlite3_stricmp(zName, zOld) ){ - renameTokenFind(pParse, pCtx, (void*)zName); + renameTokenFind(pParse, pCtx, (const void*)zName); } } } @@ -107795,15 +111184,15 @@ static void renameColumnElistNames( static void renameColumnIdlistNames( Parse *pParse, RenameCtx *pCtx, - IdList *pIdList, + const IdList *pIdList, const char *zOld ){ if( pIdList ){ int i; for(i=0; inId; i++){ - char *zName = pIdList->a[i].zName; + const char *zName = pIdList->a[i].zName; if( 0==sqlite3_stricmp(zName, zOld) ){ - renameTokenFind(pParse, pCtx, (void*)zName); + renameTokenFind(pParse, pCtx, (const void*)zName); } } } @@ -107819,32 +111208,25 @@ static int renameParseSql( const char *zDb, /* Name of schema SQL belongs to */ sqlite3 *db, /* Database handle */ const char *zSql, /* SQL to parse */ - int bTemp, /* True if SQL is from temp schema */ - const char *zDropColumn /* Name of column being dropped */ + int bTemp /* True if SQL is from temp schema */ ){ int rc; - char *zErr = 0; - db->init.iDb = bTemp ? 1 : sqlite3FindDbName(db, zDb); - if( zDropColumn ){ - db->init.bDropColumn = 1; - db->init.azInit = (char**)&zDropColumn; + sqlite3ParseObjectInit(p, db); + if( zSql==0 ){ + return SQLITE_NOMEM; } - - /* Parse the SQL statement passed as the first argument. If no error - ** occurs and the parse does not result in a new table, index or - ** trigger object, the database must be corrupt. */ - memset(p, 0, sizeof(Parse)); + if( sqlite3StrNICmp(zSql,"CREATE ",7)!=0 ){ + return SQLITE_CORRUPT_BKPT; + } + db->init.iDb = bTemp ? 1 : sqlite3FindDbName(db, zDb); p->eParseMode = PARSE_MODE_RENAME; p->db = db; p->nQueryLoop = 1; - rc = zSql ? sqlite3RunParser(p, zSql, &zErr) : SQLITE_NOMEM; - assert( p->zErrMsg==0 ); - assert( rc!=SQLITE_OK || zErr==0 ); - p->zErrMsg = zErr; + rc = sqlite3RunParser(p, zSql); if( db->mallocFailed ) rc = SQLITE_NOMEM; if( rc==SQLITE_OK - && p->pNewTable==0 && p->pNewIndex==0 && p->pNewTrigger==0 + && NEVER(p->pNewTable==0 && p->pNewIndex==0 && p->pNewTrigger==0) ){ rc = SQLITE_CORRUPT_BKPT; } @@ -107862,7 +111244,6 @@ static int renameParseSql( #endif db->init.iDb = 0; - db->init.bDropColumn = 0; return rc; } @@ -107882,51 +111263,76 @@ static int renameEditSql( const char *zNew, /* New token text */ int bQuote /* True to always quote token */ ){ - int nNew = sqlite3Strlen30(zNew); - int nSql = sqlite3Strlen30(zSql); + i64 nNew = sqlite3Strlen30(zNew); + i64 nSql = sqlite3Strlen30(zSql); sqlite3 *db = sqlite3_context_db_handle(pCtx); int rc = SQLITE_OK; - char *zQuot; + char *zQuot = 0; char *zOut; - int nQuot; + i64 nQuot = 0; + char *zBuf1 = 0; + char *zBuf2 = 0; - /* Set zQuot to point to a buffer containing a quoted copy of the - ** identifier zNew. If the corresponding identifier in the original - ** ALTER TABLE statement was quoted (bQuote==1), then set zNew to - ** point to zQuot so that all substitutions are made using the - ** quoted version of the new column name. */ - zQuot = sqlite3MPrintf(db, "\"%w\"", zNew); - if( zQuot==0 ){ - return SQLITE_NOMEM; + if( zNew ){ + /* Set zQuot to point to a buffer containing a quoted copy of the + ** identifier zNew. If the corresponding identifier in the original + ** ALTER TABLE statement was quoted (bQuote==1), then set zNew to + ** point to zQuot so that all substitutions are made using the + ** quoted version of the new column name. */ + zQuot = sqlite3MPrintf(db, "\"%w\" ", zNew); + if( zQuot==0 ){ + return SQLITE_NOMEM; + }else{ + nQuot = sqlite3Strlen30(zQuot)-1; + } + + assert( nQuot>=nNew ); + zOut = sqlite3DbMallocZero(db, nSql + pRename->nList*nQuot + 1); }else{ - nQuot = sqlite3Strlen30(zQuot); - } - if( bQuote ){ - zNew = zQuot; - nNew = nQuot; + zOut = (char*)sqlite3DbMallocZero(db, (nSql*2+1) * 3); + if( zOut ){ + zBuf1 = &zOut[nSql*2+1]; + zBuf2 = &zOut[nSql*4+2]; + } } /* At this point pRename->pList contains a list of RenameToken objects ** corresponding to all tokens in the input SQL that must be replaced - ** with the new column name. All that remains is to construct and - ** return the edited SQL string. */ - assert( nQuot>=nNew ); - zOut = sqlite3DbMallocZero(db, nSql + pRename->nList*nQuot + 1); + ** with the new column name, or with single-quoted versions of themselves. + ** All that remains is to construct and return the edited SQL string. */ if( zOut ){ int nOut = nSql; memcpy(zOut, zSql, nSql); while( pRename->pList ){ int iOff; /* Offset of token to replace in zOut */ - RenameToken *pBest = renameColumnTokenNext(pRename); - u32 nReplace; const char *zReplace; - if( sqlite3IsIdChar(*pBest->t.z) ){ - nReplace = nNew; - zReplace = zNew; + RenameToken *pBest = renameColumnTokenNext(pRename); + + if( zNew ){ + if( bQuote==0 && sqlite3IsIdChar(*pBest->t.z) ){ + nReplace = nNew; + zReplace = zNew; + }else{ + nReplace = nQuot; + zReplace = zQuot; + if( pBest->t.z[pBest->t.n]=='"' ) nReplace++; + } }else{ - nReplace = nQuot; - zReplace = zQuot; + /* Dequote the double-quoted token. Then requote it again, this time + ** using single quotes. If the character immediately following the + ** original token within the input SQL was a single quote ('), then + ** add another space after the new, single-quoted version of the + ** token. This is so that (SELECT "string"'alias') maps to + ** (SELECT 'string' 'alias'), and not (SELECT 'string''alias'). */ + memcpy(zBuf1, pBest->t.z, pBest->t.n); + zBuf1[pBest->t.n] = 0; + sqlite3Dequote(zBuf1); + sqlite3_snprintf(nSql*2, zBuf2, "%Q%s", zBuf1, + pBest->t.z[pBest->t.n]=='\'' ? " " : "" + ); + zReplace = zBuf2; + nReplace = sqlite3Strlen30(zReplace); } iOff = pBest->t.z - zSql; @@ -107990,26 +111396,35 @@ static int renameResolveTrigger(Parse *pParse){ if( rc==SQLITE_OK && pStep->zTarget ){ SrcList *pSrc = sqlite3TriggerStepSrc(pParse, pStep); if( pSrc ){ - int i; - for(i=0; inSrc && rc==SQLITE_OK; i++){ - SrcItem *p = &pSrc->a[i]; - p->iCursor = pParse->nTab++; - if( p->pSelect ){ - sqlite3SelectPrep(pParse, p->pSelect, 0); - sqlite3ExpandSubquery(pParse, p); - assert( i>0 ); - assert( pStep->pFrom->a[i-1].pSelect ); - sqlite3SelectPrep(pParse, pStep->pFrom->a[i-1].pSelect, 0); - }else{ - p->pTab = sqlite3LocateTableItem(pParse, 0, p); - if( p->pTab==0 ){ - rc = SQLITE_ERROR; - }else{ - p->pTab->nTabRef++; - rc = sqlite3ViewGetColumnNames(pParse, p->pTab); + Select *pSel = sqlite3SelectNew( + pParse, pStep->pExprList, pSrc, 0, 0, 0, 0, 0, 0 + ); + if( pSel==0 ){ + pStep->pExprList = 0; + pSrc = 0; + rc = SQLITE_NOMEM; + }else{ + sqlite3SelectPrep(pParse, pSel, 0); + rc = pParse->nErr ? SQLITE_ERROR : SQLITE_OK; + assert( pStep->pExprList==0 || pStep->pExprList==pSel->pEList ); + assert( pSrc==pSel->pSrc ); + if( pStep->pExprList ) pSel->pEList = 0; + pSel->pSrc = 0; + sqlite3SelectDelete(db, pSel); + } + if( pStep->pFrom ){ + int i; + for(i=0; ipFrom->nSrc && rc==SQLITE_OK; i++){ + SrcItem *p = &pStep->pFrom->a[i]; + if( p->pSelect ){ + sqlite3SelectPrep(pParse, p->pSelect, 0); } } } + + if( db->mallocFailed ){ + rc = SQLITE_NOMEM; + } sNC.pSrcList = pSrc; if( rc==SQLITE_OK && pStep->pWhere ){ rc = sqlite3ResolveExprNames(&sNC, pStep->pWhere); @@ -108095,13 +111510,13 @@ static void renameParseCleanup(Parse *pParse){ sqlite3DeleteTrigger(db, pParse->pNewTrigger); sqlite3DbFree(db, pParse->zErrMsg); renameTokenFree(db, pParse->pRename); - sqlite3ParserReset(pParse); + sqlite3ParseObjectReset(pParse); } /* ** SQL function: ** -** sqlite_rename_column(zSql, iCol, bQuote, zNew, zTable, zOld) +** sqlite_rename_column(SQL,TYPE,OBJ,DB,TABLE,COL,NEWNAME,QUOTE,TEMP) ** ** 0. zSql: SQL statement to rewrite ** 1. type: Type of object ("table", "view" etc.) @@ -108119,7 +111534,8 @@ static void renameParseCleanup(Parse *pParse){ ** ** This function is used internally by the ALTER TABLE RENAME COLUMN command. ** It is only accessible to SQL created using sqlite3NestedParse(). It is -** not reachable from ordinary SQL passed into sqlite3_prepare(). +** not reachable from ordinary SQL passed into sqlite3_prepare() unless the +** SQLITE_TESTCTRL_INTERNAL_FUNCTIONS test setting is enabled. */ static void renameColumnFunc( sqlite3_context *context, @@ -108157,14 +111573,14 @@ static void renameColumnFunc( sqlite3BtreeLeaveAll(db); return; } - zOld = pTab->aCol[iCol].zName; + zOld = pTab->aCol[iCol].zCnName; memset(&sCtx, 0, sizeof(sCtx)); sCtx.iCol = ((iCol==pTab->iPKey) ? -1 : iCol); #ifndef SQLITE_OMIT_AUTHORIZATION db->xAuth = 0; #endif - rc = renameParseSql(&sParse, zDb, db, zSql, bTemp, 0); + rc = renameParseSql(&sParse, zDb, db, zSql, bTemp); /* Find tokens that need to be replaced. */ memset(&sWalker, 0, sizeof(Walker)); @@ -108176,8 +111592,8 @@ static void renameColumnFunc( sCtx.pTab = pTab; if( rc!=SQLITE_OK ) goto renameColumnFunc_done; if( sParse.pNewTable ){ - Select *pSelect = sParse.pNewTable->pSelect; - if( pSelect ){ + if( IsView(sParse.pNewTable) ){ + Select *pSelect = sParse.pNewTable->u.view.pSelect; pSelect->selFlags &= ~SF_View; sParse.rc = SQLITE_OK; sqlite3SelectPrep(&sParse, pSelect, 0); @@ -108186,16 +111602,17 @@ static void renameColumnFunc( sqlite3WalkSelect(&sWalker, pSelect); } if( rc!=SQLITE_OK ) goto renameColumnFunc_done; - }else{ + }else if( IsOrdinaryTable(sParse.pNewTable) ){ /* A regular table */ int bFKOnly = sqlite3_stricmp(zTable, sParse.pNewTable->zName); FKey *pFKey; - assert( sParse.pNewTable->pSelect==0 ); sCtx.pTab = sParse.pNewTable; if( bFKOnly==0 ){ - renameTokenFind( - &sParse, &sCtx, (void*)sParse.pNewTable->aCol[iCol].zName - ); + if( iColnCol ){ + renameTokenFind( + &sParse, &sCtx, (void*)sParse.pNewTable->aCol[iCol].zCnName + ); + } if( sCtx.iCol<0 ){ renameTokenFind(&sParse, &sCtx, (void*)&sParse.pNewTable->iPKey); } @@ -108208,12 +111625,15 @@ static void renameColumnFunc( } #ifndef SQLITE_OMIT_GENERATED_COLUMNS for(i=0; inCol; i++){ - sqlite3WalkExpr(&sWalker, sParse.pNewTable->aCol[i].pDflt); + Expr *pExpr = sqlite3ColumnExpr(sParse.pNewTable, + &sParse.pNewTable->aCol[i]); + sqlite3WalkExpr(&sWalker, pExpr); } #endif } - for(pFKey=sParse.pNewTable->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + assert( IsOrdinaryTable(sParse.pNewTable) ); + for(pFKey=sParse.pNewTable->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ for(i=0; inCol; i++){ if( bFKOnly==0 && pFKey->aCol[i].iFrom==iCol ){ renameTokenFind(&sParse, &sCtx, (void*)&pFKey->aCol[i]); @@ -108264,7 +111684,9 @@ static void renameColumnFunc( renameColumnFunc_done: if( rc!=SQLITE_OK ){ - if( sParse.zErrMsg ){ + if( rc==SQLITE_ERROR && sqlite3WritableSchema(db) ){ + sqlite3_result_value(context, argv[0]); + }else if( sParse.zErrMsg ){ renameColumnParseError(context, "", argv[1], argv[2], &sParse); }else{ sqlite3_result_error_code(context, rc); @@ -108284,7 +111706,10 @@ renameColumnFunc_done: */ static int renameTableExprCb(Walker *pWalker, Expr *pExpr){ RenameCtx *p = pWalker->u.pRename; - if( pExpr->op==TK_COLUMN && p->pTab==pExpr->y.pTab ){ + if( pExpr->op==TK_COLUMN + && ALWAYS(ExprUseYTab(pExpr)) + && p->pTab==pExpr->y.pTab + ){ renameTokenFind(pWalker->pParse, p, (void*)&pExpr->y.pTab); } return WRC_Continue; @@ -108297,8 +111722,12 @@ static int renameTableSelectCb(Walker *pWalker, Select *pSelect){ int i; RenameCtx *p = pWalker->u.pRename; SrcList *pSrc = pSelect->pSrc; - if( pSelect->selFlags & SF_View ) return WRC_Prune; - if( pSrc==0 ){ + if( pSelect->selFlags & (SF_View|SF_CopyCte) ){ + testcase( pSelect->selFlags & SF_View ); + testcase( pSelect->selFlags & SF_CopyCte ); + return WRC_Prune; + } + if( NEVER(pSrc==0) ){ assert( pWalker->pParse->db->mallocFailed ); return WRC_Abort; } @@ -108368,35 +111797,38 @@ static void renameTableFunc( sWalker.xSelectCallback = renameTableSelectCb; sWalker.u.pRename = &sCtx; - rc = renameParseSql(&sParse, zDb, db, zInput, bTemp, 0); + rc = renameParseSql(&sParse, zDb, db, zInput, bTemp); if( rc==SQLITE_OK ){ int isLegacy = (db->flags & SQLITE_LegacyAlter); if( sParse.pNewTable ){ Table *pTab = sParse.pNewTable; - if( pTab->pSelect ){ + if( IsView(pTab) ){ if( isLegacy==0 ){ - Select *pSelect = pTab->pSelect; + Select *pSelect = pTab->u.view.pSelect; NameContext sNC; memset(&sNC, 0, sizeof(sNC)); sNC.pParse = &sParse; assert( pSelect->selFlags & SF_View ); pSelect->selFlags &= ~SF_View; - sqlite3SelectPrep(&sParse, pTab->pSelect, &sNC); + sqlite3SelectPrep(&sParse, pTab->u.view.pSelect, &sNC); if( sParse.nErr ){ rc = sParse.rc; }else{ - sqlite3WalkSelect(&sWalker, pTab->pSelect); + sqlite3WalkSelect(&sWalker, pTab->u.view.pSelect); } } }else{ /* Modify any FK definitions to point to the new table. */ #ifndef SQLITE_OMIT_FOREIGN_KEY - if( isLegacy==0 || (db->flags & SQLITE_ForeignKeys) ){ + if( (isLegacy==0 || (db->flags & SQLITE_ForeignKeys)) + && !IsVirtual(pTab) + ){ FKey *pFKey; - for(pFKey=pTab->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + assert( IsOrdinaryTable(pTab) ); + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ if( sqlite3_stricmp(pFKey->zTo, zOld)==0 ){ renameTokenFind(&sParse, &sCtx, (void*)pFKey->zTo); } @@ -108442,6 +111874,15 @@ static void renameTableFunc( if( pStep->zTarget && 0==sqlite3_stricmp(pStep->zTarget, zOld) ){ renameTokenFind(&sParse, &sCtx, pStep->zTarget); } + if( pStep->pFrom ){ + int i; + for(i=0; ipFrom->nSrc; i++){ + SrcItem *pItem = &pStep->pFrom->a[i]; + if( 0==sqlite3_stricmp(pItem->zName, zOld) ){ + renameTokenFind(&sParse, &sCtx, pItem->zName); + } + } + } } } } @@ -108453,7 +111894,9 @@ static void renameTableFunc( rc = renameEditSql(context, &sCtx, zInput, zNew, bQuote); } if( rc!=SQLITE_OK ){ - if( sParse.zErrMsg ){ + if( rc==SQLITE_ERROR && sqlite3WritableSchema(db) ){ + sqlite3_result_value(context, argv[3]); + }else if( sParse.zErrMsg ){ renameColumnParseError(context, "", argv[1], argv[2], &sParse); }else{ sqlite3_result_error_code(context, rc); @@ -108471,7 +111914,131 @@ static void renameTableFunc( return; } -/* +static int renameQuotefixExprCb(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_STRING && (pExpr->flags & EP_DblQuoted) ){ + renameTokenFind(pWalker->pParse, pWalker->u.pRename, (const void*)pExpr); + } + return WRC_Continue; +} + +/* SQL function: sqlite_rename_quotefix(DB,SQL) +** +** Rewrite the DDL statement "SQL" so that any string literals that use +** double-quotes use single quotes instead. +** +** Two arguments must be passed: +** +** 0: Database name ("main", "temp" etc.). +** 1: SQL statement to edit. +** +** The returned value is the modified SQL statement. For example, given +** the database schema: +** +** CREATE TABLE t1(a, b, c); +** +** SELECT sqlite_rename_quotefix('main', +** 'CREATE VIEW v1 AS SELECT "a", "string" FROM t1' +** ); +** +** returns the string: +** +** CREATE VIEW v1 AS SELECT "a", 'string' FROM t1 +** +** If there is a error in the input SQL, then raise an error, except +** if PRAGMA writable_schema=ON, then just return the input string +** unmodified following an error. +*/ +static void renameQuotefixFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + char const *zDb = (const char*)sqlite3_value_text(argv[0]); + char const *zInput = (const char*)sqlite3_value_text(argv[1]); + +#ifndef SQLITE_OMIT_AUTHORIZATION + sqlite3_xauth xAuth = db->xAuth; + db->xAuth = 0; +#endif + + sqlite3BtreeEnterAll(db); + + UNUSED_PARAMETER(NotUsed); + if( zDb && zInput ){ + int rc; + Parse sParse; + rc = renameParseSql(&sParse, zDb, db, zInput, 0); + + if( rc==SQLITE_OK ){ + RenameCtx sCtx; + Walker sWalker; + + /* Walker to find tokens that need to be replaced. */ + memset(&sCtx, 0, sizeof(RenameCtx)); + memset(&sWalker, 0, sizeof(Walker)); + sWalker.pParse = &sParse; + sWalker.xExprCallback = renameQuotefixExprCb; + sWalker.xSelectCallback = renameColumnSelectCb; + sWalker.u.pRename = &sCtx; + + if( sParse.pNewTable ){ + if( IsView(sParse.pNewTable) ){ + Select *pSelect = sParse.pNewTable->u.view.pSelect; + pSelect->selFlags &= ~SF_View; + sParse.rc = SQLITE_OK; + sqlite3SelectPrep(&sParse, pSelect, 0); + rc = (db->mallocFailed ? SQLITE_NOMEM : sParse.rc); + if( rc==SQLITE_OK ){ + sqlite3WalkSelect(&sWalker, pSelect); + } + }else{ + int i; + sqlite3WalkExprList(&sWalker, sParse.pNewTable->pCheck); +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + for(i=0; inCol; i++){ + sqlite3WalkExpr(&sWalker, + sqlite3ColumnExpr(sParse.pNewTable, + &sParse.pNewTable->aCol[i])); + } +#endif /* SQLITE_OMIT_GENERATED_COLUMNS */ + } + }else if( sParse.pNewIndex ){ + sqlite3WalkExprList(&sWalker, sParse.pNewIndex->aColExpr); + sqlite3WalkExpr(&sWalker, sParse.pNewIndex->pPartIdxWhere); + }else{ +#ifndef SQLITE_OMIT_TRIGGER + rc = renameResolveTrigger(&sParse); + if( rc==SQLITE_OK ){ + renameWalkTrigger(&sWalker, sParse.pNewTrigger); + } +#endif /* SQLITE_OMIT_TRIGGER */ + } + + if( rc==SQLITE_OK ){ + rc = renameEditSql(context, &sCtx, zInput, 0, 0); + } + renameTokenFree(db, sCtx.pList); + } + if( rc!=SQLITE_OK ){ + if( sqlite3WritableSchema(db) && rc==SQLITE_ERROR ){ + sqlite3_result_value(context, argv[1]); + }else{ + sqlite3_result_error_code(context, rc); + } + } + renameParseCleanup(&sParse); + } + +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = xAuth; +#endif + + sqlite3BtreeLeaveAll(db); +} + +/* Function: sqlite_rename_test(DB,SQL,TYPE,NAME,ISTEMP,WHEN,DQS) +** ** An SQL user function that checks that there are no parse or symbol ** resolution problems in a CREATE TRIGGER|TABLE|VIEW|INDEX statement. ** After an ALTER TABLE .. RENAME operation is performed and the schema @@ -108484,13 +112051,15 @@ static void renameTableFunc( ** 3: Object name. ** 4: True if object is from temp schema. ** 5: "when" part of error message. -** 6: Name of column being dropped, or NULL. +** 6: True to disable the DQS quirk when parsing SQL. ** -** Unless it finds an error, this function normally returns NULL. However, it -** returns integer value 1 if: +** The return value is computed as follows: ** -** * the SQL argument creates a trigger, and -** * the table that the trigger is attached to is in database zDb. +** A. If an error is seen and not in PRAGMA writable_schema=ON mode, +** then raise the error. +** B. Else if a trigger is created and the the table that the trigger is +** attached to is in database zDb, then return 1. +** C. Otherwise return NULL. */ static void renameTableTest( sqlite3_context *context, @@ -108503,7 +112072,7 @@ static void renameTableTest( int bTemp = sqlite3_value_int(argv[4]); int isLegacy = (db->flags & SQLITE_LegacyAlter); char const *zWhen = (const char*)sqlite3_value_text(argv[5]); - char const *zDropColumn = (const char*)sqlite3_value_text(argv[6]); + int bNoDQS = sqlite3_value_int(argv[6]); #ifndef SQLITE_OMIT_AUTHORIZATION sqlite3_xauth xAuth = db->xAuth; @@ -108511,16 +112080,20 @@ static void renameTableTest( #endif UNUSED_PARAMETER(NotUsed); + if( zDb && zInput ){ int rc; Parse sParse; - rc = renameParseSql(&sParse, zDb, db, zInput, bTemp, zDropColumn); + int flags = db->flags; + if( bNoDQS ) db->flags &= ~(SQLITE_DqsDML|SQLITE_DqsDDL); + rc = renameParseSql(&sParse, zDb, db, zInput, bTemp); + db->flags |= (flags & (SQLITE_DqsDML|SQLITE_DqsDDL)); if( rc==SQLITE_OK ){ - if( isLegacy==0 && sParse.pNewTable && sParse.pNewTable->pSelect ){ + if( isLegacy==0 && sParse.pNewTable && IsView(sParse.pNewTable) ){ NameContext sNC; memset(&sNC, 0, sizeof(sNC)); sNC.pParse = &sParse; - sqlite3SelectPrep(&sParse, sParse.pNewTable->pSelect, &sNC); + sqlite3SelectPrep(&sParse, sParse.pNewTable->u.view.pSelect, &sNC); if( sParse.nErr ) rc = sParse.rc; } @@ -108531,12 +112104,16 @@ static void renameTableTest( if( rc==SQLITE_OK ){ int i1 = sqlite3SchemaToIndex(db, sParse.pNewTrigger->pTabSchema); int i2 = sqlite3FindDbName(db, zDb); - if( i1==i2 ) sqlite3_result_int(context, 1); + if( i1==i2 ){ + /* Handle output case B */ + sqlite3_result_int(context, 1); + } } } } - if( rc!=SQLITE_OK && zWhen ){ + if( rc!=SQLITE_OK && zWhen && !sqlite3WritableSchema(db) ){ + /* Output case A */ renameColumnParseError(context, zWhen, argv[2], argv[3],&sParse); } renameParseCleanup(&sParse); @@ -108582,7 +112159,7 @@ static void dropColumnFunc( #endif UNUSED_PARAMETER(NotUsed); - rc = renameParseSql(&sParse, zDb, db, zSql, iSchema==1, 0); + rc = renameParseSql(&sParse, zDb, db, zSql, iSchema==1); if( rc!=SQLITE_OK ) goto drop_column_done; pTab = sParse.pNewTable; if( pTab==0 || pTab->nCol==1 || iCol>=pTab->nCol ){ @@ -108591,13 +112168,14 @@ static void dropColumnFunc( goto drop_column_done; } - pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol].zName); + pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol].zCnName); if( iColnCol-1 ){ RenameToken *pEnd; - pEnd = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol+1].zName); + pEnd = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol+1].zCnName); zEnd = (const char*)pEnd->t.z; }else{ - zEnd = (const char*)&zSql[pTab->addColOffset]; + assert( IsOrdinaryTable(pTab) ); + zEnd = (const char*)&zSql[pTab->u.tab.addColOffset]; while( ALWAYS(pCol->t.z[0]!=0) && pCol->t.z[0]!=',' ) pCol->t.z--; } @@ -108623,7 +112201,7 @@ drop_column_done: ** statement. Argument pSrc contains the possibly qualified name of the ** table being edited, and token pName the name of the column to drop. */ -SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, Token *pName){ +SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const Token *pName){ sqlite3 *db = pParse->db; /* Database handle */ Table *pTab; /* Table to modify */ int iDb; /* Index of db containing pTab in aDb[] */ @@ -108651,7 +112229,7 @@ SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, Token * } iCol = sqlite3ColumnIndex(pTab, zCol); if( iCol<0 ){ - sqlite3ErrorMsg(pParse, "no such column: \"%s\"", zCol); + sqlite3ErrorMsg(pParse, "no such column: \"%T\"", pName); goto exit_drop_column; } @@ -108675,9 +112253,16 @@ SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, Token * iDb = sqlite3SchemaToIndex(db, pTab->pSchema); assert( iDb>=0 ); zDb = db->aDb[iDb].zDbSName; +#ifndef SQLITE_OMIT_AUTHORIZATION + /* Invoke the authorization callback. */ + if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, zCol) ){ + goto exit_drop_column; + } +#endif renameTestSchema(pParse, zDb, iDb==1, "", 0); + renameFixQuotes(pParse, zDb, iDb==1); sqlite3NestedParse(pParse, - "UPDATE \"%w\"." DFLT_SCHEMA_TABLE " SET " + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " "sql = sqlite_drop_column(%d, sql, %d) " "WHERE (type=='table' AND tbl_name=%Q COLLATE nocase)" , zDb, iDb, iCol, pTab->zName @@ -108685,7 +112270,7 @@ SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, Token * /* Drop and reload the database schema. */ renameReloadSchema(pParse, iDb, INITFLAG_AlterDrop); - renameTestSchema(pParse, zDb, iDb==1, "after drop column", zCol); + renameTestSchema(pParse, zDb, iDb==1, "after drop column", 1); /* Edit rows of table on disk */ if( pParse->nErr==0 && (pTab->aCol[iCol].colFlags & COLFLAG_VIRTUAL)==0 ){ @@ -108701,33 +112286,50 @@ SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, Token * sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenWrite); addr = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v); reg = ++pParse->nMem; - pParse->nMem += pTab->nCol; if( HasRowid(pTab) ){ sqlite3VdbeAddOp2(v, OP_Rowid, iCur, reg); + pParse->nMem += pTab->nCol; }else{ pPk = sqlite3PrimaryKeyIndex(pTab); + pParse->nMem += pPk->nColumn; + for(i=0; inKeyCol; i++){ + sqlite3VdbeAddOp3(v, OP_Column, iCur, i, reg+i+1); + } + nField = pPk->nKeyCol; } + regRec = ++pParse->nMem; for(i=0; inCol; i++){ if( i!=iCol && (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){ int regOut; if( pPk ){ int iPos = sqlite3TableColumnToIndex(pPk, i); int iColPos = sqlite3TableColumnToIndex(pPk, iCol); + if( iPosnKeyCol ) continue; regOut = reg+1+iPos-(iPos>iColPos); }else{ regOut = reg+1+nField; } - sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, i, regOut); + if( i==pTab->iPKey ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, regOut); + }else{ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, i, regOut); + } nField++; } } - regRec = reg + pTab->nCol; + if( nField==0 ){ + /* dbsqlfuzz 5f09e7bcc78b4954d06bf9f2400d7715f48d1fef */ + pParse->nMem++; + sqlite3VdbeAddOp2(v, OP_Null, 0, reg+1); + nField = 1; + } sqlite3VdbeAddOp3(v, OP_MakeRecord, reg+1, nField, regRec); if( pPk ){ sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iCur, regRec, reg+1, pPk->nKeyCol); }else{ sqlite3VdbeAddOp3(v, OP_Insert, iCur, regRec, reg); } + sqlite3VdbeChangeP5(v, OPFLAG_SAVEPOSITION); sqlite3VdbeAddOp2(v, OP_Next, iCur, addr+1); VdbeCoverage(v); sqlite3VdbeJumpHere(v, addr); @@ -108747,6 +112349,7 @@ SQLITE_PRIVATE void sqlite3AlterFunctions(void){ INTERNAL_FUNCTION(sqlite_rename_table, 7, renameTableFunc), INTERNAL_FUNCTION(sqlite_rename_test, 7, renameTableTest), INTERNAL_FUNCTION(sqlite_drop_column, 3, dropColumnFunc), + INTERNAL_FUNCTION(sqlite_rename_quotefix,2, renameQuotefixFunc), }; sqlite3InsertBuiltinFuncs(aAlterTableFuncs, ArraySize(aAlterTableFuncs)); } @@ -109189,7 +112792,6 @@ static void statInit( + sizeof(tRowcnt)*3*nColUp*(nCol+mxSample); } #endif - db = sqlite3_context_db_handle(context); p = sqlite3DbMallocZero(db, n); if( p==0 ){ sqlite3_result_error_nomem(context); @@ -109604,32 +113206,29 @@ static void statGet( ** * "WHERE a=? AND b=?" matches 2 rows. ** ** If D is the count of distinct values and K is the total number of - ** rows, then each estimate is computed as: + ** rows, then each estimate is usually computed as: ** ** I = (K+D-1)/D + ** + ** In other words, I is K/D rounded up to the next whole integer. + ** However, if I is between 1.0 and 1.1 (in other words if I is + ** close to 1.0 but just a little larger) then do not round up but + ** instead keep the I value at 1.0. */ - char *z; - int i; + sqlite3_str sStat; /* Text of the constructed "stat" line */ + int i; /* Loop counter */ - char *zRet = sqlite3MallocZero( (p->nKeyCol+1)*25 ); - if( zRet==0 ){ - sqlite3_result_error_nomem(context); - return; - } - - sqlite3_snprintf(24, zRet, "%llu", + sqlite3StrAccumInit(&sStat, 0, 0, 0, (p->nKeyCol+1)*100); + sqlite3_str_appendf(&sStat, "%llu", p->nSkipAhead ? (u64)p->nEst : (u64)p->nRow); - z = zRet + sqlite3Strlen30(zRet); for(i=0; inKeyCol; i++){ u64 nDistinct = p->current.anDLt[i] + 1; u64 iVal = (p->nRow + nDistinct - 1) / nDistinct; - sqlite3_snprintf(24, z, " %llu", iVal); - z += sqlite3Strlen30(z); + if( iVal==2 && p->nRow*10 <= nDistinct*11 ) iVal = 1; + sqlite3_str_appendf(&sStat, " %llu", iVal); assert( p->current.anEq[i] ); } - assert( z[0]=='\0' && z>zRet ); - - sqlite3_result_text(context, zRet, -1, sqlite3_free); + sqlite3ResultStrAccum(context, &sStat); } #ifdef SQLITE_ENABLE_STAT4 else if( eCall==STAT_GET_ROWID ){ @@ -109648,6 +113247,8 @@ static void statGet( } }else{ tRowcnt *aCnt = 0; + sqlite3_str sStat; + int i; assert( p->iGetnSample ); switch( eCall ){ @@ -109659,23 +113260,12 @@ static void statGet( break; } } - - { - char *zRet = sqlite3MallocZero(p->nCol * 25); - if( zRet==0 ){ - sqlite3_result_error_nomem(context); - }else{ - int i; - char *z = zRet; - for(i=0; inCol; i++){ - sqlite3_snprintf(24, z, "%llu ", (u64)aCnt[i]); - z += sqlite3Strlen30(z); - } - assert( z[0]=='\0' && z>zRet ); - z[-1] = '\0'; - sqlite3_result_text(context, zRet, -1, sqlite3_free); - } + sqlite3StrAccumInit(&sStat, 0, 0, 0, p->nCol*100); + for(i=0; inCol; i++){ + sqlite3_str_appendf(&sStat, "%llu ", (u64)aCnt[i]); } + if( sStat.nChar ) sStat.nChar--; + sqlite3ResultStrAccum(context, &sStat); } #endif /* SQLITE_ENABLE_STAT4 */ #ifndef SQLITE_DEBUG @@ -109724,7 +113314,7 @@ static void analyzeVdbeCommentIndexWithColumnName( }else if( i==XN_EXPR ){ VdbeComment((v,"%s.expr(%d)",pIdx->zName, k)); }else{ - VdbeComment((v,"%s.%s", pIdx->zName, pIdx->pTable->aCol[i].zName)); + VdbeComment((v,"%s.%s", pIdx->zName, pIdx->pTable->aCol[i].zCnName)); } } #else @@ -109771,7 +113361,7 @@ static void analyzeOneTable( if( v==0 || NEVER(pTab==0) ){ return; } - if( pTab->tnum==0 ){ + if( !IsOrdinaryTable(pTab) ){ /* Do not gather statistics on views or virtual tables */ return; } @@ -109798,7 +113388,7 @@ static void analyzeOneTable( memcpy(pStat1->zName, "sqlite_stat1", 13); pStat1->nCol = 3; pStat1->iPKey = -1; - sqlite3VdbeAddOp4(pParse->pVdbe, OP_Noop, 0, 0, 0,(char*)pStat1,P4_DYNBLOB); + sqlite3VdbeAddOp4(pParse->pVdbe, OP_Noop, 0, 0, 0,(char*)pStat1,P4_DYNAMIC); } #endif @@ -110596,9 +114186,12 @@ static int loadStatTbl( */ static int loadStat4(sqlite3 *db, const char *zDb){ int rc = SQLITE_OK; /* Result codes from subroutines */ + const Table *pStat4; assert( db->lookaside.bDisable ); - if( sqlite3FindTable(db, "sqlite_stat4", zDb) ){ + if( (pStat4 = sqlite3FindTable(db, "sqlite_stat4", zDb))!=0 + && IsOrdinaryTable(pStat4) + ){ rc = loadStatTbl(db, "SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx", "SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat4", @@ -110635,6 +114228,7 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ char *zSql; int rc = SQLITE_OK; Schema *pSchema = db->aDb[iDb].pSchema; + const Table *pStat1; assert( iDb>=0 && iDbnDb ); assert( db->aDb[iDb].pBt!=0 ); @@ -110657,7 +114251,9 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ /* Load new statistics out of the sqlite_stat1 table */ sInfo.db = db; sInfo.zDatabase = db->aDb[iDb].zDbSName; - if( sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)!=0 ){ + if( (pStat1 = sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)) + && IsOrdinaryTable(pStat1) + ){ zSql = sqlite3MPrintf(db, "SELECT tbl,idx,stat FROM %Q.sqlite_stat1", sInfo.zDatabase); if( zSql==0 ){ @@ -110797,7 +114393,7 @@ static void attachFunc( if( zFile==0 ) zFile = ""; if( zName==0 ) zName = ""; -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE # define REOPEN_AS_MEMDB(db) (db->init.reopenMemdb) #else # define REOPEN_AS_MEMDB(db) (0) @@ -111048,17 +114644,18 @@ static void codeAttach( sName.pParse = pParse; if( - SQLITE_OK!=(rc = resolveAttachExpr(&sName, pFilename)) || - SQLITE_OK!=(rc = resolveAttachExpr(&sName, pDbname)) || - SQLITE_OK!=(rc = resolveAttachExpr(&sName, pKey)) + SQLITE_OK!=resolveAttachExpr(&sName, pFilename) || + SQLITE_OK!=resolveAttachExpr(&sName, pDbname) || + SQLITE_OK!=resolveAttachExpr(&sName, pKey) ){ goto attach_end; } #ifndef SQLITE_OMIT_AUTHORIZATION - if( pAuthArg ){ + if( ALWAYS(pAuthArg) ){ char *zAuthArg; if( pAuthArg->op==TK_STRING ){ + assert( !ExprHasProperty(pAuthArg, EP_IntValue) ); zAuthArg = pAuthArg->u.zToken; }else{ zAuthArg = 0; @@ -111166,19 +114763,26 @@ static int fixSelectCb(Walker *p, Select *pSelect){ if( NEVER(pList==0) ) return WRC_Continue; for(i=0, pItem=pList->a; inSrc; i++, pItem++){ if( pFix->bTemp==0 ){ - if( pItem->zDatabase && iDb!=sqlite3FindDbName(db, pItem->zDatabase) ){ - sqlite3ErrorMsg(pFix->pParse, - "%s %T cannot reference objects in database %s", - pFix->zType, pFix->pName, pItem->zDatabase); - return WRC_Abort; + if( pItem->zDatabase ){ + if( iDb!=sqlite3FindDbName(db, pItem->zDatabase) ){ + sqlite3ErrorMsg(pFix->pParse, + "%s %T cannot reference objects in database %s", + pFix->zType, pFix->pName, pItem->zDatabase); + return WRC_Abort; + } + sqlite3DbFree(db, pItem->zDatabase); + pItem->zDatabase = 0; + pItem->fg.notCte = 1; } - sqlite3DbFree(db, pItem->zDatabase); - pItem->zDatabase = 0; pItem->pSchema = pFix->pSchema; pItem->fg.fromDDL = 1; } #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) - if( sqlite3WalkExpr(&pFix->w, pList->a[i].pOn) ) return WRC_Abort; + if( pList->a[i].fg.isUsing==0 + && sqlite3WalkExpr(&pFix->w, pList->a[i].u3.pOn) + ){ + return WRC_Abort; + } #endif } if( pSelect->pWith ){ @@ -111213,7 +114817,7 @@ SQLITE_PRIVATE void sqlite3FixInit( pFix->w.pParse = pParse; pFix->w.xExprCallback = fixExprCb; pFix->w.xSelectCallback = fixSelectCb; - pFix->w.xSelectCallback2 = 0; + pFix->w.xSelectCallback2 = sqlite3WalkWinDefnDummyCallback; pFix->w.walkerDepth = 0; pFix->w.eCode = 0; pFix->w.u.pFix = pFix; @@ -111275,14 +114879,16 @@ SQLITE_PRIVATE int sqlite3FixTriggerStep( return 1; } #ifndef SQLITE_OMIT_UPSERT - if( pStep->pUpsert ){ - Upsert *pUp = pStep->pUpsert; - if( sqlite3WalkExprList(&pFix->w, pUp->pUpsertTarget) - || sqlite3WalkExpr(&pFix->w, pUp->pUpsertTargetWhere) - || sqlite3WalkExprList(&pFix->w, pUp->pUpsertSet) - || sqlite3WalkExpr(&pFix->w, pUp->pUpsertWhere) - ){ - return 1; + { + Upsert *pUp; + for(pUp=pStep->pUpsert; pUp; pUp=pUp->pNextUpsert){ + if( sqlite3WalkExprList(&pFix->w, pUp->pUpsertTarget) + || sqlite3WalkExpr(&pFix->w, pUp->pUpsertTargetWhere) + || sqlite3WalkExprList(&pFix->w, pUp->pUpsertSet) + || sqlite3WalkExpr(&pFix->w, pUp->pUpsertWhere) + ){ + return 1; + } } } #endif @@ -111472,10 +115078,10 @@ SQLITE_PRIVATE void sqlite3AuthRead( if( iCol>=0 ){ assert( iColnCol ); - zCol = pTab->aCol[iCol].zName; + zCol = pTab->aCol[iCol].zCnName; }else if( pTab->iPKey>=0 ){ assert( pTab->iPKeynCol ); - zCol = pTab->aCol[pTab->iPKey].zName; + zCol = pTab->aCol[pTab->iPKey].zCnName; }else{ zCol = "ROWID"; } @@ -111614,7 +115220,7 @@ struct TableLock { ** code to make the lock occur is generated by a later call to ** codeTableLocks() which occurs during sqlite3FinishCoding(). */ -SQLITE_PRIVATE void sqlite3TableLock( +static SQLITE_NOINLINE void lockTable( Parse *pParse, /* Parsing context */ int iDb, /* Index of the database containing the table to lock */ Pgno iTab, /* Root page number of the table to be locked */ @@ -111627,8 +115233,6 @@ SQLITE_PRIVATE void sqlite3TableLock( TableLock *p; assert( iDb>=0 ); - if( iDb==1 ) return; - if( !sqlite3BtreeSharable(pParse->db->aDb[iDb].pBt) ) return; pToplevel = sqlite3ParseToplevel(pParse); for(i=0; inTableLock; i++){ p = &pToplevel->aTableLock[i]; @@ -111652,6 +115256,17 @@ SQLITE_PRIVATE void sqlite3TableLock( sqlite3OomFault(pToplevel->db); } } +SQLITE_PRIVATE void sqlite3TableLock( + Parse *pParse, /* Parsing context */ + int iDb, /* Index of the database containing the table to lock */ + Pgno iTab, /* Root page number of the table to be locked */ + u8 isWriteLock, /* True for a write lock */ + const char *zName /* Name of the table to be locked */ +){ + if( iDb==1 ) return; + if( !sqlite3BtreeSharable(pParse->db->aDb[iDb].pBt) ) return; + lockTable(pParse, iDb, iTab, isWriteLock, zName); +} /* ** Code an OP_TableLock instruction for each table locked by the @@ -111702,11 +115317,13 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ assert( pParse->pToplevel==0 ); db = pParse->db; + assert( db->pParse==pParse ); if( pParse->nested ) return; - if( db->mallocFailed || pParse->nErr ){ - if( pParse->rc==SQLITE_OK ) pParse->rc = SQLITE_ERROR; + if( pParse->nErr ){ + if( db->mallocFailed ) pParse->rc = SQLITE_NOMEM; return; } + assert( db->mallocFailed==0 ); /* Begin by generating some termination code at the end of the ** vdbe program @@ -111729,17 +115346,20 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ int i; int reg; - addrRewind = - sqlite3VdbeAddOp1(v, OP_Rewind, pReturning->iRetCur); - VdbeCoverage(v); - reg = pReturning->iRetReg; - for(i=0; inRetCol; i++){ - sqlite3VdbeAddOp3(v, OP_Column, pReturning->iRetCur, i, reg+i); + if( pReturning->nRetCol ){ + sqlite3VdbeAddOp0(v, OP_FkCheck); + addrRewind = + sqlite3VdbeAddOp1(v, OP_Rewind, pReturning->iRetCur); + VdbeCoverage(v); + reg = pReturning->iRetReg; + for(i=0; inRetCol; i++){ + sqlite3VdbeAddOp3(v, OP_Column, pReturning->iRetCur, i, reg+i); + } + sqlite3VdbeAddOp2(v, OP_ResultRow, reg, i); + sqlite3VdbeAddOp2(v, OP_Next, pReturning->iRetCur, addrRewind+1); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addrRewind); } - sqlite3VdbeAddOp2(v, OP_ResultRow, reg, i); - sqlite3VdbeAddOp2(v, OP_Next, pReturning->iRetCur, addrRewind+1); - VdbeCoverage(v); - sqlite3VdbeJumpHere(v, addrRewind); } sqlite3VdbeAddOp0(v, OP_Halt); @@ -111766,7 +115386,9 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ int iDb, i; assert( sqlite3VdbeGetOp(v, 0)->opcode==OP_Init ); sqlite3VdbeJumpHere(v, 0); - for(iDb=0; iDbnDb; iDb++){ + assert( db->nDb>0 ); + iDb = 0; + do{ Schema *pSchema; if( DbMaskTest(pParse->cookieMask, iDb)==0 ) continue; sqlite3VdbeUsesBtree(v, iDb); @@ -111781,7 +115403,7 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ if( db->init.busy==0 ) sqlite3VdbeChangeP5(v, 1); VdbeComment((v, "usesStmtJournal=%d", pParse->mayAbort && pParse->isMultiWrite)); - } + }while( ++iDbnDb ); #ifndef SQLITE_OMIT_VIRTUALTABLE for(i=0; inVtabLock; i++){ char *vtab = (char *)sqlite3GetVTable(db, pParse->apVtabLock[i]); @@ -111820,7 +115442,9 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ if( pParse->bReturning ){ Returning *pRet = pParse->u1.pReturning; - sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRet->iRetCur, pRet->nRetCol); + if( pRet->nRetCol ){ + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRet->iRetCur, pRet->nRetCol); + } } /* Finally, jump back to the beginning of the executable code. */ @@ -111830,7 +115454,9 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ /* Get the VDBE program ready for execution */ - if( v && pParse->nErr==0 && !db->mallocFailed ){ + assert( v!=0 || pParse->nErr ); + assert( db->mallocFailed==0 || pParse->nErr ); + if( pParse->nErr==0 ){ /* A minimum of one cursor is required if autoincrement is used * See ticket [a696379c1f08866] */ assert( pParse->pAinc==0 || pParse->nTab>0 ); @@ -111844,20 +115470,21 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ /* ** Run the parser and code generator recursively in order to generate ** code for the SQL statement given onto the end of the pParse context -** currently under construction. When the parser is run recursively -** this way, the final OP_Halt is not appended and other initialization -** and finalization steps are omitted because those are handling by the -** outermost parser. +** currently under construction. Notes: ** -** Not everything is nestable. This facility is designed to permit -** INSERT, UPDATE, and DELETE operations against the schema table. Use -** care if you decide to try to use this routine for some other purposes. +** * The final OP_Halt is not appended and other initialization +** and finalization steps are omitted because those are handling by the +** outermost parser. +** +** * Built-in SQL functions always take precedence over application-defined +** SQL functions. In other words, it is not possible to override a +** built-in function. */ SQLITE_PRIVATE void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){ va_list ap; char *zSql; - char *zErrMsg = 0; sqlite3 *db = pParse->db; + u32 savedDbFlags = db->mDbFlags; char saveBuf[PARSE_TAIL_SZ]; if( pParse->nErr ) return; @@ -111876,8 +115503,9 @@ SQLITE_PRIVATE void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){ pParse->nested++; memcpy(saveBuf, PARSE_TAIL(pParse), PARSE_TAIL_SZ); memset(PARSE_TAIL(pParse), 0, PARSE_TAIL_SZ); - sqlite3RunParser(pParse, zSql, &zErrMsg); - sqlite3DbFree(db, zErrMsg); + db->mDbFlags |= DBFLAG_PreferBuiltin; + sqlite3RunParser(pParse, zSql); + db->mDbFlags = savedDbFlags; sqlite3DbFree(db, zSql); memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ); pParse->nested--; @@ -111934,17 +115562,17 @@ SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3 *db, const char *zName, const cha p = sqlite3HashFind(&db->aDb[i].pSchema->tblHash, zName); if( p==0 && sqlite3StrNICmp(zName, "sqlite_", 7)==0 ){ if( i==1 ){ - if( sqlite3StrICmp(zName+7, &ALT_TEMP_SCHEMA_TABLE[7])==0 - || sqlite3StrICmp(zName+7, &ALT_SCHEMA_TABLE[7])==0 - || sqlite3StrICmp(zName+7, &DFLT_SCHEMA_TABLE[7])==0 + if( sqlite3StrICmp(zName+7, &PREFERRED_TEMP_SCHEMA_TABLE[7])==0 + || sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 + || sqlite3StrICmp(zName+7, &LEGACY_SCHEMA_TABLE[7])==0 ){ p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, - DFLT_TEMP_SCHEMA_TABLE); + LEGACY_TEMP_SCHEMA_TABLE); } }else{ - if( sqlite3StrICmp(zName+7, &ALT_SCHEMA_TABLE[7])==0 ){ + if( sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 ){ p = sqlite3HashFind(&db->aDb[i].pSchema->tblHash, - DFLT_SCHEMA_TABLE); + LEGACY_SCHEMA_TABLE); } } } @@ -111962,11 +115590,11 @@ SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3 *db, const char *zName, const cha if( p ) break; } if( p==0 && sqlite3StrNICmp(zName, "sqlite_", 7)==0 ){ - if( sqlite3StrICmp(zName+7, &ALT_SCHEMA_TABLE[7])==0 ){ - p = sqlite3HashFind(&db->aDb[0].pSchema->tblHash, DFLT_SCHEMA_TABLE); - }else if( sqlite3StrICmp(zName+7, &ALT_TEMP_SCHEMA_TABLE[7])==0 ){ + if( sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 ){ + p = sqlite3HashFind(&db->aDb[0].pSchema->tblHash, LEGACY_SCHEMA_TABLE); + }else if( sqlite3StrICmp(zName+7, &PREFERRED_TEMP_SCHEMA_TABLE[7])==0 ){ p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, - DFLT_TEMP_SCHEMA_TABLE); + LEGACY_TEMP_SCHEMA_TABLE); } } } @@ -112006,12 +115634,13 @@ SQLITE_PRIVATE Table *sqlite3LocateTable( /* If zName is the not the name of a table in the schema created using ** CREATE, then check to see if it is the name of an virtual table that ** can be an eponymous virtual table. */ - if( pParse->disableVtab==0 ){ + if( pParse->disableVtab==0 && db->init.busy==0 ){ Module *pMod = (Module*)sqlite3HashFind(&db->aModule, zName); if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){ pMod = sqlite3PragmaVtabRegister(db, zName); } if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){ + testcase( pMod->pEpoTab==0 ); return pMod->pEpoTab; } } @@ -112029,6 +115658,8 @@ SQLITE_PRIVATE Table *sqlite3LocateTable( }else{ sqlite3ErrorMsg(pParse, "%s: %s", zMsg, zName); } + }else{ + assert( HasRowid(p) || p->iPKey<0 ); } return p; @@ -112059,6 +115690,22 @@ SQLITE_PRIVATE Table *sqlite3LocateTableItem( return sqlite3LocateTable(pParse, flags, p->zName, zDb); } +/* +** Return the preferred table name for system tables. Translate legacy +** names into the new preferred names, as appropriate. +*/ +SQLITE_PRIVATE const char *sqlite3PreferredTableName(const char *zName){ + if( sqlite3StrNICmp(zName, "sqlite_", 7)==0 ){ + if( sqlite3StrICmp(zName+7, &LEGACY_SCHEMA_TABLE[7])==0 ){ + return PREFERRED_SCHEMA_TABLE; + } + if( sqlite3StrICmp(zName+7, &LEGACY_TEMP_SCHEMA_TABLE[7])==0 ){ + return PREFERRED_TEMP_SCHEMA_TABLE; + } + } + return zName; +} + /* ** Locate the in-memory structure that describes ** a particular index given the name of that index @@ -112223,6 +115870,84 @@ SQLITE_PRIVATE void sqlite3CommitInternalChanges(sqlite3 *db){ db->mDbFlags &= ~DBFLAG_SchemaChange; } +/* +** Set the expression associated with a column. This is usually +** the DEFAULT value, but might also be the expression that computes +** the value for a generated column. +*/ +SQLITE_PRIVATE void sqlite3ColumnSetExpr( + Parse *pParse, /* Parsing context */ + Table *pTab, /* The table containing the column */ + Column *pCol, /* The column to receive the new DEFAULT expression */ + Expr *pExpr /* The new default expression */ +){ + ExprList *pList; + assert( IsOrdinaryTable(pTab) ); + pList = pTab->u.tab.pDfltList; + if( pCol->iDflt==0 + || NEVER(pList==0) + || NEVER(pList->nExpriDflt) + ){ + pCol->iDflt = pList==0 ? 1 : pList->nExpr+1; + pTab->u.tab.pDfltList = sqlite3ExprListAppend(pParse, pList, pExpr); + }else{ + sqlite3ExprDelete(pParse->db, pList->a[pCol->iDflt-1].pExpr); + pList->a[pCol->iDflt-1].pExpr = pExpr; + } +} + +/* +** Return the expression associated with a column. The expression might be +** the DEFAULT clause or the AS clause of a generated column. +** Return NULL if the column has no associated expression. +*/ +SQLITE_PRIVATE Expr *sqlite3ColumnExpr(Table *pTab, Column *pCol){ + if( pCol->iDflt==0 ) return 0; + if( NEVER(!IsOrdinaryTable(pTab)) ) return 0; + if( NEVER(pTab->u.tab.pDfltList==0) ) return 0; + if( NEVER(pTab->u.tab.pDfltList->nExpriDflt) ) return 0; + return pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr; +} + +/* +** Set the collating sequence name for a column. +*/ +SQLITE_PRIVATE void sqlite3ColumnSetColl( + sqlite3 *db, + Column *pCol, + const char *zColl +){ + i64 nColl; + i64 n; + char *zNew; + assert( zColl!=0 ); + n = sqlite3Strlen30(pCol->zCnName) + 1; + if( pCol->colFlags & COLFLAG_HASTYPE ){ + n += sqlite3Strlen30(pCol->zCnName+n) + 1; + } + nColl = sqlite3Strlen30(zColl) + 1; + zNew = sqlite3DbRealloc(db, pCol->zCnName, nColl+n); + if( zNew ){ + pCol->zCnName = zNew; + memcpy(pCol->zCnName + n, zColl, nColl); + pCol->colFlags |= COLFLAG_HASCOLL; + } +} + +/* +** Return the collating squence name for a column +*/ +SQLITE_PRIVATE const char *sqlite3ColumnColl(Column *pCol){ + const char *z; + if( (pCol->colFlags & COLFLAG_HASCOLL)==0 ) return 0; + z = pCol->zCnName; + while( *z ){ z++; } + if( pCol->colFlags & COLFLAG_HASTYPE ){ + do{ z++; }while( *z ); + } + return z+1; +} + /* ** Delete memory allocated for the column names of a table or view (the ** Table.aCol[] array). @@ -112233,12 +115958,20 @@ SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3 *db, Table *pTable){ assert( pTable!=0 ); if( (pCol = pTable->aCol)!=0 ){ for(i=0; inCol; i++, pCol++){ - assert( pCol->zName==0 || pCol->hName==sqlite3StrIHash(pCol->zName) ); - sqlite3DbFree(db, pCol->zName); - sqlite3ExprDelete(db, pCol->pDflt); - sqlite3DbFree(db, pCol->zColl); + assert( pCol->zCnName==0 || pCol->hName==sqlite3StrIHash(pCol->zCnName) ); + sqlite3DbFree(db, pCol->zCnName); } sqlite3DbFree(db, pTable->aCol); + if( IsOrdinaryTable(pTable) ){ + sqlite3ExprListDelete(db, pTable->u.tab.pDfltList); + } + if( db==0 || db->pnBytesFreed==0 ){ + pTable->aCol = 0; + pTable->nCol = 0; + if( IsOrdinaryTable(pTable) ){ + pTable->u.tab.pDfltList = 0; + } + } } } @@ -112290,19 +116023,25 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){ sqlite3FreeIndex(db, pIndex); } - /* Delete any foreign keys attached to this table. */ - sqlite3FkDelete(db, pTable); + if( IsOrdinaryTable(pTable) ){ + sqlite3FkDelete(db, pTable); + } +#ifndef SQLITE_OMIT_VIRTUAL_TABLE + else if( IsVirtual(pTable) ){ + sqlite3VtabClear(db, pTable); + } +#endif + else{ + assert( IsView(pTable) ); + sqlite3SelectDelete(db, pTable->u.view.pSelect); + } /* Delete the Table structure itself. */ sqlite3DeleteColumnNames(db, pTable); sqlite3DbFree(db, pTable->zName); sqlite3DbFree(db, pTable->zColAff); - sqlite3SelectDelete(db, pTable->pSelect); sqlite3ExprListDelete(db, pTable->pCheck); -#ifndef SQLITE_OMIT_VIRTUALTABLE - sqlite3VtabClear(db, pTable); -#endif sqlite3DbFree(db, pTable); /* Verify that no lookaside memory was used by schema tables */ @@ -112348,10 +116087,10 @@ SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3 *db, int iDb, const char ** are not \000 terminated and are not persistent. The returned string ** is \000 terminated and is persistent. */ -SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3 *db, Token *pName){ +SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3 *db, const Token *pName){ char *zName; if( pName ){ - zName = sqlite3DbStrNDup(db, (char*)pName->z, pName->n); + zName = sqlite3DbStrNDup(db, (const char*)pName->z, pName->n); sqlite3Dequote(zName); }else{ zName = 0; @@ -112365,7 +116104,7 @@ SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3 *db, Token *pName){ */ SQLITE_PRIVATE void sqlite3OpenSchemaTable(Parse *p, int iDb){ Vdbe *v = sqlite3GetVdbe(p); - sqlite3TableLock(p, iDb, SCHEMA_ROOT, 1, DFLT_SCHEMA_TABLE); + sqlite3TableLock(p, iDb, SCHEMA_ROOT, 1, LEGACY_SCHEMA_TABLE); sqlite3VdbeAddOp4Int(v, OP_OpenWrite, 0, SCHEMA_ROOT, iDb, 5); if( p->nTab==0 ){ p->nTab = 1; @@ -112445,7 +116184,7 @@ SQLITE_PRIVATE int sqlite3TwoPartName( return -1; } }else{ - assert( db->init.iDb==0 || db->init.busy || IN_RENAME_OBJECT + assert( db->init.iDb==0 || db->init.busy || IN_SPECIAL_PARSE || (db->mDbFlags & DBFLAG_Vacuum)!=0); iDb = db->init.iDb; *pUnqual = pName1; @@ -112614,6 +116353,23 @@ SQLITE_PRIVATE i16 sqlite3TableColumnToStorage(Table *pTab, i16 iCol){ } #endif +/* +** Insert a single OP_JournalMode query opcode in order to force the +** prepared statement to return false for sqlite3_stmt_readonly(). This +** is used by CREATE TABLE IF NOT EXISTS and similar if the table already +** exists, so that the prepared statement for CREATE TABLE IF NOT EXISTS +** will return false for sqlite3_stmt_readonly() even if that statement +** is a read-only no-op. +*/ +static void sqlite3ForceNotReadOnly(Parse *pParse){ + int iReg = ++pParse->nMem; + Vdbe *v = sqlite3GetVdbe(pParse); + if( v ){ + sqlite3VdbeAddOp3(v, OP_JournalMode, 0, iReg, PAGER_JOURNALMODE_QUERY); + sqlite3VdbeUsesBtree(v, 0); + } +} + /* ** Begin constructing a new table representation in memory. This is ** the first of several action routines that get called in response @@ -112709,10 +116465,12 @@ SQLITE_PRIVATE void sqlite3StartTable( pTable = sqlite3FindTable(db, zName, zDb); if( pTable ){ if( !noErr ){ - sqlite3ErrorMsg(pParse, "table %T already exists", pName); + sqlite3ErrorMsg(pParse, "%s %T already exists", + (IsView(pTable)? "view" : "table"), pName); }else{ assert( !db->init.busy || CORRUPT_DB ); sqlite3CodeVerifySchema(pParse, iDb); + sqlite3ForceNotReadOnly(pParse); } goto begin_table_error; } @@ -112741,17 +116499,6 @@ SQLITE_PRIVATE void sqlite3StartTable( assert( pParse->pNewTable==0 ); pParse->pNewTable = pTable; - /* If this is the magic sqlite_sequence table used by autoincrement, - ** then record a pointer to this table in the main database structure - ** so that INSERT can find the table easily. - */ -#ifndef SQLITE_OMIT_AUTOINCREMENT - if( !pParse->nested && strcmp(zName, "sqlite_sequence")==0 ){ - assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); - pTable->pSchema->pSeqTab = pTable; - } -#endif - /* Begin generating the code that will insert the table record into ** the schema table. Note in particular that we must go ahead ** and allocate the record number for the table entry now. Before any @@ -112821,6 +116568,7 @@ SQLITE_PRIVATE void sqlite3StartTable( /* If an error occurs, we jump here */ begin_table_error: + pParse->checkSchema = 1; sqlite3DbFree(db, zName); return; } @@ -112830,7 +116578,7 @@ begin_table_error: */ #if SQLITE_ENABLE_HIDDEN_COLUMNS SQLITE_PRIVATE void sqlite3ColumnPropertiesFromName(Table *pTab, Column *pCol){ - if( sqlite3_strnicmp(pCol->zName, "__hidden__", 10)==0 ){ + if( sqlite3_strnicmp(pCol->zCnName, "__hidden__", 10)==0 ){ pCol->colFlags |= COLFLAG_HIDDEN; if( pTab ) pTab->tabFlags |= TF_HasHidden; }else if( pTab && pCol!=pTab->aCol && (pCol[-1].colFlags & COLFLAG_HIDDEN) ){ @@ -112900,6 +116648,7 @@ SQLITE_PRIVATE void sqlite3AddReturning(Parse *pParse, ExprList *pList){ pRet->retTrig.tr_tm = TRIGGER_AFTER; pRet->retTrig.bReturning = 1; pRet->retTrig.pSchema = db->aDb[1].pSchema; + pRet->retTrig.pTabSchema = db->aDb[1].pSchema; pRet->retTrig.step_list = &pRet->retTStep; pRet->retTStep.op = TK_RETURNING; pRet->retTStep.pTrig = &pRet->retTrig; @@ -112920,7 +116669,7 @@ SQLITE_PRIVATE void sqlite3AddReturning(Parse *pParse, ExprList *pList){ ** first to get things going. Then this routine is called for each ** column. */ -SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){ +SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token sName, Token sType){ Table *p; int i; char *z; @@ -112928,55 +116677,96 @@ SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){ Column *pCol; sqlite3 *db = pParse->db; u8 hName; + Column *aNew; + u8 eType = COLTYPE_CUSTOM; + u8 szEst = 1; + char affinity = SQLITE_AFF_BLOB; if( (p = pParse->pNewTable)==0 ) return; if( p->nCol+1>db->aLimit[SQLITE_LIMIT_COLUMN] ){ sqlite3ErrorMsg(pParse, "too many columns on %s", p->zName); return; } - z = sqlite3DbMallocRaw(db, pName->n + pType->n + 2); + if( !IN_RENAME_OBJECT ) sqlite3DequoteToken(&sName); + + /* Because keywords GENERATE ALWAYS can be converted into indentifiers + ** by the parser, we can sometimes end up with a typename that ends + ** with "generated always". Check for this case and omit the surplus + ** text. */ + if( sType.n>=16 + && sqlite3_strnicmp(sType.z+(sType.n-6),"always",6)==0 + ){ + sType.n -= 6; + while( ALWAYS(sType.n>0) && sqlite3Isspace(sType.z[sType.n-1]) ) sType.n--; + if( sType.n>=9 + && sqlite3_strnicmp(sType.z+(sType.n-9),"generated",9)==0 + ){ + sType.n -= 9; + while( sType.n>0 && sqlite3Isspace(sType.z[sType.n-1]) ) sType.n--; + } + } + + /* Check for standard typenames. For standard typenames we will + ** set the Column.eType field rather than storing the typename after + ** the column name, in order to save space. */ + if( sType.n>=3 ){ + sqlite3DequoteToken(&sType); + for(i=0; i0) ); if( z==0 ) return; - if( IN_RENAME_OBJECT ) sqlite3RenameTokenMap(pParse, (void*)z, pName); - memcpy(z, pName->z, pName->n); - z[pName->n] = 0; + if( IN_RENAME_OBJECT ) sqlite3RenameTokenMap(pParse, (void*)z, &sName); + memcpy(z, sName.z, sName.n); + z[sName.n] = 0; sqlite3Dequote(z); hName = sqlite3StrIHash(z); for(i=0; inCol; i++){ - if( p->aCol[i].hName==hName && sqlite3StrICmp(z, p->aCol[i].zName)==0 ){ + if( p->aCol[i].hName==hName && sqlite3StrICmp(z, p->aCol[i].zCnName)==0 ){ sqlite3ErrorMsg(pParse, "duplicate column name: %s", z); sqlite3DbFree(db, z); return; } } - if( (p->nCol & 0x7)==0 ){ - Column *aNew; - aNew = sqlite3DbRealloc(db,p->aCol,(p->nCol+8)*sizeof(p->aCol[0])); - if( aNew==0 ){ - sqlite3DbFree(db, z); - return; - } - p->aCol = aNew; + aNew = sqlite3DbRealloc(db,p->aCol,((i64)p->nCol+1)*sizeof(p->aCol[0])); + if( aNew==0 ){ + sqlite3DbFree(db, z); + return; } + p->aCol = aNew; pCol = &p->aCol[p->nCol]; memset(pCol, 0, sizeof(p->aCol[0])); - pCol->zName = z; + pCol->zCnName = z; pCol->hName = hName; sqlite3ColumnPropertiesFromName(p, pCol); - if( pType->n==0 ){ + if( sType.n==0 ){ /* If there is no type specified, columns have the default affinity ** 'BLOB' with a default size of 4 bytes. */ - pCol->affinity = SQLITE_AFF_BLOB; - pCol->szEst = 1; + pCol->affinity = affinity; + pCol->eCType = eType; + pCol->szEst = szEst; #ifdef SQLITE_ENABLE_SORTER_REFERENCES - if( 4>=sqlite3GlobalConfig.szSorterRef ){ - pCol->colFlags |= COLFLAG_SORTERREF; + if( affinity==SQLITE_AFF_BLOB ){ + if( 4>=sqlite3GlobalConfig.szSorterRef ){ + pCol->colFlags |= COLFLAG_SORTERREF; + } } #endif }else{ zType = z + sqlite3Strlen30(z) + 1; - memcpy(zType, pType->z, pType->n); - zType[pType->n] = 0; + memcpy(zType, sType.z, sType.n); + zType[sType.n] = 0; sqlite3Dequote(zType); pCol->affinity = sqlite3AffinityType(zType, pCol); pCol->colFlags |= COLFLAG_HASTYPE; @@ -113131,7 +116921,7 @@ SQLITE_PRIVATE void sqlite3AddDefaultValue( pCol = &(p->aCol[p->nCol-1]); if( !sqlite3ExprIsConstantOrFunction(pExpr, isInit) ){ sqlite3ErrorMsg(pParse, "default value of column [%s] is not constant", - pCol->zName); + pCol->zCnName); #ifndef SQLITE_OMIT_GENERATED_COLUMNS }else if( pCol->colFlags & COLFLAG_GENERATED ){ testcase( pCol->colFlags & COLFLAG_VIRTUAL ); @@ -113142,15 +116932,15 @@ SQLITE_PRIVATE void sqlite3AddDefaultValue( /* A copy of pExpr is used instead of the original, as pExpr contains ** tokens that point to volatile memory. */ - Expr x; - sqlite3ExprDelete(db, pCol->pDflt); + Expr x, *pDfltExpr; memset(&x, 0, sizeof(x)); x.op = TK_SPAN; x.u.zToken = sqlite3DbSpanDup(db, zStart, zEnd); x.pLeft = pExpr; x.flags = EP_Skip; - pCol->pDflt = sqlite3ExprDup(db, &x, EXPRDUP_REDUCE); + pDfltExpr = sqlite3ExprDup(db, &x, EXPRDUP_REDUCE); sqlite3DbFree(db, x.u.zToken); + sqlite3ColumnSetExpr(pParse, p, pCol, pDfltExpr); } } if( IN_RENAME_OBJECT ){ @@ -113246,9 +117036,11 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey( assert( pCExpr!=0 ); sqlite3StringToId(pCExpr); if( pCExpr->op==TK_ID ){ - const char *zCName = pCExpr->u.zToken; + const char *zCName; + assert( !ExprHasProperty(pCExpr, EP_IntValue) ); + zCName = pCExpr->u.zToken; for(iCol=0; iColnCol; iCol++){ - if( sqlite3StrICmp(zCName, pTab->aCol[iCol].zName)==0 ){ + if( sqlite3StrICmp(zCName, pTab->aCol[iCol].zCnName)==0 ){ pCol = &pTab->aCol[iCol]; makeColumnPartOfPrimaryKey(pParse, pCol); break; @@ -113259,7 +117051,7 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey( } if( nTerm==1 && pCol - && sqlite3StrICmp(sqlite3ColumnType(pCol,""), "INTEGER")==0 + && pCol->eCType==COLTYPE_INTEGER && sortOrder!=SQLITE_SO_DESC ){ if( IN_RENAME_OBJECT && pList ){ @@ -113270,7 +117062,7 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey( pTab->keyConf = (u8)onError; assert( autoInc==0 || autoInc==1 ); pTab->tabFlags |= autoInc*TF_Autoincrement; - if( pList ) pParse->iPkSortOrder = pList->a[0].sortFlags; + if( pList ) pParse->iPkSortOrder = pList->a[0].fg.sortFlags; (void)sqlite3HasExplicitNulls(pParse, pList); }else if( autoInc ){ #ifndef SQLITE_OMIT_AUTOINCREMENT @@ -113339,8 +117131,7 @@ SQLITE_PRIVATE void sqlite3AddCollateType(Parse *pParse, Token *pToken){ if( sqlite3LocateCollSeq(pParse, zColl) ){ Index *pIdx; - sqlite3DbFree(db, p->aCol[i].zColl); - p->aCol[i].zColl = zColl; + sqlite3ColumnSetColl(db, &p->aCol[i], zColl); /* If the column is declared as " PRIMARY KEY COLLATE ", ** then an index may have been created on this column before the @@ -113349,12 +117140,11 @@ SQLITE_PRIVATE void sqlite3AddCollateType(Parse *pParse, Token *pToken){ for(pIdx=p->pIndex; pIdx; pIdx=pIdx->pNext){ assert( pIdx->nKeyCol==1 ); if( pIdx->aiColumn[0]==i ){ - pIdx->azColl[0] = p->aCol[i].zColl; + pIdx->azColl[0] = sqlite3ColumnColl(&p->aCol[i]); } } - }else{ - sqlite3DbFree(db, zColl); } + sqlite3DbFree(db, zColl); } /* Change the most recently parsed column to be a GENERATED ALWAYS AS @@ -113374,7 +117164,7 @@ SQLITE_PRIVATE void sqlite3AddGenerated(Parse *pParse, Expr *pExpr, Token *pType sqlite3ErrorMsg(pParse, "virtual tables cannot use computed columns"); goto generated_done; } - if( pCol->pDflt ) goto generated_error; + if( pCol->iDflt>0 ) goto generated_error; if( pType ){ if( pType->n==7 && sqlite3StrNICmp("virtual",pType->z,7)==0 ){ /* no-op */ @@ -113392,13 +117182,13 @@ SQLITE_PRIVATE void sqlite3AddGenerated(Parse *pParse, Expr *pExpr, Token *pType if( pCol->colFlags & COLFLAG_PRIMKEY ){ makeColumnPartOfPrimaryKey(pParse, pCol); /* For the error message */ } - pCol->pDflt = pExpr; + sqlite3ColumnSetExpr(pParse, pTab, pCol, pExpr); pExpr = 0; goto generated_done; generated_error: sqlite3ErrorMsg(pParse, "error in generated column \"%s\"", - pCol->zName); + pCol->zCnName); generated_done: sqlite3ExprDelete(pParse->db, pExpr); #else @@ -113500,7 +117290,7 @@ static char *createTableStmt(sqlite3 *db, Table *p){ Column *pCol; n = 0; for(pCol = p->aCol, i=0; inCol; i++, pCol++){ - n += identLength(pCol->zName) + 5; + n += identLength(pCol->zCnName) + 5; } n += identLength(p->zName); if( n<50 ){ @@ -113536,7 +117326,7 @@ static char *createTableStmt(sqlite3 *db, Table *p){ sqlite3_snprintf(n-k, &zStmt[k], zSep); k += sqlite3Strlen30(&zStmt[k]); zSep = zSep2; - identPut(zStmt, &k, pCol->zName); + identPut(zStmt, &k, pCol->zCnName); assert( pCol->affinity-SQLITE_AFF_BLOB >= 0 ); assert( pCol->affinity-SQLITE_AFF_BLOB < ArraySize(azType) ); testcase( pCol->affinity==SQLITE_AFF_BLOB ); @@ -113620,7 +117410,6 @@ static void estimateIndexWidth(Index *pIdx){ */ static int hasColumn(const i16 *aiCol, int nCol, int x){ while( nCol-- > 0 ){ - assert( aiCol[0]>=0 ); if( x==*(aiCol++) ){ return 1; } @@ -113733,7 +117522,9 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ */ if( !db->init.imposterTable ){ for(i=0; inCol; i++){ - if( (pTab->aCol[i].colFlags & COLFLAG_PRIMKEY)!=0 ){ + if( (pTab->aCol[i].colFlags & COLFLAG_PRIMKEY)!=0 + && (pTab->aCol[i].notNull==OE_None) + ){ pTab->aCol[i].notNull = OE_Abort; } } @@ -113755,19 +117546,26 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ if( pTab->iPKey>=0 ){ ExprList *pList; Token ipkToken; - sqlite3TokenInit(&ipkToken, pTab->aCol[pTab->iPKey].zName); + sqlite3TokenInit(&ipkToken, pTab->aCol[pTab->iPKey].zCnName); pList = sqlite3ExprListAppend(pParse, 0, sqlite3ExprAlloc(db, TK_ID, &ipkToken, 0)); - if( pList==0 ) return; + if( pList==0 ){ + pTab->tabFlags &= ~TF_WithoutRowid; + return; + } if( IN_RENAME_OBJECT ){ sqlite3RenameTokenRemap(pParse, pList->a[0].pExpr, &pTab->iPKey); } - pList->a[0].sortFlags = pParse->iPkSortOrder; + pList->a[0].fg.sortFlags = pParse->iPkSortOrder; assert( pParse->pNewTable==pTab ); pTab->iPKey = -1; sqlite3CreateIndex(pParse, 0, 0, 0, pList, pTab->keyConf, 0, 0, 0, 0, SQLITE_IDXTYPE_PRIMARYKEY); - if( db->mallocFailed || pParse->nErr ) return; + if( pParse->nErr ){ + pTab->tabFlags &= ~TF_WithoutRowid; + return; + } + assert( db->mallocFailed==0 ); pPk = sqlite3PrimaryKeyIndex(pTab); assert( pPk->nKeyCol==1 ); }else{ @@ -113879,7 +117677,7 @@ SQLITE_PRIVATE int sqlite3IsShadowTableOf(sqlite3 *db, Table *pTab, const char * nName = sqlite3Strlen30(pTab->zName); if( sqlite3_strnicmp(zName, pTab->zName, nName)!=0 ) return 0; if( zName[nName]!='_' ) return 0; - pMod = (Module*)sqlite3HashFind(&db->aModule, pTab->azModuleArg[0]); + pMod = (Module*)sqlite3HashFind(&db->aModule, pTab->u.vtab.azArg[0]); if( pMod==0 ) return 0; if( pMod->pModule->iVersion<3 ) return 0; if( pMod->pModule->xShadowName==0 ) return 0; @@ -113887,6 +117685,41 @@ SQLITE_PRIVATE int sqlite3IsShadowTableOf(sqlite3 *db, Table *pTab, const char * } #endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Table pTab is a virtual table. If it the virtual table implementation +** exists and has an xShadowName method, then loop over all other ordinary +** tables within the same schema looking for shadow tables of pTab, and mark +** any shadow tables seen using the TF_Shadow flag. +*/ +SQLITE_PRIVATE void sqlite3MarkAllShadowTablesOf(sqlite3 *db, Table *pTab){ + int nName; /* Length of pTab->zName */ + Module *pMod; /* Module for the virtual table */ + HashElem *k; /* For looping through the symbol table */ + + assert( IsVirtual(pTab) ); + pMod = (Module*)sqlite3HashFind(&db->aModule, pTab->u.vtab.azArg[0]); + if( pMod==0 ) return; + if( NEVER(pMod->pModule==0) ) return; + if( pMod->pModule->iVersion<3 ) return; + if( pMod->pModule->xShadowName==0 ) return; + assert( pTab->zName!=0 ); + nName = sqlite3Strlen30(pTab->zName); + for(k=sqliteHashFirst(&pTab->pSchema->tblHash); k; k=sqliteHashNext(k)){ + Table *pOther = sqliteHashData(k); + assert( pOther->zName!=0 ); + if( !IsOrdinaryTable(pOther) ) continue; + if( pOther->tabFlags & TF_Shadow ) continue; + if( sqlite3StrNICmp(pOther->zName, pTab->zName, nName)==0 + && pOther->zName[nName]=='_' + && pMod->pModule->xShadowName(pOther->zName+nName+1) + ){ + pOther->tabFlags |= TF_Shadow; + } + } +} +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ + #ifndef SQLITE_OMIT_VIRTUALTABLE /* ** Return true if zName is a shadow table name in the current database @@ -113960,7 +117793,7 @@ SQLITE_PRIVATE void sqlite3EndTable( Parse *pParse, /* Parse context */ Token *pCons, /* The ',' token after the last column defn. */ Token *pEnd, /* The ')' before options in the CREATE TABLE */ - u8 tabOpts, /* Extra table options. Usually 0. */ + u32 tabOpts, /* Extra table options. Usually 0. */ Select *pSelect /* Select from a "CREATE ... AS SELECT" */ ){ Table *p; /* The new table */ @@ -113971,7 +117804,6 @@ SQLITE_PRIVATE void sqlite3EndTable( if( pEnd==0 && pSelect==0 ){ return; } - assert( !db->mallocFailed ); p = pParse->pNewTable; if( p==0 ) return; @@ -113989,7 +117821,7 @@ SQLITE_PRIVATE void sqlite3EndTable( ** table itself. So mark it read-only. */ if( db->init.busy ){ - if( pSelect ){ + if( pSelect || (!IsOrdinaryTable(p) && db->init.newTnum) ){ sqlite3ErrorMsg(pParse, ""); return; } @@ -113997,6 +117829,44 @@ SQLITE_PRIVATE void sqlite3EndTable( if( p->tnum==1 ) p->tabFlags |= TF_Readonly; } + /* Special processing for tables that include the STRICT keyword: + ** + ** * Do not allow custom column datatypes. Every column must have + ** a datatype that is one of INT, INTEGER, REAL, TEXT, or BLOB. + ** + ** * If a PRIMARY KEY is defined, other than the INTEGER PRIMARY KEY, + ** then all columns of the PRIMARY KEY must have a NOT NULL + ** constraint. + */ + if( tabOpts & TF_Strict ){ + int ii; + p->tabFlags |= TF_Strict; + for(ii=0; iinCol; ii++){ + Column *pCol = &p->aCol[ii]; + if( pCol->eCType==COLTYPE_CUSTOM ){ + if( pCol->colFlags & COLFLAG_HASTYPE ){ + sqlite3ErrorMsg(pParse, + "unknown datatype for %s.%s: \"%s\"", + p->zName, pCol->zCnName, sqlite3ColumnType(pCol, "") + ); + }else{ + sqlite3ErrorMsg(pParse, "missing datatype for %s.%s", + p->zName, pCol->zCnName); + } + return; + }else if( pCol->eCType==COLTYPE_ANY ){ + pCol->affinity = SQLITE_AFF_BLOB; + } + if( (pCol->colFlags & COLFLAG_PRIMKEY)!=0 + && p->iPKey!=ii + && pCol->notNull == OE_None + ){ + pCol->notNull = OE_Abort; + p->tabFlags |= TF_HasNotNull; + } + } + } + assert( (p->tabFlags & TF_HasPrimaryKey)==0 || p->iPKey>=0 || sqlite3PrimaryKeyIndex(p)!=0 ); assert( (p->tabFlags & TF_HasPrimaryKey)!=0 @@ -114041,7 +117911,7 @@ SQLITE_PRIVATE void sqlite3EndTable( for(ii=0; iinCol; ii++){ u32 colFlags = p->aCol[ii].colFlags; if( (colFlags & COLFLAG_GENERATED)!=0 ){ - Expr *pX = p->aCol[ii].pDflt; + Expr *pX = sqlite3ColumnExpr(p, &p->aCol[ii]); testcase( colFlags & COLFLAG_VIRTUAL ); testcase( colFlags & COLFLAG_STORED ); if( sqlite3ResolveSelfReference(pParse, p, NC_GenCol, pX, 0) ){ @@ -114051,8 +117921,8 @@ SQLITE_PRIVATE void sqlite3EndTable( ** tree that have been allocated from lookaside memory, which is ** illegal in a schema and will lead to errors or heap corruption ** when the database connection closes. */ - sqlite3ExprDelete(db, pX); - p->aCol[ii].pDflt = sqlite3ExprAlloc(db, TK_NULL, 0, 0); + sqlite3ColumnSetExpr(pParse, p, &p->aCol[ii], + sqlite3ExprAlloc(db, TK_NULL, 0, 0)); } }else{ nNG++; @@ -114092,7 +117962,7 @@ SQLITE_PRIVATE void sqlite3EndTable( /* ** Initialize zType for the new view or table. */ - if( p->pSelect==0 ){ + if( IsOrdinaryTable(p) ){ /* A regular table */ zType = "table"; zType2 = "TABLE"; @@ -114126,6 +117996,11 @@ SQLITE_PRIVATE void sqlite3EndTable( int addrInsLoop; /* Top of the loop for inserting rows */ Table *pSelTab; /* A table that describes the SELECT results */ + if( IN_SPECIAL_PARSE ){ + pParse->rc = SQLITE_ERROR; + pParse->nErr++; + return; + } regYield = ++pParse->nMem; regRec = ++pParse->nMem; regRowid = ++pParse->nMem; @@ -114178,7 +118053,7 @@ SQLITE_PRIVATE void sqlite3EndTable( ** the information we've collected. */ sqlite3NestedParse(pParse, - "UPDATE %Q." DFLT_SCHEMA_TABLE + "UPDATE %Q." LEGACY_SCHEMA_TABLE " SET type='%s', name=%Q, tbl_name=%Q, rootpage=#%d, sql=%Q" " WHERE rowid=#%d", db->aDb[iDb].zDbSName, @@ -114196,7 +118071,7 @@ SQLITE_PRIVATE void sqlite3EndTable( /* Check to see if we need to create an sqlite_sequence table for ** keeping track of autoincrement keys. */ - if( (p->tabFlags & TF_Autoincrement)!=0 ){ + if( (p->tabFlags & TF_Autoincrement)!=0 && !IN_SPECIAL_PARSE ){ Db *pDb = &db->aDb[iDb]; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( pDb->pSchema->pSeqTab==0 ){ @@ -114219,6 +118094,7 @@ SQLITE_PRIVATE void sqlite3EndTable( Table *pOld; Schema *pSchema = p->pSchema; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + assert( HasRowid(p) || p->iPKey<0 ); pOld = sqlite3HashInsert(&pSchema->tblHash, p->zName, p); if( pOld ){ assert( p==pOld ); /* Malloc must have failed inside HashInsert() */ @@ -114227,15 +118103,26 @@ SQLITE_PRIVATE void sqlite3EndTable( } pParse->pNewTable = 0; db->mDbFlags |= DBFLAG_SchemaChange; + + /* If this is the magic sqlite_sequence table used by autoincrement, + ** then record a pointer to this table in the main database structure + ** so that INSERT can find the table easily. */ + assert( !pParse->nested ); +#ifndef SQLITE_OMIT_AUTOINCREMENT + if( strcmp(p->zName, "sqlite_sequence")==0 ){ + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + p->pSchema->pSeqTab = p; + } +#endif } #ifndef SQLITE_OMIT_ALTERTABLE - if( !pSelect && !p->pSelect ){ + if( !pSelect && IsOrdinaryTable(p) ){ assert( pCons && pEnd ); if( pCons->z==0 ){ pCons = pEnd; } - p->addColOffset = 13 + (int)(pCons->z - pParse->sNameToken.z); + p->u.tab.addColOffset = 13 + (int)(pCons->z - pParse->sNameToken.z); } #endif } @@ -114270,6 +118157,16 @@ SQLITE_PRIVATE void sqlite3CreateView( sqlite3StartTable(pParse, pName1, pName2, isTemp, 1, 0, noErr); p = pParse->pNewTable; if( p==0 || pParse->nErr ) goto create_view_fail; + + /* Legacy versions of SQLite allowed the use of the magic "rowid" column + ** on a view, even though views do not have rowids. The following flag + ** setting fixes this problem. But the fix can be disabled by compiling + ** with -DSQLITE_ALLOW_ROWID_IN_VIEW in case there are legacy apps that + ** depend upon the old buggy behavior. */ +#ifndef SQLITE_ALLOW_ROWID_IN_VIEW + p->tabFlags |= TF_NoVisibleRowid; +#endif + sqlite3TwoPartName(pParse, pName1, pName2, &pName); iDb = sqlite3SchemaToIndex(db, p->pSchema); sqlite3FixInit(&sFix, pParse, iDb, "view", pName); @@ -114282,12 +118179,13 @@ SQLITE_PRIVATE void sqlite3CreateView( */ pSelect->selFlags |= SF_View; if( IN_RENAME_OBJECT ){ - p->pSelect = pSelect; + p->u.view.pSelect = pSelect; pSelect = 0; }else{ - p->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); + p->u.view.pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); } p->pCheck = sqlite3ExprListDup(db, pCNames, EXPRDUP_REDUCE); + p->eTabType = TABTYP_VIEW; if( db->mallocFailed ) goto create_view_fail; /* Locate the end of the CREATE VIEW statement. Make sEnd point to @@ -114329,7 +118227,6 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ Table *pSelTab; /* A fake table from which we get the result set */ Select *pSel; /* Copy of the SELECT that implements the view */ int nErr = 0; /* Number of errors encountered */ - int n; /* Temporarily holds the number of cursors assigned */ sqlite3 *db = pParse->db; /* Database connection for malloc errors */ #ifndef SQLITE_OMIT_VIRTUALTABLE int rc; @@ -114341,13 +118238,12 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ assert( pTable ); #ifndef SQLITE_OMIT_VIRTUALTABLE - db->nSchemaLock++; - rc = sqlite3VtabCallConnect(pParse, pTable); - db->nSchemaLock--; - if( rc ){ - return 1; + if( IsVirtual(pTable) ){ + db->nSchemaLock++; + rc = sqlite3VtabCallConnect(pParse, pTable); + db->nSchemaLock--; + return rc; } - if( IsVirtual(pTable) ) return 0; #endif #ifndef SQLITE_OMIT_VIEW @@ -114384,12 +118280,13 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ ** to be permanent. So the computation is done on a copy of the SELECT ** statement that defines the view. */ - assert( pTable->pSelect ); - pSel = sqlite3SelectDup(db, pTable->pSelect, 0); + assert( IsView(pTable) ); + pSel = sqlite3SelectDup(db, pTable->u.view.pSelect, 0); if( pSel ){ u8 eParseMode = pParse->eParseMode; + int nTab = pParse->nTab; + int nSelect = pParse->nSelect; pParse->eParseMode = PARSE_MODE_NORMAL; - n = pParse->nTab; sqlite3SrcListAssignCursors(pParse, pSel->pSrc); pTable->nCol = -1; DisableLookaside; @@ -114401,7 +118298,8 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ #else pSelTab = sqlite3ResultSetOfSelect(pParse, pSel, SQLITE_AFF_NONE); #endif - pParse->nTab = n; + pParse->nTab = nTab; + pParse->nSelect = nSelect; if( pSelTab==0 ){ pTable->nCol = 0; nErr++; @@ -114414,10 +118312,10 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ */ sqlite3ColumnsFromExprList(pParse, pTable->pCheck, &pTable->nCol, &pTable->aCol); - if( db->mallocFailed==0 - && pParse->nErr==0 + if( pParse->nErr==0 && pTable->nCol==pSel->pEList->nExpr ){ + assert( db->mallocFailed==0 ); sqlite3SelectAddColumnTypeAndCollation(pParse, pTable, pSel, SQLITE_AFF_NONE); } @@ -114444,8 +118342,6 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ pTable->pSchema->schemaFlags |= DB_UnresetViews; if( db->mallocFailed ){ sqlite3DeleteColumnNames(db, pTable); - pTable->aCol = 0; - pTable->nCol = 0; } #endif /* SQLITE_OMIT_VIEW */ return nErr; @@ -114462,10 +118358,8 @@ static void sqliteViewResetAll(sqlite3 *db, int idx){ if( !DbHasProperty(db, idx, DB_UnresetViews) ) return; for(i=sqliteHashFirst(&db->aDb[idx].pSchema->tblHash); i;i=sqliteHashNext(i)){ Table *pTab = sqliteHashData(i); - if( pTab->pSelect ){ + if( IsView(pTab) ){ sqlite3DeleteColumnNames(db, pTab); - pTab->aCol = 0; - pTab->nCol = 0; } } DbClearProperty(db, idx, DB_UnresetViews); @@ -114539,7 +118433,7 @@ static void destroyRootPage(Parse *pParse, int iTable, int iDb){ ** token for additional information. */ sqlite3NestedParse(pParse, - "UPDATE %Q." DFLT_SCHEMA_TABLE + "UPDATE %Q." LEGACY_SCHEMA_TABLE " SET rootpage=%d WHERE #%d AND rootpage=#%d", pParse->db->aDb[iDb].zDbSName, iTable, r1, r1); #endif @@ -114674,7 +118568,7 @@ SQLITE_PRIVATE void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, in ** database. */ sqlite3NestedParse(pParse, - "DELETE FROM %Q." DFLT_SCHEMA_TABLE + "DELETE FROM %Q." LEGACY_SCHEMA_TABLE " WHERE tbl_name=%Q and type!='trigger'", pDb->zDbSName, pTab->zName); if( !isView && !IsVirtual(pTab) ){ @@ -114702,6 +118596,7 @@ SQLITE_PRIVATE int sqlite3ReadOnlyShadowTables(sqlite3 *db){ if( (db->flags & SQLITE_Defensive)!=0 && db->pVtabCtx==0 && db->nVdbeExec==0 + && !sqlite3VtabInSync(db) ){ return 1; } @@ -114721,6 +118616,9 @@ static int tableMayNotBeDropped(sqlite3 *db, Table *pTab){ if( (pTab->tabFlags & TF_Shadow)!=0 && sqlite3ReadOnlyShadowTables(db) ){ return 1; } + if( pTab->tabFlags & TF_Eponymous ){ + return 1; + } return 0; } @@ -114746,7 +118644,10 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, if( noErr ) db->suppressErr--; if( pTab==0 ){ - if( noErr ) sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase); + if( noErr ){ + sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase); + sqlite3ForceNotReadOnly(pParse); + } goto exit_drop_table; } iDb = sqlite3SchemaToIndex(db, pTab->pSchema); @@ -114802,11 +118703,11 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, /* Ensure DROP TABLE is not used on a view, and DROP VIEW is not used ** on a table. */ - if( isView && pTab->pSelect==0 ){ + if( isView && !IsView(pTab) ){ sqlite3ErrorMsg(pParse, "use DROP TABLE to delete table %s", pTab->zName); goto exit_drop_table; } - if( !isView && pTab->pSelect ){ + if( !isView && IsView(pTab) ){ sqlite3ErrorMsg(pParse, "use DROP VIEW to delete view %s", pTab->zName); goto exit_drop_table; } @@ -114857,7 +118758,7 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey( FKey *pFKey = 0; FKey *pNextTo; Table *p = pParse->pNewTable; - int nByte; + i64 nByte; int i; int nCol; char *z; @@ -114870,7 +118771,7 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey( if( pToCol && pToCol->nExpr!=1 ){ sqlite3ErrorMsg(pParse, "foreign key on %s" " should reference only one column of table %T", - p->aCol[iCol].zName, pTo); + p->aCol[iCol].zCnName, pTo); goto fk_end; } nCol = 1; @@ -114893,7 +118794,8 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey( goto fk_end; } pFKey->pFrom = p; - pFKey->pNextFrom = p->pFKey; + assert( IsOrdinaryTable(p) ); + pFKey->pNextFrom = p->u.tab.pFKey; z = (char*)&pFKey->aCol[nCol]; pFKey->zTo = z; if( IN_RENAME_OBJECT ){ @@ -114910,7 +118812,7 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey( for(i=0; inCol; j++){ - if( sqlite3StrICmp(p->aCol[j].zName, pFromCol->a[i].zEName)==0 ){ + if( sqlite3StrICmp(p->aCol[j].zCnName, pFromCol->a[i].zEName)==0 ){ pFKey->aCol[i].iFrom = j; break; } @@ -114958,7 +118860,8 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey( /* Link the foreign key to the table as the last step. */ - p->pFKey = pFKey; + assert( IsOrdinaryTable(p) ); + p->u.tab.pFKey = pFKey; pFKey = 0; fk_end: @@ -114979,7 +118882,9 @@ SQLITE_PRIVATE void sqlite3DeferForeignKey(Parse *pParse, int isDeferred){ #ifndef SQLITE_OMIT_FOREIGN_KEY Table *pTab; FKey *pFKey; - if( (pTab = pParse->pNewTable)==0 || (pFKey = pTab->pFKey)==0 ) return; + if( (pTab = pParse->pNewTable)==0 ) return; + if( NEVER(!IsOrdinaryTable(pTab)) ) return; + if( (pFKey = pTab->u.tab.pFKey)==0 ) return; assert( isDeferred==0 || isDeferred==1 ); /* EV: R-30323-21917 */ pFKey->isDeferred = (u8)isDeferred; #endif @@ -115029,7 +118934,7 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ tnum = pIndex->tnum; } pKey = sqlite3KeyInfoOfIndex(pParse, pIndex); - assert( pKey!=0 || db->mallocFailed || pParse->nErr ); + assert( pKey!=0 || pParse->nErr ); /* Open the sorter cursor if we are to use one. */ iSorter = pParse->nTab++; @@ -115139,8 +119044,8 @@ SQLITE_PRIVATE int sqlite3HasExplicitNulls(Parse *pParse, ExprList *pList){ if( pList ){ int i; for(i=0; inExpr; i++){ - if( pList->a[i].bNulls ){ - u8 sf = pList->a[i].sortFlags; + if( pList->a[i].fg.bNulls ){ + u8 sf = pList->a[i].fg.sortFlags; sqlite3ErrorMsg(pParse, "unsupported use of NULLS %s", (sf==0 || sf==3) ? "FIRST" : "LAST" ); @@ -115193,9 +119098,11 @@ SQLITE_PRIVATE void sqlite3CreateIndex( char *zExtra = 0; /* Extra space after the Index object */ Index *pPk = 0; /* PRIMARY KEY index for WITHOUT ROWID tables */ - if( db->mallocFailed || pParse->nErr>0 ){ + assert( db->pParse==pParse ); + if( pParse->nErr ){ goto exit_create_index; } + assert( db->mallocFailed==0 ); if( IN_DECLARE_VTAB && idxType!=SQLITE_IDXTYPE_PRIMARYKEY ){ goto exit_create_index; } @@ -115259,7 +119166,6 @@ SQLITE_PRIVATE void sqlite3CreateIndex( pDb = &db->aDb[iDb]; assert( pTab!=0 ); - assert( pParse->nErr==0 ); if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 && db->init.busy==0 && pTblName!=0 @@ -115271,7 +119177,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex( goto exit_create_index; } #ifndef SQLITE_OMIT_VIEW - if( pTab->pSelect ){ + if( IsView(pTab) ){ sqlite3ErrorMsg(pParse, "views may not be indexed"); goto exit_create_index; } @@ -115316,6 +119222,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex( }else{ assert( !db->init.busy ); sqlite3CodeVerifySchema(pParse, iDb); + sqlite3ForceNotReadOnly(pParse); } goto exit_create_index; } @@ -115361,7 +119268,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex( Token prevCol; Column *pCol = &pTab->aCol[pTab->nCol-1]; pCol->colFlags |= COLFLAG_UNIQUE; - sqlite3TokenInit(&prevCol, pCol->zName); + sqlite3TokenInit(&prevCol, pCol->zCnName); pList = sqlite3ExprListAppend(pParse, 0, sqlite3ExprAlloc(db, TK_ID, &prevCol, 0)); if( pList==0 ) goto exit_create_index; @@ -115379,6 +119286,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex( Expr *pExpr = pList->a[i].pExpr; assert( pExpr!=0 ); if( pExpr->op==TK_COLLATE ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); nExtra += (1 + sqlite3Strlen30(pExpr->u.zToken)); } } @@ -115474,6 +119382,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex( zColl = 0; if( pListItem->pExpr->op==TK_COLLATE ){ int nColl; + assert( !ExprHasProperty(pListItem->pExpr, EP_IntValue) ); zColl = pListItem->pExpr->u.zToken; nColl = sqlite3Strlen30(zColl) + 1; assert( nExtra>=nColl ); @@ -115482,14 +119391,14 @@ SQLITE_PRIVATE void sqlite3CreateIndex( zExtra += nColl; nExtra -= nColl; }else if( j>=0 ){ - zColl = pTab->aCol[j].zColl; + zColl = sqlite3ColumnColl(&pTab->aCol[j]); } if( !zColl ) zColl = sqlite3StrBINARY; if( !db->init.busy && !sqlite3LocateCollSeq(pParse, zColl) ){ goto exit_create_index; } pIndex->azColl[i] = zColl; - requestedSortOrder = pListItem->sortFlags & sortOrderMask; + requestedSortOrder = pListItem->fg.sortFlags & sortOrderMask; pIndex->aSortOrder[i] = (u8)requestedSortOrder; } @@ -115680,13 +119589,13 @@ SQLITE_PRIVATE void sqlite3CreateIndex( /* Add an entry in sqlite_schema for this index */ sqlite3NestedParse(pParse, - "INSERT INTO %Q." DFLT_SCHEMA_TABLE " VALUES('index',%Q,%Q,#%d,%Q);", - db->aDb[iDb].zDbSName, - pIndex->zName, - pTab->zName, - iMem, - zStmt - ); + "INSERT INTO %Q." LEGACY_SCHEMA_TABLE " VALUES('index',%Q,%Q,#%d,%Q);", + db->aDb[iDb].zDbSName, + pIndex->zName, + pTab->zName, + iMem, + zStmt + ); sqlite3DbFree(db, zStmt); /* Fill the index with data and reparse the schema. Code an OP_Expire @@ -115722,7 +119631,7 @@ exit_create_index: ** The list was already ordered when this routine was entered, so at this ** point at most a single index (the newly added index) will be out of ** order. So we have to reorder at most one index. */ - Index **ppFrom = &pTab->pIndex; + Index **ppFrom; Index *pThis; for(ppFrom=&pTab->pIndex; (pThis = *ppFrom)!=0; ppFrom=&pThis->pNext){ Index *pNext; @@ -115796,7 +119705,7 @@ SQLITE_PRIVATE void sqlite3DefaultRowEst(Index *pIdx){ if( x<99 ){ pIdx->pTable->nRowLogEst = x = 99; } - if( pIdx->pPartIdxWhere!=0 ) x -= 10; assert( 10==sqlite3LogEst(2) ); + if( pIdx->pPartIdxWhere!=0 ){ x -= 10; assert( 10==sqlite3LogEst(2) ); } a[0] = x; /* Estimate that a[1] is 10, a[2] is 9, a[3] is 8, a[4] is 7, a[5] is @@ -115820,10 +119729,10 @@ SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists sqlite3 *db = pParse->db; int iDb; - assert( pParse->nErr==0 ); /* Never called with prior errors */ if( db->mallocFailed ){ goto exit_drop_index; } + assert( pParse->nErr==0 ); /* Never called with prior non-OOM errors */ assert( pName->nSrc==1 ); if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ goto exit_drop_index; @@ -115831,9 +119740,10 @@ SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists pIndex = sqlite3FindIndex(db, pName->a[0].zName, pName->a[0].zDatabase); if( pIndex==0 ){ if( !ifExists ){ - sqlite3ErrorMsg(pParse, "no such index: %S", pName, 0); + sqlite3ErrorMsg(pParse, "no such index: %S", pName->a); }else{ sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase); + sqlite3ForceNotReadOnly(pParse); } pParse->checkSchema = 1; goto exit_drop_index; @@ -115853,7 +119763,7 @@ SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){ goto exit_drop_index; } - if( !OMIT_TEMPDB && iDb ) code = SQLITE_DROP_TEMP_INDEX; + if( !OMIT_TEMPDB && iDb==1 ) code = SQLITE_DROP_TEMP_INDEX; if( sqlite3AuthCheck(pParse, code, pIndex->zName, pTab->zName, zDb) ){ goto exit_drop_index; } @@ -115865,7 +119775,7 @@ SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists if( v ){ sqlite3BeginWriteOperation(pParse, 1, iDb); sqlite3NestedParse(pParse, - "DELETE FROM %Q." DFLT_SCHEMA_TABLE " WHERE name=%Q AND type='index'", + "DELETE FROM %Q." LEGACY_SCHEMA_TABLE " WHERE name=%Q AND type='index'", db->aDb[iDb].zDbSName, pIndex->zName ); sqlite3ClearStatTables(pParse, iDb, "idx", pIndex->zName); @@ -115931,18 +119841,17 @@ SQLITE_PRIVATE IdList *sqlite3IdListAppend(Parse *pParse, IdList *pList, Token * if( pList==0 ){ pList = sqlite3DbMallocZero(db, sizeof(IdList) ); if( pList==0 ) return 0; + }else{ + IdList *pNew; + pNew = sqlite3DbRealloc(db, pList, + sizeof(IdList) + pList->nId*sizeof(pList->a)); + if( pNew==0 ){ + sqlite3IdListDelete(db, pList); + return 0; + } + pList = pNew; } - pList->a = sqlite3ArrayAllocate( - db, - pList->a, - sizeof(pList->a[0]), - &pList->nId, - &i - ); - if( i<0 ){ - sqlite3IdListDelete(db, pList); - return 0; - } + i = pList->nId++; pList->a[i].zName = sqlite3NameFromToken(db, pToken); if( IN_RENAME_OBJECT && pList->a[i].zName ){ sqlite3RenameTokenMap(pParse, (void*)pList->a[i].zName, pToken); @@ -115956,10 +119865,10 @@ SQLITE_PRIVATE IdList *sqlite3IdListAppend(Parse *pParse, IdList *pList, Token * SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3 *db, IdList *pList){ int i; if( pList==0 ) return; + assert( pList->eU4!=EU4_EXPR ); /* EU4_EXPR mode is not currently used */ for(i=0; inId; i++){ sqlite3DbFree(db, pList->a[i].zName); } - sqlite3DbFree(db, pList->a); sqlite3DbFreeNN(db, pList); } @@ -115969,7 +119878,7 @@ SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3 *db, IdList *pList){ */ SQLITE_PRIVATE int sqlite3IdListIndex(IdList *pList, const char *zName){ int i; - if( pList==0 ) return -1; + assert( pList!=0 ); for(i=0; inId; i++){ if( sqlite3StrICmp(pList->a[i].zName, zName)==0 ) return i; } @@ -116145,8 +120054,8 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppend( SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){ int i; SrcItem *pItem; - assert(pList || pParse->db->mallocFailed ); - if( pList ){ + assert( pList || pParse->db->mallocFailed ); + if( ALWAYS(pList) ){ for(i=0, pItem=pList->a; inSrc; i++, pItem++){ if( pItem->iCursor>=0 ) continue; pItem->iCursor = pParse->nTab++; @@ -116172,8 +120081,11 @@ SQLITE_PRIVATE void sqlite3SrcListDelete(sqlite3 *db, SrcList *pList){ if( pItem->fg.isTabFunc ) sqlite3ExprListDelete(db, pItem->u1.pFuncArg); sqlite3DeleteTable(db, pItem->pTab); if( pItem->pSelect ) sqlite3SelectDelete(db, pItem->pSelect); - if( pItem->pOn ) sqlite3ExprDelete(db, pItem->pOn); - if( pItem->pUsing ) sqlite3IdListDelete(db, pItem->pUsing); + if( pItem->fg.isUsing ){ + sqlite3IdListDelete(db, pItem->u3.pUsing); + }else if( pItem->u3.pOn ){ + sqlite3ExprDelete(db, pItem->u3.pOn); + } } sqlite3DbFreeNN(db, pList); } @@ -116201,14 +120113,13 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm( Token *pDatabase, /* Name of the database containing pTable */ Token *pAlias, /* The right-hand side of the AS subexpression */ Select *pSubquery, /* A subquery used in place of a table name */ - Expr *pOn, /* The ON clause of a join */ - IdList *pUsing /* The USING clause of a join */ + OnOrUsing *pOnUsing /* Either the ON clause or the USING clause */ ){ SrcItem *pItem; sqlite3 *db = pParse->db; - if( !p && (pOn || pUsing) ){ + if( !p && pOnUsing!=0 && (pOnUsing->pOn || pOnUsing->pUsing) ){ sqlite3ErrorMsg(pParse, "a JOIN clause is required before %s", - (pOn ? "ON" : "USING") + (pOnUsing->pOn ? "ON" : "USING") ); goto append_from_error; } @@ -116228,15 +120139,27 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm( if( pAlias->n ){ pItem->zAlias = sqlite3NameFromToken(db, pAlias); } - pItem->pSelect = pSubquery; - pItem->pOn = pOn; - pItem->pUsing = pUsing; + if( pSubquery ){ + pItem->pSelect = pSubquery; + if( pSubquery->selFlags & SF_NestedFrom ){ + pItem->fg.isNestedFrom = 1; + } + } + assert( pOnUsing==0 || pOnUsing->pOn==0 || pOnUsing->pUsing==0 ); + assert( pItem->fg.isUsing==0 ); + if( pOnUsing==0 ){ + pItem->u3.pOn = 0; + }else if( pOnUsing->pUsing ){ + pItem->fg.isUsing = 1; + pItem->u3.pUsing = pOnUsing->pUsing; + }else{ + pItem->u3.pOn = pOnUsing->pOn; + } return p; - append_from_error: +append_from_error: assert( p==0 ); - sqlite3ExprDelete(db, pOn); - sqlite3IdListDelete(db, pUsing); + sqlite3ClearOnOrUsing(db, pOnUsing); sqlite3SelectDelete(db, pSubquery); return 0; } @@ -116261,6 +120184,7 @@ SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pI }else{ pItem->u1.zIndexedBy = sqlite3NameFromToken(pParse->db, pIndexedBy); pItem->fg.isIndexedBy = 1; + assert( pItem->fg.isCte==0 ); /* No collision on union u2 */ } } } @@ -116280,6 +120204,7 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, Src p1 = pNew; memcpy(&p1->a[1], p2->a, p2->nSrc*sizeof(SrcItem)); sqlite3DbFree(pParse->db, p2); + p1->a[0].fg.jointype |= (JT_LTORJ & p1->a[1].fg.jointype); } } return p1; @@ -116316,14 +120241,34 @@ SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse *pParse, SrcList *p, ExprList * ** The operator is "natural cross join". The A and B operands are stored ** in p->a[0] and p->a[1], respectively. The parser initially stores the ** operator with A. This routine shifts that operator over to B. +** +** Additional changes: +** +** * All tables to the left of the right-most RIGHT JOIN are tagged with +** JT_LTORJ (mnemonic: Left Table Of Right Join) so that the +** code generator can easily tell that the table is part of +** the left operand of at least one RIGHT JOIN. */ -SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(SrcList *p){ - if( p ){ - int i; - for(i=p->nSrc-1; i>0; i--){ - p->a[i].fg.jointype = p->a[i-1].fg.jointype; - } +SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(Parse *pParse, SrcList *p){ + (void)pParse; + if( p && p->nSrc>1 ){ + int i = p->nSrc-1; + u8 allFlags = 0; + do{ + allFlags |= p->a[i].fg.jointype = p->a[i-1].fg.jointype; + }while( (--i)>0 ); p->a[0].fg.jointype = 0; + + /* All terms to the left of a RIGHT JOIN should be tagged with the + ** JT_LTORJ flags */ + if( allFlags & JT_RIGHT ){ + for(i=p->nSrc-1; ALWAYS(i>0) && (p->a[i].fg.jointype&JT_RIGHT)==0; i--){} + i--; + assert( i>=0 ); + do{ + p->a[i].fg.jointype |= JT_LTORJ; + }while( (--i)>=0 ); + } } } @@ -116573,7 +120518,7 @@ SQLITE_PRIVATE void sqlite3UniqueConstraint( for(j=0; jnKeyCol; j++){ char *zCol; assert( pIdx->aiColumn[j]>=0 ); - zCol = pTab->aCol[pIdx->aiColumn[j]].zName; + zCol = pTab->aCol[pIdx->aiColumn[j]].zCnName; if( j ) sqlite3_str_append(&errMsg, ", ", 2); sqlite3_str_appendall(&errMsg, pTab->zName); sqlite3_str_append(&errMsg, ".", 1); @@ -116600,7 +120545,7 @@ SQLITE_PRIVATE void sqlite3RowidConstraint( int rc; if( pTab->iPKey>=0 ){ zMsg = sqlite3MPrintf(pParse->db, "%s.%s", pTab->zName, - pTab->aCol[pTab->iPKey].zName); + pTab->aCol[pTab->iPKey].zCnName); rc = SQLITE_CONSTRAINT_PRIMARYKEY; }else{ zMsg = sqlite3MPrintf(pParse->db, "%s.rowid", pTab->zName); @@ -117241,6 +121186,7 @@ SQLITE_PRIVATE FuncDef *sqlite3FunctionSearch( ){ FuncDef *p; for(p=sqlite3BuiltinFunctions.a[h]; p; p=p->u.pHash){ + assert( p->funcFlags & SQLITE_FUNC_BUILTIN ); if( sqlite3StrICmp(p->zName, zFunc)==0 ){ return p; } @@ -117261,7 +121207,7 @@ SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs( const char *zName = aDef[i].zName; int nName = sqlite3Strlen30(zName); int h = SQLITE_FUNC_HASH(zName[0], nName); - assert( zName[0]>='a' && zName[0]<='z' ); + assert( aDef[i].funcFlags & SQLITE_FUNC_BUILTIN ); pOther = sqlite3FunctionSearch(h, zName); if( pOther ){ assert( pOther!=&aDef[i] && pOther->pNext!=&aDef[i] ); @@ -117487,6 +121433,16 @@ SQLITE_PRIVATE Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){ return pTab; } +/* Generate byte-code that will report the number of rows modified +** by a DELETE, INSERT, or UPDATE statement. +*/ +SQLITE_PRIVATE void sqlite3CodeChangeCount(Vdbe *v, int regCounter, const char *zColName){ + sqlite3VdbeAddOp0(v, OP_FkCheck); + sqlite3VdbeAddOp2(v, OP_ResultRow, regCounter, 1); + sqlite3VdbeSetNumCols(v, 1); + sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zColName, SQLITE_STATIC); +} + /* Return true if table pTab is read-only. ** ** A table is read-only if any of the following are true: @@ -117527,7 +121483,7 @@ SQLITE_PRIVATE int sqlite3IsReadOnly(Parse *pParse, Table *pTab, int viewOk){ return 1; } #ifndef SQLITE_OMIT_VIEW - if( !viewOk && pTab->pSelect ){ + if( !viewOk && IsView(pTab) ){ sqlite3ErrorMsg(pParse,"cannot modify %s because it is a view",pTab->zName); return 1; } @@ -117561,8 +121517,8 @@ SQLITE_PRIVATE void sqlite3MaterializeView( assert( pFrom->nSrc==1 ); pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName); pFrom->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName); - assert( pFrom->a[0].pOn==0 ); - assert( pFrom->a[0].pUsing==0 ); + assert( pFrom->a[0].fg.isUsing==0 ); + assert( pFrom->a[0].u3.pOn==0 ); } pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, pOrderBy, SF_IncludeHidden, pLimit); @@ -117631,13 +121587,13 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere( }else{ Index *pPk = sqlite3PrimaryKeyIndex(pTab); if( pPk->nKeyCol==1 ){ - const char *zName = pTab->aCol[pPk->aiColumn[0]].zName; + const char *zName = pTab->aCol[pPk->aiColumn[0]].zCnName; pLhs = sqlite3Expr(db, TK_ID, zName); pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_ID, zName)); }else{ int i; for(i=0; inKeyCol; i++){ - Expr *p = sqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zName); + Expr *p = sqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zCnName); pEList = sqlite3ExprListAppend(pParse, pEList, p); } pLhs = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); @@ -117653,6 +121609,7 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere( pSelectSrc = sqlite3SrcListDup(db, pSrc, 0); pSrc->a[0].pTab = pTab; if( pSrc->a[0].fg.isIndexedBy ){ + assert( pSrc->a[0].fg.isCte==0 ); pSrc->a[0].u2.pIBIndex = 0; pSrc->a[0].fg.isIndexedBy = 0; sqlite3DbFree(db, pSrc->a[0].u1.zIndexedBy); @@ -117725,12 +121682,13 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( memset(&sContext, 0, sizeof(sContext)); db = pParse->db; - if( pParse->nErr || db->mallocFailed ){ + assert( db->pParse==pParse ); + if( pParse->nErr ){ goto delete_from_cleanup; } + assert( db->mallocFailed==0 ); assert( pTabList->nSrc==1 ); - /* Locate the table which we want to delete. This table has to be ** put in an SrcList structure because some of the subroutines we ** will be calling are designed to work with multiple tables and expect @@ -117744,7 +121702,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( */ #ifndef SQLITE_OMIT_TRIGGER pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); - isView = pTab->pSelect!=0; + isView = IsView(pTab); #else # define pTrigger 0 # define isView 0 @@ -117755,6 +121713,14 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( # define isView 0 #endif +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Delete() at %s:%d", __FILE__, __LINE__); + sqlite3TreeViewDelete(pParse->pWith, pTabList, pWhere, + pOrderBy, pLimit, pTrigger); + } +#endif + #ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT if( !isView ){ pWhere = sqlite3LimitWhere( @@ -117871,6 +121837,9 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ assert( pIdx->pSchema==pTab->pSchema ); sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb); + if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ + sqlite3VdbeChangeP3(v, -1, memCnt ? memCnt : -1); + } } }else #endif /* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */ @@ -117905,7 +121874,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( ** ONEPASS_SINGLE: One-pass approach - at most one row deleted. ** ONEPASS_MULTI: One-pass approach - any number of rows may be deleted. */ - pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf, iTabCur+1); + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0,0,wcf,iTabCur+1); if( pWInfo==0 ) goto delete_from_cleanup; eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); assert( IsVirtual(pTab)==0 || eOnePass!=ONEPASS_MULTI ); @@ -117991,7 +121960,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( if( eOnePass!=ONEPASS_OFF ){ assert( nKey==nPk ); /* OP_Found will use an unpacked key */ if( !IsVirtual(pTab) && aToOpen[iDataCur-iTabCur] ){ - assert( pPk!=0 || pTab->pSelect!=0 ); + assert( pPk!=0 || IsView(pTab) ); sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, addrBypass, iKey, nKey); VdbeCoverage(v); } @@ -118058,9 +122027,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( ** invoke the callback function. */ if( memCnt ){ - sqlite3VdbeAddOp2(v, OP_ChngCntRow, memCnt, 1); - sqlite3VdbeSetNumCols(v, 1); - sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted", SQLITE_STATIC); + sqlite3CodeChangeCount(v, memCnt, "rows deleted"); } delete_from_cleanup: @@ -118225,7 +122192,7 @@ SQLITE_PRIVATE void sqlite3GenerateRowDelete( ** the update-hook is not invoked for rows removed by REPLACE, but the ** pre-update-hook is. */ - if( pTab->pSelect==0 ){ + if( !IsView(pTab) ){ u8 p5 = 0; sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek); sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0)); @@ -118382,13 +122349,15 @@ SQLITE_PRIVATE int sqlite3GenerateIndexKey( continue; } sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iDataCur, j, regBase+j); - /* If the column affinity is REAL but the number is an integer, then it - ** might be stored in the table as an integer (using a compact - ** representation) then converted to REAL by an OP_RealAffinity opcode. - ** But we are getting ready to store this value back into an index, where - ** it should be converted by to INTEGER again. So omit the OP_RealAffinity - ** opcode if it is present */ - sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity); + if( pIdx->aiColumn[j]>=0 ){ + /* If the column affinity is REAL but the number is an integer, then it + ** might be stored in the table as an integer (using a compact + ** representation) then converted to REAL by an OP_RealAffinity opcode. + ** But we are getting ready to store this value back into an index, where + ** it should be converted by to INTEGER again. So omit the + ** OP_RealAffinity opcode if it is present */ + sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity); + } } if( regOut ){ sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut); @@ -118509,6 +122478,18 @@ static void typeofFunc( sqlite3_result_text(context, azType[i], -1, SQLITE_STATIC); } +/* subtype(X) +** +** Return the subtype of X +*/ +static void subtypeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + UNUSED_PARAMETER(argc); + sqlite3_result_int(context, sqlite3_value_subtype(argv[0])); +} /* ** Implementation of the length() function @@ -118670,7 +122651,7 @@ endInstrOOM: } /* -** Implementation of the printf() function. +** Implementation of the printf() (a.k.a. format()) SQL function. */ static void printfFunc( sqlite3_context *context, @@ -118983,9 +122964,9 @@ static void last_insert_rowid( /* ** Implementation of the changes() SQL function. ** -** IMP: R-62073-11209 The changes() SQL function is a wrapper -** around the sqlite3_changes() C/C++ function and hence follows the same -** rules for counting changes. +** IMP: R-32760-32347 The changes() SQL function is a wrapper +** around the sqlite3_changes64() C/C++ function and hence follows the +** same rules for counting changes. */ static void changes( sqlite3_context *context, @@ -118994,12 +122975,12 @@ static void changes( ){ sqlite3 *db = sqlite3_context_db_handle(context); UNUSED_PARAMETER2(NotUsed, NotUsed2); - sqlite3_result_int(context, sqlite3_changes(db)); + sqlite3_result_int64(context, sqlite3_changes64(db)); } /* ** Implementation of the total_changes() SQL function. The return value is -** the same as the sqlite3_total_changes() API function. +** the same as the sqlite3_total_changes64() API function. */ static void total_changes( sqlite3_context *context, @@ -119008,9 +122989,9 @@ static void total_changes( ){ sqlite3 *db = sqlite3_context_db_handle(context); UNUSED_PARAMETER2(NotUsed, NotUsed2); - /* IMP: R-52756-41993 This function is a wrapper around the - ** sqlite3_total_changes() C/C++ interface. */ - sqlite3_result_int(context, sqlite3_total_changes(db)); + /* IMP: R-11217-42568 This function is a wrapper around the + ** sqlite3_total_changes64() C/C++ interface. */ + sqlite3_result_int64(context, sqlite3_total_changes64(db)); } /* @@ -119439,39 +123420,42 @@ static const char hexdigits[] = { }; /* -** Implementation of the QUOTE() function. This function takes a single -** argument. If the argument is numeric, the return value is the same as -** the argument. If the argument is NULL, the return value is the string -** "NULL". Otherwise, the argument is enclosed in single quotes with -** single-quote escapes. +** Append to pStr text that is the SQL literal representation of the +** value contained in pValue. */ -static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ - assert( argc==1 ); - UNUSED_PARAMETER(argc); - switch( sqlite3_value_type(argv[0]) ){ +SQLITE_PRIVATE void sqlite3QuoteValue(StrAccum *pStr, sqlite3_value *pValue){ + /* As currently implemented, the string must be initially empty. + ** we might relax this requirement in the future, but that will + ** require enhancements to the implementation. */ + assert( pStr!=0 && pStr->nChar==0 ); + + switch( sqlite3_value_type(pValue) ){ case SQLITE_FLOAT: { double r1, r2; - char zBuf[50]; - r1 = sqlite3_value_double(argv[0]); - sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1); - sqlite3AtoF(zBuf, &r2, 20, SQLITE_UTF8); - if( r1!=r2 ){ - sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.20e", r1); + const char *zVal; + r1 = sqlite3_value_double(pValue); + sqlite3_str_appendf(pStr, "%!.15g", r1); + zVal = sqlite3_str_value(pStr); + if( zVal ){ + sqlite3AtoF(zVal, &r2, pStr->nChar, SQLITE_UTF8); + if( r1!=r2 ){ + sqlite3_str_reset(pStr); + sqlite3_str_appendf(pStr, "%!.20e", r1); + } } - sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); break; } case SQLITE_INTEGER: { - sqlite3_result_value(context, argv[0]); + sqlite3_str_appendf(pStr, "%lld", sqlite3_value_int64(pValue)); break; } case SQLITE_BLOB: { - char *zText = 0; - char const *zBlob = sqlite3_value_blob(argv[0]); - int nBlob = sqlite3_value_bytes(argv[0]); - assert( zBlob==sqlite3_value_blob(argv[0]) ); /* No encoding change */ - zText = (char *)contextMalloc(context, (2*(i64)nBlob)+4); - if( zText ){ + char const *zBlob = sqlite3_value_blob(pValue); + int nBlob = sqlite3_value_bytes(pValue); + assert( zBlob==sqlite3_value_blob(pValue) ); /* No encoding change */ + sqlite3StrAccumEnlarge(pStr, nBlob*2 + 4); + if( pStr->accError==0 ){ + char *zText = pStr->zText; int i; for(i=0; i>4)&0x0F]; @@ -119481,42 +123465,48 @@ static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ zText[(nBlob*2)+3] = '\0'; zText[0] = 'X'; zText[1] = '\''; - sqlite3_result_text(context, zText, -1, SQLITE_TRANSIENT); - sqlite3_free(zText); + pStr->nChar = nBlob*2 + 3; } break; } case SQLITE_TEXT: { - int i,j; - u64 n; - const unsigned char *zArg = sqlite3_value_text(argv[0]); - char *z; - - if( zArg==0 ) return; - for(i=0, n=0; zArg[i]; i++){ if( zArg[i]=='\'' ) n++; } - z = contextMalloc(context, ((i64)i)+((i64)n)+3); - if( z ){ - z[0] = '\''; - for(i=0, j=1; zArg[i]; i++){ - z[j++] = zArg[i]; - if( zArg[i]=='\'' ){ - z[j++] = '\''; - } - } - z[j++] = '\''; - z[j] = 0; - sqlite3_result_text(context, z, j, sqlite3_free); - } + const unsigned char *zArg = sqlite3_value_text(pValue); + sqlite3_str_appendf(pStr, "%Q", zArg); break; } default: { - assert( sqlite3_value_type(argv[0])==SQLITE_NULL ); - sqlite3_result_text(context, "NULL", 4, SQLITE_STATIC); + assert( sqlite3_value_type(pValue)==SQLITE_NULL ); + sqlite3_str_append(pStr, "NULL", 4); break; } } } +/* +** Implementation of the QUOTE() function. +** +** The quote(X) function returns the text of an SQL literal which is the +** value of its argument suitable for inclusion into an SQL statement. +** Strings are surrounded by single-quotes with escapes on interior quotes +** as needed. BLOBs are encoded as hexadecimal literals. Strings with +** embedded NUL characters cannot be represented as string literals in SQL +** and hence the returned string literal is truncated prior to the first NUL. +*/ +static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ + sqlite3_str str; + sqlite3 *db = sqlite3_context_db_handle(context); + assert( argc==1 ); + UNUSED_PARAMETER(argc); + sqlite3StrAccumInit(&str, db, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]); + sqlite3QuoteValue(&str,argv[0]); + sqlite3_result_text(context, sqlite3StrAccumFinish(&str), str.nChar, + SQLITE_DYNAMIC); + if( str.accError!=SQLITE_OK ){ + sqlite3_result_null(context); + sqlite3_result_error_code(context, str.accError); + } +} + /* ** The unicode() function. Return the integer unicode code-point value ** for the first character of the input string. @@ -119728,10 +123718,10 @@ static void trimFunc( ){ const unsigned char *zIn; /* Input string */ const unsigned char *zCharSet; /* Set of characters to trim */ - int nIn; /* Number of bytes in input */ + unsigned int nIn; /* Number of bytes in input */ int flags; /* 1: trimleft 2: trimright 3: trim */ int i; /* Loop counter */ - unsigned char *aLen = 0; /* Length of each character in zCharSet */ + unsigned int *aLen = 0; /* Length of each character in zCharSet */ unsigned char **azChar = 0; /* Individual characters in zCharSet */ int nChar; /* Number of characters in zCharSet */ @@ -119740,13 +123730,13 @@ static void trimFunc( } zIn = sqlite3_value_text(argv[0]); if( zIn==0 ) return; - nIn = sqlite3_value_bytes(argv[0]); + nIn = (unsigned)sqlite3_value_bytes(argv[0]); assert( zIn==sqlite3_value_text(argv[0]) ); if( argc==1 ){ - static const unsigned char lenOne[] = { 1 }; + static const unsigned lenOne[] = { 1 }; static unsigned char * const azOne[] = { (u8*)" " }; nChar = 1; - aLen = (u8*)lenOne; + aLen = (unsigned*)lenOne; azChar = (unsigned char **)azOne; zCharSet = 0; }else if( (zCharSet = sqlite3_value_text(argv[1]))==0 ){ @@ -119757,15 +123747,16 @@ static void trimFunc( SQLITE_SKIP_UTF8(z); } if( nChar>0 ){ - azChar = contextMalloc(context, ((i64)nChar)*(sizeof(char*)+1)); + azChar = contextMalloc(context, + ((i64)nChar)*(sizeof(char*)+sizeof(unsigned))); if( azChar==0 ){ return; } - aLen = (unsigned char*)&azChar[nChar]; + aLen = (unsigned*)&azChar[nChar]; for(z=zCharSet, nChar=0; *z; nChar++){ azChar[nChar] = (unsigned char *)z; SQLITE_SKIP_UTF8(z); - aLen[nChar] = (u8)(z - azChar[nChar]); + aLen[nChar] = (unsigned)(z - azChar[nChar]); } } } @@ -119773,7 +123764,7 @@ static void trimFunc( flags = SQLITE_PTR_TO_INT(sqlite3_user_data(context)); if( flags & 1 ){ while( nIn>0 ){ - int len = 0; + unsigned int len = 0; for(i=0; i0 ){ - int len = 0; + unsigned int len = 0; for(i=0; imxAlloc==0; - pAccum->mxAlloc = db->aLimit[SQLITE_LIMIT_LENGTH]; - if( !firstTerm ){ - if( argc==2 ){ - zSep = (char*)sqlite3_value_text(argv[1]); - nSep = sqlite3_value_bytes(argv[1]); - }else{ - zSep = ","; - nSep = 1; + int firstTerm = pGCC->str.mxAlloc==0; + pGCC->str.mxAlloc = db->aLimit[SQLITE_LIMIT_LENGTH]; + if( argc==1 ){ + if( !firstTerm ){ + sqlite3_str_appendchar(&pGCC->str, 1, ','); } - if( zSep ) sqlite3_str_append(pAccum, zSep, nSep); +#ifndef SQLITE_OMIT_WINDOWFUNC + else{ + pGCC->nFirstSepLength = 1; + } +#endif + }else if( !firstTerm ){ + zSep = (char*)sqlite3_value_text(argv[1]); + nSep = sqlite3_value_bytes(argv[1]); + if( zSep ){ + sqlite3_str_append(&pGCC->str, zSep, nSep); + } +#ifndef SQLITE_OMIT_WINDOWFUNC + else{ + nSep = 0; + } + if( nSep != pGCC->nFirstSepLength || pGCC->pnSepLengths != 0 ){ + int *pnsl = pGCC->pnSepLengths; + if( pnsl == 0 ){ + /* First separator length variation seen, start tracking them. */ + pnsl = (int*)sqlite3_malloc64((pGCC->nAccum+1) * sizeof(int)); + if( pnsl!=0 ){ + int i = 0, nA = pGCC->nAccum-1; + while( inFirstSepLength; + } + }else{ + pnsl = (int*)sqlite3_realloc64(pnsl, pGCC->nAccum * sizeof(int)); + } + if( pnsl!=0 ){ + if( ALWAYS(pGCC->nAccum>0) ){ + pnsl[pGCC->nAccum-1] = nSep; + } + pGCC->pnSepLengths = pnsl; + }else{ + sqlite3StrAccumSetError(&pGCC->str, SQLITE_NOMEM); + } + } +#endif } +#ifndef SQLITE_OMIT_WINDOWFUNC + else{ + pGCC->nFirstSepLength = sqlite3_value_bytes(argv[1]); + } + pGCC->nAccum += 1; +#endif zVal = (char*)sqlite3_value_text(argv[0]); nVal = sqlite3_value_bytes(argv[0]); - if( zVal ) sqlite3_str_append(pAccum, zVal, nVal); + if( zVal ) sqlite3_str_append(&pGCC->str, zVal, nVal); } } + #ifndef SQLITE_OMIT_WINDOWFUNC static void groupConcatInverse( sqlite3_context *context, int argc, sqlite3_value **argv ){ - int n; - StrAccum *pAccum; + GroupConcatCtx *pGCC; assert( argc==1 || argc==2 ); + (void)argc; /* Suppress unused parameter warning */ if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - pAccum = (StrAccum*)sqlite3_aggregate_context(context, sizeof(*pAccum)); - /* pAccum is always non-NULL since groupConcatStep() will have always + pGCC = (GroupConcatCtx*)sqlite3_aggregate_context(context, sizeof(*pGCC)); + /* pGCC is always non-NULL since groupConcatStep() will have always ** run frist to initialize it */ - if( ALWAYS(pAccum) ){ - n = sqlite3_value_bytes(argv[0]); - if( argc==2 ){ - n += sqlite3_value_bytes(argv[1]); + if( ALWAYS(pGCC) ){ + int nVS; + /* Must call sqlite3_value_text() to convert the argument into text prior + ** to invoking sqlite3_value_bytes(), in case the text encoding is UTF16 */ + (void)sqlite3_value_text(argv[0]); + nVS = sqlite3_value_bytes(argv[0]); + pGCC->nAccum -= 1; + if( pGCC->pnSepLengths!=0 ){ + assert(pGCC->nAccum >= 0); + if( pGCC->nAccum>0 ){ + nVS += *pGCC->pnSepLengths; + memmove(pGCC->pnSepLengths, pGCC->pnSepLengths+1, + (pGCC->nAccum-1)*sizeof(int)); + } }else{ - n++; + /* If removing single accumulated string, harmlessly over-do. */ + nVS += pGCC->nFirstSepLength; } - if( n>=(int)pAccum->nChar ){ - pAccum->nChar = 0; + if( nVS>=(int)pGCC->str.nChar ){ + pGCC->str.nChar = 0; }else{ - pAccum->nChar -= n; - memmove(pAccum->zText, &pAccum->zText[n], pAccum->nChar); + pGCC->str.nChar -= nVS; + memmove(pGCC->str.zText, &pGCC->str.zText[nVS], pGCC->str.nChar); + } + if( pGCC->str.nChar==0 ){ + pGCC->str.mxAlloc = 0; + sqlite3_free(pGCC->pnSepLengths); + pGCC->pnSepLengths = 0; } - if( pAccum->nChar==0 ) pAccum->mxAlloc = 0; } } #else # define groupConcatInverse 0 #endif /* SQLITE_OMIT_WINDOWFUNC */ static void groupConcatFinalize(sqlite3_context *context){ - StrAccum *pAccum; - pAccum = sqlite3_aggregate_context(context, 0); - if( pAccum ){ - if( pAccum->accError==SQLITE_TOOBIG ){ - sqlite3_result_error_toobig(context); - }else if( pAccum->accError==SQLITE_NOMEM ){ - sqlite3_result_error_nomem(context); - }else{ - sqlite3_result_text(context, sqlite3StrAccumFinish(pAccum), -1, - sqlite3_free); - } + GroupConcatCtx *pGCC + = (GroupConcatCtx*)sqlite3_aggregate_context(context, 0); + if( pGCC ){ + sqlite3ResultStrAccum(context, &pGCC->str); +#ifndef SQLITE_OMIT_WINDOWFUNC + sqlite3_free(pGCC->pnSepLengths); +#endif } } #ifndef SQLITE_OMIT_WINDOWFUNC static void groupConcatValue(sqlite3_context *context){ - sqlite3_str *pAccum; - pAccum = (sqlite3_str*)sqlite3_aggregate_context(context, 0); - if( pAccum ){ + GroupConcatCtx *pGCC + = (GroupConcatCtx*)sqlite3_aggregate_context(context, 0); + if( pGCC ){ + StrAccum *pAccum = &pGCC->str; if( pAccum->accError==SQLITE_TOOBIG ){ sqlite3_result_error_toobig(context); }else if( pAccum->accError==SQLITE_NOMEM ){ sqlite3_result_error_nomem(context); }else{ const char *zText = sqlite3_str_value(pAccum); - sqlite3_result_text(context, zText, -1, SQLITE_TRANSIENT); + sqlite3_result_text(context, zText, pAccum->nChar, SQLITE_TRANSIENT); } } } @@ -120280,11 +124341,12 @@ SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocas int nExpr; assert( pExpr!=0 ); assert( pExpr->op==TK_FUNCTION ); + assert( ExprUseXList(pExpr) ); if( !pExpr->x.pList ){ return 0; } - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); nExpr = pExpr->x.pList->nExpr; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); pDef = sqlite3FindFunction(db, pExpr->u.zToken, nExpr, SQLITE_UTF8, 0); #ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION if( pDef==0 ) return 0; @@ -120308,6 +124370,7 @@ SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocas Expr *pEscape = pExpr->x.pList->a[2].pExpr; char *zEscape; if( pEscape->op!=TK_STRING ) return 0; + assert( !ExprHasProperty(pEscape, EP_IntValue) ); zEscape = pEscape->u.zToken; if( zEscape[0]==0 || zEscape[1]!=0 ) return 0; if( zEscape[0]==aWc[0] ) return 0; @@ -120416,11 +124479,11 @@ static void logFunc( switch( SQLITE_PTR_TO_INT(sqlite3_user_data(context)) ){ case 1: /* Convert from natural logarithm to log base 10 */ - ans *= 1.0/M_LN10; + ans /= M_LN10; break; case 2: /* Convert from natural logarithm to log base 2 */ - ans *= 1.0/M_LN2; + ans /= M_LN2; break; default: break; @@ -120483,9 +124546,7 @@ static void math2Func( } /* -** Implementation of 2-argument SQL math functions: -** -** power(X,Y) - Compute X to the Y-th power +** Implementation of 0-argument pi() function. */ static void piFunc( sqlite3_context *context, @@ -120536,12 +124597,12 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ */ static FuncDef aBuiltinFunc[] = { /***** Functions only available with SQLITE_TESTCTRL_INTERNAL_FUNCTIONS *****/ +#if !defined(SQLITE_UNTESTABLE) TEST_FUNC(implies_nonnull_row, 2, INLINEFUNC_implies_nonnull_row, 0), TEST_FUNC(expr_compare, 2, INLINEFUNC_expr_compare, 0), TEST_FUNC(expr_implies_expr, 2, INLINEFUNC_expr_implies_expr, 0), -#ifdef SQLITE_DEBUG - TEST_FUNC(affinity, 1, INLINEFUNC_affinity, 0), -#endif + TEST_FUNC(affinity, 1, INLINEFUNC_affinity, 0), +#endif /* !defined(SQLITE_UNTESTABLE) */ /***** Regular functions *****/ #ifdef SQLITE_SOUNDEX FUNCTION(soundex, 1, 0, 0, soundexFunc ), @@ -120561,8 +124622,7 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ INLINE_FUNC(likelihood, 2, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY), INLINE_FUNC(likely, 1, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY), #ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC - FUNCTION2(sqlite_offset, 1, 0, 0, noopFunc, SQLITE_FUNC_OFFSET| - SQLITE_FUNC_TYPEOF), + INLINE_FUNC(sqlite_offset, 1, INLINEFUNC_sqlite_offset, 0 ), #endif FUNCTION(ltrim, 1, 1, 0, trimFunc ), FUNCTION(ltrim, 2, 1, 0, trimFunc ), @@ -120573,15 +124633,17 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ FUNCTION(min, -1, 0, 1, minmaxFunc ), FUNCTION(min, 0, 0, 1, 0 ), WAGGREGATE(min, 1, 0, 1, minmaxStep, minMaxFinalize, minMaxValue, 0, - SQLITE_FUNC_MINMAX ), + SQLITE_FUNC_MINMAX|SQLITE_FUNC_ANYORDER ), FUNCTION(max, -1, 1, 1, minmaxFunc ), FUNCTION(max, 0, 1, 1, 0 ), WAGGREGATE(max, 1, 1, 1, minmaxStep, minMaxFinalize, minMaxValue, 0, - SQLITE_FUNC_MINMAX ), + SQLITE_FUNC_MINMAX|SQLITE_FUNC_ANYORDER ), FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQLITE_FUNC_TYPEOF), + FUNCTION2(subtype, 1, 0, 0, subtypeFunc, SQLITE_FUNC_TYPEOF), FUNCTION2(length, 1, 0, 0, lengthFunc, SQLITE_FUNC_LENGTH), FUNCTION(instr, 2, 0, 0, instrFunc ), FUNCTION(printf, -1, 0, 0, printfFunc ), + FUNCTION(format, -1, 0, 0, printfFunc ), FUNCTION(unicode, 1, 0, 0, unicodeFunc ), FUNCTION(char, -1, 0, 0, charFunc ), FUNCTION(abs, 1, 0, 0, absFunc ), @@ -120613,9 +124675,10 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ WAGGREGATE(total, 1,0,0, sumStep,totalFinalize,totalFinalize,sumInverse, 0), WAGGREGATE(avg, 1,0,0, sumStep, avgFinalize, avgFinalize, sumInverse, 0), WAGGREGATE(count, 0,0,0, countStep, - countFinalize, countFinalize, countInverse, SQLITE_FUNC_COUNT ), + countFinalize, countFinalize, countInverse, + SQLITE_FUNC_COUNT|SQLITE_FUNC_ANYORDER ), WAGGREGATE(count, 1,0,0, countStep, - countFinalize, countFinalize, countInverse, 0 ), + countFinalize, countFinalize, countInverse, SQLITE_FUNC_ANYORDER ), WAGGREGATE(group_concat, 1, 0, 0, groupConcatStep, groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), WAGGREGATE(group_concat, 2, 0, 0, groupConcatStep, @@ -120679,6 +124742,7 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ #endif sqlite3WindowFunctions(); sqlite3RegisterDateTimeFunctions(); + sqlite3RegisterJsonFunctions(); sqlite3InsertBuiltinFuncs(aBuiltinFunc, ArraySize(aBuiltinFunc)); #if 0 /* Enable to print out how the built-in functions are hashed */ @@ -120690,6 +124754,7 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ for(p=sqlite3BuiltinFunctions.a[i]; p; p=p->u.pHash){ int n = sqlite3Strlen30(p->zName); int h = p->zName[0] + n; + assert( p->funcFlags & SQLITE_FUNC_BUILTIN ); printf(" %s(%d)", p->zName, h); } printf("\n"); @@ -120917,7 +124982,9 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex( */ if( pParent->iPKey>=0 ){ if( !zKey ) return 0; - if( !sqlite3StrICmp(pParent->aCol[pParent->iPKey].zName, zKey) ) return 0; + if( !sqlite3StrICmp(pParent->aCol[pParent->iPKey].zCnName, zKey) ){ + return 0; + } } }else if( paiCol ){ assert( nCol>1 ); @@ -120959,11 +125026,11 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex( /* If the index uses a collation sequence that is different from ** the default collation sequence for the column, this index is ** unusable. Bail out early in this case. */ - zDfltColl = pParent->aCol[iCol].zColl; + zDfltColl = sqlite3ColumnColl(&pParent->aCol[iCol]); if( !zDfltColl ) zDfltColl = sqlite3StrBINARY; if( sqlite3StrICmp(pIdx->azColl[i], zDfltColl) ) break; - zIdxCol = pParent->aCol[iCol].zName; + zIdxCol = pParent->aCol[iCol].zCnName; for(j=0; jaCol[j].zCol, zIdxCol)==0 ){ if( aiCol ) aiCol[i] = pFKey->aCol[j].iFrom; @@ -121090,7 +125157,6 @@ static void fkLookupParent( }else{ int nCol = pFKey->nCol; int regTemp = sqlite3GetTempRange(pParse, nCol); - int regRec = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_OpenRead, iCur, pIdx->tnum, iDb); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); @@ -121130,11 +125196,10 @@ static void fkLookupParent( sqlite3VdbeGoto(v, iOk); } - sqlite3VdbeAddOp4(v, OP_MakeRecord, regTemp, nCol, regRec, + sqlite3VdbeAddOp4(v, OP_Affinity, regTemp, nCol, 0, sqlite3IndexAffinityStr(pParse->db,pIdx), nCol); - sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regRec, 0); VdbeCoverage(v); - - sqlite3ReleaseTempReg(pParse, regRec); + sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regTemp, nCol); + VdbeCoverage(v); sqlite3ReleaseTempRange(pParse, regTemp, nCol); } } @@ -121187,7 +125252,7 @@ static Expr *exprTableRegister( pCol = &pTab->aCol[iCol]; pExpr->iTable = regBase + sqlite3TableColumnToStorage(pTab,iCol) + 1; pExpr->affExpr = pCol->affinity; - zColl = pCol->zColl; + zColl = sqlite3ColumnColl(pCol); if( zColl==0 ) zColl = db->pDfltColl->zName; pExpr = sqlite3ExprAddCollateString(pParse, pExpr, zColl); }else{ @@ -121210,6 +125275,7 @@ static Expr *exprTableColumn( ){ Expr *pExpr = sqlite3Expr(db, TK_COLUMN, 0); if( pExpr ){ + assert( ExprUseYTab(pExpr) ); pExpr->y.pTab = pTab; pExpr->iTable = iCursor; pExpr->iColumn = iCol; @@ -121235,14 +125301,10 @@ static Expr *exprTableColumn( ** Operation | FK type | Action taken ** -------------------------------------------------------------------------- ** DELETE immediate Increment the "immediate constraint counter". -** Or, if the ON (UPDATE|DELETE) action is RESTRICT, -** throw a "FOREIGN KEY constraint failed" exception. ** ** INSERT immediate Decrement the "immediate constraint counter". ** ** DELETE deferred Increment the "deferred constraint counter". -** Or, if the ON (UPDATE|DELETE) action is RESTRICT, -** throw a "FOREIGN KEY constraint failed" exception. ** ** INSERT deferred Decrement the "deferred constraint counter". ** @@ -121296,7 +125358,7 @@ static void fkScanChildren( pLeft = exprTableRegister(pParse, pTab, regData, iCol); iCol = aiCol ? aiCol[i] : pFKey->aCol[0].iFrom; assert( iCol>=0 ); - zCol = pFKey->pFrom->aCol[iCol].zName; + zCol = pFKey->pFrom->aCol[iCol].zCnName; pRight = sqlite3Expr(db, TK_ID, zCol); pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight); pWhere = sqlite3ExprAnd(pParse, pWhere, pEq); @@ -121331,7 +125393,7 @@ static void fkScanChildren( i16 iCol = pIdx->aiColumn[i]; assert( iCol>=0 ); pLeft = exprTableRegister(pParse, pTab, regData, iCol); - pRight = sqlite3Expr(db, TK_ID, pTab->aCol[iCol].zName); + pRight = sqlite3Expr(db, TK_ID, pTab->aCol[iCol].zCnName); pEq = sqlite3PExpr(pParse, TK_IS, pLeft, pRight); pAll = sqlite3ExprAnd(pParse, pAll, pEq); } @@ -121350,7 +125412,7 @@ static void fkScanChildren( ** clause. For each row found, increment either the deferred or immediate ** foreign key constraint counter. */ if( pParse->nErr==0 ){ - pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0); + pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0, 0); sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); if( pWInfo ){ sqlite3WhereEnd(pWInfo); @@ -121401,6 +125463,25 @@ static void fkTriggerDelete(sqlite3 *dbMem, Trigger *p){ } } +/* +** Clear the apTrigger[] cache of CASCADE triggers for all foreign keys +** in a particular database. This needs to happen when the schema +** changes. +*/ +SQLITE_PRIVATE void sqlite3FkClearTriggerCache(sqlite3 *db, int iDb){ + HashElem *k; + Hash *pHash = &db->aDb[iDb].pSchema->tblHash; + for(k=sqliteHashFirst(pHash); k; k=sqliteHashNext(k)){ + Table *pTab = sqliteHashData(k); + FKey *pFKey; + if( !IsOrdinaryTable(pTab) ) continue; + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ + fkTriggerDelete(db, pFKey->apTrigger[0]); pFKey->apTrigger[0] = 0; + fkTriggerDelete(db, pFKey->apTrigger[1]); pFKey->apTrigger[1] = 0; + } + } +} + /* ** This function is called to generate code that runs when table pTab is ** being dropped from the database. The SrcList passed as the second argument @@ -121420,12 +125501,12 @@ static void fkTriggerDelete(sqlite3 *dbMem, Trigger *p){ */ SQLITE_PRIVATE void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTab){ sqlite3 *db = pParse->db; - if( (db->flags&SQLITE_ForeignKeys) && !IsVirtual(pTab) ){ + if( (db->flags&SQLITE_ForeignKeys) && IsOrdinaryTable(pTab) ){ int iSkip = 0; Vdbe *v = sqlite3GetVdbe(pParse); assert( v ); /* VDBE has already been allocated */ - assert( pTab->pSelect==0 ); /* Not a view */ + assert( IsOrdinaryTable(pTab) ); if( sqlite3FkReferences(pTab)==0 ){ /* Search for a deferred foreign key constraint for which this table ** is the child table. If one cannot be found, return without @@ -121433,7 +125514,7 @@ SQLITE_PRIVATE void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTa ** the entire DELETE if there are no outstanding deferred constraints ** when this statement is run. */ FKey *p; - for(p=pTab->pFKey; p; p=p->pNextFrom){ + for(p=pTab->u.tab.pFKey; p; p=p->pNextFrom){ if( p->isDeferred || (db->flags & SQLITE_DeferFKs) ) break; } if( !p ) return; @@ -121522,7 +125603,7 @@ static int fkParentIsModified( if( aChange[iKey]>=0 || (iKey==pTab->iPKey && bChngRowid) ){ Column *pCol = &pTab->aCol[iKey]; if( zKey ){ - if( 0==sqlite3StrICmp(pCol->zName, zKey) ) return 1; + if( 0==sqlite3StrICmp(pCol->zCnName, zKey) ) return 1; }else if( pCol->colFlags & COLFLAG_PRIMKEY ){ return 1; } @@ -121589,13 +125670,14 @@ SQLITE_PRIVATE void sqlite3FkCheck( /* If foreign-keys are disabled, this function is a no-op. */ if( (db->flags&SQLITE_ForeignKeys)==0 ) return; + if( !IsOrdinaryTable(pTab) ) return; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); zDb = db->aDb[iDb].zDbSName; /* Loop through all the foreign key constraints for which pTab is the ** child table (the table that the foreign key definition is part of). */ - for(pFKey=pTab->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ Table *pTo; /* Parent table of foreign key pFKey */ Index *pIdx = 0; /* Index on key columns in pTo */ int *aiFree = 0; @@ -121662,7 +125744,7 @@ SQLITE_PRIVATE void sqlite3FkCheck( ** values read from the parent table are NULL. */ if( db->xAuth ){ int rcauth; - char *zCol = pTo->aCol[pIdx ? pIdx->aiColumn[i] : pTo->iPKey].zName; + char *zCol = pTo->aCol[pIdx ? pIdx->aiColumn[i] : pTo->iPKey].zCnName; rcauth = sqlite3AuthReadCol(pParse, pTo->zName, zCol, iDb); bIgnore = (rcauth==SQLITE_IGNORE); } @@ -121777,10 +125859,10 @@ SQLITE_PRIVATE u32 sqlite3FkOldmask( Table *pTab /* Table being modified */ ){ u32 mask = 0; - if( pParse->db->flags&SQLITE_ForeignKeys ){ + if( pParse->db->flags&SQLITE_ForeignKeys && IsOrdinaryTable(pTab) ){ FKey *p; int i; - for(p=pTab->pFKey; p; p=p->pNextFrom){ + for(p=pTab->u.tab.pFKey; p; p=p->pNextFrom){ for(i=0; inCol; i++) mask |= COLUMN_MASK(p->aCol[i].iFrom); } for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){ @@ -121830,19 +125912,19 @@ SQLITE_PRIVATE int sqlite3FkRequired( ){ int eRet = 1; /* Value to return if bHaveFK is true */ int bHaveFK = 0; /* If FK processing is required */ - if( pParse->db->flags&SQLITE_ForeignKeys ){ + if( pParse->db->flags&SQLITE_ForeignKeys && IsOrdinaryTable(pTab) ){ if( !aChange ){ /* A DELETE operation. Foreign key processing is required if the ** table in question is either the child or parent table for any ** foreign key constraint. */ - bHaveFK = (sqlite3FkReferences(pTab) || pTab->pFKey); + bHaveFK = (sqlite3FkReferences(pTab) || pTab->u.tab.pFKey); }else{ /* This is an UPDATE. Foreign key processing is only required if the ** operation modifies one or more child or parent key columns. */ FKey *p; /* Check if any child key columns are being modified. */ - for(p=pTab->pFKey; p; p=p->pNextFrom){ + for(p=pTab->u.tab.pFKey; p; p=p->pNextFrom){ if( fkChildIsModified(pTab, p, aChange, chngRowid) ){ if( 0==sqlite3_stricmp(pTab->zName, p->zTo) ) eRet = 2; bHaveFK = 1; @@ -121870,9 +125952,9 @@ SQLITE_PRIVATE int sqlite3FkRequired( ** ** It returns a pointer to a Trigger structure containing a trigger ** equivalent to the ON UPDATE or ON DELETE action specified by pFKey. -** If the action is "NO ACTION" or "RESTRICT", then a NULL pointer is -** returned (these actions require no special handling by the triggers -** sub-system, code for them is created by fkScanChildren()). +** If the action is "NO ACTION" then a NULL pointer is returned (these actions +** require no special handling by the triggers sub-system, code for them is +** created by fkScanChildren()). ** ** For example, if pFKey is the foreign key and pTab is table "p" in ** the following schema: @@ -121935,8 +126017,8 @@ static Trigger *fkActionTrigger( assert( pIdx!=0 || (pTab->iPKey>=0 && pTab->iPKeynCol) ); assert( pIdx==0 || pIdx->aiColumn[i]>=0 ); sqlite3TokenInit(&tToCol, - pTab->aCol[pIdx ? pIdx->aiColumn[i] : pTab->iPKey].zName); - sqlite3TokenInit(&tFromCol, pFKey->pFrom->aCol[iFromCol].zName); + pTab->aCol[pIdx ? pIdx->aiColumn[i] : pTab->iPKey].zCnName); + sqlite3TokenInit(&tFromCol, pFKey->pFrom->aCol[iFromCol].zCnName); /* Create the expression "OLD.zToCol = zFromCol". It is important ** that the "OLD.zToCol" term is on the LHS of the = operator, so @@ -121981,7 +126063,7 @@ static Trigger *fkActionTrigger( testcase( pCol->colFlags & COLFLAG_STORED ); pDflt = 0; }else{ - pDflt = pCol->pDflt; + pDflt = sqlite3ColumnExpr(pFKey->pFrom, pCol); } if( pDflt ){ pNew = sqlite3ExprDup(db, pDflt, 0); @@ -122001,18 +126083,23 @@ static Trigger *fkActionTrigger( nFrom = sqlite3Strlen30(zFrom); if( action==OE_Restrict ){ + int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); Token tFrom; + Token tDb; Expr *pRaise; tFrom.z = zFrom; tFrom.n = nFrom; + tDb.z = db->aDb[iDb].zDbSName; + tDb.n = sqlite3Strlen30(tDb.z); + pRaise = sqlite3Expr(db, TK_RAISE, "FOREIGN KEY constraint failed"); if( pRaise ){ pRaise->affExpr = OE_Abort; } pSelect = sqlite3SelectNew(pParse, sqlite3ExprListAppend(pParse, 0, pRaise), - sqlite3SrcListAppend(pParse, 0, &tFrom, 0), + sqlite3SrcListAppend(pParse, 0, &tDb, &tFrom), pWhere, 0, 0, 0, 0, 0 ); @@ -122118,9 +126205,9 @@ SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *db, Table *pTab){ FKey *pFKey; /* Iterator variable */ FKey *pNext; /* Copy of pFKey->pNextFrom */ - assert( db==0 || IsVirtual(pTab) - || sqlite3SchemaMutexHeld(db, 0, pTab->pSchema) ); - for(pFKey=pTab->pFKey; pFKey; pFKey=pNext){ + assert( IsOrdinaryTable(pTab) ); + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pNext){ + assert( db==0 || sqlite3SchemaMutexHeld(db, 0, pTab->pSchema) ); /* Remove the FK from the fkeyHash hash table. */ if( !db || db->pnBytesFreed==0 ){ @@ -122200,7 +126287,7 @@ SQLITE_PRIVATE void sqlite3OpenTable( }else{ Index *pPk = sqlite3PrimaryKeyIndex(pTab); assert( pPk!=0 ); - assert( pPk->tnum==pTab->tnum ); + assert( pPk->tnum==pTab->tnum || CORRUPT_DB ); sqlite3VdbeAddOp3(v, opcode, iCur, pPk->tnum, iDb); sqlite3VdbeSetP4KeyInfo(pParse, pPk); VdbeComment((v, "%s", pTab->zName)); @@ -122267,28 +126354,68 @@ SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){ } /* +** Make changes to the evolving bytecode to do affinity transformations +** of values that are about to be gathered into a row for table pTab. +** +** For ordinary (legacy, non-strict) tables: +** ----------------------------------------- +** ** Compute the affinity string for table pTab, if it has not already been ** computed. As an optimization, omit trailing SQLITE_AFF_BLOB affinities. ** -** If the affinity exists (if it is no entirely SQLITE_AFF_BLOB values) and -** if iReg>0 then code an OP_Affinity opcode that will set the affinities -** for register iReg and following. Or if affinities exists and iReg==0, +** If the affinity string is empty (because it was all SQLITE_AFF_BLOB entries +** which were then optimized out) then this routine becomes a no-op. +** +** Otherwise if iReg>0 then code an OP_Affinity opcode that will set the +** affinities for register iReg and following. Or if iReg==0, ** then just set the P4 operand of the previous opcode (which should be ** an OP_MakeRecord) to the affinity string. ** ** A column affinity string has one character per column: ** -** Character Column affinity -** ------------------------------ -** 'A' BLOB -** 'B' TEXT -** 'C' NUMERIC -** 'D' INTEGER -** 'E' REAL +** Character Column affinity +** --------- --------------- +** 'A' BLOB +** 'B' TEXT +** 'C' NUMERIC +** 'D' INTEGER +** 'E' REAL +** +** For STRICT tables: +** ------------------ +** +** Generate an appropropriate OP_TypeCheck opcode that will verify the +** datatypes against the column definitions in pTab. If iReg==0, that +** means an OP_MakeRecord opcode has already been generated and should be +** the last opcode generated. The new OP_TypeCheck needs to be inserted +** before the OP_MakeRecord. The new OP_TypeCheck should use the same +** register set as the OP_MakeRecord. If iReg>0 then register iReg is +** the first of a series of registers that will form the new record. +** Apply the type checking to that array of registers. */ SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ int i, j; - char *zColAff = pTab->zColAff; + char *zColAff; + if( pTab->tabFlags & TF_Strict ){ + if( iReg==0 ){ + /* Move the previous opcode (which should be OP_MakeRecord) forward + ** by one slot and insert a new OP_TypeCheck where the current + ** OP_MakeRecord is found */ + VdbeOp *pPrev; + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); + pPrev = sqlite3VdbeGetOp(v, -1); + assert( pPrev!=0 ); + assert( pPrev->opcode==OP_MakeRecord || sqlite3VdbeDb(v)->mallocFailed ); + pPrev->opcode = OP_TypeCheck; + sqlite3VdbeAddOp3(v, OP_MakeRecord, pPrev->p1, pPrev->p2, pPrev->p3); + }else{ + /* Insert an isolated OP_Typecheck */ + sqlite3VdbeAddOp2(v, OP_TypeCheck, iReg, pTab->nNVCol); + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); + } + return; + } + zColAff = pTab->zColAff; if( zColAff==0 ){ sqlite3 *db = sqlite3VdbeDb(v); zColAff = (char *)sqlite3DbMallocRaw(0, pTab->nCol+1); @@ -122298,7 +126425,7 @@ SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ } for(i=j=0; inCol; i++){ - assert( pTab->aCol[i].affinity!=0 ); + assert( pTab->aCol[i].affinity!=0 || sqlite3VdbeParser(v)->nErr>0 ); if( (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){ zColAff[j++] = pTab->aCol[i].affinity; } @@ -122314,6 +126441,8 @@ SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ if( iReg ){ sqlite3VdbeAddOp4(v, OP_Affinity, iReg, i, 0, zColAff, i); }else{ + assert( sqlite3VdbeGetOp(v, -1)->opcode==OP_MakeRecord + || sqlite3VdbeDb(v)->mallocFailed ); sqlite3VdbeChangeP4(v, -1, zColAff, i); } } @@ -122397,24 +126526,30 @@ SQLITE_PRIVATE void sqlite3ComputeGeneratedColumns( ** that appropriate affinity has been applied to the regular columns */ sqlite3TableAffinity(pParse->pVdbe, pTab, iRegStore); - if( (pTab->tabFlags & TF_HasStored)!=0 - && (pOp = sqlite3VdbeGetOp(pParse->pVdbe,-1))->opcode==OP_Affinity - ){ - /* Change the OP_Affinity argument to '@' (NONE) for all stored - ** columns. '@' is the no-op affinity and those columns have not - ** yet been computed. */ - int ii, jj; - char *zP4 = pOp->p4.z; - assert( zP4!=0 ); - assert( pOp->p4type==P4_DYNAMIC ); - for(ii=jj=0; zP4[jj]; ii++){ - if( pTab->aCol[ii].colFlags & COLFLAG_VIRTUAL ){ - continue; + if( (pTab->tabFlags & TF_HasStored)!=0 ){ + pOp = sqlite3VdbeGetOp(pParse->pVdbe,-1); + if( pOp->opcode==OP_Affinity ){ + /* Change the OP_Affinity argument to '@' (NONE) for all stored + ** columns. '@' is the no-op affinity and those columns have not + ** yet been computed. */ + int ii, jj; + char *zP4 = pOp->p4.z; + assert( zP4!=0 ); + assert( pOp->p4type==P4_DYNAMIC ); + for(ii=jj=0; zP4[jj]; ii++){ + if( pTab->aCol[ii].colFlags & COLFLAG_VIRTUAL ){ + continue; + } + if( pTab->aCol[ii].colFlags & COLFLAG_STORED ){ + zP4[jj] = SQLITE_AFF_NONE; + } + jj++; } - if( pTab->aCol[ii].colFlags & COLFLAG_STORED ){ - zP4[jj] = SQLITE_AFF_NONE; - } - jj++; + }else if( pOp->opcode==OP_TypeCheck ){ + /* If an OP_TypeCheck was generated because the table is STRICT, + ** then set the P3 operand to indicate that generated columns should + ** not be checked */ + pOp->p3 = 1; } } @@ -122450,7 +126585,7 @@ SQLITE_PRIVATE void sqlite3ComputeGeneratedColumns( int x; pCol->colFlags |= COLFLAG_BUSY; w.eCode = 0; - sqlite3WalkExpr(&w, pCol->pDflt); + sqlite3WalkExpr(&w, sqlite3ColumnExpr(pTab, pCol)); pCol->colFlags &= ~COLFLAG_BUSY; if( w.eCode & COLFLAG_NOTAVAIL ){ pRedo = pCol; @@ -122459,13 +126594,13 @@ SQLITE_PRIVATE void sqlite3ComputeGeneratedColumns( eProgress = 1; assert( pCol->colFlags & COLFLAG_GENERATED ); x = sqlite3TableColumnToStorage(pTab, i) + iRegStore; - sqlite3ExprCodeGeneratedColumn(pParse, pCol, x); + sqlite3ExprCodeGeneratedColumn(pParse, pTab, pCol, x); pCol->colFlags &= ~COLFLAG_NOTAVAIL; } } }while( pRedo && eProgress ); if( pRedo ){ - sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", pRedo->zName); + sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", pRedo->zCnName); } pParse->iSelfTab = 0; } @@ -122515,7 +126650,7 @@ static int autoIncBegin( ** Ticket d8dc2b3a58cd5dc2918a1d4acb 2018-05-23 */ if( pSeqTab==0 || !HasRowid(pSeqTab) - || IsVirtual(pSeqTab) + || NEVER(IsVirtual(pSeqTab)) || pSeqTab->nCol!=2 ){ pParse->nErr++; @@ -122824,9 +126959,11 @@ SQLITE_PRIVATE void sqlite3Insert( #endif db = pParse->db; - if( pParse->nErr || db->mallocFailed ){ + assert( db->pParse==pParse ); + if( pParse->nErr ){ goto insert_cleanup; } + assert( db->mallocFailed==0 ); dest.iSDParm = 0; /* Suppress a harmless compiler warning */ /* If the Select object is really just a simple VALUES() list with a @@ -122860,7 +126997,7 @@ SQLITE_PRIVATE void sqlite3Insert( */ #ifndef SQLITE_OMIT_TRIGGER pTrigger = sqlite3TriggersExist(pParse, pTab, TK_INSERT, 0, &tmask); - isView = pTab->pSelect!=0; + isView = IsView(pTab); #else # define pTrigger 0 # define tmask 0 @@ -122872,6 +127009,14 @@ SQLITE_PRIVATE void sqlite3Insert( #endif assert( (pTrigger && tmask) || (pTrigger==0 && tmask==0) ); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Insert() at %s:%d", __FILE__, __LINE__); + sqlite3TreeViewInsert(pParse->pWith, pTabList, pColumn, pSelect, pList, + onError, pUpsert, pTrigger); + } +#endif + /* If pTab is really a view, make sure it has been initialized. ** ViewGetColumnNames() is a no-op if pTab is not a view. */ @@ -122902,7 +127047,11 @@ SQLITE_PRIVATE void sqlite3Insert( ** ** This is the 2nd template. */ - if( pColumn==0 && xferOptimization(pParse, pTab, pSelect, onError, iDb) ){ + if( pColumn==0 + && pSelect!=0 + && pTrigger==0 + && xferOptimization(pParse, pTab, pSelect, onError, iDb) + ){ assert( !pTrigger ); assert( pList==0 ); goto insert_end; @@ -122946,13 +127095,15 @@ SQLITE_PRIVATE void sqlite3Insert( */ bIdListInOrder = (pTab->tabFlags & (TF_OOOHidden|TF_HasStored))==0; if( pColumn ){ + assert( pColumn->eU4!=EU4_EXPR ); + pColumn->eU4 = EU4_IDX; for(i=0; inId; i++){ - pColumn->a[i].idx = -1; + pColumn->a[i].u4.idx = -1; } for(i=0; inId; i++){ for(j=0; jnCol; j++){ - if( sqlite3StrICmp(pColumn->a[i].zName, pTab->aCol[j].zName)==0 ){ - pColumn->a[i].idx = j; + if( sqlite3StrICmp(pColumn->a[i].zName, pTab->aCol[j].zCnName)==0 ){ + pColumn->a[i].u4.idx = j; if( i!=j ) bIdListInOrder = 0; if( j==pTab->iPKey ){ ipkColumn = i; assert( !withoutRowid ); @@ -122961,7 +127112,7 @@ SQLITE_PRIVATE void sqlite3Insert( if( pTab->aCol[j].colFlags & (COLFLAG_STORED|COLFLAG_VIRTUAL) ){ sqlite3ErrorMsg(pParse, "cannot INSERT into generated column \"%s\"", - pTab->aCol[j].zName); + pTab->aCol[j].zCnName); goto insert_cleanup; } #endif @@ -122974,7 +127125,7 @@ SQLITE_PRIVATE void sqlite3Insert( bIdListInOrder = 0; }else{ sqlite3ErrorMsg(pParse, "table %S has no column named %s", - pTabList, 0, pColumn->a[i].zName); + pTabList->a, pColumn->a[i].zName); pParse->checkSchema = 1; goto insert_cleanup; } @@ -123002,7 +127153,9 @@ SQLITE_PRIVATE void sqlite3Insert( dest.nSdst = pTab->nCol; rc = sqlite3Select(pParse, pSelect, &dest); regFromSelect = dest.iSdst; - if( rc || db->mallocFailed || pParse->nErr ) goto insert_cleanup; + assert( db->pParse==pParse ); + if( rc || pParse->nErr ) goto insert_cleanup; + assert( db->mallocFailed==0 ); sqlite3VdbeEndCoroutine(v, regYield); sqlite3VdbeJumpHere(v, addrTop - 1); /* label B: */ assert( pSelect->pEList ); @@ -123102,7 +127255,7 @@ SQLITE_PRIVATE void sqlite3Insert( if( nColumn!=(pTab->nCol-nHidden) ){ sqlite3ErrorMsg(pParse, "table %S has %d columns but %d values were supplied", - pTabList, 0, pTab->nCol-nHidden, nColumn); + pTabList->a, pTab->nCol-nHidden, nColumn); goto insert_cleanup; } } @@ -123146,7 +127299,7 @@ SQLITE_PRIVATE void sqlite3Insert( pTab->zName); goto insert_cleanup; } - if( pTab->pSelect ){ + if( IsView(pTab) ){ sqlite3ErrorMsg(pParse, "cannot UPSERT a view"); goto insert_cleanup; } @@ -123245,22 +127398,29 @@ SQLITE_PRIVATE void sqlite3Insert( }else if( pColumn==0 ){ /* Hidden columns that are not explicitly named in the INSERT ** get there default value */ - sqlite3ExprCodeFactorable(pParse, pTab->aCol[i].pDflt, iRegStore); + sqlite3ExprCodeFactorable(pParse, + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + iRegStore); continue; } } if( pColumn ){ - for(j=0; jnId && pColumn->a[j].idx!=i; j++){} + assert( pColumn->eU4==EU4_IDX ); + for(j=0; jnId && pColumn->a[j].u4.idx!=i; j++){} if( j>=pColumn->nId ){ /* A column not named in the insert column list gets its ** default value */ - sqlite3ExprCodeFactorable(pParse, pTab->aCol[i].pDflt, iRegStore); + sqlite3ExprCodeFactorable(pParse, + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + iRegStore); continue; } k = j; }else if( nColumn==0 ){ /* This is INSERT INTO ... DEFAULT VALUES. Load the default value. */ - sqlite3ExprCodeFactorable(pParse, pTab->aCol[i].pDflt, iRegStore); + sqlite3ExprCodeFactorable(pParse, + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + iRegStore); continue; }else{ k = i - nHidden; @@ -123405,7 +127565,7 @@ SQLITE_PRIVATE void sqlite3Insert( }else #endif { - int isReplace; /* Set to true if constraints may cause a replace */ + int isReplace = 0;/* Set to true if constraints may cause a replace */ int bUseSeek; /* True to use OPFLAG_SEEKRESULT */ sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0, pUpsert @@ -123425,6 +127585,13 @@ SQLITE_PRIVATE void sqlite3Insert( regIns, aRegIdx, 0, appendFlag, bUseSeek ); } +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + }else if( pParse->bReturning ){ + /* If there is a RETURNING clause, populate the rowid register with + ** constant value -1, in case one or more of the returned expressions + ** refer to the "rowid" of the view. */ + sqlite3VdbeAddOp2(v, OP_Integer, -1, regRowid); +#endif } /* Update the count of rows that are inserted @@ -123478,9 +127645,7 @@ insert_end: ** invoke the callback function. */ if( regRowCount ){ - sqlite3VdbeAddOp2(v, OP_ChngCntRow, regRowCount, 1); - sqlite3VdbeSetNumCols(v, 1); - sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows inserted", SQLITE_STATIC); + sqlite3CodeChangeCount(v, regRowCount, "rows inserted"); } insert_cleanup: @@ -123768,7 +127933,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( db = pParse->db; v = pParse->pVdbe; assert( v!=0 ); - assert( pTab->pSelect==0 ); /* This table is not a VIEW */ + assert( !IsView(pTab) ); /* This table is not a VIEW */ nCol = pTab->nCol; /* pPk is the PRIMARY KEY index for WITHOUT ROWID tables and NULL for @@ -123819,7 +127984,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( } if( onError==OE_Replace ){ if( b2ndPass /* REPLACE becomes ABORT on the 2nd pass */ - || pCol->pDflt==0 /* REPLACE is ABORT if no DEFAULT value */ + || pCol->iDflt==0 /* REPLACE is ABORT if no DEFAULT value */ ){ testcase( pCol->colFlags & COLFLAG_VIRTUAL ); testcase( pCol->colFlags & COLFLAG_STORED ); @@ -123841,7 +128006,8 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( VdbeCoverage(v); assert( (pCol->colFlags & COLFLAG_GENERATED)==0 ); nSeenReplace++; - sqlite3ExprCodeCopy(pParse, pCol->pDflt, iReg); + sqlite3ExprCodeCopy(pParse, + sqlite3ColumnExpr(pTab, pCol), iReg); sqlite3VdbeJumpHere(v, addr1); break; } @@ -123851,7 +128017,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( case OE_Rollback: case OE_Fail: { char *zMsg = sqlite3MPrintf(db, "%s.%s", pTab->zName, - pCol->zName); + pCol->zCnName); sqlite3VdbeAddOp3(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL, onError, iReg); sqlite3VdbeAppendP4(v, zMsg, P4_DYNAMIC); @@ -124104,6 +128270,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( if( onError==OE_Replace /* IPK rule is REPLACE */ && onError!=overrideError /* Rules for other constraints are different */ && pTab->pIndex /* There exist other constraints */ + && !upsertIpkDelay /* IPK check already deferred by UPSERT */ ){ ipkTop = sqlite3VdbeAddOp0(v, OP_Goto)+1; VdbeComment((v, "defer IPK REPLACE until last")); @@ -124269,7 +128436,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( testcase( sqlite3TableColumnToStorage(pTab, iField)!=iField ); x = sqlite3TableColumnToStorage(pTab, iField) + regNewData + 1; sqlite3VdbeAddOp2(v, OP_SCopy, x, regIdx+i); - VdbeComment((v, "%s", pTab->aCol[iField].zName)); + VdbeComment((v, "%s", pTab->aCol[iField].zCnName)); } } sqlite3VdbeAddOp3(v, OP_MakeRecord, regIdx, pIdx->nColumn, aRegIdx[ix]); @@ -124321,6 +128488,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( ** This is not possible for ENABLE_PREUPDATE_HOOK builds, as the row ** must be explicitly deleted in order to ensure any pre-update hook ** is invoked. */ + assert( IsOrdinaryTable(pTab) ); #ifndef SQLITE_ENABLE_PREUPDATE_HOOK if( (ix==0 && pIdx->pNext==0) /* Condition 3 */ && pPk==pIdx /* Condition 2 */ @@ -124328,7 +128496,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( && ( 0==(db->flags&SQLITE_RecTriggers) || /* Condition 4 */ 0==sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0)) && ( 0==(db->flags&SQLITE_ForeignKeys) || /* Condition 5 */ - (0==pTab->pFKey && 0==sqlite3FkReferences(pTab))) + (0==pTab->u.tab.pFKey && 0==sqlite3FkReferences(pTab))) ){ sqlite3VdbeResolveLabel(v, addrUniqueOk); continue; @@ -124363,13 +128531,13 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( x = sqlite3TableColumnToIndex(pIdx, pPk->aiColumn[i]); sqlite3VdbeAddOp3(v, OP_Column, iThisCur, x, regR+i); VdbeComment((v, "%s.%s", pTab->zName, - pTab->aCol[pPk->aiColumn[i]].zName)); + pTab->aCol[pPk->aiColumn[i]].zCnName)); } } if( isUpdate ){ /* If currently processing the PRIMARY KEY of a WITHOUT ROWID ** table, only conflict if the new PRIMARY KEY values are actually - ** different from the old. + ** different from the old. See TH3 withoutrowid04.test. ** ** For a UNIQUE index, only conflict if the PRIMARY KEY values ** of the matched index row are different from the original PRIMARY @@ -124427,7 +128595,8 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( assert( onError==OE_Replace ); nConflictCk = sqlite3VdbeCurrentAddr(v) - addrConflictCk; - assert( nConflictCk>0 ); + assert( nConflictCk>0 || db->mallocFailed ); + testcase( nConflictCk<=0 ); testcase( nConflictCk>1 ); if( regTrigCnt ){ sqlite3MultiWrite(pParse); @@ -124510,6 +128679,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( if( ipkTop ){ sqlite3VdbeGoto(v, ipkTop); VdbeComment((v, "Do IPK REPLACE")); + assert( ipkBottom>0 ); sqlite3VdbeJumpHere(v, ipkBottom); } @@ -124562,7 +128732,7 @@ SQLITE_PRIVATE void sqlite3SetMakeRecordP5(Vdbe *v, Table *pTab){ if( pTab->pSchema->file_format<2 ) return; for(i=pTab->nCol-1; i>0; i--){ - if( pTab->aCol[i].pDflt!=0 ) break; + if( pTab->aCol[i].iDflt!=0 ) break; if( pTab->aCol[i].colFlags & COLFLAG_PRIMKEY ) break; } sqlite3VdbeChangeP5(v, i+1); @@ -124627,7 +128797,7 @@ SQLITE_PRIVATE void sqlite3CompleteInsertion( v = pParse->pVdbe; assert( v!=0 ); - assert( pTab->pSelect==0 ); /* This table is not a VIEW */ + assert( !IsView(pTab) ); /* This table is not a VIEW */ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ /* All REPLACE indexes are at the end of the list */ assert( pIdx->onError!=OE_Replace @@ -124640,7 +128810,6 @@ SQLITE_PRIVATE void sqlite3CompleteInsertion( } pik_flags = (useSeekResult ? OPFLAG_USESEEKRESULT : 0); if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ - assert( pParse->nested==0 ); pik_flags |= OPFLAG_NCHANGE; pik_flags |= (update_flags & OPFLAG_SAVEPOSITION); if( update_flags==0 ){ @@ -124713,8 +128882,9 @@ SQLITE_PRIVATE int sqlite3OpenTableAndIndices( assert( op==OP_OpenWrite || p5==0 ); if( IsVirtual(pTab) ){ /* This routine is a no-op for virtual tables. Leave the output - ** variables *piDataCur and *piIdxCur uninitialized so that valgrind - ** can detect if they are used by mistake in the caller. */ + ** variables *piDataCur and *piIdxCur set to illegal cursor numbers + ** for improved error detection. */ + *piDataCur = *piIdxCur = -999; return 0; } iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); @@ -124855,18 +129025,13 @@ static int xferOptimization( int destHasUniqueIdx = 0; /* True if pDest has a UNIQUE index */ int regData, regRowid; /* Registers holding data and rowid */ - if( pSelect==0 ){ - return 0; /* Must be of the form INSERT INTO ... SELECT ... */ - } + assert( pSelect!=0 ); if( pParse->pWith || pSelect->pWith ){ /* Do not attempt to process this query if there are an WITH clauses ** attached to it. Proceeding may generate a false "no such table: xxx" ** error if pSelect reads from a CTE named "xxx". */ return 0; } - if( sqlite3TriggerList(pParse, pDest) ){ - return 0; /* tab1 must not have triggers */ - } #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(pDest) ){ return 0; /* tab1 must not be a virtual table */ @@ -124929,13 +129094,8 @@ static int xferOptimization( if( HasRowid(pDest)!=HasRowid(pSrc) ){ return 0; /* source and destination must both be WITHOUT ROWID or not */ } -#ifndef SQLITE_OMIT_VIRTUALTABLE - if( IsVirtual(pSrc) ){ - return 0; /* tab2 must not be a virtual table */ - } -#endif - if( pSrc->pSelect ){ - return 0; /* tab2 may not be a view */ + if( !IsOrdinaryTable(pSrc) ){ + return 0; /* tab2 may not be a view or virtual table */ } if( pDest->nCol!=pSrc->nCol ){ return 0; /* Number of columns must be the same in tab1 and tab2 */ @@ -124943,6 +129103,9 @@ static int xferOptimization( if( pDest->iPKey!=pSrc->iPKey ){ return 0; /* Both tables must have the same INTEGER PRIMARY KEY */ } + if( (pDest->tabFlags & TF_Strict)!=0 && (pSrc->tabFlags & TF_Strict)==0 ){ + return 0; /* Cannot feed from a non-strict into a strict table */ + } for(i=0; inCol; i++){ Column *pDestCol = &pDest->aCol[i]; Column *pSrcCol = &pSrc->aCol[i]; @@ -124979,7 +129142,9 @@ static int xferOptimization( ** This requirement could be relaxed for VIRTUAL columns, I suppose. */ if( (pDestCol->colFlags & COLFLAG_GENERATED)!=0 ){ - if( sqlite3ExprCompare(0, pSrcCol->pDflt, pDestCol->pDflt, -1)!=0 ){ + if( sqlite3ExprCompare(0, + sqlite3ColumnExpr(pSrc, pSrcCol), + sqlite3ColumnExpr(pDest, pDestCol), -1)!=0 ){ testcase( pDestCol->colFlags & COLFLAG_VIRTUAL ); testcase( pDestCol->colFlags & COLFLAG_STORED ); return 0; /* Different generator expressions */ @@ -124989,7 +129154,8 @@ static int xferOptimization( if( pDestCol->affinity!=pSrcCol->affinity ){ return 0; /* Affinity must be the same on all columns */ } - if( sqlite3_stricmp(pDestCol->zColl, pSrcCol->zColl)!=0 ){ + if( sqlite3_stricmp(sqlite3ColumnColl(pDestCol), + sqlite3ColumnColl(pSrcCol))!=0 ){ return 0; /* Collating sequence must be the same on all columns */ } if( pDestCol->notNull && !pSrcCol->notNull ){ @@ -124997,11 +129163,15 @@ static int xferOptimization( } /* Default values for second and subsequent columns need to match. */ if( (pDestCol->colFlags & COLFLAG_GENERATED)==0 && i>0 ){ - assert( pDestCol->pDflt==0 || pDestCol->pDflt->op==TK_SPAN ); - assert( pSrcCol->pDflt==0 || pSrcCol->pDflt->op==TK_SPAN ); - if( (pDestCol->pDflt==0)!=(pSrcCol->pDflt==0) - || (pDestCol->pDflt && strcmp(pDestCol->pDflt->u.zToken, - pSrcCol->pDflt->u.zToken)!=0) + Expr *pDestExpr = sqlite3ColumnExpr(pDest, pDestCol); + Expr *pSrcExpr = sqlite3ColumnExpr(pSrc, pSrcCol); + assert( pDestExpr==0 || pDestExpr->op==TK_SPAN ); + assert( pDestExpr==0 || !ExprHasProperty(pDestExpr, EP_IntValue) ); + assert( pSrcExpr==0 || pSrcExpr->op==TK_SPAN ); + assert( pSrcExpr==0 || !ExprHasProperty(pSrcExpr, EP_IntValue) ); + if( (pDestExpr==0)!=(pSrcExpr==0) + || (pDestExpr!=0 && strcmp(pDestExpr->u.zToken, + pSrcExpr->u.zToken)!=0) ){ return 0; /* Default values must be the same for all columns */ } @@ -125038,7 +129208,8 @@ static int xferOptimization( ** the extra complication to make this rule less restrictive is probably ** not worth the effort. Ticket [6284df89debdfa61db8073e062908af0c9b6118e] */ - if( (db->flags & SQLITE_ForeignKeys)!=0 && pDest->pFKey!=0 ){ + assert( IsOrdinaryTable(pDest) ); + if( (db->flags & SQLITE_ForeignKeys)!=0 && pDest->u.tab.pFKey!=0 ){ return 0; } #endif @@ -125716,6 +129887,26 @@ struct sqlite3_api_routines { sqlite3_file *(*database_file_object)(const char*); /* Version 3.34.0 and later */ int (*txn_state)(sqlite3*,const char*); + /* Version 3.36.1 and later */ + sqlite3_int64 (*changes64)(sqlite3*); + sqlite3_int64 (*total_changes64)(sqlite3*); + /* Version 3.37.0 and later */ + int (*autovacuum_pages)(sqlite3*, + unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), + void*, void(*)(void*)); + /* Version 3.38.0 and later */ + int (*error_offset)(sqlite3*); + int (*vtab_rhs_value)(sqlite3_index_info*,int,sqlite3_value**); + int (*vtab_distinct)(sqlite3_index_info*); + int (*vtab_in)(sqlite3_index_info*,int,int); + int (*vtab_in_first)(sqlite3_value*,sqlite3_value**); + int (*vtab_in_next)(sqlite3_value*,sqlite3_value**); + /* Version 3.39.0 and later */ + int (*deserialize)(sqlite3*,const char*,unsigned char*, + sqlite3_int64,sqlite3_int64,unsigned); + unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*, + unsigned int); + const char *(*db_name)(sqlite3*,int); }; /* @@ -126022,6 +130213,24 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_database_file_object sqlite3_api->database_file_object /* Version 3.34.0 and later */ #define sqlite3_txn_state sqlite3_api->txn_state +/* Version 3.36.1 and later */ +#define sqlite3_changes64 sqlite3_api->changes64 +#define sqlite3_total_changes64 sqlite3_api->total_changes64 +/* Version 3.37.0 and later */ +#define sqlite3_autovacuum_pages sqlite3_api->autovacuum_pages +/* Version 3.38.0 and later */ +#define sqlite3_error_offset sqlite3_api->error_offset +#define sqlite3_vtab_rhs_value sqlite3_api->vtab_rhs_value +#define sqlite3_vtab_distinct sqlite3_api->vtab_distinct +#define sqlite3_vtab_in sqlite3_api->vtab_in +#define sqlite3_vtab_in_first sqlite3_api->vtab_in_first +#define sqlite3_vtab_in_next sqlite3_api->vtab_in_next +/* Version 3.39.0 and later */ +#ifndef SQLITE_OMIT_DESERIALIZE +#define sqlite3_deserialize sqlite3_api->deserialize +#define sqlite3_serialize sqlite3_api->serialize +#endif +#define sqlite3_db_name sqlite3_api->db_name #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) @@ -126506,6 +130715,35 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_database_file_object, /* Version 3.34.0 and later */ sqlite3_txn_state, + /* Version 3.36.1 and later */ + sqlite3_changes64, + sqlite3_total_changes64, + /* Version 3.37.0 and later */ + sqlite3_autovacuum_pages, + /* Version 3.38.0 and later */ + sqlite3_error_offset, +#ifndef SQLITE_OMIT_VIRTUALTABLE + sqlite3_vtab_rhs_value, + sqlite3_vtab_distinct, + sqlite3_vtab_in, + sqlite3_vtab_in_first, + sqlite3_vtab_in_next, +#else + 0, + 0, + 0, + 0, + 0, +#endif + /* Version 3.39.0 and later */ +#ifndef SQLITE_OMIT_DESERIALIZE + sqlite3_deserialize, + sqlite3_serialize, +#else + 0, + 0, +#endif + sqlite3_db_name }; /* True if x is the directory separator character @@ -126541,7 +130779,7 @@ static int sqlite3LoadExtension( const char *zEntry; char *zAltEntry = 0; void **aHandle; - u64 nMsg = 300 + sqlite3Strlen30(zFile); + u64 nMsg = strlen(zFile); int ii; int rc; @@ -126575,6 +130813,12 @@ static int sqlite3LoadExtension( zEntry = zProc ? zProc : "sqlite3_extension_init"; + /* tag-20210611-1. Some dlopen() implementations will segfault if given + ** an oversize filename. Most filesystems have a pathname limit of 4K, + ** so limit the extension filename length to about twice that. + ** https://sqlite.org/forum/forumpost/08a0d6d9bf */ + if( nMsg>SQLITE_MAX_PATHLEN ) goto extension_not_found; + handle = sqlite3OsDlOpen(pVfs, zFile); #if SQLITE_OS_UNIX || SQLITE_OS_WIN for(ii=0; iiaExtension[db->nExtension++] = handle; return SQLITE_OK; + +extension_not_found: + if( pzErrMsg ){ + nMsg += 300; + *pzErrMsg = zErrmsg = sqlite3_malloc64(nMsg); + if( zErrmsg ){ + assert( nMsg<0x7fffffff ); /* zErrmsg would be NULL if not so */ + sqlite3_snprintf((int)nMsg, zErrmsg, + "unable to open shared library [%.*s]", SQLITE_MAX_PATHLEN, zFile); + sqlite3OsDlError(pVfs, nMsg-1, zErrmsg); + } + } + return SQLITE_ERROR; } SQLITE_API int sqlite3_load_extension( sqlite3 *db, /* Load the extension into this database connection */ @@ -126958,13 +131206,14 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){ #define PragTyp_SOFT_HEAP_LIMIT 35 #define PragTyp_SYNCHRONOUS 36 #define PragTyp_TABLE_INFO 37 -#define PragTyp_TEMP_STORE 38 -#define PragTyp_TEMP_STORE_DIRECTORY 39 -#define PragTyp_THREADS 40 -#define PragTyp_WAL_AUTOCHECKPOINT 41 -#define PragTyp_WAL_CHECKPOINT 42 -#define PragTyp_LOCK_STATUS 43 -#define PragTyp_STATS 44 +#define PragTyp_TABLE_LIST 38 +#define PragTyp_TEMP_STORE 39 +#define PragTyp_TEMP_STORE_DIRECTORY 40 +#define PragTyp_THREADS 41 +#define PragTyp_WAL_AUTOCHECKPOINT 42 +#define PragTyp_WAL_CHECKPOINT 43 +#define PragTyp_LOCK_STATUS 44 +#define PragTyp_STATS 45 /* Property flags associated with various pragma. */ #define PragFlg_NeedSchema 0x01 /* Force schema load before running */ @@ -126997,45 +131246,51 @@ static const char *const pragCName[] = { /* 13 */ "pk", /* 14 */ "hidden", /* table_info reuses 8 */ - /* 15 */ "seqno", /* Used by: index_xinfo */ - /* 16 */ "cid", - /* 17 */ "name", - /* 18 */ "desc", - /* 19 */ "coll", - /* 20 */ "key", - /* 21 */ "name", /* Used by: function_list */ - /* 22 */ "builtin", - /* 23 */ "type", - /* 24 */ "enc", - /* 25 */ "narg", - /* 26 */ "flags", - /* 27 */ "tbl", /* Used by: stats */ - /* 28 */ "idx", - /* 29 */ "wdth", - /* 30 */ "hght", - /* 31 */ "flgs", - /* 32 */ "seq", /* Used by: index_list */ - /* 33 */ "name", - /* 34 */ "unique", - /* 35 */ "origin", - /* 36 */ "partial", - /* 37 */ "table", /* Used by: foreign_key_check */ - /* 38 */ "rowid", - /* 39 */ "parent", - /* 40 */ "fkid", - /* index_info reuses 15 */ - /* 41 */ "seq", /* Used by: database_list */ - /* 42 */ "name", - /* 43 */ "file", - /* 44 */ "busy", /* Used by: wal_checkpoint */ - /* 45 */ "log", - /* 46 */ "checkpointed", - /* collation_list reuses 32 */ - /* 47 */ "database", /* Used by: lock_status */ - /* 48 */ "status", - /* 49 */ "cache_size", /* Used by: default_cache_size */ + /* 15 */ "schema", /* Used by: table_list */ + /* 16 */ "name", + /* 17 */ "type", + /* 18 */ "ncol", + /* 19 */ "wr", + /* 20 */ "strict", + /* 21 */ "seqno", /* Used by: index_xinfo */ + /* 22 */ "cid", + /* 23 */ "name", + /* 24 */ "desc", + /* 25 */ "coll", + /* 26 */ "key", + /* 27 */ "name", /* Used by: function_list */ + /* 28 */ "builtin", + /* 29 */ "type", + /* 30 */ "enc", + /* 31 */ "narg", + /* 32 */ "flags", + /* 33 */ "tbl", /* Used by: stats */ + /* 34 */ "idx", + /* 35 */ "wdth", + /* 36 */ "hght", + /* 37 */ "flgs", + /* 38 */ "seq", /* Used by: index_list */ + /* 39 */ "name", + /* 40 */ "unique", + /* 41 */ "origin", + /* 42 */ "partial", + /* 43 */ "table", /* Used by: foreign_key_check */ + /* 44 */ "rowid", + /* 45 */ "parent", + /* 46 */ "fkid", + /* index_info reuses 21 */ + /* 47 */ "seq", /* Used by: database_list */ + /* 48 */ "name", + /* 49 */ "file", + /* 50 */ "busy", /* Used by: wal_checkpoint */ + /* 51 */ "log", + /* 52 */ "checkpointed", + /* collation_list reuses 38 */ + /* 53 */ "database", /* Used by: lock_status */ + /* 54 */ "status", + /* 55 */ "cache_size", /* Used by: default_cache_size */ /* module_list pragma_list reuses 9 */ - /* 50 */ "timeout", /* Used by: busy_timeout */ + /* 56 */ "timeout", /* Used by: busy_timeout */ }; /* Definitions of all built-in pragmas */ @@ -127086,7 +131341,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "busy_timeout", /* ePragTyp: */ PragTyp_BUSY_TIMEOUT, /* ePragFlg: */ PragFlg_Result0, - /* ColNames: */ 50, 1, + /* ColNames: */ 56, 1, /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) {/* zName: */ "cache_size", @@ -127125,7 +131380,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "collation_list", /* ePragTyp: */ PragTyp_COLLATION_LIST, /* ePragFlg: */ PragFlg_Result0, - /* ColNames: */ 32, 2, + /* ColNames: */ 38, 2, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS) @@ -127159,15 +131414,15 @@ static const PragmaName aPragmaName[] = { #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) {/* zName: */ "database_list", /* ePragTyp: */ PragTyp_DATABASE_LIST, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0, - /* ColNames: */ 41, 3, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 47, 3, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED) {/* zName: */ "default_cache_size", /* ePragTyp: */ PragTyp_DEFAULT_CACHE_SIZE, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, - /* ColNames: */ 49, 1, + /* ColNames: */ 55, 1, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) @@ -127197,7 +131452,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "foreign_key_check", /* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1|PragFlg_SchemaOpt, - /* ColNames: */ 37, 4, + /* ColNames: */ 43, 4, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FOREIGN_KEY) @@ -127240,7 +131495,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "function_list", /* ePragTyp: */ PragTyp_FUNCTION_LIST, /* ePragFlg: */ PragFlg_Result0, - /* ColNames: */ 21, 6, + /* ColNames: */ 27, 6, /* iArg: */ 0 }, #endif #endif @@ -127269,23 +131524,23 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "index_info", /* ePragTyp: */ PragTyp_INDEX_INFO, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, - /* ColNames: */ 15, 3, + /* ColNames: */ 21, 3, /* iArg: */ 0 }, {/* zName: */ "index_list", /* ePragTyp: */ PragTyp_INDEX_LIST, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, - /* ColNames: */ 32, 5, + /* ColNames: */ 38, 5, /* iArg: */ 0 }, {/* zName: */ "index_xinfo", /* ePragTyp: */ PragTyp_INDEX_INFO, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, - /* ColNames: */ 15, 6, + /* ColNames: */ 21, 6, /* iArg: */ 1 }, #endif #if !defined(SQLITE_OMIT_INTEGRITY_CHECK) {/* zName: */ "integrity_check", /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1|PragFlg_SchemaOpt, /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif @@ -127319,7 +131574,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "lock_status", /* ePragTyp: */ PragTyp_LOCK_STATUS, /* ePragFlg: */ PragFlg_Result0, - /* ColNames: */ 47, 2, + /* ColNames: */ 53, 2, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) @@ -127393,7 +131648,7 @@ static const PragmaName aPragmaName[] = { #if !defined(SQLITE_OMIT_INTEGRITY_CHECK) {/* zName: */ "quick_check", /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1|PragFlg_SchemaOpt, /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif @@ -127458,7 +131713,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "stats", /* ePragTyp: */ PragTyp_STATS, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, - /* ColNames: */ 27, 5, + /* ColNames: */ 33, 5, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) @@ -127474,6 +131729,11 @@ static const PragmaName aPragmaName[] = { /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, /* ColNames: */ 8, 6, /* iArg: */ 0 }, + {/* zName: */ "table_list", + /* ePragTyp: */ PragTyp_TABLE_LIST, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1, + /* ColNames: */ 15, 6, + /* iArg: */ 0 }, {/* zName: */ "table_xinfo", /* ePragTyp: */ PragTyp_TABLE_INFO, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, @@ -127549,7 +131809,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "wal_checkpoint", /* ePragTyp: */ PragTyp_WAL_CHECKPOINT, /* ePragFlg: */ PragFlg_NeedSchema, - /* ColNames: */ 44, 3, + /* ColNames: */ 50, 3, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) @@ -127560,7 +131820,7 @@ static const PragmaName aPragmaName[] = { /* iArg: */ SQLITE_WriteSchema|SQLITE_NoSchemaError }, #endif }; -/* Number of pragmas: 67 on by default, 77 total. */ +/* Number of pragmas: 68 on by default, 78 total. */ /************** End of pragma.h **********************************************/ /************** Continuing where we left off in pragma.c *********************/ @@ -127842,15 +132102,16 @@ static void pragmaFunclistLine( int isBuiltin, /* True if this is a built-in function */ int showInternFuncs /* True if showing internal functions */ ){ + u32 mask = + SQLITE_DETERMINISTIC | + SQLITE_DIRECTONLY | + SQLITE_SUBTYPE | + SQLITE_INNOCUOUS | + SQLITE_FUNC_INTERNAL + ; + if( showInternFuncs ) mask = 0xffffffff; for(; p; p=p->pNext){ const char *zType; - static const u32 mask = - SQLITE_DETERMINISTIC | - SQLITE_DIRECTONLY | - SQLITE_SUBTYPE | - SQLITE_INNOCUOUS | - SQLITE_FUNC_INTERNAL - ; static const char *azEnc[] = { 0, "utf8", "utf16le", "utf16be" }; assert( SQLITE_FUNC_ENCMASK==0x3 ); @@ -128002,7 +132263,11 @@ SQLITE_PRIVATE void sqlite3Pragma( /* Locate the pragma in the lookup table */ pPragma = pragmaLocate(zLeft); - if( pPragma==0 ) goto pragma_out; + if( pPragma==0 ){ + /* IMP: R-43042-22504 No error messages are generated if an + ** unknown pragma is issued. */ + goto pragma_out; + } /* Make sure the database schema is loaded if the pragma requires that */ if( (pPragma->mPragFlg & PragFlg_NeedSchema)!=0 ){ @@ -128338,7 +132603,7 @@ SQLITE_PRIVATE void sqlite3Pragma( */ #ifndef SQLITE_OMIT_AUTOVACUUM case PragTyp_INCREMENTAL_VACUUM: { - int iLimit, addr; + int iLimit = 0, addr; if( zRight==0 || !sqlite3GetInt32(zRight, &iLimit) || iLimit<=0 ){ iLimit = 0x7fffffff; } @@ -128495,6 +132760,7 @@ SQLITE_PRIVATE void sqlite3Pragma( ** */ case PragTyp_TEMP_STORE_DIRECTORY: { + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); if( !zRight ){ returnSingleText(v, sqlite3_temp_directory); }else{ @@ -128504,6 +132770,7 @@ SQLITE_PRIVATE void sqlite3Pragma( rc = sqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res); if( rc!=SQLITE_OK || res==0 ){ sqlite3ErrorMsg(pParse, "not a writable directory"); + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); goto pragma_out; } } @@ -128521,6 +132788,7 @@ SQLITE_PRIVATE void sqlite3Pragma( } #endif /* SQLITE_OMIT_WSD */ } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); break; } @@ -128539,6 +132807,7 @@ SQLITE_PRIVATE void sqlite3Pragma( ** */ case PragTyp_DATA_STORE_DIRECTORY: { + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); if( !zRight ){ returnSingleText(v, sqlite3_data_directory); }else{ @@ -128548,6 +132817,7 @@ SQLITE_PRIVATE void sqlite3Pragma( rc = sqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res); if( rc!=SQLITE_OK || res==0 ){ sqlite3ErrorMsg(pParse, "not a writable directory"); + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); goto pragma_out; } } @@ -128559,6 +132829,7 @@ SQLITE_PRIVATE void sqlite3Pragma( } #endif /* SQLITE_OMIT_WSD */ } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); break; } #endif @@ -128652,6 +132923,14 @@ SQLITE_PRIVATE void sqlite3Pragma( }else{ db->flags &= ~mask; if( mask==SQLITE_DeferFKs ) db->nDeferredImmCons = 0; + if( (mask & SQLITE_WriteSchema)!=0 + && sqlite3_stricmp(zRight, "reset")==0 + ){ + /* IMP: R-60817-01178 If the argument is "RESET" then schema + ** writing is disabled (as with "PRAGMA writable_schema=OFF") and, + ** in addition, the schema is reloaded. */ + sqlite3ResetAllSchemasOfConnection(db); + } } /* Many of the flag-pragmas modify the code generated by the SQL @@ -128692,6 +132971,7 @@ SQLITE_PRIVATE void sqlite3Pragma( sqlite3ViewGetColumnNames(pParse, pTab); for(i=0, pCol=pTab->aCol; inCol; i++, pCol++){ int isHidden = 0; + const Expr *pColExpr; if( pCol->colFlags & COLFLAG_NOINSERT ){ if( pPragma->iArg==0 ){ nHidden++; @@ -128712,13 +132992,16 @@ SQLITE_PRIVATE void sqlite3Pragma( }else{ for(k=1; k<=pTab->nCol && pPk->aiColumn[k-1]!=i; k++){} } - assert( pCol->pDflt==0 || pCol->pDflt->op==TK_SPAN || isHidden>=2 ); + pColExpr = sqlite3ColumnExpr(pTab,pCol); + assert( pColExpr==0 || pColExpr->op==TK_SPAN || isHidden>=2 ); + assert( pColExpr==0 || !ExprHasProperty(pColExpr, EP_IntValue) + || isHidden>=2 ); sqlite3VdbeMultiLoad(v, 1, pPragma->iArg ? "issisii" : "issisi", i-nHidden, - pCol->zName, + pCol->zCnName, sqlite3ColumnType(pCol,""), pCol->notNull ? 1 : 0, - pCol->pDflt && isHidden<2 ? pCol->pDflt->u.zToken : 0, + (isHidden>=2 || pColExpr==0) ? 0 : pColExpr->u.zToken, k, isHidden); } @@ -128726,6 +133009,85 @@ SQLITE_PRIVATE void sqlite3Pragma( } break; + /* + ** PRAGMA table_list + ** + ** Return a single row for each table, virtual table, or view in the + ** entire schema. + ** + ** schema: Name of attached database hold this table + ** name: Name of the table itself + ** type: "table", "view", "virtual", "shadow" + ** ncol: Number of columns + ** wr: True for a WITHOUT ROWID table + ** strict: True for a STRICT table + */ + case PragTyp_TABLE_LIST: { + int ii; + pParse->nMem = 6; + sqlite3CodeVerifyNamedSchema(pParse, zDb); + for(ii=0; iinDb; ii++){ + HashElem *k; + Hash *pHash; + int initNCol; + if( zDb && sqlite3_stricmp(zDb, db->aDb[ii].zDbSName)!=0 ) continue; + + /* Ensure that the Table.nCol field is initialized for all views + ** and virtual tables. Each time we initialize a Table.nCol value + ** for a table, that can potentially disrupt the hash table, so restart + ** the initialization scan. + */ + pHash = &db->aDb[ii].pSchema->tblHash; + initNCol = sqliteHashCount(pHash); + while( initNCol-- ){ + for(k=sqliteHashFirst(pHash); 1; k=sqliteHashNext(k) ){ + Table *pTab; + if( k==0 ){ initNCol = 0; break; } + pTab = sqliteHashData(k); + if( pTab->nCol==0 ){ + char *zSql = sqlite3MPrintf(db, "SELECT*FROM\"%w\"", pTab->zName); + if( zSql ){ + sqlite3_stmt *pDummy = 0; + (void)sqlite3_prepare(db, zSql, -1, &pDummy, 0); + (void)sqlite3_finalize(pDummy); + sqlite3DbFree(db, zSql); + } + if( db->mallocFailed ){ + sqlite3ErrorMsg(db->pParse, "out of memory"); + db->pParse->rc = SQLITE_NOMEM_BKPT; + } + pHash = &db->aDb[ii].pSchema->tblHash; + break; + } + } + } + + for(k=sqliteHashFirst(pHash); k; k=sqliteHashNext(k) ){ + Table *pTab = sqliteHashData(k); + const char *zType; + if( zRight && sqlite3_stricmp(zRight, pTab->zName)!=0 ) continue; + if( IsView(pTab) ){ + zType = "view"; + }else if( IsVirtual(pTab) ){ + zType = "virtual"; + }else if( pTab->tabFlags & TF_Shadow ){ + zType = "shadow"; + }else{ + zType = "table"; + } + sqlite3VdbeMultiLoad(v, 1, "sssiii", + db->aDb[ii].zDbSName, + sqlite3PreferredTableName(pTab->zName), + zType, + pTab->nCol, + (pTab->tabFlags & TF_WithoutRowid)!=0, + (pTab->tabFlags & TF_Strict)!=0 + ); + } + } + } + break; + #ifdef SQLITE_DEBUG case PragTyp_STATS: { Index *pIdx; @@ -128735,7 +133097,7 @@ SQLITE_PRIVATE void sqlite3Pragma( for(i=sqliteHashFirst(&pDb->pSchema->tblHash); i; i=sqliteHashNext(i)){ Table *pTab = sqliteHashData(i); sqlite3VdbeMultiLoad(v, 1, "ssiii", - pTab->zName, + sqlite3PreferredTableName(pTab->zName), 0, pTab->szTabRow, pTab->nRowLogEst, @@ -128785,7 +133147,7 @@ SQLITE_PRIVATE void sqlite3Pragma( for(i=0; iaiColumn[i]; sqlite3VdbeMultiLoad(v, 1, "iisX", i, cnum, - cnum<0 ? 0 : pTab->aCol[cnum].zName); + cnum<0 ? 0 : pTab->aCol[cnum].zCnName); if( pPragma->iArg ){ sqlite3VdbeMultiLoad(v, 4, "isiX", pIdx->aSortOrder[i], @@ -128854,11 +133216,13 @@ SQLITE_PRIVATE void sqlite3Pragma( pParse->nMem = 6; for(i=0; iu.pHash ){ + assert( p->funcFlags & SQLITE_FUNC_BUILTIN ); pragmaFunclistLine(v, p, 1, showInternFunc); } } for(j=sqliteHashFirst(&db->aFunc); j; j=sqliteHashNext(j)){ p = (FuncDef*)sqliteHashData(j); + assert( (p->funcFlags & SQLITE_FUNC_BUILTIN)==0 ); pragmaFunclistLine(v, p, 0, showInternFunc); } } @@ -128892,8 +133256,8 @@ SQLITE_PRIVATE void sqlite3Pragma( FKey *pFK; Table *pTab; pTab = sqlite3FindTable(db, zRight, zDb); - if( pTab ){ - pFK = pTab->pFKey; + if( pTab && IsOrdinaryTable(pTab) ){ + pFK = pTab->u.tab.pFKey; if( pFK ){ int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); int i = 0; @@ -128906,7 +133270,7 @@ SQLITE_PRIVATE void sqlite3Pragma( i, j, pFK->zTo, - pTab->aCol[pFK->aCol[j].iFrom].zName, + pTab->aCol[pFK->aCol[j].iFrom].zCnName, pFK->aCol[j].zCol, actionName(pFK->aAction[1]), /* ON UPDATE */ actionName(pFK->aAction[0]), /* ON DELETE */ @@ -128933,7 +133297,6 @@ SQLITE_PRIVATE void sqlite3Pragma( HashElem *k; /* Loop counter: Next table in schema */ int x; /* result variable */ int regResult; /* 3 registers to hold a result row */ - int regKey; /* Register to hold key for checking the FK */ int regRow; /* Registers to hold a row from pTab */ int addrTop; /* Top of a loop checking foreign keys */ int addrOk; /* Jump here if the key is OK */ @@ -128941,7 +133304,6 @@ SQLITE_PRIVATE void sqlite3Pragma( regResult = pParse->nMem+1; pParse->nMem += 4; - regKey = ++pParse->nMem; regRow = ++pParse->nMem; k = sqliteHashFirst(&db->aDb[iDb].pSchema->tblHash); while( k ){ @@ -128952,7 +133314,7 @@ SQLITE_PRIVATE void sqlite3Pragma( pTab = (Table*)sqliteHashData(k); k = sqliteHashNext(k); } - if( pTab==0 || pTab->pFKey==0 ) continue; + if( pTab==0 || !IsOrdinaryTable(pTab) || pTab->u.tab.pFKey==0 ) continue; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); zDb = db->aDb[iDb].zDbSName; sqlite3CodeVerifySchema(pParse, iDb); @@ -128960,7 +133322,8 @@ SQLITE_PRIVATE void sqlite3Pragma( if( pTab->nCol+regRow>pParse->nMem ) pParse->nMem = pTab->nCol + regRow; sqlite3OpenTable(pParse, 0, iDb, pTab, OP_OpenRead); sqlite3VdbeLoadString(v, regResult, pTab->zName); - for(i=1, pFK=pTab->pFKey; pFK; i++, pFK=pFK->pNextFrom){ + assert( IsOrdinaryTable(pTab) ); + for(i=1, pFK=pTab->u.tab.pFKey; pFK; i++, pFK=pFK->pNextFrom){ pParent = sqlite3FindTable(db, pFK->zTo, zDb); if( pParent==0 ) continue; pIdx = 0; @@ -128982,7 +133345,8 @@ SQLITE_PRIVATE void sqlite3Pragma( if( pFK ) break; if( pParse->nTabnTab = i; addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, 0); VdbeCoverage(v); - for(i=1, pFK=pTab->pFKey; pFK; i++, pFK=pFK->pNextFrom){ + assert( IsOrdinaryTable(pTab) ); + for(i=1, pFK=pTab->u.tab.pFKey; pFK; i++, pFK=pFK->pNextFrom){ pParent = sqlite3FindTable(db, pFK->zTo, zDb); pIdx = 0; aiCols = 0; @@ -128996,6 +133360,7 @@ SQLITE_PRIVATE void sqlite3Pragma( ** regRow..regRow+n. If any of the child key values are NULL, this ** row cannot cause an FK violation. Jump directly to addrOk in ** this case. */ + if( regRow+pFK->nCol>pParse->nMem ) pParse->nMem = regRow+pFK->nCol; for(j=0; jnCol; j++){ int iCol = aiCols ? aiCols[j] : pFK->aCol[j].iFrom; sqlite3ExprCodeGetColumnOfTable(v, pTab, 0, iCol, regRow+j); @@ -129005,9 +133370,9 @@ SQLITE_PRIVATE void sqlite3Pragma( /* Generate code to query the parent index for a matching parent ** key. If a match is found, jump to addrOk. */ if( pIdx ){ - sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, pFK->nCol, regKey, + sqlite3VdbeAddOp4(v, OP_Affinity, regRow, pFK->nCol, 0, sqlite3IndexAffinityStr(db,pIdx), pFK->nCol); - sqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regKey, 0); + sqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regRow, pFK->nCol); VdbeCoverage(v); }else if( pParent ){ int jmp = sqlite3VdbeCurrentAddr(v)+2; @@ -129182,8 +133547,9 @@ SQLITE_PRIVATE void sqlite3Pragma( int loopTop; int iDataCur, iIdxCur; int r1 = -1; + int bStrict; - if( pTab->tnum<1 ) continue; /* Skip VIEWs or VIRTUAL TABLEs */ + if( !IsOrdinaryTable(pTab) ) continue; if( pObjTab && pObjTab!=pTab ) continue; pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, 0, @@ -129203,23 +133569,48 @@ SQLITE_PRIVATE void sqlite3Pragma( /* Sanity check on record header decoding */ sqlite3VdbeAddOp3(v, OP_Column, iDataCur, pTab->nNVCol-1,3); sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG); + VdbeComment((v, "(right-most column)")); } - /* Verify that all NOT NULL columns really are NOT NULL */ + /* Verify that all NOT NULL columns really are NOT NULL. At the + ** same time verify the type of the content of STRICT tables */ + bStrict = (pTab->tabFlags & TF_Strict)!=0; for(j=0; jnCol; j++){ char *zErr; - int jmp2; + Column *pCol = pTab->aCol + j; + int doError, jmp2; if( j==pTab->iPKey ) continue; - if( pTab->aCol[j].notNull==0 ) continue; + if( pCol->notNull==0 && !bStrict ) continue; + doError = bStrict ? sqlite3VdbeMakeLabel(pParse) : 0; sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3); if( sqlite3VdbeGetOp(v,-1)->opcode==OP_Column ){ sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG); } - jmp2 = sqlite3VdbeAddOp1(v, OP_NotNull, 3); VdbeCoverage(v); - zErr = sqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName, - pTab->aCol[j].zName); - sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); - integrityCheckResultRow(v); - sqlite3VdbeJumpHere(v, jmp2); + if( pCol->notNull ){ + jmp2 = sqlite3VdbeAddOp1(v, OP_NotNull, 3); VdbeCoverage(v); + zErr = sqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName, + pCol->zCnName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + if( bStrict && pCol->eCType!=COLTYPE_ANY ){ + sqlite3VdbeGoto(v, doError); + }else{ + integrityCheckResultRow(v); + } + sqlite3VdbeJumpHere(v, jmp2); + } + if( (pTab->tabFlags & TF_Strict)!=0 + && pCol->eCType!=COLTYPE_ANY + ){ + jmp2 = sqlite3VdbeAddOp3(v, OP_IsNullOrType, 3, 0, + sqlite3StdTypeMap[pCol->eCType-1]); + VdbeCoverage(v); + zErr = sqlite3MPrintf(db, "non-%s value in %s.%s", + sqlite3StdType[pCol->eCType-1], + pTab->zName, pTab->aCol[j].zCnName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + sqlite3VdbeResolveLabel(v, doError); + integrityCheckResultRow(v); + sqlite3VdbeJumpHere(v, jmp2); + } } /* Verify CHECK constraints */ if( pTab->pCheck && (db->flags & SQLITE_IgnoreChecks)==0 ){ @@ -129753,12 +134144,12 @@ SQLITE_PRIVATE void sqlite3Pragma( case PragTyp_ANALYSIS_LIMIT: { sqlite3_int64 N; if( zRight - && sqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK + && sqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK /* IMP: R-40975-20399 */ && N>=0 ){ db->nAnalysisLimit = (int)(N&0x7fffffff); } - returnSingleInt(v, db->nAnalysisLimit); + returnSingleInt(v, db->nAnalysisLimit); /* IMP: R-57594-65522 */ break; } @@ -130160,10 +134551,15 @@ static void corruptSchema( pData->rc = SQLITE_NOMEM_BKPT; }else if( pData->pzErrMsg[0]!=0 ){ /* A error message has already been generated. Do not overwrite it */ - }else if( pData->mInitFlags & (INITFLAG_AlterRename|INITFLAG_AlterDrop) ){ + }else if( pData->mInitFlags & (INITFLAG_AlterMask) ){ + static const char *azAlterType[] = { + "rename", + "drop column", + "add column" + }; *pData->pzErrMsg = sqlite3MPrintf(db, "error in %s %s after %s: %s", azObj[0], azObj[1], - (pData->mInitFlags & INITFLAG_AlterRename) ? "rename" : "drop column", + azAlterType[(pData->mInitFlags&INITFLAG_AlterMask)-1], zExtra ); pData->rc = SQLITE_ERROR; @@ -130227,6 +134623,7 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char UNUSED_PARAMETER2(NotUsed, argc); assert( sqlite3_mutex_held(db->mutex) ); db->mDbFlags |= DBFLAG_EncodingFixed; + if( argv==0 ) return 0; /* Might happen if EMPTY_RESULT_CALLBACKS are on */ pData->nInitRow++; if( db->mallocFailed ){ corruptSchema(pData, argv, 0); @@ -130234,7 +134631,6 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char } assert( iDb>=0 && iDbnDb ); - if( argv==0 ) return 0; /* Might happen if EMPTY_RESULT_CALLBACKS are on */ if( argv[3]==0 ){ corruptSchema(pData, argv, 0); }else if( argv[4] @@ -130265,7 +134661,7 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char } } db->init.orphanTrigger = 0; - db->init.azInit = argv; + db->init.azInit = (const char**)argv; pStmt = 0; TESTONLY(rcp = ) sqlite3Prepare(db, argv[4], -1, 0, 0, &pStmt, 0); rc = db->errCode; @@ -130284,6 +134680,7 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char } } } + db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */ sqlite3_finalize(pStmt); }else if( argv[1]==0 || (argv[4]!=0 && argv[4][0]!=0) ){ corruptSchema(pData, argv, 0); @@ -130508,18 +134905,22 @@ SQLITE_PRIVATE int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFl } #endif } + assert( pDb == &(db->aDb[iDb]) ); if( db->mallocFailed ){ rc = SQLITE_NOMEM_BKPT; sqlite3ResetAllSchemasOfConnection(db); - } - if( rc==SQLITE_OK || (db->flags&SQLITE_NoSchemaError)){ - /* Black magic: If the SQLITE_NoSchemaError flag is set, then consider - ** the schema loaded, even if errors occurred. In this situation the - ** current sqlite3_prepare() operation will fail, but the following one - ** will attempt to compile the supplied statement against whatever subset - ** of the schema was loaded before the error occurred. The primary - ** purpose of this is to allow access to the sqlite_schema table - ** even when its contents have been corrupted. + pDb = &db->aDb[iDb]; + }else + if( rc==SQLITE_OK || ((db->flags&SQLITE_NoSchemaError) && rc!=SQLITE_NOMEM)){ + /* Hack: If the SQLITE_NoSchemaError flag is set, then consider + ** the schema loaded, even if errors (other than OOM) occurred. In + ** this situation the current sqlite3_prepare() operation will fail, + ** but the following one will attempt to compile the supplied statement + ** against whatever subset of the schema was loaded before the error + ** occurred. + ** + ** The primary purpose of this is to allow access to the sqlite_schema + ** table even when its contents have been corrupted. */ DbSetProperty(db, iDb, DB_SchemaLoaded); rc = SQLITE_OK; @@ -130629,6 +135030,7 @@ static void schemaIsValid(Parse *pParse){ rc = sqlite3BtreeBeginTrans(pBt, 0, 0); if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){ sqlite3OomFault(db); + pParse->rc = SQLITE_NOMEM; } if( rc!=SQLITE_OK ) return; openedTransaction = 1; @@ -130688,8 +135090,14 @@ SQLITE_PRIVATE int sqlite3SchemaToIndex(sqlite3 *db, Schema *pSchema){ /* ** Free all memory allocations in the pParse object */ -SQLITE_PRIVATE void sqlite3ParserReset(Parse *pParse){ +SQLITE_PRIVATE void sqlite3ParseObjectReset(Parse *pParse){ sqlite3 *db = pParse->db; + assert( db!=0 ); + assert( db->pParse==pParse ); + assert( pParse->nested==0 ); +#ifndef SQLITE_OMIT_SHARED_CACHE + sqlite3DbFree(db, pParse->aTableLock); +#endif while( pParse->pCleanup ){ ParseCleanup *pCleanup = pParse->pCleanup; pParse->pCleanup = pCleanup->pNext; @@ -130700,11 +135108,12 @@ SQLITE_PRIVATE void sqlite3ParserReset(Parse *pParse){ if( pParse->pConstExpr ){ sqlite3ExprListDelete(db, pParse->pConstExpr); } - if( db ){ - assert( db->lookaside.bDisable >= pParse->disableLookaside ); - db->lookaside.bDisable -= pParse->disableLookaside; - db->lookaside.sz = db->lookaside.bDisable ? 0 : db->lookaside.szTrue; - } + assert( db->lookaside.bDisable >= pParse->disableLookaside ); + db->lookaside.bDisable -= pParse->disableLookaside; + db->lookaside.sz = db->lookaside.bDisable ? 0 : db->lookaside.szTrue; + assert( pParse->db->pParse==pParse ); + db->pParse = pParse->pOuterParse; + pParse->db = 0; pParse->disableLookaside = 0; } @@ -130717,7 +135126,7 @@ SQLITE_PRIVATE void sqlite3ParserReset(Parse *pParse){ ** cost for this mechansim (an extra malloc), so it should not be used ** for common cleanups that happen on most calls. But for less ** common cleanups, we save a single NULL-pointer comparison in -** sqlite3ParserReset(), which reduces the total CPU cycle count. +** sqlite3ParseObjectReset(), which reduces the total CPU cycle count. ** ** If a memory allocation error occurs, then the cleanup happens immediately. ** When either SQLITE_DEBUG or SQLITE_COVERAGE_TEST are defined, the @@ -130757,6 +135166,33 @@ SQLITE_PRIVATE void *sqlite3ParserAddCleanup( return pPtr; } +/* +** Turn bulk memory into a valid Parse object and link that Parse object +** into database connection db. +** +** Call sqlite3ParseObjectReset() to undo this operation. +** +** Caution: Do not confuse this routine with sqlite3ParseObjectInit() which +** is generated by Lemon. +*/ +SQLITE_PRIVATE void sqlite3ParseObjectInit(Parse *pParse, sqlite3 *db){ + memset(PARSE_HDR(pParse), 0, PARSE_HDR_SZ); + memset(PARSE_TAIL(pParse), 0, PARSE_TAIL_SZ); + assert( db->pParse!=pParse ); + pParse->pOuterParse = db->pParse; + db->pParse = pParse; + pParse->db = db; + if( db->mallocFailed ) sqlite3ErrorMsg(pParse, "out of memory"); +} + +/* +** Maximum number of times that we will try again to prepare a statement +** that returns SQLITE_ERROR_RETRY. +*/ +#ifndef SQLITE_MAX_PREPARE_RETRY +# define SQLITE_MAX_PREPARE_RETRY 25 +#endif + /* ** Compile the UTF-8 encoded SQL statement zSql into a statement handle. */ @@ -130769,16 +135205,19 @@ static int sqlite3Prepare( sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ const char **pzTail /* OUT: End of parsed string */ ){ - char *zErrMsg = 0; /* Error message */ int rc = SQLITE_OK; /* Result code */ int i; /* Loop counter */ Parse sParse; /* Parsing context */ - memset(&sParse, 0, PARSE_HDR_SZ); + /* sqlite3ParseObjectInit(&sParse, db); // inlined for performance */ + memset(PARSE_HDR(&sParse), 0, PARSE_HDR_SZ); memset(PARSE_TAIL(&sParse), 0, PARSE_TAIL_SZ); + sParse.pOuterParse = db->pParse; + db->pParse = &sParse; + sParse.db = db; sParse.pReprepare = pReprepare; assert( ppStmt && *ppStmt==0 ); - /* assert( !db->mallocFailed ); // not true with SQLITE_USE_ALLOCA */ + if( db->mallocFailed ) sqlite3ErrorMsg(&sParse, "out of memory"); assert( sqlite3_mutex_held(db->mutex) ); /* For a long-term use prepared statement avoid the use of @@ -130831,7 +135270,6 @@ static int sqlite3Prepare( sqlite3VtabUnlockList(db); - sParse.db = db; if( nBytes>=0 && (nBytes==0 || zSql[nBytes-1]!=0) ){ char *zSqlCopy; int mxLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH]; @@ -130844,14 +135282,14 @@ static int sqlite3Prepare( } zSqlCopy = sqlite3DbStrNDup(db, zSql, nBytes); if( zSqlCopy ){ - sqlite3RunParser(&sParse, zSqlCopy, &zErrMsg); + sqlite3RunParser(&sParse, zSqlCopy); sParse.zTail = &zSql[sParse.zTail-zSqlCopy]; sqlite3DbFree(db, zSqlCopy); }else{ sParse.zTail = &zSql[nBytes]; } }else{ - sqlite3RunParser(&sParse, zSql, &zErrMsg); + sqlite3RunParser(&sParse, zSql); } assert( 0==sParse.nQueryLoop ); @@ -130864,9 +135302,10 @@ static int sqlite3Prepare( } if( db->mallocFailed ){ sParse.rc = SQLITE_NOMEM_BKPT; + sParse.checkSchema = 0; } if( sParse.rc!=SQLITE_OK && sParse.rc!=SQLITE_DONE ){ - if( sParse.checkSchema ){ + if( sParse.checkSchema && db->init.busy==0 ){ schemaIsValid(&sParse); } if( sParse.pVdbe ){ @@ -130874,14 +135313,14 @@ static int sqlite3Prepare( } assert( 0==(*ppStmt) ); rc = sParse.rc; - if( zErrMsg ){ - sqlite3ErrorWithMsg(db, rc, "%s", zErrMsg); - sqlite3DbFree(db, zErrMsg); + if( sParse.zErrMsg ){ + sqlite3ErrorWithMsg(db, rc, "%s", sParse.zErrMsg); + sqlite3DbFree(db, sParse.zErrMsg); }else{ sqlite3Error(db, rc); } }else{ - assert( zErrMsg==0 ); + assert( sParse.zErrMsg==0 ); *ppStmt = (sqlite3_stmt*)sParse.pVdbe; rc = SQLITE_OK; sqlite3ErrorClear(db); @@ -130897,7 +135336,7 @@ static int sqlite3Prepare( end_prepare: - sqlite3ParserReset(&sParse); + sqlite3ParseObjectReset(&sParse); return rc; } static int sqlite3LockAndPrepare( @@ -130927,7 +135366,8 @@ static int sqlite3LockAndPrepare( ** reset is considered a permanent error. */ rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail); assert( rc==SQLITE_OK || *ppStmt==0 ); - }while( rc==SQLITE_ERROR_RETRY + if( rc==SQLITE_OK || db->mallocFailed ) break; + }while( (rc==SQLITE_ERROR_RETRY && (cnt++) same as JOIN +** NATURAL CROSS JOIN -> same as NATURAL JOIN +** OUTER LEFT JOIN -> same as LEFT JOIN +** LEFT NATURAL JOIN -> same as NATURAL LEFT JOIN +** LEFT RIGHT JOIN -> same as FULL JOIN +** RIGHT OUTER FULL JOIN -> same as FULL JOIN +** CROSS CROSS CROSS JOIN -> same as JOIN +** +** The only restrictions on the join type name are: +** +** * "INNER" cannot appear together with "OUTER", "LEFT", "RIGHT", +** or "FULL". +** +** * "CROSS" cannot appear together with "OUTER", "LEFT", "RIGHT, +** or "FULL". +** +** * If "OUTER" is present then there must also be one of +** "LEFT", "RIGHT", or "FULL" */ SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){ int jointype = 0; @@ -131363,13 +135849,13 @@ SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *p u8 nChar; /* Length of the keyword in characters */ u8 code; /* Join type mask */ } aKeyword[] = { - /* natural */ { 0, 7, JT_NATURAL }, - /* left */ { 6, 4, JT_LEFT|JT_OUTER }, - /* outer */ { 10, 5, JT_OUTER }, - /* right */ { 14, 5, JT_RIGHT|JT_OUTER }, - /* full */ { 19, 4, JT_LEFT|JT_RIGHT|JT_OUTER }, - /* inner */ { 23, 5, JT_INNER }, - /* cross */ { 28, 5, JT_INNER|JT_CROSS }, + /* (0) natural */ { 0, 7, JT_NATURAL }, + /* (1) left */ { 6, 4, JT_LEFT|JT_OUTER }, + /* (2) outer */ { 10, 5, JT_OUTER }, + /* (3) right */ { 14, 5, JT_RIGHT|JT_OUTER }, + /* (4) full */ { 19, 4, JT_LEFT|JT_RIGHT|JT_OUTER }, + /* (5) inner */ { 23, 5, JT_INNER }, + /* (6) cross */ { 28, 5, JT_INNER|JT_CROSS }, }; int i, j; apAll[0] = pA; @@ -131392,18 +135878,15 @@ SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *p } if( (jointype & (JT_INNER|JT_OUTER))==(JT_INNER|JT_OUTER) || - (jointype & JT_ERROR)!=0 + (jointype & JT_ERROR)!=0 || + (jointype & (JT_OUTER|JT_LEFT|JT_RIGHT))==JT_OUTER ){ - const char *zSp = " "; - assert( pB!=0 ); - if( pC==0 ){ zSp++; } - sqlite3ErrorMsg(pParse, "unknown or unsupported join type: " - "%T %T%s%T", pA, pB, zSp, pC); - jointype = JT_INNER; - }else if( (jointype & JT_OUTER)!=0 - && (jointype & (JT_LEFT|JT_RIGHT))!=JT_LEFT ){ - sqlite3ErrorMsg(pParse, - "RIGHT and FULL OUTER JOINs are not currently supported"); + const char *zSp1 = " "; + const char *zSp2 = " "; + if( pB==0 ){ zSp1++; } + if( pC==0 ){ zSp2++; } + sqlite3ErrorMsg(pParse, "unknown join type: " + "%T%s%T%s%T", pA, zSp1, pB, zSp2, pC); jointype = JT_INNER; } return jointype; @@ -131418,14 +135901,31 @@ SQLITE_PRIVATE int sqlite3ColumnIndex(Table *pTab, const char *zCol){ u8 h = sqlite3StrIHash(zCol); Column *pCol; for(pCol=pTab->aCol, i=0; inCol; pCol++, i++){ - if( pCol->hName==h && sqlite3StrICmp(pCol->zName, zCol)==0 ) return i; + if( pCol->hName==h && sqlite3StrICmp(pCol->zCnName, zCol)==0 ) return i; } return -1; } /* -** Search the first N tables in pSrc, from left to right, looking for a -** table that has a column named zCol. +** Mark a subquery result column as having been used. +*/ +SQLITE_PRIVATE void sqlite3SrcItemColumnUsed(SrcItem *pItem, int iCol){ + assert( pItem!=0 ); + assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) ); + if( pItem->fg.isNestedFrom ){ + ExprList *pResults; + assert( pItem->pSelect!=0 ); + pResults = pItem->pSelect->pEList; + assert( pResults!=0 ); + assert( iCol>=0 && iColnExpr ); + pResults->a[iCol].fg.bUsed = 1; + } +} + +/* +** Search the tables iStart..iEnd (inclusive) in pSrc, looking for a +** table that has a column named zCol. The search is left-to-right. +** The first match found is returned. ** ** When found, set *piTab and *piCol to the table index and column index ** of the matching column and return TRUE. @@ -131434,22 +135934,27 @@ SQLITE_PRIVATE int sqlite3ColumnIndex(Table *pTab, const char *zCol){ */ static int tableAndColumnIndex( SrcList *pSrc, /* Array of tables to search */ - int N, /* Number of tables in pSrc->a[] to search */ + int iStart, /* First member of pSrc->a[] to check */ + int iEnd, /* Last member of pSrc->a[] to check */ const char *zCol, /* Name of the column we are looking for */ int *piTab, /* Write index of pSrc->a[] here */ int *piCol, /* Write index of pSrc->a[*piTab].pTab->aCol[] here */ - int bIgnoreHidden /* True to ignore hidden columns */ + int bIgnoreHidden /* Ignore hidden columns */ ){ int i; /* For looping over tables in pSrc */ int iCol; /* Index of column matching zCol */ + assert( iEndnSrc ); + assert( iStart>=0 ); assert( (piTab==0)==(piCol==0) ); /* Both or neither are NULL */ - for(i=0; ia[i].pTab, zCol); if( iCol>=0 && (bIgnoreHidden==0 || IsHiddenColumn(&pSrc->a[i].pTab->aCol[iCol])==0) ){ if( piTab ){ + sqlite3SrcItemColumnUsed(&pSrc->a[i], iCol); *piTab = i; *piCol = iCol; } @@ -131460,63 +135965,19 @@ static int tableAndColumnIndex( } /* -** This function is used to add terms implied by JOIN syntax to the -** WHERE clause expression of a SELECT statement. The new term, which -** is ANDed with the existing WHERE clause, is of the form: -** -** (tab1.col1 = tab2.col2) -** -** where tab1 is the iSrc'th table in SrcList pSrc and tab2 is the -** (iSrc+1)'th. Column col1 is column iColLeft of tab1, and col2 is -** column iColRight of tab2. -*/ -static void addWhereTerm( - Parse *pParse, /* Parsing context */ - SrcList *pSrc, /* List of tables in FROM clause */ - int iLeft, /* Index of first table to join in pSrc */ - int iColLeft, /* Index of column in first table */ - int iRight, /* Index of second table in pSrc */ - int iColRight, /* Index of column in second table */ - int isOuterJoin, /* True if this is an OUTER join */ - Expr **ppWhere /* IN/OUT: The WHERE clause to add to */ -){ - sqlite3 *db = pParse->db; - Expr *pE1; - Expr *pE2; - Expr *pEq; - - assert( iLeftnSrc>iRight ); - assert( pSrc->a[iLeft].pTab ); - assert( pSrc->a[iRight].pTab ); - - pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iColLeft); - pE2 = sqlite3CreateColumnExpr(db, pSrc, iRight, iColRight); - - pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2); - if( pEq && isOuterJoin ){ - ExprSetProperty(pEq, EP_FromJoin); - assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) ); - ExprSetVVAProperty(pEq, EP_NoReduce); - pEq->iRightJoinTable = pE2->iTable; - } - *ppWhere = sqlite3ExprAnd(pParse, *ppWhere, pEq); -} - -/* -** Set the EP_FromJoin property on all terms of the given expression. -** And set the Expr.iRightJoinTable to iTable for every term in the +** Set the EP_OuterON property on all terms of the given expression. +** And set the Expr.w.iJoin to iTable for every term in the ** expression. ** -** The EP_FromJoin property is used on terms of an expression to tell -** the LEFT OUTER JOIN processing logic that this term is part of the +** The EP_OuterON property is used on terms of an expression to tell +** the OUTER JOIN processing logic that this term is part of the ** join restriction specified in the ON or USING clause and not a part ** of the more general WHERE clause. These terms are moved over to the ** WHERE clause during join processing but we need to remember that they ** originated in the ON or USING clause. ** -** The Expr.iRightJoinTable tells the WHERE clause processing that the -** expression depends on table iRightJoinTable even if that table is not +** The Expr.w.iJoin tells the WHERE clause processing that the +** expression depends on table w.iJoin even if that table is not ** explicitly mentioned in the expression. That information is needed ** for cases like this: ** @@ -131529,64 +135990,86 @@ static void addWhereTerm( ** after the t1 loop and rows with t1.x!=5 will never appear in ** the output, which is incorrect. */ -SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr *p, int iTable){ +SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr *p, int iTable, u32 joinFlag){ + assert( joinFlag==EP_OuterON || joinFlag==EP_InnerON ); while( p ){ - ExprSetProperty(p, EP_FromJoin); + ExprSetProperty(p, joinFlag); assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); ExprSetVVAProperty(p, EP_NoReduce); - p->iRightJoinTable = iTable; - if( p->op==TK_FUNCTION && p->x.pList ){ - int i; - for(i=0; ix.pList->nExpr; i++){ - sqlite3SetJoinExpr(p->x.pList->a[i].pExpr, iTable); + p->w.iJoin = iTable; + if( p->op==TK_FUNCTION ){ + assert( ExprUseXList(p) ); + if( p->x.pList ){ + int i; + for(i=0; ix.pList->nExpr; i++){ + sqlite3SetJoinExpr(p->x.pList->a[i].pExpr, iTable, joinFlag); + } } } - sqlite3SetJoinExpr(p->pLeft, iTable); + sqlite3SetJoinExpr(p->pLeft, iTable, joinFlag); p = p->pRight; } } -/* Undo the work of sqlite3SetJoinExpr(). In the expression p, convert every -** term that is marked with EP_FromJoin and iRightJoinTable==iTable into -** an ordinary term that omits the EP_FromJoin mark. +/* Undo the work of sqlite3SetJoinExpr(). This is used when a LEFT JOIN +** is simplified into an ordinary JOIN, and when an ON expression is +** "pushed down" into the WHERE clause of a subquery. ** -** This happens when a LEFT JOIN is simplified into an ordinary JOIN. +** Convert every term that is marked with EP_OuterON and w.iJoin==iTable into +** an ordinary term that omits the EP_OuterON mark. Or if iTable<0, then +** just clear every EP_OuterON and EP_InnerON mark from the expression tree. +** +** If nullable is true, that means that Expr p might evaluate to NULL even +** if it is a reference to a NOT NULL column. This can happen, for example, +** if the table that p references is on the left side of a RIGHT JOIN. +** If nullable is true, then take care to not remove the EP_CanBeNull bit. +** See forum thread https://sqlite.org/forum/forumpost/b40696f50145d21c */ -static void unsetJoinExpr(Expr *p, int iTable){ +static void unsetJoinExpr(Expr *p, int iTable, int nullable){ while( p ){ - if( ExprHasProperty(p, EP_FromJoin) - && (iTable<0 || p->iRightJoinTable==iTable) ){ - ExprClearProperty(p, EP_FromJoin); + if( iTable<0 || (ExprHasProperty(p, EP_OuterON) && p->w.iJoin==iTable) ){ + ExprClearProperty(p, EP_OuterON|EP_InnerON); + if( iTable>=0 ) ExprSetProperty(p, EP_InnerON); } - if( p->op==TK_COLUMN && p->iTable==iTable ){ + if( p->op==TK_COLUMN && p->iTable==iTable && !nullable ){ ExprClearProperty(p, EP_CanBeNull); } - if( p->op==TK_FUNCTION && p->x.pList ){ - int i; - for(i=0; ix.pList->nExpr; i++){ - unsetJoinExpr(p->x.pList->a[i].pExpr, iTable); + if( p->op==TK_FUNCTION ){ + assert( ExprUseXList(p) ); + if( p->x.pList ){ + int i; + for(i=0; ix.pList->nExpr; i++){ + unsetJoinExpr(p->x.pList->a[i].pExpr, iTable, nullable); + } } } - unsetJoinExpr(p->pLeft, iTable); + unsetJoinExpr(p->pLeft, iTable, nullable); p = p->pRight; } } /* ** This routine processes the join information for a SELECT statement. -** ON and USING clauses are converted into extra terms of the WHERE clause. -** NATURAL joins also create extra WHERE clause terms. +** +** * A NATURAL join is converted into a USING join. After that, we +** do not need to be concerned with NATURAL joins and we only have +** think about USING joins. +** +** * ON and USING clauses result in extra terms being added to the +** WHERE clause to enforce the specified constraints. The extra +** WHERE clause terms will be tagged with EP_OuterON or +** EP_InnerON so that we know that they originated in ON/USING. ** ** The terms of a FROM clause are contained in the Select.pSrc structure. ** The left most table is the first entry in Select.pSrc. The right-most ** table is the last entry. The join operator is held in the entry to -** the left. Thus entry 0 contains the join operator for the join between +** the right. Thus entry 1 contains the join operator for the join between ** entries 0 and 1. Any ON or USING clauses associated with the join are -** also attached to the left entry. +** also attached to the right entry. ** ** This routine returns the number of errors encountered. */ -static int sqliteProcessJoin(Parse *pParse, Select *p){ +static int sqlite3ProcessJoin(Parse *pParse, Select *p){ SrcList *pSrc; /* All tables in the FROM clause */ int i, j; /* Loop counters */ SrcItem *pLeft; /* Left table being joined */ @@ -131597,49 +136080,41 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){ pRight = &pLeft[1]; for(i=0; inSrc-1; i++, pRight++, pLeft++){ Table *pRightTab = pRight->pTab; - int isOuter; + u32 joinType; if( NEVER(pLeft->pTab==0 || pRightTab==0) ) continue; - isOuter = (pRight->fg.jointype & JT_OUTER)!=0; + joinType = (pRight->fg.jointype & JT_OUTER)!=0 ? EP_OuterON : EP_InnerON; - /* When the NATURAL keyword is present, add WHERE clause terms for - ** every column that the two tables have in common. + /* If this is a NATURAL join, synthesize an approprate USING clause + ** to specify which columns should be joined. */ if( pRight->fg.jointype & JT_NATURAL ){ - if( pRight->pOn || pRight->pUsing ){ + IdList *pUsing = 0; + if( pRight->fg.isUsing || pRight->u3.pOn ){ sqlite3ErrorMsg(pParse, "a NATURAL join may not have " "an ON or USING clause", 0); return 1; } for(j=0; jnCol; j++){ char *zName; /* Name of column in the right table */ - int iLeft; /* Matching left table */ - int iLeftCol; /* Matching column in the left table */ if( IsHiddenColumn(&pRightTab->aCol[j]) ) continue; - zName = pRightTab->aCol[j].zName; - if( tableAndColumnIndex(pSrc, i+1, zName, &iLeft, &iLeftCol, 1) ){ - addWhereTerm(pParse, pSrc, iLeft, iLeftCol, i+1, j, - isOuter, &p->pWhere); + zName = pRightTab->aCol[j].zCnName; + if( tableAndColumnIndex(pSrc, 0, i, zName, 0, 0, 1) ){ + pUsing = sqlite3IdListAppend(pParse, pUsing, 0); + if( pUsing ){ + assert( pUsing->nId>0 ); + assert( pUsing->a[pUsing->nId-1].zName==0 ); + pUsing->a[pUsing->nId-1].zName = sqlite3DbStrDup(pParse->db, zName); + } } } - } - - /* Disallow both ON and USING clauses in the same join - */ - if( pRight->pOn && pRight->pUsing ){ - sqlite3ErrorMsg(pParse, "cannot have both ON and USING " - "clauses in the same join"); - return 1; - } - - /* Add the ON clause to the end of the WHERE clause, connected by - ** an AND operator. - */ - if( pRight->pOn ){ - if( isOuter ) sqlite3SetJoinExpr(pRight->pOn, pRight->iCursor); - p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pRight->pOn); - pRight->pOn = 0; + if( pUsing ){ + pRight->fg.isUsing = 1; + pRight->fg.isSynthUsing = 1; + pRight->u3.pUsing = pUsing; + } + if( pParse->nErr ) return 1; } /* Create extra terms on the WHERE clause for each column named @@ -131649,27 +136124,88 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){ ** Report an error if any column mentioned in the USING clause is ** not contained in both tables to be joined. */ - if( pRight->pUsing ){ - IdList *pList = pRight->pUsing; + if( pRight->fg.isUsing ){ + IdList *pList = pRight->u3.pUsing; + sqlite3 *db = pParse->db; + assert( pList!=0 ); for(j=0; jnId; j++){ char *zName; /* Name of the term in the USING clause */ int iLeft; /* Table on the left with matching column name */ int iLeftCol; /* Column number of matching column on the left */ int iRightCol; /* Column number of matching column on the right */ + Expr *pE1; /* Reference to the column on the LEFT of the join */ + Expr *pE2; /* Reference to the column on the RIGHT of the join */ + Expr *pEq; /* Equality constraint. pE1 == pE2 */ zName = pList->a[j].zName; iRightCol = sqlite3ColumnIndex(pRightTab, zName); if( iRightCol<0 - || !tableAndColumnIndex(pSrc, i+1, zName, &iLeft, &iLeftCol, 0) + || tableAndColumnIndex(pSrc, 0, i, zName, &iLeft, &iLeftCol, + pRight->fg.isSynthUsing)==0 ){ sqlite3ErrorMsg(pParse, "cannot join using column %s - column " "not present in both tables", zName); return 1; } - addWhereTerm(pParse, pSrc, iLeft, iLeftCol, i+1, iRightCol, - isOuter, &p->pWhere); + pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iLeftCol); + sqlite3SrcItemColumnUsed(&pSrc->a[iLeft], iLeftCol); + if( (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ + /* This branch runs if the query contains one or more RIGHT or FULL + ** JOINs. If only a single table on the left side of this join + ** contains the zName column, then this branch is a no-op. + ** But if there are two or more tables on the left side + ** of the join, construct a coalesce() function that gathers all + ** such tables. Raise an error if more than one of those references + ** to zName is not also within a prior USING clause. + ** + ** We really ought to raise an error if there are two or more + ** non-USING references to zName on the left of an INNER or LEFT + ** JOIN. But older versions of SQLite do not do that, so we avoid + ** adding a new error so as to not break legacy applications. + */ + ExprList *pFuncArgs = 0; /* Arguments to the coalesce() */ + static const Token tkCoalesce = { "coalesce", 8 }; + while( tableAndColumnIndex(pSrc, iLeft+1, i, zName, &iLeft, &iLeftCol, + pRight->fg.isSynthUsing)!=0 ){ + if( pSrc->a[iLeft].fg.isUsing==0 + || sqlite3IdListIndex(pSrc->a[iLeft].u3.pUsing, zName)<0 + ){ + sqlite3ErrorMsg(pParse, "ambiguous reference to %s in USING()", + zName); + break; + } + pFuncArgs = sqlite3ExprListAppend(pParse, pFuncArgs, pE1); + pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iLeftCol); + sqlite3SrcItemColumnUsed(&pSrc->a[iLeft], iLeftCol); + } + if( pFuncArgs ){ + pFuncArgs = sqlite3ExprListAppend(pParse, pFuncArgs, pE1); + pE1 = sqlite3ExprFunction(pParse, pFuncArgs, &tkCoalesce, 0); + } + } + pE2 = sqlite3CreateColumnExpr(db, pSrc, i+1, iRightCol); + sqlite3SrcItemColumnUsed(pRight, iRightCol); + pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2); + assert( pE2!=0 || pEq==0 ); + if( pEq ){ + ExprSetProperty(pEq, joinType); + assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) ); + ExprSetVVAProperty(pEq, EP_NoReduce); + pEq->w.iJoin = pE2->iTable; + } + p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pEq); } } + + /* Add the ON clause to the end of the WHERE clause, connected by + ** an AND operator. + */ + else if( pRight->u3.pOn ){ + sqlite3SetJoinExpr(pRight->u3.pOn, pRight->iCursor, joinType); + p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pRight->u3.pOn); + pRight->u3.pOn = 0; + pRight->fg.isOn = 1; + } } return 0; } @@ -131888,31 +136424,157 @@ static void codeOffset( } /* -** Add code that will check to make sure the N registers starting at iMem -** form a distinct entry. iTab is a sorting index that holds previously -** seen combinations of the N values. A new entry is made in iTab -** if the current N values are new. +** Add code that will check to make sure the array of registers starting at +** iMem form a distinct entry. This is used by both "SELECT DISTINCT ..." and +** distinct aggregates ("SELECT count(DISTINCT ) ..."). Three strategies +** are available. Which is used depends on the value of parameter eTnctType, +** as follows: ** -** A jump to addrRepeat is made and the N+1 values are popped from the -** stack if the top N elements are not distinct. +** WHERE_DISTINCT_UNORDERED/WHERE_DISTINCT_NOOP: +** Build an ephemeral table that contains all entries seen before and +** skip entries which have been seen before. +** +** Parameter iTab is the cursor number of an ephemeral table that must +** be opened before the VM code generated by this routine is executed. +** The ephemeral cursor table is queried for a record identical to the +** record formed by the current array of registers. If one is found, +** jump to VM address addrRepeat. Otherwise, insert a new record into +** the ephemeral cursor and proceed. +** +** The returned value in this case is a copy of parameter iTab. +** +** WHERE_DISTINCT_ORDERED: +** In this case rows are being delivered sorted order. The ephermal +** table is not required. Instead, the current set of values +** is compared against previous row. If they match, the new row +** is not distinct and control jumps to VM address addrRepeat. Otherwise, +** the VM program proceeds with processing the new row. +** +** The returned value in this case is the register number of the first +** in an array of registers used to store the previous result row so that +** it can be compared to the next. The caller must ensure that this +** register is initialized to NULL. (The fixDistinctOpenEph() routine +** will take care of this initialization.) +** +** WHERE_DISTINCT_UNIQUE: +** In this case it has already been determined that the rows are distinct. +** No special action is required. The return value is zero. +** +** Parameter pEList is the list of expressions used to generated the +** contents of each row. It is used by this routine to determine (a) +** how many elements there are in the array of registers and (b) the +** collation sequences that should be used for the comparisons if +** eTnctType is WHERE_DISTINCT_ORDERED. */ -static void codeDistinct( +static int codeDistinct( Parse *pParse, /* Parsing and code generating context */ + int eTnctType, /* WHERE_DISTINCT_* value */ int iTab, /* A sorting index used to test for distinctness */ int addrRepeat, /* Jump to here if not distinct */ - int N, /* Number of elements */ - int iMem /* First element */ + ExprList *pEList, /* Expression for each element */ + int regElem /* First element */ ){ - Vdbe *v; - int r1; + int iRet = 0; + int nResultCol = pEList->nExpr; + Vdbe *v = pParse->pVdbe; - v = pParse->pVdbe; - r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, iMem, N); VdbeCoverage(v); - sqlite3VdbeAddOp3(v, OP_MakeRecord, iMem, N, r1); - sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r1, iMem, N); - sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); - sqlite3ReleaseTempReg(pParse, r1); + switch( eTnctType ){ + case WHERE_DISTINCT_ORDERED: { + int i; + int iJump; /* Jump destination */ + int regPrev; /* Previous row content */ + + /* Allocate space for the previous row */ + iRet = regPrev = pParse->nMem+1; + pParse->nMem += nResultCol; + + iJump = sqlite3VdbeCurrentAddr(v) + nResultCol; + for(i=0; ia[i].pExpr); + if( idb->mallocFailed ); + sqlite3VdbeAddOp3(v, OP_Copy, regElem, regPrev, nResultCol-1); + break; + } + + case WHERE_DISTINCT_UNIQUE: { + /* nothing to do */ + break; + } + + default: { + int r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, regElem, nResultCol); + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regElem, nResultCol, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r1, regElem, nResultCol); + sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + sqlite3ReleaseTempReg(pParse, r1); + iRet = iTab; + break; + } + } + + return iRet; +} + +/* +** This routine runs after codeDistinct(). It makes necessary +** adjustments to the OP_OpenEphemeral opcode that the codeDistinct() +** routine made use of. This processing must be done separately since +** sometimes codeDistinct is called before the OP_OpenEphemeral is actually +** laid down. +** +** WHERE_DISTINCT_NOOP: +** WHERE_DISTINCT_UNORDERED: +** +** No adjustments necessary. This function is a no-op. +** +** WHERE_DISTINCT_UNIQUE: +** +** The ephemeral table is not needed. So change the +** OP_OpenEphemeral opcode into an OP_Noop. +** +** WHERE_DISTINCT_ORDERED: +** +** The ephemeral table is not needed. But we do need register +** iVal to be initialized to NULL. So change the OP_OpenEphemeral +** into an OP_Null on the iVal register. +*/ +static void fixDistinctOpenEph( + Parse *pParse, /* Parsing and code generating context */ + int eTnctType, /* WHERE_DISTINCT_* value */ + int iVal, /* Value returned by codeDistinct() */ + int iOpenEphAddr /* Address of OP_OpenEphemeral instruction for iTab */ +){ + if( pParse->nErr==0 + && (eTnctType==WHERE_DISTINCT_UNIQUE || eTnctType==WHERE_DISTINCT_ORDERED) + ){ + Vdbe *v = pParse->pVdbe; + sqlite3VdbeChangeToNoop(v, iOpenEphAddr); + if( sqlite3VdbeGetOp(v, iOpenEphAddr+1)->opcode==OP_Explain ){ + sqlite3VdbeChangeToNoop(v, iOpenEphAddr+1); + } + if( eTnctType==WHERE_DISTINCT_ORDERED ){ + /* Change the OP_OpenEphemeral to an OP_Null that sets the MEM_Cleared + ** bit on the first register of the previous value. This will cause the + ** OP_Ne added in codeDistinct() to always fail on the first iteration of + ** the loop even if the first row is all NULLs. */ + VdbeOp *pOp = sqlite3VdbeGetOp(v, iOpenEphAddr); + pOp->opcode = OP_Null; + pOp->p1 = 1; + pOp->p2 = iVal; + } + } } #ifdef SQLITE_ENABLE_SORTER_REFERENCES @@ -131932,7 +136594,7 @@ static void codeDistinct( ** retrieved directly from table t1. If the values are very large, this ** can be more efficient than storing them directly in the sorter records. ** -** The ExprList_item.bSorterRef flag is set for each expression in pEList +** The ExprList_item.fg.bSorterRef flag is set for each expression in pEList ** for which the sorter-reference optimization should be enabled. ** Additionally, the pSort->aDefer[] array is populated with entries ** for all cursors required to evaluate all selected expressions. Finally. @@ -131953,9 +136615,13 @@ static void selectExprDefer( struct ExprList_item *pItem = &pEList->a[i]; if( pItem->u.x.iOrderByCol==0 ){ Expr *pExpr = pItem->pExpr; - Table *pTab = pExpr->y.pTab; - if( pExpr->op==TK_COLUMN && pExpr->iColumn>=0 && pTab && !IsVirtual(pTab) - && (pTab->aCol[pExpr->iColumn].colFlags & COLFLAG_SORTERREF) + Table *pTab; + if( pExpr->op==TK_COLUMN + && pExpr->iColumn>=0 + && ALWAYS( ExprUseYTab(pExpr) ) + && (pTab = pExpr->y.pTab)!=0 + && IsOrdinaryTable(pTab) + && (pTab->aCol[pExpr->iColumn].colFlags & COLFLAG_SORTERREF)!=0 ){ int j; for(j=0; jiTable = pExpr->iTable; + assert( ExprUseYTab(pNew) ); pNew->y.pTab = pExpr->y.pTab; pNew->iColumn = pPk ? pPk->aiColumn[k] : -1; pExtra = sqlite3ExprListAppend(pParse, pExtra, pNew); @@ -131987,7 +136654,7 @@ static void selectExprDefer( nDefer++; } } - pItem->bSorterRef = 1; + pItem->fg.bSorterRef = 1; } } } @@ -132118,7 +136785,7 @@ static void selectInnerLoop( for(i=0; inExpr; i++){ if( pEList->a[i].u.x.iOrderByCol>0 #ifdef SQLITE_ENABLE_SORTER_REFERENCES - || pEList->a[i].bSorterRef + || pEList->a[i].fg.bSorterRef #endif ){ nResultCol--; @@ -132160,59 +136827,11 @@ static void selectInnerLoop( ** part of the result. */ if( hasDistinct ){ - switch( pDistinct->eTnctType ){ - case WHERE_DISTINCT_ORDERED: { - VdbeOp *pOp; /* No longer required OpenEphemeral instr. */ - int iJump; /* Jump destination */ - int regPrev; /* Previous row content */ - - /* Allocate space for the previous row */ - regPrev = pParse->nMem+1; - pParse->nMem += nResultCol; - - /* Change the OP_OpenEphemeral coded earlier to an OP_Null - ** sets the MEM_Cleared bit on the first register of the - ** previous value. This will cause the OP_Ne below to always - ** fail on the first iteration of the loop even if the first - ** row is all NULLs. - */ - sqlite3VdbeChangeToNoop(v, pDistinct->addrTnct); - pOp = sqlite3VdbeGetOp(v, pDistinct->addrTnct); - pOp->opcode = OP_Null; - pOp->p1 = 1; - pOp->p2 = regPrev; - pOp = 0; /* Ensure pOp is not used after sqlite3VdbeAddOp() */ - - iJump = sqlite3VdbeCurrentAddr(v) + nResultCol; - for(i=0; ipEList->a[i].pExpr); - if( idb->mallocFailed ); - sqlite3VdbeAddOp3(v, OP_Copy, regResult, regPrev, nResultCol-1); - break; - } - - case WHERE_DISTINCT_UNIQUE: { - sqlite3VdbeChangeToNoop(v, pDistinct->addrTnct); - break; - } - - default: { - assert( pDistinct->eTnctType==WHERE_DISTINCT_UNORDERED ); - codeDistinct(pParse, pDistinct->tabTnct, iContinue, nResultCol, - regResult); - break; - } - } + int eType = pDistinct->eTnctType; + int iTab = pDistinct->tabTnct; + assert( nResultCol==p->pEList->nExpr ); + iTab = codeDistinct(pParse, eType, iTab, iContinue, p->pEList, regResult); + fixDistinctOpenEph(pParse, eType, iTab, pDistinct->addrTnct); if( pSort==0 ){ codeOffset(v, p->iOffset, iContinue); } @@ -132459,7 +137078,7 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ p->nRef = 1; memset(&p[1], 0, nExtra); }else{ - sqlite3OomFault(db); + return (KeyInfo*)sqlite3OomFault(db); } return p; } @@ -132528,7 +137147,7 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoFromExprList( assert( sqlite3KeyInfoIsWriteable(pInfo) ); for(i=iStart, pItem=pList->a+iStart; iaColl[i-iStart] = sqlite3ExprNNCollSeq(pParse, pItem->pExpr); - pInfo->aSortFlags[i-iStart] = pItem->sortFlags; + pInfo->aSortFlags[i-iStart] = pItem->fg.sortFlags; } } return pInfo; @@ -132630,6 +137249,9 @@ static void generateSortTail( iTab = pSort->iECursor; if( eDest==SRT_Output || eDest==SRT_Coroutine || eDest==SRT_Mem ){ + if( eDest==SRT_Mem && p->iOffset ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, pDest->iSdst); + } regRowid = 0; regRow = pDest->iSdst; }else{ @@ -132653,7 +137275,7 @@ static void generateSortTail( if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce); addr = 1 + sqlite3VdbeAddOp2(v, OP_SorterSort, iTab, addrBreak); VdbeCoverage(v); - codeOffset(v, p->iOffset, addrContinue); + assert( p->iLimit==0 && p->iOffset==0 ); sqlite3VdbeAddOp3(v, OP_SorterData, iTab, regSortOut, iSortTab); bSeq = 0; }else{ @@ -132661,10 +137283,13 @@ static void generateSortTail( codeOffset(v, p->iOffset, addrContinue); iSortTab = iTab; bSeq = 1; + if( p->iOffset>0 ){ + sqlite3VdbeAddOp2(v, OP_AddImm, p->iLimit, -1); + } } for(i=0, iCol=nKey+bSeq-1; i=0; i--){ #ifdef SQLITE_ENABLE_SORTER_REFERENCES - if( aOutEx[i].bSorterRef ){ + if( aOutEx[i].fg.bSorterRef ){ sqlite3ExprCode(pParse, aOutEx[i].pExpr, regRow+i); }else #endif @@ -132872,13 +137497,19 @@ static const char *columnTypeImpl( break; } - assert( pTab && pExpr->y.pTab==pTab ); + assert( pTab && ExprUseYTab(pExpr) && pExpr->y.pTab==pTab ); if( pS ){ /* The "table" is actually a sub-select or a view in the FROM clause ** of the SELECT statement. Return the declaration type and origin ** data for the result-set column of the sub-select. */ - if( iCol>=0 && iColpEList->nExpr ){ + if( iColpEList->nExpr +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + && iCol>=0 +#else + && ALWAYS(iCol>=0) +#endif + ){ /* If iCol is less than zero, then the expression requests the ** rowid of the sub-select or view. This expression is legal (see ** test case misc2.2.2) - it always evaluates to NULL. @@ -132900,7 +137531,7 @@ static const char *columnTypeImpl( zType = "INTEGER"; zOrigCol = "rowid"; }else{ - zOrigCol = pTab->aCol[iCol].zName; + zOrigCol = pTab->aCol[iCol].zCnName; zType = sqlite3ColumnType(&pTab->aCol[iCol],0); } zOrigTab = pTab->zName; @@ -132926,9 +137557,11 @@ static const char *columnTypeImpl( ** statement. */ NameContext sNC; - Select *pS = pExpr->x.pSelect; - Expr *p = pS->pEList->a[0].pExpr; - assert( ExprHasProperty(pExpr, EP_xIsSelect) ); + Select *pS; + Expr *p; + assert( ExprUseXSelect(pExpr) ); + pS = pExpr->x.pSelect; + p = pS->pEList->a[0].pExpr; sNC.pSrcList = pS->pSrc; sNC.pNext = pNC; sNC.pParse = pNC->pParse; @@ -133020,7 +137653,7 @@ static void generateColumnTypes( ** then the result column name with the table name ** prefix, ex: TABLE.COLUMN. Otherwise use zSpan. */ -static void generateColumnNames( +SQLITE_PRIVATE void sqlite3GenerateColumnNames( Parse *pParse, /* Parser context */ Select *pSelect /* Generate column names for this SELECT statement */ ){ @@ -133057,8 +137690,9 @@ static void generateColumnNames( assert( p!=0 ); assert( p->op!=TK_AGG_COLUMN ); /* Agg processing has not run yet */ - assert( p->op!=TK_COLUMN || p->y.pTab!=0 ); /* Covering idx not yet coded */ - if( pEList->a[i].zEName && pEList->a[i].eEName==ENAME_NAME ){ + assert( p->op!=TK_COLUMN + || (ExprUseYTab(p) && p->y.pTab!=0) ); /* Covering idx not yet coded */ + if( pEList->a[i].zEName && pEList->a[i].fg.eEName==ENAME_NAME ){ /* An AS clause always takes first priority */ char *zName = pEList->a[i].zEName; sqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, SQLITE_TRANSIENT); @@ -133072,7 +137706,7 @@ static void generateColumnNames( if( iCol<0 ){ zCol = "rowid"; }else{ - zCol = pTab->aCol[iCol].zName; + zCol = pTab->aCol[iCol].zCnName; } if( fullName ){ char *zName = 0; @@ -133110,7 +137744,7 @@ static void generateColumnNames( ** and will break if those assumptions changes. Hence, use extreme caution ** when modifying this routine to avoid breaking legacy. ** -** See Also: generateColumnNames() +** See Also: sqlite3GenerateColumnNames() */ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( Parse *pParse, /* Parsing context */ @@ -133143,27 +137777,33 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( *paCol = aCol; for(i=0, pCol=aCol; imallocFailed; i++, pCol++){ + struct ExprList_item *pX = &pEList->a[i]; + struct ExprList_item *pCollide; /* Get an appropriate name for the column */ - if( (zName = pEList->a[i].zEName)!=0 && pEList->a[i].eEName==ENAME_NAME ){ + if( (zName = pX->zEName)!=0 && pX->fg.eEName==ENAME_NAME ){ /* If the column contains an "AS " phrase, use as the name */ }else{ - Expr *pColExpr = sqlite3ExprSkipCollateAndLikely(pEList->a[i].pExpr); + Expr *pColExpr = sqlite3ExprSkipCollateAndLikely(pX->pExpr); while( ALWAYS(pColExpr!=0) && pColExpr->op==TK_DOT ){ pColExpr = pColExpr->pRight; assert( pColExpr!=0 ); } - if( pColExpr->op==TK_COLUMN && (pTab = pColExpr->y.pTab)!=0 ){ + if( pColExpr->op==TK_COLUMN + && ALWAYS( ExprUseYTab(pColExpr) ) + && ALWAYS( pColExpr->y.pTab!=0 ) + ){ /* For columns use the column name name */ int iCol = pColExpr->iColumn; + pTab = pColExpr->y.pTab; if( iCol<0 ) iCol = pTab->iPKey; - zName = iCol>=0 ? pTab->aCol[iCol].zName : "rowid"; + zName = iCol>=0 ? pTab->aCol[iCol].zCnName : "rowid"; }else if( pColExpr->op==TK_ID ){ assert( !ExprHasProperty(pColExpr, EP_IntValue) ); zName = pColExpr->u.zToken; }else{ /* Use the original text of the column expression as its name */ - zName = pEList->a[i].zEName; + assert( zName==pX->zEName ); /* pointer comparison intended */ } } if( zName && !sqlite3IsTrueOrFalse(zName) ){ @@ -133176,7 +137816,10 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( ** append an integer to the name so that it becomes unique. */ cnt = 0; - while( zName && sqlite3HashFind(&ht, zName)!=0 ){ + while( zName && (pCollide = sqlite3HashFind(&ht, zName))!=0 ){ + if( pCollide->fg.bUsingTerm ){ + pCol->colFlags |= COLFLAG_NOEXPAND; + } nName = sqlite3Strlen30(zName); if( nName>0 ){ for(j=nName-1; j>0 && sqlite3Isdigit(zName[j]); j--){} @@ -133185,17 +137828,20 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( zName = sqlite3MPrintf(db, "%.*z:%u", nName, zName, ++cnt); if( cnt>3 ) sqlite3_randomness(sizeof(cnt), &cnt); } - pCol->zName = zName; + pCol->zCnName = zName; pCol->hName = sqlite3StrIHash(zName); + if( pX->fg.bNoExpand ){ + pCol->colFlags |= COLFLAG_NOEXPAND; + } sqlite3ColumnPropertiesFromName(0, pCol); - if( zName && sqlite3HashInsert(&ht, zName, pCol)==pCol ){ + if( zName && sqlite3HashInsert(&ht, zName, pX)==pX ){ sqlite3OomFault(db); } } sqlite3HashClear(&ht); if( db->mallocFailed ){ for(j=0; jpEList->a; for(i=0, pCol=pTab->aCol; inCol; i++, pCol++){ const char *zType; - int n, m; + i64 n, m; pTab->tabFlags |= (pCol->colFlags & COLFLAG_NOINSERT); p = a[i].pExpr; zType = columnType(&sNC, p, 0, 0, 0); @@ -133247,17 +137893,21 @@ SQLITE_PRIVATE void sqlite3SelectAddColumnTypeAndCollation( pCol->affinity = sqlite3ExprAffinity(p); if( zType ){ m = sqlite3Strlen30(zType); - n = sqlite3Strlen30(pCol->zName); - pCol->zName = sqlite3DbReallocOrFree(db, pCol->zName, n+m+2); - if( pCol->zName ){ - memcpy(&pCol->zName[n+1], zType, m+1); + n = sqlite3Strlen30(pCol->zCnName); + pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+m+2); + if( pCol->zCnName ){ + memcpy(&pCol->zCnName[n+1], zType, m+1); pCol->colFlags |= COLFLAG_HASTYPE; + }else{ + testcase( pCol->colFlags & COLFLAG_HASTYPE ); + pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL); } } if( pCol->affinity<=SQLITE_AFF_NONE ) pCol->affinity = aff; pColl = sqlite3ExprCollSeq(pParse, p); - if( pColl && pCol->zColl==0 ){ - pCol->zColl = sqlite3DbStrDup(db, pColl->zName); + if( pColl ){ + assert( pTab->pIndex==0 ); + sqlite3ColumnSetColl(db, pCol, pColl->zName); } } pTab->szTabRow = 1; /* Any non-zero value works */ @@ -133421,7 +138071,7 @@ static CollSeq *multiSelectCollSeq(Parse *pParse, Select *p, int iCol){ */ static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){ ExprList *pOrderBy = p->pOrderBy; - int nOrderBy = p->pOrderBy->nExpr; + int nOrderBy = ALWAYS(pOrderBy!=0) ? pOrderBy->nExpr : 0; sqlite3 *db = pParse->db; KeyInfo *pRet = sqlite3KeyInfoAlloc(db, nOrderBy+nExtra, 1); if( pRet ){ @@ -133441,7 +138091,7 @@ static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){ } assert( sqlite3KeyInfoIsWriteable(pRet) ); pRet->aColl[i] = pColl; - pRet->aSortFlags[i] = pOrderBy->a[i].sortFlags; + pRet->aSortFlags[i] = pOrderBy->a[i].fg.sortFlags; } } @@ -133493,7 +138143,7 @@ static void generateWithRecursiveQuery( SrcList *pSrc = p->pSrc; /* The FROM clause of the recursive query */ int nCol = p->pEList->nExpr; /* Number of columns in the recursive table */ Vdbe *v = pParse->pVdbe; /* The prepared statement under construction */ - Select *pSetup = p->pPrior; /* The setup query */ + Select *pSetup; /* The setup query */ Select *pFirstRec; /* Left-most recursive term */ int addrTop; /* Top of the loop */ int addrCont, addrBreak; /* CONTINUE and BREAK addresses */ @@ -133577,7 +138227,6 @@ static void generateWithRecursiveQuery( ** iDistinct table. pFirstRec is left pointing to the left-most ** recursive term of the CTE. */ - pFirstRec = p; for(pFirstRec=p; ALWAYS(pFirstRec!=0); pFirstRec=pFirstRec->pPrior){ if( pFirstRec->selFlags & SF_Aggregate ){ sqlite3ErrorMsg(pParse, "recursive aggregate queries not supported"); @@ -133660,7 +138309,7 @@ static int multiSelectOrderBy( ** The "LIMIT of exactly 1" case of condition (1) comes about when a VALUES ** clause occurs within scalar expression (ex: "SELECT (VALUES(1),(2),(3))"). ** The sqlite3CodeSubselect will have added the LIMIT 1 clause in tht case. -** Since the limit is exactly 1, we only need to evalutes the left-most VALUES. +** Since the limit is exactly 1, we only need to evaluate the left-most VALUES. */ static int multiSelectValues( Parse *pParse, /* Parsing context */ @@ -133808,11 +138457,12 @@ static int multiSelect( switch( p->op ){ case TK_ALL: { int addr = 0; - int nLimit; + int nLimit = 0; /* Initialize to suppress harmless compiler warning */ assert( !pPrior->pLimit ); pPrior->iLimit = p->iLimit; pPrior->iOffset = p->iOffset; pPrior->pLimit = p->pLimit; + SELECTTRACE(1, pParse, p, ("multiSelect UNION ALL left...\n")); rc = sqlite3Select(pParse, pPrior, &dest); pPrior->pLimit = 0; if( rc ){ @@ -133830,6 +138480,7 @@ static int multiSelect( } } ExplainQueryPlan((pParse, 1, "UNION ALL")); + SELECTTRACE(1, pParse, p, ("multiSelect UNION ALL right...\n")); rc = sqlite3Select(pParse, p, &dest); testcase( rc!=SQLITE_OK ); pDelete = p->pPrior; @@ -133882,6 +138533,7 @@ static int multiSelect( */ assert( !pPrior->pOrderBy ); sqlite3SelectDestInit(&uniondest, priorOp, unionTab); + SELECTTRACE(1, pParse, p, ("multiSelect EXCEPT/UNION left...\n")); rc = sqlite3Select(pParse, pPrior, &uniondest); if( rc ){ goto multi_select_end; @@ -133901,6 +138553,7 @@ static int multiSelect( uniondest.eDest = op; ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", sqlite3SelectOpName(p->op))); + SELECTTRACE(1, pParse, p, ("multiSelect EXCEPT/UNION right...\n")); rc = sqlite3Select(pParse, p, &uniondest); testcase( rc!=SQLITE_OK ); assert( p->pOrderBy==0 ); @@ -133961,6 +138614,7 @@ static int multiSelect( /* Code the SELECTs to our left into temporary table "tab1". */ sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1); + SELECTTRACE(1, pParse, p, ("multiSelect INTERSECT left...\n")); rc = sqlite3Select(pParse, pPrior, &intersectdest); if( rc ){ goto multi_select_end; @@ -133977,6 +138631,7 @@ static int multiSelect( intersectdest.iSDParm = tab2; ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", sqlite3SelectOpName(p->op))); + SELECTTRACE(1, pParse, p, ("multiSelect INTERSECT right...\n")); rc = sqlite3Select(pParse, p, &intersectdest); testcase( rc!=SQLITE_OK ); pDelete = p->pPrior; @@ -134037,6 +138692,7 @@ static int multiSelect( int nCol; /* Number of columns in result set */ assert( p->pNext==0 ); + assert( p->pEList!=0 ); nCol = p->pEList->nExpr; pKeyInfo = sqlite3KeyInfoAlloc(db, nCol, 1); if( !pKeyInfo ){ @@ -134071,7 +138727,11 @@ static int multiSelect( multi_select_end: pDest->iSdst = dest.iSdst; pDest->nSdst = dest.nSdst; - sqlite3SelectDelete(db, pDelete); + if( pDelete ){ + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3SelectDelete, + pDelete); + } return rc; } #endif /* SQLITE_OMIT_COMPOUND_SELECT */ @@ -134325,6 +138985,8 @@ static int multiSelectOrderBy( ){ int i, j; /* Loop counters */ Select *pPrior; /* Another SELECT immediately to our left */ + Select *pSplit; /* Left-most SELECT in the right-hand group */ + int nSelect; /* Number of SELECT statements in the compound */ Vdbe *v; /* Generate code to this VDBE */ SelectDest destA; /* Destination for coroutine A */ SelectDest destB; /* Destination for coroutine B */ @@ -134370,8 +139032,7 @@ static int multiSelectOrderBy( /* Patch up the ORDER BY clause */ op = p->op; - pPrior = p->pPrior; - assert( pPrior->pOrderBy==0 ); + assert( p->pPrior->pOrderBy==0 ); pOrderBy = p->pOrderBy; assert( pOrderBy ); nOrderBy = pOrderBy->nExpr; @@ -134384,6 +139045,7 @@ static int multiSelectOrderBy( for(i=1; db->mallocFailed==0 && i<=p->pEList->nExpr; i++){ struct ExprList_item *pItem; for(j=0, pItem=pOrderBy->a; ju.x.iOrderByCol>0 ); if( pItem->u.x.iOrderByCol==i ) break; } @@ -134410,6 +139072,7 @@ static int multiSelectOrderBy( struct ExprList_item *pItem; aPermute[0] = nOrderBy; for(i=1, pItem=pOrderBy->a; i<=nOrderBy; i++, pItem++){ + assert( pItem!=0 ); assert( pItem->u.x.iOrderByCol>0 ); assert( pItem->u.x.iOrderByCol<=p->pEList->nExpr ); aPermute[i] = pItem->u.x.iOrderByCol - 1; @@ -134419,11 +139082,6 @@ static int multiSelectOrderBy( pKeyMerge = 0; } - /* Reattach the ORDER BY clause to the query. - */ - p->pOrderBy = pOrderBy; - pPrior->pOrderBy = sqlite3ExprListDup(pParse->db, pOrderBy, 0); - /* Allocate a range of temporary registers and the KeyInfo needed ** for the logic that removes duplicate result rows when the ** operator is UNION, EXCEPT, or INTERSECT (but not UNION ALL). @@ -134448,12 +139106,30 @@ static int multiSelectOrderBy( /* Separate the left and the right query from one another */ - p->pPrior = 0; - pPrior->pNext = 0; - sqlite3ResolveOrderGroupBy(pParse, p, p->pOrderBy, "ORDER"); - if( pPrior->pPrior==0 ){ - sqlite3ResolveOrderGroupBy(pParse, pPrior, pPrior->pOrderBy, "ORDER"); + nSelect = 1; + if( (op==TK_ALL || op==TK_UNION) + && OptimizationEnabled(db, SQLITE_BalancedMerge) + ){ + for(pSplit=p; pSplit->pPrior!=0 && pSplit->op==op; pSplit=pSplit->pPrior){ + nSelect++; + assert( pSplit->pPrior->pNext==pSplit ); + } } + if( nSelect<=3 ){ + pSplit = p; + }else{ + pSplit = p; + for(i=2; ipPrior; } + } + pPrior = pSplit->pPrior; + assert( pPrior!=0 ); + pSplit->pPrior = 0; + pPrior->pNext = 0; + assert( p->pOrderBy == pOrderBy ); + assert( pOrderBy!=0 || db->mallocFailed ); + pPrior->pOrderBy = sqlite3ExprListDup(pParse->db, pOrderBy, 0); + sqlite3ResolveOrderGroupBy(pParse, p, p->pOrderBy, "ORDER"); + sqlite3ResolveOrderGroupBy(pParse, pPrior, pPrior->pOrderBy, "ORDER"); /* Compute the limit registers */ computeLimitRegisters(pParse, p, labelEnd); @@ -134602,13 +139278,16 @@ static int multiSelectOrderBy( */ sqlite3VdbeResolveLabel(v, labelEnd); - /* Reassembly the compound query so that it will be freed correctly + /* Reassemble the compound query so that it will be freed correctly ** by the calling function */ - if( p->pPrior ){ - sqlite3SelectDelete(db, p->pPrior); + if( pSplit->pPrior ){ + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3SelectDelete, pSplit->pPrior); } - p->pPrior = pPrior; - pPrior->pNext = p; + pSplit->pPrior = pPrior; + pPrior->pNext = pSplit; + sqlite3ExprListDelete(db, pPrior->pOrderBy); + pPrior->pOrderBy = 0; /*** TBD: Insert subroutine calls to close cursors on incomplete **** subqueries ****/ @@ -134624,12 +139303,40 @@ static int multiSelectOrderBy( ** ** All references to columns in table iTable are to be replaced by corresponding ** expressions in pEList. +** +** ## About "isOuterJoin": +** +** The isOuterJoin column indicates that the replacement will occur into a +** position in the parent that NULL-able due to an OUTER JOIN. Either the +** target slot in the parent is the right operand of a LEFT JOIN, or one of +** the left operands of a RIGHT JOIN. In either case, we need to potentially +** bypass the substituted expression with OP_IfNullRow. +** +** Suppose the original expression integer constant. Even though the table +** has the nullRow flag set, because the expression is an integer constant, +** it will not be NULLed out. So instead, we insert an OP_IfNullRow opcode +** that checks to see if the nullRow flag is set on the table. If the nullRow +** flag is set, then the value in the register is set to NULL and the original +** expression is bypassed. If the nullRow flag is not set, then the original +** expression runs to populate the register. +** +** Example where this is needed: +** +** CREATE TABLE t1(a INTEGER PRIMARY KEY, b INT); +** CREATE TABLE t2(x INT UNIQUE); +** +** SELECT a,b,m,x FROM t1 LEFT JOIN (SELECT 59 AS m,x FROM t2) ON b=x; +** +** When the subquery on the right side of the LEFT JOIN is flattened, we +** have to add OP_IfNullRow in front of the OP_Integer that implements the +** "m" value of the subquery so that a NULL will be loaded instead of 59 +** when processing a non-matched row of the left. */ typedef struct SubstContext { Parse *pParse; /* The parsing context */ int iTable; /* Replace references to this table */ int iNewTable; /* New table number */ - int isLeftJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */ + int isOuterJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */ ExprList *pEList; /* Replacement expressions */ } SubstContext; @@ -134655,18 +139362,22 @@ static Expr *substExpr( Expr *pExpr /* Expr in which substitution occurs */ ){ if( pExpr==0 ) return 0; - if( ExprHasProperty(pExpr, EP_FromJoin) - && pExpr->iRightJoinTable==pSubst->iTable + if( ExprHasProperty(pExpr, EP_OuterON|EP_InnerON) + && pExpr->w.iJoin==pSubst->iTable ){ - pExpr->iRightJoinTable = pSubst->iNewTable; + testcase( ExprHasProperty(pExpr, EP_InnerON) ); + pExpr->w.iJoin = pSubst->iNewTable; } if( pExpr->op==TK_COLUMN && pExpr->iTable==pSubst->iTable && !ExprHasProperty(pExpr, EP_FixedCol) ){ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW if( pExpr->iColumn<0 ){ pExpr->op = TK_NULL; - }else{ + }else +#endif + { Expr *pNew; Expr *pCopy = pSubst->pEList->a[pExpr->iColumn].pExpr; Expr ifNullRow; @@ -134676,7 +139387,7 @@ static Expr *substExpr( sqlite3VectorErrorMsg(pSubst->pParse, pCopy); }else{ sqlite3 *db = pSubst->pParse->db; - if( pSubst->isLeftJoin && pCopy->op!=TK_COLUMN ){ + if( pSubst->isOuterJoin && pCopy->op!=TK_COLUMN ){ memset(&ifNullRow, 0, sizeof(ifNullRow)); ifNullRow.op = TK_IF_NULL_ROW; ifNullRow.pLeft = pCopy; @@ -134686,26 +139397,34 @@ static Expr *substExpr( } testcase( ExprHasProperty(pCopy, EP_Subquery) ); pNew = sqlite3ExprDup(db, pCopy, 0); - if( pNew && pSubst->isLeftJoin ){ + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pNew); + return pExpr; + } + if( pSubst->isOuterJoin ){ ExprSetProperty(pNew, EP_CanBeNull); } - if( pNew && ExprHasProperty(pExpr,EP_FromJoin) ){ - sqlite3SetJoinExpr(pNew, pExpr->iRightJoinTable); + if( ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) ){ + sqlite3SetJoinExpr(pNew, pExpr->w.iJoin, + pExpr->flags & (EP_OuterON|EP_InnerON)); } sqlite3ExprDelete(db, pExpr); pExpr = pNew; + if( pExpr->op==TK_TRUEFALSE ){ + pExpr->u.iValue = sqlite3ExprTruthValue(pExpr); + pExpr->op = TK_INTEGER; + ExprSetProperty(pExpr, EP_IntValue); + } /* Ensure that the expression now has an implicit collation sequence, ** just as it did when it was a column of a view or sub-query. */ - if( pExpr ){ - if( pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE ){ - CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse, pExpr); - pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr, - (pColl ? pColl->zName : "BINARY") - ); - } - ExprClearProperty(pExpr, EP_Collate); + if( pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE ){ + CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse, pExpr); + pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr, + (pColl ? pColl->zName : "BINARY") + ); } + ExprClearProperty(pExpr, EP_Collate); } } }else{ @@ -134714,7 +139433,7 @@ static Expr *substExpr( } pExpr->pLeft = substExpr(pSubst, pExpr->pLeft); pExpr->pRight = substExpr(pSubst, pExpr->pRight); - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ substSelect(pSubst, pExpr->x.pSelect, 1); }else{ substExprList(pSubst, pExpr->x.pList); @@ -134805,10 +139524,10 @@ static void recomputeColumnsUsed( ** new cursor number assigned, set an entry in the aCsrMap[] array ** to map the old cursor number to the new: ** -** aCsrMap[iOld] = iNew; +** aCsrMap[iOld+1] = iNew; ** ** The array is guaranteed by the caller to be large enough for all -** existing cursor numbers in pSrc. +** existing cursor numbers in pSrc. aCsrMap[0] is the array size. ** ** If pSrc contains any sub-selects, call this routine recursively ** on the FROM clause of each such sub-select, with iExcept set to -1. @@ -134824,7 +139543,11 @@ static void srclistRenumberCursors( for(i=0, pItem=pSrc->a; inSrc; i++, pItem++){ if( i!=iExcept ){ Select *p; - pItem->iCursor = aCsrMap[pItem->iCursor] = pParse->nTab++; + assert( pItem->iCursor < aCsrMap[0] ); + if( !pItem->fg.isRecursive || aCsrMap[pItem->iCursor+1]==0 ){ + aCsrMap[pItem->iCursor+1] = pParse->nTab++; + } + pItem->iCursor = aCsrMap[pItem->iCursor+1]; for(p=pItem->pSelect; p; p=p->pPrior){ srclistRenumberCursors(pParse, aCsrMap, p->pSrc, -1); } @@ -134832,18 +139555,28 @@ static void srclistRenumberCursors( } } +/* +** *piCursor is a cursor number. Change it if it needs to be mapped. +*/ +static void renumberCursorDoMapping(Walker *pWalker, int *piCursor){ + int *aCsrMap = pWalker->u.aiCol; + int iCsr = *piCursor; + if( iCsr < aCsrMap[0] && aCsrMap[iCsr+1]>0 ){ + *piCursor = aCsrMap[iCsr+1]; + } +} + /* ** Expression walker callback used by renumberCursors() to update ** Expr objects to match newly assigned cursor numbers. */ static int renumberCursorsCb(Walker *pWalker, Expr *pExpr){ - int *aCsrMap = pWalker->u.aiCol; int op = pExpr->op; - if( (op==TK_COLUMN || op==TK_IF_NULL_ROW) && aCsrMap[pExpr->iTable] ){ - pExpr->iTable = aCsrMap[pExpr->iTable]; + if( op==TK_COLUMN || op==TK_IF_NULL_ROW ){ + renumberCursorDoMapping(pWalker, &pExpr->iTable); } - if( ExprHasProperty(pExpr, EP_FromJoin) && aCsrMap[pExpr->iRightJoinTable] ){ - pExpr->iRightJoinTable = aCsrMap[pExpr->iRightJoinTable]; + if( ExprHasProperty(pExpr, EP_OuterON) ){ + renumberCursorDoMapping(pWalker, &pExpr->w.iJoin); } return WRC_Continue; } @@ -134928,6 +139661,7 @@ static void renumberCursors( ** table and ** (3c) the outer query may not be an aggregate. ** (3d) the outer query may not be DISTINCT. +** See also (26) for restrictions on RIGHT JOIN. ** ** (4) The subquery can not be DISTINCT. ** @@ -134979,6 +139713,9 @@ static void renumberCursors( ** (17d2) DISTINCT ** (17e) the subquery may not contain window functions, and ** (17f) the subquery must not be the RHS of a LEFT JOIN. +** (17g) either the subquery is the first element of the outer +** query or there are no RIGHT or FULL JOINs in any arm +** of the subquery. (This is a duplicate of condition (27b).) ** ** The parent and sub-query may contain WHERE clauses. Subject to ** rules (11), (13) and (14), they may also contain ORDER BY, @@ -135026,6 +139763,23 @@ static void renumberCursors( ** function in the select list or ORDER BY clause, flattening ** is not attempted. ** +** (26) The subquery may not be the right operand of a RIGHT JOIN. +** See also (3) for restrictions on LEFT JOIN. +** +** (27) The subquery may not contain a FULL or RIGHT JOIN unless it +** is the first element of the parent query. This must be the +** the case if: +** (27a) the subquery is not compound query, and +** (27b) the subquery is a compound query and the RIGHT JOIN occurs +** in any arm of the compound query. (See also (17g).) +** +** (28) The subquery is not a MATERIALIZED CTE. +** +** (29) Either the subquery is not the right-hand operand of a join with an +** ON or USING clause nor the right-hand operand of a NATURAL JOIN, or +** the right-most table within the FROM clause of the subquery +** is not part of an outer join. +** ** ** In this routine, the "p" parameter is a pointer to the outer query. ** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query @@ -135051,7 +139805,7 @@ static int flattenSubquery( SrcList *pSubSrc; /* The FROM clause of the subquery */ int iParent; /* VDBE cursor number of the pSub result set temp table */ int iNewParent = -1;/* Replacement table for iParent */ - int isLeftJoin = 0; /* True if pSub is the right side of a LEFT JOIN */ + int isOuterJoin = 0; /* True if pSub is the right side of a LEFT JOIN */ int i; /* Loop counter */ Expr *pWhere; /* The WHERE clause */ SrcItem *pSubitem; /* The subquery */ @@ -135124,25 +139878,63 @@ static int flattenSubquery( ** ** See also tickets #306, #350, and #3300. */ - if( (pSubitem->fg.jointype & JT_OUTER)!=0 ){ - isLeftJoin = 1; - if( pSubSrc->nSrc>1 /* (3a) */ - || isAgg /* (3b) */ - || IsVirtual(pSubSrc->a[0].pTab) /* (3c) */ - || (p->selFlags & SF_Distinct)!=0 /* (3d) */ + if( (pSubitem->fg.jointype & (JT_OUTER|JT_LTORJ))!=0 ){ + if( pSubSrc->nSrc>1 /* (3a) */ + || isAgg /* (3c) */ + || IsVirtual(pSubSrc->a[0].pTab) /* (3b) */ + || (p->selFlags & SF_Distinct)!=0 /* (3d) */ + || (pSubitem->fg.jointype & JT_RIGHT)!=0 /* (26) */ + ){ + return 0; + } + isOuterJoin = 1; + } +#ifdef SQLITE_EXTRA_IFNULLROW + else if( iFrom>0 && !isAgg ){ + /* Setting isOuterJoin to -1 causes OP_IfNullRow opcodes to be generated for + ** every reference to any result column from subquery in a join, even + ** though they are not necessary. This will stress-test the OP_IfNullRow + ** opcode. */ + isOuterJoin = -1; + } +#endif + + assert( pSubSrc->nSrc>0 ); /* True by restriction (7) */ + if( iFrom>0 && (pSubSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ + return 0; /* Restriction (27a) */ + } + if( pSubitem->fg.isCte && pSubitem->u2.pCteUse->eM10d==M10d_Yes ){ + return 0; /* (28) */ + } + + /* Restriction (29): + ** + ** We do not want two constraints on the same term of the flattened + ** query where one constraint has EP_InnerON and the other is EP_OuterON. + ** To prevent this, one or the other of the following conditions must be + ** false: + ** + ** (29a) The right-most entry in the FROM clause of the subquery + ** must not be part of an outer join. + ** + ** (29b) The subquery itself must not be the right operand of a + ** NATURAL join or a join that as an ON or USING clause. + ** + ** These conditions are sufficient to keep an EP_OuterON from being + ** flattened into an EP_InnerON. Restrictions (3a) and (27a) prevent + ** an EP_InnerON from being flattened into an EP_OuterON. + */ + if( pSubSrc->nSrc>=2 + && (pSubSrc->a[pSubSrc->nSrc-1].fg.jointype & JT_OUTER)!=0 + ){ + if( (pSubitem->fg.jointype & JT_NATURAL)!=0 + || pSubitem->fg.isUsing + || NEVER(pSubitem->u3.pOn!=0) /* ON clause already shifted into WHERE */ + || pSubitem->fg.isOn ){ return 0; } } -#ifdef SQLITE_EXTRA_IFNULLROW - else if( iFrom>0 && !isAgg ){ - /* Setting isLeftJoin to -1 causes OP_IfNullRow opcodes to be generated for - ** every reference to any result column from subquery in a join, even - ** though they are not necessary. This will stress-test the OP_IfNullRow - ** opcode. */ - isLeftJoin = -1; - } -#endif /* Restriction (17): If the sub-query is a compound SELECT, then it must ** use only the UNION ALL operator. And none of the simple select queries @@ -135153,7 +139945,7 @@ static int flattenSubquery( if( pSub->pOrderBy ){ return 0; /* Restriction (20) */ } - if( isAgg || (p->selFlags & SF_Distinct)!=0 || isLeftJoin>0 ){ + if( isAgg || (p->selFlags & SF_Distinct)!=0 || isOuterJoin>0 ){ return 0; /* (17d1), (17d2), or (17f) */ } for(pSub1=pSub; pSub1; pSub1=pSub1->pPrior){ @@ -135171,6 +139963,12 @@ static int flattenSubquery( ){ return 0; } + if( iFrom>0 && (pSub1->pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ + /* Without this restriction, the JT_LTORJ flag would end up being + ** omitted on left-hand tables of the right join that is being + ** flattened. */ + return 0; /* Restrictions (17g), (27b) */ + } testcase( pSub1->pSrc->nSrc>1 ); } @@ -135187,7 +139985,9 @@ static int flattenSubquery( if( pSrc->nSrc>1 ){ if( pParse->nSelect>500 ) return 0; - aCsrMap = sqlite3DbMallocZero(db, pParse->nTab*sizeof(int)); + if( OptimizationDisabled(db, SQLITE_FlttnUnionAll) ) return 0; + aCsrMap = sqlite3DbMallocZero(db, ((i64)pParse->nTab+1)*sizeof(int)); + if( aCsrMap ) aCsrMap[0] = pParse->nTab; } } @@ -135210,7 +140010,7 @@ static int flattenSubquery( pSubitem->zName = 0; pSubitem->zAlias = 0; pSubitem->pSelect = 0; - assert( pSubitem->pOn==0 ); + assert( pSubitem->fg.isUsing!=0 || pSubitem->u3.pOn==0 ); /* If the sub-query is a compound SELECT statement, then (by restrictions ** 17 and 18 above) it must be a UNION ALL and the parent query must @@ -135264,7 +140064,7 @@ static int flattenSubquery( p->pPrior = pPrior; }else{ pNew->selId = ++pParse->nSelect; - if( aCsrMap && db->mallocFailed==0 ){ + if( aCsrMap && ALWAYS(db->mallocFailed==0) ){ renumberCursors(pParse, pNew, iFrom, aCsrMap); } pNew->pPrior = pPrior; @@ -135320,6 +140120,7 @@ static int flattenSubquery( for(pParent=p; pParent; pParent=pParent->pPrior, pSub=pSub->pPrior){ int nSubSrc; u8 jointype = 0; + u8 ltorj = pSrc->a[iFrom].fg.jointype & JT_LTORJ; assert( pSub!=0 ); pSubSrc = pSub->pSrc; /* FROM clause of subquery */ nSubSrc = pSubSrc->nSrc; /* Number of terms in subquery FROM clause */ @@ -135354,13 +140155,16 @@ static int flattenSubquery( ** outer query. */ for(i=0; ia[i+iFrom].pUsing); - assert( pSrc->a[i+iFrom].fg.isTabFunc==0 ); - pSrc->a[i+iFrom] = pSubSrc->a[i]; + SrcItem *pItem = &pSrc->a[i+iFrom]; + if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing); + assert( pItem->fg.isTabFunc==0 ); + *pItem = pSubSrc->a[i]; + pItem->fg.jointype |= ltorj; iNewParent = pSubSrc->a[i].iCursor; memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); } - pSrc->a[iFrom].fg.jointype = jointype; + pSrc->a[iFrom].fg.jointype &= JT_LTORJ; + pSrc->a[iFrom].fg.jointype |= jointype | ltorj; /* Now begin substituting subquery result set expressions for ** references to the iParent in the outer query. @@ -135395,8 +140199,8 @@ static int flattenSubquery( } pWhere = pSub->pWhere; pSub->pWhere = 0; - if( isLeftJoin>0 ){ - sqlite3SetJoinExpr(pWhere, iNewParent); + if( isOuterJoin>0 ){ + sqlite3SetJoinExpr(pWhere, iNewParent, EP_OuterON); } if( pWhere ){ if( pParent->pWhere ){ @@ -135410,7 +140214,7 @@ static int flattenSubquery( x.pParse = pParse; x.iTable = iParent; x.iNewTable = iNewParent; - x.isLeftJoin = isLeftJoin; + x.isOuterJoin = isOuterJoin; x.pEList = pSub->pEList; substSelect(&x, pParent, 0); } @@ -135445,8 +140249,8 @@ static int flattenSubquery( sqlite3WalkSelect(&w,pSub1); sqlite3SelectDelete(db, pSub1); -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x100 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x100 ){ SELECTTRACE(0x100,pParse,p,("After flattening:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -135463,8 +140267,12 @@ static int flattenSubquery( typedef struct WhereConst WhereConst; struct WhereConst { Parse *pParse; /* Parsing context */ + u8 *pOomFault; /* Pointer to pParse->db->mallocFailed */ int nConst; /* Number for COLUMN=CONSTANT terms */ int nChng; /* Number of times a constant is propagated */ + int bHasAffBlob; /* At least one column in apExpr[] as affinity BLOB */ + u32 mExcludeOn; /* Which ON expressions to exclude from considertion. + ** Either EP_OuterON or EP_InnerON|EP_OuterON */ Expr **apExpr; /* [i*2] is COLUMN and [i*2+1] is VALUE */ }; @@ -135503,6 +140311,9 @@ static void constInsert( return; /* Already present. Return without doing anything. */ } } + if( sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){ + pConst->bHasAffBlob = 1; + } pConst->nConst++; pConst->apExpr = sqlite3DbReallocOrFree(pConst->pParse->db, pConst->apExpr, @@ -135523,8 +140334,12 @@ static void constInsert( */ static void findConstInWhere(WhereConst *pConst, Expr *pExpr){ Expr *pRight, *pLeft; - if( pExpr==0 ) return; - if( ExprHasProperty(pExpr, EP_FromJoin) ) return; + if( NEVER(pExpr==0) ) return; + if( ExprHasProperty(pExpr, pConst->mExcludeOn) ){ + testcase( ExprHasProperty(pExpr, EP_OuterON) ); + testcase( ExprHasProperty(pExpr, EP_InnerON) ); + return; + } if( pExpr->op==TK_AND ){ findConstInWhere(pConst, pExpr->pRight); findConstInWhere(pConst, pExpr->pLeft); @@ -135544,37 +140359,84 @@ static void findConstInWhere(WhereConst *pConst, Expr *pExpr){ } /* -** This is a Walker expression callback. pExpr is a candidate expression -** to be replaced by a value. If pExpr is equivalent to one of the -** columns named in pWalker->u.pConst, then overwrite it with its -** corresponding value. +** This is a helper function for Walker callback propagateConstantExprRewrite(). +** +** Argument pExpr is a candidate expression to be replaced by a value. If +** pExpr is equivalent to one of the columns named in pWalker->u.pConst, +** then overwrite it with the corresponding value. Except, do not do so +** if argument bIgnoreAffBlob is non-zero and the affinity of pExpr +** is SQLITE_AFF_BLOB. */ -static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){ +static int propagateConstantExprRewriteOne( + WhereConst *pConst, + Expr *pExpr, + int bIgnoreAffBlob +){ int i; - WhereConst *pConst; + if( pConst->pOomFault[0] ) return WRC_Prune; if( pExpr->op!=TK_COLUMN ) return WRC_Continue; - if( ExprHasProperty(pExpr, EP_FixedCol|EP_FromJoin) ){ + if( ExprHasProperty(pExpr, EP_FixedCol|pConst->mExcludeOn) ){ testcase( ExprHasProperty(pExpr, EP_FixedCol) ); - testcase( ExprHasProperty(pExpr, EP_FromJoin) ); + testcase( ExprHasProperty(pExpr, EP_OuterON) ); + testcase( ExprHasProperty(pExpr, EP_InnerON) ); return WRC_Continue; } - pConst = pWalker->u.pConst; for(i=0; inConst; i++){ Expr *pColumn = pConst->apExpr[i*2]; if( pColumn==pExpr ) continue; if( pColumn->iTable!=pExpr->iTable ) continue; if( pColumn->iColumn!=pExpr->iColumn ) continue; + if( bIgnoreAffBlob && sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){ + break; + } /* A match is found. Add the EP_FixedCol property */ pConst->nChng++; ExprClearProperty(pExpr, EP_Leaf); ExprSetProperty(pExpr, EP_FixedCol); assert( pExpr->pLeft==0 ); pExpr->pLeft = sqlite3ExprDup(pConst->pParse->db, pConst->apExpr[i*2+1], 0); + if( pConst->pParse->db->mallocFailed ) return WRC_Prune; break; } return WRC_Prune; } +/* +** This is a Walker expression callback. pExpr is a node from the WHERE +** clause of a SELECT statement. This function examines pExpr to see if +** any substitutions based on the contents of pWalker->u.pConst should +** be made to pExpr or its immediate children. +** +** A substitution is made if: +** +** + pExpr is a column with an affinity other than BLOB that matches +** one of the columns in pWalker->u.pConst, or +** +** + pExpr is a binary comparison operator (=, <=, >=, <, >) that +** uses an affinity other than TEXT and one of its immediate +** children is a column that matches one of the columns in +** pWalker->u.pConst. +*/ +static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){ + WhereConst *pConst = pWalker->u.pConst; + assert( TK_GT==TK_EQ+1 ); + assert( TK_LE==TK_EQ+2 ); + assert( TK_LT==TK_EQ+3 ); + assert( TK_GE==TK_EQ+4 ); + if( pConst->bHasAffBlob ){ + if( (pExpr->op>=TK_EQ && pExpr->op<=TK_GE) + || pExpr->op==TK_IS + ){ + propagateConstantExprRewriteOne(pConst, pExpr->pLeft, 0); + if( pConst->pOomFault[0] ) return WRC_Prune; + if( sqlite3ExprAffinity(pExpr->pLeft)!=SQLITE_AFF_TEXT ){ + propagateConstantExprRewriteOne(pConst, pExpr->pRight, 0); + } + } + } + return propagateConstantExprRewriteOne(pConst, pExpr, pConst->bHasAffBlob); +} + /* ** The WHERE-clause constant propagation optimization. ** @@ -135610,6 +140472,21 @@ static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){ ** routines know to generate the constant "123" instead of looking up the ** column value. Also, to avoid collation problems, this optimization is ** only attempted if the "a=123" term uses the default BINARY collation. +** +** 2021-05-25 forum post 6a06202608: Another troublesome case is... +** +** CREATE TABLE t1(x); +** INSERT INTO t1 VALUES(10.0); +** SELECT 1 FROM t1 WHERE x=10 AND x LIKE 10; +** +** The query should return no rows, because the t1.x value is '10.0' not '10' +** and '10.0' is not LIKE '10'. But if we are not careful, the first WHERE +** term "x=10" will cause the second WHERE term to become "10 LIKE 10", +** resulting in a false positive. To avoid this, constant propagation for +** columns with BLOB affinity is only allowed if the constant is used with +** operators ==, <=, <, >=, >, or IS in a way that will cause the correct +** type conversions to occur. See logic associated with the bHasAffBlob flag +** for details. */ static int propagateConstants( Parse *pParse, /* The parsing context */ @@ -135619,10 +140496,23 @@ static int propagateConstants( Walker w; int nChng = 0; x.pParse = pParse; + x.pOomFault = &pParse->db->mallocFailed; do{ x.nConst = 0; x.nChng = 0; x.apExpr = 0; + x.bHasAffBlob = 0; + if( ALWAYS(p->pSrc!=0) + && p->pSrc->nSrc>0 + && (p->pSrc->a[0].fg.jointype & JT_LTORJ)!=0 + ){ + /* Do not propagate constants on any ON clause if there is a + ** RIGHT JOIN anywhere in the query */ + x.mExcludeOn = EP_InnerON | EP_OuterON; + }else{ + /* Do not propagate constants through the ON clause of a LEFT JOIN */ + x.mExcludeOn = EP_OuterON; + } findConstInWhere(&x, p->pWhere); if( x.nConst ){ memset(&w, 0, sizeof(w)); @@ -135742,13 +140632,13 @@ static int pushDownWhereTerms( Parse *pParse, /* Parse context (for malloc() and error reporting) */ Select *pSubq, /* The subquery whose WHERE clause is to be augmented */ Expr *pWhere, /* The WHERE clause of the outer query */ - int iCursor, /* Cursor number of the subquery */ - int isLeftJoin /* True if pSubq is the right term of a LEFT JOIN */ + SrcItem *pSrc /* The subquery term of the outer FROM clause */ ){ Expr *pNew; int nChng = 0; if( pWhere==0 ) return 0; if( pSubq->selFlags & (SF_Recursive|SF_MultiPart) ) return 0; + if( pSrc->fg.jointype & (JT_LTORJ|JT_RIGHT) ) return 0; #ifndef SQLITE_OMIT_WINDOWFUNC if( pSubq->pPrior ){ @@ -135778,30 +140668,35 @@ static int pushDownWhereTerms( return 0; /* restriction (3) */ } while( pWhere->op==TK_AND ){ - nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight, - iCursor, isLeftJoin); + nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight, pSrc); pWhere = pWhere->pLeft; } + +#if 0 /* Legacy code. Checks now done by sqlite3ExprIsTableConstraint() */ if( isLeftJoin - && (ExprHasProperty(pWhere,EP_FromJoin)==0 - || pWhere->iRightJoinTable!=iCursor) + && (ExprHasProperty(pWhere,EP_OuterON)==0 + || pWhere->w.iJoin!=iCursor) ){ return 0; /* restriction (4) */ } - if( ExprHasProperty(pWhere,EP_FromJoin) && pWhere->iRightJoinTable!=iCursor ){ + if( ExprHasProperty(pWhere,EP_OuterON) + && pWhere->w.iJoin!=iCursor + ){ return 0; /* restriction (5) */ } - if( sqlite3ExprIsTableConstant(pWhere, iCursor) ){ +#endif + + if( sqlite3ExprIsTableConstraint(pWhere, pSrc) ){ nChng++; pSubq->selFlags |= SF_PushDown; while( pSubq ){ SubstContext x; pNew = sqlite3ExprDup(pParse->db, pWhere, 0); - unsetJoinExpr(pNew, -1); + unsetJoinExpr(pNew, -1, 1); x.pParse = pParse; - x.iTable = iCursor; - x.iNewTable = iCursor; - x.isLeftJoin = 0; + x.iTable = pSrc->iCursor; + x.iNewTable = pSrc->iCursor; + x.isOuterJoin = 0; x.pEList = pSubq->pEList; pNew = substExpr(&x, pNew); #ifndef SQLITE_OMIT_WINDOWFUNC @@ -135842,7 +140737,7 @@ static int pushDownWhereTerms( */ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){ int eRet = WHERE_ORDERBY_NORMAL; /* Return value */ - ExprList *pEList = pFunc->x.pList; /* Arguments to agg function */ + ExprList *pEList; /* Arguments to agg function */ const char *zFunc; /* Name of aggregate function pFunc */ ExprList *pOrderBy; u8 sortFlags = 0; @@ -135850,6 +140745,8 @@ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){ assert( *ppMinMax==0 ); assert( pFunc->op==TK_AGG_FUNCTION ); assert( !IsWindowFunc(pFunc) ); + assert( ExprUseXList(pFunc) ); + pEList = pFunc->x.pList; if( pEList==0 || pEList->nExpr!=1 || ExprHasProperty(pFunc, EP_WinFunc) @@ -135857,6 +140754,7 @@ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){ ){ return eRet; } + assert( !ExprHasProperty(pFunc, EP_IntValue) ); zFunc = pFunc->u.zToken; if( sqlite3StrICmp(zFunc, "min")==0 ){ eRet = WHERE_ORDERBY_MIN; @@ -135871,7 +140769,7 @@ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){ } *ppMinMax = pOrderBy = sqlite3ExprListDup(db, pEList, 0); assert( pOrderBy!=0 || db->mallocFailed ); - if( pOrderBy ) pOrderBy->a[0].sortFlags = sortFlags; + if( pOrderBy ) pOrderBy->a[0].fg.sortFlags = sortFlags; return eRet; } @@ -135884,7 +140782,13 @@ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){ ** ** where table is a database table, not a sub-select or view. If the query ** does match this pattern, then a pointer to the Table object representing -** is returned. Otherwise, 0 is returned. +** is returned. Otherwise, NULL is returned. +** +** This routine checks to see if it is safe to use the count optimization. +** A correct answer is still obtained (though perhaps more slowly) if +** this routine returns NULL when it could have returned a table pointer. +** But returning the pointer when NULL should have been returned can +** result in incorrect answers and/or crashes. So, when in doubt, return NULL. */ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){ Table *pTab; @@ -135892,19 +140796,27 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){ assert( !p->pGroupBy ); - if( p->pWhere || p->pEList->nExpr!=1 - || p->pSrc->nSrc!=1 || p->pSrc->a[0].pSelect + if( p->pWhere + || p->pEList->nExpr!=1 + || p->pSrc->nSrc!=1 + || p->pSrc->a[0].pSelect + || pAggInfo->nFunc!=1 + || p->pHaving ){ return 0; } pTab = p->pSrc->a[0].pTab; + assert( pTab!=0 ); + assert( !IsView(pTab) ); + if( !IsOrdinaryTable(pTab) ) return 0; pExpr = p->pEList->a[0].pExpr; - assert( pTab && !pTab->pSelect && pExpr ); - - if( IsVirtual(pTab) ) return 0; + assert( pExpr!=0 ); if( pExpr->op!=TK_AGG_FUNCTION ) return 0; - if( NEVER(pAggInfo->nFunc==0) ) return 0; + if( pExpr->pAggInfo!=pAggInfo ) return 0; if( (pAggInfo->aFunc[0].pFunc->funcFlags&SQLITE_FUNC_COUNT)==0 ) return 0; + assert( pAggInfo->aFunc[0].pFExpr==pExpr ); + testcase( ExprHasProperty(pExpr, EP_Distinct) ); + testcase( ExprHasProperty(pExpr, EP_WinFunc) ); if( ExprHasProperty(pExpr, EP_Distinct|EP_WinFunc) ) return 0; return pTab; @@ -135933,6 +140845,7 @@ SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){ pParse->checkSchema = 1; return SQLITE_ERROR; } + assert( pFrom->fg.isCte==0 ); pFrom->u2.pIBIndex = pIdx; return SQLITE_OK; } @@ -135993,7 +140906,7 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ pNew = sqlite3DbMallocZero(db, sizeof(*pNew) ); if( pNew==0 ) return WRC_Abort; memset(&dummy, 0, sizeof(dummy)); - pNewSrc = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&dummy,pNew,0,0); + pNewSrc = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&dummy,pNew,0); if( pNewSrc==0 ) return WRC_Abort; *pNew = *p; p->pSrc = pNewSrc; @@ -136059,6 +140972,7 @@ static struct Cte *searchWith( return &p->a[i]; } } + if( p->bView ) break; } return 0; } @@ -136068,23 +140982,33 @@ static struct Cte *searchWith( ** ** This routine pushes the WITH clause passed as the second argument ** onto the top of the stack. If argument bFree is true, then this -** WITH clause will never be popped from the stack. In this case it -** should be freed along with the Parse object. In other cases, when +** WITH clause will never be popped from the stack but should instead +** be freed along with the Parse object. In other cases, when ** bFree==0, the With object will be freed along with the SELECT ** statement with which it is associated. +** +** This routine returns a copy of pWith. Or, if bFree is true and +** the pWith object is destroyed immediately due to an OOM condition, +** then this routine return NULL. +** +** If bFree is true, do not continue to use the pWith pointer after +** calling this routine, Instead, use only the return value. */ -SQLITE_PRIVATE void sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){ +SQLITE_PRIVATE With *sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){ if( pWith ){ - assert( pParse->pWith!=pWith ); - pWith->pOuter = pParse->pWith; - pParse->pWith = pWith; if( bFree ){ - sqlite3ParserAddCleanup(pParse, - (void(*)(sqlite3*,void*))sqlite3WithDelete, - pWith); - testcase( pParse->earlyCleanup ); + pWith = (With*)sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3WithDelete, + pWith); + if( pWith==0 ) return 0; + } + if( pParse->nErr==0 ){ + assert( pParse->pWith!=pWith ); + pWith->pOuter = pParse->pWith; + pParse->pWith = pWith; } } + return pWith; } /* @@ -136114,11 +141038,24 @@ static int resolveFromTermToCte( /* There are no WITH clauses in the stack. No match is possible */ return 0; } + if( pParse->nErr ){ + /* Prior errors might have left pParse->pWith in a goofy state, so + ** go no further. */ + return 0; + } if( pFrom->zDatabase!=0 ){ /* The FROM term contains a schema qualifier (ex: main.t1) and so ** it cannot possibly be a CTE reference. */ return 0; } + if( pFrom->fg.notCte ){ + /* The FROM term is specifically excluded from matching a CTE. + ** (1) It is part of a trigger that used to have zDatabase but had + ** zDatabase removed by sqlite3FixTriggerStep(). + ** (2) This is the first term in the FROM clause of an UPDATE. + */ + return 0; + } pCte = searchWith(pParse->pWith, pFrom, &pWith); if( pCte ){ sqlite3 *db = pParse->db; @@ -136164,7 +141101,12 @@ static int resolveFromTermToCte( pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid; pFrom->pSelect = sqlite3SelectDup(db, pCte->pSelect, 0); if( db->mallocFailed ) return 2; + pFrom->pSelect->selFlags |= SF_CopyCte; assert( pFrom->pSelect ); + if( pFrom->fg.isIndexedBy ){ + sqlite3ErrorMsg(pParse, "no such index: \"%s\"", pFrom->u1.zIndexedBy); + return 2; + } pFrom->fg.isCte = 1; pFrom->u2.pCteUse = pCteUse; pCteUse->nUse++; @@ -136267,7 +141209,7 @@ static int resolveFromTermToCte( ** sqlite3SelectExpand() when walking a SELECT tree to resolve table ** names and other FROM clause elements. */ -static void selectPopWith(Walker *pWalker, Select *p){ +SQLITE_PRIVATE void sqlite3SelectPopWith(Walker *pWalker, Select *p){ Parse *pParse = pWalker->pParse; if( OK_IF_ALWAYS_TRUE(pParse->pWith) && p->pPrior==0 ){ With *pWith = findRightmost(p)->pWith; @@ -136277,8 +141219,6 @@ static void selectPopWith(Walker *pWalker, Select *p){ } } } -#else -#define selectPopWith 0 #endif /* @@ -136299,17 +141239,47 @@ SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse *pParse, SrcItem *pFrom){ if( pFrom->zAlias ){ pTab->zName = sqlite3DbStrDup(pParse->db, pFrom->zAlias); }else{ - pTab->zName = sqlite3MPrintf(pParse->db, "subquery_%u", pSel->selId); + pTab->zName = sqlite3MPrintf(pParse->db, "%!S", pFrom); } while( pSel->pPrior ){ pSel = pSel->pPrior; } sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol); pTab->iPKey = -1; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); - pTab->tabFlags |= TF_Ephemeral; - +#ifndef SQLITE_ALLOW_ROWID_IN_VIEW + /* The usual case - do not allow ROWID on a subquery */ + pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid; +#else + pTab->tabFlags |= TF_Ephemeral; /* Legacy compatibility mode */ +#endif return pParse->nErr ? SQLITE_ERROR : SQLITE_OK; } + +/* +** Check the N SrcItem objects to the right of pBase. (N might be zero!) +** If any of those SrcItem objects have a USING clause containing zName +** then return true. +** +** If N is zero, or none of the N SrcItem objects to the right of pBase +** contains a USING clause, or if none of the USING clauses contain zName, +** then return false. +*/ +static int inAnyUsingClause( + const char *zName, /* Name we are looking for */ + SrcItem *pBase, /* The base SrcItem. Looking at pBase[1] and following */ + int N /* How many SrcItems to check */ +){ + while( N>0 ){ + N--; + pBase++; + if( pBase->fg.isUsing==0 ) continue; + if( NEVER(pBase->u3.pUsing==0) ) continue; + if( sqlite3IdListIndex(pBase->u3.pUsing, zName)>=0 ) return 1; + } + return 0; +} + + /* ** This routine is a Walker callback for "expanding" a SELECT statement. ** "Expanding" means to do the following: @@ -136359,6 +141329,15 @@ static int selectExpander(Walker *pWalker, Select *p){ } pTabList = p->pSrc; pEList = p->pEList; + if( pParse->pWith && (p->selFlags & SF_View) ){ + if( p->pWith==0 ){ + p->pWith = (With*)sqlite3DbMallocZero(db, sizeof(With)); + if( p->pWith==0 ){ + return WRC_Abort; + } + } + p->pWith->bView = 1; + } sqlite3WithPush(pParse, p->pWith, 0); /* Make sure cursor numbers have been assigned to all entries in @@ -136406,29 +141385,31 @@ static int selectExpander(Walker *pWalker, Select *p){ return WRC_Abort; } #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) - if( IsVirtual(pTab) || pTab->pSelect ){ + if( !IsOrdinaryTable(pTab) ){ i16 nCol; u8 eCodeOrig = pWalker->eCode; if( sqlite3ViewGetColumnNames(pParse, pTab) ) return WRC_Abort; assert( pFrom->pSelect==0 ); - if( pTab->pSelect - && (db->flags & SQLITE_EnableView)==0 - && pTab->pSchema!=db->aDb[1].pSchema - ){ - sqlite3ErrorMsg(pParse, "access to view \"%s\" prohibited", - pTab->zName); + if( IsView(pTab) ){ + if( (db->flags & SQLITE_EnableView)==0 + && pTab->pSchema!=db->aDb[1].pSchema + ){ + sqlite3ErrorMsg(pParse, "access to view \"%s\" prohibited", + pTab->zName); + } + pFrom->pSelect = sqlite3SelectDup(db, pTab->u.view.pSelect, 0); } #ifndef SQLITE_OMIT_VIRTUALTABLE - if( IsVirtual(pTab) + else if( ALWAYS(IsVirtual(pTab)) && pFrom->fg.fromDDL - && ALWAYS(pTab->pVTable!=0) - && pTab->pVTable->eVtabRisk > ((db->flags & SQLITE_TrustedSchema)!=0) + && ALWAYS(pTab->u.vtab.p!=0) + && pTab->u.vtab.p->eVtabRisk > ((db->flags & SQLITE_TrustedSchema)!=0) ){ sqlite3ErrorMsg(pParse, "unsafe use of virtual table \"%s\"", pTab->zName); } + assert( SQLITE_VTABRISK_Normal==1 && SQLITE_VTABRISK_High==2 ); #endif - pFrom->pSelect = sqlite3SelectDup(db, pTab->pSelect, 0); nCol = pTab->nCol; pTab->nCol = -1; pWalker->eCode = 1; /* Turn on Select.selId renumbering */ @@ -136447,7 +141428,8 @@ static int selectExpander(Walker *pWalker, Select *p){ /* Process NATURAL keywords, and ON and USING clauses of joins. */ - if( pParse->nErr || db->mallocFailed || sqliteProcessJoin(pParse, p) ){ + assert( db->mallocFailed==0 || pParse->nErr!=0 ); + if( pParse->nErr || sqlite3ProcessJoin(pParse, p) ){ return WRC_Abort; } @@ -136495,7 +141477,7 @@ static int selectExpander(Walker *pWalker, Select *p){ pNew = sqlite3ExprListAppend(pParse, pNew, a[k].pExpr); if( pNew ){ pNew->a[pNew->nExpr-1].zEName = a[k].zEName; - pNew->a[pNew->nExpr-1].eEName = a[k].eEName; + pNew->a[pNew->nExpr-1].fg.eEName = a[k].fg.eEName; a[k].zEName = 0; } a[k].pExpr = 0; @@ -136510,32 +141492,60 @@ static int selectExpander(Walker *pWalker, Select *p){ zTName = pE->pLeft->u.zToken; } for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){ - Table *pTab = pFrom->pTab; - Select *pSub = pFrom->pSelect; - char *zTabName = pFrom->zAlias; - const char *zSchemaName = 0; - int iDb; - if( zTabName==0 ){ + Table *pTab = pFrom->pTab; /* Table for this data source */ + ExprList *pNestedFrom; /* Result-set of a nested FROM clause */ + char *zTabName; /* AS name for this data source */ + const char *zSchemaName = 0; /* Schema name for this data source */ + int iDb; /* Schema index for this data src */ + IdList *pUsing; /* USING clause for pFrom[1] */ + + if( (zTabName = pFrom->zAlias)==0 ){ zTabName = pTab->zName; } if( db->mallocFailed ) break; - if( pSub==0 || (pSub->selFlags & SF_NestedFrom)==0 ){ - pSub = 0; + assert( (int)pFrom->fg.isNestedFrom == IsNestedFrom(pFrom->pSelect) ); + if( pFrom->fg.isNestedFrom ){ + assert( pFrom->pSelect!=0 ); + pNestedFrom = pFrom->pSelect->pEList; + assert( pNestedFrom!=0 ); + assert( pNestedFrom->nExpr==pTab->nCol ); + }else{ if( zTName && sqlite3StrICmp(zTName, zTabName)!=0 ){ continue; } + pNestedFrom = 0; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); zSchemaName = iDb>=0 ? db->aDb[iDb].zDbSName : "*"; } + if( i+1nSrc + && pFrom[1].fg.isUsing + && (selFlags & SF_NestedFrom)!=0 + ){ + int ii; + pUsing = pFrom[1].u3.pUsing; + for(ii=0; iinId; ii++){ + const char *zUName = pUsing->a[ii].zName; + pRight = sqlite3Expr(db, TK_ID, zUName); + pNew = sqlite3ExprListAppend(pParse, pNew, pRight); + if( pNew ){ + struct ExprList_item *pX = &pNew->a[pNew->nExpr-1]; + assert( pX->zEName==0 ); + pX->zEName = sqlite3MPrintf(db,"..%s", zUName); + pX->fg.eEName = ENAME_TAB; + pX->fg.bUsingTerm = 1; + } + } + }else{ + pUsing = 0; + } for(j=0; jnCol; j++){ - char *zName = pTab->aCol[j].zName; - char *zColname; /* The computed column name */ - char *zToFree; /* Malloced string that needs to be freed */ - Token sColname; /* Computed column name as a token */ + char *zName = pTab->aCol[j].zCnName; + struct ExprList_item *pX; /* Newly added ExprList term */ assert( zName ); - if( zTName && pSub - && sqlite3MatchEName(&pSub->pEList->a[j], 0, zTName, 0)==0 + if( zTName + && pNestedFrom + && sqlite3MatchEName(&pNestedFrom->a[j], 0, zTName, 0)==0 ){ continue; } @@ -136549,57 +141559,75 @@ static int selectExpander(Walker *pWalker, Select *p){ ){ continue; } + if( (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)!=0 + && zTName==0 + && (selFlags & (SF_NestedFrom))==0 + ){ + continue; + } tableSeen = 1; - if( i>0 && zTName==0 ){ - if( (pFrom->fg.jointype & JT_NATURAL)!=0 - && tableAndColumnIndex(pTabList, i, zName, 0, 0, 1) + if( i>0 && zTName==0 && (selFlags & SF_NestedFrom)==0 ){ + if( pFrom->fg.isUsing + && sqlite3IdListIndex(pFrom->u3.pUsing, zName)>=0 ){ - /* In a NATURAL join, omit the join columns from the - ** table to the right of the join */ - continue; - } - if( sqlite3IdListIndex(pFrom->pUsing, zName)>=0 ){ /* In a join with a USING clause, omit columns in the ** using clause from the table on the right. */ continue; } } pRight = sqlite3Expr(db, TK_ID, zName); - zColname = zName; - zToFree = 0; - if( longNames || pTabList->nSrc>1 ){ + if( (pTabList->nSrc>1 + && ( (pFrom->fg.jointype & JT_LTORJ)==0 + || (selFlags & SF_NestedFrom)!=0 + || !inAnyUsingClause(zName,pFrom,pTabList->nSrc-i-1) + ) + ) + || IN_RENAME_OBJECT + ){ Expr *pLeft; pLeft = sqlite3Expr(db, TK_ID, zTabName); pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); + if( IN_RENAME_OBJECT && pE->pLeft ){ + sqlite3RenameTokenRemap(pParse, pLeft, pE->pLeft); + } if( zSchemaName ){ pLeft = sqlite3Expr(db, TK_ID, zSchemaName); pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pExpr); } - if( longNames ){ - zColname = sqlite3MPrintf(db, "%s.%s", zTabName, zName); - zToFree = zColname; - } }else{ pExpr = pRight; } pNew = sqlite3ExprListAppend(pParse, pNew, pExpr); - sqlite3TokenInit(&sColname, zColname); - sqlite3ExprListSetName(pParse, pNew, &sColname, 0); - if( pNew && (p->selFlags & SF_NestedFrom)!=0 && !IN_RENAME_OBJECT ){ - struct ExprList_item *pX = &pNew->a[pNew->nExpr-1]; - sqlite3DbFree(db, pX->zEName); - if( pSub ){ - pX->zEName = sqlite3DbStrDup(db, pSub->pEList->a[j].zEName); + if( pNew==0 ){ + break; /* OOM */ + } + pX = &pNew->a[pNew->nExpr-1]; + assert( pX->zEName==0 ); + if( (selFlags & SF_NestedFrom)!=0 && !IN_RENAME_OBJECT ){ + if( pNestedFrom ){ + pX->zEName = sqlite3DbStrDup(db, pNestedFrom->a[j].zEName); testcase( pX->zEName==0 ); }else{ pX->zEName = sqlite3MPrintf(db, "%s.%s.%s", - zSchemaName, zTabName, zColname); + zSchemaName, zTabName, zName); testcase( pX->zEName==0 ); } - pX->eEName = ENAME_TAB; + pX->fg.eEName = ENAME_TAB; + if( (pFrom->fg.isUsing + && sqlite3IdListIndex(pFrom->u3.pUsing, zName)>=0) + || (pUsing && sqlite3IdListIndex(pUsing, zName)>=0) + || (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)!=0 + ){ + pX->fg.bNoExpand = 1; + } + }else if( longNames ){ + pX->zEName = sqlite3MPrintf(db, "%s.%s", zTabName, zName); + pX->fg.eEName = ENAME_NAME; + }else{ + pX->zEName = sqlite3DbStrDup(db, zName); + pX->fg.eEName = ENAME_NAME; } - sqlite3DbFree(db, zToFree); } } if( !tableSeen ){ @@ -136623,6 +141651,12 @@ static int selectExpander(Walker *pWalker, Select *p){ p->selFlags |= SF_ComplexResult; } } +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x100 ){ + SELECTTRACE(0x100,pParse,p,("After result-set wildcard expansion:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif return WRC_Continue; } @@ -136659,7 +141693,7 @@ static void sqlite3SelectExpand(Parse *pParse, Select *pSelect){ sqlite3WalkSelect(&w, pSelect); } w.xSelectCallback = selectExpander; - w.xSelectCallback2 = selectPopWith; + w.xSelectCallback2 = sqlite3SelectPopWith; w.eCode = 0; sqlite3WalkSelect(&w, pSelect); } @@ -136744,12 +141778,13 @@ SQLITE_PRIVATE void sqlite3SelectPrep( NameContext *pOuterNC /* Name context for container */ ){ assert( p!=0 || pParse->db->mallocFailed ); + assert( pParse->db->pParse==pParse ); if( pParse->db->mallocFailed ) return; if( p->selFlags & SF_HasTypeInfo ) return; sqlite3SelectExpand(pParse, p); - if( pParse->nErr || pParse->db->mallocFailed ) return; + if( pParse->nErr ) return; sqlite3ResolveSelectNames(pParse, p, pOuterNC); - if( pParse->nErr || pParse->db->mallocFailed ) return; + if( pParse->nErr ) return; sqlite3SelectAddTypeInfo(pParse, p); } @@ -136766,8 +141801,10 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ int i; struct AggInfo_func *pFunc; int nReg = pAggInfo->nFunc + pAggInfo->nColumn; + assert( pParse->db->pParse==pParse ); + assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); if( nReg==0 ) return; - if( pParse->nErr || pParse->db->mallocFailed ) return; + if( pParse->nErr ) return; #ifdef SQLITE_DEBUG /* Verify that all AggInfo registers are within the range specified by ** AggInfo.mnReg..AggInfo.mxReg */ @@ -136785,15 +141822,17 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ for(pFunc=pAggInfo->aFunc, i=0; inFunc; i++, pFunc++){ if( pFunc->iDistinct>=0 ){ Expr *pE = pFunc->pFExpr; - assert( !ExprHasProperty(pE, EP_xIsSelect) ); + assert( ExprUseXList(pE) ); if( pE->x.pList==0 || pE->x.pList->nExpr!=1 ){ sqlite3ErrorMsg(pParse, "DISTINCT aggregates must have exactly one " "argument"); pFunc->iDistinct = -1; }else{ KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pE->x.pList,0,0); - sqlite3VdbeAddOp4(v, OP_OpenEphemeral, pFunc->iDistinct, 0, 0, - (char*)pKeyInfo, P4_KEYINFO); + pFunc->iDistAddr = sqlite3VdbeAddOp4(v, OP_OpenEphemeral, + pFunc->iDistinct, 0, 0, (char*)pKeyInfo, P4_KEYINFO); + ExplainQueryPlan((pParse, 0, "USE TEMP B-TREE FOR %s(DISTINCT)", + pFunc->pFunc->zName)); } } } @@ -136808,8 +141847,9 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){ int i; struct AggInfo_func *pF; for(i=0, pF=pAggInfo->aFunc; inFunc; i++, pF++){ - ExprList *pList = pF->pFExpr->x.pList; - assert( !ExprHasProperty(pF->pFExpr, EP_xIsSelect) ); + ExprList *pList; + assert( ExprUseXList(pF->pFExpr) ); + pList = pF->pFExpr->x.pList; sqlite3VdbeAddOp2(v, OP_AggFinal, pF->iMem, pList ? pList->nExpr : 0); sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); } @@ -136825,7 +141865,12 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){ ** registers if register regAcc contains 0. The caller will take care ** of setting and clearing regAcc. */ -static void updateAccumulator(Parse *pParse, int regAcc, AggInfo *pAggInfo){ +static void updateAccumulator( + Parse *pParse, + int regAcc, + AggInfo *pAggInfo, + int eDistinctType +){ Vdbe *v = pParse->pVdbe; int i; int regHit = 0; @@ -136838,9 +141883,10 @@ static void updateAccumulator(Parse *pParse, int regAcc, AggInfo *pAggInfo){ int nArg; int addrNext = 0; int regAgg; - ExprList *pList = pF->pFExpr->x.pList; - assert( !ExprHasProperty(pF->pFExpr, EP_xIsSelect) ); + ExprList *pList; + assert( ExprUseXList(pF->pFExpr) ); assert( !IsWindowFunc(pF->pFExpr) ); + pList = pF->pFExpr->x.pList; if( ExprHasProperty(pF->pFExpr, EP_WinFunc) ){ Expr *pFilter = pF->pFExpr->y.pWin->pFilter; if( pAggInfo->nAccumulator @@ -136871,13 +141917,12 @@ static void updateAccumulator(Parse *pParse, int regAcc, AggInfo *pAggInfo){ nArg = 0; regAgg = 0; } - if( pF->iDistinct>=0 ){ + if( pF->iDistinct>=0 && pList ){ if( addrNext==0 ){ addrNext = sqlite3VdbeMakeLabel(pParse); } - testcase( nArg==0 ); /* Error condition */ - testcase( nArg>1 ); /* Also an error */ - codeDistinct(pParse, pF->iDistinct, addrNext, 1, regAgg); + pF->iDistinct = codeDistinct(pParse, eDistinctType, + pF->iDistinct, addrNext, pList, regAgg); } if( pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ CollSeq *pColl = 0; @@ -136929,7 +141974,7 @@ static void explainSimpleCount( ){ if( pParse->explain==2 ){ int bCover = (pIdx!=0 && (HasRowid(pTab) || !IsPrimaryKeyIndex(pIdx))); - sqlite3VdbeExplain(pParse, 0, "SCAN TABLE %s%s%s", + sqlite3VdbeExplain(pParse, 0, "SCAN %s%s%s", pTab->zName, bCover ? " USING COVERING INDEX " : "", bCover ? pIdx->zName : "" @@ -136954,8 +141999,16 @@ static void explainSimpleCount( static int havingToWhereExprCb(Walker *pWalker, Expr *pExpr){ if( pExpr->op!=TK_AND ){ Select *pS = pWalker->u.pSelect; + /* This routine is called before the HAVING clause of the current + ** SELECT is analyzed for aggregates. So if pExpr->pAggInfo is set + ** here, it indicates that the expression is a correlated reference to a + ** column from an outer aggregate query, or an aggregate function that + ** belongs to an outer query. Do not move the expression to the WHERE + ** clause in this obscure case, as doing so may corrupt the outer Select + ** statements AggInfo structure. */ if( sqlite3ExprIsConstantOrGroupBy(pWalker->pParse, pExpr, pS->pGroupBy) && ExprAlwaysFalse(pExpr)==0 + && pExpr->pAggInfo==0 ){ sqlite3 *db = pWalker->pParse->db; Expr *pNew = sqlite3Expr(db, TK_INTEGER, "1"); @@ -136994,8 +142047,8 @@ static void havingToWhere(Parse *pParse, Select *p){ sWalker.xExprCallback = havingToWhereExprCb; sWalker.u.pSelect = p; sqlite3WalkExpr(&sWalker, p->pHaving); -#if SELECTTRACE_ENABLED - if( sWalker.eCode && (sqlite3SelectTrace & 0x100)!=0 ){ +#if TREETRACE_ENABLED + if( sWalker.eCode && (sqlite3TreeTrace & 0x100)!=0 ){ SELECTTRACE(0x100,pParse,p,("Move HAVING terms into WHERE:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -137079,7 +142132,9 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ if( p->pGroupBy ) return 0; pExpr = p->pEList->a[0].pExpr; if( pExpr->op!=TK_AGG_FUNCTION ) return 0; /* Result is an aggregate */ + assert( ExprUseUToken(pExpr) ); if( sqlite3_stricmp(pExpr->u.zToken,"count") ) return 0; /* Is count() */ + assert( ExprUseXList(pExpr) ); if( pExpr->x.pList!=0 ) return 0; /* Must be count(*) */ if( p->pSrc->nSrc!=1 ) return 0; /* One table in FROM */ pSub = p->pSrc->a[0].pSelect; @@ -137125,8 +142180,8 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ p->pEList->a[0].pExpr = pExpr; p->selFlags &= ~SF_Aggregate; -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x400 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x400 ){ SELECTTRACE(0x400,pParse,p,("After count-of-view optimization:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -137135,6 +142190,29 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ } #endif /* SQLITE_COUNTOFVIEW_OPTIMIZATION */ +/* +** If any term of pSrc, or any SF_NestedFrom sub-query, is not the same +** as pSrcItem but has the same alias as p0, then return true. +** Otherwise return false. +*/ +static int sameSrcAlias(SrcItem *p0, SrcList *pSrc){ + int i; + for(i=0; inSrc; i++){ + SrcItem *p1 = &pSrc->a[i]; + if( p1==p0 ) continue; + if( p0->pTab==p1->pTab && 0==sqlite3_stricmp(p0->zAlias, p1->zAlias) ){ + return 1; + } + if( p1->pSelect + && (p1->pSelect->selFlags & SF_NestedFrom)!=0 + && sameSrcAlias(p0, p1->pSelect->pSrc) + ){ + return 1; + } + } + return 0; +} + /* ** Generate code for the SELECT statement given in the p argument. ** @@ -137172,15 +142250,21 @@ SQLITE_PRIVATE int sqlite3Select( u8 minMaxFlag; /* Flag for min/max queries */ db = pParse->db; + assert( pParse==db->pParse ); v = sqlite3GetVdbe(pParse); - if( p==0 || db->mallocFailed || pParse->nErr ){ + if( p==0 || pParse->nErr ){ return 1; } + assert( db->mallocFailed==0 ); if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1; -#if SELECTTRACE_ENABLED +#if TREETRACE_ENABLED SELECTTRACE(1,pParse,p, ("begin processing:\n", pParse->addrExplain)); - if( sqlite3SelectTrace & 0x100 ){ - sqlite3TreeViewSelect(0, p, 0); + if( sqlite3TreeTrace & 0x10100 ){ + if( (sqlite3TreeTrace & 0x10001)==0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Select() at %s:%d", + __FILE__, __LINE__); + } + sqlite3ShowSelect(p); } #endif @@ -137194,9 +142278,9 @@ SQLITE_PRIVATE int sqlite3Select( pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_DistFifo ); /* All of these destinations are also able to ignore the ORDER BY clause */ if( p->pOrderBy ){ -#if SELECTTRACE_ENABLED +#if TREETRACE_ENABLED SELECTTRACE(1,pParse,p, ("dropping superfluous ORDER BY:\n")); - if( sqlite3SelectTrace & 0x100 ){ + if( sqlite3TreeTrace & 0x100 ){ sqlite3TreeViewExprList(0, p->pOrderBy, 0, "ORDERBY"); } #endif @@ -137210,47 +142294,55 @@ SQLITE_PRIVATE int sqlite3Select( p->selFlags |= SF_NoopOrderBy; } sqlite3SelectPrep(pParse, p, 0); - if( pParse->nErr || db->mallocFailed ){ + if( pParse->nErr ){ goto select_end; } + assert( db->mallocFailed==0 ); assert( p->pEList!=0 ); -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x104 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x104 ){ SELECTTRACE(0x104,pParse,p, ("after name resolution:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif - /* If the SF_UpdateFrom flag is set, then this function is being called + /* If the SF_UFSrcCheck flag is set, then this function is being called ** as part of populating the temp table for an UPDATE...FROM statement. ** In this case, it is an error if the target object (pSrc->a[0]) name - ** or alias is duplicated within FROM clause (pSrc->a[1..n]). */ - if( p->selFlags & SF_UpdateFrom ){ + ** or alias is duplicated within FROM clause (pSrc->a[1..n]). + ** + ** Postgres disallows this case too. The reason is that some other + ** systems handle this case differently, and not all the same way, + ** which is just confusing. To avoid this, we follow PG's lead and + ** disallow it altogether. */ + if( p->selFlags & SF_UFSrcCheck ){ SrcItem *p0 = &p->pSrc->a[0]; - for(i=1; ipSrc->nSrc; i++){ - SrcItem *p1 = &p->pSrc->a[i]; - if( p0->pTab==p1->pTab && 0==sqlite3_stricmp(p0->zAlias, p1->zAlias) ){ - sqlite3ErrorMsg(pParse, - "target object/alias may not appear in FROM clause: %s", - p0->zAlias ? p0->zAlias : p0->pTab->zName - ); - goto select_end; - } + if( sameSrcAlias(p0, p->pSrc) ){ + sqlite3ErrorMsg(pParse, + "target object/alias may not appear in FROM clause: %s", + p0->zAlias ? p0->zAlias : p0->pTab->zName + ); + goto select_end; } + + /* Clear the SF_UFSrcCheck flag. The check has already been performed, + ** and leaving this flag set can cause errors if a compound sub-query + ** in p->pSrc is flattened into this query and this function called + ** again as part of compound SELECT processing. */ + p->selFlags &= ~SF_UFSrcCheck; } if( pDest->eDest==SRT_Output ){ - generateColumnNames(pParse, p); + sqlite3GenerateColumnNames(pParse, p); } #ifndef SQLITE_OMIT_WINDOWFUNC - rc = sqlite3WindowRewrite(pParse, p); - if( rc ){ - assert( db->mallocFailed || pParse->nErr>0 ); + if( sqlite3WindowRewrite(pParse, p) ){ + assert( pParse->nErr ); goto select_end; } -#if SELECTTRACE_ENABLED - if( p->pWin && (sqlite3SelectTrace & 0x108)!=0 ){ +#if TREETRACE_ENABLED + if( p->pWin && (sqlite3TreeTrace & 0x108)!=0 ){ SELECTTRACE(0x104,pParse,p, ("after window rewrite:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -137278,14 +142370,16 @@ SQLITE_PRIVATE int sqlite3Select( /* Convert LEFT JOIN into JOIN if there are terms of the right table ** of the LEFT JOIN used in the WHERE clause. */ - if( (pItem->fg.jointype & JT_LEFT)!=0 + if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))==JT_LEFT && sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor) && OptimizationEnabled(db, SQLITE_SimplifyJoin) ){ SELECTTRACE(0x100,pParse,p, ("LEFT-JOIN simplifies to JOIN on term %d\n",i)); pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER); - unsetJoinExpr(p->pWhere, pItem->iCursor); + assert( pItem->iCursor>=0 ); + unsetJoinExpr(p->pWhere, pItem->iCursor, + pTabList->a[0].fg.jointype & JT_LTORJ); } /* No futher action if this term of the FROM clause is no a subquery */ @@ -137309,6 +142403,41 @@ SQLITE_PRIVATE int sqlite3Select( if( (pSub->selFlags & SF_Aggregate)!=0 ) continue; assert( pSub->pGroupBy==0 ); + /* If a FROM-clause subquery has an ORDER BY clause that is not + ** really doing anything, then delete it now so that it does not + ** interfere with query flattening. See the discussion at + ** https://sqlite.org/forum/forumpost/2d76f2bcf65d256a + ** + ** Beware of these cases where the ORDER BY clause may not be safely + ** omitted: + ** + ** (1) There is also a LIMIT clause + ** (2) The subquery was added to help with window-function + ** processing + ** (3) The subquery is in the FROM clause of an UPDATE + ** (4) The outer query uses an aggregate function other than + ** the built-in count(), min(), or max(). + ** (5) The ORDER BY isn't going to accomplish anything because + ** one of: + ** (a) The outer query has a different ORDER BY clause + ** (b) The subquery is part of a join + ** See forum post 062d576715d277c8 + */ + if( pSub->pOrderBy!=0 + && (p->pOrderBy!=0 || pTabList->nSrc>1) /* Condition (5) */ + && pSub->pLimit==0 /* Condition (1) */ + && (pSub->selFlags & SF_OrderByReqd)==0 /* Condition (2) */ + && (p->selFlags & SF_OrderByReqd)==0 /* Condition (3) and (4) */ + && OptimizationEnabled(db, SQLITE_OmitOrderBy) + ){ + SELECTTRACE(0x100,pParse,p, + ("omit superfluous ORDER BY on %r FROM-clause subquery\n",i+1)); + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3ExprListDelete, + pSub->pOrderBy); + pSub->pOrderBy = 0; + } + /* If the outer query contains a "complex" result set (that is, ** if the result set of the outer query uses functions or subqueries) ** and if the subquery contains an ORDER BY clause and if @@ -137331,7 +142460,7 @@ SQLITE_PRIVATE int sqlite3Select( && i==0 && (p->selFlags & SF_ComplexResult)!=0 && (pTabList->nSrc==1 - || (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0) + || (pTabList->a[1].fg.jointype&(JT_OUTER|JT_CROSS))!=0) ){ continue; } @@ -137355,9 +142484,9 @@ SQLITE_PRIVATE int sqlite3Select( */ if( p->pPrior ){ rc = multiSelect(pParse, p, pDest); -#if SELECTTRACE_ENABLED +#if TREETRACE_ENABLED SELECTTRACE(0x1,pParse,p,("end compound-select processing\n")); - if( (sqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ + if( (sqlite3TreeTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ sqlite3TreeViewSelect(0, p, 0); } #endif @@ -137371,12 +142500,13 @@ SQLITE_PRIVATE int sqlite3Select( ** as the equivalent optimization will be handled by query planner in ** sqlite3WhereBegin(). */ - if( pTabList->nSrc>1 + if( p->pWhere!=0 + && p->pWhere->op==TK_AND && OptimizationEnabled(db, SQLITE_PropagateConst) && propagateConstants(pParse, p) ){ -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x100 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x100 ){ SELECTTRACE(0x100,pParse,p,("After constant propagation:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -137434,19 +142564,8 @@ SQLITE_PRIVATE int sqlite3Select( pSub = pItem->pSelect; if( pSub==0 ) continue; - /* The code for a subquery should only be generated once, though it is - ** technically harmless for it to be generated multiple times. The - ** following assert() will detect if something changes to cause - ** the same subquery to be coded multiple times, as a signal to the - ** developers to try to optimize the situation. - ** - ** Update 2019-07-24: - ** See ticket https://sqlite.org/src/tktview/c52b09c7f38903b1311cec40. - ** The dbsqlfuzz fuzzer found a case where the same subquery gets - ** coded twice. So this assert() now becomes a testcase(). It should - ** be very rare, though. - */ - testcase( pItem->addrFillSub!=0 ); + /* The code for a subquery should only be generated once. */ + assert( pItem->addrFillSub==0 ); /* Increment Parse.nHeight by the height of the largest expression ** tree referred to by this, the parent select. The child select @@ -137461,12 +142580,12 @@ SQLITE_PRIVATE int sqlite3Select( ** inside the subquery. This can help the subquery to run more efficiently. */ if( OptimizationEnabled(db, SQLITE_PushDown) - && (pItem->fg.isCte==0 || pItem->u2.pCteUse->eM10d!=M10d_Yes) - && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem->iCursor, - (pItem->fg.jointype & JT_OUTER)!=0) + && (pItem->fg.isCte==0 + || (pItem->u2.pCteUse->eM10d!=M10d_Yes && pItem->u2.pCteUse->nUse<2)) + && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem) ){ -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x100 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x100 ){ SELECTTRACE(0x100,pParse,p, ("After WHERE-clause push-down into subquery %d:\n", pSub->selId)); sqlite3TreeViewSelect(0, p, 0); @@ -137482,18 +142601,19 @@ SQLITE_PRIVATE int sqlite3Select( /* Generate code to implement the subquery ** - ** The subquery is implemented as a co-routine if: + ** The subquery is implemented as a co-routine if all of the following are + ** true: + ** ** (1) the subquery is guaranteed to be the outer loop (so that ** it does not need to be computed more than once), and ** (2) the subquery is not a CTE that should be materialized - ** - ** TODO: Are there other reasons beside (1) and (2) to use a co-routine - ** implementation? + ** (3) the subquery is not part of a left operand for a RIGHT JOIN */ if( i==0 && (pTabList->nSrc==1 - || (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0) /* (1) */ - && (pItem->fg.isCte==0 || pItem->u2.pCteUse->eM10d!=M10d_Yes) /* (2) */ + || (pTabList->a[1].fg.jointype&(JT_OUTER|JT_CROSS))!=0) /* (1) */ + && (pItem->fg.isCte==0 || pItem->u2.pCteUse->eM10d!=M10d_Yes) /* (2) */ + && (pTabList->a[0].fg.jointype & JT_LTORJ)==0 /* (3) */ ){ /* Implement a co-routine that will return a single row of the result ** set on each invocation. @@ -137502,10 +142622,10 @@ SQLITE_PRIVATE int sqlite3Select( pItem->regReturn = ++pParse->nMem; sqlite3VdbeAddOp3(v, OP_InitCoroutine, pItem->regReturn, 0, addrTop); - VdbeComment((v, "%s", pItem->pTab->zName)); + VdbeComment((v, "%!S", pItem)); pItem->addrFillSub = addrTop; sqlite3SelectDestInit(&dest, SRT_Coroutine, pItem->regReturn); - ExplainQueryPlan((pParse, 1, "CO-ROUTINE %u", pSub->selId)); + ExplainQueryPlan((pParse, 1, "CO-ROUTINE %!S", pItem)); sqlite3Select(pParse, pSub, &dest); pItem->pTab->nRowLogEst = pSub->nSelectRow; pItem->fg.viaCoroutine = 1; @@ -137522,6 +142642,7 @@ SQLITE_PRIVATE int sqlite3Select( sqlite3VdbeAddOp2(v, OP_Gosub, pCteUse->regRtn, pCteUse->addrM9e); if( pItem->iCursor!=pCteUse->iCur ){ sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pCteUse->iCur); + VdbeComment((v, "%!S", pItem)); } pSub->nSelectRow = pCteUse->nRowEst; }else if( (pPrior = isSelfJoinView(pTabList, pItem))!=0 ){ @@ -137533,34 +142654,33 @@ SQLITE_PRIVATE int sqlite3Select( sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor); pSub->nSelectRow = pPrior->pSelect->nSelectRow; }else{ - /* Materalize the view. If the view is not correlated, generate a + /* Materialize the view. If the view is not correlated, generate a ** subroutine to do the materialization so that subsequent uses of ** the same view can reuse the materialization. */ int topAddr; int onceAddr = 0; - int retAddr; - testcase( pItem->addrFillSub==0 ); /* Ticket c52b09c7f38903b1311 */ pItem->regReturn = ++pParse->nMem; - topAddr = sqlite3VdbeAddOp2(v, OP_Integer, 0, pItem->regReturn); + topAddr = sqlite3VdbeAddOp0(v, OP_Goto); pItem->addrFillSub = topAddr+1; + pItem->fg.isMaterialized = 1; if( pItem->fg.isCorrelated==0 ){ /* If the subquery is not correlated and if we are not inside of ** a trigger, then we only need to compute the value of the subquery ** once. */ onceAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); - VdbeComment((v, "materialize \"%s\"", pItem->pTab->zName)); + VdbeComment((v, "materialize %!S", pItem)); }else{ - VdbeNoopComment((v, "materialize \"%s\"", pItem->pTab->zName)); + VdbeNoopComment((v, "materialize %!S", pItem)); } sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor); - ExplainQueryPlan((pParse, 1, "MATERIALIZE %u", pSub->selId)); + ExplainQueryPlan((pParse, 1, "MATERIALIZE %!S", pItem)); sqlite3Select(pParse, pSub, &dest); pItem->pTab->nRowLogEst = pSub->nSelectRow; if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr); - retAddr = sqlite3VdbeAddOp1(v, OP_Return, pItem->regReturn); - VdbeComment((v, "end %s", pItem->pTab->zName)); - sqlite3VdbeChangeP1(v, topAddr, retAddr); + sqlite3VdbeAddOp2(v, OP_Return, pItem->regReturn, topAddr+1); + VdbeComment((v, "end %!S", pItem)); + sqlite3VdbeJumpHere(v, topAddr); sqlite3ClearTempRegCache(pParse); if( pItem->fg.isCte && pItem->fg.isCorrelated==0 ){ CteUse *pCteUse = pItem->u2.pCteUse; @@ -137584,8 +142704,8 @@ SQLITE_PRIVATE int sqlite3Select( pHaving = p->pHaving; sDistinct.isTnct = (p->selFlags & SF_Distinct)!=0; -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x400 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x400 ){ SELECTTRACE(0x400,pParse,p,("After all FROM-clause analysis:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -137619,9 +142739,10 @@ SQLITE_PRIVATE int sqlite3Select( ** the sDistinct.isTnct is still set. Hence, isTnct represents the ** original setting of the SF_Distinct flag, not the current setting */ assert( sDistinct.isTnct ); + sDistinct.isTnct = 2; -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x400 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x400 ){ SELECTTRACE(0x400,pParse,p,("Transform DISTINCT into GROUP BY:\n")); sqlite3TreeViewSelect(0, p, 0); } @@ -137654,6 +142775,18 @@ SQLITE_PRIVATE int sqlite3Select( */ if( pDest->eDest==SRT_EphemTab ){ sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pDest->iSDParm, pEList->nExpr); + if( p->selFlags & SF_NestedFrom ){ + /* Delete or NULL-out result columns that will never be used */ + int ii; + for(ii=pEList->nExpr-1; ii>0 && pEList->a[ii].fg.bUsed==0; ii--){ + sqlite3ExprDelete(db, pEList->a[ii].pExpr); + sqlite3DbFree(db, pEList->a[ii].zEName); + pEList->nExpr--; + } + for(ii=0; iinExpr; ii++){ + if( pEList->a[ii].fg.bUsed==0 ) pEList->a[ii].pExpr->op = TK_NULL; + } + } } /* Set the limiter. @@ -137698,7 +142831,7 @@ SQLITE_PRIVATE int sqlite3Select( /* Begin the database scan. */ SELECTTRACE(1,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy, - p->pEList, wctrlFlags, p->nSelectRow); + p->pEList, p, wctrlFlags, p->nSelectRow); if( pWInfo==0 ) goto select_end; if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){ p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo); @@ -137803,8 +142936,9 @@ SQLITE_PRIVATE int sqlite3Select( ** ORDER BY to maximize the chances of rows being delivered in an ** order that makes the ORDER BY redundant. */ for(ii=0; iinExpr; ii++){ - u8 sortFlags = sSort.pOrderBy->a[ii].sortFlags & KEYINFO_ORDER_DESC; - pGroupBy->a[ii].sortFlags = sortFlags; + u8 sortFlags; + sortFlags = sSort.pOrderBy->a[ii].fg.sortFlags & KEYINFO_ORDER_DESC; + pGroupBy->a[ii].fg.sortFlags = sortFlags; } if( sqlite3ExprListCompare(pGroupBy, sSort.pOrderBy, -1)==0 ){ orderByGrp = 1; @@ -137860,7 +142994,7 @@ SQLITE_PRIVATE int sqlite3Select( } for(i=0; inFunc; i++){ Expr *pExpr = pAggInfo->aFunc[i].pFExpr; - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); + assert( ExprUseXList(pExpr) ); sNC.ncFlags |= NC_InAggFunc; sqlite3ExprAnalyzeAggList(&sNC, pExpr->x.pList); #ifndef SQLITE_OMIT_WINDOWFUNC @@ -137873,8 +143007,8 @@ SQLITE_PRIVATE int sqlite3Select( } pAggInfo->mxReg = pParse->nMem; if( db->mallocFailed ) goto select_end; -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x400 ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x400 ){ int ii; SELECTTRACE(0x400,pParse,p,("After aggregate analysis %p:\n", pAggInfo)); sqlite3TreeViewSelect(0, p, 0); @@ -137909,6 +143043,22 @@ SQLITE_PRIVATE int sqlite3Select( int addrSortingIdx; /* The OP_OpenEphemeral for the sorting index */ int addrReset; /* Subroutine for resetting the accumulator */ int regReset; /* Return address register for reset subroutine */ + ExprList *pDistinct = 0; + u16 distFlag = 0; + int eDist = WHERE_DISTINCT_NOOP; + + if( pAggInfo->nFunc==1 + && pAggInfo->aFunc[0].iDistinct>=0 + && ALWAYS(pAggInfo->aFunc[0].pFExpr!=0) + && ALWAYS(ExprUseXList(pAggInfo->aFunc[0].pFExpr)) + && pAggInfo->aFunc[0].pFExpr->x.pList!=0 + ){ + Expr *pExpr = pAggInfo->aFunc[0].pFExpr->x.pList->a[0].pExpr; + pExpr = sqlite3ExprDup(db, pExpr, 0); + pDistinct = sqlite3ExprListDup(db, pGroupBy, 0); + pDistinct = sqlite3ExprListAppend(pParse, pDistinct, pExpr); + distFlag = pDistinct ? (WHERE_WANT_DISTINCT|WHERE_AGG_DISTINCT) : 0; + } /* If there is a GROUP BY clause we might need a sorting index to ** implement it. Allocate that sorting index now. If it turns out @@ -137945,10 +143095,15 @@ SQLITE_PRIVATE int sqlite3Select( */ sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset); SELECTTRACE(1,pParse,p,("WhereBegin\n")); - pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, 0, - WHERE_GROUPBY | (orderByGrp ? WHERE_SORTBYGROUP : 0), 0 + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, pDistinct, + 0, (sDistinct.isTnct==2 ? WHERE_DISTINCTBY : WHERE_GROUPBY) + | (orderByGrp ? WHERE_SORTBYGROUP : 0) | distFlag, 0 ); - if( pWInfo==0 ) goto select_end; + if( pWInfo==0 ){ + sqlite3ExprListDelete(db, pDistinct); + goto select_end; + } + eDist = sqlite3WhereIsDistinct(pWInfo); SELECTTRACE(1,pParse,p,("WhereBegin returns\n")); if( sqlite3WhereIsOrdered(pWInfo)==pGroupBy->nExpr ){ /* The optimizer is able to deliver rows in group by order so @@ -138066,7 +143221,7 @@ SQLITE_PRIVATE int sqlite3Select( ** the current row */ sqlite3VdbeJumpHere(v, addr1); - updateAccumulator(pParse, iUseFlag, pAggInfo); + updateAccumulator(pParse, iUseFlag, pAggInfo, eDist); sqlite3VdbeAddOp2(v, OP_Integer, 1, iUseFlag); VdbeComment((v, "indicate data in accumulator")); @@ -138080,6 +143235,7 @@ SQLITE_PRIVATE int sqlite3Select( sqlite3WhereEnd(pWInfo); sqlite3VdbeChangeToNoop(v, addrSortingIdx); } + sqlite3ExprListDelete(db, pDistinct); /* Output the final row of result */ @@ -138123,6 +143279,10 @@ SQLITE_PRIVATE int sqlite3Select( VdbeComment((v, "indicate accumulator empty")); sqlite3VdbeAddOp1(v, OP_Return, regReset); + if( distFlag!=0 && eDist!=WHERE_DISTINCT_NOOP ){ + struct AggInfo_func *pF = &pAggInfo->aFunc[0]; + fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr); + } } /* endif pGroupBy. Begin aggregate queries without GROUP BY: */ else { Table *pTab; @@ -138186,6 +143346,9 @@ SQLITE_PRIVATE int sqlite3Select( explainSimpleCount(pParse, pTab, pBest); }else{ int regAcc = 0; /* "populate accumulators" flag */ + ExprList *pDistinct = 0; + u16 distFlag = 0; + int eDist; /* If there are accumulator registers but no min() or max() functions ** without FILTER clauses, allocate register regAcc. Register regAcc @@ -138209,6 +143372,10 @@ SQLITE_PRIVATE int sqlite3Select( regAcc = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Integer, 0, regAcc); } + }else if( pAggInfo->nFunc==1 && pAggInfo->aFunc[0].iDistinct>=0 ){ + assert( ExprUseXList(pAggInfo->aFunc[0].pFExpr) ); + pDistinct = pAggInfo->aFunc[0].pFExpr->x.pList; + distFlag = pDistinct ? (WHERE_WANT_DISTINCT|WHERE_AGG_DISTINCT) : 0; } /* This case runs if the aggregate has no GROUP BY clause. The @@ -138228,12 +143395,20 @@ SQLITE_PRIVATE int sqlite3Select( SELECTTRACE(1,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pMinMaxOrderBy, - 0, minMaxFlag, 0); + pDistinct, 0, minMaxFlag|distFlag, 0); if( pWInfo==0 ){ goto select_end; } SELECTTRACE(1,pParse,p,("WhereBegin returns\n")); - updateAccumulator(pParse, regAcc, pAggInfo); + eDist = sqlite3WhereIsDistinct(pWInfo); + updateAccumulator(pParse, regAcc, pAggInfo, eDist); + if( eDist!=WHERE_DISTINCT_NOOP ){ + struct AggInfo_func *pF = pAggInfo->aFunc; + if( pF ){ + fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr); + } + } + if( regAcc ) sqlite3VdbeAddOp2(v, OP_Integer, 1, regAcc); if( minMaxFlag ){ sqlite3WhereMinMaxOptEarlyOut(v, pWInfo); @@ -138278,6 +143453,8 @@ SQLITE_PRIVATE int sqlite3Select( ** successful coding of the SELECT. */ select_end: + assert( db->mallocFailed==0 || db->mallocFailed==1 ); + assert( db->mallocFailed==0 || pParse->nErr!=0 ); sqlite3ExprListDelete(db, pMinMaxOrderBy); #ifdef SQLITE_DEBUG if( pAggInfo && !db->mallocFailed ){ @@ -138296,9 +143473,9 @@ select_end: } #endif -#if SELECTTRACE_ENABLED +#if TREETRACE_ENABLED SELECTTRACE(0x1,pParse,p,("end processing\n")); - if( (sqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ + if( (sqlite3TreeTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ sqlite3TreeViewSelect(0, p, 0); } #endif @@ -138563,34 +143740,43 @@ SQLITE_PRIVATE Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){ Trigger *pList; /* List of triggers to return */ HashElem *p; /* Loop variable for TEMP triggers */ - if( pParse->disableTriggers ){ - return 0; - } + assert( pParse->disableTriggers==0 ); pTmpSchema = pParse->db->aDb[1].pSchema; p = sqliteHashFirst(&pTmpSchema->trigHash); - if( p==0 ){ - return pTab->pTrigger; - } pList = pTab->pTrigger; - if( pTmpSchema!=pTab->pSchema ){ - while( p ){ - Trigger *pTrig = (Trigger *)sqliteHashData(p); - if( pTrig->pTabSchema==pTab->pSchema - && 0==sqlite3StrICmp(pTrig->table, pTab->zName) - ){ - pTrig->pNext = pList; - pList = pTrig; - }else if( pTrig->op==TK_RETURNING ){ - assert( pParse->bReturning ); - assert( &(pParse->u1.pReturning->retTrig) == pTrig ); - pTrig->table = pTab->zName; - pTrig->pTabSchema = pTab->pSchema; - pTrig->pNext = pList; - pList = pTrig; - } - p = sqliteHashNext(p); + while( p ){ + Trigger *pTrig = (Trigger *)sqliteHashData(p); + if( pTrig->pTabSchema==pTab->pSchema + && pTrig->table + && 0==sqlite3StrICmp(pTrig->table, pTab->zName) + && pTrig->pTabSchema!=pTmpSchema + ){ + pTrig->pNext = pList; + pList = pTrig; + }else if( pTrig->op==TK_RETURNING ){ +#ifndef SQLITE_OMIT_VIRTUALTABLE + assert( pParse->db->pVtabCtx==0 ); +#endif + assert( pParse->bReturning ); + assert( &(pParse->u1.pReturning->retTrig) == pTrig ); + pTrig->table = pTab->zName; + pTrig->pTabSchema = pTab->pSchema; + pTrig->pNext = pList; + pList = pTrig; } + p = sqliteHashNext(p); } +#if 0 + if( pList ){ + Trigger *pX; + printf("Triggers for %s:", pTab->zName); + for(pX=pList; pX; pX=pX->pNext){ + printf(" %s", pX->zName); + } + printf("\n"); + fflush(stdout); + } +#endif return pList; } @@ -138718,14 +143904,14 @@ SQLITE_PRIVATE void sqlite3BeginTrigger( /* INSTEAD of triggers are only for views and views only support INSTEAD ** of triggers. */ - if( pTab->pSelect && tr_tm!=TK_INSTEAD ){ + if( IsView(pTab) && tr_tm!=TK_INSTEAD ){ sqlite3ErrorMsg(pParse, "cannot create %s trigger on view: %S", - (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName, 0); + (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName->a); goto trigger_orphan_error; } - if( !pTab->pSelect && tr_tm==TK_INSTEAD ){ + if( !IsView(pTab) && tr_tm==TK_INSTEAD ){ sqlite3ErrorMsg(pParse, "cannot create INSTEAD OF" - " trigger on table: %S", pTableName, 0); + " trigger on table: %S", pTableName->a); goto trigger_orphan_error; } @@ -138860,7 +144046,7 @@ SQLITE_PRIVATE void sqlite3FinishTrigger( z = sqlite3DbStrNDup(db, (char*)pAll->z, pAll->n); testcase( z==0 ); sqlite3NestedParse(pParse, - "INSERT INTO %Q." DFLT_SCHEMA_TABLE + "INSERT INTO %Q." LEGACY_SCHEMA_TABLE " VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')", db->aDb[iDb].zDbSName, zName, pTrig->table, z); @@ -138945,6 +144131,7 @@ static TriggerStep *triggerStepAllocate( sqlite3 *db = pParse->db; TriggerStep *pTriggerStep; + if( pParse->nErr ) return 0; pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep) + pName->n + 1); if( pTriggerStep ){ char *z = (char*)&pTriggerStep[1]; @@ -139015,7 +144202,7 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep( SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep( Parse *pParse, /* Parser */ Token *pTableName, /* Name of the table to be updated */ - SrcList *pFrom, + SrcList *pFrom, /* FROM clause for an UPDATE-FROM, or NULL */ ExprList *pEList, /* The SET clause: list of column and new values */ Expr *pWhere, /* The WHERE clause */ u8 orconf, /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */ @@ -139122,7 +144309,7 @@ SQLITE_PRIVATE void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr) } if( !pTrigger ){ if( !noErr ){ - sqlite3ErrorMsg(pParse, "no such trigger: %S", pName, 0); + sqlite3ErrorMsg(pParse, "no such trigger: %S", pName->a); }else{ sqlite3CodeVerifyNamedSchema(pParse, zDb); } @@ -139174,7 +144361,7 @@ SQLITE_PRIVATE void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){ */ if( (v = sqlite3GetVdbe(pParse))!=0 ){ sqlite3NestedParse(pParse, - "DELETE FROM %Q." DFLT_SCHEMA_TABLE " WHERE name=%Q AND type='trigger'", + "DELETE FROM %Q." LEGACY_SCHEMA_TABLE " WHERE name=%Q AND type='trigger'", db->aDb[iDb].zDbSName, pTrigger->zName ); sqlite3ChangeCookie(pParse, iDb); @@ -139228,13 +144415,22 @@ static int checkColumnOverlap(IdList *pIdList, ExprList *pEList){ return 0; } +/* +** Return true if any TEMP triggers exist +*/ +static int tempTriggersExist(sqlite3 *db){ + if( NEVER(db->aDb[1].pSchema==0) ) return 0; + if( sqliteHashFirst(&db->aDb[1].pSchema->trigHash)==0 ) return 0; + return 1; +} + /* ** Return a list of all triggers on table pTab if there exists at least ** one trigger that must be fired when an operation of type 'op' is ** performed on the table, and, if that operation is an UPDATE, if at ** least one of the columns in pChanges is being modified. */ -SQLITE_PRIVATE Trigger *sqlite3TriggersExist( +static SQLITE_NOINLINE Trigger *triggersReallyExist( Parse *pParse, /* Parse context */ Table *pTab, /* The table the contains the triggers */ int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */ @@ -139297,6 +144493,22 @@ exit_triggers_exist: } return (mask ? pList : 0); } +SQLITE_PRIVATE Trigger *sqlite3TriggersExist( + Parse *pParse, /* Parse context */ + Table *pTab, /* The table the contains the triggers */ + int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */ + ExprList *pChanges, /* Columns that change in an UPDATE statement */ + int *pMask /* OUT: Mask of TRIGGER_BEFORE|TRIGGER_AFTER */ +){ + assert( pTab!=0 ); + if( (pTab->pTrigger==0 && !tempTriggersExist(pParse->db)) + || pParse->disableTriggers + ){ + if( pMask ) *pMask = 0; + return 0; + } + return triggersReallyExist(pParse,pTab,op,pChanges,pMask); +} /* ** Convert the pStep->zTarget string into a SrcList and return a pointer @@ -139326,6 +144538,14 @@ SQLITE_PRIVATE SrcList *sqlite3TriggerStepSrc( } if( pStep->pFrom ){ SrcList *pDup = sqlite3SrcListDup(db, pStep->pFrom, 0); + if( pDup && pDup->nSrc>1 && !IN_RENAME_OBJECT ){ + Select *pSubquery; + Token as; + pSubquery = sqlite3SelectNew(pParse,0,pDup,0,0,0,0,SF_NestedFrom,0); + as.n = 0; + as.z = 0; + pDup = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&as,pSubquery,0); + } pSrc = sqlite3SrcListAppendList(pParse, pSrc, pDup); } }else{ @@ -139376,12 +144596,12 @@ static ExprList *sqlite3ExpandReturning( for(jj=0; jjnCol; jj++){ Expr *pNewExpr; if( IsHiddenColumn(pTab->aCol+jj) ) continue; - pNewExpr = sqlite3Expr(db, TK_ID, pTab->aCol[jj].zName); + pNewExpr = sqlite3Expr(db, TK_ID, pTab->aCol[jj].zCnName); pNew = sqlite3ExprListAppend(pParse, pNew, pNewExpr); if( !db->mallocFailed ){ struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1]; - pItem->zEName = sqlite3DbStrDup(db, pTab->aCol[jj].zName); - pItem->eEName = ENAME_NAME; + pItem->zEName = sqlite3DbStrDup(db, pTab->aCol[jj].zCnName); + pItem->fg.eEName = ENAME_NAME; } } }else{ @@ -139390,19 +144610,10 @@ static ExprList *sqlite3ExpandReturning( if( !db->mallocFailed && ALWAYS(pList->a[i].zEName!=0) ){ struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1]; pItem->zEName = sqlite3DbStrDup(db, pList->a[i].zEName); - pItem->eEName = pList->a[i].eEName; + pItem->fg.eEName = pList->a[i].fg.eEName; } } } - if( !db->mallocFailed ){ - Vdbe *v = pParse->pVdbe; - assert( v!=0 ); - sqlite3VdbeSetNumCols(v, pNew->nExpr); - for(i=0; inExpr; i++){ - sqlite3VdbeSetColName(v, i, COLNAME_NAME, pNew->a[i].zEName, - SQLITE_TRANSIENT); - } - } return pNew; } @@ -139418,15 +144629,32 @@ static void codeReturningTrigger( int regIn /* The first in an array of registers */ ){ Vdbe *v = pParse->pVdbe; + sqlite3 *db = pParse->db; ExprList *pNew; Returning *pReturning; + Select sSelect; + SrcList sFrom; assert( v!=0 ); assert( pParse->bReturning ); + assert( db->pParse==pParse ); pReturning = pParse->u1.pReturning; assert( pTrigger == &(pReturning->retTrig) ); + memset(&sSelect, 0, sizeof(sSelect)); + memset(&sFrom, 0, sizeof(sFrom)); + sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0); + sSelect.pSrc = &sFrom; + sFrom.nSrc = 1; + sFrom.a[0].pTab = pTab; + sFrom.a[0].iCursor = -1; + sqlite3SelectPrep(pParse, &sSelect, 0); + if( pParse->nErr==0 ){ + assert( db->mallocFailed==0 ); + sqlite3GenerateColumnNames(pParse, &sSelect); + } + sqlite3ExprListDelete(db, sSelect.pEList); pNew = sqlite3ExpandReturning(pParse, pReturning->pReturnEL, pTab); - if( pNew ){ + if( !db->mallocFailed ){ NameContext sNC; memset(&sNC, 0, sizeof(sNC)); if( pReturning->nRetCol==0 ){ @@ -139438,23 +144666,30 @@ static void codeReturningTrigger( sNC.ncFlags = NC_UBaseReg; pParse->eTriggerOp = pTrigger->op; pParse->pTriggerTab = pTab; - if( sqlite3ResolveExprListNames(&sNC, pNew)==SQLITE_OK ){ + if( sqlite3ResolveExprListNames(&sNC, pNew)==SQLITE_OK + && ALWAYS(!db->mallocFailed) + ){ int i; int nCol = pNew->nExpr; int reg = pParse->nMem+1; pParse->nMem += nCol+2; pReturning->iRetReg = reg; for(i=0; ia[i].pExpr, reg+i); + Expr *pCol = pNew->a[i].pExpr; + assert( pCol!=0 ); /* Due to !db->mallocFailed ~9 lines above */ + sqlite3ExprCodeFactorable(pParse, pCol, reg+i); + if( sqlite3ExprAffinity(pCol)==SQLITE_AFF_REAL ){ + sqlite3VdbeAddOp1(v, OP_RealAffinity, reg+i); + } } sqlite3VdbeAddOp3(v, OP_MakeRecord, reg, i, reg+i); sqlite3VdbeAddOp2(v, OP_NewRowid, pReturning->iRetCur, reg+i+1); sqlite3VdbeAddOp3(v, OP_Insert, pReturning->iRetCur, reg+i, reg+i+1); } - sqlite3ExprListDelete(pParse->db, pNew); - pParse->eTriggerOp = 0; - pParse->pTriggerTab = 0; } + sqlite3ExprListDelete(db, pNew); + pParse->eTriggerOp = 0; + pParse->pTriggerTab = 0; } @@ -139596,8 +144831,8 @@ static TriggerPrg *codeRowTrigger( Vdbe *v; /* Temporary VM */ NameContext sNC; /* Name context for sub-vdbe */ SubProgram *pProgram = 0; /* Sub-vdbe for trigger program */ - Parse *pSubParse; /* Parse context for sub-vdbe */ int iEndTrigger = 0; /* Label to jump to if WHEN is false */ + Parse sSubParse; /* Parse context for sub-vdbe */ assert( pTrigger->zName==0 || pTab==tableOfTrigger(pTrigger) ); assert( pTop->pVdbe ); @@ -139619,19 +144854,17 @@ static TriggerPrg *codeRowTrigger( /* Allocate and populate a new Parse context to use for coding the ** trigger sub-program. */ - pSubParse = sqlite3StackAllocZero(db, sizeof(Parse)); - if( !pSubParse ) return 0; + sqlite3ParseObjectInit(&sSubParse, db); memset(&sNC, 0, sizeof(sNC)); - sNC.pParse = pSubParse; - pSubParse->db = db; - pSubParse->pTriggerTab = pTab; - pSubParse->pToplevel = pTop; - pSubParse->zAuthContext = pTrigger->zName; - pSubParse->eTriggerOp = pTrigger->op; - pSubParse->nQueryLoop = pParse->nQueryLoop; - pSubParse->disableVtab = pParse->disableVtab; + sNC.pParse = &sSubParse; + sSubParse.pTriggerTab = pTab; + sSubParse.pToplevel = pTop; + sSubParse.zAuthContext = pTrigger->zName; + sSubParse.eTriggerOp = pTrigger->op; + sSubParse.nQueryLoop = pParse->nQueryLoop; + sSubParse.disableVtab = pParse->disableVtab; - v = sqlite3GetVdbe(pSubParse); + v = sqlite3GetVdbe(&sSubParse); if( v ){ VdbeComment((v, "Start: %s.%s (%s %s%s%s ON %s)", pTrigger->zName, onErrorText(orconf), @@ -139654,17 +144887,17 @@ static TriggerPrg *codeRowTrigger( ** OP_Halt inserted at the end of the program. */ if( pTrigger->pWhen ){ pWhen = sqlite3ExprDup(db, pTrigger->pWhen, 0); - if( SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen) - && db->mallocFailed==0 + if( db->mallocFailed==0 + && SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen) ){ - iEndTrigger = sqlite3VdbeMakeLabel(pSubParse); - sqlite3ExprIfFalse(pSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL); + iEndTrigger = sqlite3VdbeMakeLabel(&sSubParse); + sqlite3ExprIfFalse(&sSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL); } sqlite3ExprDelete(db, pWhen); } /* Code the trigger program into the sub-vdbe. */ - codeTriggerProgram(pSubParse, pTrigger->step_list, orconf); + codeTriggerProgram(&sSubParse, pTrigger->step_list, orconf); /* Insert an OP_Halt at the end of the sub-program. */ if( iEndTrigger ){ @@ -139672,23 +144905,24 @@ static TriggerPrg *codeRowTrigger( } sqlite3VdbeAddOp0(v, OP_Halt); VdbeComment((v, "End: %s.%s", pTrigger->zName, onErrorText(orconf))); + transferParseError(pParse, &sSubParse); - transferParseError(pParse, pSubParse); - if( db->mallocFailed==0 && pParse->nErr==0 ){ + if( pParse->nErr==0 ){ + assert( db->mallocFailed==0 ); pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pTop->nMaxArg); } - pProgram->nMem = pSubParse->nMem; - pProgram->nCsr = pSubParse->nTab; + pProgram->nMem = sSubParse.nMem; + pProgram->nCsr = sSubParse.nTab; pProgram->token = (void *)pTrigger; - pPrg->aColmask[0] = pSubParse->oldmask; - pPrg->aColmask[1] = pSubParse->newmask; + pPrg->aColmask[0] = sSubParse.oldmask; + pPrg->aColmask[1] = sSubParse.newmask; sqlite3VdbeDelete(v); + }else{ + transferParseError(pParse, &sSubParse); } - assert( !pSubParse->pTriggerPrg && !pSubParse->nMaxArg ); - sqlite3ParserReset(pSubParse); - sqlite3StackFree(db, pSubParse); - + assert( !sSubParse.pTriggerPrg && !sSubParse.nMaxArg ); + sqlite3ParseObjectReset(&sSubParse); return pPrg; } @@ -139721,6 +144955,7 @@ static TriggerPrg *getRowTrigger( /* If an existing TriggerPrg could not be located, create a new one. */ if( !pPrg ){ pPrg = codeRowTrigger(pParse, pTrigger, pTab, orconf); + pParse->db->errByteOffset = -1; } return pPrg; @@ -139743,7 +144978,7 @@ SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect( Vdbe *v = sqlite3GetVdbe(pParse); /* Main VM */ TriggerPrg *pPrg; pPrg = getRowTrigger(pParse, p, pTab, orconf); - assert( pPrg || pParse->nErr || pParse->db->mallocFailed ); + assert( pPrg || pParse->nErr ); /* Code the OP_Program opcode in the parent VDBE. P4 of the OP_Program ** is a pointer to the sub-vdbe containing the trigger program. */ @@ -139974,13 +145209,14 @@ static void updateVirtualTable( */ SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){ assert( pTab!=0 ); - if( !pTab->pSelect ){ + if( !IsView(pTab) ){ sqlite3_value *pValue = 0; u8 enc = ENC(sqlite3VdbeDb(v)); Column *pCol = &pTab->aCol[i]; - VdbeComment((v, "%s.%s", pTab->zName, pCol->zName)); + VdbeComment((v, "%s.%s", pTab->zName, pCol->zCnName)); assert( inCol ); - sqlite3ValueFromExpr(sqlite3VdbeDb(v), pCol->pDflt, enc, + sqlite3ValueFromExpr(sqlite3VdbeDb(v), + sqlite3ColumnExpr(pTab,pCol), enc, pCol->affinity, &pValue); if( pValue ){ sqlite3VdbeAppendP4(v, pValue, P4_MEM); @@ -140134,6 +145370,7 @@ static void updateFromSelect( assert( pTabList->nSrc>1 ); if( pSrc ){ + pSrc->a[0].fg.notCte = 1; pSrc->a[0].iCursor = -1; pSrc->a[0].pTab->nTabRef--; pSrc->a[0].pTab = 0; @@ -140149,7 +145386,7 @@ static void updateFromSelect( pList = sqlite3ExprListAppend(pParse, pList, pNew); } eDest = IsVirtual(pTab) ? SRT_Table : SRT_Upfrom; - }else if( pTab->pSelect ){ + }else if( IsView(pTab) ){ for(i=0; inCol; i++){ pList = sqlite3ExprListAppend(pParse, pList, exprRowColumn(pParse, i)); } @@ -140163,7 +145400,8 @@ static void updateFromSelect( } #endif } - if( ALWAYS(pChanges) ){ + assert( pChanges!=0 || pParse->db->mallocFailed ); + if( pChanges ){ for(i=0; inExpr; i++){ pList = sqlite3ExprListAppend(pParse, pList, sqlite3ExprDup(db, pChanges->a[i].pExpr, 0) @@ -140171,8 +145409,9 @@ static void updateFromSelect( } } pSelect = sqlite3SelectNew(pParse, pList, - pSrc, pWhere2, pGrp, 0, pOrderBy2, SF_UpdateFrom|SF_IncludeHidden, pLimit2 + pSrc, pWhere2, pGrp, 0, pOrderBy2, SF_UFSrcCheck|SF_IncludeHidden, pLimit2 ); + if( pSelect ) pSelect->selFlags |= SF_OrderByReqd; sqlite3SelectDestInit(&dest, eDest, iEph); dest.iSDParm2 = (pPk ? pPk->nKeyCol : -1); sqlite3Select(pParse, pSelect, &dest); @@ -140257,9 +145496,11 @@ SQLITE_PRIVATE void sqlite3Update( memset(&sContext, 0, sizeof(sContext)); db = pParse->db; - if( pParse->nErr || db->mallocFailed ){ + assert( db->pParse==pParse ); + if( pParse->nErr ){ goto update_cleanup; } + assert( db->mallocFailed==0 ); /* Locate the table which we want to update. */ @@ -140272,7 +145513,7 @@ SQLITE_PRIVATE void sqlite3Update( */ #ifndef SQLITE_OMIT_TRIGGER pTrigger = sqlite3TriggersExist(pParse, pTab, TK_UPDATE, pChanges, &tmask); - isView = pTab->pSelect!=0; + isView = IsView(pTab); assert( pTrigger || tmask==0 ); #else # define pTrigger 0 @@ -140284,6 +145525,14 @@ SQLITE_PRIVATE void sqlite3Update( # define isView 0 #endif +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Update() at %s:%d", __FILE__, __LINE__); + sqlite3TreeViewUpdate(pParse->pWith, pTabList, pChanges, pWhere, + onError, pOrderBy, pLimit, pUpsert, pTrigger); + } +#endif + /* If there was a FROM clause, set nChangeFrom to the number of expressions ** in the change-list. Otherwise, set it to 0. There cannot be a FROM ** clause if this function is being called to generate code for part of @@ -140361,13 +145610,16 @@ SQLITE_PRIVATE void sqlite3Update( */ chngRowid = chngPk = 0; for(i=0; inExpr; i++){ + u8 hCol = sqlite3StrIHash(pChanges->a[i].zEName); /* If this is an UPDATE with a FROM clause, do not resolve expressions ** here. The call to sqlite3Select() below will do that. */ if( nChangeFrom==0 && sqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr) ){ goto update_cleanup; } for(j=0; jnCol; j++){ - if( sqlite3StrICmp(pTab->aCol[j].zName, pChanges->a[i].zEName)==0 ){ + if( pTab->aCol[j].hName==hCol + && sqlite3StrICmp(pTab->aCol[j].zCnName, pChanges->a[i].zEName)==0 + ){ if( j==pTab->iPKey ){ chngRowid = 1; pRowidExpr = pChanges->a[i].pExpr; @@ -140381,7 +145633,7 @@ SQLITE_PRIVATE void sqlite3Update( testcase( pTab->aCol[j].colFlags & COLFLAG_STORED ); sqlite3ErrorMsg(pParse, "cannot UPDATE generated column \"%s\"", - pTab->aCol[j].zName); + pTab->aCol[j].zCnName); goto update_cleanup; } #endif @@ -140405,7 +145657,7 @@ SQLITE_PRIVATE void sqlite3Update( { int rc; rc = sqlite3AuthCheck(pParse, SQLITE_UPDATE, pTab->zName, - j<0 ? "ROWID" : pTab->aCol[j].zName, + j<0 ? "ROWID" : pTab->aCol[j].zCnName, db->aDb[iDb].zDbSName); if( rc==SQLITE_DENY ){ goto update_cleanup; @@ -140437,8 +145689,10 @@ SQLITE_PRIVATE void sqlite3Update( for(i=0; inCol; i++){ if( aXRef[i]>=0 ) continue; if( (pTab->aCol[i].colFlags & COLFLAG_GENERATED)==0 ) continue; - if( sqlite3ExprReferencesUpdatedColumn(pTab->aCol[i].pDflt, - aXRef, chngRowid) ){ + if( sqlite3ExprReferencesUpdatedColumn( + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + aXRef, chngRowid) + ){ aXRef[i] = 99999; bProgress = 1; } @@ -140626,7 +145880,7 @@ SQLITE_PRIVATE void sqlite3Update( if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){ flags |= WHERE_ONEPASS_MULTIROW; } - pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags,iIdxCur); + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere,0,0,0,flags,iIdxCur); if( pWInfo==0 ) goto update_cleanup; /* A one-pass strategy that might update more than one row may not @@ -140713,7 +145967,12 @@ SQLITE_PRIVATE void sqlite3Update( /* Top of the update loop */ if( eOnePass!=ONEPASS_OFF ){ - if( !isView && aiCurOnePass[0]!=iDataCur && aiCurOnePass[1]!=iDataCur ){ + if( aiCurOnePass[0]!=iDataCur + && aiCurOnePass[1]!=iDataCur +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + && !isView +#endif + ){ assert( pPk ); sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelBreak, regKey,nKey); VdbeCoverage(v); @@ -140918,7 +146177,7 @@ SQLITE_PRIVATE void sqlite3Update( }else{ sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue,regOldRowid); } - VdbeCoverageNeverTaken(v); + VdbeCoverage(v); } /* Do FK constraint checks. */ @@ -141021,9 +146280,7 @@ SQLITE_PRIVATE void sqlite3Update( ** that information. */ if( regRowCount ){ - sqlite3VdbeAddOp2(v, OP_ChngCntRow, regRowCount, 1); - sqlite3VdbeSetNumCols(v, 1); - sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows updated", SQLITE_STATIC); + sqlite3CodeChangeCount(v, regRowCount, "rows updated"); } update_cleanup: @@ -141145,7 +146402,9 @@ static void updateVirtualTable( regRowid = ++pParse->nMem; /* Start scanning the virtual table */ - pWInfo = sqlite3WhereBegin(pParse, pSrc,pWhere,0,0,WHERE_ONEPASS_DESIRED,0); + pWInfo = sqlite3WhereBegin( + pParse, pSrc, pWhere, 0, 0, 0, WHERE_ONEPASS_DESIRED, 0 + ); if( pWInfo==0 ) return; /* Populate the argument registers. */ @@ -141525,7 +146784,7 @@ SQLITE_PRIVATE void sqlite3UpsertDoUpdate( k = sqlite3TableColumnToIndex(pIdx, pPk->aiColumn[i]); sqlite3VdbeAddOp3(v, OP_Column, iCur, k, iPk+i); VdbeComment((v, "%s.%s", pIdx->zName, - pTab->aCol[pPk->aiColumn[i]].zName)); + pTab->aCol[pPk->aiColumn[i]].zCnName)); } sqlite3VdbeVerifyAbortable(v, OE_Abort); i = sqlite3VdbeAddOp4Int(v, OP_Found, iDataCur, 0, iPk, nPk); @@ -141707,8 +146966,8 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( Btree *pTemp; /* The temporary database we vacuum into */ u32 saved_mDbFlags; /* Saved value of db->mDbFlags */ u64 saved_flags; /* Saved value of db->flags */ - int saved_nChange; /* Saved value of db->nChange */ - int saved_nTotalChange; /* Saved value of db->nTotalChange */ + i64 saved_nChange; /* Saved value of db->nChange */ + i64 saved_nTotalChange; /* Saved value of db->nTotalChange */ u32 saved_openFlags; /* Saved value of db->openFlags */ u8 saved_mTrace; /* Saved trace settings */ Db *pDb = 0; /* Database to detach at end of vacuum */ @@ -141806,7 +147065,9 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( /* Do not attempt to change the page size for a WAL database */ if( sqlite3PagerGetJournalMode(sqlite3BtreePager(pMain)) - ==PAGER_JOURNALMODE_WAL ){ + ==PAGER_JOURNALMODE_WAL + && pOut==0 + ){ db->nextPagesize = 0; } @@ -141922,6 +147183,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( assert( rc==SQLITE_OK ); if( pOut==0 ){ + nRes = sqlite3BtreeGetRequestedReserve(pTemp); rc = sqlite3BtreeSetPageSize(pMain, sqlite3BtreeGetPageSize(pTemp), nRes,1); } @@ -142155,7 +147417,7 @@ SQLITE_PRIVATE void sqlite3VtabLock(VTable *pVTab){ SQLITE_PRIVATE VTable *sqlite3GetVTable(sqlite3 *db, Table *pTab){ VTable *pVtab; assert( IsVirtual(pTab) ); - for(pVtab=pTab->pVTable; pVtab && pVtab->db!=db; pVtab=pVtab->pNext); + for(pVtab=pTab->u.vtab.p; pVtab && pVtab->db!=db; pVtab=pVtab->pNext); return pVtab; } @@ -142168,7 +147430,8 @@ SQLITE_PRIVATE void sqlite3VtabUnlock(VTable *pVTab){ assert( db ); assert( pVTab->nRef>0 ); - assert( db->magic==SQLITE_MAGIC_OPEN || db->magic==SQLITE_MAGIC_ZOMBIE ); + assert( db->eOpenState==SQLITE_STATE_OPEN + || db->eOpenState==SQLITE_STATE_ZOMBIE ); pVTab->nRef--; if( pVTab->nRef==0 ){ @@ -142183,21 +147446,24 @@ SQLITE_PRIVATE void sqlite3VtabUnlock(VTable *pVTab){ /* ** Table p is a virtual table. This function moves all elements in the -** p->pVTable list to the sqlite3.pDisconnect lists of their associated +** p->u.vtab.p list to the sqlite3.pDisconnect lists of their associated ** database connections to be disconnected at the next opportunity. ** Except, if argument db is not NULL, then the entry associated with -** connection db is left in the p->pVTable list. +** connection db is left in the p->u.vtab.p list. */ static VTable *vtabDisconnectAll(sqlite3 *db, Table *p){ VTable *pRet = 0; - VTable *pVTable = p->pVTable; - p->pVTable = 0; + VTable *pVTable; + + assert( IsVirtual(p) ); + pVTable = p->u.vtab.p; + p->u.vtab.p = 0; /* Assert that the mutex (if any) associated with the BtShared database ** that contains table p is held by the caller. See header comments ** above function sqlite3VtabUnlockList() for an explanation of why ** this makes it safe to access the sqlite3.pDisconnect list of any - ** database connection that may have an entry in the p->pVTable list. + ** database connection that may have an entry in the p->u.vtab.p list. */ assert( db==0 || sqlite3SchemaMutexHeld(db, 0, p->pSchema) ); @@ -142207,7 +147473,7 @@ static VTable *vtabDisconnectAll(sqlite3 *db, Table *p){ assert( db2 ); if( db2==db ){ pRet = pVTable; - p->pVTable = pRet; + p->u.vtab.p = pRet; pRet->pNext = 0; }else{ pVTable->pNext = db2->pDisconnect; @@ -142235,7 +147501,7 @@ SQLITE_PRIVATE void sqlite3VtabDisconnect(sqlite3 *db, Table *p){ assert( sqlite3BtreeHoldsAllMutexes(db) ); assert( sqlite3_mutex_held(db->mutex) ); - for(ppVTab=&p->pVTable; *ppVTab; ppVTab=&(*ppVTab)->pNext){ + for(ppVTab=&p->u.vtab.p; *ppVTab; ppVTab=&(*ppVTab)->pNext){ if( (*ppVTab)->db==db ){ VTable *pVTab = *ppVTab; *ppVTab = pVTab->pNext; @@ -142298,37 +147564,41 @@ SQLITE_PRIVATE void sqlite3VtabUnlockList(sqlite3 *db){ ** database connection. */ SQLITE_PRIVATE void sqlite3VtabClear(sqlite3 *db, Table *p){ + assert( IsVirtual(p) ); if( !db || db->pnBytesFreed==0 ) vtabDisconnectAll(0, p); - if( p->azModuleArg ){ + if( p->u.vtab.azArg ){ int i; - for(i=0; inModuleArg; i++){ - if( i!=1 ) sqlite3DbFree(db, p->azModuleArg[i]); + for(i=0; iu.vtab.nArg; i++){ + if( i!=1 ) sqlite3DbFree(db, p->u.vtab.azArg[i]); } - sqlite3DbFree(db, p->azModuleArg); + sqlite3DbFree(db, p->u.vtab.azArg); } } /* -** Add a new module argument to pTable->azModuleArg[]. +** Add a new module argument to pTable->u.vtab.azArg[]. ** The string is not copied - the pointer is stored. The ** string will be freed automatically when the table is ** deleted. */ static void addModuleArgument(Parse *pParse, Table *pTable, char *zArg){ - sqlite3_int64 nBytes = sizeof(char *)*(2+pTable->nModuleArg); + sqlite3_int64 nBytes; char **azModuleArg; sqlite3 *db = pParse->db; - if( pTable->nModuleArg+3>=db->aLimit[SQLITE_LIMIT_COLUMN] ){ + + assert( IsVirtual(pTable) ); + nBytes = sizeof(char *)*(2+pTable->u.vtab.nArg); + if( pTable->u.vtab.nArg+3>=db->aLimit[SQLITE_LIMIT_COLUMN] ){ sqlite3ErrorMsg(pParse, "too many columns on %s", pTable->zName); } - azModuleArg = sqlite3DbRealloc(db, pTable->azModuleArg, nBytes); + azModuleArg = sqlite3DbRealloc(db, pTable->u.vtab.azArg, nBytes); if( azModuleArg==0 ){ sqlite3DbFree(db, zArg); }else{ - int i = pTable->nModuleArg++; + int i = pTable->u.vtab.nArg++; azModuleArg[i] = zArg; azModuleArg[i+1] = 0; - pTable->azModuleArg = azModuleArg; + pTable->u.vtab.azArg = azModuleArg; } } @@ -142351,10 +147621,11 @@ SQLITE_PRIVATE void sqlite3VtabBeginParse( pTable = pParse->pNewTable; if( pTable==0 ) return; assert( 0==pTable->pIndex ); + pTable->eTabType = TABTYP_VTAB; db = pParse->db; - assert( pTable->nModuleArg==0 ); + assert( pTable->u.vtab.nArg==0 ); addModuleArgument(pParse, pTable, sqlite3NameFromToken(db, pModuleName)); addModuleArgument(pParse, pTable, 0); addModuleArgument(pParse, pTable, sqlite3DbStrDup(db, pTable->zName)); @@ -142371,11 +147642,11 @@ SQLITE_PRIVATE void sqlite3VtabBeginParse( ** sqlite_schema table, has already been made by sqlite3StartTable(). ** The second call, to obtain permission to create the table, is made now. */ - if( pTable->azModuleArg ){ + if( pTable->u.vtab.azArg ){ int iDb = sqlite3SchemaToIndex(db, pTable->pSchema); assert( iDb>=0 ); /* The database the table is being created in */ sqlite3AuthCheck(pParse, SQLITE_CREATE_VTABLE, pTable->zName, - pTable->azModuleArg[0], pParse->db->aDb[iDb].zDbSName); + pTable->u.vtab.azArg[0], pParse->db->aDb[iDb].zDbSName); } #endif } @@ -142403,9 +147674,10 @@ SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ sqlite3 *db = pParse->db; /* The database connection */ if( pTab==0 ) return; + assert( IsVirtual(pTab) ); addArgumentToVtab(pParse); pParse->sArg.z = 0; - if( pTab->nModuleArg<1 ) return; + if( pTab->u.vtab.nArg<1 ) return; /* If the CREATE VIRTUAL TABLE statement is being entered for the ** first time (in other words if the virtual table is actually being @@ -142438,7 +147710,7 @@ SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ */ iDb = sqlite3SchemaToIndex(db, pTab->pSchema); sqlite3NestedParse(pParse, - "UPDATE %Q." DFLT_SCHEMA_TABLE " " + "UPDATE %Q." LEGACY_SCHEMA_TABLE " " "SET type='table', name=%Q, tbl_name=%Q, rootpage=0, sql=%Q " "WHERE rowid=#%d", db->aDb[iDb].zDbSName, @@ -142458,18 +147730,14 @@ SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ iReg = ++pParse->nMem; sqlite3VdbeLoadString(v, iReg, pTab->zName); sqlite3VdbeAddOp2(v, OP_VCreate, iDb, iReg); - } - - /* If we are rereading the sqlite_schema table create the in-memory - ** record of the table. The xConnect() method is not called until - ** the first time the virtual table is used in an SQL statement. This - ** allows a schema that contains virtual tables to be loaded before - ** the required virtual table implementations are registered. */ - else { + }else{ + /* If we are rereading the sqlite_schema table create the in-memory + ** record of the table. */ Table *pOld; Schema *pSchema = pTab->pSchema; const char *zName = pTab->zName; - assert( sqlite3SchemaMutexHeld(db, 0, pSchema) ); + assert( zName!=0 ); + sqlite3MarkAllShadowTablesOf(db, pTab); pOld = sqlite3HashInsert(&pSchema->tblHash, zName, pTab); if( pOld ){ sqlite3OomFault(db); @@ -142520,13 +147788,16 @@ static int vtabCallConstructor( VtabCtx sCtx; VTable *pVTable; int rc; - const char *const*azArg = (const char *const*)pTab->azModuleArg; - int nArg = pTab->nModuleArg; + const char *const*azArg; + int nArg = pTab->u.vtab.nArg; char *zErr = 0; char *zModuleName; int iDb; VtabCtx *pCtx; + assert( IsVirtual(pTab) ); + azArg = (const char *const*)pTab->u.vtab.azArg; + /* Check that the virtual-table is not already being initialized */ for(pCtx=db->pVtabCtx; pCtx; pCtx=pCtx->pPrior){ if( pCtx->pTab==pTab ){ @@ -142553,7 +147824,7 @@ static int vtabCallConstructor( pVTable->eVtabRisk = SQLITE_VTABRISK_Normal; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - pTab->azModuleArg[1] = db->aDb[iDb].zDbSName; + pTab->u.vtab.azArg[1] = db->aDb[iDb].zDbSName; /* Invoke the virtual table constructor */ assert( &db->pVtabCtx ); @@ -142592,12 +147863,12 @@ static int vtabCallConstructor( int iCol; u16 oooHidden = 0; /* If everything went according to plan, link the new VTable structure - ** into the linked list headed by pTab->pVTable. Then loop through the + ** into the linked list headed by pTab->u.vtab.p. Then loop through the ** columns of the table to see if any of them contain the token "hidden". ** If so, set the Column COLFLAG_HIDDEN flag and remove the token from ** the type string. */ - pVTable->pNext = pTab->pVTable; - pTab->pVTable = pVTable; + pVTable->pNext = pTab->u.vtab.p; + pTab->u.vtab.p = pVTable; for(iCol=0; iColnCol; iCol++){ char *zType = sqlite3ColumnType(&pTab->aCol[iCol], ""); @@ -142650,16 +147921,17 @@ SQLITE_PRIVATE int sqlite3VtabCallConnect(Parse *pParse, Table *pTab){ int rc; assert( pTab ); - if( !IsVirtual(pTab) || sqlite3GetVTable(db, pTab) ){ + assert( IsVirtual(pTab) ); + if( sqlite3GetVTable(db, pTab) ){ return SQLITE_OK; } /* Locate the required virtual table module */ - zMod = pTab->azModuleArg[0]; + zMod = pTab->u.vtab.azArg[0]; pMod = (Module*)sqlite3HashFind(&db->aModule, zMod); if( !pMod ){ - const char *zModule = pTab->azModuleArg[0]; + const char *zModule = pTab->u.vtab.azArg[0]; sqlite3ErrorMsg(pParse, "no such module: %s", zModule); rc = SQLITE_ERROR; }else{ @@ -142722,10 +147994,10 @@ SQLITE_PRIVATE int sqlite3VtabCallCreate(sqlite3 *db, int iDb, const char *zTab, const char *zMod; pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName); - assert( pTab && IsVirtual(pTab) && !pTab->pVTable ); + assert( pTab && IsVirtual(pTab) && !pTab->u.vtab.p ); /* Locate the required virtual table module */ - zMod = pTab->azModuleArg[0]; + zMod = pTab->u.vtab.azArg[0]; pMod = (Module*)sqlite3HashFind(&db->aModule, zMod); /* If the module has been registered and includes a Create method, @@ -142760,8 +148032,8 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ VtabCtx *pCtx; int rc = SQLITE_OK; Table *pTab; - char *zErr = 0; Parse sParse; + int initBusy; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) || zCreateTable==0 ){ @@ -142778,20 +148050,27 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ pTab = pCtx->pTab; assert( IsVirtual(pTab) ); - memset(&sParse, 0, sizeof(sParse)); + sqlite3ParseObjectInit(&sParse, db); sParse.eParseMode = PARSE_MODE_DECLARE_VTAB; - sParse.db = db; + sParse.disableTriggers = 1; + /* We should never be able to reach this point while loading the + ** schema. Nevertheless, defend against that (turn off db->init.busy) + ** in case a bug arises. */ + assert( db->init.busy==0 ); + initBusy = db->init.busy; + db->init.busy = 0; sParse.nQueryLoop = 1; - if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable, &zErr) - && sParse.pNewTable - && !db->mallocFailed - && !sParse.pNewTable->pSelect - && !IsVirtual(sParse.pNewTable) + if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable) + && ALWAYS(sParse.pNewTable!=0) + && ALWAYS(!db->mallocFailed) + && IsOrdinaryTable(sParse.pNewTable) ){ + assert( sParse.zErrMsg==0 ); if( !pTab->aCol ){ Table *pNew = sParse.pNewTable; Index *pIdx; pTab->aCol = pNew->aCol; + sqlite3ExprListDelete(db, pNew->u.tab.pDfltList); pTab->nNVCol = pTab->nCol = pNew->nCol; pTab->tabFlags |= pNew->tabFlags & (TF_WithoutRowid|TF_NoVisibleRowid); pNew->nCol = 0; @@ -142816,8 +148095,9 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ } pCtx->bDeclared = 1; }else{ - sqlite3ErrorWithMsg(db, SQLITE_ERROR, (zErr ? "%s" : 0), zErr); - sqlite3DbFree(db, zErr); + sqlite3ErrorWithMsg(db, SQLITE_ERROR, + (sParse.zErrMsg ? "%s" : 0), sParse.zErrMsg); + sqlite3DbFree(db, sParse.zErrMsg); rc = SQLITE_ERROR; } sParse.eParseMode = PARSE_MODE_NORMAL; @@ -142826,7 +148106,8 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ sqlite3VdbeFinalize(sParse.pVdbe); } sqlite3DeleteTable(db, sParse.pNewTable); - sqlite3ParserReset(&sParse); + sqlite3ParseObjectReset(&sParse); + db->init.busy = initBusy; assert( (rc&0xff)==rc ); rc = sqlite3ApiExit(db, rc); @@ -142846,10 +148127,13 @@ SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab Table *pTab; pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName); - if( pTab!=0 && ALWAYS(pTab->pVTable!=0) ){ + if( ALWAYS(pTab!=0) + && ALWAYS(IsVirtual(pTab)) + && ALWAYS(pTab->u.vtab.p!=0) + ){ VTable *p; int (*xDestroy)(sqlite3_vtab *); - for(p=pTab->pVTable; p; p=p->pNext){ + for(p=pTab->u.vtab.p; p; p=p->pNext){ assert( p->pVtab ); if( p->pVtab->nRef>0 ){ return SQLITE_LOCKED; @@ -142863,9 +148147,9 @@ SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab rc = xDestroy(p->pVtab); /* Remove the sqlite3_vtab* from the aVTrans[] array, if applicable */ if( rc==SQLITE_OK ){ - assert( pTab->pVTable==p && p->pNext==0 ); + assert( pTab->u.vtab.p==p && p->pNext==0 ); p->pVtab = 0; - pTab->pVTable = 0; + pTab->u.vtab.p = 0; sqlite3VtabUnlock(p); } sqlite3DeleteTable(db, pTab); @@ -143079,6 +148363,7 @@ SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction( /* Check to see the left operand is a column in a virtual table */ if( NEVER(pExpr==0) ) return pDef; if( pExpr->op!=TK_COLUMN ) return pDef; + assert( ExprUseYTab(pExpr) ); pTab = pExpr->y.pTab; if( pTab==0 ) return pDef; if( !IsVirtual(pTab) ) return pDef; @@ -143153,8 +148438,9 @@ SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){ /* ** Check to see if virtual table module pMod can be have an eponymous ** virtual table instance. If it can, create one if one does not already -** exist. Return non-zero if the eponymous virtual table instance exists -** when this routine returns, and return zero if it does not exist. +** exist. Return non-zero if either the eponymous virtual table instance +** exists when this routine returns or if an attempt to create it failed +** and an error message was left in pParse. ** ** An eponymous virtual table instance is one that is named after its ** module, and more importantly, does not require a CREATE VIRTUAL TABLE @@ -143181,9 +148467,11 @@ SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){ } pMod->pEpoTab = pTab; pTab->nTabRef = 1; + pTab->eTabType = TABTYP_VTAB; pTab->pSchema = db->aDb[0].pSchema; - assert( pTab->nModuleArg==0 ); + assert( pTab->u.vtab.nArg==0 ); pTab->iPKey = -1; + pTab->tabFlags |= TF_Eponymous; addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName)); addModuleArgument(pParse, pTab, 0); addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName)); @@ -143192,7 +148480,6 @@ SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){ sqlite3ErrorMsg(pParse, "%s", zErr); sqlite3DbFree(db, zErr); sqlite3VtabEponymousTableClear(db, pMod); - return 0; } return 1; } @@ -143339,6 +148626,28 @@ typedef struct WhereLoopBuilder WhereLoopBuilder; typedef struct WhereScan WhereScan; typedef struct WhereOrCost WhereOrCost; typedef struct WhereOrSet WhereOrSet; +typedef struct WhereMemBlock WhereMemBlock; +typedef struct WhereRightJoin WhereRightJoin; + +/* +** This object is a header on a block of allocated memory that will be +** automatically freed when its WInfo oject is destructed. +*/ +struct WhereMemBlock { + WhereMemBlock *pNext; /* Next block in the chain */ + u64 sz; /* Bytes of space */ +}; + +/* +** Extra information attached to a WhereLevel that is a RIGHT JOIN. +*/ +struct WhereRightJoin { + int iMatch; /* Cursor used to determine prior matched rows */ + int regBloom; /* Bloom filter for iRJMatch */ + int regReturn; /* Return register for the interior subroutine */ + int addrSubrtn; /* Starting address for the interior subroutine */ + int endSubrtn; /* The last opcode in the interior subroutine */ +}; /* ** This object contains information needed to implement a single nested @@ -143371,6 +148680,8 @@ struct WhereLevel { u32 iLikeRepCntr; /* LIKE range processing counter register (times 2) */ int addrLikeRep; /* LIKE range processing address */ #endif + int regFilter; /* Bloom filter */ + WhereRightJoin *pRJ; /* Extra information for RIGHT JOIN */ u8 iFrom; /* Which entry in the FROM clause */ u8 op, p3, p5; /* Opcode, P3 & P5 of the opcode that ends the loop */ int p1, p2; /* Operands of the opcode used to end the loop */ @@ -143385,7 +148696,7 @@ struct WhereLevel { u8 eEndLoopOp; /* IN Loop terminator. OP_Next or OP_Prev */ } *aInLoop; /* Information about each nested IN operator */ } in; /* Used when pWLoop->wsFlags&WHERE_IN_ABLE */ - Index *pCovidx; /* Possible covering index for WHERE_MULTI_OR */ + Index *pCoveringIdx; /* Possible covering index for WHERE_MULTI_OR */ } u; struct WhereLoop *pWLoop; /* The selected WhereLoop object */ Bitmask notReady; /* FROM entries not usable at this level */ @@ -143429,10 +148740,12 @@ struct WhereLoop { } btree; struct { /* Information for virtual tables */ int idxNum; /* Index number */ - u8 needFree; /* True if sqlite3_free(idxStr) is needed */ + u32 needFree : 1; /* True if sqlite3_free(idxStr) is needed */ + u32 bOmitOffset : 1; /* True to let virtual table handle offset */ i8 isOrdered; /* True if satisfies ORDER BY */ u16 omitMask; /* Terms that may be omitted */ char *idxStr; /* Index identifier string */ + u32 mHandleIn; /* Terms to handle as IN(...) instead of == */ } vtab; } u; u32 wsFlags; /* WHERE_* flags describing the plan */ @@ -143576,7 +148889,7 @@ struct WhereTerm { #define TERM_COPIED 0x0008 /* Has a child */ #define TERM_ORINFO 0x0010 /* Need to free the WhereTerm.u.pOrInfo object */ #define TERM_ANDINFO 0x0020 /* Need to free the WhereTerm.u.pAndInfo obj */ -#define TERM_OR_OK 0x0040 /* Used during OR-clause processing */ +#define TERM_OK 0x0040 /* Used during OR-clause processing */ #define TERM_VNULL 0x0080 /* Manufactured x>NULL or x<=NULL term */ #define TERM_LIKEOPT 0x0100 /* Virtual terms from the LIKE optimization */ #define TERM_LIKECOND 0x0200 /* Conditionally this LIKE operator term */ @@ -143589,6 +148902,7 @@ struct WhereTerm { #else # define TERM_HIGHTRUTH 0 /* Only used with STAT4 */ #endif +#define TERM_SLICE 0x8000 /* One slice of a row-value/vector comparison */ /* ** An instance of the WhereScan object is used as an iterator for locating @@ -143599,11 +148913,11 @@ struct WhereScan { WhereClause *pWC; /* WhereClause currently being scanned */ const char *zCollName; /* Required collating sequence, if not NULL */ Expr *pIdxExpr; /* Search for this index expression */ - char idxaff; /* Must match this affinity, if zCollName!=NULL */ - unsigned char nEquiv; /* Number of entries in aEquiv[] */ - unsigned char iEquiv; /* Next unused slot in aEquiv[] */ - u32 opMask; /* Acceptable operators */ int k; /* Resume scanning at this->pWC->a[this->k] */ + u32 opMask; /* Acceptable operators */ + char idxaff; /* Must match this affinity, if zCollName!=NULL */ + unsigned char iEquiv; /* Current slot in aiCur[] and aiColumn[] */ + unsigned char nEquiv; /* Number of entries in aiCur[] and aiColumn[] */ int aiCur[11]; /* Cursors in the equivalence class */ i16 aiColumn[11]; /* Corresponding column number in the eq-class */ }; @@ -143627,6 +148941,7 @@ struct WhereClause { u8 hasOr; /* True if any a[].eOperator is WO_OR */ int nTerm; /* Number of terms */ int nSlot; /* Number of entries in a[] */ + int nBase; /* Number of terms through the last non-Virtual */ WhereTerm *a; /* Each a[] describes a term of the WHERE cluase */ #if defined(SQLITE_SMALL_STACK) WhereTerm aStatic[1]; /* Initial static space for a[] */ @@ -143684,11 +148999,6 @@ struct WhereMaskSet { int ix[BMS]; /* Cursor assigned to each bit */ }; -/* -** Initialize a WhereMaskSet object -*/ -#define initMaskSet(P) (P)->n=0 - /* ** This object is a convenience wrapper holding all information needed ** to construct WhereLoop objects for a particular query. @@ -143696,7 +149006,6 @@ struct WhereMaskSet { struct WhereLoopBuilder { WhereInfo *pWInfo; /* Information about this WHERE */ WhereClause *pWC; /* WHERE clause terms */ - ExprList *pOrderBy; /* ORDER BY clause */ WhereLoop *pNew; /* Template WhereLoop */ WhereOrSet *pOrSet; /* Record best loops here, if not NULL */ #ifdef SQLITE_ENABLE_STAT4 @@ -143764,6 +149073,9 @@ struct WhereInfo { ExprList *pOrderBy; /* The ORDER BY clause or NULL */ ExprList *pResultSet; /* Result set of the query */ Expr *pWhere; /* The complete WHERE clause */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + Select *pLimit; /* Used to access LIMIT expr/registers for vtabs */ +#endif int aiCurOnePass[2]; /* OP_OpenWrite cursors for the ONEPASS opt */ int iContinue; /* Jump here to continue with next record */ int iBreak; /* Jump here to break out of the loop */ @@ -143783,6 +149095,7 @@ struct WhereInfo { int iEndWhere; /* End of the WHERE clause itself */ WhereLoop *pLoops; /* List of all WhereLoop objects */ WhereExprMod *pExprMods; /* Expression modifications */ + WhereMemBlock *pMemToFree;/* Memory to free when this object destroyed */ Bitmask revMask; /* Mask of ORDER BY terms that need reversing */ WhereClause sWC; /* Decomposition of the WHERE clause */ WhereMaskSet sMaskSet; /* Map cursor numbers to bitmasks */ @@ -143808,6 +149121,8 @@ SQLITE_PRIVATE WhereTerm *sqlite3WhereFindTerm( u32 op, /* Mask of WO_xx values describing operator */ Index *pIdx /* Must be compatible with this index, if not NULL */ ); +SQLITE_PRIVATE void *sqlite3WhereMalloc(WhereInfo *pWInfo, u64 nByte); +SQLITE_PRIVATE void *sqlite3WhereRealloc(WhereInfo *pWInfo, void *pOld, u64 nByte); /* wherecode.c: */ #ifndef SQLITE_OMIT_EXPLAIN @@ -143817,8 +149132,14 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan( WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ ); +SQLITE_PRIVATE int sqlite3WhereExplainBloomFilter( + const Parse *pParse, /* Parse context */ + const WhereInfo *pWInfo, /* WHERE clause */ + const WhereLevel *pLevel /* Bloom filter on this level */ +); #else # define sqlite3WhereExplainOneScan(u,v,w,x) 0 +# define sqlite3WhereExplainBloomFilter(u,v,w) 0 #endif /* SQLITE_OMIT_EXPLAIN */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS SQLITE_PRIVATE void sqlite3WhereAddScanStatus( @@ -143838,11 +149159,17 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( WhereLevel *pLevel, /* The current level pointer */ Bitmask notReady /* Which tables are currently available */ ); +SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3WhereRightJoinLoop( + WhereInfo *pWInfo, + int iLevel, + WhereLevel *pLevel +); /* whereexpr.c: */ SQLITE_PRIVATE void sqlite3WhereClauseInit(WhereClause*,WhereInfo*); SQLITE_PRIVATE void sqlite3WhereClauseClear(WhereClause*); SQLITE_PRIVATE void sqlite3WhereSplit(WhereClause*,Expr*,u8); +SQLITE_PRIVATE void sqlite3WhereAddLimit(WhereClause*, Select*); SQLITE_PRIVATE Bitmask sqlite3WhereExprUsage(WhereMaskSet*, Expr*); SQLITE_PRIVATE Bitmask sqlite3WhereExprUsageNN(WhereMaskSet*, Expr*); SQLITE_PRIVATE Bitmask sqlite3WhereExprListUsage(WhereMaskSet*, ExprList*); @@ -143879,8 +149206,9 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*); #define WO_AND 0x0400 /* Two or more AND-connected terms */ #define WO_EQUIV 0x0800 /* Of the form A==B, both columns */ #define WO_NOOP 0x1000 /* This term does not restrict search space */ +#define WO_ROWVAL 0x2000 /* A row-value term */ -#define WO_ALL 0x1fff /* Mask of all possible WO_* values */ +#define WO_ALL 0x3fff /* Mask of all possible WO_* values */ #define WO_SINGLE 0x01ff /* Mask of all non-compound WO_* values */ /* @@ -143910,6 +149238,10 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*); #define WHERE_IN_EARLYOUT 0x00040000 /* Perhaps quit IN loops early */ #define WHERE_BIGNULL_SORT 0x00080000 /* Column nEq of index is BIGNULL */ #define WHERE_IN_SEEKSCAN 0x00100000 /* Seek-scan optimization for IN */ +#define WHERE_TRANSCONS 0x00200000 /* Uses a transitive constraint */ +#define WHERE_BLOOMFILTER 0x00400000 /* Consider using a Bloom-filter */ +#define WHERE_SELFCULL 0x00800000 /* nOut reduced by extra WHERE terms */ +#define WHERE_OMIT_OFFSET 0x01000000 /* Set offset counter to zero */ #endif /* !defined(SQLITE_WHEREINT_H) */ @@ -143925,7 +149257,7 @@ static const char *explainIndexColumnName(Index *pIdx, int i){ i = pIdx->aiColumn[i]; if( i==XN_EXPR ) return ""; if( i==XN_ROWID ) return "rowid"; - return pIdx->pTable->aCol[i].zName; + return pIdx->pTable->aCol[i].zCnName; } /* @@ -144044,16 +149376,8 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan( || (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX)); sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH); - sqlite3_str_appendall(&str, isSearch ? "SEARCH" : "SCAN"); - if( pItem->pSelect ){ - sqlite3_str_appendf(&str, " SUBQUERY %u", pItem->pSelect->selId); - }else{ - sqlite3_str_appendf(&str, " TABLE %s", pItem->zName); - } - - if( pItem->zAlias ){ - sqlite3_str_appendf(&str, " AS %s", pItem->zAlias); - } + str.printfFlags = SQLITE_PRINTF_INTERNAL; + sqlite3_str_appendf(&str, "%s %S", isSearch ? "SEARCH" : "SCAN", pItem); if( (flags & (WHERE_IPK|WHERE_VIRTUALTABLE))==0 ){ const char *zFmt = 0; Index *pIdx; @@ -144080,19 +149404,27 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan( explainIndexRange(&str, pLoop); } }else if( (flags & WHERE_IPK)!=0 && (flags & WHERE_CONSTRAINT)!=0 ){ - const char *zRangeOp; + char cRangeOp; +#if 0 /* Better output, but breaks many tests */ + const Table *pTab = pItem->pTab; + const char *zRowid = pTab->iPKey>=0 ? pTab->aCol[pTab->iPKey].zCnName: + "rowid"; +#else + const char *zRowid = "rowid"; +#endif + sqlite3_str_appendf(&str, " USING INTEGER PRIMARY KEY (%s", zRowid); if( flags&(WHERE_COLUMN_EQ|WHERE_COLUMN_IN) ){ - zRangeOp = "="; + cRangeOp = '='; }else if( (flags&WHERE_BOTH_LIMIT)==WHERE_BOTH_LIMIT ){ - zRangeOp = ">? AND rowid<"; + sqlite3_str_appendf(&str, ">? AND %s", zRowid); + cRangeOp = '<'; }else if( flags&WHERE_BTM_LIMIT ){ - zRangeOp = ">"; + cRangeOp = '>'; }else{ assert( flags&WHERE_TOP_LIMIT); - zRangeOp = "<"; + cRangeOp = '<'; } - sqlite3_str_appendf(&str, - " USING INTEGER PRIMARY KEY (rowid%s?)",zRangeOp); + sqlite3_str_appendf(&str, "%c?)", cRangeOp); } #ifndef SQLITE_OMIT_VIRTUALTABLE else if( (flags & WHERE_VIRTUALTABLE)!=0 ){ @@ -144100,6 +149432,9 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan( pLoop->u.vtab.idxNum, pLoop->u.vtab.idxStr); } #endif + if( pItem->fg.jointype & JT_LEFT ){ + sqlite3_str_appendf(&str, " LEFT-JOIN"); + } #ifdef SQLITE_EXPLAIN_ESTIMATED_ROWS if( pLoop->nOut>=10 ){ sqlite3_str_appendf(&str, " (~%llu rows)", @@ -144115,6 +149450,56 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan( } return ret; } + +/* +** Add a single OP_Explain opcode that describes a Bloom filter. +** +** Or if not processing EXPLAIN QUERY PLAN and not in a SQLITE_DEBUG and/or +** SQLITE_ENABLE_STMT_SCANSTATUS build, then OP_Explain opcodes are not +** required and this routine is a no-op. +** +** If an OP_Explain opcode is added to the VM, its address is returned. +** Otherwise, if no OP_Explain is coded, zero is returned. +*/ +SQLITE_PRIVATE int sqlite3WhereExplainBloomFilter( + const Parse *pParse, /* Parse context */ + const WhereInfo *pWInfo, /* WHERE clause */ + const WhereLevel *pLevel /* Bloom filter on this level */ +){ + int ret = 0; + SrcItem *pItem = &pWInfo->pTabList->a[pLevel->iFrom]; + Vdbe *v = pParse->pVdbe; /* VM being constructed */ + sqlite3 *db = pParse->db; /* Database handle */ + char *zMsg; /* Text to add to EQP output */ + int i; /* Loop counter */ + WhereLoop *pLoop; /* The where loop */ + StrAccum str; /* EQP output string */ + char zBuf[100]; /* Initial space for EQP output string */ + + sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH); + str.printfFlags = SQLITE_PRINTF_INTERNAL; + sqlite3_str_appendf(&str, "BLOOM FILTER ON %S (", pItem); + pLoop = pLevel->pWLoop; + if( pLoop->wsFlags & WHERE_IPK ){ + const Table *pTab = pItem->pTab; + if( pTab->iPKey>=0 ){ + sqlite3_str_appendf(&str, "%s=?", pTab->aCol[pTab->iPKey].zCnName); + }else{ + sqlite3_str_appendf(&str, "rowid=?"); + } + }else{ + for(i=pLoop->nSkip; iu.btree.nEq; i++){ + const char *z = explainIndexColumnName(pLoop->u.btree.pIndex, i); + if( i>pLoop->nSkip ) sqlite3_str_append(&str, " AND ", 5); + sqlite3_str_appendf(&str, "%s=?", z); + } + } + sqlite3_str_append(&str, ")", 1); + zMsg = sqlite3StrAccumFinish(&str); + ret = sqlite3VdbeAddOp4(v, OP_Explain, sqlite3VdbeCurrentAddr(v), + pParse->addrExplain, 0, zMsg,P4_DYNAMIC); + return ret; +} #endif /* SQLITE_OMIT_EXPLAIN */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS @@ -144193,7 +149578,7 @@ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){ int nLoop = 0; assert( pTerm!=0 ); while( (pTerm->wtFlags & TERM_CODED)==0 - && (pLevel->iLeftJoin==0 || ExprHasProperty(pTerm->pExpr, EP_FromJoin)) + && (pLevel->iLeftJoin==0 || ExprHasProperty(pTerm->pExpr, EP_OuterON)) && (pLevel->notReady & pTerm->prereqAll)==0 ){ if( nLoop && (pTerm->wtFlags & TERM_LIKE)!=0 ){ @@ -144201,6 +149586,12 @@ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){ }else{ pTerm->wtFlags |= TERM_CODED; } +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x20000 ){ + sqlite3DebugPrintf("DISABLE-"); + sqlite3WhereTermPrint(pTerm, (int)(pTerm - (pTerm->pWC->a))); + } +#endif if( pTerm->iParent<0 ) break; pTerm = &pTerm->pWC->a[pTerm->iParent]; assert( pTerm!=0 ); @@ -144314,16 +149705,23 @@ static Expr *removeUnindexableInClauseTerms( Expr *pNew; pNew = sqlite3ExprDup(db, pX, 0); if( db->mallocFailed==0 ){ - ExprList *pOrigRhs = pNew->x.pSelect->pEList; /* Original unmodified RHS */ - ExprList *pOrigLhs = pNew->pLeft->x.pList; /* Original unmodified LHS */ + ExprList *pOrigRhs; /* Original unmodified RHS */ + ExprList *pOrigLhs; /* Original unmodified LHS */ ExprList *pRhs = 0; /* New RHS after modifications */ ExprList *pLhs = 0; /* New LHS after mods */ int i; /* Loop counter */ Select *pSelect; /* Pointer to the SELECT on the RHS */ + assert( ExprUseXSelect(pNew) ); + pOrigRhs = pNew->x.pSelect->pEList; + assert( pNew->pLeft!=0 ); + assert( ExprUseXList(pNew->pLeft) ); + pOrigLhs = pNew->pLeft->x.pList; for(i=iEq; inLTerm; i++){ if( pLoop->aLTerm[i]->pExpr==pX ){ - int iField = pLoop->aLTerm[i]->u.x.iField - 1; + int iField; + assert( (pLoop->aLTerm[i]->eOperator & (WO_OR|WO_AND))==0 ); + iField = pLoop->aLTerm[i]->u.x.iField - 1; if( pOrigRhs->a[iField].pExpr==0 ) continue; /* Duplicate PK column */ pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr); pOrigRhs->a[iField].pExpr = 0; @@ -144438,19 +149836,25 @@ static int codeEqualityTerm( } iTab = 0; - if( (pX->flags & EP_xIsSelect)==0 || pX->x.pSelect->pEList->nExpr==1 ){ + if( !ExprUseXSelect(pX) || pX->x.pSelect->pEList->nExpr==1 ){ eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0, &iTab); }else{ - sqlite3 *db = pParse->db; - pX = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX); - - if( !db->mallocFailed ){ - aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq); + Expr *pExpr = pTerm->pExpr; + if( pExpr->iTable==0 || !ExprHasProperty(pExpr, EP_Subrtn) ){ + sqlite3 *db = pParse->db; + pX = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX); + if( !db->mallocFailed ){ + aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq); + eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap,&iTab); + pExpr->iTable = iTab; + } + sqlite3ExprDelete(db, pX); + }else{ + int n = sqlite3ExprVectorSize(pX->pLeft); + aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*MAX(nEq,n)); eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap, &iTab); - pTerm->pExpr->iTable = iTab; } - sqlite3ExprDelete(db, pX); - pX = pTerm->pExpr; + pX = pExpr; } if( eType==IN_INDEX_INDEX_DESC ){ @@ -144460,8 +149864,8 @@ static int codeEqualityTerm( sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iTab, 0); VdbeCoverageIf(v, bRev); VdbeCoverageIf(v, !bRev); - assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 ); + assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 ); pLoop->wsFlags |= WHERE_IN_ABLE; if( pLevel->u.in.nIn==0 ){ pLevel->addrNxt = sqlite3VdbeMakeLabel(pParse); @@ -144473,8 +149877,9 @@ static int codeEqualityTerm( i = pLevel->u.in.nIn; pLevel->u.in.nIn += nEq; pLevel->u.in.aInLoop = - sqlite3DbReallocOrFree(pParse->db, pLevel->u.in.aInLoop, - sizeof(pLevel->u.in.aInLoop[0])*pLevel->u.in.nIn); + sqlite3WhereRealloc(pTerm->pWC->pWInfo, + pLevel->u.in.aInLoop, + sizeof(pLevel->u.in.aInLoop[0])*pLevel->u.in.nIn); pIn = pLevel->u.in.aInLoop; if( pIn ){ int iMap = 0; /* Index in aiMap[] */ @@ -144518,7 +149923,22 @@ static int codeEqualityTerm( sqlite3DbFree(pParse->db, aiMap); #endif } - disableTerm(pLevel, pTerm); + + /* As an optimization, try to disable the WHERE clause term that is + ** driving the index as it will always be true. The correct answer is + ** obtained regardless, but we might get the answer with fewer CPU cycles + ** by omitting the term. + ** + ** But do not disable the term unless we are certain that the term is + ** not a transitive constraint. For an example of where that does not + ** work, see https://sqlite.org/forum/forumpost/eb8613976a (2021-05-04) + */ + if( (pLevel->pWLoop->wsFlags & WHERE_TRANSCONS)==0 + || (pTerm->eOperator & WO_EQUIV)==0 + ){ + disableTerm(pLevel, pTerm); + } + return iReg; } @@ -144604,11 +150024,13 @@ static int codeAllEqualityTerms( if( nSkip ){ int iIdxCur = pLevel->iIdxCur; + sqlite3VdbeAddOp3(v, OP_Null, 0, regBase, regBase+nSkip-1); sqlite3VdbeAddOp1(v, (bRev?OP_Last:OP_Rewind), iIdxCur); VdbeCoverageIf(v, bRev==0); VdbeCoverageIf(v, bRev!=0); VdbeComment((v, "begin skip-scan on %s", pIdx->zName)); j = sqlite3VdbeAddOp0(v, OP_Goto); + assert( pLevel->addrSkip==0 ); pLevel->addrSkip = sqlite3VdbeAddOp4Int(v, (bRev?OP_SeekLT:OP_SeekGT), iIdxCur, 0, regBase, nSkip); VdbeCoverageIf(v, bRev==0); @@ -144638,9 +150060,12 @@ static int codeAllEqualityTerms( sqlite3ReleaseTempReg(pParse, regBase); regBase = r1; }else{ - sqlite3VdbeAddOp2(v, OP_SCopy, r1, regBase+j); + sqlite3VdbeAddOp2(v, OP_Copy, r1, regBase+j); } } + } + for(j=nSkip; jaLTerm[j]; if( pTerm->eOperator & WO_IN ){ if( pTerm->pExpr->flags & EP_xIsSelect ){ /* No affinity ever needs to be (or should be) applied to a value @@ -144655,7 +150080,8 @@ static int codeAllEqualityTerms( sqlite3VdbeAddOp2(v, OP_IsNull, regBase+j, pLevel->addrBrk); VdbeCoverage(v); } - if( zAff ){ + if( pParse->nErr==0 ){ + assert( pParse->db->mallocFailed==0 ); if( sqlite3CompareAffinity(pRight, zAff[j])==SQLITE_AFF_BLOB ){ zAff[j] = SQLITE_AFF_BLOB; } @@ -144845,7 +150271,7 @@ static void codeCursorHint( sWalker.pParse = pParse; sWalker.u.pCCurHint = &sHint; pWC = &pWInfo->sWC; - for(i=0; inTerm; i++){ + for(i=0; inBase; i++){ pTerm = &pWC->a[i]; if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; if( pTerm->prereqAll & pLevel->notReady ) continue; @@ -144874,8 +150300,8 @@ static void codeCursorHint( */ if( pTabItem->fg.jointype & JT_LEFT ){ Expr *pExpr = pTerm->pExpr; - if( !ExprHasProperty(pExpr, EP_FromJoin) - || pExpr->iRightJoinTable!=pTabItem->iCursor + if( !ExprHasProperty(pExpr, EP_OuterON) + || pExpr->w.iJoin!=pTabItem->iCursor ){ sWalker.eCode = 0; sWalker.xExprCallback = codeCursorHintIsOrFunction; @@ -144883,7 +150309,7 @@ static void codeCursorHint( if( sWalker.eCode ) continue; } }else{ - if( ExprHasProperty(pTerm->pExpr, EP_FromJoin) ) continue; + if( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) continue; } /* All terms in pWLoop->aLTerm[] except pEndRange are used to initialize @@ -144931,13 +150357,21 @@ static void codeCursorHint( ** ** OP_DeferredSeek $iCur $iRowid ** +** Which causes a seek on $iCur to the row with rowid $iRowid. +** ** However, if the scan currently being coded is a branch of an OR-loop and -** the statement currently being coded is a SELECT, then P3 of OP_DeferredSeek -** is set to iIdxCur and P4 is set to point to an array of integers -** containing one entry for each column of the table cursor iCur is open -** on. For each table column, if the column is the i'th column of the -** index, then the corresponding array entry is set to (i+1). If the column -** does not appear in the index at all, the array entry is set to 0. +** the statement currently being coded is a SELECT, then additional information +** is added that might allow OP_Column to omit the seek and instead do its +** lookup on the index, thus avoiding an expensive seek operation. To +** enable this optimization, the P3 of OP_DeferredSeek is set to iIdxCur +** and P4 is set to an array of integers containing one entry for each column +** in the table. For each table column, if the column is the i'th +** column of the index, then the corresponding array entry is set to (i+1). +** If the column does not appear in the index at all, the array entry is set +** to 0. The OP_Column opcode can check this array to see if the column it +** wants is in the index and if it is, it will substitute the index cursor +** and column number and continue with those new values, rather than seeking +** the table cursor. */ static void codeDeferredSeek( WhereInfo *pWInfo, /* Where clause context */ @@ -144953,7 +150387,7 @@ static void codeDeferredSeek( pWInfo->bDeferredSeek = 1; sqlite3VdbeAddOp3(v, OP_DeferredSeek, iIdxCur, 0, iCur); - if( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE) + if( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN)) && DbMaskAllZero(sqlite3ParseToplevel(pParse)->writeMask) ){ int i; @@ -144987,7 +150421,7 @@ static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){ assert( nReg>0 ); if( p && sqlite3ExprIsVector(p) ){ #ifndef SQLITE_OMIT_SUBQUERY - if( (p->flags & EP_xIsSelect) ){ + if( ExprUseXSelect(p) ){ Vdbe *v = pParse->pVdbe; int iSelect; assert( p->op==TK_SELECT ); @@ -144997,14 +150431,16 @@ static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){ #endif { int i; - ExprList *pList = p->x.pList; + const ExprList *pList; + assert( ExprUseXList(p) ); + pList = p->x.pList; assert( nReg<=pList->nExpr ); for(i=0; ia[i].pExpr, iReg+i); } } }else{ - assert( nReg==1 ); + assert( nReg==1 || pParse->nErr ); sqlite3ExprCode(pParse, p, iReg); } } @@ -145045,15 +150481,15 @@ static void preserveExpr(IdxExprTrans *pTrans, Expr *pExpr){ static int whereIndexExprTransNode(Walker *p, Expr *pExpr){ IdxExprTrans *pX = p->u.pIdxTrans; if( sqlite3ExprCompare(0, pExpr, pX->pIdxExpr, pX->iTabCur)==0 ){ + pExpr = sqlite3ExprSkipCollate(pExpr); preserveExpr(pX, pExpr); pExpr->affExpr = sqlite3ExprAffinity(pExpr); pExpr->op = TK_COLUMN; pExpr->iTable = pX->iIdxCur; pExpr->iColumn = pX->iIdxCol; - pExpr->y.pTab = 0; - testcase( ExprHasProperty(pExpr, EP_Skip) ); testcase( ExprHasProperty(pExpr, EP_Unlikely) ); - ExprClearProperty(pExpr, EP_Skip|EP_Unlikely); + ExprClearProperty(pExpr, EP_Skip|EP_Unlikely|EP_WinFunc|EP_Subrtn); + pExpr->y.pTab = 0; return WRC_Prune; }else{ return WRC_Continue; @@ -145068,7 +150504,7 @@ static int whereIndexExprTransColumn(Walker *p, Expr *pExpr){ if( pExpr->op==TK_COLUMN ){ IdxExprTrans *pX = p->u.pIdxTrans; if( pExpr->iTable==pX->iTabCur && pExpr->iColumn==pX->iTabCol ){ - assert( pExpr->y.pTab!=0 ); + assert( ExprUseYTab(pExpr) && pExpr->y.pTab!=0 ); preserveExpr(pX, pExpr); pExpr->affExpr = sqlite3TableColumnAffinity(pExpr->y.pTab,pExpr->iColumn); pExpr->iTable = pX->iIdxCur; @@ -145116,15 +150552,16 @@ static void whereIndexExprTrans( for(iIdxCol=0; iIdxColnColumn; iIdxCol++){ i16 iRef = pIdx->aiColumn[iIdxCol]; if( iRef==XN_EXPR ){ - assert( aColExpr->a[iIdxCol].pExpr!=0 ); + assert( aColExpr!=0 && aColExpr->a[iIdxCol].pExpr!=0 ); x.pIdxExpr = aColExpr->a[iIdxCol].pExpr; if( sqlite3ExprIsConstant(x.pIdxExpr) ) continue; w.xExprCallback = whereIndexExprTransNode; #ifndef SQLITE_OMIT_GENERATED_COLUMNS }else if( iRef>=0 && (pTab->aCol[iRef].colFlags & COLFLAG_VIRTUAL)!=0 - && (pTab->aCol[iRef].zColl==0 - || sqlite3StrICmp(pTab->aCol[iRef].zColl, sqlite3StrBINARY)==0) + && ((pTab->aCol[iRef].colFlags & COLFLAG_HASCOLL)==0 + || sqlite3StrICmp(sqlite3ColumnColl(&pTab->aCol[iRef]), + sqlite3StrBINARY)==0) ){ /* Check to see if there are direct references to generated columns ** that are contained in the index. Pulling the generated column @@ -145173,6 +150610,68 @@ static void whereApplyPartialIndexConstraints( } } +/* +** This routine is called right after An OP_Filter has been generated and +** before the corresponding index search has been performed. This routine +** checks to see if there are additional Bloom filters in inner loops that +** can be checked prior to doing the index lookup. If there are available +** inner-loop Bloom filters, then evaluate those filters now, before the +** index lookup. The idea is that a Bloom filter check is way faster than +** an index lookup, and the Bloom filter might return false, meaning that +** the index lookup can be skipped. +** +** We know that an inner loop uses a Bloom filter because it has the +** WhereLevel.regFilter set. If an inner-loop Bloom filter is checked, +** then clear the WhereLevel.regFilter value to prevent the Bloom filter +** from being checked a second time when the inner loop is evaluated. +*/ +static SQLITE_NOINLINE void filterPullDown( + Parse *pParse, /* Parsing context */ + WhereInfo *pWInfo, /* Complete information about the WHERE clause */ + int iLevel, /* Which level of pWInfo->a[] should be coded */ + int addrNxt, /* Jump here to bypass inner loops */ + Bitmask notReady /* Loops that are not ready */ +){ + while( ++iLevel < pWInfo->nLevel ){ + WhereLevel *pLevel = &pWInfo->a[iLevel]; + WhereLoop *pLoop = pLevel->pWLoop; + if( pLevel->regFilter==0 ) continue; + if( pLevel->pWLoop->nSkip ) continue; + /* ,--- Because sqlite3ConstructBloomFilter() has will not have set + ** vvvvv--' pLevel->regFilter if this were true. */ + if( NEVER(pLoop->prereq & notReady) ) continue; + assert( pLevel->addrBrk==0 ); + pLevel->addrBrk = addrNxt; + if( pLoop->wsFlags & WHERE_IPK ){ + WhereTerm *pTerm = pLoop->aLTerm[0]; + int regRowid; + assert( pTerm!=0 ); + assert( pTerm->pExpr!=0 ); + testcase( pTerm->wtFlags & TERM_VIRTUAL ); + regRowid = sqlite3GetTempReg(pParse); + regRowid = codeEqualityTerm(pParse, pTerm, pLevel, 0, 0, regRowid); + sqlite3VdbeAddOp4Int(pParse->pVdbe, OP_Filter, pLevel->regFilter, + addrNxt, regRowid, 1); + VdbeCoverage(pParse->pVdbe); + }else{ + u16 nEq = pLoop->u.btree.nEq; + int r1; + char *zStartAff; + + assert( pLoop->wsFlags & WHERE_INDEXED ); + assert( (pLoop->wsFlags & WHERE_COLUMN_IN)==0 ); + r1 = codeAllEqualityTerms(pParse,pLevel,0,0,&zStartAff); + codeApplyAffinity(pParse, r1, nEq, zStartAff); + sqlite3DbFree(pParse->db, zStartAff); + sqlite3VdbeAddOp4Int(pParse->pVdbe, OP_Filter, pLevel->regFilter, + addrNxt, r1, nEq); + VdbeCoverage(pParse->pVdbe); + } + pLevel->regFilter = 0; + pLevel->addrBrk = 0; + } +} + /* ** Generate code for the start of the iLevel-th loop in the WHERE clause ** implementation described by pWInfo. @@ -145243,7 +150742,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** initialize a memory cell that records if this table matches any ** row of the left table of the join. */ - assert( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE) + assert( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN)) || pLevel->iFrom>0 || (pTabItem[0].fg.jointype & JT_LEFT)==0 ); if( pLevel->iFrom>0 && (pTabItem[0].fg.jointype & JT_LEFT)!=0 ){ @@ -145254,7 +150753,10 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( /* Compute a safe address to jump to if we discover that the table for ** this loop is empty and can never contribute content. */ - for(j=iLevel; j>0 && pWInfo->a[j].iLeftJoin==0; j--){} + for(j=iLevel; j>0; j--){ + if( pWInfo->a[j].iLeftJoin ) break; + if( pWInfo->a[j].pRJ ) break; + } addrHalt = pWInfo->a[j].addrBrk; /* Special case of a FROM clause subquery implemented as a co-routine */ @@ -145275,7 +150777,6 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( int iReg; /* P3 Value for OP_VFilter */ int addrNotFound; int nConstraint = pLoop->nLTerm; - int iIn; /* Counter for IN constraints */ iReg = sqlite3GetTempRange(pParse, nConstraint+2); addrNotFound = pLevel->addrBrk; @@ -145284,11 +150785,27 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( pTerm = pLoop->aLTerm[j]; if( NEVER(pTerm==0) ) continue; if( pTerm->eOperator & WO_IN ){ - codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); - addrNotFound = pLevel->addrNxt; + if( SMASKBIT32(j) & pLoop->u.vtab.mHandleIn ){ + int iTab = pParse->nTab++; + int iCache = ++pParse->nMem; + sqlite3CodeRhsOfIN(pParse, pTerm->pExpr, iTab); + sqlite3VdbeAddOp3(v, OP_VInitIn, iTab, iTarget, iCache); + }else{ + codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); + addrNotFound = pLevel->addrNxt; + } }else{ Expr *pRight = pTerm->pExpr->pRight; codeExprOrVector(pParse, pRight, iTarget, 1); + if( pTerm->eMatchOp==SQLITE_INDEX_CONSTRAINT_OFFSET + && pLoop->u.vtab.bOmitOffset + ){ + assert( pTerm->eOperator==WO_AUX ); + assert( pWInfo->pLimit!=0 ); + assert( pWInfo->pLimit->iOffset>0 ); + sqlite3VdbeAddOp2(v, OP_Integer, 0, pWInfo->pLimit->iOffset); + VdbeComment((v,"Zero OFFSET counter")); + } } } sqlite3VdbeAddOp2(v, OP_Integer, pLoop->u.vtab.idxNum, iReg); @@ -145304,40 +150821,55 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( pLevel->p1 = iCur; pLevel->op = pWInfo->eOnePass ? OP_Noop : OP_VNext; pLevel->p2 = sqlite3VdbeCurrentAddr(v); - iIn = pLevel->u.in.nIn; - for(j=nConstraint-1; j>=0; j--){ + assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 ); + + for(j=0; jaLTerm[j]; - if( (pTerm->eOperator & WO_IN)!=0 ) iIn--; if( j<16 && (pLoop->u.vtab.omitMask>>j)&1 ){ disableTerm(pLevel, pTerm); - }else if( (pTerm->eOperator & WO_IN)!=0 - && sqlite3ExprVectorSize(pTerm->pExpr->pLeft)==1 + continue; + } + if( (pTerm->eOperator & WO_IN)!=0 + && (SMASKBIT32(j) & pLoop->u.vtab.mHandleIn)==0 + && !db->mallocFailed ){ Expr *pCompare; /* The comparison operator */ Expr *pRight; /* RHS of the comparison */ VdbeOp *pOp; /* Opcode to access the value of the IN constraint */ + int iIn; /* IN loop corresponding to the j-th constraint */ /* Reload the constraint value into reg[iReg+j+2]. The same value ** was loaded into the same register prior to the OP_VFilter, but ** the xFilter implementation might have changed the datatype or - ** encoding of the value in the register, so it *must* be reloaded. */ - assert( pLevel->u.in.aInLoop!=0 || db->mallocFailed ); - if( !db->mallocFailed ){ - assert( iIn>=0 && iInu.in.nIn ); + ** encoding of the value in the register, so it *must* be reloaded. + */ + for(iIn=0; ALWAYS(iInu.in.nIn); iIn++){ pOp = sqlite3VdbeGetOp(v, pLevel->u.in.aInLoop[iIn].addrInTop); - assert( pOp->opcode==OP_Column || pOp->opcode==OP_Rowid ); - assert( pOp->opcode!=OP_Column || pOp->p3==iReg+j+2 ); - assert( pOp->opcode!=OP_Rowid || pOp->p2==iReg+j+2 ); - testcase( pOp->opcode==OP_Rowid ); - sqlite3VdbeAddOp3(v, pOp->opcode, pOp->p1, pOp->p2, pOp->p3); + if( (pOp->opcode==OP_Column && pOp->p3==iReg+j+2) + || (pOp->opcode==OP_Rowid && pOp->p2==iReg+j+2) + ){ + testcase( pOp->opcode==OP_Rowid ); + sqlite3VdbeAddOp3(v, pOp->opcode, pOp->p1, pOp->p2, pOp->p3); + break; + } } /* Generate code that will continue to the next row if - ** the IN constraint is not satisfied */ + ** the IN constraint is not satisfied + */ pCompare = sqlite3PExpr(pParse, TK_EQ, 0, 0); - assert( pCompare!=0 || db->mallocFailed ); - if( pCompare ){ - pCompare->pLeft = pTerm->pExpr->pLeft; + if( !db->mallocFailed ){ + int iFld = pTerm->u.x.iField; + Expr *pLeft = pTerm->pExpr->pLeft; + assert( pLeft!=0 ); + if( iFld>0 ){ + assert( pLeft->op==TK_VECTOR ); + assert( ExprUseXList(pLeft) ); + assert( iFld<=pLeft->x.pList->nExpr ); + pCompare->pLeft = pLeft->x.pList->a[iFld-1].pExpr; + }else{ + pCompare->pLeft = pLeft; + } pCompare->pRight = pRight = sqlite3Expr(db, TK_REGISTER, 0); if( pRight ){ pRight->iTable = iReg+j+2; @@ -145346,11 +150878,11 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ); } pCompare->pLeft = 0; - sqlite3ExprDelete(db, pCompare); } + sqlite3ExprDelete(db, pCompare); } } - assert( iIn==0 || db->mallocFailed ); + /* These registers need to be preserved in case there is an IN operator ** loop. So we could deallocate the registers here (and potentially ** reuse them later) if (pLoop->wsFlags & WHERE_IN_ABLE)==0. But it seems @@ -145378,12 +150910,15 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( iRowidReg = codeEqualityTerm(pParse, pTerm, pLevel, 0, bRev, iReleaseReg); if( iRowidReg!=iReleaseReg ) sqlite3ReleaseTempReg(pParse, iReleaseReg); addrNxt = pLevel->addrNxt; + if( pLevel->regFilter ){ + sqlite3VdbeAddOp4Int(v, OP_Filter, pLevel->regFilter, addrNxt, + iRowidReg, 1); + VdbeCoverage(v); + filterPullDown(pParse, pWInfo, iLevel, addrNxt, notReady); + } sqlite3VdbeAddOp3(v, OP_SeekRowid, iCur, addrNxt, iRowidReg); VdbeCoverage(v); pLevel->op = OP_Noop; - if( (pTerm->prereqAll & pLevel->notReady)==0 ){ - pTerm->wtFlags |= TERM_CODED; - } }else if( (pLoop->wsFlags & WHERE_IPK)!=0 && (pLoop->wsFlags & WHERE_COLUMN_RANGE)!=0 ){ @@ -145631,9 +151166,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** a forward order scan on a descending index, interchange the ** start and end terms (pRangeStart and pRangeEnd). */ - if( (nEqnKeyCol && bRev==(pIdx->aSortOrder[nEq]==SQLITE_SO_ASC)) - || (bRev && pIdx->nKeyCol==nEq) - ){ + if( (nEqnColumn && bRev==(pIdx->aSortOrder[nEq]==SQLITE_SO_ASC)) ){ SWAP(WhereTerm *, pRangeEnd, pRangeStart); SWAP(u8, bSeekPastNull, bStopAtNull); SWAP(u8, nBtm, nTop); @@ -145708,6 +151241,12 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( sqlite3VdbeAddOp2(v, OP_Integer, 1, regBignull); VdbeComment((v, "NULL-scan pass ctr")); } + if( pLevel->regFilter ){ + sqlite3VdbeAddOp4Int(v, OP_Filter, pLevel->regFilter, addrNxt, + regBase, nEq); + VdbeCoverage(v); + filterPullDown(pParse, pWInfo, iLevel, addrNxt, notReady); + } op = aStartOp[(start_constraints<<2) + (startEq<<1) + bRev]; assert( op!=0 ); @@ -145756,8 +151295,19 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** range (if any). */ nConstraint = nEq; + assert( pLevel->p2==0 ); if( pRangeEnd ){ Expr *pRight = pRangeEnd->pExpr->pRight; + if( addrSeekScan ){ + /* For a seek-scan that has a range on the lowest term of the index, + ** we have to make the top of the loop be code that sets the end + ** condition of the range. Otherwise, the OP_SeekScan might jump + ** over that initialization, leaving the range-end value set to the + ** range-start value, resulting in a wrong answer. + ** See ticket 5981a8c041a3c2f3 (2021-11-02). + */ + pLevel->p2 = sqlite3VdbeCurrentAddr(v); + } codeExprOrVector(pParse, pRight, regBase+nEq, nTop); whereLikeOptimizationStringFixup(v, pLevel, pRangeEnd); if( (pRangeEnd->wtFlags & TERM_VNULL)==0 @@ -145791,7 +151341,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( sqlite3DbFree(db, zEndAff); /* Top of the loop body */ - pLevel->p2 = sqlite3VdbeCurrentAddr(v); + if( pLevel->p2==0 ) pLevel->p2 = sqlite3VdbeCurrentAddr(v); /* Check if the index cursor is past the end of the range. */ if( nConstraint ){ @@ -145833,7 +151383,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( /* Seek the table cursor, if required */ omitTable = (pLoop->wsFlags & WHERE_IDX_ONLY)!=0 - && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0; + && (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0; if( omitTable ){ /* pIdx is a covering index. No need to access the main table. */ }else if( HasRowid(pIdx->pTable) ){ @@ -145867,7 +151417,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** move forward to the next index. ** https://sqlite.org/src/info/4e8e4857d32d401f */ - if( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 ){ + if( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0 ){ whereIndexExprTrans(pIdx, iCur, iIdxCur, pWInfo); } @@ -145886,7 +151436,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( /* The following assert() is not a requirement, merely an observation: ** The OR-optimization doesn't work for the right hand table of ** a LEFT JOIN: */ - assert( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 ); + assert( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0 ); } /* Record the instruction used to terminate the loop. */ @@ -146024,7 +151574,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( iRetInit = sqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn); /* If the original WHERE clause is z of the form: (x1 OR x2 OR ...) AND y - ** Then for every term xN, evaluate as the subexpression: xN AND z + ** Then for every term xN, evaluate as the subexpression: xN AND y ** That way, terms in y that are factored into the disjunction will ** be picked up by the recursive calls to sqlite3WhereBegin() below. ** @@ -146036,6 +151586,20 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** This optimization also only applies if the (x1 OR x2 OR ...) term ** is not contained in the ON clause of a LEFT JOIN. ** See ticket http://www.sqlite.org/src/info/f2369304e4 + ** + ** 2022-02-04: Do not push down slices of a row-value comparison. + ** In other words, "w" or "y" may not be a slice of a vector. Otherwise, + ** the initialization of the right-hand operand of the vector comparison + ** might not occur, or might occur only in an OR branch that is not + ** taken. dbsqlfuzz 80a9fade844b4fb43564efc972bcb2c68270f5d1. + ** + ** 2022-03-03: Do not push down expressions that involve subqueries. + ** The subquery might get coded as a subroutine. Any table-references + ** in the subquery might be resolved to index-references for the index on + ** the OR branch in which the subroutine is coded. But if the subroutine + ** is invoked from a different OR branch that uses a different index, such + ** index-references will not work. tag-20220303a + ** https://sqlite.org/forum/forumpost/36937b197273d403 */ if( pWC->nTerm>1 ){ int iTerm; @@ -146044,9 +151608,12 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( if( &pWC->a[iTerm] == pTerm ) continue; testcase( pWC->a[iTerm].wtFlags & TERM_VIRTUAL ); testcase( pWC->a[iTerm].wtFlags & TERM_CODED ); - if( (pWC->a[iTerm].wtFlags & (TERM_VIRTUAL|TERM_CODED))!=0 ) continue; + testcase( pWC->a[iTerm].wtFlags & TERM_SLICE ); + if( (pWC->a[iTerm].wtFlags & (TERM_VIRTUAL|TERM_CODED|TERM_SLICE))!=0 ){ + continue; + } if( (pWC->a[iTerm].eOperator & WO_ALL)==0 ) continue; - testcase( pWC->a[iTerm].wtFlags & TERM_ORINFO ); + if( ExprHasProperty(pExpr, EP_Subquery) ) continue; /* tag-20220303a */ pExpr = sqlite3ExprDup(db, pExpr, 0); pAndExpr = sqlite3ExprAnd(pParse, pAndExpr, pExpr); } @@ -146054,7 +151621,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( /* The extra 0x10000 bit on the opcode is masked off and does not ** become part of the new Expr.op. However, it does make the ** op==TK_AND comparison inside of sqlite3PExpr() false, and this - ** prevents sqlite3PExpr() from implementing AND short-circuit + ** prevents sqlite3PExpr() from applying the AND short-circuit ** optimization, which we do not want here. */ pAndExpr = sqlite3PExpr(pParse, TK_AND|0x10000, 0, pAndExpr); } @@ -146070,10 +151637,16 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( if( pOrTerm->leftCursor==iCur || (pOrTerm->eOperator & WO_AND)!=0 ){ WhereInfo *pSubWInfo; /* Info for single OR-term scan */ Expr *pOrExpr = pOrTerm->pExpr; /* Current OR clause term */ + Expr *pDelete; /* Local copy of OR clause term */ int jmp1 = 0; /* Address of jump operation */ testcase( (pTabItem[0].fg.jointype & JT_LEFT)!=0 - && !ExprHasProperty(pOrExpr, EP_FromJoin) + && !ExprHasProperty(pOrExpr, EP_OuterON) ); /* See TH3 vtab25.400 and ticket 614b25314c766238 */ + pDelete = pOrExpr = sqlite3ExprDup(db, pOrExpr, 0); + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pDelete); + continue; + } if( pAndExpr ){ pAndExpr->pLeft = pOrExpr; pOrExpr = pAndExpr; @@ -146081,9 +151654,9 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( /* Loop through table entries that match term pOrTerm. */ ExplainQueryPlan((pParse, 1, "INDEX %d", ii+1)); WHERETRACE(0xffff, ("Subplan for OR-clause:\n")); - pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0, + pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0, 0, WHERE_OR_SUBCLAUSE, iCovCur); - assert( pSubWInfo || pParse->nErr || db->mallocFailed ); + assert( pSubWInfo || pParse->nErr ); if( pSubWInfo ){ WhereLoop *pSubLoop; int addrExplain = sqlite3WhereExplainOneScan( @@ -146188,10 +151761,14 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( sqlite3WhereEnd(pSubWInfo); ExplainQueryPlanPop(pParse); } + sqlite3ExprDelete(db, pDelete); } } ExplainQueryPlanPop(pParse); - pLevel->u.pCovidx = pCov; + assert( pLevel->pWLoop==pLoop ); + assert( (pLoop->wsFlags & WHERE_MULTI_OR)!=0 ); + assert( (pLoop->wsFlags & WHERE_IN_ABLE)==0 ); + pLevel->u.pCoveringIdx = pCov; if( pCov ) pLevel->iIdxCur = iCovCur; if( pAndExpr ){ pAndExpr->pLeft = 0; @@ -146201,6 +151778,14 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( sqlite3VdbeGoto(v, pLevel->addrBrk); sqlite3VdbeResolveLabel(v, iLoopBody); + /* Set the P2 operand of the OP_Return opcode that will end the current + ** loop to point to this spot, which is the top of the next containing + ** loop. The byte-code formatter will use that P2 value as a hint to + ** indent everything in between the this point and the final OP_Return. + ** See tag-20220407a in vdbe.c and shell.c */ + assert( pLevel->op==OP_Return ); + pLevel->p2 = sqlite3VdbeCurrentAddr(v); + if( pWInfo->nLevel>1 ){ sqlite3StackFree(db, pOrTab); } if( !untestedTerms ) disableTerm(pLevel, pTerm); }else @@ -146263,10 +151848,22 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( } pE = pTerm->pExpr; assert( pE!=0 ); - if( (pTabItem->fg.jointype&JT_LEFT) && !ExprHasProperty(pE,EP_FromJoin) ){ - continue; + if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ){ + if( !ExprHasProperty(pE,EP_OuterON|EP_InnerON) ){ + /* Defer processing WHERE clause constraints until after outer + ** join processing. tag-20220513a */ + continue; + }else if( (pTabItem->fg.jointype & JT_LEFT)==JT_LEFT + && !ExprHasProperty(pE,EP_OuterON) ){ + continue; + }else{ + Bitmask m = sqlite3WhereGetMask(&pWInfo->sMaskSet, pE->w.iJoin); + if( m & pLevel->notReady ){ + /* An ON clause that is not ripe */ + continue; + } + } } - if( iLoop==1 && !sqlite3ExprCoveredByIndex(pE, pLevel->iTabCur, pIdx) ){ iNext = 2; continue; @@ -146318,14 +151915,14 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** then we cannot use the "t1.a=t2.b" constraint, but we can code ** the implied "t1.a=123" constraint. */ - for(pTerm=pWC->a, j=pWC->nTerm; j>0; j--, pTerm++){ + for(pTerm=pWC->a, j=pWC->nBase; j>0; j--, pTerm++){ Expr *pE, sEAlt; WhereTerm *pAlt; if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) continue; if( (pTerm->eOperator & WO_EQUIV)==0 ) continue; if( pTerm->leftCursor!=iCur ) continue; - if( pTabItem->fg.jointype & JT_LEFT ) continue; + if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ) continue; pE = pTerm->pExpr; #ifdef WHERETRACE_ENABLED /* 0x800 */ if( sqlite3WhereTrace & 0x800 ){ @@ -146333,14 +151930,15 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( sqlite3WhereTermPrint(pTerm, pWC->nTerm-j); } #endif - assert( !ExprHasProperty(pE, EP_FromJoin) ); + assert( !ExprHasProperty(pE, EP_OuterON) ); assert( (pTerm->prereqRight & pLevel->notReady)!=0 ); + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); pAlt = sqlite3WhereFindTerm(pWC, iCur, pTerm->u.x.leftColumn, notReady, WO_EQ|WO_IN|WO_IS, 0); if( pAlt==0 ) continue; if( pAlt->wtFlags & (TERM_CODED) ) continue; if( (pAlt->eOperator & WO_IN) - && (pAlt->pExpr->flags & EP_xIsSelect) + && ExprUseXSelect(pAlt->pExpr) && (pAlt->pExpr->x.pSelect->pEList->nExpr>1) ){ continue; @@ -146352,6 +151950,48 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( sEAlt = *pAlt->pExpr; sEAlt.pLeft = pE->pLeft; sqlite3ExprIfFalse(pParse, &sEAlt, addrCont, SQLITE_JUMPIFNULL); + pAlt->wtFlags |= TERM_CODED; + } + + /* For a RIGHT OUTER JOIN, record the fact that the current row has + ** been matched at least once. + */ + if( pLevel->pRJ ){ + Table *pTab; + int nPk; + int r; + int jmp1 = 0; + WhereRightJoin *pRJ = pLevel->pRJ; + + /* pTab is the right-hand table of the RIGHT JOIN. Generate code that + ** will record that the current row of that table has been matched at + ** least once. This is accomplished by storing the PK for the row in + ** both the iMatch index and the regBloom Bloom filter. + */ + pTab = pWInfo->pTabList->a[pLevel->iFrom].pTab; + if( HasRowid(pTab) ){ + r = sqlite3GetTempRange(pParse, 2); + sqlite3ExprCodeGetColumnOfTable(v, pTab, pLevel->iTabCur, -1, r+1); + nPk = 1; + }else{ + int iPk; + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + nPk = pPk->nKeyCol; + r = sqlite3GetTempRange(pParse, nPk+1); + for(iPk=0; iPkaiColumn[iPk]; + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol,r+1+iPk); + } + } + jmp1 = sqlite3VdbeAddOp4Int(v, OP_Found, pRJ->iMatch, 0, r+1, nPk); + VdbeCoverage(v); + VdbeComment((v, "match against %s", pTab->zName)); + sqlite3VdbeAddOp3(v, OP_MakeRecord, r+1, nPk, r); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, pRJ->iMatch, r, r+1, nPk); + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pRJ->regBloom, 0, r+1, nPk); + sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + sqlite3VdbeJumpHere(v, jmp1); + sqlite3ReleaseTempRange(pParse, r, nPk+1); } /* For a LEFT OUTER JOIN, generate code that will record the fact that @@ -146361,7 +152001,31 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( pLevel->addrFirst = sqlite3VdbeCurrentAddr(v); sqlite3VdbeAddOp2(v, OP_Integer, 1, pLevel->iLeftJoin); VdbeComment((v, "record LEFT JOIN hit")); - for(pTerm=pWC->a, j=0; jnTerm; j++, pTerm++){ + if( pLevel->pRJ==0 ){ + goto code_outer_join_constraints; /* WHERE clause constraints */ + } + } + + if( pLevel->pRJ ){ + /* Create a subroutine used to process all interior loops and code + ** of the RIGHT JOIN. During normal operation, the subroutine will + ** be in-line with the rest of the code. But at the end, a separate + ** loop will run that invokes this subroutine for unmatched rows + ** of pTab, with all tables to left begin set to NULL. + */ + WhereRightJoin *pRJ = pLevel->pRJ; + sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pRJ->regReturn); + pRJ->addrSubrtn = sqlite3VdbeCurrentAddr(v); + assert( pParse->withinRJSubrtn < 255 ); + pParse->withinRJSubrtn++; + + /* WHERE clause constraints must be deferred until after outer join + ** row elimination has completed, since WHERE clause constraints apply + ** to the results of the OUTER JOIN. The following loop generates the + ** appropriate WHERE clause constraint checks. tag-20220513a. + */ + code_outer_join_constraints: + for(pTerm=pWC->a, j=0; jnBase; j++, pTerm++){ testcase( pTerm->wtFlags & TERM_VIRTUAL ); testcase( pTerm->wtFlags & TERM_CODED ); if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; @@ -146369,6 +152033,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( assert( pWInfo->untestedTerms ); continue; } + if( pTabItem->fg.jointype & JT_LTORJ ) continue; assert( pTerm->pExpr ); sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL); pTerm->wtFlags |= TERM_CODED; @@ -146389,6 +152054,96 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( return pLevel->notReady; } +/* +** Generate the code for the loop that finds all non-matched terms +** for a RIGHT JOIN. +*/ +SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3WhereRightJoinLoop( + WhereInfo *pWInfo, + int iLevel, + WhereLevel *pLevel +){ + Parse *pParse = pWInfo->pParse; + Vdbe *v = pParse->pVdbe; + WhereRightJoin *pRJ = pLevel->pRJ; + Expr *pSubWhere = 0; + WhereClause *pWC = &pWInfo->sWC; + WhereInfo *pSubWInfo; + WhereLoop *pLoop = pLevel->pWLoop; + SrcItem *pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; + SrcList sFrom; + Bitmask mAll = 0; + int k; + + ExplainQueryPlan((pParse, 1, "RIGHT-JOIN %s", pTabItem->pTab->zName)); + sqlite3VdbeNoJumpsOutsideSubrtn(v, pRJ->addrSubrtn, pRJ->endSubrtn, + pRJ->regReturn); + for(k=0; ka[k].pWLoop->maskSelf; + sqlite3VdbeAddOp1(v, OP_NullRow, pWInfo->a[k].iTabCur); + iIdxCur = pWInfo->a[k].iIdxCur; + if( iIdxCur ){ + sqlite3VdbeAddOp1(v, OP_NullRow, iIdxCur); + } + } + if( (pTabItem->fg.jointype & JT_LTORJ)==0 ){ + mAll |= pLoop->maskSelf; + for(k=0; knTerm; k++){ + WhereTerm *pTerm = &pWC->a[k]; + if( (pTerm->wtFlags & (TERM_VIRTUAL|TERM_SLICE))!=0 + && pTerm->eOperator!=WO_ROWVAL + ){ + break; + } + if( pTerm->prereqAll & ~mAll ) continue; + if( ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) ) continue; + pSubWhere = sqlite3ExprAnd(pParse, pSubWhere, + sqlite3ExprDup(pParse->db, pTerm->pExpr, 0)); + } + } + sFrom.nSrc = 1; + sFrom.nAlloc = 1; + memcpy(&sFrom.a[0], pTabItem, sizeof(SrcItem)); + sFrom.a[0].fg.jointype = 0; + assert( pParse->withinRJSubrtn < 100 ); + pParse->withinRJSubrtn++; + pSubWInfo = sqlite3WhereBegin(pParse, &sFrom, pSubWhere, 0, 0, 0, + WHERE_RIGHT_JOIN, 0); + if( pSubWInfo ){ + int iCur = pLevel->iTabCur; + int r = ++pParse->nMem; + int nPk; + int jmp; + int addrCont = sqlite3WhereContinueLabel(pSubWInfo); + Table *pTab = pTabItem->pTab; + if( HasRowid(pTab) ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, -1, r); + nPk = 1; + }else{ + int iPk; + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + nPk = pPk->nKeyCol; + pParse->nMem += nPk - 1; + for(iPk=0; iPkaiColumn[iPk]; + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol,r+iPk); + } + } + jmp = sqlite3VdbeAddOp4Int(v, OP_Filter, pRJ->regBloom, 0, r, nPk); + VdbeCoverage(v); + sqlite3VdbeAddOp4Int(v, OP_Found, pRJ->iMatch, addrCont, r, nPk); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, jmp); + sqlite3VdbeAddOp2(v, OP_Gosub, pRJ->regReturn, pRJ->addrSubrtn); + sqlite3WhereEnd(pSubWInfo); + } + sqlite3ExprDelete(pParse->db, pSubWhere); + ExplainQueryPlanPop(pParse); + assert( pParse->withinRJSubrtn>0 ); + pParse->withinRJSubrtn--; +} + /************** End of wherecode.c *******************************************/ /************** Begin file whereexpr.c ***************************************/ /* @@ -146457,7 +152212,7 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){ if( pWC->nTerm>=pWC->nSlot ){ WhereTerm *pOld = pWC->a; sqlite3 *db = pWC->pWInfo->pParse->db; - pWC->a = sqlite3DbMallocRawNN(db, sizeof(pWC->a[0])*pWC->nSlot*2 ); + pWC->a = sqlite3WhereMalloc(pWC->pWInfo, sizeof(pWC->a[0])*pWC->nSlot*2 ); if( pWC->a==0 ){ if( wtFlags & TERM_DYNAMIC ){ sqlite3ExprDelete(db, p); @@ -146466,12 +152221,10 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){ return 0; } memcpy(pWC->a, pOld, sizeof(pWC->a[0])*pWC->nTerm); - if( pOld!=pWC->aStatic ){ - sqlite3DbFree(db, pOld); - } - pWC->nSlot = sqlite3DbMallocSize(db, pWC->a)/sizeof(pWC->a[0]); + pWC->nSlot = pWC->nSlot*2; } pTerm = &pWC->a[idx = pWC->nTerm++]; + if( (wtFlags & TERM_VIRTUAL)==0 ) pWC->nBase = pWC->nTerm; if( p && ExprHasProperty(p, EP_Unlikely) ){ pTerm->truthProb = sqlite3LogEst(p->iTable) - 270; }else{ @@ -146588,6 +152341,7 @@ static int isLikeOrGlob( #ifdef SQLITE_EBCDIC if( *pnoCase ) return 0; #endif + assert( ExprUseXList(pExpr) ); pList = pExpr->x.pList; pLeft = pList->a[1].pExpr; @@ -146603,7 +152357,8 @@ static int isLikeOrGlob( sqlite3VdbeSetVarmask(pParse->pVdbe, iCol); assert( pRight->op==TK_VARIABLE || pRight->op==TK_REGISTER ); }else if( op==TK_STRING ){ - z = (u8*)pRight->u.zToken; + assert( !ExprHasProperty(pRight, EP_IntValue) ); + z = (u8*)pRight->u.zToken; } if( z ){ @@ -146632,7 +152387,9 @@ static int isLikeOrGlob( pPrefix = sqlite3Expr(db, TK_STRING, (char*)z); if( pPrefix ){ int iFrom, iTo; - char *zNew = pPrefix->u.zToken; + char *zNew; + assert( !ExprHasProperty(pPrefix, EP_IntValue) ); + zNew = pPrefix->u.zToken; zNew[cnt] = 0; for(iFrom=iTo=0; iFromop!=TK_COLUMN || sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT - || IsVirtual(pLeft->y.pTab) /* Value might be numeric */ + || (ALWAYS( ExprUseYTab(pLeft) ) + && pLeft->y.pTab + && IsVirtual(pLeft->y.pTab)) /* Might be numeric */ ){ int isNum; double rDummy; @@ -146684,6 +152443,7 @@ static int isLikeOrGlob( if( op==TK_VARIABLE ){ Vdbe *v = pParse->pVdbe; sqlite3VdbeSetVarmask(v, pRight->iColumn); + assert( !ExprHasProperty(pRight, EP_IntValue) ); if( *pisComplete && pRight->u.zToken[1] ){ /* If the rhs of the LIKE expression is a variable, and the current ** value of the variable means there is no need to invoke the LIKE @@ -146757,6 +152517,7 @@ static int isAuxiliaryVtabOperator( Expr *pCol; /* Column reference */ int i; + assert( ExprUseXList(pExpr) ); pList = pExpr->x.pList; if( pList==0 || pList->nExpr!=2 ){ return 0; @@ -146770,9 +152531,11 @@ static int isAuxiliaryVtabOperator( ** MATCH(expression,vtab_column) */ pCol = pList->a[1].pExpr; + assert( pCol->op!=TK_COLUMN || ExprUseYTab(pCol) ); testcase( pCol->op==TK_COLUMN && pCol->y.pTab==0 ); if( ExprIsVtab(pCol) ){ for(i=0; iu.zToken, aOp[i].zOp)==0 ){ *peOp2 = aOp[i].eOp2; *ppRight = pList->a[0].pExpr; @@ -146793,6 +152556,7 @@ static int isAuxiliaryVtabOperator( ** with function names in an arbitrary case. */ pCol = pList->a[0].pExpr; + assert( pCol->op!=TK_COLUMN || ExprUseYTab(pCol) ); testcase( pCol->op==TK_COLUMN && pCol->y.pTab==0 ); if( ExprIsVtab(pCol) ){ sqlite3_vtab *pVtab; @@ -146802,6 +152566,7 @@ static int isAuxiliaryVtabOperator( pVtab = sqlite3GetVTable(db, pCol->y.pTab)->pVtab; assert( pVtab!=0 ); assert( pVtab->pModule!=0 ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); pMod = (sqlite3_module *)pVtab->pModule; if( pMod->xFindFunction!=0 ){ i = pMod->xFindFunction(pVtab,2, pExpr->u.zToken, &xNotUsed, &pNotUsed); @@ -146817,10 +152582,12 @@ static int isAuxiliaryVtabOperator( int res = 0; Expr *pLeft = pExpr->pLeft; Expr *pRight = pExpr->pRight; + assert( pLeft->op!=TK_COLUMN || ExprUseYTab(pLeft) ); testcase( pLeft->op==TK_COLUMN && pLeft->y.pTab==0 ); if( ExprIsVtab(pLeft) ){ res++; } + assert( pRight==0 || pRight->op!=TK_COLUMN || ExprUseYTab(pRight) ); testcase( pRight && pRight->op==TK_COLUMN && pRight->y.pTab==0 ); if( pRight && ExprIsVtab(pRight) ){ res++; @@ -146842,9 +152609,9 @@ static int isAuxiliaryVtabOperator( ** a join, then transfer the appropriate markings over to derived. */ static void transferJoinMarkings(Expr *pDerived, Expr *pBase){ - if( pDerived ){ - pDerived->flags |= pBase->flags & EP_FromJoin; - pDerived->iRightJoinTable = pBase->iRightJoinTable; + if( pDerived && ExprHasProperty(pBase, EP_OuterON|EP_InnerON) ){ + pDerived->flags |= pBase->flags & (EP_OuterON|EP_InnerON); + pDerived->w.iJoin = pBase->w.iJoin; } } @@ -146904,6 +152671,7 @@ static void whereCombineDisjuncts( int op; /* Operator for the combined expression */ int idxNew; /* Index in pWC of the next virtual term */ + if( (pOne->wtFlags | pTwo->wtFlags) & TERM_VNULL ) return; if( (pOne->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; if( (pTwo->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; if( (eOp & (WO_EQ|WO_LT|WO_LE))!=eOp @@ -147072,6 +152840,7 @@ static void exprAnalyzeOrTerm( pOrTerm->u.pAndInfo = pAndInfo; pOrTerm->wtFlags |= TERM_ANDINFO; pOrTerm->eOperator = WO_AND; + pOrTerm->leftCursor = -1; pAndWC = &pAndInfo->wc; memset(pAndWC->aStatic, 0, sizeof(pAndWC->aStatic)); sqlite3WhereClauseInit(pAndWC, pWC->pWInfo); @@ -147114,11 +152883,10 @@ static void exprAnalyzeOrTerm( ** empty. */ pOrInfo->indexable = indexable; + pTerm->eOperator = WO_OR; + pTerm->leftCursor = -1; if( indexable ){ - pTerm->eOperator = WO_OR; pWC->hasOr = 1; - }else{ - pTerm->eOperator = WO_OR; } /* For a two-way OR, attempt to implementation case 2. @@ -147173,7 +152941,7 @@ static void exprAnalyzeOrTerm( pOrTerm = pOrWc->a; for(i=pOrWc->nTerm-1; i>=0; i--, pOrTerm++){ assert( pOrTerm->eOperator & WO_EQ ); - pOrTerm->wtFlags &= ~TERM_OR_OK; + pOrTerm->wtFlags &= ~TERM_OK; if( pOrTerm->leftCursor==iCursor ){ /* This is the 2-bit case and we are on the second iteration and ** current term is from the first iteration. So skip this term. */ @@ -147191,6 +152959,7 @@ static void exprAnalyzeOrTerm( assert( pOrTerm->wtFlags & (TERM_COPIED|TERM_VIRTUAL) ); continue; } + assert( (pOrTerm->eOperator & (WO_OR|WO_AND))==0 ); iColumn = pOrTerm->u.x.leftColumn; iCursor = pOrTerm->leftCursor; pLeft = pOrTerm->pExpr->pLeft; @@ -147211,8 +152980,9 @@ static void exprAnalyzeOrTerm( okToChngToIN = 1; for(; i>=0 && okToChngToIN; i--, pOrTerm++){ assert( pOrTerm->eOperator & WO_EQ ); + assert( (pOrTerm->eOperator & (WO_OR|WO_AND))==0 ); if( pOrTerm->leftCursor!=iCursor ){ - pOrTerm->wtFlags &= ~TERM_OR_OK; + pOrTerm->wtFlags &= ~TERM_OK; }else if( pOrTerm->u.x.leftColumn!=iColumn || (iColumn==XN_EXPR && sqlite3ExprCompare(pParse, pOrTerm->pExpr->pLeft, pLeft, -1) )){ @@ -147228,7 +152998,7 @@ static void exprAnalyzeOrTerm( if( affRight!=0 && affRight!=affLeft ){ okToChngToIN = 0; }else{ - pOrTerm->wtFlags |= TERM_OR_OK; + pOrTerm->wtFlags |= TERM_OK; } } } @@ -147245,8 +153015,9 @@ static void exprAnalyzeOrTerm( Expr *pNew; /* The complete IN operator */ for(i=pOrWc->nTerm-1, pOrTerm=pOrWc->a; i>=0; i--, pOrTerm++){ - if( (pOrTerm->wtFlags & TERM_OR_OK)==0 ) continue; + if( (pOrTerm->wtFlags & TERM_OK)==0 ) continue; assert( pOrTerm->eOperator & WO_EQ ); + assert( (pOrTerm->eOperator & (WO_OR|WO_AND))==0 ); assert( pOrTerm->leftCursor==iCursor ); assert( pOrTerm->u.x.leftColumn==iColumn ); pDup = sqlite3ExprDup(db, pOrTerm->pExpr->pRight, 0); @@ -147259,12 +153030,12 @@ static void exprAnalyzeOrTerm( if( pNew ){ int idxNew; transferJoinMarkings(pNew, pExpr); - assert( !ExprHasProperty(pNew, EP_xIsSelect) ); + assert( ExprUseXList(pNew) ); pNew->x.pList = pList; idxNew = whereClauseInsert(pWC, pNew, TERM_VIRTUAL|TERM_DYNAMIC); testcase( idxNew==0 ); exprAnalyze(pSrc, pWC, idxNew); - /* pTerm = &pWC->a[idxTerm]; // would be needed if pTerm where used again */ + /* pTerm = &pWC->a[idxTerm]; // would be needed if pTerm where reused */ markTermAsChild(pWC, idxNew, idxTerm); }else{ sqlite3ExprListDelete(db, pList); @@ -147294,7 +153065,7 @@ static int termIsEquivalence(Parse *pParse, Expr *pExpr){ CollSeq *pColl; if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0; if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0; - if( ExprHasProperty(pExpr, EP_FromJoin) ) return 0; + if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; aff1 = sqlite3ExprAffinity(pExpr->pLeft); aff2 = sqlite3ExprAffinity(pExpr->pRight); if( aff1!=aff2 @@ -147325,7 +153096,9 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){ int i; for(i=0; inSrc; i++){ mask |= exprSelectUsage(pMaskSet, pSrc->a[i].pSelect); - mask |= sqlite3WhereExprUsage(pMaskSet, pSrc->a[i].pOn); + if( pSrc->a[i].fg.isUsing==0 ){ + mask |= sqlite3WhereExprUsage(pMaskSet, pSrc->a[i].u3.pOn); + } if( pSrc->a[i].fg.isTabFunc ){ mask |= sqlite3WhereExprListUsage(pMaskSet, pSrc->a[i].u1.pFuncArg); } @@ -147387,7 +153160,9 @@ static int exprMightBeIndexed( assert( TK_ISop==TK_VECTOR && (op>=TK_GT && ALWAYS(op<=TK_GE)) ){ + assert( ExprUseXList(pExpr) ); pExpr = pExpr->x.pList->a[0].pExpr; + } if( pExpr->op==TK_COLUMN ){ @@ -147400,276 +153175,6 @@ static int exprMightBeIndexed( return exprMightBeIndexed2(pFrom,mPrereq,aiCurCol,pExpr); } -/* -** Expression callback for exprUsesSrclist(). -*/ -static int exprUsesSrclistCb(Walker *p, Expr *pExpr){ - if( pExpr->op==TK_COLUMN ){ - SrcList *pSrc = p->u.pSrcList; - int iCsr = pExpr->iTable; - int ii; - for(ii=0; iinSrc; ii++){ - if( pSrc->a[ii].iCursor==iCsr ){ - return p->eCode ? WRC_Abort : WRC_Continue; - } - } - return p->eCode ? WRC_Continue : WRC_Abort; - } - return WRC_Continue; -} - -/* -** Select callback for exprUsesSrclist(). -*/ -static int exprUsesSrclistSelectCb(Walker *NotUsed1, Select *NotUsed2){ - UNUSED_PARAMETER(NotUsed1); - UNUSED_PARAMETER(NotUsed2); - return WRC_Abort; -} - -/* -** This function always returns true if expression pExpr contains -** a sub-select. -** -** If there is no sub-select in pExpr, then return true if pExpr -** contains a TK_COLUMN node for a table that is (bUses==1) -** or is not (bUses==0) in pSrc. -** -** Said another way: -** -** bUses Return Meaning -** -------- ------ ------------------------------------------------ -** -** bUses==1 true pExpr contains either a sub-select or a -** TK_COLUMN referencing pSrc. -** -** bUses==1 false pExpr contains no sub-selects and all TK_COLUMN -** nodes reference tables not found in pSrc -** -** bUses==0 true pExpr contains either a sub-select or a TK_COLUMN -** that references a table not in pSrc. -** -** bUses==0 false pExpr contains no sub-selects and all TK_COLUMN -** nodes reference pSrc -*/ -static int exprUsesSrclist(SrcList *pSrc, Expr *pExpr, int bUses){ - Walker sWalker; - memset(&sWalker, 0, sizeof(Walker)); - sWalker.eCode = bUses; - sWalker.u.pSrcList = pSrc; - sWalker.xExprCallback = exprUsesSrclistCb; - sWalker.xSelectCallback = exprUsesSrclistSelectCb; - return (sqlite3WalkExpr(&sWalker, pExpr)==WRC_Abort); -} - -/* -** Context object used by exprExistsToInIter() as it iterates through an -** expression tree. -*/ -struct ExistsToInCtx { - SrcList *pSrc; /* The tables in an EXISTS(SELECT ... FROM ...) */ - Expr *pInLhs; /* OUT: Use this as the LHS of the IN operator */ - Expr *pEq; /* OUT: The == term that include pInLhs */ - Expr **ppAnd; /* OUT: The AND operator that includes pEq as a child */ - Expr **ppParent; /* The AND operator currently being examined */ -}; - -/* -** Iterate through all AND connected nodes in the expression tree -** headed by (*ppExpr), populating the structure passed as the first -** argument with the values required by exprAnalyzeExistsFindEq(). -** -** This function returns non-zero if the expression tree does not meet -** the two conditions described by the header comment for -** exprAnalyzeExistsFindEq(), or zero if it does. -*/ -static int exprExistsToInIter(struct ExistsToInCtx *p, Expr **ppExpr){ - Expr *pExpr = *ppExpr; - switch( pExpr->op ){ - case TK_AND: - p->ppParent = ppExpr; - if( exprExistsToInIter(p, &pExpr->pLeft) ) return 1; - p->ppParent = ppExpr; - if( exprExistsToInIter(p, &pExpr->pRight) ) return 1; - break; - case TK_EQ: { - int bLeft = exprUsesSrclist(p->pSrc, pExpr->pLeft, 0); - int bRight = exprUsesSrclist(p->pSrc, pExpr->pRight, 0); - if( bLeft || bRight ){ - if( (bLeft && bRight) || p->pInLhs ) return 1; - p->pInLhs = bLeft ? pExpr->pLeft : pExpr->pRight; - if( exprUsesSrclist(p->pSrc, p->pInLhs, 1) ) return 1; - p->pEq = pExpr; - p->ppAnd = p->ppParent; - } - break; - } - default: - if( exprUsesSrclist(p->pSrc, pExpr, 0) ){ - return 1; - } - break; - } - - return 0; -} - -/* -** This function is used by exprAnalyzeExists() when creating virtual IN(...) -** terms equivalent to user-supplied EXIST(...) clauses. It splits the WHERE -** clause of the Select object passed as the first argument into one or more -** expressions joined by AND operators, and then tests if the following are -** true: -** -** 1. Exactly one of the AND separated terms refers to the outer -** query, and it is an == (TK_EQ) expression. -** -** 2. Only one side of the == expression refers to the outer query, and -** it does not refer to any columns from the inner query. -** -** If both these conditions are true, then a pointer to the side of the == -** expression that refers to the outer query is returned. The caller will -** use this expression as the LHS of the IN(...) virtual term. Or, if one -** or both of the above conditions are not true, NULL is returned. -** -** If non-NULL is returned and ppEq is non-NULL, *ppEq is set to point -** to the == expression node before returning. If pppAnd is non-NULL and -** the == node is not the root of the WHERE clause, then *pppAnd is set -** to point to the pointer to the AND node that is the parent of the == -** node within the WHERE expression tree. -*/ -static Expr *exprAnalyzeExistsFindEq( - Select *pSel, /* The SELECT of the EXISTS */ - Expr **ppEq, /* OUT: == node from WHERE clause */ - Expr ***pppAnd /* OUT: Pointer to parent of ==, if any */ -){ - struct ExistsToInCtx ctx; - memset(&ctx, 0, sizeof(ctx)); - ctx.pSrc = pSel->pSrc; - if( exprExistsToInIter(&ctx, &pSel->pWhere) ){ - return 0; - } - if( ppEq ) *ppEq = ctx.pEq; - if( pppAnd ) *pppAnd = ctx.ppAnd; - return ctx.pInLhs; -} - -/* -** Term idxTerm of the WHERE clause passed as the second argument is an -** EXISTS expression with a correlated SELECT statement on the RHS. -** This function analyzes the SELECT statement, and if possible adds an -** equivalent "? IN(SELECT...)" virtual term to the WHERE clause. -** -** For an EXISTS term such as the following: -** -** EXISTS (SELECT ... FROM WHERE = AND ) -** -** The virtual IN() term added is: -** -** IN (SELECT FROM WHERE ) -** -** The virtual term is only added if the following conditions are met: -** -** 1. The sub-select must not be an aggregate or use window functions, -** -** 2. The sub-select must not be a compound SELECT, -** -** 3. Expression must refer to at least one column from the outer -** query, and must not refer to any column from the inner query -** (i.e. from ). -** -** 4. and must not refer to any values from the outer query. -** In other words, once has been removed, the inner query -** must not be correlated. -** -*/ -static void exprAnalyzeExists( - SrcList *pSrc, /* the FROM clause */ - WhereClause *pWC, /* the WHERE clause */ - int idxTerm /* Index of the term to be analyzed */ -){ - Parse *pParse = pWC->pWInfo->pParse; - WhereTerm *pTerm = &pWC->a[idxTerm]; - Expr *pExpr = pTerm->pExpr; - Select *pSel = pExpr->x.pSelect; - Expr *pDup = 0; - Expr *pEq = 0; - Expr *pRet = 0; - Expr *pInLhs = 0; - Expr **ppAnd = 0; - int idxNew; - sqlite3 *db = pParse->db; - - assert( pExpr->op==TK_EXISTS ); - assert( (pExpr->flags & EP_VarSelect) && (pExpr->flags & EP_xIsSelect) ); - - if( pSel->selFlags & SF_Aggregate ) return; -#ifndef SQLITE_OMIT_WINDOWFUNC - if( pSel->pWin ) return; -#endif - if( pSel->pPrior ) return; - if( pSel->pWhere==0 ) return; - if( pSel->pLimit ) return; - if( 0==exprAnalyzeExistsFindEq(pSel, 0, 0) ) return; - - pDup = sqlite3ExprDup(db, pExpr, 0); - if( db->mallocFailed ){ - sqlite3ExprDelete(db, pDup); - return; - } - pSel = pDup->x.pSelect; - sqlite3ExprListDelete(db, pSel->pEList); - pSel->pEList = 0; - - pInLhs = exprAnalyzeExistsFindEq(pSel, &pEq, &ppAnd); - assert( pInLhs && pEq ); - assert( pEq==pSel->pWhere || ppAnd ); - if( pInLhs==pEq->pLeft ){ - pRet = pEq->pRight; - }else{ - CollSeq *p = sqlite3ExprCompareCollSeq(pParse, pEq); - pInLhs = sqlite3ExprAddCollateString(pParse, pInLhs, p?p->zName:"BINARY"); - pRet = pEq->pLeft; - } - - assert( pDup->pLeft==0 ); - pDup->op = TK_IN; - pDup->pLeft = pInLhs; - pDup->flags &= ~EP_VarSelect; - if( pRet->op==TK_VECTOR ){ - pSel->pEList = pRet->x.pList; - pRet->x.pList = 0; - sqlite3ExprDelete(db, pRet); - }else{ - pSel->pEList = sqlite3ExprListAppend(pParse, 0, pRet); - } - pEq->pLeft = 0; - pEq->pRight = 0; - if( ppAnd ){ - Expr *pAnd = *ppAnd; - Expr *pOther = (pAnd->pLeft==pEq) ? pAnd->pRight : pAnd->pLeft; - pAnd->pLeft = pAnd->pRight = 0; - sqlite3ExprDelete(db, pAnd); - *ppAnd = pOther; - }else{ - assert( pSel->pWhere==pEq ); - pSel->pWhere = 0; - } - sqlite3ExprDelete(db, pEq); - -#ifdef WHERETRACE_ENABLED /* 0x20 */ - if( sqlite3WhereTrace & 0x20 ){ - sqlite3DebugPrintf("Convert EXISTS:\n"); - sqlite3TreeViewExpr(0, pExpr, 0); - sqlite3DebugPrintf("into IN:\n"); - sqlite3TreeViewExpr(0, pDup, 0); - } -#endif - idxNew = whereClauseInsert(pWC, pDup, TERM_VIRTUAL|TERM_DYNAMIC); - exprAnalyze(pSrc, pWC, idxNew); - markTermAsChild(pWC, idxNew, idxTerm); - pWC->a[idxTerm].wtFlags |= TERM_COPIED; -} /* ** The input to this routine is an WhereTerm structure with only the @@ -147713,36 +153218,67 @@ static void exprAnalyze( if( db->mallocFailed ){ return; } + assert( pWC->nTerm > idxTerm ); pTerm = &pWC->a[idxTerm]; pMaskSet = &pWInfo->sMaskSet; pExpr = pTerm->pExpr; + assert( pExpr!=0 ); /* Because malloc() has not failed */ assert( pExpr->op!=TK_AS && pExpr->op!=TK_COLLATE ); + pMaskSet->bVarSelect = 0; prereqLeft = sqlite3WhereExprUsage(pMaskSet, pExpr->pLeft); op = pExpr->op; if( op==TK_IN ){ assert( pExpr->pRight==0 ); if( sqlite3ExprCheckIN(pParse, pExpr) ) return; - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ pTerm->prereqRight = exprSelectUsage(pMaskSet, pExpr->x.pSelect); }else{ pTerm->prereqRight = sqlite3WhereExprListUsage(pMaskSet, pExpr->x.pList); } - }else if( op==TK_ISNULL ){ - pTerm->prereqRight = 0; + prereqAll = prereqLeft | pTerm->prereqRight; }else{ pTerm->prereqRight = sqlite3WhereExprUsage(pMaskSet, pExpr->pRight); + if( pExpr->pLeft==0 + || ExprHasProperty(pExpr, EP_xIsSelect|EP_IfNullRow) + || pExpr->x.pList!=0 + ){ + prereqAll = sqlite3WhereExprUsageNN(pMaskSet, pExpr); + }else{ + prereqAll = prereqLeft | pTerm->prereqRight; + } } - pMaskSet->bVarSelect = 0; - prereqAll = sqlite3WhereExprUsageNN(pMaskSet, pExpr); if( pMaskSet->bVarSelect ) pTerm->wtFlags |= TERM_VARSELECT; - if( ExprHasProperty(pExpr, EP_FromJoin) ){ - Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->iRightJoinTable); - prereqAll |= x; - extraRight = x-1; /* ON clause terms may not be used with an index - ** on left table of a LEFT JOIN. Ticket #3015 */ - if( (prereqAll>>1)>=x ){ - sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); - return; + +#ifdef SQLITE_DEBUG + if( prereqAll!=sqlite3WhereExprUsageNN(pMaskSet, pExpr) ){ + printf("\n*** Incorrect prereqAll computed for:\n"); + sqlite3TreeViewExpr(0,pExpr,0); + assert( 0 ); + } +#endif + + if( ExprHasProperty(pExpr, EP_OuterON|EP_InnerON) ){ + Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->w.iJoin); + if( ExprHasProperty(pExpr, EP_OuterON) ){ + prereqAll |= x; + extraRight = x-1; /* ON clause terms may not be used with an index + ** on left table of a LEFT JOIN. Ticket #3015 */ + if( (prereqAll>>1)>=x ){ + sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); + return; + } + }else if( (prereqAll>>1)>=x ){ + /* The ON clause of an INNER JOIN references a table to its right. + ** Most other SQL database engines raise an error. But SQLite versions + ** 3.0 through 3.38 just put the ON clause constraint into the WHERE + ** clause and carried on. Beginning with 3.39, raise an error only + ** if there is a RIGHT or FULL JOIN in the query. This makes SQLite + ** more like other systems, and also preserves legacy. */ + if( ALWAYS(pSrc->nSrc>0) && (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ + sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); + return; + } + ExprClearProperty(pExpr, EP_InnerON); } } pTerm->prereqAll = prereqAll; @@ -147758,17 +153294,20 @@ static void exprAnalyze( if( pTerm->u.x.iField>0 ){ assert( op==TK_IN ); assert( pLeft->op==TK_VECTOR ); + assert( ExprUseXList(pLeft) ); pLeft = pLeft->x.pList->a[pTerm->u.x.iField-1].pExpr; } if( exprMightBeIndexed(pSrc, prereqLeft, aiCurCol, pLeft, op) ){ pTerm->leftCursor = aiCurCol[0]; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); pTerm->u.x.leftColumn = aiCurCol[1]; pTerm->eOperator = operatorMask(op) & opMask; } if( op==TK_IS ) pTerm->wtFlags |= TERM_IS; if( pRight && exprMightBeIndexed(pSrc, pTerm->prereqRight, aiCurCol, pRight, op) + && !ExprHasProperty(pRight, EP_FixedCol) ){ WhereTerm *pNew; Expr *pDup; @@ -147799,12 +153338,18 @@ static void exprAnalyze( } pNew->wtFlags |= exprCommute(pParse, pDup); pNew->leftCursor = aiCurCol[0]; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); pNew->u.x.leftColumn = aiCurCol[1]; testcase( (prereqLeft | extraRight) != prereqLeft ); pNew->prereqRight = prereqLeft | extraRight; pNew->prereqAll = prereqAll; pNew->eOperator = (operatorMask(pDup->op) + eExtraOp) & opMask; - }else if( op==TK_ISNULL && 0==sqlite3ExprCanBeNull(pLeft) ){ + }else + if( op==TK_ISNULL + && !ExprHasProperty(pExpr,EP_OuterON) + && 0==sqlite3ExprCanBeNull(pLeft) + ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); pExpr->op = TK_TRUEFALSE; pExpr->u.zToken = "false"; ExprSetProperty(pExpr, EP_IsFalse); @@ -147830,9 +153375,11 @@ static void exprAnalyze( ** BETWEEN term is skipped. */ else if( pExpr->op==TK_BETWEEN && pWC->op==TK_AND ){ - ExprList *pList = pExpr->x.pList; + ExprList *pList; int i; static const u8 ops[] = {TK_GE, TK_LE}; + assert( ExprUseXList(pExpr) ); + pList = pExpr->x.pList; assert( pList!=0 ); assert( pList->nExpr==2 ); for(i=0; i<2; i++){ @@ -147861,16 +153408,6 @@ static void exprAnalyze( pTerm = &pWC->a[idxTerm]; } #endif /* SQLITE_OMIT_OR_OPTIMIZATION */ - - else if( pExpr->op==TK_EXISTS ){ - /* Perhaps treat an EXISTS operator as an IN operator */ - if( (pExpr->flags & EP_VarSelect)!=0 - && OptimizationEnabled(db, SQLITE_ExistsToIN) - ){ - exprAnalyzeExists(pSrc, pWC, idxTerm); - } - } - /* The form "x IS NOT NULL" can sometimes be evaluated more efficiently ** as "x>NULL" if x is not an INTEGER PRIMARY KEY. So construct a ** virtual term of that form. @@ -147880,7 +153417,7 @@ static void exprAnalyze( else if( pExpr->op==TK_NOTNULL ){ if( pExpr->pLeft->op==TK_COLUMN && pExpr->pLeft->iColumn>=0 - && !ExprHasProperty(pExpr, EP_FromJoin) + && !ExprHasProperty(pExpr, EP_OuterON) ){ Expr *pNewExpr; Expr *pLeft = pExpr->pLeft; @@ -147935,8 +153472,12 @@ static void exprAnalyze( const char *zCollSeqName; /* Name of collating sequence */ const u16 wtFlags = TERM_LIKEOPT | TERM_VIRTUAL | TERM_DYNAMIC; + assert( ExprUseXList(pExpr) ); pLeft = pExpr->x.pList->a[1].pExpr; pStr2 = sqlite3ExprDup(db, pStr1, 0); + assert( pStr1==0 || !ExprHasProperty(pStr1, EP_IntValue) ); + assert( pStr2==0 || !ExprHasProperty(pStr2, EP_IntValue) ); + /* Convert the lower bound to upper-case and the upper bound to ** lower-case (upper-case is less than lower-case in ASCII) so that @@ -147999,7 +153540,10 @@ static void exprAnalyze( ** no longer used. ** ** This is only required if at least one side of the comparison operation - ** is not a sub-select. */ + ** is not a sub-select. + ** + ** tag-20220128a + */ if( (pExpr->op==TK_EQ || pExpr->op==TK_IS) && (nLeft = sqlite3ExprVectorSize(pExpr->pLeft))>1 && sqlite3ExprVectorSize(pExpr->pRight)==nLeft @@ -148011,17 +153555,17 @@ static void exprAnalyze( for(i=0; ipLeft, i); - Expr *pRight = sqlite3ExprForVectorField(pParse, pExpr->pRight, i); + Expr *pLeft = sqlite3ExprForVectorField(pParse, pExpr->pLeft, i, nLeft); + Expr *pRight = sqlite3ExprForVectorField(pParse, pExpr->pRight, i, nLeft); pNew = sqlite3PExpr(pParse, pExpr->op, pLeft, pRight); transferJoinMarkings(pNew, pExpr); - idxNew = whereClauseInsert(pWC, pNew, TERM_DYNAMIC); + idxNew = whereClauseInsert(pWC, pNew, TERM_DYNAMIC|TERM_SLICE); exprAnalyze(pSrc, pWC, idxNew); } pTerm = &pWC->a[idxTerm]; pTerm->wtFlags |= TERM_CODED|TERM_VIRTUAL; /* Disable the original */ - pTerm->eOperator = 0; + pTerm->eOperator = WO_ROWVAL; } /* If there is a vector IN term - e.g. "(a, b) IN (SELECT ...)" - create @@ -148036,6 +153580,7 @@ static void exprAnalyze( else if( pExpr->op==TK_IN && pTerm->u.x.iField==0 && pExpr->pLeft->op==TK_VECTOR + && ALWAYS( ExprUseXSelect(pExpr) ) && pExpr->x.pSelect->pPrior==0 #ifndef SQLITE_OMIT_WINDOWFUNC && pExpr->x.pSelect->pWin==0 @@ -148045,7 +153590,7 @@ static void exprAnalyze( int i; for(i=0; ipLeft); i++){ int idxNew; - idxNew = whereClauseInsert(pWC, pExpr, TERM_VIRTUAL); + idxNew = whereClauseInsert(pWC, pExpr, TERM_VIRTUAL|TERM_SLICE); pWC->a[idxNew].u.x.iField = i+1; exprAnalyze(pSrc, pWC, idxNew); markTermAsChild(pWC, idxNew, idxTerm); @@ -148076,9 +153621,9 @@ static void exprAnalyze( Expr *pNewExpr; pNewExpr = sqlite3PExpr(pParse, TK_MATCH, 0, sqlite3ExprDup(db, pRight, 0)); - if( ExprHasProperty(pExpr, EP_FromJoin) && pNewExpr ){ - ExprSetProperty(pNewExpr, EP_FromJoin); - pNewExpr->iRightJoinTable = pExpr->iRightJoinTable; + if( ExprHasProperty(pExpr, EP_OuterON) && pNewExpr ){ + ExprSetProperty(pNewExpr, EP_OuterON); + pNewExpr->w.iJoin = pExpr->w.iJoin; } idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); testcase( idxNew==0 ); @@ -148141,6 +153686,113 @@ SQLITE_PRIVATE void sqlite3WhereSplit(WhereClause *pWC, Expr *pExpr, u8 op){ } } +/* +** Add either a LIMIT (if eMatchOp==SQLITE_INDEX_CONSTRAINT_LIMIT) or +** OFFSET (if eMatchOp==SQLITE_INDEX_CONSTRAINT_OFFSET) term to the +** where-clause passed as the first argument. The value for the term +** is found in register iReg. +** +** In the common case where the value is a simple integer +** (example: "LIMIT 5 OFFSET 10") then the expression codes as a +** TK_INTEGER so that it will be available to sqlite3_vtab_rhs_value(). +** If not, then it codes as a TK_REGISTER expression. +*/ +static void whereAddLimitExpr( + WhereClause *pWC, /* Add the constraint to this WHERE clause */ + int iReg, /* Register that will hold value of the limit/offset */ + Expr *pExpr, /* Expression that defines the limit/offset */ + int iCsr, /* Cursor to which the constraint applies */ + int eMatchOp /* SQLITE_INDEX_CONSTRAINT_LIMIT or _OFFSET */ +){ + Parse *pParse = pWC->pWInfo->pParse; + sqlite3 *db = pParse->db; + Expr *pNew; + int iVal = 0; + + if( sqlite3ExprIsInteger(pExpr, &iVal) && iVal>=0 ){ + Expr *pVal = sqlite3Expr(db, TK_INTEGER, 0); + if( pVal==0 ) return; + ExprSetProperty(pVal, EP_IntValue); + pVal->u.iValue = iVal; + pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal); + }else{ + Expr *pVal = sqlite3Expr(db, TK_REGISTER, 0); + if( pVal==0 ) return; + pVal->iTable = iReg; + pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal); + } + if( pNew ){ + WhereTerm *pTerm; + int idx; + idx = whereClauseInsert(pWC, pNew, TERM_DYNAMIC|TERM_VIRTUAL); + pTerm = &pWC->a[idx]; + pTerm->leftCursor = iCsr; + pTerm->eOperator = WO_AUX; + pTerm->eMatchOp = eMatchOp; + } +} + +/* +** Possibly add terms corresponding to the LIMIT and OFFSET clauses of the +** SELECT statement passed as the second argument. These terms are only +** added if: +** +** 1. The SELECT statement has a LIMIT clause, and +** 2. The SELECT statement is not an aggregate or DISTINCT query, and +** 3. The SELECT statement has exactly one object in its from clause, and +** that object is a virtual table, and +** 4. There are no terms in the WHERE clause that will not be passed +** to the virtual table xBestIndex method. +** 5. The ORDER BY clause, if any, will be made available to the xBestIndex +** method. +** +** LIMIT and OFFSET terms are ignored by most of the planner code. They +** exist only so that they may be passed to the xBestIndex method of the +** single virtual table in the FROM clause of the SELECT. +*/ +SQLITE_PRIVATE void sqlite3WhereAddLimit(WhereClause *pWC, Select *p){ + assert( p==0 || (p->pGroupBy==0 && (p->selFlags & SF_Aggregate)==0) ); + if( (p && p->pLimit) /* 1 */ + && (p->selFlags & (SF_Distinct|SF_Aggregate))==0 /* 2 */ + && (p->pSrc->nSrc==1 && IsVirtual(p->pSrc->a[0].pTab)) /* 3 */ + ){ + ExprList *pOrderBy = p->pOrderBy; + int iCsr = p->pSrc->a[0].iCursor; + int ii; + + /* Check condition (4). Return early if it is not met. */ + for(ii=0; iinTerm; ii++){ + if( pWC->a[ii].wtFlags & TERM_CODED ){ + /* This term is a vector operation that has been decomposed into + ** other, subsequent terms. It can be ignored. See tag-20220128a */ + assert( pWC->a[ii].wtFlags & TERM_VIRTUAL ); + assert( pWC->a[ii].eOperator==WO_ROWVAL ); + continue; + } + if( pWC->a[ii].leftCursor!=iCsr ) return; + } + + /* Check condition (5). Return early if it is not met. */ + if( pOrderBy ){ + for(ii=0; iinExpr; ii++){ + Expr *pExpr = pOrderBy->a[ii].pExpr; + if( pExpr->op!=TK_COLUMN ) return; + if( pExpr->iTable!=iCsr ) return; + if( pOrderBy->a[ii].fg.sortFlags & KEYINFO_ORDER_BIGNULL ) return; + } + } + + /* All conditions are met. Add the terms to the where-clause object. */ + assert( p->pLimit->op==TK_LIMIT ); + whereAddLimitExpr(pWC, p->iLimit, p->pLimit->pLeft, + iCsr, SQLITE_INDEX_CONSTRAINT_LIMIT); + if( p->iOffset>0 ){ + whereAddLimitExpr(pWC, p->iOffset, p->pLimit->pRight, + iCsr, SQLITE_INDEX_CONSTRAINT_OFFSET); + } + } +} + /* ** Initialize a preallocated WhereClause structure. */ @@ -148152,6 +153804,7 @@ SQLITE_PRIVATE void sqlite3WhereClauseInit( pWC->hasOr = 0; pWC->pOuter = 0; pWC->nTerm = 0; + pWC->nBase = 0; pWC->nSlot = ArraySize(pWC->aStatic); pWC->a = pWC->aStatic; } @@ -148162,22 +153815,36 @@ SQLITE_PRIVATE void sqlite3WhereClauseInit( ** sqlite3WhereClauseInit(). */ SQLITE_PRIVATE void sqlite3WhereClauseClear(WhereClause *pWC){ - int i; - WhereTerm *a; sqlite3 *db = pWC->pWInfo->pParse->db; - for(i=pWC->nTerm-1, a=pWC->a; i>=0; i--, a++){ - if( a->wtFlags & TERM_DYNAMIC ){ - sqlite3ExprDelete(db, a->pExpr); + assert( pWC->nTerm>=pWC->nBase ); + if( pWC->nTerm>0 ){ + WhereTerm *a = pWC->a; + WhereTerm *aLast = &pWC->a[pWC->nTerm-1]; +#ifdef SQLITE_DEBUG + int i; + /* Verify that every term past pWC->nBase is virtual */ + for(i=pWC->nBase; inTerm; i++){ + assert( (pWC->a[i].wtFlags & TERM_VIRTUAL)!=0 ); } - if( a->wtFlags & TERM_ORINFO ){ - whereOrInfoDelete(db, a->u.pOrInfo); - }else if( a->wtFlags & TERM_ANDINFO ){ - whereAndInfoDelete(db, a->u.pAndInfo); +#endif + while(1){ + assert( a->eMatchOp==0 || a->eOperator==WO_AUX ); + if( a->wtFlags & TERM_DYNAMIC ){ + sqlite3ExprDelete(db, a->pExpr); + } + if( a->wtFlags & (TERM_ORINFO|TERM_ANDINFO) ){ + if( a->wtFlags & TERM_ORINFO ){ + assert( (a->wtFlags & TERM_ANDINFO)==0 ); + whereOrInfoDelete(db, a->u.pOrInfo); + }else{ + assert( (a->wtFlags & TERM_ANDINFO)!=0 ); + whereAndInfoDelete(db, a->u.pAndInfo); + } + } + if( a==aLast ) break; + a++; } } - if( pWC->a!=pWC->aStatic ){ - sqlite3DbFree(db, pWC->a); - } } @@ -148185,28 +153852,52 @@ SQLITE_PRIVATE void sqlite3WhereClauseClear(WhereClause *pWC){ ** These routines walk (recursively) an expression tree and generate ** a bitmask indicating which tables are used in that expression ** tree. +** +** sqlite3WhereExprUsage(MaskSet, Expr) -> +** +** Return a Bitmask of all tables referenced by Expr. Expr can be +** be NULL, in which case 0 is returned. +** +** sqlite3WhereExprUsageNN(MaskSet, Expr) -> +** +** Same as sqlite3WhereExprUsage() except that Expr must not be +** NULL. The "NN" suffix on the name stands for "Not Null". +** +** sqlite3WhereExprListUsage(MaskSet, ExprList) -> +** +** Return a Bitmask of all tables referenced by every expression +** in the expression list ExprList. ExprList can be NULL, in which +** case 0 is returned. +** +** sqlite3WhereExprUsageFull(MaskSet, ExprList) -> +** +** Internal use only. Called only by sqlite3WhereExprUsageNN() for +** complex expressions that require pushing register values onto +** the stack. Many calls to sqlite3WhereExprUsageNN() do not need +** the more complex analysis done by this routine. Hence, the +** computations done by this routine are broken out into a separate +** "no-inline" function to avoid the stack push overhead in the +** common case where it is not needed. */ -SQLITE_PRIVATE Bitmask sqlite3WhereExprUsageNN(WhereMaskSet *pMaskSet, Expr *p){ +static SQLITE_NOINLINE Bitmask sqlite3WhereExprUsageFull( + WhereMaskSet *pMaskSet, + Expr *p +){ Bitmask mask; - if( p->op==TK_COLUMN && !ExprHasProperty(p, EP_FixedCol) ){ - return sqlite3WhereGetMask(pMaskSet, p->iTable); - }else if( ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){ - assert( p->op!=TK_IF_NULL_ROW ); - return 0; - } mask = (p->op==TK_IF_NULL_ROW) ? sqlite3WhereGetMask(pMaskSet, p->iTable) : 0; if( p->pLeft ) mask |= sqlite3WhereExprUsageNN(pMaskSet, p->pLeft); if( p->pRight ){ mask |= sqlite3WhereExprUsageNN(pMaskSet, p->pRight); assert( p->x.pList==0 ); - }else if( ExprHasProperty(p, EP_xIsSelect) ){ + }else if( ExprUseXSelect(p) ){ if( ExprHasProperty(p, EP_VarSelect) ) pMaskSet->bVarSelect = 1; mask |= exprSelectUsage(pMaskSet, p->x.pSelect); }else if( p->x.pList ){ mask |= sqlite3WhereExprListUsage(pMaskSet, p->x.pList); } #ifndef SQLITE_OMIT_WINDOWFUNC - if( (p->op==TK_FUNCTION || p->op==TK_AGG_FUNCTION) && p->y.pWin ){ + if( (p->op==TK_FUNCTION || p->op==TK_AGG_FUNCTION) && ExprUseYWin(p) ){ + assert( p->y.pWin!=0 ); mask |= sqlite3WhereExprListUsage(pMaskSet, p->y.pWin->pPartition); mask |= sqlite3WhereExprListUsage(pMaskSet, p->y.pWin->pOrderBy); mask |= sqlite3WhereExprUsage(pMaskSet, p->y.pWin->pFilter); @@ -148214,6 +153905,15 @@ SQLITE_PRIVATE Bitmask sqlite3WhereExprUsageNN(WhereMaskSet *pMaskSet, Expr *p){ #endif return mask; } +SQLITE_PRIVATE Bitmask sqlite3WhereExprUsageNN(WhereMaskSet *pMaskSet, Expr *p){ + if( p->op==TK_COLUMN && !ExprHasProperty(p, EP_FixedCol) ){ + return sqlite3WhereGetMask(pMaskSet, p->iTable); + }else if( ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){ + assert( p->op!=TK_IF_NULL_ROW ); + return 0; + } + return sqlite3WhereExprUsageFull(pMaskSet, p); +} SQLITE_PRIVATE Bitmask sqlite3WhereExprUsage(WhereMaskSet *pMaskSet, Expr *p){ return p ? sqlite3WhereExprUsageNN(pMaskSet,p) : 0; } @@ -148271,6 +153971,7 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs( if( pArgs==0 ) return; for(j=k=0; jnExpr; j++){ Expr *pRhs; + u32 joinType; while( knCol && (pTab->aCol[k].colFlags & COLFLAG_HIDDEN)==0 ){k++;} if( k>=pTab->nCol ){ sqlite3ErrorMsg(pParse, "too many arguments on %s() - max %d", @@ -148281,13 +153982,18 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs( if( pColRef==0 ) return; pColRef->iTable = pItem->iCursor; pColRef->iColumn = k++; + assert( ExprUseYTab(pColRef) ); pColRef->y.pTab = pTab; + pItem->colUsed |= sqlite3ExprColUsed(pColRef); pRhs = sqlite3PExpr(pParse, TK_UPLUS, sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0), 0); pTerm = sqlite3PExpr(pParse, TK_EQ, pColRef, pRhs); - if( pItem->fg.jointype & JT_LEFT ){ - sqlite3SetJoinExpr(pTerm, pItem->iCursor); + if( pItem->fg.jointype & (JT_LEFT|JT_LTORJ) ){ + joinType = EP_OuterON; + }else{ + joinType = EP_InnerON; } + sqlite3SetJoinExpr(pTerm, pItem->iCursor, joinType); whereClauseInsert(pWC, pTerm, TERM_DYNAMIC); } } @@ -148326,8 +154032,14 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs( */ typedef struct HiddenIndexInfo HiddenIndexInfo; struct HiddenIndexInfo { - WhereClause *pWC; /* The Where clause being analyzed */ - Parse *pParse; /* The parsing context */ + WhereClause *pWC; /* The Where clause being analyzed */ + Parse *pParse; /* The parsing context */ + int eDistinct; /* Value to return from sqlite3_vtab_distinct() */ + u32 mIn; /* Mask of terms that are IN (...) */ + u32 mHandleIn; /* Terms that vtab will handle as IN (...) */ + sqlite3_value *aRhs[1]; /* RHS values for constraints. MUST BE LAST + ** because extra space is allocated to hold up + ** to nTerm such values */ }; /* Forward declaration of methods */ @@ -148392,7 +154104,7 @@ SQLITE_PRIVATE int sqlite3WhereOrderByLimitOptLabel(WhereInfo *pWInfo){ } pInner = &pWInfo->a[pWInfo->nLevel-1]; assert( pInner->addrNxt!=0 ); - return pInner->addrNxt; + return pInner->pRJ ? pWInfo->iContinue : pInner->addrNxt; } /* @@ -148530,7 +154242,12 @@ whereOrInsert_done: SQLITE_PRIVATE Bitmask sqlite3WhereGetMask(WhereMaskSet *pMaskSet, int iCursor){ int i; assert( pMaskSet->n<=(int)sizeof(Bitmask)*8 ); - for(i=0; in; i++){ + assert( pMaskSet->n>0 || pMaskSet->ix[0]<0 ); + assert( iCursor>=-1 ); + if( pMaskSet->ix[0]==iCursor ){ + return 1; + } + for(i=1; in; i++){ if( pMaskSet->ix[i]==iCursor ){ return MASKBIT(i); } @@ -148538,6 +154255,30 @@ SQLITE_PRIVATE Bitmask sqlite3WhereGetMask(WhereMaskSet *pMaskSet, int iCursor){ return 0; } +/* Allocate memory that is automatically freed when pWInfo is freed. +*/ +SQLITE_PRIVATE void *sqlite3WhereMalloc(WhereInfo *pWInfo, u64 nByte){ + WhereMemBlock *pBlock; + pBlock = sqlite3DbMallocRawNN(pWInfo->pParse->db, nByte+sizeof(*pBlock)); + if( pBlock ){ + pBlock->pNext = pWInfo->pMemToFree; + pBlock->sz = nByte; + pWInfo->pMemToFree = pBlock; + pBlock++; + } + return (void*)pBlock; +} +SQLITE_PRIVATE void *sqlite3WhereRealloc(WhereInfo *pWInfo, void *pOld, u64 nByte){ + void *pNew = sqlite3WhereMalloc(pWInfo, nByte); + if( pNew && pOld ){ + WhereMemBlock *pOldBlk = (WhereMemBlock*)pOld; + pOldBlk--; + assert( pOldBlk->szsz); + } + return pNew; +} + /* ** Create a new mask for cursor iCursor. ** @@ -148557,7 +154298,9 @@ static void createMask(WhereMaskSet *pMaskSet, int iCursor){ */ static Expr *whereRightSubexprIsColumn(Expr *p){ p = sqlite3ExprSkipCollateAndLikely(p->pRight); - if( ALWAYS(p!=0) && p->op==TK_COLUMN ) return p; + if( ALWAYS(p!=0) && p->op==TK_COLUMN && !ExprHasProperty(p, EP_FixedCol) ){ + return p; + } return 0; } @@ -148580,14 +154323,16 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ iColumn = pScan->aiColumn[pScan->iEquiv-1]; iCur = pScan->aiCur[pScan->iEquiv-1]; assert( pWC!=0 ); + assert( iCur>=0 ); do{ for(pTerm=pWC->a+k; knTerm; k++, pTerm++){ + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 || pTerm->leftCursor<0 ); if( pTerm->leftCursor==iCur && pTerm->u.x.leftColumn==iColumn && (iColumn!=XN_EXPR || sqlite3ExprCompareSkip(pTerm->pExpr->pLeft, pScan->pIdxExpr,iCur)==0) - && (pScan->iEquiv<=1 || !ExprHasProperty(pTerm->pExpr, EP_FromJoin)) + && (pScan->iEquiv<=1 || !ExprHasProperty(pTerm->pExpr, EP_OuterON)) ){ if( (pTerm->eOperator & WO_EQUIV)!=0 && pScan->nEquivaiCur) @@ -148623,7 +154368,8 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ } } if( (pTerm->eOperator & (WO_EQ|WO_IS))!=0 - && (pX = pTerm->pExpr->pRight)->op==TK_COLUMN + && (pX = pTerm->pExpr->pRight, ALWAYS(pX!=0)) + && pX->op==TK_COLUMN && pX->iTable==pScan->aiCur[0] && pX->iColumn==pScan->aiColumn[0] ){ @@ -148632,6 +154378,18 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ } pScan->pWC = pWC; pScan->k = k+1; +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x20000 ){ + int ii; + sqlite3DebugPrintf("SCAN-TERM %p: nEquiv=%d", + pTerm, pScan->nEquiv); + for(ii=0; iinEquiv; ii++){ + sqlite3DebugPrintf(" {%d:%d}", + pScan->aiCur[ii], pScan->aiColumn[ii]); + } + sqlite3DebugPrintf("\n"); + } +#endif return pTerm; } } @@ -148698,16 +154456,16 @@ static WhereTerm *whereScanInit( if( pIdx ){ int j = iColumn; iColumn = pIdx->aiColumn[j]; - if( iColumn==XN_EXPR ){ - pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr; - pScan->zCollName = pIdx->azColl[j]; - pScan->aiColumn[0] = XN_EXPR; - return whereScanInitIndexExpr(pScan); - }else if( iColumn==pIdx->pTable->iPKey ){ + if( iColumn==pIdx->pTable->iPKey ){ iColumn = XN_ROWID; }else if( iColumn>=0 ){ pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity; pScan->zCollName = pIdx->azColl[j]; + }else if( iColumn==XN_EXPR ){ + pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr; + pScan->zCollName = pIdx->azColl[j]; + pScan->aiColumn[0] = XN_EXPR; + return whereScanInitIndexExpr(pScan); } }else if( iColumn==XN_EXPR ){ return 0; @@ -148788,7 +154546,7 @@ static int findIndexCol( for(i=0; inExpr; i++){ Expr *p = sqlite3ExprSkipCollateAndLikely(pList->a[i].pExpr); if( ALWAYS(p!=0) - && p->op==TK_COLUMN + && (p->op==TK_COLUMN || p->op==TK_AGG_COLUMN) && p->iColumn==pIdx->aiColumn[iCol] && p->iTable==iBase ){ @@ -148853,7 +154611,8 @@ static int isDistinctRedundant( for(i=0; inExpr; i++){ Expr *p = sqlite3ExprSkipCollateAndLikely(pDistinct->a[i].pExpr); if( NEVER(p==0) ) continue; - if( p->op==TK_COLUMN && p->iTable==iBase && p->iColumn<0 ) return 1; + if( p->op!=TK_COLUMN && p->op!=TK_AGG_COLUMN ) continue; + if( p->iTable==iBase && p->iColumn<0 ) return 1; } /* Loop through all indices on the table, checking each to see if it makes @@ -148871,6 +154630,7 @@ static int isDistinctRedundant( */ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ if( !IsUniqueIndex(pIdx) ) continue; + if( pIdx->pPartIdxWhere ) continue; for(i=0; inKeyCol; i++){ if( 0==sqlite3WhereFindTerm(pWC, iBase, i, ~(Bitmask)0, WO_EQ, pIdx) ){ if( findIndexCol(pParse, pDistinct, iBase, pIdx, i)<0 ) break; @@ -148924,15 +154684,16 @@ static void translateColumnToCopy( pOp->p1 = pOp->p2 + iRegister; pOp->p2 = pOp->p3; pOp->p3 = 0; + pOp->p5 = 2; /* Cause the MEM_Subtype flag to be cleared */ }else if( pOp->opcode==OP_Rowid ){ - if( iAutoidxCur ){ - pOp->opcode = OP_Sequence; - pOp->p1 = iAutoidxCur; - }else{ + pOp->opcode = OP_Sequence; + pOp->p1 = iAutoidxCur; +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( iAutoidxCur==0 ){ pOp->opcode = OP_Null; - pOp->p1 = 0; pOp->p3 = 0; } +#endif } } } @@ -148948,12 +154709,14 @@ static void whereTraceIndexInfoInputs(sqlite3_index_info *p){ int i; if( !sqlite3WhereTrace ) return; for(i=0; inConstraint; i++){ - sqlite3DebugPrintf(" constraint[%d]: col=%d termid=%d op=%d usabled=%d\n", + sqlite3DebugPrintf( + " constraint[%d]: col=%d termid=%d op=%d usabled=%d collseq=%s\n", i, p->aConstraint[i].iColumn, p->aConstraint[i].iTermOffset, p->aConstraint[i].op, - p->aConstraint[i].usable); + p->aConstraint[i].usable, + sqlite3_vtab_collation(p,i)); } for(i=0; inOrderBy; i++){ sqlite3DebugPrintf(" orderby[%d]: col=%d desc=%d\n", @@ -148989,23 +154752,27 @@ static void whereTraceIndexInfoOutputs(sqlite3_index_info *p){ ** index existed. */ static int termCanDriveIndex( - WhereTerm *pTerm, /* WHERE clause term to check */ - SrcItem *pSrc, /* Table we are trying to access */ - Bitmask notReady /* Tables in outer loops of the join */ + const WhereTerm *pTerm, /* WHERE clause term to check */ + const SrcItem *pSrc, /* Table we are trying to access */ + const Bitmask notReady /* Tables in outer loops of the join */ ){ char aff; if( pTerm->leftCursor!=pSrc->iCursor ) return 0; if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) return 0; - if( (pSrc->fg.jointype & JT_LEFT) - && !ExprHasProperty(pTerm->pExpr, EP_FromJoin) - && (pTerm->eOperator & WO_IS) - ){ - /* Cannot use an IS term from the WHERE clause as an index driver for - ** the RHS of a LEFT JOIN. Such a term can only be used if it is from - ** the ON clause. */ - return 0; + assert( (pSrc->fg.jointype & JT_RIGHT)==0 ); + if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ){ + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT ); + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ ); + testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) + testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) ); + if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) + || pTerm->pExpr->w.iJoin != pSrc->iCursor + ){ + return 0; /* See tag-20191211-001 */ + } } if( (pTerm->prereqRight & notReady)!=0 ) return 0; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); if( pTerm->u.x.leftColumn<0 ) return 0; aff = pSrc->pTab->aCol[pTerm->u.x.leftColumn].affinity; if( !sqlite3IndexAffinityOk(pTerm->pExpr, aff) ) return 0; @@ -149021,11 +154788,11 @@ static int termCanDriveIndex( ** and to set up the WhereLevel object pLevel so that the code generator ** makes use of the automatic index. */ -static void constructAutomaticIndex( +static SQLITE_NOINLINE void constructAutomaticIndex( Parse *pParse, /* The parsing context */ - WhereClause *pWC, /* The WHERE clause */ - SrcItem *pSrc, /* The FROM clause term to get the next index */ - Bitmask notReady, /* Mask of cursors that are not available */ + const WhereClause *pWC, /* The WHERE clause */ + const SrcItem *pSrc, /* The FROM clause term to get the next index */ + const Bitmask notReady, /* Mask of cursors that are not available */ WhereLevel *pLevel /* Write new index here */ ){ int nKeyCol; /* Number of columns in the constructed index */ @@ -149067,25 +154834,27 @@ static void constructAutomaticIndex( idxCols = 0; for(pTerm=pWC->a; pTermpExpr; - assert( !ExprHasProperty(pExpr, EP_FromJoin) /* prereq always non-zero */ - || pExpr->iRightJoinTable!=pSrc->iCursor /* for the right-hand */ - || pLoop->prereq!=0 ); /* table of a LEFT JOIN */ - if( pLoop->prereq==0 - && (pTerm->wtFlags & TERM_VIRTUAL)==0 - && !ExprHasProperty(pExpr, EP_FromJoin) - && sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor) ){ + /* Make the automatic index a partial index if there are terms in the + ** WHERE clause (or the ON clause of a LEFT join) that constrain which + ** rows of the target table (pSrc) that can be used. */ + if( (pTerm->wtFlags & TERM_VIRTUAL)==0 + && sqlite3ExprIsTableConstraint(pExpr, pSrc) + ){ pPartial = sqlite3ExprAnd(pParse, pPartial, sqlite3ExprDup(pParse->db, pExpr, 0)); } if( termCanDriveIndex(pTerm, pSrc, notReady) ){ - int iCol = pTerm->u.x.leftColumn; - Bitmask cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); + int iCol; + Bitmask cMask; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + iCol = pTerm->u.x.leftColumn; + cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); testcase( iCol==BMS ); testcase( iCol==BMS-1 ); if( !sentWarning ){ sqlite3_log(SQLITE_WARNING_AUTOINDEX, "automatic index on %s(%s)", pTable->zName, - pTable->aCol[iCol].zName); + pTable->aCol[iCol].zCnName); sentWarning = 1; } if( (idxCols & cMask)==0 ){ @@ -149097,7 +154866,7 @@ static void constructAutomaticIndex( } } } - assert( nKeyCol>0 ); + assert( nKeyCol>0 || pParse->db->mallocFailed ); pLoop->u.btree.nEq = pLoop->nLTerm = nKeyCol; pLoop->wsFlags = WHERE_COLUMN_EQ | WHERE_IDX_ONLY | WHERE_INDEXED | WHERE_AUTO_INDEX; @@ -149131,8 +154900,11 @@ static void constructAutomaticIndex( idxCols = 0; for(pTerm=pWC->a; pTermu.x.leftColumn; - Bitmask cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); + int iCol; + Bitmask cMask; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + iCol = pTerm->u.x.leftColumn; + cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); testcase( iCol==BMS-1 ); testcase( iCol==BMS ); if( (idxCols & cMask)==0 ){ @@ -149174,6 +154946,10 @@ static void constructAutomaticIndex( sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol+1); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "for %s", pTable->zName)); + if( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) ){ + pLevel->regFilter = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Blob, 10000, pLevel->regFilter); + } /* Fill the automatic index with content */ pTabItem = &pWC->pWInfo->pTabList->a[pLevel->iFrom]; @@ -149196,6 +154972,10 @@ static void constructAutomaticIndex( regBase = sqlite3GenerateIndexKey( pParse, pIdx, pLevel->iTabCur, regRecord, 0, 0, 0, 0 ); + if( pLevel->regFilter ){ + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, + regBase, pLoop->u.btree.nEq); + } sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); if( pPartial ) sqlite3VdbeResolveLabel(v, iContinue); @@ -149222,22 +155002,149 @@ end_auto_index_create: } #endif /* SQLITE_OMIT_AUTOMATIC_INDEX */ +/* +** Generate bytecode that will initialize a Bloom filter that is appropriate +** for pLevel. +** +** If there are inner loops within pLevel that have the WHERE_BLOOMFILTER +** flag set, initialize a Bloomfilter for them as well. Except don't do +** this recursive initialization if the SQLITE_BloomPulldown optimization has +** been turned off. +** +** When the Bloom filter is initialized, the WHERE_BLOOMFILTER flag is cleared +** from the loop, but the regFilter value is set to a register that implements +** the Bloom filter. When regFilter is positive, the +** sqlite3WhereCodeOneLoopStart() will generate code to test the Bloom filter +** and skip the subsequence B-Tree seek if the Bloom filter indicates that +** no matching rows exist. +** +** This routine may only be called if it has previously been determined that +** the loop would benefit from a Bloom filter, and the WHERE_BLOOMFILTER bit +** is set. +*/ +static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( + WhereInfo *pWInfo, /* The WHERE clause */ + int iLevel, /* Index in pWInfo->a[] that is pLevel */ + WhereLevel *pLevel, /* Make a Bloom filter for this FROM term */ + Bitmask notReady /* Loops that are not ready */ +){ + int addrOnce; /* Address of opening OP_Once */ + int addrTop; /* Address of OP_Rewind */ + int addrCont; /* Jump here to skip a row */ + const WhereTerm *pTerm; /* For looping over WHERE clause terms */ + const WhereTerm *pWCEnd; /* Last WHERE clause term */ + Parse *pParse = pWInfo->pParse; /* Parsing context */ + Vdbe *v = pParse->pVdbe; /* VDBE under construction */ + WhereLoop *pLoop = pLevel->pWLoop; /* The loop being coded */ + int iCur; /* Cursor for table getting the filter */ + + assert( pLoop!=0 ); + assert( v!=0 ); + assert( pLoop->wsFlags & WHERE_BLOOMFILTER ); + + addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + do{ + const SrcItem *pItem; + const Table *pTab; + u64 sz; + sqlite3WhereExplainBloomFilter(pParse, pWInfo, pLevel); + addrCont = sqlite3VdbeMakeLabel(pParse); + iCur = pLevel->iTabCur; + pLevel->regFilter = ++pParse->nMem; + + /* The Bloom filter is a Blob held in a register. Initialize it + ** to zero-filled blob of at least 80K bits, but maybe more if the + ** estimated size of the table is larger. We could actually + ** measure the size of the table at run-time using OP_Count with + ** P3==1 and use that value to initialize the blob. But that makes + ** testing complicated. By basing the blob size on the value in the + ** sqlite_stat1 table, testing is much easier. + */ + pItem = &pWInfo->pTabList->a[pLevel->iFrom]; + assert( pItem!=0 ); + pTab = pItem->pTab; + assert( pTab!=0 ); + sz = sqlite3LogEstToInt(pTab->nRowLogEst); + if( sz<10000 ){ + sz = 10000; + }else if( sz>10000000 ){ + sz = 10000000; + } + sqlite3VdbeAddOp2(v, OP_Blob, (int)sz, pLevel->regFilter); + + addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v); + pWCEnd = &pWInfo->sWC.a[pWInfo->sWC.nTerm]; + for(pTerm=pWInfo->sWC.a; pTermpExpr; + if( (pTerm->wtFlags & TERM_VIRTUAL)==0 + && sqlite3ExprIsTableConstraint(pExpr, pItem) + ){ + sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL); + } + } + if( pLoop->wsFlags & WHERE_IPK ){ + int r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp2(v, OP_Rowid, iCur, r1); + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, r1, 1); + sqlite3ReleaseTempReg(pParse, r1); + }else{ + Index *pIdx = pLoop->u.btree.pIndex; + int n = pLoop->u.btree.nEq; + int r1 = sqlite3GetTempRange(pParse, n); + int jj; + for(jj=0; jjaiColumn[jj]; + assert( pIdx->pTable==pItem->pTab ); + sqlite3ExprCodeGetColumnOfTable(v, pIdx->pTable, iCur, iCol,r1+jj); + } + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, r1, n); + sqlite3ReleaseTempRange(pParse, r1, n); + } + sqlite3VdbeResolveLabel(v, addrCont); + sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addrTop); + pLoop->wsFlags &= ~WHERE_BLOOMFILTER; + if( OptimizationDisabled(pParse->db, SQLITE_BloomPulldown) ) break; + while( ++iLevel < pWInfo->nLevel ){ + const SrcItem *pTabItem; + pLevel = &pWInfo->a[iLevel]; + pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; + if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ) ) continue; + pLoop = pLevel->pWLoop; + if( NEVER(pLoop==0) ) continue; + if( pLoop->prereq & notReady ) continue; + if( (pLoop->wsFlags & (WHERE_BLOOMFILTER|WHERE_COLUMN_IN)) + ==WHERE_BLOOMFILTER + ){ + /* This is a candidate for bloom-filter pull-down (early evaluation). + ** The test that WHERE_COLUMN_IN is omitted is important, as we are + ** not able to do early evaluation of bloom filters that make use of + ** the IN operator */ + break; + } + } + }while( iLevel < pWInfo->nLevel ); + sqlite3VdbeJumpHere(v, addrOnce); +} + + #ifndef SQLITE_OMIT_VIRTUALTABLE /* ** Allocate and populate an sqlite3_index_info structure. It is the ** responsibility of the caller to eventually release the structure -** by passing the pointer returned by this function to sqlite3_free(). +** by passing the pointer returned by this function to freeIndexInfo(). */ static sqlite3_index_info *allocateIndexInfo( - Parse *pParse, /* The parsing context */ + WhereInfo *pWInfo, /* The WHERE clause */ WhereClause *pWC, /* The WHERE clause being analyzed */ Bitmask mUnusable, /* Ignore terms with these prereqs */ SrcItem *pSrc, /* The FROM clause term that is the vtab */ - ExprList *pOrderBy, /* The ORDER BY clause */ u16 *pmNoOmit /* Mask of terms not to omit */ ){ int i, j; int nTerm; + Parse *pParse = pWInfo->pParse; struct sqlite3_index_constraint *pIdxCons; struct sqlite3_index_orderby *pIdxOrderBy; struct sqlite3_index_constraint_usage *pUsage; @@ -149246,10 +155153,21 @@ static sqlite3_index_info *allocateIndexInfo( int nOrderBy; sqlite3_index_info *pIdxInfo; u16 mNoOmit = 0; + const Table *pTab; + int eDistinct = 0; + ExprList *pOrderBy = pWInfo->pOrderBy; - /* Count the number of possible WHERE clause constraints referring - ** to this virtual table */ + assert( pSrc!=0 ); + pTab = pSrc->pTab; + assert( pTab!=0 ); + assert( IsVirtual(pTab) ); + + /* Find all WHERE clause constraints referring to this virtual table. + ** Mark each term with the TERM_OK flag. Set nTerm to the number of + ** terms found. + */ for(i=nTerm=0, pTerm=pWC->a; inTerm; i++, pTerm++){ + pTerm->wtFlags &= ~TERM_OK; if( pTerm->leftCursor != pSrc->iCursor ) continue; if( pTerm->prereqRight & mUnusable ) continue; assert( IsPowerOfTwo(pTerm->eOperator & ~WO_EQUIV) ); @@ -149259,8 +155177,29 @@ static sqlite3_index_info *allocateIndexInfo( testcase( pTerm->eOperator & WO_ALL ); if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue; if( pTerm->wtFlags & TERM_VNULL ) continue; - assert( pTerm->u.x.leftColumn>=(-1) ); + + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + assert( pTerm->u.x.leftColumn>=XN_ROWID ); + assert( pTerm->u.x.leftColumnnCol ); + + /* tag-20191211-002: WHERE-clause constraints are not useful to the + ** right-hand table of a LEFT JOIN nor to the either table of a + ** RIGHT JOIN. See tag-20191211-001 for the + ** equivalent restriction for ordinary tables. */ + if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ){ + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT ); + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_RIGHT ); + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ ); + testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) ); + testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) ); + if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) + || pTerm->pExpr->w.iJoin != pSrc->iCursor + ){ + continue; + } + } nTerm++; + pTerm->wtFlags |= TERM_OK; } /* If the ORDER BY clause contains only columns in the current @@ -149272,11 +155211,49 @@ static sqlite3_index_info *allocateIndexInfo( int n = pOrderBy->nExpr; for(i=0; ia[i].pExpr; - if( pExpr->op!=TK_COLUMN || pExpr->iTable!=pSrc->iCursor ) break; - if( pOrderBy->a[i].sortFlags & KEYINFO_ORDER_BIGNULL ) break; + Expr *pE2; + + /* Skip over constant terms in the ORDER BY clause */ + if( sqlite3ExprIsConstant(pExpr) ){ + continue; + } + + /* Virtual tables are unable to deal with NULLS FIRST */ + if( pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_BIGNULL ) break; + + /* First case - a direct column references without a COLLATE operator */ + if( pExpr->op==TK_COLUMN && pExpr->iTable==pSrc->iCursor ){ + assert( pExpr->iColumn>=XN_ROWID && pExpr->iColumnnCol ); + continue; + } + + /* 2nd case - a column reference with a COLLATE operator. Only match + ** of the COLLATE operator matches the collation of the column. */ + if( pExpr->op==TK_COLLATE + && (pE2 = pExpr->pLeft)->op==TK_COLUMN + && pE2->iTable==pSrc->iCursor + ){ + const char *zColl; /* The collating sequence name */ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + assert( pExpr->u.zToken!=0 ); + assert( pE2->iColumn>=XN_ROWID && pE2->iColumnnCol ); + pExpr->iColumn = pE2->iColumn; + if( pE2->iColumn<0 ) continue; /* Collseq does not matter for rowid */ + zColl = sqlite3ColumnColl(&pTab->aCol[pE2->iColumn]); + if( zColl==0 ) zColl = sqlite3StrBINARY; + if( sqlite3_stricmp(pExpr->u.zToken, zColl)==0 ) continue; + } + + /* No matches cause a break out of the loop */ + break; } - if( i==n){ + if( i==n ){ nOrderBy = n; + if( (pWInfo->wctrlFlags & WHERE_DISTINCTBY) ){ + eDistinct = 2 + ((pWInfo->wctrlFlags & WHERE_SORTBYGROUP)!=0); + }else if( pWInfo->wctrlFlags & WHERE_GROUPBY ){ + eDistinct = 1; + } } } @@ -149284,46 +155261,35 @@ static sqlite3_index_info *allocateIndexInfo( */ pIdxInfo = sqlite3DbMallocZero(pParse->db, sizeof(*pIdxInfo) + (sizeof(*pIdxCons) + sizeof(*pUsage))*nTerm - + sizeof(*pIdxOrderBy)*nOrderBy + sizeof(*pHidden) ); + + sizeof(*pIdxOrderBy)*nOrderBy + sizeof(*pHidden) + + sizeof(sqlite3_value*)*nTerm ); if( pIdxInfo==0 ){ sqlite3ErrorMsg(pParse, "out of memory"); return 0; } pHidden = (struct HiddenIndexInfo*)&pIdxInfo[1]; - pIdxCons = (struct sqlite3_index_constraint*)&pHidden[1]; + pIdxCons = (struct sqlite3_index_constraint*)&pHidden->aRhs[nTerm]; pIdxOrderBy = (struct sqlite3_index_orderby*)&pIdxCons[nTerm]; pUsage = (struct sqlite3_index_constraint_usage*)&pIdxOrderBy[nOrderBy]; - pIdxInfo->nOrderBy = nOrderBy; pIdxInfo->aConstraint = pIdxCons; pIdxInfo->aOrderBy = pIdxOrderBy; pIdxInfo->aConstraintUsage = pUsage; pHidden->pWC = pWC; pHidden->pParse = pParse; + pHidden->eDistinct = eDistinct; + pHidden->mIn = 0; for(i=j=0, pTerm=pWC->a; inTerm; i++, pTerm++){ u16 op; - if( pTerm->leftCursor != pSrc->iCursor ) continue; - if( pTerm->prereqRight & mUnusable ) continue; - assert( IsPowerOfTwo(pTerm->eOperator & ~WO_EQUIV) ); - testcase( pTerm->eOperator & WO_IN ); - testcase( pTerm->eOperator & WO_IS ); - testcase( pTerm->eOperator & WO_ISNULL ); - testcase( pTerm->eOperator & WO_ALL ); - if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue; - if( pTerm->wtFlags & TERM_VNULL ) continue; - - /* tag-20191211-002: WHERE-clause constraints are not useful to the - ** right-hand table of a LEFT JOIN. See tag-20191211-001 for the - ** equivalent restriction for ordinary tables. */ - if( (pSrc->fg.jointype & JT_LEFT)!=0 - && !ExprHasProperty(pTerm->pExpr, EP_FromJoin) - ){ - continue; - } - assert( pTerm->u.x.leftColumn>=(-1) ); + if( (pTerm->wtFlags & TERM_OK)==0 ) continue; pIdxCons[j].iColumn = pTerm->u.x.leftColumn; pIdxCons[j].iTermOffset = i; op = pTerm->eOperator & WO_ALL; - if( op==WO_IN ) op = WO_EQ; + if( op==WO_IN ){ + if( (pTerm->wtFlags & TERM_SLICE)==0 ){ + pHidden->mIn |= SMASKBIT32(j); + } + op = WO_EQ; + } if( op==WO_AUX ){ pIdxCons[j].op = pTerm->eMatchOp; }else if( op & (WO_ISNULL|WO_IS) ){ @@ -149356,17 +155322,42 @@ static sqlite3_index_info *allocateIndexInfo( j++; } + assert( j==nTerm ); pIdxInfo->nConstraint = j; - for(i=0; ia[i].pExpr; - pIdxOrderBy[i].iColumn = pExpr->iColumn; - pIdxOrderBy[i].desc = pOrderBy->a[i].sortFlags & KEYINFO_ORDER_DESC; + if( sqlite3ExprIsConstant(pExpr) ) continue; + assert( pExpr->op==TK_COLUMN + || (pExpr->op==TK_COLLATE && pExpr->pLeft->op==TK_COLUMN + && pExpr->iColumn==pExpr->pLeft->iColumn) ); + pIdxOrderBy[j].iColumn = pExpr->iColumn; + pIdxOrderBy[j].desc = pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_DESC; + j++; } + pIdxInfo->nOrderBy = j; *pmNoOmit = mNoOmit; return pIdxInfo; } +/* +** Free an sqlite3_index_info structure allocated by allocateIndexInfo() +** and possibly modified by xBestIndex methods. +*/ +static void freeIndexInfo(sqlite3 *db, sqlite3_index_info *pIdxInfo){ + HiddenIndexInfo *pHidden; + int i; + assert( pIdxInfo!=0 ); + pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + assert( pHidden->pParse!=0 ); + assert( pHidden->pParse->db==db ); + for(i=0; inConstraint; i++){ + sqlite3ValueFree(pHidden->aRhs[i]); /* IMP: R-14553-25174 */ + pHidden->aRhs[i] = 0; + } + sqlite3DbFree(db, pIdxInfo); +} + /* ** The table object reference passed as the second argument to this function ** must represent a virtual table. This function invokes the xBestIndex() @@ -149388,7 +155379,9 @@ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ int rc; whereTraceIndexInfoInputs(p); + pParse->db->nSchemaLock++; rc = pVtab->pModule->xBestIndex(pVtab, p); + pParse->db->nSchemaLock--; whereTraceIndexInfoOutputs(p); if( rc!=SQLITE_OK && rc!=SQLITE_CONSTRAINT ){ @@ -149442,7 +155435,7 @@ static int whereKeyStats( #endif assert( pRec!=0 ); assert( pIdx->nSample>0 ); - assert( pRec->nField>0 && pRec->nField<=pIdx->nSampleCol ); + assert( pRec->nField>0 ); /* Do a binary search to find the first sample greater than or equal ** to pRec. If pRec contains a single field, the set of samples to search @@ -149488,7 +155481,7 @@ static int whereKeyStats( ** it is extended to two fields. The duplicates that this creates do not ** cause any problems. */ - nField = pRec->nField; + nField = MIN(pRec->nField, pIdx->nSample); iCol = 0; iSample = pIdx->nSample * nField; do{ @@ -150079,9 +156072,10 @@ SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){ memcpy(zType, "....", 5); if( pTerm->wtFlags & TERM_VIRTUAL ) zType[0] = 'V'; if( pTerm->eOperator & WO_EQUIV ) zType[1] = 'E'; - if( ExprHasProperty(pTerm->pExpr, EP_FromJoin) ) zType[2] = 'L'; + if( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) zType[2] = 'L'; if( pTerm->wtFlags & TERM_CODED ) zType[3] = 'C'; if( pTerm->eOperator & WO_SINGLE ){ + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); sqlite3_snprintf(sizeof(zLeft),zLeft,"left={%d:%d}", pTerm->leftCursor, pTerm->u.x.leftColumn); }else if( (pTerm->eOperator & WO_OR)!=0 && pTerm->u.pOrInfo!=0 ){ @@ -150099,7 +156093,7 @@ SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){ sqlite3DebugPrintf(" prob=%-3d prereq=%llx,%llx", pTerm->truthProb, (u64)pTerm->prereqAll, (u64)pTerm->prereqRight); } - if( pTerm->u.x.iField ){ + if( (pTerm->eOperator & (WO_OR|WO_AND))==0 && pTerm->u.x.iField ){ sqlite3DebugPrintf(" iField=%d", pTerm->u.x.iField); } if( pTerm->iParent>=0 ){ @@ -150161,9 +156155,9 @@ SQLITE_PRIVATE void sqlite3WhereLoopPrint(WhereLoop *p, WhereClause *pWC){ sqlite3_free(z); } if( p->wsFlags & WHERE_SKIPSCAN ){ - sqlite3DebugPrintf(" f %05x %d-%d", p->wsFlags, p->nLTerm,p->nSkip); + sqlite3DebugPrintf(" f %06x %d-%d", p->wsFlags, p->nLTerm,p->nSkip); }else{ - sqlite3DebugPrintf(" f %05x N %d", p->wsFlags, p->nLTerm); + sqlite3DebugPrintf(" f %06x N %d", p->wsFlags, p->nLTerm); } sqlite3DebugPrintf(" cost %d,%d,%d\n", p->rSetup, p->rRun, p->nOut); if( p->nLTerm && (sqlite3WhereTrace & 0x100)!=0 ){ @@ -150234,7 +156228,7 @@ static int whereLoopResize(sqlite3 *db, WhereLoop *p, int n){ static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){ whereLoopClearUnion(db, pTo); if( whereLoopResize(db, pTo, pFrom->nLTerm) ){ - memset(&pTo->u, 0, sizeof(pTo->u)); + memset(pTo, 0, WHERE_LOOP_XFER_SZ); return SQLITE_NOMEM_BKPT; } memcpy(pTo, pFrom, WHERE_LOOP_XFER_SZ); @@ -150259,14 +156253,7 @@ static void whereLoopDelete(sqlite3 *db, WhereLoop *p){ ** Free a WhereInfo structure */ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ - int i; assert( pWInfo!=0 ); - for(i=0; inLevel; i++){ - WhereLevel *pLevel = &pWInfo->a[i]; - if( pLevel->pWLoop && (pLevel->pWLoop->wsFlags & WHERE_IN_ABLE) ){ - sqlite3DbFree(db, pLevel->u.in.aInLoop); - } - } sqlite3WhereClauseClear(&pWInfo->sWC); while( pWInfo->pLoops ){ WhereLoop *p = pWInfo->pLoops; @@ -150274,13 +156261,30 @@ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ whereLoopDelete(db, p); } assert( pWInfo->pExprMods==0 ); + while( pWInfo->pMemToFree ){ + WhereMemBlock *pNext = pWInfo->pMemToFree->pNext; + sqlite3DbFreeNN(db, pWInfo->pMemToFree); + pWInfo->pMemToFree = pNext; + } sqlite3DbFreeNN(db, pWInfo); } +/* Undo all Expr node modifications +*/ +static void whereUndoExprMods(WhereInfo *pWInfo){ + while( pWInfo->pExprMods ){ + WhereExprMod *p = pWInfo->pExprMods; + pWInfo->pExprMods = p->pNext; + memcpy(p->pExpr, &p->orig, sizeof(p->orig)); + sqlite3DbFree(pWInfo->pParse->db, p); + } +} + /* ** Return TRUE if all of the following are true: ** -** (1) X has the same or lower cost that Y +** (1) X has the same or lower cost, or returns the same or fewer rows, +** than Y. ** (2) X uses fewer WHERE clause terms than Y ** (3) Every WHERE clause term used by X is also used by Y ** (4) X skips at least as many columns as Y @@ -150303,11 +156307,8 @@ static int whereLoopCheaperProperSubset( if( pX->nLTerm-pX->nSkip >= pY->nLTerm-pY->nSkip ){ return 0; /* X is not a subset of Y */ } + if( pX->rRun>pY->rRun && pX->nOut>pY->nOut ) return 0; if( pY->nSkip > pX->nSkip ) return 0; - if( pX->rRun >= pY->rRun ){ - if( pX->rRun > pY->rRun ) return 0; /* X costs more than Y */ - if( pX->nOut > pY->nOut ) return 0; /* X costs more than Y */ - } for(i=pX->nLTerm-1; i>=0; i--){ if( pX->aLTerm[i]==0 ) continue; for(j=pY->nLTerm-1; j>=0; j--){ @@ -150323,8 +156324,8 @@ static int whereLoopCheaperProperSubset( } /* -** Try to adjust the cost of WhereLoop pTemplate upwards or downwards so -** that: +** Try to adjust the cost and number of output rows of WhereLoop pTemplate +** upwards or downwards so that: ** ** (1) pTemplate costs less than any other WhereLoops that are a proper ** subset of pTemplate @@ -150345,16 +156346,20 @@ static void whereLoopAdjustCost(const WhereLoop *p, WhereLoop *pTemplate){ /* Adjust pTemplate cost downward so that it is cheaper than its ** subset p. */ WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n", - pTemplate->rRun, pTemplate->nOut, p->rRun, p->nOut-1)); - pTemplate->rRun = p->rRun; - pTemplate->nOut = p->nOut - 1; + pTemplate->rRun, pTemplate->nOut, + MIN(p->rRun, pTemplate->rRun), + MIN(p->nOut - 1, pTemplate->nOut))); + pTemplate->rRun = MIN(p->rRun, pTemplate->rRun); + pTemplate->nOut = MIN(p->nOut - 1, pTemplate->nOut); }else if( whereLoopCheaperProperSubset(pTemplate, p) ){ /* Adjust pTemplate cost upward so that it is costlier than p since ** pTemplate is a proper subset of p */ WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n", - pTemplate->rRun, pTemplate->nOut, p->rRun, p->nOut+1)); - pTemplate->rRun = p->rRun; - pTemplate->nOut = p->nOut + 1; + pTemplate->rRun, pTemplate->nOut, + MAX(p->rRun, pTemplate->rRun), + MAX(p->nOut + 1, pTemplate->nOut))); + pTemplate->rRun = MAX(p->rRun, pTemplate->rRun); + pTemplate->nOut = MAX(p->nOut + 1, pTemplate->nOut); } } } @@ -150609,11 +156614,11 @@ static void whereLoopOutputAdjust( LogEst iReduce = 0; /* pLoop->nOut should not exceed nRow-iReduce */ assert( (pLoop->wsFlags & WHERE_AUTO_INDEX)==0 ); - for(i=pWC->nTerm, pTerm=pWC->a; i>0; i--, pTerm++){ + for(i=pWC->nBase, pTerm=pWC->a; i>0; i--, pTerm++){ assert( pTerm!=0 ); - if( (pTerm->wtFlags & TERM_VIRTUAL)!=0 ) break; - if( (pTerm->prereqAll & pLoop->maskSelf)==0 ) continue; if( (pTerm->prereqAll & notAllowed)!=0 ) continue; + if( (pTerm->prereqAll & pLoop->maskSelf)==0 ) continue; + if( (pTerm->wtFlags & TERM_VIRTUAL)!=0 ) continue; for(j=pLoop->nLTerm-1; j>=0; j--){ pX = pLoop->aLTerm[j]; if( pX==0 ) continue; @@ -150621,6 +156626,23 @@ static void whereLoopOutputAdjust( if( pX->iParent>=0 && (&pWC->a[pX->iParent])==pTerm ) break; } if( j<0 ){ + if( pLoop->maskSelf==pTerm->prereqAll ){ + /* If there are extra terms in the WHERE clause not used by an index + ** that depend only on the table being scanned, and that will tend to + ** cause many rows to be omitted, then mark that table as + ** "self-culling". + ** + ** 2022-03-24: Self-culling only applies if either the extra terms + ** are straight comparison operators that are non-true with NULL + ** operand, or if the loop is not an OUTER JOIN. + */ + if( (pTerm->eOperator & 0x3f)!=0 + || (pWC->pWInfo->pTabList->a[pLoop->iTab].fg.jointype + & (JT_LEFT|JT_LTORJ))==0 + ){ + pLoop->wsFlags |= WHERE_SELFCULL; + } + } if( pTerm->truthProb<=0 ){ /* If a truth probability is specified using the likelihood() hints, ** then use the probability provided by the application. */ @@ -150648,7 +156670,9 @@ static void whereLoopOutputAdjust( } } } - if( pLoop->nOut > nRow-iReduce ) pLoop->nOut = nRow - iReduce; + if( pLoop->nOut > nRow-iReduce ){ + pLoop->nOut = nRow - iReduce; + } } /* @@ -150685,9 +156709,12 @@ static int whereRangeVectorLen( char aff; /* Comparison affinity */ char idxaff = 0; /* Indexed columns affinity */ CollSeq *pColl; /* Comparison collation sequence */ - Expr *pLhs = pTerm->pExpr->pLeft->x.pList->a[i].pExpr; - Expr *pRhs = pTerm->pExpr->pRight; - if( pRhs->flags & EP_xIsSelect ){ + Expr *pLhs, *pRhs; + + assert( ExprUseXList(pTerm->pExpr->pLeft) ); + pLhs = pTerm->pExpr->pLeft->x.pList->a[i].pExpr; + pRhs = pTerm->pExpr->pRight; + if( ExprUseXSelect(pRhs) ){ pRhs = pRhs->x.pSelect->pEList->a[i].pExpr; }else{ pRhs = pRhs->x.pList->a[i].pExpr; @@ -150782,6 +156809,8 @@ static int whereLoopAddBtreeIndex( if( pProbe->bUnordered ) opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE); assert( pNew->u.btree.nEqnColumn ); + assert( pNew->u.btree.nEqnKeyCol + || pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY ); saved_nEq = pNew->u.btree.nEq; saved_nBtm = pNew->u.btree.nBtm; @@ -150816,12 +156845,29 @@ static int whereLoopAddBtreeIndex( if( pTerm->wtFlags & TERM_LIKEOPT && pTerm->eOperator==WO_LT ) continue; /* tag-20191211-001: Do not allow constraints from the WHERE clause to - ** be used by the right table of a LEFT JOIN. Only constraints in the - ** ON clause are allowed. See tag-20191211-002 for the vtab equivalent. */ - if( (pSrc->fg.jointype & JT_LEFT)!=0 - && !ExprHasProperty(pTerm->pExpr, EP_FromJoin) - ){ - continue; + ** be used by the right table of a LEFT JOIN nor by the left table of a + ** RIGHT JOIN. Only constraints in the ON clause are allowed. + ** See tag-20191211-002 for the vtab equivalent. + ** + ** 2022-06-06: See https://sqlite.org/forum/forumpost/206d99a16dd9212f + ** for an example of a WHERE clause constraints that may not be used on + ** the right table of a RIGHT JOIN because the constraint implies a + ** not-NULL condition on the left table of the RIGHT JOIN. + ** + ** 2022-06-10: The same condition applies to termCanDriveIndex() above. + ** https://sqlite.org/forum/forumpost/51e6959f61 + */ + if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ){ + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT ); + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_RIGHT ); + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ ); + testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) + testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) ); + if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) + || pTerm->pExpr->w.iJoin != pSrc->iCursor + ){ + continue; + } } if( IsUniqueIndex(pProbe) && saved_nEq==pProbe->nKeyCol-1 ){ @@ -150846,7 +156892,7 @@ static int whereLoopAddBtreeIndex( if( eOp & WO_IN ){ Expr *pExpr = pTerm->pExpr; - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ /* "x IN (SELECT ...)": TUNING: the SELECT returns 25 rows */ int i; nIn = 46; assert( 46==sqlite3LogEst(25) ); @@ -150864,7 +156910,7 @@ static int whereLoopAddBtreeIndex( nIn = sqlite3LogEst(pExpr->x.pList->nExpr); } if( pProbe->hasStat1 && rLogSize>=10 ){ - LogEst M, logK, safetyMargin; + LogEst M, logK, x; /* Let: ** N = the total number of rows in the table ** K = the number of entries on the RHS of the IN operator @@ -150887,16 +156933,25 @@ static int whereLoopAddBtreeIndex( */ M = pProbe->aiRowLogEst[saved_nEq]; logK = estLog(nIn); - safetyMargin = 10; /* TUNING: extra weight for indexed IN */ - if( M + logK + safetyMargin < nIn + rLogSize ){ + /* TUNING v----- 10 to bias toward indexed IN */ + x = M + logK + 10 - (nIn + rLogSize); + if( x>=0 ){ WHERETRACE(0x40, - ("Scan preferred over IN operator on column %d of \"%s\" (%d<%d)\n", - saved_nEq, pProbe->zName, M+logK+10, nIn+rLogSize)); + ("IN operator (N=%d M=%d logK=%d nIn=%d rLogSize=%d x=%d) " + "prefers indexed lookup\n", + saved_nEq, M, logK, nIn, rLogSize, x)); + }else if( nInMul<2 && OptimizationEnabled(db, SQLITE_SeekScan) ){ + WHERETRACE(0x40, + ("IN operator (N=%d M=%d logK=%d nIn=%d rLogSize=%d x=%d" + " nInMul=%d) prefers skip-scan\n", + saved_nEq, M, logK, nIn, rLogSize, x, nInMul)); pNew->wsFlags |= WHERE_IN_SEEKSCAN; }else{ WHERETRACE(0x40, - ("IN operator preferred on column %d of \"%s\" (%d>=%d)\n", - saved_nEq, pProbe->zName, M+logK+10, nIn+rLogSize)); + ("IN operator (N=%d M=%d logK=%d nIn=%d rLogSize=%d x=%d" + " nInMul=%d) prefers normal scan\n", + saved_nEq, M, logK, nIn, rLogSize, x, nInMul)); + continue; } } pNew->wsFlags |= WHERE_COLUMN_IN; @@ -150915,6 +156970,7 @@ static int whereLoopAddBtreeIndex( pNew->wsFlags |= WHERE_UNQ_WANTED; } } + if( scan.iEquiv>1 ) pNew->wsFlags |= WHERE_TRANSCONS; }else if( eOp & WO_ISNULL ){ pNew->wsFlags |= WHERE_COLUMN_NULL; }else if( eOp & (WO_GT|WO_GE) ){ @@ -150976,8 +157032,8 @@ static int whereLoopAddBtreeIndex( tRowcnt nOut = 0; if( nInMul==0 && pProbe->nSample - && pNew->u.btree.nEq<=pProbe->nSampleCol - && ((eOp & WO_IN)==0 || !ExprHasProperty(pTerm->pExpr, EP_xIsSelect)) + && ALWAYS(pNew->u.btree.nEq<=pProbe->nSampleCol) + && ((eOp & WO_IN)==0 || ExprUseXList(pTerm->pExpr)) && OptimizationEnabled(db, SQLITE_Stat4) ){ Expr *pExpr = pTerm->pExpr; @@ -151058,6 +157114,8 @@ static int whereLoopAddBtreeIndex( if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 && pNew->u.btree.nEqnColumn + && (pNew->u.btree.nEqnKeyCol || + pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY) ){ whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn); } @@ -151161,24 +157219,28 @@ static int indexMightHelpWithOrderBy( */ static int whereUsablePartialIndex( int iTab, /* The table for which we want an index */ - int isLeft, /* True if iTab is the right table of a LEFT JOIN */ + u8 jointype, /* The JT_* flags on the join */ WhereClause *pWC, /* The WHERE clause of the query */ Expr *pWhere /* The WHERE clause from the partial index */ ){ int i; WhereTerm *pTerm; - Parse *pParse = pWC->pWInfo->pParse; + Parse *pParse; + + if( jointype & JT_LTORJ ) return 0; + pParse = pWC->pWInfo->pParse; while( pWhere->op==TK_AND ){ - if( !whereUsablePartialIndex(iTab,isLeft,pWC,pWhere->pLeft) ) return 0; + if( !whereUsablePartialIndex(iTab,jointype,pWC,pWhere->pLeft) ) return 0; pWhere = pWhere->pRight; } if( pParse->db->flags & SQLITE_EnableQPSG ) pParse = 0; for(i=0, pTerm=pWC->a; inTerm; i++, pTerm++){ Expr *pExpr; pExpr = pTerm->pExpr; - if( (!ExprHasProperty(pExpr, EP_FromJoin) || pExpr->iRightJoinTable==iTab) - && (isLeft==0 || ExprHasProperty(pExpr, EP_FromJoin)) + if( (!ExprHasProperty(pExpr, EP_OuterON) || pExpr->w.iJoin==iTab) + && ((jointype & JT_OUTER)==0 || ExprHasProperty(pExpr, EP_OuterON)) && sqlite3ExprImpliesExpr(pParse, pExpr, pWhere, iTab) + && (pTerm->wtFlags & TERM_VNULL)==0 ){ return 1; } @@ -151238,7 +157300,6 @@ static int whereLoopAddBtree( int iSortIdx = 1; /* Index number */ int b; /* A boolean value */ LogEst rSize; /* number of rows in the table */ - LogEst rLogSize; /* Logarithm of the number of rows in the table */ WhereClause *pWC; /* The parsed WHERE clause */ Table *pTab; /* Table being queried */ @@ -151251,6 +157312,7 @@ static int whereLoopAddBtree( assert( !IsVirtual(pSrc->pTab) ); if( pSrc->fg.isIndexedBy ){ + assert( pSrc->fg.isCte==0 ); /* An INDEXED BY clause specifies a particular index to use */ pProbe = pSrc->u2.pIBIndex; }else if( !HasRowid(pTab) ){ @@ -151281,22 +157343,24 @@ static int whereLoopAddBtree( pProbe = &sPk; } rSize = pTab->nRowLogEst; - rLogSize = estLog(rSize); #ifndef SQLITE_OMIT_AUTOMATIC_INDEX /* Automatic indexes */ if( !pBuilder->pOrSet /* Not part of an OR optimization */ - && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 + && (pWInfo->wctrlFlags & (WHERE_RIGHT_JOIN|WHERE_OR_SUBCLAUSE))==0 && (pWInfo->pParse->db->flags & SQLITE_AutoIndex)!=0 && !pSrc->fg.isIndexedBy /* Has no INDEXED BY clause */ && !pSrc->fg.notIndexed /* Has no NOT INDEXED clause */ && HasRowid(pTab) /* Not WITHOUT ROWID table. (FIXME: Why not?) */ && !pSrc->fg.isCorrelated /* Not a correlated subquery */ && !pSrc->fg.isRecursive /* Not a recursive common table expression. */ + && (pSrc->fg.jointype & JT_RIGHT)==0 /* Not the right tab of a RIGHT JOIN */ ){ /* Generate auto-index WhereLoops */ + LogEst rLogSize; /* Logarithm of the number of rows in the table */ WhereTerm *pTerm; WhereTerm *pWCEnd = pWC->a + pWC->nTerm; + rLogSize = estLog(rSize); for(pTerm=pWC->a; rc==SQLITE_OK && pTermprereqRight & pNew->maskSelf ) continue; if( termCanDriveIndex(pTerm, pSrc, 0) ){ @@ -151314,7 +157378,7 @@ static int whereLoopAddBtree( ** those objects, since there is no opportunity to add schema ** indexes on subqueries and views. */ pNew->rSetup = rLogSize + rSize; - if( pTab->pSelect==0 && (pTab->tabFlags & TF_Ephemeral)==0 ){ + if( !IsView(pTab) && (pTab->tabFlags & TF_Ephemeral)==0 ){ pNew->rSetup += 28; }else{ pNew->rSetup -= 10; @@ -151340,9 +157404,8 @@ static int whereLoopAddBtree( for(; rc==SQLITE_OK && pProbe; pProbe=(pSrc->fg.isIndexedBy ? 0 : pProbe->pNext), iSortIdx++ ){ - int isLeft = (pSrc->fg.jointype & JT_OUTER)!=0; if( pProbe->pPartIdxWhere!=0 - && !whereUsablePartialIndex(pSrc->iCursor, isLeft, pWC, + && !whereUsablePartialIndex(pSrc->iCursor, pSrc->fg.jointype, pWC, pProbe->pPartIdxWhere) ){ testcase( pNew->iTab!=pSrc->iCursor ); /* See ticket [98d973b8f5] */ @@ -151450,7 +157513,14 @@ static int whereLoopAddBtree( } ApplyCostMultiplier(pNew->rRun, pTab->costMult); whereLoopOutputAdjust(pWC, pNew, rSize); - rc = whereLoopInsert(pBuilder, pNew); + if( (pSrc->fg.jointype & JT_RIGHT)!=0 && pProbe->aColExpr ){ + /* Do not do an SCAN of a index-on-expression in a RIGHT JOIN + ** because the cursor used to access the index might not be + ** positioned to the correct row during the right-join no-match + ** loop. */ + }else{ + rc = whereLoopInsert(pBuilder, pNew); + } pNew->nOut = rSize; if( rc ) break; } @@ -151476,6 +157546,15 @@ static int whereLoopAddBtree( #ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Return true if pTerm is a virtual table LIMIT or OFFSET term. +*/ +static int isLimitTerm(WhereTerm *pTerm){ + assert( pTerm->eOperator==WO_AUX || pTerm->eMatchOp==0 ); + return pTerm->eMatchOp>=SQLITE_INDEX_CONSTRAINT_LIMIT + && pTerm->eMatchOp<=SQLITE_INDEX_CONSTRAINT_OFFSET; +} + /* ** Argument pIdxInfo is already populated with all constraints that may ** be used by the virtual table identified by pBuilder->pNew->iTab. This @@ -151503,9 +157582,11 @@ static int whereLoopAddVirtualOne( u16 mExclude, /* Exclude terms using these operators */ sqlite3_index_info *pIdxInfo, /* Populated object for xBestIndex */ u16 mNoOmit, /* Do not omit these constraints */ - int *pbIn /* OUT: True if plan uses an IN(...) op */ + int *pbIn, /* OUT: True if plan uses an IN(...) op */ + int *pbRetryLimit /* OUT: Retry without LIMIT/OFFSET */ ){ WhereClause *pWC = pBuilder->pWC; + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; struct sqlite3_index_constraint *pIdxCons; struct sqlite3_index_constraint_usage *pUsage = pIdxInfo->aConstraintUsage; int i; @@ -151528,6 +157609,7 @@ static int whereLoopAddVirtualOne( pIdxCons->usable = 0; if( (pTerm->prereqRight & mUsable)==pTerm->prereqRight && (pTerm->eOperator & mExclude)==0 + && (pbRetryLimit || !isLimitTerm(pTerm)) ){ pIdxCons->usable = 1; } @@ -151543,6 +157625,7 @@ static int whereLoopAddVirtualOne( pIdxInfo->estimatedRows = 25; pIdxInfo->idxFlags = 0; pIdxInfo->colUsed = (sqlite3_int64)pSrc->colUsed; + pHidden->mHandleIn = 0; /* Invoke the virtual table xBestIndex() method */ rc = vtabBestIndex(pParse, pSrc->pTab, pIdxInfo); @@ -151560,8 +157643,8 @@ static int whereLoopAddVirtualOne( mxTerm = -1; assert( pNew->nLSlot>=nConstraint ); - for(i=0; iaLTerm[i] = 0; - pNew->u.vtab.omitMask = 0; + memset(pNew->aLTerm, 0, sizeof(pNew->aLTerm[0])*nConstraint ); + memset(&pNew->u.vtab, 0, sizeof(pNew->u.vtab)); pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint; for(i=0; ieMatchOp==SQLITE_INDEX_CONSTRAINT_OFFSET ){ + pNew->u.vtab.bOmitOffset = 1; + } } - if( (pTerm->eOperator & WO_IN)!=0 ){ + if( SMASKBIT32(i) & pHidden->mHandleIn ){ + pNew->u.vtab.mHandleIn |= MASKBIT32(iTerm); + }else if( (pTerm->eOperator & WO_IN)!=0 ){ /* A virtual table that is constrained by an IN clause may not ** consume the ORDER BY clause because (1) the order of IN terms ** is not necessarily related to the order of output terms and @@ -151606,6 +157694,22 @@ static int whereLoopAddVirtualOne( pIdxInfo->idxFlags &= ~SQLITE_INDEX_SCAN_UNIQUE; *pbIn = 1; assert( (mExclude & WO_IN)==0 ); } + + assert( pbRetryLimit || !isLimitTerm(pTerm) ); + if( isLimitTerm(pTerm) && *pbIn ){ + /* If there is an IN(...) term handled as an == (separate call to + ** xFilter for each value on the RHS of the IN) and a LIMIT or + ** OFFSET term handled as well, the plan is unusable. Set output + ** variable *pbRetryLimit to true to tell the caller to retry with + ** LIMIT and OFFSET disabled. */ + if( pIdxInfo->needToFreeIdxStr ){ + sqlite3_free(pIdxInfo->idxStr); + pIdxInfo->idxStr = 0; + pIdxInfo->needToFreeIdxStr = 0; + } + *pbRetryLimit = 1; + return SQLITE_OK; + } } } @@ -151650,11 +157754,19 @@ static int whereLoopAddVirtualOne( } /* -** If this function is invoked from within an xBestIndex() callback, it -** returns a pointer to a buffer containing the name of the collation -** sequence associated with element iCons of the sqlite3_index_info.aConstraint -** array. Or, if iCons is out of range or there is no active xBestIndex -** call, return NULL. +** Return the collating sequence for a constraint passed into xBestIndex. +** +** pIdxInfo must be an sqlite3_index_info structure passed into xBestIndex. +** This routine depends on there being a HiddenIndexInfo structure immediately +** following the sqlite3_index_info structure. +** +** Return a pointer to the collation name: +** +** 1. If there is an explicit COLLATE operator on the constaint, return it. +** +** 2. Else, if the column has an alternative collation, return that. +** +** 3. Otherwise, return "BINARY". */ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info *pIdxInfo, int iCons){ HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; @@ -151671,6 +157783,97 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info *pIdxInfo, int return zRet; } +/* +** Return true if constraint iCons is really an IN(...) constraint, or +** false otherwise. If iCons is an IN(...) constraint, set (if bHandle!=0) +** or clear (if bHandle==0) the flag to handle it using an iterator. +*/ +SQLITE_API int sqlite3_vtab_in(sqlite3_index_info *pIdxInfo, int iCons, int bHandle){ + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + u32 m = SMASKBIT32(iCons); + if( m & pHidden->mIn ){ + if( bHandle==0 ){ + pHidden->mHandleIn &= ~m; + }else if( bHandle>0 ){ + pHidden->mHandleIn |= m; + } + return 1; + } + return 0; +} + +/* +** This interface is callable from within the xBestIndex callback only. +** +** If possible, set (*ppVal) to point to an object containing the value +** on the right-hand-side of constraint iCons. +*/ +SQLITE_API int sqlite3_vtab_rhs_value( + sqlite3_index_info *pIdxInfo, /* Copy of first argument to xBestIndex */ + int iCons, /* Constraint for which RHS is wanted */ + sqlite3_value **ppVal /* Write value extracted here */ +){ + HiddenIndexInfo *pH = (HiddenIndexInfo*)&pIdxInfo[1]; + sqlite3_value *pVal = 0; + int rc = SQLITE_OK; + if( iCons<0 || iCons>=pIdxInfo->nConstraint ){ + rc = SQLITE_MISUSE; /* EV: R-30545-25046 */ + }else{ + if( pH->aRhs[iCons]==0 ){ + WhereTerm *pTerm = &pH->pWC->a[pIdxInfo->aConstraint[iCons].iTermOffset]; + rc = sqlite3ValueFromExpr( + pH->pParse->db, pTerm->pExpr->pRight, ENC(pH->pParse->db), + SQLITE_AFF_BLOB, &pH->aRhs[iCons] + ); + testcase( rc!=SQLITE_OK ); + } + pVal = pH->aRhs[iCons]; + } + *ppVal = pVal; + + if( rc==SQLITE_OK && pVal==0 ){ /* IMP: R-19933-32160 */ + rc = SQLITE_NOTFOUND; /* IMP: R-36424-56542 */ + } + + return rc; +} + +/* +** Return true if ORDER BY clause may be handled as DISTINCT. +*/ +SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info *pIdxInfo){ + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + assert( pHidden->eDistinct>=0 && pHidden->eDistinct<=3 ); + return pHidden->eDistinct; +} + +#if (defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)) \ + && !defined(SQLITE_OMIT_VIRTUALTABLE) +/* +** Cause the prepared statement that is associated with a call to +** xBestIndex to potentiall use all schemas. If the statement being +** prepared is read-only, then just start read transactions on all +** schemas. But if this is a write operation, start writes on all +** schemas. +** +** This is used by the (built-in) sqlite_dbpage virtual table. +*/ +SQLITE_PRIVATE void sqlite3VtabUsesAllSchemas(sqlite3_index_info *pIdxInfo){ + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + Parse *pParse = pHidden->pParse; + int nDb = pParse->db->nDb; + int i; + for(i=0; iwriteMask ){ + for(i=0; ipNew->iTab. That table is guaranteed to be a virtual table. @@ -151712,6 +157915,7 @@ static int whereLoopAddVirtual( WhereLoop *pNew; Bitmask mBest; /* Tables used by best possible plan */ u16 mNoOmit; + int bRetry = 0; /* True to retry with LIMIT/OFFSET disabled */ assert( (mPrereq & mUnusable)==0 ); pWInfo = pBuilder->pWInfo; @@ -151720,8 +157924,7 @@ static int whereLoopAddVirtual( pNew = pBuilder->pNew; pSrc = &pWInfo->pTabList->a[pNew->iTab]; assert( IsVirtual(pSrc->pTab) ); - p = allocateIndexInfo(pParse, pWC, mUnusable, pSrc, pBuilder->pOrderBy, - &mNoOmit); + p = allocateIndexInfo(pWInfo, pWC, mUnusable, pSrc, &mNoOmit); if( p==0 ) return SQLITE_NOMEM_BKPT; pNew->rSetup = 0; pNew->wsFlags = WHERE_VIRTUALTABLE; @@ -151729,14 +157932,22 @@ static int whereLoopAddVirtual( pNew->u.vtab.needFree = 0; nConstraint = p->nConstraint; if( whereLoopResize(pParse->db, pNew, nConstraint) ){ - sqlite3DbFree(pParse->db, p); + freeIndexInfo(pParse->db, p); return SQLITE_NOMEM_BKPT; } /* First call xBestIndex() with all constraints usable. */ WHERETRACE(0x800, ("BEGIN %s.addVirtual()\n", pSrc->pTab->zName)); WHERETRACE(0x40, (" VirtualOne: all usable\n")); - rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn, &bRetry + ); + if( bRetry ){ + assert( rc==SQLITE_OK ); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn, 0 + ); + } /* If the call to xBestIndex() with all terms enabled produced a plan ** that does not require any source tables (IOW: a plan with mBest==0) @@ -151754,7 +157965,7 @@ static int whereLoopAddVirtual( if( bIn ){ WHERETRACE(0x40, (" VirtualOne: all usable w/o IN\n")); rc = whereLoopAddVirtualOne( - pBuilder, mPrereq, ALLBITS, WO_IN, p, mNoOmit, &bIn); + pBuilder, mPrereq, ALLBITS, WO_IN, p, mNoOmit, &bIn, 0); assert( bIn==0 ); mBestNoIn = pNew->prereq & ~mPrereq; if( mBestNoIn==0 ){ @@ -151781,7 +157992,7 @@ static int whereLoopAddVirtual( WHERETRACE(0x40, (" VirtualOne: mPrev=%04llx mNext=%04llx\n", (sqlite3_uint64)mPrev, (sqlite3_uint64)mNext)); rc = whereLoopAddVirtualOne( - pBuilder, mPrereq, mNext|mPrereq, 0, p, mNoOmit, &bIn); + pBuilder, mPrereq, mNext|mPrereq, 0, p, mNoOmit, &bIn, 0); if( pNew->prereq==mPrereq ){ seenZero = 1; if( bIn==0 ) seenZeroNoIN = 1; @@ -151794,7 +158005,7 @@ static int whereLoopAddVirtual( if( rc==SQLITE_OK && seenZero==0 ){ WHERETRACE(0x40, (" VirtualOne: all disabled\n")); rc = whereLoopAddVirtualOne( - pBuilder, mPrereq, mPrereq, 0, p, mNoOmit, &bIn); + pBuilder, mPrereq, mPrereq, 0, p, mNoOmit, &bIn, 0); if( bIn==0 ) seenZeroNoIN = 1; } @@ -151804,12 +158015,12 @@ static int whereLoopAddVirtual( if( rc==SQLITE_OK && seenZeroNoIN==0 ){ WHERETRACE(0x40, (" VirtualOne: all disabled and w/o IN\n")); rc = whereLoopAddVirtualOne( - pBuilder, mPrereq, mPrereq, WO_IN, p, mNoOmit, &bIn); + pBuilder, mPrereq, mPrereq, WO_IN, p, mNoOmit, &bIn, 0); } } if( p->needToFreeIdxStr ) sqlite3_free(p->idxStr); - sqlite3DbFreeNN(pParse->db, p); + freeIndexInfo(pParse->db, p); WHERETRACE(0x800, ("END %s.addVirtual(), rc=%d\n", pSrc->pTab->zName, rc)); return rc; } @@ -151842,6 +158053,9 @@ static int whereLoopAddOr( pItem = pWInfo->pTabList->a + pNew->iTab; iCur = pItem->iCursor; + /* The multi-index OR optimization does not work for RIGHT and FULL JOIN */ + if( pItem->fg.jointype & JT_RIGHT ) return SQLITE_OK; + for(pTerm=pWC->a; pTermeOperator & WO_OR)!=0 && (pTerm->u.pOrInfo->indexable & pNew->maskSelf)!=0 @@ -151853,7 +158067,6 @@ static int whereLoopAddOr( int i, j; sSubBuild = *pBuilder; - sSubBuild.pOrderBy = 0; sSubBuild.pOrSet = &sCur; WHERETRACE(0x200, ("Begin processing OR-clause %p\n", pTerm)); @@ -151865,6 +158078,7 @@ static int whereLoopAddOr( tempWC.pOuter = pWC; tempWC.op = TK_AND; tempWC.nTerm = 1; + tempWC.nBase = 1; tempWC.a = pOrTerm; sSubBuild.pWC = &tempWC; }else{ @@ -151889,7 +158103,9 @@ static int whereLoopAddOr( if( rc==SQLITE_OK ){ rc = whereLoopAddOr(&sSubBuild, mPrereq, mUnusable); } - assert( rc==SQLITE_OK || rc==SQLITE_DONE || sCur.n==0 ); + assert( rc==SQLITE_OK || rc==SQLITE_DONE || sCur.n==0 + || rc==SQLITE_NOMEM ); + testcase( rc==SQLITE_NOMEM && sCur.n>0 ); testcase( rc==SQLITE_DONE ); if( sCur.n==0 ){ sSum.n = 0; @@ -151953,8 +158169,11 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ SrcItem *pEnd = &pTabList->a[pWInfo->nLevel]; sqlite3 *db = pWInfo->pParse->db; int rc = SQLITE_OK; + int bFirstPastRJ = 0; + int hasRightJoin = 0; WhereLoop *pNew; + /* Loop over the tables in the join, from left to right */ pNew = pBuilder->pNew; whereLoopInit(pNew); @@ -151964,18 +158183,30 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ pNew->iTab = iTab; pBuilder->iPlanLimit += SQLITE_QUERY_PLANNER_LIMIT_INCR; pNew->maskSelf = sqlite3WhereGetMask(&pWInfo->sMaskSet, pItem->iCursor); - if( (pItem->fg.jointype & (JT_LEFT|JT_CROSS))!=0 ){ - /* This condition is true when pItem is the FROM clause term on the - ** right-hand-side of a LEFT or CROSS JOIN. */ - mPrereq = mPrior; - }else{ + if( bFirstPastRJ + || (pItem->fg.jointype & (JT_OUTER|JT_CROSS|JT_LTORJ))!=0 + ){ + /* Add prerequisites to prevent reordering of FROM clause terms + ** across CROSS joins and outer joins. The bFirstPastRJ boolean + ** prevents the right operand of a RIGHT JOIN from being swapped with + ** other elements even further to the right. + ** + ** The JT_LTORJ case and the hasRightJoin flag work together to + ** prevent FROM-clause terms from moving from the right side of + ** a LEFT JOIN over to the left side of that join if the LEFT JOIN + ** is itself on the left side of a RIGHT JOIN. + */ + if( pItem->fg.jointype & JT_LTORJ ) hasRightJoin = 1; + mPrereq |= mPrior; + bFirstPastRJ = (pItem->fg.jointype & JT_RIGHT)!=0; + }else if( !hasRightJoin ){ mPrereq = 0; } #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(pItem->pTab) ){ SrcItem *p; for(p=&pItem[1]; pfg.jointype & (JT_LEFT|JT_CROSS)) ){ + if( mUnusable || (p->fg.jointype & (JT_OUTER|JT_CROSS)) ){ mUnusable |= sqlite3WhereGetMask(&pWInfo->sMaskSet, p->iCursor); } } @@ -152100,7 +158331,9 @@ static i8 wherePathSatisfiesOrderBy( pLoop = pLast; } if( pLoop->wsFlags & WHERE_VIRTUALTABLE ){ - if( pLoop->u.vtab.isOrdered && (wctrlFlags & WHERE_DISTINCTBY)==0 ){ + if( pLoop->u.vtab.isOrdered + && ((wctrlFlags&(WHERE_DISTINCTBY|WHERE_SORTBYGROUP))!=WHERE_DISTINCTBY) + ){ obSat = obDone; } break; @@ -152118,7 +158351,7 @@ static i8 wherePathSatisfiesOrderBy( if( MASKBIT(i) & obSat ) continue; pOBExpr = sqlite3ExprSkipCollateAndLikely(pOrderBy->a[i].pExpr); if( NEVER(pOBExpr==0) ) continue; - if( pOBExpr->op!=TK_COLUMN ) continue; + if( pOBExpr->op!=TK_COLUMN && pOBExpr->op!=TK_AGG_COLUMN ) continue; if( pOBExpr->iTable!=iCur ) continue; pTerm = sqlite3WhereFindTerm(&pWInfo->sWC, iCur, pOBExpr->iColumn, ~ready, eqOpMask, 0); @@ -152158,6 +158391,10 @@ static i8 wherePathSatisfiesOrderBy( assert( nColumn==nKeyCol+1 || !HasRowid(pIndex->pTable) ); assert( pIndex->aiColumn[nColumn-1]==XN_ROWID || !HasRowid(pIndex->pTable)); + /* All relevant terms of the index must also be non-NULL in order + ** for isOrderDistinct to be true. So the isOrderDistint value + ** computed here might be a false positive. Corrections will be + ** made at tag-20210426-1 below */ isOrderDistinct = IsUniqueIndex(pIndex) && (pLoop->wsFlags & WHERE_SKIPSCAN)==0; } @@ -152225,14 +158462,18 @@ static i8 wherePathSatisfiesOrderBy( } /* An unconstrained column that might be NULL means that this - ** WhereLoop is not well-ordered + ** WhereLoop is not well-ordered. tag-20210426-1 */ - if( isOrderDistinct - && iColumn>=0 - && j>=pLoop->u.btree.nEq - && pIndex->pTable->aCol[iColumn].notNull==0 - ){ - isOrderDistinct = 0; + if( isOrderDistinct ){ + if( iColumn>=0 + && j>=pLoop->u.btree.nEq + && pIndex->pTable->aCol[iColumn].notNull==0 + ){ + isOrderDistinct = 0; + } + if( iColumn==XN_EXPR ){ + isOrderDistinct = 0; + } } /* Find the ORDER BY term that corresponds to the j-th column @@ -152247,7 +158488,7 @@ static i8 wherePathSatisfiesOrderBy( if( NEVER(pOBExpr==0) ) continue; if( (wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY))==0 ) bOnce = 0; if( iColumn>=XN_ROWID ){ - if( pOBExpr->op!=TK_COLUMN ) continue; + if( pOBExpr->op!=TK_COLUMN && pOBExpr->op!=TK_AGG_COLUMN ) continue; if( pOBExpr->iTable!=iCur ) continue; if( pOBExpr->iColumn!=iColumn ) continue; }else{ @@ -152270,16 +158511,18 @@ static i8 wherePathSatisfiesOrderBy( /* Make sure the sort order is compatible in an ORDER BY clause. ** Sort order is irrelevant for a GROUP BY clause. */ if( revSet ){ - if( (rev ^ revIdx)!=(pOrderBy->a[i].sortFlags&KEYINFO_ORDER_DESC) ){ + if( (rev ^ revIdx) + != (pOrderBy->a[i].fg.sortFlags&KEYINFO_ORDER_DESC) + ){ isMatch = 0; } }else{ - rev = revIdx ^ (pOrderBy->a[i].sortFlags & KEYINFO_ORDER_DESC); + rev = revIdx ^ (pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_DESC); if( rev ) *pRevMask |= MASKBIT(iLoop); revSet = 1; } } - if( isMatch && (pOrderBy->a[i].sortFlags & KEYINFO_ORDER_BIGNULL) ){ + if( isMatch && (pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_BIGNULL) ){ if( j==pLoop->u.btree.nEq ){ pLoop->wsFlags |= WHERE_BIGNULL_SORT; }else{ @@ -152326,7 +158569,7 @@ static i8 wherePathSatisfiesOrderBy( if( obSat==obDone ) return (i8)nOrderBy; if( !isOrderDistinct ){ for(i=nOrderBy-1; i>0; i--){ - Bitmask m = MASKBIT(i) - 1; + Bitmask m = ALWAYS(iwctrlFlags & WHERE_GROUPBY ); + assert( pWInfo->wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY) ); assert( pWInfo->wctrlFlags & WHERE_SORTBYGROUP ); return pWInfo->sorted; } @@ -152416,7 +158659,7 @@ static LogEst whereSortingCost( }else if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT) ){ /* TUNING: In the sort for a DISTINCT operator, assume that the DISTINCT ** reduces the number of output rows by a factor of 2 */ - if( nRow>10 ) nRow -= 10; assert( 10==sqlite3LogEst(2) ); + if( nRow>10 ){ nRow -= 10; assert( 10==sqlite3LogEst(2) ); } } rSortCost += estLog(nRow); return rSortCost; @@ -152760,12 +159003,12 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ } pWInfo->bOrderedInnerLoop = 0; if( pWInfo->pOrderBy ){ + pWInfo->nOBSat = pFrom->isOrdered; if( pWInfo->wctrlFlags & WHERE_DISTINCTBY ){ if( pFrom->isOrdered==pWInfo->pOrderBy->nExpr ){ pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; } }else{ - pWInfo->nOBSat = pFrom->isOrdered; pWInfo->revMask = pFrom->revLoop; if( pWInfo->nOBSat<=0 ){ pWInfo->nOBSat = 0; @@ -152836,6 +159079,7 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ int j; Table *pTab; Index *pIdx; + WhereScan scan; pWInfo = pBuilder->pWInfo; if( pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE ) return 0; @@ -152843,13 +159087,18 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ pItem = pWInfo->pTabList->a; pTab = pItem->pTab; if( IsVirtual(pTab) ) return 0; - if( pItem->fg.isIndexedBy ) return 0; + if( pItem->fg.isIndexedBy || pItem->fg.notIndexed ){ + testcase( pItem->fg.isIndexedBy ); + testcase( pItem->fg.notIndexed ); + return 0; + } iCur = pItem->iCursor; pWC = &pWInfo->sWC; pLoop = pBuilder->pNew; pLoop->wsFlags = 0; pLoop->nSkip = 0; - pTerm = sqlite3WhereFindTerm(pWC, iCur, -1, 0, WO_EQ|WO_IS, 0); + pTerm = whereScanInit(&scan, pWC, iCur, -1, WO_EQ|WO_IS, 0); + while( pTerm && pTerm->prereqRight ) pTerm = whereScanNext(&scan); if( pTerm ){ testcase( pTerm->eOperator & WO_IS ); pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_IPK|WHERE_ONEROW; @@ -152868,7 +159117,8 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ ) continue; opMask = pIdx->uniqNotNull ? (WO_EQ|WO_IS) : WO_EQ; for(j=0; jnKeyCol; j++){ - pTerm = sqlite3WhereFindTerm(pWC, iCur, j, 0, opMask, pIdx); + pTerm = whereScanInit(&scan, pWC, iCur, j, opMask, pIdx); + while( pTerm && pTerm->prereqRight ) pTerm = whereScanNext(&scan); if( pTerm==0 ) break; testcase( pTerm->eOperator & WO_IS ); pLoop->aLTerm[j] = pTerm; @@ -152897,8 +159147,14 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ if( pWInfo->wctrlFlags & WHERE_WANT_DISTINCT ){ pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; } + if( scan.iEquiv>1 ) pLoop->wsFlags |= WHERE_TRANSCONS; #ifdef SQLITE_DEBUG pLoop->cId = '0'; +#endif +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace ){ + sqlite3DebugPrintf("whereShortCut() used to compute solution\n"); + } #endif return 1; } @@ -152953,6 +159209,150 @@ static void showAllWhereLoops(WhereInfo *pWInfo, WhereClause *pWC){ # define WHERETRACE_ALL_LOOPS(W,C) #endif +/* Attempt to omit tables from a join that do not affect the result. +** For a table to not affect the result, the following must be true: +** +** 1) The query must not be an aggregate. +** 2) The table must be the RHS of a LEFT JOIN. +** 3) Either the query must be DISTINCT, or else the ON or USING clause +** must contain a constraint that limits the scan of the table to +** at most a single row. +** 4) The table must not be referenced by any part of the query apart +** from its own USING or ON clause. +** +** For example, given: +** +** CREATE TABLE t1(ipk INTEGER PRIMARY KEY, v1); +** CREATE TABLE t2(ipk INTEGER PRIMARY KEY, v2); +** CREATE TABLE t3(ipk INTEGER PRIMARY KEY, v3); +** +** then table t2 can be omitted from the following: +** +** SELECT v1, v3 FROM t1 +** LEFT JOIN t2 ON (t1.ipk=t2.ipk) +** LEFT JOIN t3 ON (t1.ipk=t3.ipk) +** +** or from: +** +** SELECT DISTINCT v1, v3 FROM t1 +** LEFT JOIN t2 +** LEFT JOIN t3 ON (t1.ipk=t3.ipk) +*/ +static SQLITE_NOINLINE Bitmask whereOmitNoopJoin( + WhereInfo *pWInfo, + Bitmask notReady +){ + int i; + Bitmask tabUsed; + + /* Preconditions checked by the caller */ + assert( pWInfo->nLevel>=2 ); + assert( OptimizationEnabled(pWInfo->pParse->db, SQLITE_OmitNoopJoin) ); + + /* These two preconditions checked by the caller combine to guarantee + ** condition (1) of the header comment */ + assert( pWInfo->pResultSet!=0 ); + assert( 0==(pWInfo->wctrlFlags & WHERE_AGG_DISTINCT) ); + + tabUsed = sqlite3WhereExprListUsage(&pWInfo->sMaskSet, pWInfo->pResultSet); + if( pWInfo->pOrderBy ){ + tabUsed |= sqlite3WhereExprListUsage(&pWInfo->sMaskSet, pWInfo->pOrderBy); + } + for(i=pWInfo->nLevel-1; i>=1; i--){ + WhereTerm *pTerm, *pEnd; + SrcItem *pItem; + WhereLoop *pLoop; + pLoop = pWInfo->a[i].pWLoop; + pItem = &pWInfo->pTabList->a[pLoop->iTab]; + if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))!=JT_LEFT ) continue; + if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)==0 + && (pLoop->wsFlags & WHERE_ONEROW)==0 + ){ + continue; + } + if( (tabUsed & pLoop->maskSelf)!=0 ) continue; + pEnd = pWInfo->sWC.a + pWInfo->sWC.nTerm; + for(pTerm=pWInfo->sWC.a; pTermprereqAll & pLoop->maskSelf)!=0 ){ + if( !ExprHasProperty(pTerm->pExpr, EP_OuterON) + || pTerm->pExpr->w.iJoin!=pItem->iCursor + ){ + break; + } + } + } + if( pTerm drop loop %c not used\n", pLoop->cId)); + notReady &= ~pLoop->maskSelf; + for(pTerm=pWInfo->sWC.a; pTermprereqAll & pLoop->maskSelf)!=0 ){ + pTerm->wtFlags |= TERM_CODED; + } + } + if( i!=pWInfo->nLevel-1 ){ + int nByte = (pWInfo->nLevel-1-i) * sizeof(WhereLevel); + memmove(&pWInfo->a[i], &pWInfo->a[i+1], nByte); + } + pWInfo->nLevel--; + assert( pWInfo->nLevel>0 ); + } + return notReady; +} + +/* +** Check to see if there are any SEARCH loops that might benefit from +** using a Bloom filter. Consider a Bloom filter if: +** +** (1) The SEARCH happens more than N times where N is the number +** of rows in the table that is being considered for the Bloom +** filter. +** (2) Some searches are expected to find zero rows. (This is determined +** by the WHERE_SELFCULL flag on the term.) +** (3) Bloom-filter processing is not disabled. (Checked by the +** caller.) +** (4) The size of the table being searched is known by ANALYZE. +** +** This block of code merely checks to see if a Bloom filter would be +** appropriate, and if so sets the WHERE_BLOOMFILTER flag on the +** WhereLoop. The implementation of the Bloom filter comes further +** down where the code for each WhereLoop is generated. +*/ +static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful( + const WhereInfo *pWInfo +){ + int i; + LogEst nSearch; + + assert( pWInfo->nLevel>=2 ); + assert( OptimizationEnabled(pWInfo->pParse->db, SQLITE_BloomFilter) ); + nSearch = pWInfo->a[0].pWLoop->nOut; + for(i=1; inLevel; i++){ + WhereLoop *pLoop = pWInfo->a[i].pWLoop; + const unsigned int reqFlags = (WHERE_SELFCULL|WHERE_COLUMN_EQ); + if( (pLoop->wsFlags & reqFlags)==reqFlags + /* vvvvvv--- Always the case if WHERE_COLUMN_EQ is defined */ + && ALWAYS((pLoop->wsFlags & (WHERE_IPK|WHERE_INDEXED))!=0) + ){ + SrcItem *pItem = &pWInfo->pTabList->a[pLoop->iTab]; + Table *pTab = pItem->pTab; + pTab->tabFlags |= TF_StatsUsed; + if( nSearch > pTab->nRowLogEst + && (pTab->tabFlags & TF_HasStat1)!=0 + ){ + testcase( pItem->fg.jointype & JT_LEFT ); + pLoop->wsFlags |= WHERE_BLOOMFILTER; + pLoop->wsFlags &= ~WHERE_IDX_ONLY; + WHERETRACE(0xffff, ( + "-> use Bloom-filter on loop %c because there are ~%.1e " + "lookups into %s which has only ~%.1e rows\n", + pLoop->cId, (double)sqlite3LogEstToInt(nSearch), pTab->zName, + (double)sqlite3LogEstToInt(pTab->nRowLogEst))); + } + } + nSearch += pLoop->nOut; + } +} + /* ** Generate the beginning of the loop used for WHERE clause processing. ** The return value is a pointer to an opaque structure that contains @@ -153047,6 +159447,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( Expr *pWhere, /* The WHERE clause */ ExprList *pOrderBy, /* An ORDER BY (or GROUP BY) clause, or NULL */ ExprList *pResultSet, /* Query result set. Req'd for DISTINCT */ + Select *pLimit, /* Use this LIMIT/OFFSET clause, if any */ u16 wctrlFlags, /* The WHERE_* flags defined in sqliteInt.h */ int iAuxArg /* If WHERE_OR_SUBCLAUSE is set, index cursor number ** If WHERE_USE_LIMIT, then the limit amount */ @@ -153081,13 +159482,6 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( /* An ORDER/GROUP BY clause of more than 63 terms cannot be optimized */ testcase( pOrderBy && pOrderBy->nExpr==BMS-1 ); if( pOrderBy && pOrderBy->nExpr>=BMS ) pOrderBy = 0; - sWLB.pOrderBy = pOrderBy; - - /* Disable the DISTINCT optimization if SQLITE_DistinctOpt is set via - ** sqlite3_test_ctrl(SQLITE_TESTCTRL_OPTIMIZATIONS,...) */ - if( OptimizationDisabled(db, SQLITE_DistinctOpt) ){ - wctrlFlags &= ~WHERE_WANT_DISTINCT; - } /* The number of tables in the FROM clause is limited by the number of ** bits in a Bitmask @@ -153112,7 +159506,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( ** field (type Bitmask) it must be aligned on an 8-byte boundary on ** some architectures. Hence the ROUND8() below. */ - nByteWInfo = ROUND8(sizeof(WhereInfo)+(nTabList-1)*sizeof(WhereLevel)); + nByteWInfo = ROUND8P(sizeof(WhereInfo)+(nTabList-1)*sizeof(WhereLevel)); pWInfo = sqlite3DbMallocRawNN(db, nByteWInfo + sizeof(WhereLoop)); if( db->mallocFailed ){ sqlite3DbFree(db, pWInfo); @@ -153130,11 +159524,18 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( pWInfo->wctrlFlags = wctrlFlags; pWInfo->iLimit = iAuxArg; pWInfo->savedNQueryLoop = pParse->nQueryLoop; +#ifndef SQLITE_OMIT_VIRTUALTABLE + pWInfo->pLimit = pLimit; +#endif memset(&pWInfo->nOBSat, 0, offsetof(WhereInfo,sWC) - offsetof(WhereInfo,nOBSat)); memset(&pWInfo->a[0], 0, sizeof(WhereLoop)+nTabList*sizeof(WhereLevel)); assert( pWInfo->eOnePass==ONEPASS_OFF ); /* ONEPASS defaults to OFF */ pMaskSet = &pWInfo->sMaskSet; + pMaskSet->n = 0; + pMaskSet->ix[0] = -99; /* Initialize ix[0] to a value that can never be + ** a valid cursor number, to avoid an initial + ** test for pMaskSet->n==0 in sqlite3WhereGetMask() */ sWLB.pWInfo = pWInfo; sWLB.pWC = &pWInfo->sWC; sWLB.pNew = (WhereLoop*)(((char*)pWInfo)+nByteWInfo); @@ -153147,7 +159548,6 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( /* Split the WHERE clause into separate subexpressions where each ** subexpression is separated by an AND operator. */ - initMaskSet(pMaskSet); sqlite3WhereClauseInit(&pWInfo->sWC, pWInfo); sqlite3WhereSplit(&pWInfo->sWC, pWhere, TK_AND); @@ -153155,7 +159555,9 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( */ if( nTabList==0 ){ if( pOrderBy ) pWInfo->nOBSat = pOrderBy->nExpr; - if( wctrlFlags & WHERE_WANT_DISTINCT ){ + if( (wctrlFlags & WHERE_WANT_DISTINCT)!=0 + && OptimizationEnabled(db, SQLITE_DistinctOpt) + ){ pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; } ExplainQueryPlan((pParse, 0, "SCAN CONSTANT ROW")); @@ -153193,7 +159595,8 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( /* Analyze all of the subexpressions. */ sqlite3WhereExprAnalyze(pTabList, &pWInfo->sWC); - if( db->mallocFailed ) goto whereBeginError; + sqlite3WhereAddLimit(&pWInfo->sWC, pLimit); + if( pParse->nErr ) goto whereBeginError; /* Special case: WHERE terms that do not refer to any tables in the join ** (constant expressions). Evaluate each such term, and jump over all the @@ -153206,7 +159609,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( ** FROM ... WHERE random()>0; -- eval random() once per row ** FROM ... WHERE (SELECT random())>0; -- eval random() once overall */ - for(ii=0; iinTerm; ii++){ + for(ii=0; iinBase; ii++){ WhereTerm *pT = &sWLB.pWC->a[ii]; if( pT->wtFlags & TERM_VIRTUAL ) continue; if( pT->prereqAll==0 && (nTabList==0 || exprIsDeterministic(pT->pExpr)) ){ @@ -153216,7 +159619,12 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( } if( wctrlFlags & WHERE_WANT_DISTINCT ){ - if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pResultSet) ){ + if( OptimizationDisabled(db, SQLITE_DistinctOpt) ){ + /* Disable the DISTINCT optimization if SQLITE_DistinctOpt is set via + ** sqlite3_test_ctrl(SQLITE_TESTCTRL_OPTIMIZATIONS,...) */ + wctrlFlags &= ~WHERE_WANT_DISTINCT; + pWInfo->wctrlFlags &= ~WHERE_WANT_DISTINCT; + }else if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pResultSet) ){ /* The DISTINCT marking is pointless. Ignore it. */ pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; }else if( pOrderBy==0 ){ @@ -153287,9 +159695,10 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( if( pWInfo->pOrderBy==0 && (db->flags & SQLITE_ReverseOrder)!=0 ){ pWInfo->revMask = ALLBITS; } - if( pParse->nErr || db->mallocFailed ){ + if( pParse->nErr ){ goto whereBeginError; } + assert( db->mallocFailed==0 ); #ifdef WHERETRACE_ENABLED if( sqlite3WhereTrace ){ sqlite3DebugPrintf("---- Solution nRow=%d", pWInfo->nRowOut); @@ -153317,83 +159726,36 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( } #endif - /* Attempt to omit tables from the join that do not affect the result. - ** For a table to not affect the result, the following must be true: + /* Attempt to omit tables from a join that do not affect the result. + ** See the comment on whereOmitNoopJoin() for further information. ** - ** 1) The query must not be an aggregate. - ** 2) The table must be the RHS of a LEFT JOIN. - ** 3) Either the query must be DISTINCT, or else the ON or USING clause - ** must contain a constraint that limits the scan of the table to - ** at most a single row. - ** 4) The table must not be referenced by any part of the query apart - ** from its own USING or ON clause. - ** - ** For example, given: - ** - ** CREATE TABLE t1(ipk INTEGER PRIMARY KEY, v1); - ** CREATE TABLE t2(ipk INTEGER PRIMARY KEY, v2); - ** CREATE TABLE t3(ipk INTEGER PRIMARY KEY, v3); - ** - ** then table t2 can be omitted from the following: - ** - ** SELECT v1, v3 FROM t1 - ** LEFT JOIN t2 ON (t1.ipk=t2.ipk) - ** LEFT JOIN t3 ON (t1.ipk=t3.ipk) - ** - ** or from: - ** - ** SELECT DISTINCT v1, v3 FROM t1 - ** LEFT JOIN t2 - ** LEFT JOIN t3 ON (t1.ipk=t3.ipk) + ** This query optimization is factored out into a separate "no-inline" + ** procedure to keep the sqlite3WhereBegin() procedure from becoming + ** too large. If sqlite3WhereBegin() becomes too large, that prevents + ** some C-compiler optimizers from in-lining the + ** sqlite3WhereCodeOneLoopStart() procedure, and it is important to + ** in-line sqlite3WhereCodeOneLoopStart() for performance reasons. */ notReady = ~(Bitmask)0; if( pWInfo->nLevel>=2 - && pResultSet!=0 /* guarantees condition (1) above */ + && pResultSet!=0 /* these two combine to guarantee */ + && 0==(wctrlFlags & WHERE_AGG_DISTINCT) /* condition (1) above */ && OptimizationEnabled(db, SQLITE_OmitNoopJoin) ){ - int i; - Bitmask tabUsed = sqlite3WhereExprListUsage(pMaskSet, pResultSet); - if( sWLB.pOrderBy ){ - tabUsed |= sqlite3WhereExprListUsage(pMaskSet, sWLB.pOrderBy); - } - for(i=pWInfo->nLevel-1; i>=1; i--){ - WhereTerm *pTerm, *pEnd; - SrcItem *pItem; - pLoop = pWInfo->a[i].pWLoop; - pItem = &pWInfo->pTabList->a[pLoop->iTab]; - if( (pItem->fg.jointype & JT_LEFT)==0 ) continue; - if( (wctrlFlags & WHERE_WANT_DISTINCT)==0 - && (pLoop->wsFlags & WHERE_ONEROW)==0 - ){ - continue; - } - if( (tabUsed & pLoop->maskSelf)!=0 ) continue; - pEnd = sWLB.pWC->a + sWLB.pWC->nTerm; - for(pTerm=sWLB.pWC->a; pTermprereqAll & pLoop->maskSelf)!=0 ){ - if( !ExprHasProperty(pTerm->pExpr, EP_FromJoin) - || pTerm->pExpr->iRightJoinTable!=pItem->iCursor - ){ - break; - } - } - } - if( pTerm drop loop %c not used\n", pLoop->cId)); - notReady &= ~pLoop->maskSelf; - for(pTerm=sWLB.pWC->a; pTermprereqAll & pLoop->maskSelf)!=0 ){ - pTerm->wtFlags |= TERM_CODED; - } - } - if( i!=pWInfo->nLevel-1 ){ - int nByte = (pWInfo->nLevel-1-i) * sizeof(WhereLevel); - memmove(&pWInfo->a[i], &pWInfo->a[i+1], nByte); - } - pWInfo->nLevel--; - nTabList--; - } + notReady = whereOmitNoopJoin(pWInfo, notReady); + nTabList = pWInfo->nLevel; + assert( nTabList>0 ); } + + /* Check to see if there are any SEARCH loops that might benefit from + ** using a Bloom filter. + */ + if( pWInfo->nLevel>=2 + && OptimizationEnabled(db, SQLITE_BloomFilter) + ){ + whereCheckIfBloomFilterIsUseful(pWInfo); + } + #if defined(WHERETRACE_ENABLED) if( sqlite3WhereTrace & 0x100 ){ /* Display all terms of the WHERE clause */ sqlite3DebugPrintf("---- WHERE clause at end of analysis:\n"); @@ -153454,7 +159816,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( pTab = pTabItem->pTab; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); pLoop = pLevel->pWLoop; - if( (pTab->tabFlags & TF_Ephemeral)!=0 || pTab->pSelect ){ + if( (pTab->tabFlags & TF_Ephemeral)!=0 || IsView(pTab) ){ /* Do nothing */ }else #ifndef SQLITE_OMIT_VIRTUALTABLE @@ -153466,8 +159828,10 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( /* noop */ }else #endif - if( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 - && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0 ){ + if( ((pLoop->wsFlags & WHERE_IDX_ONLY)==0 + && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0) + || (pTabItem->fg.jointype & (JT_LTORJ|JT_RIGHT))!=0 + ){ int op = OP_OpenRead; if( pWInfo->eOnePass!=ONEPASS_OFF ){ op = OP_OpenWrite; @@ -153480,6 +159844,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( if( pWInfo->eOnePass==ONEPASS_OFF && pTab->nColtabFlags & (TF_HasGenerated|TF_WithoutRowid))==0 + && (pLoop->wsFlags & (WHERE_AUTO_INDEX|WHERE_BLOOMFILTER))==0 ){ /* If we know that only a prefix of the record will be used, ** it is advantageous to reduce the "column count" field in @@ -153535,6 +159900,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( iIndexCur = pParse->nTab++; } pLevel->iIdxCur = iIndexCur; + assert( pIx!=0 ); assert( pIx->pSchema==pTab->pSchema ); assert( iIndexCur>=0 ); if( op ){ @@ -153568,6 +159934,37 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( } } if( iDb>=0 ) sqlite3CodeVerifySchema(pParse, iDb); + if( (pTabItem->fg.jointype & JT_RIGHT)!=0 + && (pLevel->pRJ = sqlite3WhereMalloc(pWInfo, sizeof(WhereRightJoin)))!=0 + ){ + WhereRightJoin *pRJ = pLevel->pRJ; + pRJ->iMatch = pParse->nTab++; + pRJ->regBloom = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Blob, 65536, pRJ->regBloom); + pRJ->regReturn = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Null, 0, pRJ->regReturn); + assert( pTab==pTabItem->pTab ); + if( HasRowid(pTab) ){ + KeyInfo *pInfo; + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRJ->iMatch, 1); + pInfo = sqlite3KeyInfoAlloc(pParse->db, 1, 0); + if( pInfo ){ + pInfo->aColl[0] = 0; + pInfo->aSortFlags[0] = 0; + sqlite3VdbeAppendP4(v, pInfo, P4_KEYINFO); + } + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRJ->iMatch, pPk->nKeyCol); + sqlite3VdbeSetP4KeyInfo(pParse, pPk); + } + pLoop->wsFlags &= ~WHERE_IDX_ONLY; + /* The nature of RIGHT JOIN processing is such that it messes up + ** the output order. So omit any ORDER BY/GROUP BY elimination + ** optimizations. We need to do an actual sort for RIGHT JOIN. */ + pWInfo->nOBSat = 0; + pWInfo->eDistinct = WHERE_DISTINCT_UNORDERED; + } } pWInfo->iTop = sqlite3VdbeCurrentAddr(v); if( db->mallocFailed ) goto whereBeginError; @@ -153579,15 +159976,31 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( for(ii=0; iinErr ) goto whereBeginError; pLevel = &pWInfo->a[ii]; wsFlags = pLevel->pWLoop->wsFlags; + pSrc = &pTabList->a[pLevel->iFrom]; + if( pSrc->fg.isMaterialized ){ + if( pSrc->fg.isCorrelated ){ + sqlite3VdbeAddOp2(v, OP_Gosub, pSrc->regReturn, pSrc->addrFillSub); + }else{ + int iOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Gosub, pSrc->regReturn, pSrc->addrFillSub); + sqlite3VdbeJumpHere(v, iOnce); + } + } + if( (wsFlags & (WHERE_AUTO_INDEX|WHERE_BLOOMFILTER))!=0 ){ + if( (wsFlags & WHERE_AUTO_INDEX)!=0 ){ #ifndef SQLITE_OMIT_AUTOMATIC_INDEX - if( (pLevel->pWLoop->wsFlags & WHERE_AUTO_INDEX)!=0 ){ - constructAutomaticIndex(pParse, &pWInfo->sWC, - &pTabList->a[pLevel->iFrom], notReady, pLevel); + constructAutomaticIndex(pParse, &pWInfo->sWC, + &pTabList->a[pLevel->iFrom], notReady, pLevel); +#endif + }else{ + sqlite3ConstructBloomFilter(pWInfo, ii, pLevel, notReady); + } if( db->mallocFailed ) goto whereBeginError; } -#endif addrExplain = sqlite3WhereExplainOneScan( pParse, pTabList, pLevel, wctrlFlags ); @@ -153607,6 +160020,8 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( /* Jump here if malloc fails */ whereBeginError: if( pWInfo ){ + testcase( pWInfo->pExprMods!=0 ); + whereUndoExprMods(pWInfo); pParse->nQueryLoop = pWInfo->savedNQueryLoop; whereInfoFree(db, pWInfo); } @@ -153633,6 +160048,26 @@ whereBeginError: } #endif +#ifdef SQLITE_DEBUG +/* +** Return true if cursor iCur is opened by instruction k of the +** bytecode. Used inside of assert() only. +*/ +static int cursorIsOpen(Vdbe *v, int iCur, int k){ + while( k>=0 ){ + VdbeOp *pOp = sqlite3VdbeGetOp(v,k--); + if( pOp->p1!=iCur ) continue; + if( pOp->opcode==OP_Close ) return 0; + if( pOp->opcode==OP_OpenRead ) return 1; + if( pOp->opcode==OP_OpenWrite ) return 1; + if( pOp->opcode==OP_OpenDup ) return 1; + if( pOp->opcode==OP_OpenAutoindex ) return 1; + if( pOp->opcode==OP_OpenEphemeral ) return 1; + } + return 0; +} +#endif /* SQLITE_DEBUG */ + /* ** Generate the end of the WHERE loop. See comments on ** sqlite3WhereBegin() for additional information. @@ -153646,6 +160081,7 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ SrcList *pTabList = pWInfo->pTabList; sqlite3 *db = pParse->db; int iEnd = sqlite3VdbeCurrentAddr(v); + int nRJ = 0; /* Generate loop termination code. */ @@ -153653,6 +160089,17 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ for(i=pWInfo->nLevel-1; i>=0; i--){ int addr; pLevel = &pWInfo->a[i]; + if( pLevel->pRJ ){ + /* Terminate the subroutine that forms the interior of the loop of + ** the RIGHT JOIN table */ + WhereRightJoin *pRJ = pLevel->pRJ; + sqlite3VdbeResolveLabel(v, pLevel->addrCont); + pLevel->addrCont = 0; + pRJ->endSubrtn = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp3(v, OP_Return, pRJ->regReturn, pRJ->addrSubrtn, 1); + VdbeCoverage(v); + nRJ++; + } pLoop = pLevel->pWLoop; if( pLevel->op!=OP_Noop ){ #ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT @@ -153680,7 +160127,7 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ } #endif /* SQLITE_DISABLE_SKIPAHEAD_DISTINCT */ /* The common case: Advance to the next row */ - sqlite3VdbeResolveLabel(v, pLevel->addrCont); + if( pLevel->addrCont ) sqlite3VdbeResolveLabel(v, pLevel->addrCont); sqlite3VdbeAddOp3(v, pLevel->op, pLevel->p1, pLevel->p2, pLevel->p3); sqlite3VdbeChangeP5(v, pLevel->p5); VdbeCoverage(v); @@ -153695,14 +160142,16 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ #ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT if( addrSeek ) sqlite3VdbeJumpHere(v, addrSeek); #endif - }else{ + }else if( pLevel->addrCont ){ sqlite3VdbeResolveLabel(v, pLevel->addrCont); } - if( pLoop->wsFlags & WHERE_IN_ABLE && pLevel->u.in.nIn>0 ){ + if( (pLoop->wsFlags & WHERE_IN_ABLE)!=0 && pLevel->u.in.nIn>0 ){ struct InLoop *pIn; int j; sqlite3VdbeResolveLabel(v, pLevel->addrNxt); for(j=pLevel->u.in.nIn, pIn=&pLevel->u.in.aInLoop[j-1]; j>0; j--, pIn--){ + assert( sqlite3VdbeGetOp(v, pIn->addrInTop+1)->opcode==OP_IsNull + || pParse->db->mallocFailed ); sqlite3VdbeJumpHere(v, pIn->addrInTop+1); if( pIn->eEndLoopOp!=OP_Noop ){ if( pIn->nPrefix ){ @@ -153727,6 +160176,11 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ sqlite3VdbeCurrentAddr(v)+2, pIn->iBase, pIn->nPrefix); VdbeCoverage(v); + /* Retarget the OP_IsNull against the left operand of IN so + ** it jumps past the OP_IfNoHope. This is because the + ** OP_IsNull also bypasses the OP_Affinity opcode that is + ** required by OP_IfNoHope. */ + sqlite3VdbeJumpHere(v, pIn->addrInTop+1); } } sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop); @@ -153738,6 +160192,10 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ } } sqlite3VdbeResolveLabel(v, pLevel->addrBrk); + if( pLevel->pRJ ){ + sqlite3VdbeAddOp3(v, OP_Return, pLevel->pRJ->regReturn, 0, 1); + VdbeCoverage(v); + } if( pLevel->addrSkip ){ sqlite3VdbeGoto(v, pLevel->addrSkip); VdbeComment((v, "next skip-scan on %s", pLoop->u.btree.pIndex->zName)); @@ -153760,8 +160218,14 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iTabCur); } if( (ws & WHERE_INDEXED) - || ((ws & WHERE_MULTI_OR) && pLevel->u.pCovidx) + || ((ws & WHERE_MULTI_OR) && pLevel->u.pCoveringIdx) ){ + if( ws & WHERE_MULTI_OR ){ + Index *pIx = pLevel->u.pCoveringIdx; + int iDb = sqlite3SchemaToIndex(db, pIx->pSchema); + sqlite3VdbeAddOp3(v, OP_ReopenIdx, pLevel->iIdxCur, pIx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIx); + } sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iIdxCur); } if( pLevel->op==OP_Return ){ @@ -153775,12 +160239,8 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ pWInfo->pTabList->a[pLevel->iFrom].pTab->zName)); } - /* The "break" point is here, just past the end of the outer loop. - ** Set it. - */ - sqlite3VdbeResolveLabel(v, pWInfo->iBreak); - assert( pWInfo->nLevel<=pTabList->nSrc ); + if( pWInfo->pExprMods ) whereUndoExprMods(pWInfo); for(i=0, pLevel=pWInfo->a; inLevel; i++, pLevel++){ int k, last; VdbeOp *pOp, *pLastOp; @@ -153790,6 +160250,15 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ assert( pTab!=0 ); pLoop = pLevel->pWLoop; + /* Do RIGHT JOIN processing. Generate code that will output the + ** unmatched rows of the right operand of the RIGHT JOIN with + ** all of the columns of the left operand set to NULL. + */ + if( pLevel->pRJ ){ + sqlite3WhereRightJoinLoop(pWInfo, i, pLevel); + continue; + } + /* For a co-routine, change all OP_Column references to the table of ** the co-routine into OP_Copy of result contained in a register. ** OP_Rowid becomes OP_Null. @@ -153801,29 +160270,6 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ continue; } -#ifdef SQLITE_ENABLE_EARLY_CURSOR_CLOSE - /* Close all of the cursors that were opened by sqlite3WhereBegin. - ** Except, do not close cursors that will be reused by the OR optimization - ** (WHERE_OR_SUBCLAUSE). And do not close the OP_OpenWrite cursors - ** created for the ONEPASS optimization. - */ - if( (pTab->tabFlags & TF_Ephemeral)==0 - && pTab->pSelect==0 - && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 - ){ - int ws = pLoop->wsFlags; - if( pWInfo->eOnePass==ONEPASS_OFF && (ws & WHERE_IDX_ONLY)==0 ){ - sqlite3VdbeAddOp1(v, OP_Close, pTabItem->iCursor); - } - if( (ws & WHERE_INDEXED)!=0 - && (ws & (WHERE_IPK|WHERE_AUTO_INDEX))==0 - && pLevel->iIdxCur!=pWInfo->aiCurOnePass[1] - ){ - sqlite3VdbeAddOp1(v, OP_Close, pLevel->iIdxCur); - } - } -#endif - /* If this scan uses an index, make VDBE code substitutions to read data ** from the index instead of from the table where possible. In some cases ** this optimization prevents the table from ever being read, which can @@ -153838,7 +160284,7 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ if( pLoop->wsFlags & (WHERE_INDEXED|WHERE_IDX_ONLY) ){ pIdx = pLoop->u.btree.pIndex; }else if( pLoop->wsFlags & WHERE_MULTI_OR ){ - pIdx = pLevel->u.pCovidx; + pIdx = pLevel->u.pCoveringIdx; } if( pIdx && !db->mallocFailed @@ -153861,7 +160307,7 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ #endif pOp = sqlite3VdbeGetOp(v, k); pLastOp = pOp + (last - k); - assert( pOpnErr>0 && pOp==pLastOp) ); + assert( pOp<=pLastOp ); do{ if( pOp->p1!=pLevel->iTabCur ){ /* no-op */ @@ -153872,6 +160318,11 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ ){ int x = pOp->p2; assert( pIdx->pTable==pTab ); +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + if( pOp->opcode==OP_Offset ){ + /* Do not need to translate the column number */ + }else +#endif if( !HasRowid(pTab) ){ Index *pPk = sqlite3PrimaryKeyIndex(pTab); x = pPk->aiColumn[x]; @@ -153885,9 +160336,22 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ pOp->p2 = x; pOp->p1 = pLevel->iIdxCur; OpcodeRewriteTrace(db, k, pOp); + }else{ + /* Unable to translate the table reference into an index + ** reference. Verify that this is harmless - that the + ** table being referenced really is open. + */ +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 + || cursorIsOpen(v,pOp->p1,k) + || pOp->opcode==OP_Offset + ); +#else + assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 + || cursorIsOpen(v,pOp->p1,k) + ); +#endif } - assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 || x>=0 - || pWInfo->eOnePass ); }else if( pOp->opcode==OP_Rowid ){ pOp->p1 = pLevel->iIdxCur; pOp->opcode = OP_IdxRowid; @@ -153906,18 +160370,16 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ } } - /* Undo all Expr node modifications */ - while( pWInfo->pExprMods ){ - WhereExprMod *p = pWInfo->pExprMods; - pWInfo->pExprMods = p->pNext; - memcpy(p->pExpr, &p->orig, sizeof(p->orig)); - sqlite3DbFree(db, p); - } + /* The "break" point is here, just past the end of the outer loop. + ** Set it. + */ + sqlite3VdbeResolveLabel(v, pWInfo->iBreak); /* Final cleanup */ pParse->nQueryLoop = pWInfo->savedNQueryLoop; whereInfoFree(db, pWInfo); + pParse->withinRJSubrtn -= nRJ; return; } @@ -154506,7 +160968,7 @@ static void noopValueFunc(sqlite3_context *p){ UNUSED_PARAMETER(p); /*no-op*/ } /* Window functions that use all window interfaces: xStep, xFinal, ** xValue, and xInverse */ #define WINDOWFUNCALL(name,nArg,extra) { \ - nArg, (SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ + nArg, (SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ name ## StepFunc, name ## FinalizeFunc, name ## ValueFunc, \ name ## InvFunc, name ## Name, {0} \ } @@ -154514,7 +160976,7 @@ static void noopValueFunc(sqlite3_context *p){ UNUSED_PARAMETER(p); /*no-op*/ } /* Window functions that are implemented using bytecode and thus have ** no-op routines for their methods */ #define WINDOWFUNCNOOP(name,nArg,extra) { \ - nArg, (SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ + nArg, (SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ noopStepFunc, noopValueFunc, noopValueFunc, \ noopStepFunc, name ## Name, {0} \ } @@ -154523,7 +160985,7 @@ static void noopValueFunc(sqlite3_context *p){ UNUSED_PARAMETER(p); /*no-op*/ } ** same routine for xFinalize and xValue and which never call ** xInverse. */ #define WINDOWFUNCX(name,nArg,extra) { \ - nArg, (SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ + nArg, (SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ name ## StepFunc, name ## ValueFunc, name ## ValueFunc, \ noopStepFunc, name ## Name, {0} \ } @@ -154649,7 +161111,7 @@ SQLITE_PRIVATE void sqlite3WindowUpdate( } } } - pWin->pFunc = pFunc; + pWin->pWFunc = pFunc; } /* @@ -154713,6 +161175,7 @@ static int selectWindowRewriteExprCb(Walker *pWalker, Expr *pExpr){ case TK_AGG_FUNCTION: case TK_COLUMN: { int iCol = -1; + if( pParse->db->mallocFailed ) return WRC_Abort; if( p->pSub ){ int i; for(i=0; ipSub->nExpr; i++){ @@ -154822,14 +161285,17 @@ static ExprList *exprListAppendList( int i; int nInit = pList ? pList->nExpr : 0; for(i=0; inExpr; i++){ - Expr *pDup = sqlite3ExprDup(pParse->db, pAppend->a[i].pExpr, 0); + sqlite3 *db = pParse->db; + Expr *pDup = sqlite3ExprDup(db, pAppend->a[i].pExpr, 0); assert( pDup==0 || !ExprHasProperty(pDup, EP_MemToken) ); - if( bIntToNull && pDup ){ + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pDup); + break; + } + if( bIntToNull ){ int iDummy; Expr *pSub; - for(pSub=pDup; ExprHasProperty(pSub, EP_Skip); pSub=pSub->pLeft){ - assert( pSub ); - } + pSub = sqlite3ExprSkipCollateAndLikely(pDup); if( sqlite3ExprIsInteger(pSub, &iDummy) ){ pSub->op = TK_NULL; pSub->flags &= ~(EP_IntValue|EP_IsTrue|EP_IsFalse); @@ -154837,7 +161303,7 @@ static ExprList *exprListAppendList( } } pList = sqlite3ExprListAppend(pParse, pList, pDup); - if( pList ) pList->a[nInit+i].sortFlags = pAppend->a[i].sortFlags; + if( pList ) pList->a[nInit+i].fg.sortFlags = pAppend->a[i].fg.sortFlags; } } return pList; @@ -154860,6 +161326,15 @@ static int sqlite3WindowExtraAggFuncDepth(Walker *pWalker, Expr *pExpr){ return WRC_Continue; } +static int disallowAggregatesInOrderByCb(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_AGG_FUNCTION && pExpr->pAggInfo==0 ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3ErrorMsg(pWalker->pParse, + "misuse of aggregate: %s()", pExpr->u.zToken); + } + return WRC_Continue; +} + /* ** If the SELECT statement passed as the second argument does not invoke ** any SQL window functions, this function is a no-op. Otherwise, it @@ -154869,7 +161344,11 @@ static int sqlite3WindowExtraAggFuncDepth(Walker *pWalker, Expr *pExpr){ */ SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){ int rc = SQLITE_OK; - if( p->pWin && p->pPrior==0 && (p->selFlags & SF_WinRewrite)==0 ){ + if( p->pWin + && p->pPrior==0 + && ALWAYS((p->selFlags & SF_WinRewrite)==0) + && ALWAYS(!IN_RENAME_OBJECT) + ){ Vdbe *v = sqlite3GetVdbe(pParse); sqlite3 *db = pParse->db; Select *pSub = 0; /* The subquery */ @@ -154893,6 +161372,11 @@ SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){ } sqlite3AggInfoPersistWalkerInit(&w, pParse); sqlite3WalkSelect(&w, p); + if( (p->selFlags & SF_Aggregate)==0 ){ + w.xExprCallback = disallowAggregatesInOrderByCb; + w.xSelectCallback = 0; + sqlite3WalkExprList(&w, p->pOrderBy); + } p->pSrc = 0; p->pWhere = 0; @@ -154937,8 +161421,11 @@ SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){ ** window function - one for the accumulator, another for interim ** results. */ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - ExprList *pArgs = pWin->pOwner->x.pList; - if( pWin->pFunc->funcFlags & SQLITE_FUNC_SUBTYPE ){ + ExprList *pArgs; + assert( ExprUseXList(pWin->pOwner) ); + assert( pWin->pWFunc!=0 ); + pArgs = pWin->pOwner->x.pList; + if( pWin->pWFunc->funcFlags & SQLITE_FUNC_SUBTYPE ){ selectWindowRewriteEList(pParse, pMWin, pSrc, pArgs, pTab, &pSublist); pWin->iArgCol = (pSublist ? pSublist->nExpr : 0); pWin->bExprArgs = 1; @@ -154974,11 +161461,14 @@ SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){ ("New window-function subquery in FROM clause of (%u/%p)\n", p->selId, p)); p->pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); + assert( pSub!=0 || p->pSrc==0 ); /* Due to db->mallocFailed test inside + ** of sqlite3DbMallocRawNN() called from + ** sqlite3SrcListAppend() */ if( p->pSrc ){ Table *pTab2; p->pSrc->a[0].pSelect = pSub; sqlite3SrcListAssignCursors(pParse, p->pSrc); - pSub->selFlags |= SF_Expanded; + pSub->selFlags |= SF_Expanded|SF_OrderByReqd; pTab2 = sqlite3ResultSetOfSelect(pParse, pSub, SQLITE_AFF_NONE); pSub->selFlags |= (selFlags & SF_Aggregate); if( pTab2==0 ){ @@ -155001,15 +161491,14 @@ SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){ sqlite3SelectDelete(db, pSub); } if( db->mallocFailed ) rc = SQLITE_NOMEM; - sqlite3DbFree(db, pTab); + + /* Defer deleting the temporary table pTab because if an error occurred, + ** there could still be references to that table embedded in the + ** result-set or ORDER BY clause of the SELECT statement p. */ + sqlite3ParserAddCleanup(pParse, sqlite3DbFree, pTab); } - if( rc ){ - if( pParse->nErr==0 ){ - assert( pParse->db->mallocFailed ); - sqlite3ErrorToParser(pParse->db, SQLITE_NOMEM); - } - } + assert( rc==SQLITE_OK || pParse->nErr!=0 ); return rc; } @@ -155250,7 +161739,12 @@ SQLITE_PRIVATE void sqlite3WindowLink(Select *pSel, Window *pWin){ ** different, or 2 if it cannot be determined if the objects are identical ** or not. Identical window objects can be processed in a single scan. */ -SQLITE_PRIVATE int sqlite3WindowCompare(Parse *pParse, Window *p1, Window *p2, int bFilter){ +SQLITE_PRIVATE int sqlite3WindowCompare( + const Parse *pParse, + const Window *p1, + const Window *p2, + int bFilter +){ int res; if( NEVER(p1==0) || NEVER(p2==0) ) return 1; if( p1->eFrmType!=p2->eFrmType ) return 1; @@ -155313,7 +161807,7 @@ SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse *pParse, Select *pSelect){ } for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - FuncDef *p = pWin->pFunc; + FuncDef *p = pWin->pWFunc; if( (p->funcFlags & SQLITE_FUNC_MINMAX) && pWin->eStart!=TK_UNBOUNDED ){ /* The inline versions of min() and max() require a single ephemeral ** table and 3 registers. The registers are used as follows: @@ -155322,12 +161816,15 @@ SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse *pParse, Select *pSelect){ ** regApp+1: integer value used to ensure keys are unique ** regApp+2: output of MakeRecord */ - ExprList *pList = pWin->pOwner->x.pList; - KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pList, 0, 0); + ExprList *pList; + KeyInfo *pKeyInfo; + assert( ExprUseXList(pWin->pOwner) ); + pList = pWin->pOwner->x.pList; + pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pList, 0, 0); pWin->csrApp = pParse->nTab++; pWin->regApp = pParse->nMem+1; pParse->nMem += 3; - if( pKeyInfo && pWin->pFunc->zName[1]=='i' ){ + if( pKeyInfo && pWin->pWFunc->zName[1]=='i' ){ assert( pKeyInfo->aSortFlags[0]==0 ); pKeyInfo->aSortFlags[0] = KEYINFO_ORDER_DESC; } @@ -155411,7 +161908,9 @@ static void windowCheckValue(Parse *pParse, int reg, int eCond){ ** with the object passed as the only argument to this function. */ static int windowArgCount(Window *pWin){ - ExprList *pList = pWin->pOwner->x.pList; + const ExprList *pList; + assert( ExprUseXList(pWin->pOwner) ); + pList = pWin->pOwner->x.pList; return (pList ? pList->nExpr : 0); } @@ -155489,6 +161988,7 @@ struct WindowCodeArg { int regGosub; /* Register used with OP_Gosub(addrGosub) */ int regArg; /* First in array of accumulator registers */ int eDelete; /* See above */ + int regRowid; WindowCsrAndReg start; WindowCsrAndReg current; @@ -155547,7 +162047,7 @@ static void windowAggStep( Vdbe *v = sqlite3GetVdbe(pParse); Window *pWin; for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - FuncDef *pFunc = pWin->pFunc; + FuncDef *pFunc = pWin->pWFunc; int regArg; int nArg = pWin->bExprArgs ? 0 : windowArgCount(pWin); int i; @@ -155595,6 +162095,7 @@ static void windowAggStep( int addrIf = 0; if( pWin->pFilter ){ int regTmp; + assert( ExprUseXList(pWin->pOwner) ); assert( pWin->bExprArgs || !nArg ||nArg==pWin->pOwner->x.pList->nExpr ); assert( pWin->bExprArgs || nArg ||pWin->pOwner->x.pList==0 ); regTmp = sqlite3GetTempReg(pParse); @@ -155605,16 +162106,17 @@ static void windowAggStep( } if( pWin->bExprArgs ){ - int iStart = sqlite3VdbeCurrentAddr(v); - VdbeOp *pOp, *pEnd; + int iOp = sqlite3VdbeCurrentAddr(v); + int iEnd; + assert( ExprUseXList(pWin->pOwner) ); nArg = pWin->pOwner->x.pList->nExpr; regArg = sqlite3GetTempRange(pParse, nArg); sqlite3ExprCodeExprList(pParse, pWin->pOwner->x.pList, regArg, 0, 0); - pEnd = sqlite3VdbeGetOp(v, -1); - for(pOp=sqlite3VdbeGetOp(v, iStart); pOp<=pEnd; pOp++){ - if( pOp->opcode==OP_Column && pOp->p1==pWin->iEphCsr ){ + for(iEnd=sqlite3VdbeCurrentAddr(v); iOpopcode==OP_Column && pOp->p1==pMWin->iEphCsr ){ pOp->p1 = csr; } } @@ -155622,6 +162124,7 @@ static void windowAggStep( if( pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ CollSeq *pColl; assert( nArg>0 ); + assert( ExprUseXList(pWin->pOwner) ); pColl = sqlite3ExprNNCollSeq(pParse, pWin->pOwner->x.pList->a[0].pExpr); sqlite3VdbeAddOp4(v, OP_CollSeq, 0,0,0, (const char*)pColl, P4_COLLSEQ); } @@ -155658,7 +162161,7 @@ static void windowAggFinal(WindowCodeArg *p, int bFin){ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ if( pMWin->regStartRowid==0 - && (pWin->pFunc->funcFlags & SQLITE_FUNC_MINMAX) + && (pWin->pWFunc->funcFlags & SQLITE_FUNC_MINMAX) && (pWin->eStart!=TK_UNBOUNDED) ){ sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult); @@ -155672,12 +162175,12 @@ static void windowAggFinal(WindowCodeArg *p, int bFin){ int nArg = windowArgCount(pWin); if( bFin ){ sqlite3VdbeAddOp2(v, OP_AggFinal, pWin->regAccum, nArg); - sqlite3VdbeAppendP4(v, pWin->pFunc, P4_FUNCDEF); + sqlite3VdbeAppendP4(v, pWin->pWFunc, P4_FUNCDEF); sqlite3VdbeAddOp2(v, OP_Copy, pWin->regAccum, pWin->regResult); sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum); }else{ sqlite3VdbeAddOp3(v, OP_AggValue,pWin->regAccum,nArg,pWin->regResult); - sqlite3VdbeAppendP4(v, pWin->pFunc, P4_FUNCDEF); + sqlite3VdbeAppendP4(v, pWin->pWFunc, P4_FUNCDEF); } } } @@ -155806,7 +162309,8 @@ static void windowReturnOneRow(WindowCodeArg *p){ Window *pWin; for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - FuncDef *pFunc = pWin->pFunc; + FuncDef *pFunc = pWin->pWFunc; + assert( ExprUseXList(pWin->pOwner) ); if( pFunc->zName==nth_valueName || pFunc->zName==first_valueName ){ @@ -155877,7 +162381,7 @@ static int windowInitAccum(Parse *pParse, Window *pMWin){ int nArg = 0; Window *pWin; for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - FuncDef *pFunc = pWin->pFunc; + FuncDef *pFunc = pWin->pWFunc; assert( pWin->regAccum ); sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum); nArg = MAX(nArg, windowArgCount(pWin)); @@ -155907,7 +162411,7 @@ static int windowCacheFrame(Window *pMWin){ Window *pWin; if( pMWin->regStartRowid ) return 1; for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - FuncDef *pFunc = pWin->pFunc; + FuncDef *pFunc = pWin->pWFunc; if( (pFunc->zName==nth_valueName) || (pFunc->zName==first_valueName) || (pFunc->zName==leadName) @@ -155972,7 +162476,7 @@ static void windowIfNewPeer( ** if( csr1.peerVal - regVal <= csr2.peerVal ) goto lbl; ** ** A special type of arithmetic is used such that if csr1.peerVal is not -** a numeric type (real or integer), then the result of the addition addition +** a numeric type (real or integer), then the result of the addition ** or subtraction is a a copy of csr1.peerVal. */ static void windowCodeRangeTest( @@ -155991,11 +162495,16 @@ static void windowCodeRangeTest( int regString = ++pParse->nMem; /* Reg. for constant value '' */ int arith = OP_Add; /* OP_Add or OP_Subtract */ int addrGe; /* Jump destination */ + int addrDone = sqlite3VdbeMakeLabel(pParse); /* Address past OP_Ge */ CollSeq *pColl; + /* Read the peer-value from each cursor into a register */ + windowReadPeerValues(p, csr1, reg1); + windowReadPeerValues(p, csr2, reg2); + assert( op==OP_Ge || op==OP_Gt || op==OP_Le ); assert( pOrderBy && pOrderBy->nExpr==1 ); - if( pOrderBy->a[0].sortFlags & KEYINFO_ORDER_DESC ){ + if( pOrderBy->a[0].fg.sortFlags & KEYINFO_ORDER_DESC ){ switch( op ){ case OP_Ge: op = OP_Le; break; case OP_Gt: op = OP_Lt; break; @@ -156004,34 +162513,11 @@ static void windowCodeRangeTest( arith = OP_Subtract; } - /* Read the peer-value from each cursor into a register */ - windowReadPeerValues(p, csr1, reg1); - windowReadPeerValues(p, csr2, reg2); - VdbeModuleComment((v, "CodeRangeTest: if( R%d %s R%d %s R%d ) goto lbl", reg1, (arith==OP_Add ? "+" : "-"), regVal, ((op==OP_Ge) ? ">=" : (op==OP_Le) ? "<=" : (op==OP_Gt) ? ">" : "<"), reg2 )); - /* Register reg1 currently contains csr1.peerVal (the peer-value from csr1). - ** This block adds (or subtracts for DESC) the numeric value in regVal - ** from it. Or, if reg1 is not numeric (it is a NULL, a text value or a blob), - ** then leave reg1 as it is. In pseudo-code, this is implemented as: - ** - ** if( reg1>='' ) goto addrGe; - ** reg1 = reg1 +/- regVal - ** addrGe: - ** - ** Since all strings and blobs are greater-than-or-equal-to an empty string, - ** the add/subtract is skipped for these, as required. If reg1 is a NULL, - ** then the arithmetic is performed, but since adding or subtracting from - ** NULL is always NULL anyway, this case is handled as required too. */ - sqlite3VdbeAddOp4(v, OP_String8, 0, regString, 0, "", P4_STATIC); - addrGe = sqlite3VdbeAddOp3(v, OP_Ge, regString, 0, reg1); - VdbeCoverage(v); - sqlite3VdbeAddOp3(v, arith, regVal, reg1, reg1); - sqlite3VdbeJumpHere(v, addrGe); - /* If the BIGNULL flag is set for the ORDER BY, then it is required to ** consider NULL values to be larger than all other values, instead of ** the usual smaller. The VDBE opcodes OP_Ge and so on do not handle this @@ -156051,7 +162537,7 @@ static void windowCodeRangeTest( ** Additionally, if either reg1 or reg2 are NULL but the jump to lbl is ** not taken, control jumps over the comparison operator coded below this ** block. */ - if( pOrderBy->a[0].sortFlags & KEYINFO_ORDER_BIGNULL ){ + if( pOrderBy->a[0].fg.sortFlags & KEYINFO_ORDER_BIGNULL ){ /* This block runs if reg1 contains a NULL. */ int addr = sqlite3VdbeAddOp1(v, OP_NotNull, reg1); VdbeCoverage(v); switch( op ){ @@ -156068,16 +162554,38 @@ static void windowCodeRangeTest( break; default: assert( op==OP_Lt ); /* no-op */ break; } - sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+3); + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrDone); /* This block runs if reg1 is not NULL, but reg2 is. */ sqlite3VdbeJumpHere(v, addr); sqlite3VdbeAddOp2(v, OP_IsNull, reg2, lbl); VdbeCoverage(v); if( op==OP_Gt || op==OP_Ge ){ - sqlite3VdbeChangeP2(v, -1, sqlite3VdbeCurrentAddr(v)+1); + sqlite3VdbeChangeP2(v, -1, addrDone); } } + /* Register reg1 currently contains csr1.peerVal (the peer-value from csr1). + ** This block adds (or subtracts for DESC) the numeric value in regVal + ** from it. Or, if reg1 is not numeric (it is a NULL, a text value or a blob), + ** then leave reg1 as it is. In pseudo-code, this is implemented as: + ** + ** if( reg1>='' ) goto addrGe; + ** reg1 = reg1 +/- regVal + ** addrGe: + ** + ** Since all strings and blobs are greater-than-or-equal-to an empty string, + ** the add/subtract is skipped for these, as required. If reg1 is a NULL, + ** then the arithmetic is performed, but since adding or subtracting from + ** NULL is always NULL anyway, this case is handled as required too. */ + sqlite3VdbeAddOp4(v, OP_String8, 0, regString, 0, "", P4_STATIC); + addrGe = sqlite3VdbeAddOp3(v, OP_Ge, regString, 0, reg1); + VdbeCoverage(v); + if( (op==OP_Ge && arith==OP_Add) || (op==OP_Le && arith==OP_Subtract) ){ + sqlite3VdbeAddOp3(v, op, reg2, lbl, reg1); VdbeCoverage(v); + } + sqlite3VdbeAddOp3(v, arith, regVal, reg1, reg1); + sqlite3VdbeJumpHere(v, addrGe); + /* Compare registers reg2 and reg1, taking the jump if required. Note that ** control skips over this test if the BIGNULL flag is set and either ** reg1 or reg2 contain a NULL value. */ @@ -156085,6 +162593,7 @@ static void windowCodeRangeTest( pColl = sqlite3ExprNNCollSeq(pParse, pOrderBy->a[0].pExpr); sqlite3VdbeAppendP4(v, (void*)pColl, P4_COLLSEQ); sqlite3VdbeChangeP5(v, SQLITE_NULLEQ); + sqlite3VdbeResolveLabel(v, addrDone); assert( op==OP_Ge || op==OP_Gt || op==OP_Lt || op==OP_Le ); testcase(op==OP_Ge); VdbeCoverageIf(v, op==OP_Ge); @@ -156160,16 +162669,24 @@ static int windowCodeOp( /* If this is a (RANGE BETWEEN a FOLLOWING AND b FOLLOWING) or ** (RANGE BETWEEN b PRECEDING AND a PRECEDING) frame, ensure the ** start cursor does not advance past the end cursor within the - ** temporary table. It otherwise might, if (a>b). */ + ** temporary table. It otherwise might, if (a>b). Also ensure that, + ** if the input cursor is still finding new rows, that the end + ** cursor does not go past it to EOF. */ if( pMWin->eStart==pMWin->eEnd && regCountdown - && pMWin->eFrmType==TK_RANGE && op==WINDOW_AGGINVERSE + && pMWin->eFrmType==TK_RANGE ){ int regRowid1 = sqlite3GetTempReg(pParse); int regRowid2 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp2(v, OP_Rowid, p->start.csr, regRowid1); - sqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid2); - sqlite3VdbeAddOp3(v, OP_Ge, regRowid2, lblDone, regRowid1); - VdbeCoverage(v); + if( op==WINDOW_AGGINVERSE ){ + sqlite3VdbeAddOp2(v, OP_Rowid, p->start.csr, regRowid1); + sqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid2); + sqlite3VdbeAddOp3(v, OP_Ge, regRowid2, lblDone, regRowid1); + VdbeCoverage(v); + }else if( p->regRowid ){ + sqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid1); + sqlite3VdbeAddOp3(v, OP_Ge, p->regRowid, lblDone, regRowid1); + VdbeCoverageNeverNull(v); + } sqlite3ReleaseTempReg(pParse, regRowid1); sqlite3ReleaseTempReg(pParse, regRowid2); assert( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_FOLLOWING ); @@ -156252,7 +162769,7 @@ SQLITE_PRIVATE Window *sqlite3WindowDup(sqlite3 *db, Expr *pOwner, Window *p){ pNew->zName = sqlite3DbStrDup(db, p->zName); pNew->zBase = sqlite3DbStrDup(db, p->zBase); pNew->pFilter = sqlite3ExprDup(db, p->pFilter, 0); - pNew->pFunc = p->pFunc; + pNew->pWFunc = p->pWFunc; pNew->pPartition = sqlite3ExprListDup(db, p->pPartition, 0); pNew->pOrderBy = sqlite3ExprListDup(db, p->pOrderBy, 0); pNew->eFrmType = p->eFrmType; @@ -156666,7 +163183,6 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( int addrEmpty; /* Address of OP_Rewind in flush: */ int regNew; /* Array of registers holding new input row */ int regRecord; /* regNew array in record form */ - int regRowid; /* Rowid for regRecord in eph table */ int regNewPeer = 0; /* Peer values for new row (part of regNew) */ int regPeer = 0; /* Peer values for current row */ int regFlushPart = 0; /* Register for "Gosub flush_partition" */ @@ -156738,7 +163254,7 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( regNew = pParse->nMem+1; pParse->nMem += nInput; regRecord = ++pParse->nMem; - regRowid = ++pParse->nMem; + s.regRowid = ++pParse->nMem; /* If the window frame contains an " PRECEDING" or " FOLLOWING" ** clause, allocate registers to store the results of evaluating each @@ -156794,9 +163310,9 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( } /* Insert the new row into the ephemeral table */ - sqlite3VdbeAddOp2(v, OP_NewRowid, csrWrite, regRowid); - sqlite3VdbeAddOp3(v, OP_Insert, csrWrite, regRecord, regRowid); - addrNe = sqlite3VdbeAddOp3(v, OP_Ne, pMWin->regOne, 0, regRowid); + sqlite3VdbeAddOp2(v, OP_NewRowid, csrWrite, s.regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, csrWrite, regRecord, s.regRowid); + addrNe = sqlite3VdbeAddOp3(v, OP_Ne, pMWin->regOne, 0, s.regRowid); VdbeCoverageNeverNull(v); /* This block is run for the first row of each partition */ @@ -156914,6 +163430,7 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( sqlite3VdbeJumpHere(v, addrGosubFlush); } + s.regRowid = 0; addrEmpty = sqlite3VdbeAddOp1(v, OP_Rewind, csrWrite); VdbeCoverage(v); if( pMWin->eEnd==TK_PRECEDING ){ @@ -157129,10 +163646,7 @@ static void updateDeleteLimitError( } - /* Construct a new Expr object from a single identifier. Use the - ** new Expr to populate pOut. Set the span of pOut to be the identifier - ** that created the expression. - */ + /* Construct a new Expr object from a single token */ static Expr *tokenExpr(Parse *pParse, int op, Token t){ Expr *p = sqlite3DbMallocRawNN(pParse->db, sizeof(Expr)+t.n+1); if( p ){ @@ -157141,17 +163655,18 @@ static void updateDeleteLimitError( p->affExpr = 0; p->flags = EP_Leaf; ExprClearVVAProperties(p); - p->iAgg = -1; + /* p->iAgg = -1; // Not required */ p->pLeft = p->pRight = 0; - p->x.pList = 0; p->pAggInfo = 0; - p->y.pTab = 0; + memset(&p->x, 0, sizeof(p->x)); + memset(&p->y, 0, sizeof(p->y)); p->op2 = 0; p->iTable = 0; p->iColumn = 0; p->u.zToken = (char*)&p[1]; memcpy(p->u.zToken, t.z, t.n); p->u.zToken[t.n] = 0; + p->w.iOfst = (int)(t.z - pParse->zTail); if( sqlite3Isquote(p->u.zToken[0]) ){ sqlite3DequoteExpr(p); } @@ -157231,8 +163746,8 @@ static void updateDeleteLimitError( #define TK_LP 22 #define TK_RP 23 #define TK_AS 24 -#define TK_WITHOUT 25 -#define TK_COMMA 26 +#define TK_COMMA 25 +#define TK_WITHOUT 26 #define TK_ABORT 27 #define TK_ACTION 28 #define TK_AFTER 29 @@ -157318,77 +163833,79 @@ static void updateDeleteLimitError( #define TK_SLASH 109 #define TK_REM 110 #define TK_CONCAT 111 -#define TK_COLLATE 112 -#define TK_BITNOT 113 -#define TK_ON 114 -#define TK_INDEXED 115 -#define TK_STRING 116 -#define TK_JOIN_KW 117 -#define TK_CONSTRAINT 118 -#define TK_DEFAULT 119 -#define TK_NULL 120 -#define TK_PRIMARY 121 -#define TK_UNIQUE 122 -#define TK_CHECK 123 -#define TK_REFERENCES 124 -#define TK_AUTOINCR 125 -#define TK_INSERT 126 -#define TK_DELETE 127 -#define TK_UPDATE 128 -#define TK_SET 129 -#define TK_DEFERRABLE 130 -#define TK_FOREIGN 131 -#define TK_DROP 132 -#define TK_UNION 133 -#define TK_ALL 134 -#define TK_EXCEPT 135 -#define TK_INTERSECT 136 -#define TK_SELECT 137 -#define TK_VALUES 138 -#define TK_DISTINCT 139 -#define TK_DOT 140 -#define TK_FROM 141 -#define TK_JOIN 142 -#define TK_USING 143 -#define TK_ORDER 144 -#define TK_GROUP 145 -#define TK_HAVING 146 -#define TK_LIMIT 147 -#define TK_WHERE 148 -#define TK_RETURNING 149 -#define TK_INTO 150 -#define TK_NOTHING 151 -#define TK_FLOAT 152 -#define TK_BLOB 153 -#define TK_INTEGER 154 -#define TK_VARIABLE 155 -#define TK_CASE 156 -#define TK_WHEN 157 -#define TK_THEN 158 -#define TK_ELSE 159 -#define TK_INDEX 160 -#define TK_ALTER 161 -#define TK_ADD 162 -#define TK_WINDOW 163 -#define TK_OVER 164 -#define TK_FILTER 165 -#define TK_COLUMN 166 -#define TK_AGG_FUNCTION 167 -#define TK_AGG_COLUMN 168 -#define TK_TRUEFALSE 169 -#define TK_ISNOT 170 -#define TK_FUNCTION 171 -#define TK_UMINUS 172 -#define TK_UPLUS 173 -#define TK_TRUTH 174 -#define TK_REGISTER 175 -#define TK_VECTOR 176 -#define TK_SELECT_COLUMN 177 -#define TK_IF_NULL_ROW 178 -#define TK_ASTERISK 179 -#define TK_SPAN 180 -#define TK_SPACE 181 -#define TK_ILLEGAL 182 +#define TK_PTR 112 +#define TK_COLLATE 113 +#define TK_BITNOT 114 +#define TK_ON 115 +#define TK_INDEXED 116 +#define TK_STRING 117 +#define TK_JOIN_KW 118 +#define TK_CONSTRAINT 119 +#define TK_DEFAULT 120 +#define TK_NULL 121 +#define TK_PRIMARY 122 +#define TK_UNIQUE 123 +#define TK_CHECK 124 +#define TK_REFERENCES 125 +#define TK_AUTOINCR 126 +#define TK_INSERT 127 +#define TK_DELETE 128 +#define TK_UPDATE 129 +#define TK_SET 130 +#define TK_DEFERRABLE 131 +#define TK_FOREIGN 132 +#define TK_DROP 133 +#define TK_UNION 134 +#define TK_ALL 135 +#define TK_EXCEPT 136 +#define TK_INTERSECT 137 +#define TK_SELECT 138 +#define TK_VALUES 139 +#define TK_DISTINCT 140 +#define TK_DOT 141 +#define TK_FROM 142 +#define TK_JOIN 143 +#define TK_USING 144 +#define TK_ORDER 145 +#define TK_GROUP 146 +#define TK_HAVING 147 +#define TK_LIMIT 148 +#define TK_WHERE 149 +#define TK_RETURNING 150 +#define TK_INTO 151 +#define TK_NOTHING 152 +#define TK_FLOAT 153 +#define TK_BLOB 154 +#define TK_INTEGER 155 +#define TK_VARIABLE 156 +#define TK_CASE 157 +#define TK_WHEN 158 +#define TK_THEN 159 +#define TK_ELSE 160 +#define TK_INDEX 161 +#define TK_ALTER 162 +#define TK_ADD 163 +#define TK_WINDOW 164 +#define TK_OVER 165 +#define TK_FILTER 166 +#define TK_COLUMN 167 +#define TK_AGG_FUNCTION 168 +#define TK_AGG_COLUMN 169 +#define TK_TRUEFALSE 170 +#define TK_ISNOT 171 +#define TK_FUNCTION 172 +#define TK_UMINUS 173 +#define TK_UPLUS 174 +#define TK_TRUTH 175 +#define TK_REGISTER 176 +#define TK_VECTOR 177 +#define TK_SELECT_COLUMN 178 +#define TK_IF_NULL_ROW 179 +#define TK_ASTERISK 180 +#define TK_SPAN 181 +#define TK_ERROR 182 +#define TK_SPACE 183 +#define TK_ILLEGAL 184 #endif /**************** End token definitions ***************************************/ @@ -157448,29 +163965,31 @@ static void updateDeleteLimitError( #endif /************* Begin control #defines *****************************************/ #define YYCODETYPE unsigned short int -#define YYNOCODE 316 +#define YYNOCODE 319 #define YYACTIONTYPE unsigned short int #define YYWILDCARD 101 #define sqlite3ParserTOKENTYPE Token typedef union { int yyinit; sqlite3ParserTOKENTYPE yy0; - Window* yy19; - struct TrigEvent yy50; - int yy60; - struct FrameBound yy113; - Upsert* yy178; - With* yy195; - IdList* yy288; - SrcList* yy291; - Select* yy307; - ExprList* yy338; - TriggerStep* yy483; - const char* yy528; - u8 yy570; - Expr* yy602; - Cte* yy607; - struct {int value; int mask;} yy615; + TriggerStep* yy33; + Window* yy41; + Select* yy47; + SrcList* yy131; + struct TrigEvent yy180; + struct {int value; int mask;} yy231; + IdList* yy254; + u32 yy285; + ExprList* yy322; + Cte* yy385; + int yy394; + Upsert* yy444; + u8 yy516; + With* yy521; + const char* yy522; + Expr* yy528; + OnOrUsing yy561; + struct FrameBound yy595; } YYMINORTYPE; #ifndef YYSTACKDEPTH #define YYSTACKDEPTH 100 @@ -157486,18 +164005,18 @@ typedef union { #define sqlite3ParserCTX_FETCH Parse *pParse=yypParser->pParse; #define sqlite3ParserCTX_STORE yypParser->pParse=pParse; #define YYFALLBACK 1 -#define YYNSTATE 570 -#define YYNRULE 398 -#define YYNRULE_WITH_ACTION 337 -#define YYNTOKEN 183 -#define YY_MAX_SHIFT 569 -#define YY_MIN_SHIFTREDUCE 825 -#define YY_MAX_SHIFTREDUCE 1222 -#define YY_ERROR_ACTION 1223 -#define YY_ACCEPT_ACTION 1224 -#define YY_NO_ACTION 1225 -#define YY_MIN_REDUCE 1226 -#define YY_MAX_REDUCE 1623 +#define YYNSTATE 576 +#define YYNRULE 405 +#define YYNRULE_WITH_ACTION 342 +#define YYNTOKEN 185 +#define YY_MAX_SHIFT 575 +#define YY_MIN_SHIFTREDUCE 835 +#define YY_MAX_SHIFTREDUCE 1239 +#define YY_ERROR_ACTION 1240 +#define YY_ACCEPT_ACTION 1241 +#define YY_NO_ACTION 1242 +#define YY_MIN_REDUCE 1243 +#define YY_MAX_REDUCE 1647 /************* End control #defines *******************************************/ #define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0]))) @@ -157564,600 +164083,618 @@ typedef union { ** yy_default[] Default action for each state. ** *********** Begin parsing tables **********************************************/ -#define YY_ACTTAB_COUNT (2020) +#define YY_ACTTAB_COUNT (2098) static const YYACTIONTYPE yy_action[] = { - /* 0 */ 563, 1295, 563, 1274, 168, 361, 115, 112, 218, 373, - /* 10 */ 563, 1295, 374, 563, 488, 563, 115, 112, 218, 406, - /* 20 */ 1300, 1300, 41, 41, 41, 41, 514, 1504, 520, 1298, - /* 30 */ 1298, 959, 41, 41, 1257, 71, 71, 51, 51, 960, - /* 40 */ 557, 557, 557, 122, 123, 113, 1200, 1200, 1035, 1038, - /* 50 */ 1028, 1028, 120, 120, 121, 121, 121, 121, 414, 406, - /* 60 */ 273, 273, 273, 273, 115, 112, 218, 115, 112, 218, - /* 70 */ 197, 268, 545, 560, 515, 560, 1260, 563, 385, 248, - /* 80 */ 215, 521, 399, 122, 123, 113, 1200, 1200, 1035, 1038, - /* 90 */ 1028, 1028, 120, 120, 121, 121, 121, 121, 540, 13, - /* 100 */ 13, 1259, 119, 119, 119, 119, 118, 118, 117, 117, - /* 110 */ 117, 116, 441, 1176, 419, 1531, 446, 137, 512, 1539, - /* 120 */ 1545, 372, 1547, 6, 371, 1176, 1148, 1584, 1148, 406, - /* 130 */ 1545, 534, 115, 112, 218, 1267, 99, 441, 121, 121, - /* 140 */ 121, 121, 119, 119, 119, 119, 118, 118, 117, 117, - /* 150 */ 117, 116, 441, 122, 123, 113, 1200, 1200, 1035, 1038, - /* 160 */ 1028, 1028, 120, 120, 121, 121, 121, 121, 197, 1176, - /* 170 */ 1177, 1178, 241, 304, 554, 501, 498, 497, 473, 124, - /* 180 */ 394, 1176, 1177, 1178, 1176, 496, 119, 119, 119, 119, - /* 190 */ 118, 118, 117, 117, 117, 116, 441, 139, 540, 406, - /* 200 */ 121, 121, 121, 121, 114, 117, 117, 117, 116, 441, - /* 210 */ 541, 1532, 119, 119, 119, 119, 118, 118, 117, 117, - /* 220 */ 117, 116, 441, 122, 123, 113, 1200, 1200, 1035, 1038, - /* 230 */ 1028, 1028, 120, 120, 121, 121, 121, 121, 406, 320, - /* 240 */ 1176, 1177, 1178, 81, 342, 1590, 396, 80, 119, 119, - /* 250 */ 119, 119, 118, 118, 117, 117, 117, 116, 441, 1176, - /* 260 */ 211, 450, 122, 123, 113, 1200, 1200, 1035, 1038, 1028, - /* 270 */ 1028, 120, 120, 121, 121, 121, 121, 251, 450, 449, - /* 280 */ 273, 273, 119, 119, 119, 119, 118, 118, 117, 117, - /* 290 */ 117, 116, 441, 560, 1224, 1, 1, 569, 2, 1228, - /* 300 */ 317, 1176, 319, 1561, 305, 337, 140, 340, 406, 430, - /* 310 */ 469, 1533, 1197, 1308, 348, 1176, 1177, 1178, 168, 462, - /* 320 */ 330, 119, 119, 119, 119, 118, 118, 117, 117, 117, - /* 330 */ 116, 441, 122, 123, 113, 1200, 1200, 1035, 1038, 1028, - /* 340 */ 1028, 120, 120, 121, 121, 121, 121, 273, 273, 563, - /* 350 */ 83, 450, 416, 1564, 569, 2, 1228, 1176, 1177, 1178, - /* 360 */ 560, 305, 471, 140, 944, 995, 860, 563, 467, 1197, - /* 370 */ 1308, 13, 13, 137, 229, 118, 118, 117, 117, 117, - /* 380 */ 116, 441, 96, 318, 946, 504, 424, 361, 562, 71, - /* 390 */ 71, 119, 119, 119, 119, 118, 118, 117, 117, 117, - /* 400 */ 116, 441, 427, 205, 273, 273, 445, 1015, 259, 276, - /* 410 */ 356, 507, 351, 506, 246, 406, 959, 560, 328, 344, - /* 420 */ 347, 315, 860, 1006, 960, 126, 545, 1005, 313, 304, - /* 430 */ 554, 229, 538, 1539, 148, 544, 281, 6, 203, 122, - /* 440 */ 123, 113, 1200, 1200, 1035, 1038, 1028, 1028, 120, 120, - /* 450 */ 121, 121, 121, 121, 563, 217, 563, 12, 406, 1005, - /* 460 */ 1005, 1007, 502, 445, 119, 119, 119, 119, 118, 118, - /* 470 */ 117, 117, 117, 116, 441, 452, 71, 71, 70, 70, - /* 480 */ 944, 137, 122, 123, 113, 1200, 1200, 1035, 1038, 1028, - /* 490 */ 1028, 120, 120, 121, 121, 121, 121, 1530, 119, 119, - /* 500 */ 119, 119, 118, 118, 117, 117, 117, 116, 441, 403, - /* 510 */ 402, 241, 1176, 545, 501, 498, 497, 1468, 1143, 451, - /* 520 */ 267, 267, 513, 1540, 496, 142, 1176, 6, 406, 530, - /* 530 */ 194, 1143, 864, 560, 1143, 461, 182, 304, 554, 32, - /* 540 */ 379, 119, 119, 119, 119, 118, 118, 117, 117, 117, - /* 550 */ 116, 441, 122, 123, 113, 1200, 1200, 1035, 1038, 1028, - /* 560 */ 1028, 120, 120, 121, 121, 121, 121, 406, 1176, 1177, - /* 570 */ 1178, 857, 568, 1176, 1228, 925, 1176, 454, 361, 305, - /* 580 */ 189, 140, 1176, 1177, 1178, 519, 529, 404, 1308, 183, - /* 590 */ 1015, 122, 123, 113, 1200, 1200, 1035, 1038, 1028, 1028, - /* 600 */ 120, 120, 121, 121, 121, 121, 1006, 16, 16, 370, - /* 610 */ 1005, 119, 119, 119, 119, 118, 118, 117, 117, 117, - /* 620 */ 116, 441, 273, 273, 1537, 150, 1176, 98, 6, 1176, - /* 630 */ 1177, 1178, 1176, 1177, 1178, 560, 380, 406, 376, 438, - /* 640 */ 437, 1161, 1005, 1005, 1007, 1025, 1025, 1036, 1039, 229, - /* 650 */ 119, 119, 119, 119, 118, 118, 117, 117, 117, 116, - /* 660 */ 441, 122, 123, 113, 1200, 1200, 1035, 1038, 1028, 1028, - /* 670 */ 120, 120, 121, 121, 121, 121, 406, 1143, 1619, 392, - /* 680 */ 1016, 445, 1176, 1177, 1178, 1207, 525, 1207, 1530, 995, - /* 690 */ 1143, 304, 554, 1143, 5, 563, 543, 3, 361, 216, - /* 700 */ 122, 123, 113, 1200, 1200, 1035, 1038, 1028, 1028, 120, - /* 710 */ 120, 121, 121, 121, 121, 143, 563, 13, 13, 1029, - /* 720 */ 119, 119, 119, 119, 118, 118, 117, 117, 117, 116, - /* 730 */ 441, 1176, 426, 563, 1176, 563, 274, 274, 13, 13, - /* 740 */ 1078, 1176, 328, 457, 316, 147, 406, 211, 361, 560, - /* 750 */ 1000, 213, 511, 293, 477, 55, 55, 71, 71, 119, - /* 760 */ 119, 119, 119, 118, 118, 117, 117, 117, 116, 441, - /* 770 */ 122, 123, 113, 1200, 1200, 1035, 1038, 1028, 1028, 120, - /* 780 */ 120, 121, 121, 121, 121, 406, 455, 1176, 1177, 1178, - /* 790 */ 1176, 1177, 1178, 471, 526, 149, 404, 1176, 1177, 1178, - /* 800 */ 105, 270, 103, 563, 944, 563, 116, 441, 1530, 122, - /* 810 */ 123, 113, 1200, 1200, 1035, 1038, 1028, 1028, 120, 120, - /* 820 */ 121, 121, 121, 121, 945, 13, 13, 13, 13, 119, - /* 830 */ 119, 119, 119, 118, 118, 117, 117, 117, 116, 441, - /* 840 */ 191, 563, 192, 563, 416, 439, 439, 439, 1083, 1083, - /* 850 */ 485, 561, 285, 914, 914, 406, 462, 330, 1530, 830, - /* 860 */ 831, 832, 206, 71, 71, 71, 71, 286, 119, 119, - /* 870 */ 119, 119, 118, 118, 117, 117, 117, 116, 441, 122, - /* 880 */ 123, 113, 1200, 1200, 1035, 1038, 1028, 1028, 120, 120, - /* 890 */ 121, 121, 121, 121, 563, 217, 563, 1122, 1617, 406, - /* 900 */ 300, 1617, 301, 416, 1278, 1473, 244, 243, 242, 1249, - /* 910 */ 412, 556, 412, 282, 842, 279, 71, 71, 71, 71, - /* 920 */ 944, 1415, 1473, 1475, 101, 113, 1200, 1200, 1035, 1038, - /* 930 */ 1028, 1028, 120, 120, 121, 121, 121, 121, 119, 119, - /* 940 */ 119, 119, 118, 118, 117, 117, 117, 116, 441, 273, - /* 950 */ 273, 1099, 563, 436, 1143, 440, 563, 1122, 1618, 357, - /* 960 */ 1558, 1618, 560, 546, 488, 197, 1100, 1143, 378, 290, - /* 970 */ 1143, 1306, 284, 460, 71, 71, 1120, 405, 13, 13, - /* 980 */ 145, 1101, 119, 119, 119, 119, 118, 118, 117, 117, - /* 990 */ 117, 116, 441, 542, 104, 1473, 509, 273, 273, 294, - /* 1000 */ 1514, 294, 900, 273, 273, 273, 273, 563, 1503, 563, - /* 1010 */ 560, 545, 901, 464, 406, 1058, 560, 852, 560, 198, - /* 1020 */ 547, 1080, 920, 404, 1400, 1080, 146, 919, 38, 56, - /* 1030 */ 56, 15, 15, 563, 406, 12, 1120, 471, 122, 123, - /* 1040 */ 113, 1200, 1200, 1035, 1038, 1028, 1028, 120, 120, 121, - /* 1050 */ 121, 121, 121, 1460, 406, 43, 43, 483, 122, 123, - /* 1060 */ 113, 1200, 1200, 1035, 1038, 1028, 1028, 120, 120, 121, - /* 1070 */ 121, 121, 121, 563, 852, 9, 471, 251, 122, 111, - /* 1080 */ 113, 1200, 1200, 1035, 1038, 1028, 1028, 120, 120, 121, - /* 1090 */ 121, 121, 121, 563, 421, 57, 57, 119, 119, 119, - /* 1100 */ 119, 118, 118, 117, 117, 117, 116, 441, 1176, 493, - /* 1110 */ 563, 289, 1197, 478, 1516, 44, 44, 119, 119, 119, - /* 1120 */ 119, 118, 118, 117, 117, 117, 116, 441, 880, 563, - /* 1130 */ 536, 563, 58, 58, 488, 1414, 245, 119, 119, 119, - /* 1140 */ 119, 118, 118, 117, 117, 117, 116, 441, 563, 535, - /* 1150 */ 291, 59, 59, 60, 60, 438, 437, 406, 1154, 505, - /* 1160 */ 304, 554, 477, 1204, 1176, 1177, 1178, 881, 1206, 1197, - /* 1170 */ 61, 61, 1246, 357, 1558, 1538, 1205, 563, 1467, 6, - /* 1180 */ 1176, 488, 123, 113, 1200, 1200, 1035, 1038, 1028, 1028, - /* 1190 */ 120, 120, 121, 121, 121, 121, 1400, 1143, 410, 62, - /* 1200 */ 62, 1207, 1099, 1207, 411, 447, 273, 273, 537, 1154, - /* 1210 */ 1143, 108, 555, 1143, 4, 391, 1220, 1100, 1512, 560, - /* 1220 */ 347, 516, 428, 548, 308, 1307, 1536, 1077, 558, 1077, - /* 1230 */ 6, 488, 1101, 1400, 488, 309, 1176, 1177, 1178, 563, - /* 1240 */ 119, 119, 119, 119, 118, 118, 117, 117, 117, 116, - /* 1250 */ 441, 442, 278, 551, 563, 273, 273, 273, 273, 563, - /* 1260 */ 327, 45, 45, 552, 563, 528, 422, 563, 560, 1400, - /* 1270 */ 560, 108, 555, 137, 4, 1303, 46, 46, 335, 563, - /* 1280 */ 482, 47, 47, 477, 479, 307, 49, 49, 558, 50, - /* 1290 */ 50, 563, 1015, 563, 1221, 563, 1400, 563, 106, 106, - /* 1300 */ 8, 63, 63, 423, 563, 107, 312, 442, 565, 564, - /* 1310 */ 563, 442, 1005, 64, 64, 65, 65, 14, 14, 66, - /* 1320 */ 66, 391, 1121, 552, 1312, 1180, 128, 128, 563, 304, - /* 1330 */ 554, 563, 67, 67, 563, 359, 560, 532, 563, 484, - /* 1340 */ 563, 1196, 531, 222, 1005, 1005, 1007, 1008, 27, 522, - /* 1350 */ 52, 52, 1015, 68, 68, 563, 69, 69, 106, 106, - /* 1360 */ 53, 53, 156, 156, 563, 107, 434, 442, 565, 564, - /* 1370 */ 272, 215, 1005, 425, 563, 359, 563, 157, 157, 563, - /* 1380 */ 1535, 292, 1180, 98, 6, 1344, 76, 76, 1215, 475, - /* 1390 */ 413, 169, 226, 563, 245, 563, 54, 54, 72, 72, - /* 1400 */ 1221, 129, 129, 1343, 1005, 1005, 1007, 1008, 27, 1563, - /* 1410 */ 1165, 444, 456, 433, 277, 73, 73, 130, 130, 389, - /* 1420 */ 389, 388, 262, 386, 1165, 444, 839, 1519, 277, 108, - /* 1430 */ 555, 321, 4, 389, 389, 388, 262, 386, 563, 223, - /* 1440 */ 839, 311, 468, 84, 202, 523, 558, 1492, 303, 310, - /* 1450 */ 563, 110, 404, 223, 563, 311, 206, 30, 404, 277, - /* 1460 */ 131, 131, 411, 310, 389, 389, 388, 262, 386, 442, - /* 1470 */ 920, 839, 127, 127, 563, 919, 155, 155, 1491, 225, - /* 1480 */ 563, 552, 871, 563, 223, 476, 311, 161, 31, 563, - /* 1490 */ 135, 563, 480, 225, 310, 532, 154, 154, 332, 17, - /* 1500 */ 533, 161, 136, 136, 135, 134, 134, 224, 228, 355, - /* 1510 */ 1015, 132, 132, 133, 133, 1589, 106, 106, 889, 354, - /* 1520 */ 563, 224, 563, 107, 225, 442, 565, 564, 1117, 275, - /* 1530 */ 1005, 393, 161, 518, 563, 135, 108, 555, 417, 4, - /* 1540 */ 1340, 407, 75, 75, 77, 77, 304, 554, 867, 563, - /* 1550 */ 336, 563, 224, 558, 463, 407, 74, 74, 465, 1065, - /* 1560 */ 304, 554, 1005, 1005, 1007, 1008, 27, 962, 963, 543, - /* 1570 */ 448, 42, 42, 48, 48, 326, 442, 325, 98, 997, - /* 1580 */ 470, 287, 250, 250, 448, 1009, 407, 472, 552, 339, - /* 1590 */ 250, 304, 554, 879, 878, 331, 108, 555, 98, 4, - /* 1600 */ 1277, 494, 532, 345, 247, 867, 98, 531, 341, 886, - /* 1610 */ 887, 1126, 1076, 558, 1076, 448, 1065, 1015, 1061, 953, - /* 1620 */ 343, 247, 250, 106, 106, 1291, 917, 1276, 850, 110, - /* 1630 */ 107, 144, 442, 565, 564, 918, 442, 1005, 110, 1275, - /* 1640 */ 350, 360, 1009, 1331, 1352, 299, 1399, 1577, 552, 1327, - /* 1650 */ 1552, 550, 1338, 549, 1405, 1256, 1248, 1237, 1236, 1238, - /* 1660 */ 1571, 489, 265, 200, 1324, 363, 365, 367, 11, 1005, - /* 1670 */ 1005, 1007, 1008, 27, 390, 221, 1386, 1015, 280, 1391, - /* 1680 */ 1381, 208, 323, 106, 106, 924, 1374, 453, 283, 324, - /* 1690 */ 107, 474, 442, 565, 564, 1390, 499, 1005, 212, 288, - /* 1700 */ 1274, 397, 353, 108, 555, 195, 4, 1464, 369, 1463, - /* 1710 */ 1574, 1215, 1212, 329, 553, 171, 207, 383, 1511, 196, - /* 1720 */ 558, 254, 1509, 415, 100, 555, 83, 4, 204, 1005, - /* 1730 */ 1005, 1007, 1008, 27, 219, 79, 82, 1469, 180, 166, - /* 1740 */ 173, 558, 458, 442, 175, 176, 177, 178, 35, 1387, - /* 1750 */ 492, 459, 231, 1395, 96, 552, 1393, 1392, 395, 184, - /* 1760 */ 481, 466, 36, 235, 442, 89, 398, 266, 487, 1480, - /* 1770 */ 1458, 237, 188, 338, 508, 429, 552, 490, 400, 238, - /* 1780 */ 334, 1239, 239, 1294, 1015, 1293, 1292, 1285, 91, 871, - /* 1790 */ 106, 106, 213, 431, 1588, 432, 524, 107, 517, 442, - /* 1800 */ 565, 564, 401, 1264, 1005, 1015, 1263, 1587, 352, 1262, - /* 1810 */ 1557, 106, 106, 1586, 1284, 297, 298, 358, 107, 1335, - /* 1820 */ 442, 565, 564, 95, 362, 1005, 253, 252, 435, 125, - /* 1830 */ 543, 10, 1444, 1543, 377, 1542, 1005, 1005, 1007, 1008, - /* 1840 */ 27, 302, 102, 97, 527, 1336, 260, 1317, 364, 1245, - /* 1850 */ 1334, 34, 566, 1171, 366, 381, 375, 1005, 1005, 1007, - /* 1860 */ 1008, 27, 1333, 1359, 368, 1316, 199, 382, 261, 263, - /* 1870 */ 264, 1358, 158, 1496, 141, 1497, 1495, 567, 1234, 1229, - /* 1880 */ 1494, 295, 159, 209, 210, 78, 826, 443, 201, 306, - /* 1890 */ 220, 1075, 138, 1073, 160, 314, 162, 172, 1196, 174, - /* 1900 */ 903, 227, 230, 322, 1089, 179, 163, 164, 418, 85, - /* 1910 */ 420, 181, 170, 408, 409, 86, 87, 165, 88, 1092, - /* 1920 */ 232, 233, 1088, 151, 18, 234, 1081, 250, 333, 185, - /* 1930 */ 1209, 486, 236, 186, 37, 841, 491, 354, 240, 346, - /* 1940 */ 503, 187, 90, 167, 19, 495, 20, 869, 500, 349, - /* 1950 */ 92, 882, 296, 152, 93, 510, 1127, 1159, 153, 1041, - /* 1960 */ 214, 1128, 39, 94, 269, 271, 952, 190, 947, 110, - /* 1970 */ 1149, 1145, 1153, 249, 1133, 1147, 7, 33, 21, 193, - /* 1980 */ 22, 23, 24, 25, 1152, 539, 98, 1056, 26, 1042, - /* 1990 */ 1040, 1044, 1098, 1045, 1097, 256, 255, 28, 40, 387, - /* 2000 */ 1010, 851, 109, 29, 1167, 559, 384, 257, 913, 258, - /* 2010 */ 1166, 1579, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1578, + /* 0 */ 568, 208, 568, 118, 115, 229, 568, 118, 115, 229, + /* 10 */ 568, 1314, 377, 1293, 408, 562, 562, 562, 568, 409, + /* 20 */ 378, 1314, 1276, 41, 41, 41, 41, 208, 1526, 71, + /* 30 */ 71, 971, 419, 41, 41, 491, 303, 279, 303, 972, + /* 40 */ 397, 71, 71, 125, 126, 80, 1217, 1217, 1050, 1053, + /* 50 */ 1040, 1040, 123, 123, 124, 124, 124, 124, 476, 409, + /* 60 */ 1241, 1, 1, 575, 2, 1245, 550, 118, 115, 229, + /* 70 */ 317, 480, 146, 480, 524, 118, 115, 229, 529, 1327, + /* 80 */ 417, 523, 142, 125, 126, 80, 1217, 1217, 1050, 1053, + /* 90 */ 1040, 1040, 123, 123, 124, 124, 124, 124, 118, 115, + /* 100 */ 229, 327, 122, 122, 122, 122, 121, 121, 120, 120, + /* 110 */ 120, 119, 116, 444, 284, 284, 284, 284, 442, 442, + /* 120 */ 442, 1567, 376, 1569, 1192, 375, 1163, 565, 1163, 565, + /* 130 */ 409, 1567, 537, 259, 226, 444, 101, 145, 449, 316, + /* 140 */ 559, 240, 122, 122, 122, 122, 121, 121, 120, 120, + /* 150 */ 120, 119, 116, 444, 125, 126, 80, 1217, 1217, 1050, + /* 160 */ 1053, 1040, 1040, 123, 123, 124, 124, 124, 124, 142, + /* 170 */ 294, 1192, 339, 448, 120, 120, 120, 119, 116, 444, + /* 180 */ 127, 1192, 1193, 1194, 148, 441, 440, 568, 119, 116, + /* 190 */ 444, 124, 124, 124, 124, 117, 122, 122, 122, 122, + /* 200 */ 121, 121, 120, 120, 120, 119, 116, 444, 454, 113, + /* 210 */ 13, 13, 546, 122, 122, 122, 122, 121, 121, 120, + /* 220 */ 120, 120, 119, 116, 444, 422, 316, 559, 1192, 1193, + /* 230 */ 1194, 149, 1224, 409, 1224, 124, 124, 124, 124, 122, + /* 240 */ 122, 122, 122, 121, 121, 120, 120, 120, 119, 116, + /* 250 */ 444, 465, 342, 1037, 1037, 1051, 1054, 125, 126, 80, + /* 260 */ 1217, 1217, 1050, 1053, 1040, 1040, 123, 123, 124, 124, + /* 270 */ 124, 124, 1279, 522, 222, 1192, 568, 409, 224, 514, + /* 280 */ 175, 82, 83, 122, 122, 122, 122, 121, 121, 120, + /* 290 */ 120, 120, 119, 116, 444, 1007, 16, 16, 1192, 133, + /* 300 */ 133, 125, 126, 80, 1217, 1217, 1050, 1053, 1040, 1040, + /* 310 */ 123, 123, 124, 124, 124, 124, 122, 122, 122, 122, + /* 320 */ 121, 121, 120, 120, 120, 119, 116, 444, 1041, 546, + /* 330 */ 1192, 373, 1192, 1193, 1194, 252, 1434, 399, 504, 501, + /* 340 */ 500, 111, 560, 566, 4, 926, 926, 433, 499, 340, + /* 350 */ 460, 328, 360, 394, 1237, 1192, 1193, 1194, 563, 568, + /* 360 */ 122, 122, 122, 122, 121, 121, 120, 120, 120, 119, + /* 370 */ 116, 444, 284, 284, 369, 1580, 1607, 441, 440, 154, + /* 380 */ 409, 445, 71, 71, 1286, 565, 1221, 1192, 1193, 1194, + /* 390 */ 85, 1223, 271, 557, 543, 515, 1561, 568, 98, 1222, + /* 400 */ 6, 1278, 472, 142, 125, 126, 80, 1217, 1217, 1050, + /* 410 */ 1053, 1040, 1040, 123, 123, 124, 124, 124, 124, 550, + /* 420 */ 13, 13, 1027, 507, 1224, 1192, 1224, 549, 109, 109, + /* 430 */ 222, 568, 1238, 175, 568, 427, 110, 197, 445, 570, + /* 440 */ 569, 430, 1552, 1017, 325, 551, 1192, 270, 287, 368, + /* 450 */ 510, 363, 509, 257, 71, 71, 543, 71, 71, 359, + /* 460 */ 316, 559, 1613, 122, 122, 122, 122, 121, 121, 120, + /* 470 */ 120, 120, 119, 116, 444, 1017, 1017, 1019, 1020, 27, + /* 480 */ 284, 284, 1192, 1193, 1194, 1158, 568, 1612, 409, 901, + /* 490 */ 190, 550, 356, 565, 550, 937, 533, 517, 1158, 516, + /* 500 */ 413, 1158, 552, 1192, 1193, 1194, 568, 544, 1554, 51, + /* 510 */ 51, 214, 125, 126, 80, 1217, 1217, 1050, 1053, 1040, + /* 520 */ 1040, 123, 123, 124, 124, 124, 124, 1192, 474, 135, + /* 530 */ 135, 409, 284, 284, 1490, 505, 121, 121, 120, 120, + /* 540 */ 120, 119, 116, 444, 1007, 565, 518, 217, 541, 1561, + /* 550 */ 316, 559, 142, 6, 532, 125, 126, 80, 1217, 1217, + /* 560 */ 1050, 1053, 1040, 1040, 123, 123, 124, 124, 124, 124, + /* 570 */ 1555, 122, 122, 122, 122, 121, 121, 120, 120, 120, + /* 580 */ 119, 116, 444, 485, 1192, 1193, 1194, 482, 281, 1267, + /* 590 */ 957, 252, 1192, 373, 504, 501, 500, 1192, 340, 571, + /* 600 */ 1192, 571, 409, 292, 499, 957, 876, 191, 480, 316, + /* 610 */ 559, 384, 290, 380, 122, 122, 122, 122, 121, 121, + /* 620 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1217, + /* 630 */ 1217, 1050, 1053, 1040, 1040, 123, 123, 124, 124, 124, + /* 640 */ 124, 409, 394, 1136, 1192, 869, 100, 284, 284, 1192, + /* 650 */ 1193, 1194, 373, 1093, 1192, 1193, 1194, 1192, 1193, 1194, + /* 660 */ 565, 455, 32, 373, 233, 125, 126, 80, 1217, 1217, + /* 670 */ 1050, 1053, 1040, 1040, 123, 123, 124, 124, 124, 124, + /* 680 */ 1433, 959, 568, 228, 958, 122, 122, 122, 122, 121, + /* 690 */ 121, 120, 120, 120, 119, 116, 444, 1158, 228, 1192, + /* 700 */ 157, 1192, 1193, 1194, 1553, 13, 13, 301, 957, 1232, + /* 710 */ 1158, 153, 409, 1158, 373, 1583, 1176, 5, 369, 1580, + /* 720 */ 429, 1238, 3, 957, 122, 122, 122, 122, 121, 121, + /* 730 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1217, + /* 740 */ 1217, 1050, 1053, 1040, 1040, 123, 123, 124, 124, 124, + /* 750 */ 124, 409, 208, 567, 1192, 1028, 1192, 1193, 1194, 1192, + /* 760 */ 388, 852, 155, 1552, 286, 402, 1098, 1098, 488, 568, + /* 770 */ 465, 342, 1319, 1319, 1552, 125, 126, 80, 1217, 1217, + /* 780 */ 1050, 1053, 1040, 1040, 123, 123, 124, 124, 124, 124, + /* 790 */ 129, 568, 13, 13, 374, 122, 122, 122, 122, 121, + /* 800 */ 121, 120, 120, 120, 119, 116, 444, 302, 568, 453, + /* 810 */ 528, 1192, 1193, 1194, 13, 13, 1192, 1193, 1194, 1297, + /* 820 */ 463, 1267, 409, 1317, 1317, 1552, 1012, 453, 452, 200, + /* 830 */ 299, 71, 71, 1265, 122, 122, 122, 122, 121, 121, + /* 840 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1217, + /* 850 */ 1217, 1050, 1053, 1040, 1040, 123, 123, 124, 124, 124, + /* 860 */ 124, 409, 227, 1073, 1158, 284, 284, 419, 312, 278, + /* 870 */ 278, 285, 285, 1419, 406, 405, 382, 1158, 565, 568, + /* 880 */ 1158, 1196, 565, 1600, 565, 125, 126, 80, 1217, 1217, + /* 890 */ 1050, 1053, 1040, 1040, 123, 123, 124, 124, 124, 124, + /* 900 */ 453, 1482, 13, 13, 1536, 122, 122, 122, 122, 121, + /* 910 */ 121, 120, 120, 120, 119, 116, 444, 201, 568, 354, + /* 920 */ 1586, 575, 2, 1245, 840, 841, 842, 1562, 317, 1212, + /* 930 */ 146, 6, 409, 255, 254, 253, 206, 1327, 9, 1196, + /* 940 */ 262, 71, 71, 424, 122, 122, 122, 122, 121, 121, + /* 950 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1217, + /* 960 */ 1217, 1050, 1053, 1040, 1040, 123, 123, 124, 124, 124, + /* 970 */ 124, 568, 284, 284, 568, 1213, 409, 574, 313, 1245, + /* 980 */ 349, 1296, 352, 419, 317, 565, 146, 491, 525, 1643, + /* 990 */ 395, 371, 491, 1327, 70, 70, 1295, 71, 71, 240, + /* 1000 */ 1325, 104, 80, 1217, 1217, 1050, 1053, 1040, 1040, 123, + /* 1010 */ 123, 124, 124, 124, 124, 122, 122, 122, 122, 121, + /* 1020 */ 121, 120, 120, 120, 119, 116, 444, 1114, 284, 284, + /* 1030 */ 428, 448, 1525, 1213, 439, 284, 284, 1489, 1352, 311, + /* 1040 */ 474, 565, 1115, 971, 491, 491, 217, 1263, 565, 1538, + /* 1050 */ 568, 972, 207, 568, 1027, 240, 383, 1116, 519, 122, + /* 1060 */ 122, 122, 122, 121, 121, 120, 120, 120, 119, 116, + /* 1070 */ 444, 1018, 107, 71, 71, 1017, 13, 13, 912, 568, + /* 1080 */ 1495, 568, 284, 284, 97, 526, 491, 448, 913, 1326, + /* 1090 */ 1322, 545, 409, 284, 284, 565, 151, 209, 1495, 1497, + /* 1100 */ 262, 450, 55, 55, 56, 56, 565, 1017, 1017, 1019, + /* 1110 */ 443, 332, 409, 527, 12, 295, 125, 126, 80, 1217, + /* 1120 */ 1217, 1050, 1053, 1040, 1040, 123, 123, 124, 124, 124, + /* 1130 */ 124, 347, 409, 864, 1534, 1213, 125, 126, 80, 1217, + /* 1140 */ 1217, 1050, 1053, 1040, 1040, 123, 123, 124, 124, 124, + /* 1150 */ 124, 1137, 1641, 474, 1641, 371, 125, 114, 80, 1217, + /* 1160 */ 1217, 1050, 1053, 1040, 1040, 123, 123, 124, 124, 124, + /* 1170 */ 124, 1495, 329, 474, 331, 122, 122, 122, 122, 121, + /* 1180 */ 121, 120, 120, 120, 119, 116, 444, 203, 1419, 568, + /* 1190 */ 1294, 864, 464, 1213, 436, 122, 122, 122, 122, 121, + /* 1200 */ 121, 120, 120, 120, 119, 116, 444, 553, 1137, 1642, + /* 1210 */ 539, 1642, 15, 15, 892, 122, 122, 122, 122, 121, + /* 1220 */ 121, 120, 120, 120, 119, 116, 444, 568, 298, 538, + /* 1230 */ 1135, 1419, 1559, 1560, 1331, 409, 6, 6, 1169, 1268, + /* 1240 */ 415, 320, 284, 284, 1419, 508, 565, 525, 300, 457, + /* 1250 */ 43, 43, 568, 893, 12, 565, 330, 478, 425, 407, + /* 1260 */ 126, 80, 1217, 1217, 1050, 1053, 1040, 1040, 123, 123, + /* 1270 */ 124, 124, 124, 124, 568, 57, 57, 288, 1192, 1419, + /* 1280 */ 496, 458, 392, 392, 391, 273, 389, 1135, 1558, 849, + /* 1290 */ 1169, 407, 6, 568, 321, 1158, 470, 44, 44, 1557, + /* 1300 */ 1114, 426, 234, 6, 323, 256, 540, 256, 1158, 431, + /* 1310 */ 568, 1158, 322, 17, 487, 1115, 58, 58, 122, 122, + /* 1320 */ 122, 122, 121, 121, 120, 120, 120, 119, 116, 444, + /* 1330 */ 1116, 216, 481, 59, 59, 1192, 1193, 1194, 111, 560, + /* 1340 */ 324, 4, 236, 456, 526, 568, 237, 456, 568, 437, + /* 1350 */ 168, 556, 420, 141, 479, 563, 568, 293, 568, 1095, + /* 1360 */ 568, 293, 568, 1095, 531, 568, 872, 8, 60, 60, + /* 1370 */ 235, 61, 61, 568, 414, 568, 414, 568, 445, 62, + /* 1380 */ 62, 45, 45, 46, 46, 47, 47, 199, 49, 49, + /* 1390 */ 557, 568, 359, 568, 100, 486, 50, 50, 63, 63, + /* 1400 */ 64, 64, 561, 415, 535, 410, 568, 1027, 568, 534, + /* 1410 */ 316, 559, 316, 559, 65, 65, 14, 14, 568, 1027, + /* 1420 */ 568, 512, 932, 872, 1018, 109, 109, 931, 1017, 66, + /* 1430 */ 66, 131, 131, 110, 451, 445, 570, 569, 416, 177, + /* 1440 */ 1017, 132, 132, 67, 67, 568, 467, 568, 932, 471, + /* 1450 */ 1364, 283, 226, 931, 315, 1363, 407, 568, 459, 407, + /* 1460 */ 1017, 1017, 1019, 239, 407, 86, 213, 1350, 52, 52, + /* 1470 */ 68, 68, 1017, 1017, 1019, 1020, 27, 1585, 1180, 447, + /* 1480 */ 69, 69, 288, 97, 108, 1541, 106, 392, 392, 391, + /* 1490 */ 273, 389, 568, 879, 849, 883, 568, 111, 560, 466, + /* 1500 */ 4, 568, 152, 30, 38, 568, 1132, 234, 396, 323, + /* 1510 */ 111, 560, 527, 4, 563, 53, 53, 322, 568, 163, + /* 1520 */ 163, 568, 337, 468, 164, 164, 333, 563, 76, 76, + /* 1530 */ 568, 289, 1514, 568, 31, 1513, 568, 445, 338, 483, + /* 1540 */ 100, 54, 54, 344, 72, 72, 296, 236, 1080, 557, + /* 1550 */ 445, 879, 1360, 134, 134, 168, 73, 73, 141, 161, + /* 1560 */ 161, 1574, 557, 535, 568, 319, 568, 348, 536, 1009, + /* 1570 */ 473, 261, 261, 891, 890, 235, 535, 568, 1027, 568, + /* 1580 */ 475, 534, 261, 367, 109, 109, 521, 136, 136, 130, + /* 1590 */ 130, 1027, 110, 366, 445, 570, 569, 109, 109, 1017, + /* 1600 */ 162, 162, 156, 156, 568, 110, 1080, 445, 570, 569, + /* 1610 */ 410, 351, 1017, 568, 353, 316, 559, 568, 343, 568, + /* 1620 */ 100, 497, 357, 258, 100, 898, 899, 140, 140, 355, + /* 1630 */ 1310, 1017, 1017, 1019, 1020, 27, 139, 139, 362, 451, + /* 1640 */ 137, 137, 138, 138, 1017, 1017, 1019, 1020, 27, 1180, + /* 1650 */ 447, 568, 372, 288, 111, 560, 1021, 4, 392, 392, + /* 1660 */ 391, 273, 389, 568, 1141, 849, 568, 1076, 568, 258, + /* 1670 */ 492, 563, 568, 211, 75, 75, 555, 962, 234, 261, + /* 1680 */ 323, 111, 560, 929, 4, 113, 77, 77, 322, 74, + /* 1690 */ 74, 42, 42, 1373, 445, 48, 48, 1418, 563, 974, + /* 1700 */ 975, 1092, 1091, 1092, 1091, 862, 557, 150, 930, 1346, + /* 1710 */ 113, 1358, 554, 1424, 1021, 1275, 1266, 1254, 236, 1253, + /* 1720 */ 1255, 445, 1593, 1343, 308, 276, 168, 309, 11, 141, + /* 1730 */ 393, 310, 232, 557, 1405, 1027, 335, 291, 1400, 219, + /* 1740 */ 336, 109, 109, 936, 297, 1410, 235, 341, 477, 110, + /* 1750 */ 502, 445, 570, 569, 1393, 1409, 1017, 400, 1293, 365, + /* 1760 */ 223, 1486, 1027, 1485, 1355, 1356, 1354, 1353, 109, 109, + /* 1770 */ 204, 1596, 1232, 558, 265, 218, 110, 205, 445, 570, + /* 1780 */ 569, 410, 387, 1017, 1533, 179, 316, 559, 1017, 1017, + /* 1790 */ 1019, 1020, 27, 230, 1531, 1229, 79, 560, 85, 4, + /* 1800 */ 418, 215, 548, 81, 84, 188, 1406, 173, 181, 461, + /* 1810 */ 451, 35, 462, 563, 183, 1017, 1017, 1019, 1020, 27, + /* 1820 */ 184, 1491, 185, 186, 495, 242, 98, 398, 1412, 36, + /* 1830 */ 1411, 484, 91, 469, 401, 1414, 445, 192, 1480, 246, + /* 1840 */ 1502, 490, 346, 277, 248, 196, 493, 511, 557, 350, + /* 1850 */ 1256, 249, 250, 403, 1313, 1312, 111, 560, 432, 4, + /* 1860 */ 1311, 1304, 93, 1611, 883, 1610, 224, 404, 434, 520, + /* 1870 */ 263, 435, 1579, 563, 1283, 1282, 364, 1027, 306, 1281, + /* 1880 */ 264, 1609, 1565, 109, 109, 370, 1303, 307, 1564, 438, + /* 1890 */ 128, 110, 1378, 445, 570, 569, 445, 546, 1017, 10, + /* 1900 */ 1466, 105, 381, 1377, 34, 572, 99, 1336, 557, 314, + /* 1910 */ 1186, 530, 272, 274, 379, 210, 1335, 547, 385, 386, + /* 1920 */ 275, 573, 1251, 1246, 411, 412, 1518, 165, 178, 1519, + /* 1930 */ 1017, 1017, 1019, 1020, 27, 1517, 1516, 1027, 78, 147, + /* 1940 */ 166, 220, 221, 109, 109, 836, 304, 167, 446, 212, + /* 1950 */ 318, 110, 231, 445, 570, 569, 144, 1090, 1017, 1088, + /* 1960 */ 326, 180, 169, 1212, 182, 334, 238, 915, 241, 1104, + /* 1970 */ 187, 170, 171, 421, 87, 88, 423, 189, 89, 90, + /* 1980 */ 172, 1107, 243, 1103, 244, 158, 18, 245, 345, 247, + /* 1990 */ 1017, 1017, 1019, 1020, 27, 261, 1096, 193, 1226, 489, + /* 2000 */ 194, 37, 366, 851, 494, 251, 195, 506, 92, 19, + /* 2010 */ 498, 358, 20, 503, 881, 361, 94, 894, 305, 159, + /* 2020 */ 513, 39, 95, 1174, 160, 1056, 966, 1143, 96, 174, + /* 2030 */ 1142, 225, 280, 282, 198, 960, 113, 1164, 1160, 260, + /* 2040 */ 21, 22, 23, 1162, 1168, 1167, 1148, 24, 33, 25, + /* 2050 */ 202, 542, 26, 100, 1071, 102, 1057, 103, 7, 1055, + /* 2060 */ 1059, 1113, 1060, 1112, 266, 267, 28, 40, 390, 1022, + /* 2070 */ 863, 112, 29, 564, 1182, 1181, 268, 176, 143, 925, + /* 2080 */ 1242, 1242, 1242, 1242, 1242, 1242, 1242, 1242, 1242, 1242, + /* 2090 */ 1242, 1242, 1242, 1242, 269, 1602, 1242, 1601, }; static const YYCODETYPE yy_lookahead[] = { - /* 0 */ 191, 220, 191, 222, 191, 191, 271, 272, 273, 216, - /* 10 */ 191, 230, 216, 191, 191, 191, 271, 272, 273, 19, - /* 20 */ 232, 233, 213, 214, 213, 214, 202, 292, 202, 232, - /* 30 */ 233, 31, 213, 214, 213, 213, 214, 213, 214, 39, - /* 40 */ 207, 208, 209, 43, 44, 45, 46, 47, 48, 49, - /* 50 */ 50, 51, 52, 53, 54, 55, 56, 57, 235, 19, - /* 60 */ 236, 237, 236, 237, 271, 272, 273, 271, 272, 273, - /* 70 */ 191, 210, 250, 249, 250, 249, 213, 191, 199, 253, - /* 80 */ 254, 259, 203, 43, 44, 45, 46, 47, 48, 49, - /* 90 */ 50, 51, 52, 53, 54, 55, 56, 57, 191, 213, - /* 100 */ 214, 213, 102, 103, 104, 105, 106, 107, 108, 109, - /* 110 */ 110, 111, 112, 59, 228, 301, 293, 81, 305, 306, - /* 120 */ 311, 312, 311, 310, 313, 59, 86, 212, 88, 19, - /* 130 */ 311, 312, 271, 272, 273, 220, 26, 112, 54, 55, - /* 140 */ 56, 57, 102, 103, 104, 105, 106, 107, 108, 109, - /* 150 */ 110, 111, 112, 43, 44, 45, 46, 47, 48, 49, - /* 160 */ 50, 51, 52, 53, 54, 55, 56, 57, 191, 115, - /* 170 */ 116, 117, 118, 137, 138, 121, 122, 123, 191, 69, - /* 180 */ 203, 115, 116, 117, 59, 131, 102, 103, 104, 105, - /* 190 */ 106, 107, 108, 109, 110, 111, 112, 72, 191, 19, - /* 200 */ 54, 55, 56, 57, 58, 108, 109, 110, 111, 112, - /* 210 */ 303, 304, 102, 103, 104, 105, 106, 107, 108, 109, - /* 220 */ 110, 111, 112, 43, 44, 45, 46, 47, 48, 49, - /* 230 */ 50, 51, 52, 53, 54, 55, 56, 57, 19, 16, - /* 240 */ 115, 116, 117, 24, 16, 227, 202, 67, 102, 103, - /* 250 */ 104, 105, 106, 107, 108, 109, 110, 111, 112, 59, - /* 260 */ 26, 191, 43, 44, 45, 46, 47, 48, 49, 50, - /* 270 */ 51, 52, 53, 54, 55, 56, 57, 24, 208, 209, - /* 280 */ 236, 237, 102, 103, 104, 105, 106, 107, 108, 109, - /* 290 */ 110, 111, 112, 249, 183, 184, 185, 186, 187, 188, - /* 300 */ 77, 59, 79, 191, 193, 77, 195, 79, 19, 19, - /* 310 */ 266, 304, 59, 202, 24, 115, 116, 117, 191, 127, - /* 320 */ 128, 102, 103, 104, 105, 106, 107, 108, 109, 110, - /* 330 */ 111, 112, 43, 44, 45, 46, 47, 48, 49, 50, - /* 340 */ 51, 52, 53, 54, 55, 56, 57, 236, 237, 191, - /* 350 */ 150, 281, 191, 185, 186, 187, 188, 115, 116, 117, - /* 360 */ 249, 193, 191, 195, 26, 73, 59, 191, 114, 116, - /* 370 */ 202, 213, 214, 81, 263, 106, 107, 108, 109, 110, - /* 380 */ 111, 112, 148, 160, 142, 95, 228, 191, 191, 213, - /* 390 */ 214, 102, 103, 104, 105, 106, 107, 108, 109, 110, - /* 400 */ 111, 112, 112, 149, 236, 237, 295, 100, 118, 119, - /* 410 */ 120, 121, 122, 123, 124, 19, 31, 249, 126, 23, - /* 420 */ 130, 260, 115, 116, 39, 22, 250, 120, 191, 137, - /* 430 */ 138, 263, 305, 306, 238, 259, 265, 310, 149, 43, - /* 440 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, - /* 450 */ 54, 55, 56, 57, 191, 117, 191, 210, 19, 152, - /* 460 */ 153, 154, 23, 295, 102, 103, 104, 105, 106, 107, - /* 470 */ 108, 109, 110, 111, 112, 266, 213, 214, 213, 214, - /* 480 */ 142, 81, 43, 44, 45, 46, 47, 48, 49, 50, - /* 490 */ 51, 52, 53, 54, 55, 56, 57, 301, 102, 103, - /* 500 */ 104, 105, 106, 107, 108, 109, 110, 111, 112, 106, - /* 510 */ 107, 118, 59, 250, 121, 122, 123, 280, 76, 119, - /* 520 */ 236, 237, 259, 306, 131, 72, 59, 310, 19, 87, - /* 530 */ 283, 89, 23, 249, 92, 288, 22, 137, 138, 22, - /* 540 */ 275, 102, 103, 104, 105, 106, 107, 108, 109, 110, - /* 550 */ 111, 112, 43, 44, 45, 46, 47, 48, 49, 50, - /* 560 */ 51, 52, 53, 54, 55, 56, 57, 19, 115, 116, - /* 570 */ 117, 23, 186, 59, 188, 108, 59, 241, 191, 193, - /* 580 */ 26, 195, 115, 116, 117, 191, 144, 251, 202, 22, - /* 590 */ 100, 43, 44, 45, 46, 47, 48, 49, 50, 51, - /* 600 */ 52, 53, 54, 55, 56, 57, 116, 213, 214, 191, - /* 610 */ 120, 102, 103, 104, 105, 106, 107, 108, 109, 110, - /* 620 */ 111, 112, 236, 237, 306, 238, 59, 26, 310, 115, - /* 630 */ 116, 117, 115, 116, 117, 249, 246, 19, 248, 106, - /* 640 */ 107, 23, 152, 153, 154, 46, 47, 48, 49, 263, - /* 650 */ 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - /* 660 */ 112, 43, 44, 45, 46, 47, 48, 49, 50, 51, - /* 670 */ 52, 53, 54, 55, 56, 57, 19, 76, 298, 299, - /* 680 */ 23, 295, 115, 116, 117, 152, 191, 154, 301, 73, - /* 690 */ 89, 137, 138, 92, 22, 191, 144, 22, 191, 191, - /* 700 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 710 */ 53, 54, 55, 56, 57, 163, 191, 213, 214, 120, - /* 720 */ 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - /* 730 */ 112, 59, 228, 191, 59, 191, 236, 237, 213, 214, - /* 740 */ 11, 59, 126, 127, 128, 238, 19, 26, 191, 249, - /* 750 */ 23, 164, 165, 228, 191, 213, 214, 213, 214, 102, - /* 760 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, - /* 770 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 780 */ 53, 54, 55, 56, 57, 19, 241, 115, 116, 117, - /* 790 */ 115, 116, 117, 191, 250, 238, 251, 115, 116, 117, - /* 800 */ 157, 23, 159, 191, 26, 191, 111, 112, 301, 43, - /* 810 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, - /* 820 */ 54, 55, 56, 57, 142, 213, 214, 213, 214, 102, - /* 830 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, - /* 840 */ 228, 191, 228, 191, 191, 207, 208, 209, 126, 127, - /* 850 */ 128, 133, 289, 135, 136, 19, 127, 128, 301, 7, - /* 860 */ 8, 9, 141, 213, 214, 213, 214, 265, 102, 103, - /* 870 */ 104, 105, 106, 107, 108, 109, 110, 111, 112, 43, - /* 880 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, - /* 890 */ 54, 55, 56, 57, 191, 117, 191, 22, 23, 19, - /* 900 */ 250, 26, 250, 191, 223, 191, 126, 127, 128, 205, - /* 910 */ 206, 205, 206, 260, 21, 202, 213, 214, 213, 214, - /* 920 */ 142, 270, 208, 209, 158, 45, 46, 47, 48, 49, - /* 930 */ 50, 51, 52, 53, 54, 55, 56, 57, 102, 103, - /* 940 */ 104, 105, 106, 107, 108, 109, 110, 111, 112, 236, - /* 950 */ 237, 12, 191, 250, 76, 250, 191, 22, 23, 308, - /* 960 */ 309, 26, 249, 202, 191, 191, 27, 89, 191, 202, - /* 970 */ 92, 202, 260, 80, 213, 214, 101, 203, 213, 214, - /* 980 */ 22, 42, 102, 103, 104, 105, 106, 107, 108, 109, - /* 990 */ 110, 111, 112, 228, 158, 281, 108, 236, 237, 225, - /* 1000 */ 191, 227, 63, 236, 237, 236, 237, 191, 235, 191, - /* 1010 */ 249, 250, 73, 241, 19, 122, 249, 59, 249, 24, - /* 1020 */ 259, 29, 134, 251, 191, 33, 22, 139, 24, 213, - /* 1030 */ 214, 213, 214, 191, 19, 210, 101, 191, 43, 44, - /* 1040 */ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, - /* 1050 */ 55, 56, 57, 160, 19, 213, 214, 65, 43, 44, - /* 1060 */ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, - /* 1070 */ 55, 56, 57, 191, 116, 22, 191, 24, 43, 44, - /* 1080 */ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, - /* 1090 */ 55, 56, 57, 191, 261, 213, 214, 102, 103, 104, - /* 1100 */ 105, 106, 107, 108, 109, 110, 111, 112, 59, 19, - /* 1110 */ 191, 265, 59, 288, 191, 213, 214, 102, 103, 104, - /* 1120 */ 105, 106, 107, 108, 109, 110, 111, 112, 35, 191, - /* 1130 */ 66, 191, 213, 214, 191, 270, 46, 102, 103, 104, - /* 1140 */ 105, 106, 107, 108, 109, 110, 111, 112, 191, 85, - /* 1150 */ 265, 213, 214, 213, 214, 106, 107, 19, 94, 66, - /* 1160 */ 137, 138, 191, 114, 115, 116, 117, 74, 119, 116, - /* 1170 */ 213, 214, 202, 308, 309, 306, 127, 191, 235, 310, - /* 1180 */ 59, 191, 44, 45, 46, 47, 48, 49, 50, 51, - /* 1190 */ 52, 53, 54, 55, 56, 57, 191, 76, 196, 213, - /* 1200 */ 214, 152, 12, 154, 114, 191, 236, 237, 87, 145, - /* 1210 */ 89, 19, 20, 92, 22, 22, 23, 27, 191, 249, - /* 1220 */ 130, 202, 129, 202, 191, 235, 306, 152, 36, 154, - /* 1230 */ 310, 191, 42, 191, 191, 191, 115, 116, 117, 191, - /* 1240 */ 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - /* 1250 */ 112, 59, 99, 63, 191, 236, 237, 236, 237, 191, - /* 1260 */ 289, 213, 214, 71, 191, 144, 261, 191, 249, 191, - /* 1270 */ 249, 19, 20, 81, 22, 235, 213, 214, 235, 191, - /* 1280 */ 278, 213, 214, 191, 282, 132, 213, 214, 36, 213, - /* 1290 */ 214, 191, 100, 191, 101, 191, 191, 191, 106, 107, - /* 1300 */ 48, 213, 214, 261, 191, 113, 191, 115, 116, 117, - /* 1310 */ 191, 59, 120, 213, 214, 213, 214, 213, 214, 213, - /* 1320 */ 214, 22, 23, 71, 237, 59, 213, 214, 191, 137, - /* 1330 */ 138, 191, 213, 214, 191, 191, 249, 85, 191, 261, - /* 1340 */ 191, 26, 90, 15, 152, 153, 154, 155, 156, 19, - /* 1350 */ 213, 214, 100, 213, 214, 191, 213, 214, 106, 107, - /* 1360 */ 213, 214, 213, 214, 191, 113, 261, 115, 116, 117, - /* 1370 */ 253, 254, 120, 229, 191, 191, 191, 213, 214, 191, - /* 1380 */ 306, 289, 116, 26, 310, 191, 213, 214, 60, 19, - /* 1390 */ 296, 297, 24, 191, 46, 191, 213, 214, 213, 214, - /* 1400 */ 101, 213, 214, 191, 152, 153, 154, 155, 156, 0, - /* 1410 */ 1, 2, 191, 229, 5, 213, 214, 213, 214, 10, - /* 1420 */ 11, 12, 13, 14, 1, 2, 17, 191, 5, 19, - /* 1430 */ 20, 191, 22, 10, 11, 12, 13, 14, 191, 30, - /* 1440 */ 17, 32, 241, 148, 149, 115, 36, 191, 241, 40, - /* 1450 */ 191, 26, 251, 30, 191, 32, 141, 22, 251, 5, - /* 1460 */ 213, 214, 114, 40, 10, 11, 12, 13, 14, 59, - /* 1470 */ 134, 17, 213, 214, 191, 139, 213, 214, 191, 70, - /* 1480 */ 191, 71, 125, 191, 30, 115, 32, 78, 53, 191, - /* 1490 */ 81, 191, 191, 70, 40, 85, 213, 214, 191, 22, - /* 1500 */ 90, 78, 213, 214, 81, 213, 214, 98, 140, 120, - /* 1510 */ 100, 213, 214, 213, 214, 23, 106, 107, 26, 130, - /* 1520 */ 191, 98, 191, 113, 70, 115, 116, 117, 23, 22, - /* 1530 */ 120, 26, 78, 19, 191, 81, 19, 20, 61, 22, - /* 1540 */ 191, 132, 213, 214, 213, 214, 137, 138, 59, 191, - /* 1550 */ 191, 191, 98, 36, 128, 132, 213, 214, 128, 59, - /* 1560 */ 137, 138, 152, 153, 154, 155, 156, 83, 84, 144, - /* 1570 */ 161, 213, 214, 213, 214, 23, 59, 151, 26, 23, - /* 1580 */ 23, 151, 26, 26, 161, 59, 132, 23, 71, 191, - /* 1590 */ 26, 137, 138, 119, 120, 23, 19, 20, 26, 22, - /* 1600 */ 223, 23, 85, 23, 26, 116, 26, 90, 191, 7, - /* 1610 */ 8, 97, 152, 36, 154, 161, 116, 100, 23, 23, - /* 1620 */ 191, 26, 26, 106, 107, 191, 23, 223, 23, 26, - /* 1630 */ 113, 26, 115, 116, 117, 23, 59, 120, 26, 191, - /* 1640 */ 191, 191, 116, 255, 191, 252, 191, 140, 71, 191, - /* 1650 */ 315, 233, 191, 191, 191, 191, 191, 191, 191, 191, - /* 1660 */ 191, 285, 284, 239, 252, 252, 252, 252, 240, 152, - /* 1670 */ 153, 154, 155, 156, 189, 294, 268, 100, 242, 268, - /* 1680 */ 264, 211, 290, 106, 107, 108, 264, 256, 256, 243, - /* 1690 */ 113, 290, 115, 116, 117, 268, 217, 120, 226, 243, - /* 1700 */ 222, 268, 216, 19, 20, 246, 22, 216, 256, 216, - /* 1710 */ 194, 60, 38, 242, 277, 294, 240, 242, 198, 246, - /* 1720 */ 36, 140, 198, 198, 19, 20, 150, 22, 149, 152, - /* 1730 */ 153, 154, 155, 156, 294, 291, 291, 280, 22, 43, - /* 1740 */ 231, 36, 18, 59, 234, 234, 234, 234, 267, 269, - /* 1750 */ 18, 198, 197, 231, 148, 71, 269, 269, 243, 231, - /* 1760 */ 198, 243, 267, 197, 59, 157, 243, 198, 62, 287, - /* 1770 */ 243, 197, 22, 198, 114, 64, 71, 218, 218, 197, - /* 1780 */ 286, 198, 197, 215, 100, 215, 215, 224, 22, 125, - /* 1790 */ 106, 107, 164, 24, 221, 112, 143, 113, 302, 115, - /* 1800 */ 116, 117, 218, 215, 120, 100, 217, 221, 215, 215, - /* 1810 */ 309, 106, 107, 215, 224, 279, 279, 218, 113, 258, - /* 1820 */ 115, 116, 117, 114, 257, 120, 91, 198, 82, 147, - /* 1830 */ 144, 22, 274, 314, 198, 314, 152, 153, 154, 155, - /* 1840 */ 156, 276, 157, 146, 145, 258, 25, 247, 257, 201, - /* 1850 */ 258, 26, 200, 13, 257, 244, 246, 152, 153, 154, - /* 1860 */ 155, 156, 258, 262, 257, 247, 245, 243, 192, 192, - /* 1870 */ 6, 262, 204, 210, 219, 210, 210, 190, 190, 190, - /* 1880 */ 210, 219, 204, 211, 211, 210, 4, 3, 22, 162, - /* 1890 */ 15, 23, 16, 23, 204, 138, 129, 150, 26, 141, - /* 1900 */ 20, 24, 143, 16, 1, 141, 129, 129, 61, 53, - /* 1910 */ 37, 150, 297, 300, 300, 53, 53, 129, 53, 115, - /* 1920 */ 34, 140, 1, 5, 22, 114, 68, 26, 160, 68, - /* 1930 */ 75, 41, 140, 114, 24, 20, 19, 130, 124, 23, - /* 1940 */ 96, 22, 22, 37, 22, 67, 22, 59, 67, 24, - /* 1950 */ 22, 28, 67, 23, 148, 22, 97, 23, 23, 23, - /* 1960 */ 140, 23, 22, 26, 23, 23, 115, 22, 142, 26, - /* 1970 */ 75, 88, 75, 34, 23, 86, 44, 22, 34, 26, - /* 1980 */ 34, 34, 34, 34, 93, 24, 26, 23, 34, 23, - /* 1990 */ 23, 23, 23, 11, 23, 22, 26, 22, 22, 15, - /* 2000 */ 23, 23, 22, 22, 1, 26, 23, 140, 134, 140, - /* 2010 */ 1, 140, 316, 316, 316, 316, 316, 316, 316, 140, - /* 2020 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2030 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2040 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2050 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2060 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2070 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2080 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2090 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2100 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2110 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2120 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2130 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2140 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2150 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2160 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2170 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2180 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2190 */ 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, - /* 2200 */ 316, 316, 316, + /* 0 */ 193, 193, 193, 274, 275, 276, 193, 274, 275, 276, + /* 10 */ 193, 223, 219, 225, 206, 210, 211, 212, 193, 19, + /* 20 */ 219, 233, 216, 216, 217, 216, 217, 193, 295, 216, + /* 30 */ 217, 31, 193, 216, 217, 193, 228, 213, 230, 39, + /* 40 */ 206, 216, 217, 43, 44, 45, 46, 47, 48, 49, + /* 50 */ 50, 51, 52, 53, 54, 55, 56, 57, 193, 19, + /* 60 */ 185, 186, 187, 188, 189, 190, 253, 274, 275, 276, + /* 70 */ 195, 193, 197, 193, 261, 274, 275, 276, 253, 204, + /* 80 */ 238, 204, 81, 43, 44, 45, 46, 47, 48, 49, + /* 90 */ 50, 51, 52, 53, 54, 55, 56, 57, 274, 275, + /* 100 */ 276, 262, 102, 103, 104, 105, 106, 107, 108, 109, + /* 110 */ 110, 111, 112, 113, 239, 240, 239, 240, 210, 211, + /* 120 */ 212, 314, 315, 314, 59, 316, 86, 252, 88, 252, + /* 130 */ 19, 314, 315, 256, 257, 113, 25, 72, 296, 138, + /* 140 */ 139, 266, 102, 103, 104, 105, 106, 107, 108, 109, + /* 150 */ 110, 111, 112, 113, 43, 44, 45, 46, 47, 48, + /* 160 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 81, + /* 170 */ 292, 59, 292, 298, 108, 109, 110, 111, 112, 113, + /* 180 */ 69, 116, 117, 118, 72, 106, 107, 193, 111, 112, + /* 190 */ 113, 54, 55, 56, 57, 58, 102, 103, 104, 105, + /* 200 */ 106, 107, 108, 109, 110, 111, 112, 113, 120, 25, + /* 210 */ 216, 217, 145, 102, 103, 104, 105, 106, 107, 108, + /* 220 */ 109, 110, 111, 112, 113, 231, 138, 139, 116, 117, + /* 230 */ 118, 164, 153, 19, 155, 54, 55, 56, 57, 102, + /* 240 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + /* 250 */ 113, 128, 129, 46, 47, 48, 49, 43, 44, 45, + /* 260 */ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + /* 270 */ 56, 57, 216, 193, 25, 59, 193, 19, 165, 166, + /* 280 */ 193, 67, 24, 102, 103, 104, 105, 106, 107, 108, + /* 290 */ 109, 110, 111, 112, 113, 73, 216, 217, 59, 216, + /* 300 */ 217, 43, 44, 45, 46, 47, 48, 49, 50, 51, + /* 310 */ 52, 53, 54, 55, 56, 57, 102, 103, 104, 105, + /* 320 */ 106, 107, 108, 109, 110, 111, 112, 113, 121, 145, + /* 330 */ 59, 193, 116, 117, 118, 119, 273, 204, 122, 123, + /* 340 */ 124, 19, 20, 134, 22, 136, 137, 19, 132, 127, + /* 350 */ 128, 129, 24, 22, 23, 116, 117, 118, 36, 193, + /* 360 */ 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + /* 370 */ 112, 113, 239, 240, 311, 312, 215, 106, 107, 241, + /* 380 */ 19, 59, 216, 217, 223, 252, 115, 116, 117, 118, + /* 390 */ 151, 120, 26, 71, 193, 308, 309, 193, 149, 128, + /* 400 */ 313, 216, 269, 81, 43, 44, 45, 46, 47, 48, + /* 410 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 253, + /* 420 */ 216, 217, 100, 95, 153, 59, 155, 261, 106, 107, + /* 430 */ 25, 193, 101, 193, 193, 231, 114, 25, 116, 117, + /* 440 */ 118, 113, 304, 121, 193, 204, 59, 119, 120, 121, + /* 450 */ 122, 123, 124, 125, 216, 217, 193, 216, 217, 131, + /* 460 */ 138, 139, 230, 102, 103, 104, 105, 106, 107, 108, + /* 470 */ 109, 110, 111, 112, 113, 153, 154, 155, 156, 157, + /* 480 */ 239, 240, 116, 117, 118, 76, 193, 23, 19, 25, + /* 490 */ 22, 253, 23, 252, 253, 108, 87, 204, 89, 261, + /* 500 */ 198, 92, 261, 116, 117, 118, 193, 306, 307, 216, + /* 510 */ 217, 150, 43, 44, 45, 46, 47, 48, 49, 50, + /* 520 */ 51, 52, 53, 54, 55, 56, 57, 59, 193, 216, + /* 530 */ 217, 19, 239, 240, 283, 23, 106, 107, 108, 109, + /* 540 */ 110, 111, 112, 113, 73, 252, 253, 142, 308, 309, + /* 550 */ 138, 139, 81, 313, 145, 43, 44, 45, 46, 47, + /* 560 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + /* 570 */ 307, 102, 103, 104, 105, 106, 107, 108, 109, 110, + /* 580 */ 111, 112, 113, 281, 116, 117, 118, 285, 23, 193, + /* 590 */ 25, 119, 59, 193, 122, 123, 124, 59, 127, 203, + /* 600 */ 59, 205, 19, 268, 132, 25, 23, 22, 193, 138, + /* 610 */ 139, 249, 204, 251, 102, 103, 104, 105, 106, 107, + /* 620 */ 108, 109, 110, 111, 112, 113, 43, 44, 45, 46, + /* 630 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 640 */ 57, 19, 22, 23, 59, 23, 25, 239, 240, 116, + /* 650 */ 117, 118, 193, 11, 116, 117, 118, 116, 117, 118, + /* 660 */ 252, 269, 22, 193, 15, 43, 44, 45, 46, 47, + /* 670 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + /* 680 */ 273, 143, 193, 118, 143, 102, 103, 104, 105, 106, + /* 690 */ 107, 108, 109, 110, 111, 112, 113, 76, 118, 59, + /* 700 */ 241, 116, 117, 118, 304, 216, 217, 292, 143, 60, + /* 710 */ 89, 241, 19, 92, 193, 193, 23, 22, 311, 312, + /* 720 */ 231, 101, 22, 143, 102, 103, 104, 105, 106, 107, + /* 730 */ 108, 109, 110, 111, 112, 113, 43, 44, 45, 46, + /* 740 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 750 */ 57, 19, 193, 193, 59, 23, 116, 117, 118, 59, + /* 760 */ 201, 21, 241, 304, 22, 206, 127, 128, 129, 193, + /* 770 */ 128, 129, 235, 236, 304, 43, 44, 45, 46, 47, + /* 780 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + /* 790 */ 22, 193, 216, 217, 193, 102, 103, 104, 105, 106, + /* 800 */ 107, 108, 109, 110, 111, 112, 113, 231, 193, 193, + /* 810 */ 193, 116, 117, 118, 216, 217, 116, 117, 118, 226, + /* 820 */ 80, 193, 19, 235, 236, 304, 23, 211, 212, 231, + /* 830 */ 204, 216, 217, 205, 102, 103, 104, 105, 106, 107, + /* 840 */ 108, 109, 110, 111, 112, 113, 43, 44, 45, 46, + /* 850 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 860 */ 57, 19, 193, 123, 76, 239, 240, 193, 253, 239, + /* 870 */ 240, 239, 240, 193, 106, 107, 193, 89, 252, 193, + /* 880 */ 92, 59, 252, 141, 252, 43, 44, 45, 46, 47, + /* 890 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + /* 900 */ 284, 161, 216, 217, 193, 102, 103, 104, 105, 106, + /* 910 */ 107, 108, 109, 110, 111, 112, 113, 231, 193, 16, + /* 920 */ 187, 188, 189, 190, 7, 8, 9, 309, 195, 25, + /* 930 */ 197, 313, 19, 127, 128, 129, 262, 204, 22, 117, + /* 940 */ 24, 216, 217, 263, 102, 103, 104, 105, 106, 107, + /* 950 */ 108, 109, 110, 111, 112, 113, 43, 44, 45, 46, + /* 960 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 970 */ 57, 193, 239, 240, 193, 59, 19, 188, 253, 190, + /* 980 */ 77, 226, 79, 193, 195, 252, 197, 193, 19, 301, + /* 990 */ 302, 193, 193, 204, 216, 217, 226, 216, 217, 266, + /* 1000 */ 204, 159, 45, 46, 47, 48, 49, 50, 51, 52, + /* 1010 */ 53, 54, 55, 56, 57, 102, 103, 104, 105, 106, + /* 1020 */ 107, 108, 109, 110, 111, 112, 113, 12, 239, 240, + /* 1030 */ 232, 298, 238, 117, 253, 239, 240, 238, 259, 260, + /* 1040 */ 193, 252, 27, 31, 193, 193, 142, 204, 252, 193, + /* 1050 */ 193, 39, 262, 193, 100, 266, 278, 42, 204, 102, + /* 1060 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + /* 1070 */ 113, 117, 159, 216, 217, 121, 216, 217, 63, 193, + /* 1080 */ 193, 193, 239, 240, 115, 116, 193, 298, 73, 238, + /* 1090 */ 238, 231, 19, 239, 240, 252, 22, 24, 211, 212, + /* 1100 */ 24, 193, 216, 217, 216, 217, 252, 153, 154, 155, + /* 1110 */ 253, 16, 19, 144, 213, 268, 43, 44, 45, 46, + /* 1120 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 1130 */ 57, 238, 19, 59, 193, 59, 43, 44, 45, 46, + /* 1140 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 1150 */ 57, 22, 23, 193, 25, 193, 43, 44, 45, 46, + /* 1160 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 1170 */ 57, 284, 77, 193, 79, 102, 103, 104, 105, 106, + /* 1180 */ 107, 108, 109, 110, 111, 112, 113, 286, 193, 193, + /* 1190 */ 193, 117, 291, 117, 232, 102, 103, 104, 105, 106, + /* 1200 */ 107, 108, 109, 110, 111, 112, 113, 204, 22, 23, + /* 1210 */ 66, 25, 216, 217, 35, 102, 103, 104, 105, 106, + /* 1220 */ 107, 108, 109, 110, 111, 112, 113, 193, 268, 85, + /* 1230 */ 101, 193, 309, 309, 240, 19, 313, 313, 94, 208, + /* 1240 */ 209, 193, 239, 240, 193, 66, 252, 19, 268, 244, + /* 1250 */ 216, 217, 193, 74, 213, 252, 161, 19, 263, 254, + /* 1260 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + /* 1270 */ 54, 55, 56, 57, 193, 216, 217, 5, 59, 193, + /* 1280 */ 19, 244, 10, 11, 12, 13, 14, 101, 309, 17, + /* 1290 */ 146, 254, 313, 193, 193, 76, 115, 216, 217, 309, + /* 1300 */ 12, 263, 30, 313, 32, 46, 87, 46, 89, 130, + /* 1310 */ 193, 92, 40, 22, 263, 27, 216, 217, 102, 103, + /* 1320 */ 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + /* 1330 */ 42, 150, 291, 216, 217, 116, 117, 118, 19, 20, + /* 1340 */ 193, 22, 70, 260, 116, 193, 24, 264, 193, 263, + /* 1350 */ 78, 63, 61, 81, 116, 36, 193, 260, 193, 29, + /* 1360 */ 193, 264, 193, 33, 145, 193, 59, 48, 216, 217, + /* 1370 */ 98, 216, 217, 193, 115, 193, 115, 193, 59, 216, + /* 1380 */ 217, 216, 217, 216, 217, 216, 217, 255, 216, 217, + /* 1390 */ 71, 193, 131, 193, 25, 65, 216, 217, 216, 217, + /* 1400 */ 216, 217, 208, 209, 85, 133, 193, 100, 193, 90, + /* 1410 */ 138, 139, 138, 139, 216, 217, 216, 217, 193, 100, + /* 1420 */ 193, 108, 135, 116, 117, 106, 107, 140, 121, 216, + /* 1430 */ 217, 216, 217, 114, 162, 116, 117, 118, 299, 300, + /* 1440 */ 121, 216, 217, 216, 217, 193, 244, 193, 135, 244, + /* 1450 */ 193, 256, 257, 140, 244, 193, 254, 193, 193, 254, + /* 1460 */ 153, 154, 155, 141, 254, 149, 150, 258, 216, 217, + /* 1470 */ 216, 217, 153, 154, 155, 156, 157, 0, 1, 2, + /* 1480 */ 216, 217, 5, 115, 158, 193, 160, 10, 11, 12, + /* 1490 */ 13, 14, 193, 59, 17, 126, 193, 19, 20, 129, + /* 1500 */ 22, 193, 22, 22, 24, 193, 23, 30, 25, 32, + /* 1510 */ 19, 20, 144, 22, 36, 216, 217, 40, 193, 216, + /* 1520 */ 217, 193, 152, 129, 216, 217, 193, 36, 216, 217, + /* 1530 */ 193, 99, 193, 193, 53, 193, 193, 59, 23, 193, + /* 1540 */ 25, 216, 217, 193, 216, 217, 152, 70, 59, 71, + /* 1550 */ 59, 117, 193, 216, 217, 78, 216, 217, 81, 216, + /* 1560 */ 217, 318, 71, 85, 193, 133, 193, 193, 90, 23, + /* 1570 */ 23, 25, 25, 120, 121, 98, 85, 193, 100, 193, + /* 1580 */ 23, 90, 25, 121, 106, 107, 19, 216, 217, 216, + /* 1590 */ 217, 100, 114, 131, 116, 117, 118, 106, 107, 121, + /* 1600 */ 216, 217, 216, 217, 193, 114, 117, 116, 117, 118, + /* 1610 */ 133, 193, 121, 193, 193, 138, 139, 193, 23, 193, + /* 1620 */ 25, 23, 23, 25, 25, 7, 8, 216, 217, 193, + /* 1630 */ 193, 153, 154, 155, 156, 157, 216, 217, 193, 162, + /* 1640 */ 216, 217, 216, 217, 153, 154, 155, 156, 157, 1, + /* 1650 */ 2, 193, 193, 5, 19, 20, 59, 22, 10, 11, + /* 1660 */ 12, 13, 14, 193, 97, 17, 193, 23, 193, 25, + /* 1670 */ 288, 36, 193, 242, 216, 217, 236, 23, 30, 25, + /* 1680 */ 32, 19, 20, 23, 22, 25, 216, 217, 40, 216, + /* 1690 */ 217, 216, 217, 193, 59, 216, 217, 193, 36, 83, + /* 1700 */ 84, 153, 153, 155, 155, 23, 71, 25, 23, 193, + /* 1710 */ 25, 193, 193, 193, 117, 193, 193, 193, 70, 193, + /* 1720 */ 193, 59, 193, 255, 255, 287, 78, 255, 243, 81, + /* 1730 */ 191, 255, 297, 71, 271, 100, 293, 245, 267, 214, + /* 1740 */ 246, 106, 107, 108, 246, 271, 98, 245, 293, 114, + /* 1750 */ 220, 116, 117, 118, 267, 271, 121, 271, 225, 219, + /* 1760 */ 229, 219, 100, 219, 259, 259, 259, 259, 106, 107, + /* 1770 */ 249, 196, 60, 280, 141, 243, 114, 249, 116, 117, + /* 1780 */ 118, 133, 245, 121, 200, 297, 138, 139, 153, 154, + /* 1790 */ 155, 156, 157, 297, 200, 38, 19, 20, 151, 22, + /* 1800 */ 200, 150, 140, 294, 294, 22, 272, 43, 234, 18, + /* 1810 */ 162, 270, 200, 36, 237, 153, 154, 155, 156, 157, + /* 1820 */ 237, 283, 237, 237, 18, 199, 149, 246, 272, 270, + /* 1830 */ 272, 200, 158, 246, 246, 234, 59, 234, 246, 199, + /* 1840 */ 290, 62, 289, 200, 199, 22, 221, 115, 71, 200, + /* 1850 */ 200, 199, 199, 221, 218, 218, 19, 20, 64, 22, + /* 1860 */ 218, 227, 22, 224, 126, 224, 165, 221, 24, 305, + /* 1870 */ 200, 113, 312, 36, 218, 220, 218, 100, 282, 218, + /* 1880 */ 91, 218, 317, 106, 107, 221, 227, 282, 317, 82, + /* 1890 */ 148, 114, 265, 116, 117, 118, 59, 145, 121, 22, + /* 1900 */ 277, 158, 200, 265, 25, 202, 147, 250, 71, 279, + /* 1910 */ 13, 146, 194, 194, 249, 248, 250, 140, 247, 246, + /* 1920 */ 6, 192, 192, 192, 303, 303, 213, 207, 300, 213, + /* 1930 */ 153, 154, 155, 156, 157, 213, 213, 100, 213, 222, + /* 1940 */ 207, 214, 214, 106, 107, 4, 222, 207, 3, 22, + /* 1950 */ 163, 114, 15, 116, 117, 118, 16, 23, 121, 23, + /* 1960 */ 139, 151, 130, 25, 142, 16, 24, 20, 144, 1, + /* 1970 */ 142, 130, 130, 61, 53, 53, 37, 151, 53, 53, + /* 1980 */ 130, 116, 34, 1, 141, 5, 22, 115, 161, 141, + /* 1990 */ 153, 154, 155, 156, 157, 25, 68, 68, 75, 41, + /* 2000 */ 115, 24, 131, 20, 19, 125, 22, 96, 22, 22, + /* 2010 */ 67, 23, 22, 67, 59, 24, 22, 28, 67, 23, + /* 2020 */ 22, 22, 149, 23, 23, 23, 116, 23, 25, 37, + /* 2030 */ 97, 141, 23, 23, 22, 143, 25, 75, 88, 34, + /* 2040 */ 34, 34, 34, 86, 75, 93, 23, 34, 22, 34, + /* 2050 */ 25, 24, 34, 25, 23, 142, 23, 142, 44, 23, + /* 2060 */ 23, 23, 11, 23, 25, 22, 22, 22, 15, 23, + /* 2070 */ 23, 22, 22, 25, 1, 1, 141, 25, 23, 135, + /* 2080 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2090 */ 319, 319, 319, 319, 141, 141, 319, 141, 319, 319, + /* 2100 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2110 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2120 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2130 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2140 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2150 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2160 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2170 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2180 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2190 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2200 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2210 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2220 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2230 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2240 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2250 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2260 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2270 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2280 */ 319, 319, 319, }; -#define YY_SHIFT_COUNT (569) +#define YY_SHIFT_COUNT (575) #define YY_SHIFT_MIN (0) -#define YY_SHIFT_MAX (2009) +#define YY_SHIFT_MAX (2074) static const unsigned short int yy_shift_ofst[] = { - /* 0 */ 1423, 1409, 1454, 1192, 1192, 36, 1252, 1410, 1517, 1684, - /* 10 */ 1684, 1684, 292, 0, 0, 180, 1015, 1684, 1684, 1684, - /* 20 */ 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, - /* 30 */ 1049, 1049, 1121, 1121, 54, 400, 36, 36, 36, 36, - /* 40 */ 36, 40, 110, 219, 289, 396, 439, 509, 548, 618, - /* 50 */ 657, 727, 766, 836, 995, 1015, 1015, 1015, 1015, 1015, - /* 60 */ 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, - /* 70 */ 1015, 1015, 1015, 1035, 1015, 1138, 880, 880, 1577, 1684, - /* 80 */ 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, - /* 90 */ 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, - /* 100 */ 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, - /* 110 */ 1684, 1684, 1684, 1705, 1684, 1684, 1684, 1684, 1684, 1684, - /* 120 */ 1684, 1684, 1684, 1684, 1684, 1684, 1684, 146, 84, 84, - /* 130 */ 84, 84, 84, 362, 269, 125, 97, 453, 66, 66, - /* 140 */ 893, 1090, 66, 66, 533, 533, 66, 554, 554, 554, - /* 150 */ 554, 192, 587, 587, 695, 25, 2020, 2020, 290, 290, - /* 160 */ 290, 200, 514, 514, 514, 514, 939, 939, 442, 875, - /* 170 */ 935, 66, 66, 66, 66, 66, 66, 66, 66, 66, - /* 180 */ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, - /* 190 */ 66, 601, 601, 66, 729, 878, 878, 1266, 1266, 552, - /* 200 */ 1023, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 307, 490, - /* 210 */ 490, 567, 393, 517, 467, 672, 242, 682, 675, 66, - /* 220 */ 66, 66, 66, 66, 66, 66, 66, 66, 66, 616, - /* 230 */ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, - /* 240 */ 66, 66, 1093, 1093, 1093, 66, 66, 66, 778, 66, - /* 250 */ 66, 66, 1053, 1064, 66, 66, 1190, 66, 66, 66, - /* 260 */ 66, 66, 66, 66, 66, 722, 992, 718, 253, 253, - /* 270 */ 253, 253, 338, 718, 718, 888, 403, 852, 1328, 254, - /* 280 */ 1295, 721, 1330, 1295, 1330, 1370, 234, 254, 254, 234, - /* 290 */ 254, 721, 1370, 1357, 1492, 1348, 385, 385, 385, 1330, - /* 300 */ 1425, 1425, 643, 1315, 1336, 1004, 1651, 1651, 1581, 1581, - /* 310 */ 1674, 1674, 1581, 1576, 1579, 1716, 1696, 1724, 1724, 1724, - /* 320 */ 1724, 1581, 1732, 1606, 1579, 1579, 1606, 1716, 1696, 1606, - /* 330 */ 1696, 1606, 1581, 1732, 1608, 1706, 1581, 1732, 1750, 1581, - /* 340 */ 1732, 1581, 1732, 1750, 1660, 1660, 1660, 1711, 1766, 1766, - /* 350 */ 1750, 1660, 1664, 1660, 1711, 1660, 1660, 1628, 1769, 1683, - /* 360 */ 1683, 1750, 1653, 1709, 1653, 1709, 1653, 1709, 1653, 1709, - /* 370 */ 1581, 1735, 1735, 1746, 1746, 1682, 1686, 1809, 1581, 1685, - /* 380 */ 1682, 1697, 1699, 1606, 1821, 1825, 1840, 1840, 1864, 1864, - /* 390 */ 1864, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, - /* 400 */ 2020, 2020, 2020, 2020, 2020, 2020, 599, 223, 1193, 1299, - /* 410 */ 228, 780, 958, 1505, 1153, 1435, 1368, 1426, 1430, 1552, - /* 420 */ 1477, 1556, 1557, 1564, 1572, 1578, 1580, 1489, 1474, 1602, - /* 430 */ 1389, 1514, 1500, 1595, 1596, 1484, 1603, 1075, 1460, 1605, - /* 440 */ 1612, 1526, 1507, 1882, 1884, 1866, 1727, 1875, 1876, 1868, - /* 450 */ 1870, 1757, 1747, 1767, 1872, 1872, 1877, 1758, 1880, 1759, - /* 460 */ 1887, 1903, 1764, 1777, 1872, 1778, 1847, 1873, 1872, 1761, - /* 470 */ 1856, 1862, 1863, 1865, 1788, 1804, 1886, 1781, 1921, 1918, - /* 480 */ 1902, 1811, 1768, 1858, 1901, 1861, 1855, 1890, 1792, 1819, - /* 490 */ 1910, 1915, 1917, 1807, 1814, 1919, 1878, 1920, 1922, 1916, - /* 500 */ 1924, 1881, 1888, 1925, 1844, 1923, 1928, 1885, 1906, 1930, - /* 510 */ 1806, 1933, 1934, 1935, 1936, 1937, 1938, 1940, 1859, 1820, - /* 520 */ 1941, 1942, 1851, 1939, 1945, 1826, 1943, 1944, 1946, 1947, - /* 530 */ 1948, 1883, 1895, 1889, 1932, 1897, 1891, 1949, 1951, 1955, - /* 540 */ 1961, 1953, 1960, 1954, 1964, 1943, 1966, 1967, 1968, 1969, - /* 550 */ 1970, 1971, 1973, 1982, 1975, 1976, 1977, 1978, 1980, 1981, - /* 560 */ 1979, 1874, 1867, 1869, 1871, 1879, 1983, 1984, 2003, 2009, + /* 0 */ 1648, 1477, 1272, 322, 322, 1, 1319, 1478, 1491, 1837, + /* 10 */ 1837, 1837, 471, 0, 0, 214, 1093, 1837, 1837, 1837, + /* 20 */ 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, + /* 30 */ 271, 271, 1219, 1219, 216, 88, 1, 1, 1, 1, + /* 40 */ 1, 40, 111, 258, 361, 469, 512, 583, 622, 693, + /* 50 */ 732, 803, 842, 913, 1073, 1093, 1093, 1093, 1093, 1093, + /* 60 */ 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, + /* 70 */ 1093, 1093, 1093, 1113, 1093, 1216, 957, 957, 1635, 1662, + /* 80 */ 1777, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, + /* 90 */ 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, + /* 100 */ 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, + /* 110 */ 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, + /* 120 */ 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, + /* 130 */ 137, 181, 181, 181, 181, 181, 181, 181, 94, 430, + /* 140 */ 66, 65, 112, 366, 533, 533, 740, 1261, 533, 533, + /* 150 */ 79, 79, 533, 412, 412, 412, 77, 412, 123, 113, + /* 160 */ 113, 22, 22, 2098, 2098, 328, 328, 328, 239, 468, + /* 170 */ 468, 468, 468, 1015, 1015, 409, 366, 1129, 1186, 533, + /* 180 */ 533, 533, 533, 533, 533, 533, 533, 533, 533, 533, + /* 190 */ 533, 533, 533, 533, 533, 533, 533, 533, 533, 969, + /* 200 */ 621, 621, 533, 642, 788, 788, 1228, 1228, 822, 822, + /* 210 */ 67, 1274, 2098, 2098, 2098, 2098, 2098, 2098, 2098, 1307, + /* 220 */ 954, 954, 585, 472, 640, 387, 695, 538, 541, 700, + /* 230 */ 533, 533, 533, 533, 533, 533, 533, 533, 533, 533, + /* 240 */ 222, 533, 533, 533, 533, 533, 533, 533, 533, 533, + /* 250 */ 533, 533, 533, 1179, 1179, 1179, 533, 533, 533, 565, + /* 260 */ 533, 533, 533, 916, 1144, 533, 533, 1288, 533, 533, + /* 270 */ 533, 533, 533, 533, 533, 533, 639, 1330, 209, 1076, + /* 280 */ 1076, 1076, 1076, 580, 209, 209, 1313, 768, 917, 649, + /* 290 */ 1181, 1316, 405, 1316, 1238, 249, 1181, 1181, 249, 1181, + /* 300 */ 405, 1238, 1369, 464, 1259, 1012, 1012, 1012, 1368, 1368, + /* 310 */ 1368, 1368, 184, 184, 1326, 904, 1287, 1480, 1712, 1712, + /* 320 */ 1633, 1633, 1757, 1757, 1633, 1647, 1651, 1783, 1764, 1791, + /* 330 */ 1791, 1791, 1791, 1633, 1806, 1677, 1651, 1651, 1677, 1783, + /* 340 */ 1764, 1677, 1764, 1677, 1633, 1806, 1674, 1779, 1633, 1806, + /* 350 */ 1823, 1633, 1806, 1633, 1806, 1823, 1732, 1732, 1732, 1794, + /* 360 */ 1840, 1840, 1823, 1732, 1738, 1732, 1794, 1732, 1732, 1701, + /* 370 */ 1844, 1758, 1758, 1823, 1633, 1789, 1789, 1807, 1807, 1742, + /* 380 */ 1752, 1877, 1633, 1743, 1742, 1759, 1765, 1677, 1879, 1897, + /* 390 */ 1897, 1914, 1914, 1914, 2098, 2098, 2098, 2098, 2098, 2098, + /* 400 */ 2098, 2098, 2098, 2098, 2098, 2098, 2098, 2098, 2098, 207, + /* 410 */ 1095, 331, 620, 903, 806, 1074, 1483, 1432, 1481, 1322, + /* 420 */ 1370, 1394, 1515, 1291, 1546, 1547, 1557, 1595, 1598, 1599, + /* 430 */ 1434, 1453, 1618, 1462, 1567, 1489, 1644, 1654, 1616, 1660, + /* 440 */ 1548, 1549, 1682, 1685, 1597, 742, 1941, 1945, 1927, 1787, + /* 450 */ 1937, 1940, 1934, 1936, 1821, 1810, 1832, 1938, 1938, 1942, + /* 460 */ 1822, 1947, 1824, 1949, 1968, 1828, 1841, 1938, 1842, 1912, + /* 470 */ 1939, 1938, 1826, 1921, 1922, 1925, 1926, 1850, 1865, 1948, + /* 480 */ 1843, 1982, 1980, 1964, 1872, 1827, 1928, 1970, 1929, 1923, + /* 490 */ 1958, 1848, 1885, 1977, 1983, 1985, 1871, 1880, 1984, 1943, + /* 500 */ 1986, 1987, 1988, 1990, 1946, 1955, 1991, 1911, 1989, 1994, + /* 510 */ 1951, 1992, 1996, 1873, 1998, 2000, 2001, 2002, 2003, 2004, + /* 520 */ 1999, 1933, 1890, 2009, 2010, 1910, 2005, 2012, 1892, 2011, + /* 530 */ 2006, 2007, 2008, 2013, 1950, 1962, 1957, 2014, 1969, 1952, + /* 540 */ 2015, 2023, 2026, 2027, 2025, 2028, 2018, 1913, 1915, 2031, + /* 550 */ 2011, 2033, 2036, 2037, 2038, 2039, 2040, 2043, 2051, 2044, + /* 560 */ 2045, 2046, 2047, 2049, 2050, 2048, 1944, 1935, 1953, 1954, + /* 570 */ 1956, 2052, 2055, 2053, 2073, 2074, }; -#define YY_REDUCE_COUNT (405) -#define YY_REDUCE_MIN (-265) -#define YY_REDUCE_MAX (1690) +#define YY_REDUCE_COUNT (408) +#define YY_REDUCE_MIN (-271) +#define YY_REDUCE_MAX (1740) static const short yy_reduce_ofst[] = { - /* 0 */ 111, 168, 386, 761, -176, -174, -191, -189, -181, -178, - /* 10 */ 176, 263, 44, -207, -204, -265, -139, -114, 158, 504, - /* 20 */ 525, 544, 612, 614, 650, 652, 765, 265, 703, 705, - /* 30 */ 70, 714, -187, 127, 774, 713, 767, 769, 970, 1019, - /* 40 */ 1021, -255, -255, -255, -255, -255, -255, -255, -255, -255, - /* 50 */ -255, -255, -255, -255, -255, -255, -255, -255, -255, -255, - /* 60 */ -255, -255, -255, -255, -255, -255, -255, -255, -255, -255, - /* 70 */ -255, -255, -255, -255, -255, -255, -255, -255, 394, 542, - /* 80 */ 816, 818, 842, 882, 902, 919, 938, 940, 957, 986, - /* 90 */ 1048, 1063, 1068, 1073, 1076, 1088, 1100, 1102, 1104, 1106, - /* 100 */ 1113, 1119, 1137, 1140, 1143, 1147, 1149, 1164, 1173, 1183, - /* 110 */ 1185, 1188, 1202, 1204, 1247, 1259, 1263, 1283, 1289, 1292, - /* 120 */ 1298, 1300, 1329, 1331, 1343, 1358, 1360, -255, -255, -255, - /* 130 */ -255, -255, -255, -255, -255, 196, -255, 387, -177, 507, - /* 140 */ 1002, -219, 557, -93, -167, 638, -121, 284, 500, 284, - /* 150 */ 500, 247, 651, 865, -255, -255, -255, -255, -85, -85, - /* 160 */ -85, 237, 171, 602, 846, 885, -212, -203, 217, 380, - /* 170 */ 380, -23, 161, 653, 712, 773, 943, 990, 1040, 563, - /* 180 */ 833, 971, 1005, 1042, 1092, 1078, 1043, 1144, 1184, -186, - /* 190 */ 1105, 318, 869, 7, 825, 920, 1074, 704, 706, 390, - /* 200 */ 1087, 1094, 336, 545, 772, 1201, 1117, 1207, -179, -137, - /* 210 */ -112, -13, 18, 112, 197, 418, 495, 508, 777, 809, - /* 220 */ 923, 1014, 1027, 1033, 1044, 1115, 1194, 1212, 1221, 209, - /* 230 */ 1236, 1240, 1256, 1287, 1301, 1307, 1349, 1359, 1398, 1417, - /* 240 */ 1429, 1434, 681, 1377, 1404, 1448, 1449, 1450, 1388, 1453, - /* 250 */ 1455, 1458, 1393, 1335, 1461, 1462, 1418, 1463, 197, 1464, - /* 260 */ 1465, 1466, 1467, 1468, 1469, 1376, 1378, 1424, 1412, 1413, - /* 270 */ 1414, 1415, 1388, 1424, 1424, 1428, 1470, 1485, 1381, 1408, - /* 280 */ 1416, 1436, 1431, 1422, 1432, 1392, 1446, 1411, 1427, 1456, - /* 290 */ 1433, 1471, 1401, 1479, 1472, 1478, 1486, 1491, 1493, 1452, - /* 300 */ 1459, 1473, 1437, 1475, 1476, 1516, 1421, 1440, 1520, 1524, - /* 310 */ 1444, 1445, 1525, 1457, 1480, 1481, 1509, 1510, 1511, 1512, - /* 320 */ 1513, 1553, 1555, 1515, 1487, 1488, 1518, 1495, 1522, 1523, - /* 330 */ 1528, 1527, 1562, 1566, 1482, 1494, 1569, 1574, 1559, 1575, - /* 340 */ 1582, 1583, 1585, 1560, 1568, 1570, 1571, 1563, 1573, 1586, - /* 350 */ 1584, 1588, 1589, 1593, 1590, 1594, 1598, 1501, 1496, 1536, - /* 360 */ 1537, 1599, 1561, 1567, 1587, 1591, 1592, 1597, 1604, 1607, - /* 370 */ 1629, 1519, 1521, 1601, 1609, 1600, 1610, 1558, 1636, 1565, - /* 380 */ 1618, 1621, 1611, 1624, 1648, 1652, 1676, 1677, 1687, 1688, - /* 390 */ 1689, 1613, 1614, 1615, 1668, 1663, 1665, 1666, 1670, 1678, - /* 400 */ 1655, 1662, 1672, 1673, 1675, 1690, + /* 0 */ -125, 733, 789, 241, 293, -123, -193, -191, -183, -187, + /* 10 */ 166, 238, 133, -207, -199, -267, -176, -6, 204, 489, + /* 20 */ 576, -175, 598, 686, 615, 725, 860, 778, 781, 857, + /* 30 */ 616, 887, 87, 240, -192, 408, 626, 796, 843, 854, + /* 40 */ 1003, -271, -271, -271, -271, -271, -271, -271, -271, -271, + /* 50 */ -271, -271, -271, -271, -271, -271, -271, -271, -271, -271, + /* 60 */ -271, -271, -271, -271, -271, -271, -271, -271, -271, -271, + /* 70 */ -271, -271, -271, -271, -271, -271, -271, -271, 80, 83, + /* 80 */ 313, 886, 888, 996, 1034, 1059, 1081, 1100, 1117, 1152, + /* 90 */ 1155, 1163, 1165, 1167, 1169, 1172, 1180, 1182, 1184, 1198, + /* 100 */ 1200, 1213, 1215, 1225, 1227, 1252, 1254, 1264, 1299, 1303, + /* 110 */ 1308, 1312, 1325, 1328, 1337, 1340, 1343, 1371, 1373, 1384, + /* 120 */ 1386, 1411, 1420, 1424, 1426, 1458, 1470, 1473, 1475, 1479, + /* 130 */ -271, -271, -271, -271, -271, -271, -271, -271, -271, -271, + /* 140 */ -271, 138, 459, 396, -158, 470, 302, -212, 521, 201, + /* 150 */ -195, -92, 559, 630, 632, 630, -271, 632, 901, 63, + /* 160 */ 407, -271, -271, -271, -271, 161, 161, 161, 251, 335, + /* 170 */ 847, 960, 980, 537, 588, 618, 628, 688, 688, -166, + /* 180 */ -161, 674, 790, 794, 799, 851, 852, -122, 680, -120, + /* 190 */ 995, 1038, 415, 1051, 893, 798, 962, 400, 1086, 779, + /* 200 */ 923, 924, 263, 1041, 979, 990, 1083, 1097, 1031, 1194, + /* 210 */ 362, 994, 1139, 1005, 1037, 1202, 1205, 1195, 1210, -194, + /* 220 */ 56, 185, -135, 232, 522, 560, 601, 617, 669, 683, + /* 230 */ 711, 856, 908, 941, 1048, 1101, 1147, 1257, 1262, 1265, + /* 240 */ 392, 1292, 1333, 1339, 1342, 1346, 1350, 1359, 1374, 1418, + /* 250 */ 1421, 1436, 1437, 593, 755, 770, 997, 1445, 1459, 1209, + /* 260 */ 1500, 1504, 1516, 1132, 1243, 1518, 1519, 1440, 1520, 560, + /* 270 */ 1522, 1523, 1524, 1526, 1527, 1529, 1382, 1438, 1431, 1468, + /* 280 */ 1469, 1472, 1476, 1209, 1431, 1431, 1485, 1525, 1539, 1435, + /* 290 */ 1463, 1471, 1492, 1487, 1443, 1494, 1474, 1484, 1498, 1486, + /* 300 */ 1502, 1455, 1530, 1531, 1533, 1540, 1542, 1544, 1505, 1506, + /* 310 */ 1507, 1508, 1521, 1528, 1493, 1537, 1532, 1575, 1488, 1496, + /* 320 */ 1584, 1594, 1509, 1510, 1600, 1538, 1534, 1541, 1574, 1577, + /* 330 */ 1583, 1585, 1586, 1612, 1626, 1581, 1556, 1558, 1587, 1559, + /* 340 */ 1601, 1588, 1603, 1592, 1631, 1640, 1550, 1553, 1643, 1645, + /* 350 */ 1625, 1649, 1652, 1650, 1653, 1632, 1636, 1637, 1642, 1634, + /* 360 */ 1639, 1641, 1646, 1656, 1655, 1658, 1659, 1661, 1663, 1560, + /* 370 */ 1564, 1596, 1605, 1664, 1670, 1565, 1571, 1627, 1638, 1657, + /* 380 */ 1665, 1623, 1702, 1630, 1666, 1667, 1671, 1673, 1703, 1718, + /* 390 */ 1719, 1729, 1730, 1731, 1621, 1622, 1628, 1720, 1713, 1716, + /* 400 */ 1722, 1723, 1733, 1717, 1724, 1727, 1728, 1725, 1740, }; static const YYACTIONTYPE yy_default[] = { - /* 0 */ 1623, 1623, 1623, 1453, 1223, 1332, 1223, 1223, 1223, 1453, - /* 10 */ 1453, 1453, 1223, 1362, 1362, 1506, 1254, 1223, 1223, 1223, - /* 20 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1452, 1223, 1223, - /* 30 */ 1223, 1223, 1541, 1541, 1223, 1223, 1223, 1223, 1223, 1223, - /* 40 */ 1223, 1223, 1371, 1223, 1378, 1223, 1223, 1223, 1223, 1223, - /* 50 */ 1454, 1455, 1223, 1223, 1223, 1505, 1507, 1470, 1385, 1384, - /* 60 */ 1383, 1382, 1488, 1349, 1376, 1369, 1373, 1448, 1449, 1447, - /* 70 */ 1451, 1455, 1454, 1223, 1372, 1419, 1433, 1418, 1223, 1223, - /* 80 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 90 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 100 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 110 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 120 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1427, 1432, 1438, - /* 130 */ 1431, 1428, 1421, 1420, 1422, 1223, 1423, 1223, 1223, 1223, - /* 140 */ 1244, 1296, 1223, 1223, 1223, 1223, 1223, 1525, 1524, 1223, - /* 150 */ 1223, 1254, 1413, 1412, 1424, 1425, 1435, 1434, 1513, 1576, - /* 160 */ 1575, 1471, 1223, 1223, 1223, 1223, 1223, 1223, 1541, 1223, - /* 170 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 180 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 190 */ 1223, 1541, 1541, 1223, 1254, 1541, 1541, 1250, 1250, 1356, - /* 200 */ 1223, 1520, 1323, 1323, 1323, 1323, 1332, 1323, 1223, 1223, - /* 210 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 220 */ 1223, 1223, 1223, 1510, 1508, 1223, 1223, 1223, 1223, 1223, - /* 230 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 240 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 250 */ 1223, 1223, 1328, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 260 */ 1223, 1223, 1223, 1223, 1570, 1223, 1483, 1310, 1328, 1328, - /* 270 */ 1328, 1328, 1330, 1311, 1309, 1322, 1255, 1230, 1615, 1388, - /* 280 */ 1377, 1329, 1351, 1377, 1351, 1612, 1375, 1388, 1388, 1375, - /* 290 */ 1388, 1329, 1612, 1271, 1592, 1266, 1362, 1362, 1362, 1351, - /* 300 */ 1356, 1356, 1450, 1329, 1322, 1223, 1615, 1615, 1337, 1337, - /* 310 */ 1614, 1614, 1337, 1471, 1599, 1397, 1299, 1305, 1305, 1305, - /* 320 */ 1305, 1337, 1241, 1375, 1599, 1599, 1375, 1397, 1299, 1375, - /* 330 */ 1299, 1375, 1337, 1241, 1487, 1609, 1337, 1241, 1461, 1337, - /* 340 */ 1241, 1337, 1241, 1461, 1297, 1297, 1297, 1286, 1223, 1223, - /* 350 */ 1461, 1297, 1271, 1297, 1286, 1297, 1297, 1559, 1223, 1465, - /* 360 */ 1465, 1461, 1355, 1350, 1355, 1350, 1355, 1350, 1355, 1350, - /* 370 */ 1337, 1551, 1551, 1365, 1365, 1370, 1356, 1456, 1337, 1223, - /* 380 */ 1370, 1368, 1366, 1375, 1247, 1289, 1573, 1573, 1569, 1569, - /* 390 */ 1569, 1620, 1620, 1520, 1585, 1254, 1254, 1254, 1254, 1585, - /* 400 */ 1273, 1273, 1255, 1255, 1254, 1585, 1223, 1223, 1223, 1223, - /* 410 */ 1223, 1223, 1580, 1223, 1515, 1472, 1341, 1223, 1223, 1223, - /* 420 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 430 */ 1223, 1526, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 440 */ 1223, 1223, 1402, 1223, 1226, 1517, 1223, 1223, 1223, 1223, - /* 450 */ 1223, 1223, 1223, 1223, 1379, 1380, 1342, 1223, 1223, 1223, - /* 460 */ 1223, 1223, 1223, 1223, 1394, 1223, 1223, 1223, 1389, 1223, - /* 470 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1611, 1223, 1223, - /* 480 */ 1223, 1223, 1223, 1223, 1486, 1485, 1223, 1223, 1339, 1223, - /* 490 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 500 */ 1223, 1223, 1269, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 510 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 520 */ 1223, 1223, 1223, 1223, 1223, 1223, 1367, 1223, 1223, 1223, - /* 530 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 540 */ 1223, 1556, 1357, 1223, 1223, 1602, 1223, 1223, 1223, 1223, - /* 550 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, - /* 560 */ 1596, 1313, 1404, 1223, 1403, 1407, 1223, 1235, 1223, 1223, + /* 0 */ 1647, 1647, 1647, 1475, 1240, 1351, 1240, 1240, 1240, 1475, + /* 10 */ 1475, 1475, 1240, 1381, 1381, 1528, 1273, 1240, 1240, 1240, + /* 20 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1474, 1240, 1240, + /* 30 */ 1240, 1240, 1563, 1563, 1240, 1240, 1240, 1240, 1240, 1240, + /* 40 */ 1240, 1240, 1390, 1240, 1397, 1240, 1240, 1240, 1240, 1240, + /* 50 */ 1476, 1477, 1240, 1240, 1240, 1527, 1529, 1492, 1404, 1403, + /* 60 */ 1402, 1401, 1510, 1369, 1395, 1388, 1392, 1470, 1471, 1469, + /* 70 */ 1473, 1477, 1476, 1240, 1391, 1438, 1454, 1437, 1240, 1240, + /* 80 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, + /* 90 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, + /* 100 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, + /* 110 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, + /* 120 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, + /* 130 */ 1446, 1453, 1452, 1451, 1460, 1450, 1447, 1440, 1439, 1441, + /* 140 */ 1442, 1240, 1240, 1264, 1240, 1240, 1261, 1315, 1240, 1240, + /* 150 */ 1240, 1240, 1240, 1547, 1546, 1240, 1443, 1240, 1273, 1432, + /* 160 */ 1431, 1457, 1444, 1456, 1455, 1535, 1599, 1598, 1493, 1240, + /* 170 */ 1240, 1240, 1240, 1240, 1240, 1563, 1240, 1240, 1240, 1240, + /* 180 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, + /* 190 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1371, + /* 200 */ 1563, 1563, 1240, 1273, 1563, 1563, 1372, 1372, 1269, 1269, + /* 210 */ 1375, 1240, 1542, 1342, 1342, 1342, 1342, 1351, 1342, 1240, + /* 220 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, + /* 230 */ 1240, 1240, 1240, 1240, 1532, 1530, 1240, 1240, 1240, 1240, + /* 240 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, + /* 250 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, + /* 260 */ 1240, 1240, 1240, 1347, 1240, 1240, 1240, 1240, 1240, 1240, + /* 270 */ 1240, 1240, 1240, 1240, 1240, 1592, 1240, 1505, 1329, 1347, + /* 280 */ 1347, 1347, 1347, 1349, 1330, 1328, 1341, 1274, 1247, 1639, + /* 290 */ 1407, 1396, 1348, 1396, 1636, 1394, 1407, 1407, 1394, 1407, + /* 300 */ 1348, 1636, 1290, 1615, 1285, 1381, 1381, 1381, 1371, 1371, + /* 310 */ 1371, 1371, 1375, 1375, 1472, 1348, 1341, 1240, 1639, 1639, + /* 320 */ 1357, 1357, 1638, 1638, 1357, 1493, 1623, 1416, 1318, 1324, + /* 330 */ 1324, 1324, 1324, 1357, 1258, 1394, 1623, 1623, 1394, 1416, + /* 340 */ 1318, 1394, 1318, 1394, 1357, 1258, 1509, 1633, 1357, 1258, + /* 350 */ 1483, 1357, 1258, 1357, 1258, 1483, 1316, 1316, 1316, 1305, + /* 360 */ 1240, 1240, 1483, 1316, 1290, 1316, 1305, 1316, 1316, 1581, + /* 370 */ 1240, 1487, 1487, 1483, 1357, 1573, 1573, 1384, 1384, 1389, + /* 380 */ 1375, 1478, 1357, 1240, 1389, 1387, 1385, 1394, 1308, 1595, + /* 390 */ 1595, 1591, 1591, 1591, 1644, 1644, 1542, 1608, 1273, 1273, + /* 400 */ 1273, 1273, 1608, 1292, 1292, 1274, 1274, 1273, 1608, 1240, + /* 410 */ 1240, 1240, 1240, 1240, 1240, 1603, 1240, 1537, 1494, 1361, + /* 420 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, + /* 430 */ 1240, 1240, 1240, 1240, 1548, 1240, 1240, 1240, 1240, 1240, + /* 440 */ 1240, 1240, 1240, 1240, 1240, 1421, 1240, 1243, 1539, 1240, + /* 450 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1398, 1399, 1362, + /* 460 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1413, 1240, 1240, + /* 470 */ 1240, 1408, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, + /* 480 */ 1635, 1240, 1240, 1240, 1240, 1240, 1240, 1508, 1507, 1240, + /* 490 */ 1240, 1359, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, + /* 500 */ 1240, 1240, 1240, 1240, 1240, 1288, 1240, 1240, 1240, 1240, + /* 510 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, + /* 520 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1386, + /* 530 */ 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, + /* 540 */ 1240, 1240, 1240, 1240, 1578, 1376, 1240, 1240, 1240, 1240, + /* 550 */ 1626, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, + /* 560 */ 1240, 1240, 1240, 1240, 1240, 1619, 1332, 1423, 1240, 1422, + /* 570 */ 1426, 1262, 1240, 1252, 1240, 1240, }; /********** End of lemon-generated parsing tables *****************************/ @@ -158202,8 +164739,8 @@ static const YYCODETYPE yyFallback[] = { 0, /* LP => nothing */ 0, /* RP => nothing */ 0, /* AS => nothing */ - 59, /* WITHOUT => ID */ 0, /* COMMA => nothing */ + 59, /* WITHOUT => ID */ 59, /* ABORT => ID */ 59, /* ACTION => ID */ 59, /* AFTER => ID */ @@ -158289,6 +164826,7 @@ static const YYCODETYPE yyFallback[] = { 0, /* SLASH => nothing */ 0, /* REM => nothing */ 0, /* CONCAT => nothing */ + 0, /* PTR => nothing */ 0, /* COLLATE => nothing */ 0, /* BITNOT => nothing */ 0, /* ON => nothing */ @@ -158358,6 +164896,7 @@ static const YYCODETYPE yyFallback[] = { 0, /* IF_NULL_ROW => nothing */ 0, /* ASTERISK => nothing */ 0, /* SPAN => nothing */ + 0, /* ERROR => nothing */ 0, /* SPACE => nothing */ 0, /* ILLEGAL => nothing */ }; @@ -158411,9 +164950,9 @@ struct yyParser { }; typedef struct yyParser yyParser; +/* #include */ #ifndef NDEBUG /* #include */ -/* #include */ static FILE *yyTraceFILE = 0; static char *yyTracePrompt = 0; #endif /* NDEBUG */ @@ -158473,8 +165012,8 @@ static const char *const yyTokenName[] = { /* 22 */ "LP", /* 23 */ "RP", /* 24 */ "AS", - /* 25 */ "WITHOUT", - /* 26 */ "COMMA", + /* 25 */ "COMMA", + /* 26 */ "WITHOUT", /* 27 */ "ABORT", /* 28 */ "ACTION", /* 29 */ "AFTER", @@ -158560,210 +165099,213 @@ static const char *const yyTokenName[] = { /* 109 */ "SLASH", /* 110 */ "REM", /* 111 */ "CONCAT", - /* 112 */ "COLLATE", - /* 113 */ "BITNOT", - /* 114 */ "ON", - /* 115 */ "INDEXED", - /* 116 */ "STRING", - /* 117 */ "JOIN_KW", - /* 118 */ "CONSTRAINT", - /* 119 */ "DEFAULT", - /* 120 */ "NULL", - /* 121 */ "PRIMARY", - /* 122 */ "UNIQUE", - /* 123 */ "CHECK", - /* 124 */ "REFERENCES", - /* 125 */ "AUTOINCR", - /* 126 */ "INSERT", - /* 127 */ "DELETE", - /* 128 */ "UPDATE", - /* 129 */ "SET", - /* 130 */ "DEFERRABLE", - /* 131 */ "FOREIGN", - /* 132 */ "DROP", - /* 133 */ "UNION", - /* 134 */ "ALL", - /* 135 */ "EXCEPT", - /* 136 */ "INTERSECT", - /* 137 */ "SELECT", - /* 138 */ "VALUES", - /* 139 */ "DISTINCT", - /* 140 */ "DOT", - /* 141 */ "FROM", - /* 142 */ "JOIN", - /* 143 */ "USING", - /* 144 */ "ORDER", - /* 145 */ "GROUP", - /* 146 */ "HAVING", - /* 147 */ "LIMIT", - /* 148 */ "WHERE", - /* 149 */ "RETURNING", - /* 150 */ "INTO", - /* 151 */ "NOTHING", - /* 152 */ "FLOAT", - /* 153 */ "BLOB", - /* 154 */ "INTEGER", - /* 155 */ "VARIABLE", - /* 156 */ "CASE", - /* 157 */ "WHEN", - /* 158 */ "THEN", - /* 159 */ "ELSE", - /* 160 */ "INDEX", - /* 161 */ "ALTER", - /* 162 */ "ADD", - /* 163 */ "WINDOW", - /* 164 */ "OVER", - /* 165 */ "FILTER", - /* 166 */ "COLUMN", - /* 167 */ "AGG_FUNCTION", - /* 168 */ "AGG_COLUMN", - /* 169 */ "TRUEFALSE", - /* 170 */ "ISNOT", - /* 171 */ "FUNCTION", - /* 172 */ "UMINUS", - /* 173 */ "UPLUS", - /* 174 */ "TRUTH", - /* 175 */ "REGISTER", - /* 176 */ "VECTOR", - /* 177 */ "SELECT_COLUMN", - /* 178 */ "IF_NULL_ROW", - /* 179 */ "ASTERISK", - /* 180 */ "SPAN", - /* 181 */ "SPACE", - /* 182 */ "ILLEGAL", - /* 183 */ "input", - /* 184 */ "cmdlist", - /* 185 */ "ecmd", - /* 186 */ "cmdx", - /* 187 */ "explain", - /* 188 */ "cmd", - /* 189 */ "transtype", - /* 190 */ "trans_opt", - /* 191 */ "nm", - /* 192 */ "savepoint_opt", - /* 193 */ "create_table", - /* 194 */ "create_table_args", - /* 195 */ "createkw", - /* 196 */ "temp", - /* 197 */ "ifnotexists", - /* 198 */ "dbnm", - /* 199 */ "columnlist", - /* 200 */ "conslist_opt", - /* 201 */ "table_options", - /* 202 */ "select", - /* 203 */ "columnname", - /* 204 */ "carglist", - /* 205 */ "typetoken", - /* 206 */ "typename", - /* 207 */ "signed", - /* 208 */ "plus_num", - /* 209 */ "minus_num", - /* 210 */ "scanpt", - /* 211 */ "scantok", - /* 212 */ "ccons", - /* 213 */ "term", - /* 214 */ "expr", - /* 215 */ "onconf", - /* 216 */ "sortorder", - /* 217 */ "autoinc", - /* 218 */ "eidlist_opt", - /* 219 */ "refargs", - /* 220 */ "defer_subclause", - /* 221 */ "generated", - /* 222 */ "refarg", - /* 223 */ "refact", - /* 224 */ "init_deferred_pred_opt", - /* 225 */ "conslist", - /* 226 */ "tconscomma", - /* 227 */ "tcons", - /* 228 */ "sortlist", - /* 229 */ "eidlist", - /* 230 */ "defer_subclause_opt", - /* 231 */ "orconf", - /* 232 */ "resolvetype", - /* 233 */ "raisetype", - /* 234 */ "ifexists", - /* 235 */ "fullname", - /* 236 */ "selectnowith", - /* 237 */ "oneselect", - /* 238 */ "wqlist", - /* 239 */ "multiselect_op", - /* 240 */ "distinct", - /* 241 */ "selcollist", - /* 242 */ "from", - /* 243 */ "where_opt", - /* 244 */ "groupby_opt", - /* 245 */ "having_opt", - /* 246 */ "orderby_opt", - /* 247 */ "limit_opt", - /* 248 */ "window_clause", - /* 249 */ "values", - /* 250 */ "nexprlist", - /* 251 */ "sclp", - /* 252 */ "as", - /* 253 */ "seltablist", - /* 254 */ "stl_prefix", - /* 255 */ "joinop", - /* 256 */ "indexed_opt", - /* 257 */ "on_opt", - /* 258 */ "using_opt", - /* 259 */ "exprlist", - /* 260 */ "xfullname", - /* 261 */ "idlist", - /* 262 */ "nulls", - /* 263 */ "with", - /* 264 */ "where_opt_ret", - /* 265 */ "setlist", - /* 266 */ "insert_cmd", - /* 267 */ "idlist_opt", - /* 268 */ "upsert", - /* 269 */ "returning", - /* 270 */ "filter_over", - /* 271 */ "likeop", - /* 272 */ "between_op", - /* 273 */ "in_op", - /* 274 */ "paren_exprlist", - /* 275 */ "case_operand", - /* 276 */ "case_exprlist", - /* 277 */ "case_else", - /* 278 */ "uniqueflag", - /* 279 */ "collate", - /* 280 */ "vinto", - /* 281 */ "nmnum", - /* 282 */ "trigger_decl", - /* 283 */ "trigger_cmd_list", - /* 284 */ "trigger_time", - /* 285 */ "trigger_event", - /* 286 */ "foreach_clause", - /* 287 */ "when_clause", - /* 288 */ "trigger_cmd", - /* 289 */ "trnm", - /* 290 */ "tridxby", - /* 291 */ "database_kw_opt", - /* 292 */ "key_opt", - /* 293 */ "add_column_fullname", - /* 294 */ "kwcolumn_opt", - /* 295 */ "create_vtab", - /* 296 */ "vtabarglist", - /* 297 */ "vtabarg", - /* 298 */ "vtabargtoken", - /* 299 */ "lp", - /* 300 */ "anylist", - /* 301 */ "wqitem", - /* 302 */ "wqas", - /* 303 */ "windowdefn_list", - /* 304 */ "windowdefn", - /* 305 */ "window", - /* 306 */ "frame_opt", - /* 307 */ "part_opt", - /* 308 */ "filter_clause", - /* 309 */ "over_clause", - /* 310 */ "range_or_rows", - /* 311 */ "frame_bound", - /* 312 */ "frame_bound_s", - /* 313 */ "frame_bound_e", - /* 314 */ "frame_exclude_opt", - /* 315 */ "frame_exclude", + /* 112 */ "PTR", + /* 113 */ "COLLATE", + /* 114 */ "BITNOT", + /* 115 */ "ON", + /* 116 */ "INDEXED", + /* 117 */ "STRING", + /* 118 */ "JOIN_KW", + /* 119 */ "CONSTRAINT", + /* 120 */ "DEFAULT", + /* 121 */ "NULL", + /* 122 */ "PRIMARY", + /* 123 */ "UNIQUE", + /* 124 */ "CHECK", + /* 125 */ "REFERENCES", + /* 126 */ "AUTOINCR", + /* 127 */ "INSERT", + /* 128 */ "DELETE", + /* 129 */ "UPDATE", + /* 130 */ "SET", + /* 131 */ "DEFERRABLE", + /* 132 */ "FOREIGN", + /* 133 */ "DROP", + /* 134 */ "UNION", + /* 135 */ "ALL", + /* 136 */ "EXCEPT", + /* 137 */ "INTERSECT", + /* 138 */ "SELECT", + /* 139 */ "VALUES", + /* 140 */ "DISTINCT", + /* 141 */ "DOT", + /* 142 */ "FROM", + /* 143 */ "JOIN", + /* 144 */ "USING", + /* 145 */ "ORDER", + /* 146 */ "GROUP", + /* 147 */ "HAVING", + /* 148 */ "LIMIT", + /* 149 */ "WHERE", + /* 150 */ "RETURNING", + /* 151 */ "INTO", + /* 152 */ "NOTHING", + /* 153 */ "FLOAT", + /* 154 */ "BLOB", + /* 155 */ "INTEGER", + /* 156 */ "VARIABLE", + /* 157 */ "CASE", + /* 158 */ "WHEN", + /* 159 */ "THEN", + /* 160 */ "ELSE", + /* 161 */ "INDEX", + /* 162 */ "ALTER", + /* 163 */ "ADD", + /* 164 */ "WINDOW", + /* 165 */ "OVER", + /* 166 */ "FILTER", + /* 167 */ "COLUMN", + /* 168 */ "AGG_FUNCTION", + /* 169 */ "AGG_COLUMN", + /* 170 */ "TRUEFALSE", + /* 171 */ "ISNOT", + /* 172 */ "FUNCTION", + /* 173 */ "UMINUS", + /* 174 */ "UPLUS", + /* 175 */ "TRUTH", + /* 176 */ "REGISTER", + /* 177 */ "VECTOR", + /* 178 */ "SELECT_COLUMN", + /* 179 */ "IF_NULL_ROW", + /* 180 */ "ASTERISK", + /* 181 */ "SPAN", + /* 182 */ "ERROR", + /* 183 */ "SPACE", + /* 184 */ "ILLEGAL", + /* 185 */ "input", + /* 186 */ "cmdlist", + /* 187 */ "ecmd", + /* 188 */ "cmdx", + /* 189 */ "explain", + /* 190 */ "cmd", + /* 191 */ "transtype", + /* 192 */ "trans_opt", + /* 193 */ "nm", + /* 194 */ "savepoint_opt", + /* 195 */ "create_table", + /* 196 */ "create_table_args", + /* 197 */ "createkw", + /* 198 */ "temp", + /* 199 */ "ifnotexists", + /* 200 */ "dbnm", + /* 201 */ "columnlist", + /* 202 */ "conslist_opt", + /* 203 */ "table_option_set", + /* 204 */ "select", + /* 205 */ "table_option", + /* 206 */ "columnname", + /* 207 */ "carglist", + /* 208 */ "typetoken", + /* 209 */ "typename", + /* 210 */ "signed", + /* 211 */ "plus_num", + /* 212 */ "minus_num", + /* 213 */ "scanpt", + /* 214 */ "scantok", + /* 215 */ "ccons", + /* 216 */ "term", + /* 217 */ "expr", + /* 218 */ "onconf", + /* 219 */ "sortorder", + /* 220 */ "autoinc", + /* 221 */ "eidlist_opt", + /* 222 */ "refargs", + /* 223 */ "defer_subclause", + /* 224 */ "generated", + /* 225 */ "refarg", + /* 226 */ "refact", + /* 227 */ "init_deferred_pred_opt", + /* 228 */ "conslist", + /* 229 */ "tconscomma", + /* 230 */ "tcons", + /* 231 */ "sortlist", + /* 232 */ "eidlist", + /* 233 */ "defer_subclause_opt", + /* 234 */ "orconf", + /* 235 */ "resolvetype", + /* 236 */ "raisetype", + /* 237 */ "ifexists", + /* 238 */ "fullname", + /* 239 */ "selectnowith", + /* 240 */ "oneselect", + /* 241 */ "wqlist", + /* 242 */ "multiselect_op", + /* 243 */ "distinct", + /* 244 */ "selcollist", + /* 245 */ "from", + /* 246 */ "where_opt", + /* 247 */ "groupby_opt", + /* 248 */ "having_opt", + /* 249 */ "orderby_opt", + /* 250 */ "limit_opt", + /* 251 */ "window_clause", + /* 252 */ "values", + /* 253 */ "nexprlist", + /* 254 */ "sclp", + /* 255 */ "as", + /* 256 */ "seltablist", + /* 257 */ "stl_prefix", + /* 258 */ "joinop", + /* 259 */ "on_using", + /* 260 */ "indexed_by", + /* 261 */ "exprlist", + /* 262 */ "xfullname", + /* 263 */ "idlist", + /* 264 */ "indexed_opt", + /* 265 */ "nulls", + /* 266 */ "with", + /* 267 */ "where_opt_ret", + /* 268 */ "setlist", + /* 269 */ "insert_cmd", + /* 270 */ "idlist_opt", + /* 271 */ "upsert", + /* 272 */ "returning", + /* 273 */ "filter_over", + /* 274 */ "likeop", + /* 275 */ "between_op", + /* 276 */ "in_op", + /* 277 */ "paren_exprlist", + /* 278 */ "case_operand", + /* 279 */ "case_exprlist", + /* 280 */ "case_else", + /* 281 */ "uniqueflag", + /* 282 */ "collate", + /* 283 */ "vinto", + /* 284 */ "nmnum", + /* 285 */ "trigger_decl", + /* 286 */ "trigger_cmd_list", + /* 287 */ "trigger_time", + /* 288 */ "trigger_event", + /* 289 */ "foreach_clause", + /* 290 */ "when_clause", + /* 291 */ "trigger_cmd", + /* 292 */ "trnm", + /* 293 */ "tridxby", + /* 294 */ "database_kw_opt", + /* 295 */ "key_opt", + /* 296 */ "add_column_fullname", + /* 297 */ "kwcolumn_opt", + /* 298 */ "create_vtab", + /* 299 */ "vtabarglist", + /* 300 */ "vtabarg", + /* 301 */ "vtabargtoken", + /* 302 */ "lp", + /* 303 */ "anylist", + /* 304 */ "wqitem", + /* 305 */ "wqas", + /* 306 */ "windowdefn_list", + /* 307 */ "windowdefn", + /* 308 */ "window", + /* 309 */ "frame_opt", + /* 310 */ "part_opt", + /* 311 */ "filter_clause", + /* 312 */ "over_clause", + /* 313 */ "range_or_rows", + /* 314 */ "frame_bound", + /* 315 */ "frame_bound_s", + /* 316 */ "frame_bound_e", + /* 317 */ "frame_exclude_opt", + /* 318 */ "frame_exclude", }; #endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */ @@ -158790,385 +165332,392 @@ static const char *const yyRuleName[] = { /* 16 */ "ifnotexists ::= IF NOT EXISTS", /* 17 */ "temp ::= TEMP", /* 18 */ "temp ::=", - /* 19 */ "create_table_args ::= LP columnlist conslist_opt RP table_options", + /* 19 */ "create_table_args ::= LP columnlist conslist_opt RP table_option_set", /* 20 */ "create_table_args ::= AS select", - /* 21 */ "table_options ::=", - /* 22 */ "table_options ::= WITHOUT nm", - /* 23 */ "columnname ::= nm typetoken", - /* 24 */ "typetoken ::=", - /* 25 */ "typetoken ::= typename LP signed RP", - /* 26 */ "typetoken ::= typename LP signed COMMA signed RP", - /* 27 */ "typename ::= typename ID|STRING", - /* 28 */ "scanpt ::=", - /* 29 */ "scantok ::=", - /* 30 */ "ccons ::= CONSTRAINT nm", - /* 31 */ "ccons ::= DEFAULT scantok term", - /* 32 */ "ccons ::= DEFAULT LP expr RP", - /* 33 */ "ccons ::= DEFAULT PLUS scantok term", - /* 34 */ "ccons ::= DEFAULT MINUS scantok term", - /* 35 */ "ccons ::= DEFAULT scantok ID|INDEXED", - /* 36 */ "ccons ::= NOT NULL onconf", - /* 37 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc", - /* 38 */ "ccons ::= UNIQUE onconf", - /* 39 */ "ccons ::= CHECK LP expr RP", - /* 40 */ "ccons ::= REFERENCES nm eidlist_opt refargs", - /* 41 */ "ccons ::= defer_subclause", - /* 42 */ "ccons ::= COLLATE ID|STRING", - /* 43 */ "generated ::= LP expr RP", - /* 44 */ "generated ::= LP expr RP ID", - /* 45 */ "autoinc ::=", - /* 46 */ "autoinc ::= AUTOINCR", - /* 47 */ "refargs ::=", - /* 48 */ "refargs ::= refargs refarg", - /* 49 */ "refarg ::= MATCH nm", - /* 50 */ "refarg ::= ON INSERT refact", - /* 51 */ "refarg ::= ON DELETE refact", - /* 52 */ "refarg ::= ON UPDATE refact", - /* 53 */ "refact ::= SET NULL", - /* 54 */ "refact ::= SET DEFAULT", - /* 55 */ "refact ::= CASCADE", - /* 56 */ "refact ::= RESTRICT", - /* 57 */ "refact ::= NO ACTION", - /* 58 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", - /* 59 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", - /* 60 */ "init_deferred_pred_opt ::=", - /* 61 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", - /* 62 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", - /* 63 */ "conslist_opt ::=", - /* 64 */ "tconscomma ::= COMMA", - /* 65 */ "tcons ::= CONSTRAINT nm", - /* 66 */ "tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf", - /* 67 */ "tcons ::= UNIQUE LP sortlist RP onconf", - /* 68 */ "tcons ::= CHECK LP expr RP onconf", - /* 69 */ "tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt", - /* 70 */ "defer_subclause_opt ::=", - /* 71 */ "onconf ::=", - /* 72 */ "onconf ::= ON CONFLICT resolvetype", - /* 73 */ "orconf ::=", - /* 74 */ "orconf ::= OR resolvetype", - /* 75 */ "resolvetype ::= IGNORE", - /* 76 */ "resolvetype ::= REPLACE", - /* 77 */ "cmd ::= DROP TABLE ifexists fullname", - /* 78 */ "ifexists ::= IF EXISTS", - /* 79 */ "ifexists ::=", - /* 80 */ "cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select", - /* 81 */ "cmd ::= DROP VIEW ifexists fullname", - /* 82 */ "cmd ::= select", - /* 83 */ "select ::= WITH wqlist selectnowith", - /* 84 */ "select ::= WITH RECURSIVE wqlist selectnowith", - /* 85 */ "select ::= selectnowith", - /* 86 */ "selectnowith ::= selectnowith multiselect_op oneselect", - /* 87 */ "multiselect_op ::= UNION", - /* 88 */ "multiselect_op ::= UNION ALL", - /* 89 */ "multiselect_op ::= EXCEPT|INTERSECT", - /* 90 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt", - /* 91 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt", - /* 92 */ "values ::= VALUES LP nexprlist RP", - /* 93 */ "values ::= values COMMA LP nexprlist RP", - /* 94 */ "distinct ::= DISTINCT", - /* 95 */ "distinct ::= ALL", - /* 96 */ "distinct ::=", - /* 97 */ "sclp ::=", - /* 98 */ "selcollist ::= sclp scanpt expr scanpt as", - /* 99 */ "selcollist ::= sclp scanpt STAR", - /* 100 */ "selcollist ::= sclp scanpt nm DOT STAR", - /* 101 */ "as ::= AS nm", - /* 102 */ "as ::=", - /* 103 */ "from ::=", - /* 104 */ "from ::= FROM seltablist", - /* 105 */ "stl_prefix ::= seltablist joinop", - /* 106 */ "stl_prefix ::=", - /* 107 */ "seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt", - /* 108 */ "seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt", - /* 109 */ "seltablist ::= stl_prefix LP select RP as on_opt using_opt", - /* 110 */ "seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt", - /* 111 */ "dbnm ::=", - /* 112 */ "dbnm ::= DOT nm", - /* 113 */ "fullname ::= nm", - /* 114 */ "fullname ::= nm DOT nm", - /* 115 */ "xfullname ::= nm", - /* 116 */ "xfullname ::= nm DOT nm", - /* 117 */ "xfullname ::= nm DOT nm AS nm", - /* 118 */ "xfullname ::= nm AS nm", - /* 119 */ "joinop ::= COMMA|JOIN", - /* 120 */ "joinop ::= JOIN_KW JOIN", - /* 121 */ "joinop ::= JOIN_KW nm JOIN", - /* 122 */ "joinop ::= JOIN_KW nm nm JOIN", - /* 123 */ "on_opt ::= ON expr", - /* 124 */ "on_opt ::=", - /* 125 */ "indexed_opt ::=", - /* 126 */ "indexed_opt ::= INDEXED BY nm", - /* 127 */ "indexed_opt ::= NOT INDEXED", - /* 128 */ "using_opt ::= USING LP idlist RP", - /* 129 */ "using_opt ::=", - /* 130 */ "orderby_opt ::=", - /* 131 */ "orderby_opt ::= ORDER BY sortlist", - /* 132 */ "sortlist ::= sortlist COMMA expr sortorder nulls", - /* 133 */ "sortlist ::= expr sortorder nulls", - /* 134 */ "sortorder ::= ASC", - /* 135 */ "sortorder ::= DESC", - /* 136 */ "sortorder ::=", - /* 137 */ "nulls ::= NULLS FIRST", - /* 138 */ "nulls ::= NULLS LAST", - /* 139 */ "nulls ::=", - /* 140 */ "groupby_opt ::=", - /* 141 */ "groupby_opt ::= GROUP BY nexprlist", - /* 142 */ "having_opt ::=", - /* 143 */ "having_opt ::= HAVING expr", - /* 144 */ "limit_opt ::=", - /* 145 */ "limit_opt ::= LIMIT expr", - /* 146 */ "limit_opt ::= LIMIT expr OFFSET expr", - /* 147 */ "limit_opt ::= LIMIT expr COMMA expr", - /* 148 */ "cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret", - /* 149 */ "where_opt ::=", - /* 150 */ "where_opt ::= WHERE expr", - /* 151 */ "where_opt_ret ::=", - /* 152 */ "where_opt_ret ::= WHERE expr", - /* 153 */ "where_opt_ret ::= RETURNING selcollist", - /* 154 */ "where_opt_ret ::= WHERE expr RETURNING selcollist", - /* 155 */ "cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret", - /* 156 */ "setlist ::= setlist COMMA nm EQ expr", - /* 157 */ "setlist ::= setlist COMMA LP idlist RP EQ expr", - /* 158 */ "setlist ::= nm EQ expr", - /* 159 */ "setlist ::= LP idlist RP EQ expr", - /* 160 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert", - /* 161 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning", - /* 162 */ "upsert ::=", - /* 163 */ "upsert ::= RETURNING selcollist", - /* 164 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert", - /* 165 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert", - /* 166 */ "upsert ::= ON CONFLICT DO NOTHING returning", - /* 167 */ "upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning", - /* 168 */ "returning ::= RETURNING selcollist", - /* 169 */ "insert_cmd ::= INSERT orconf", - /* 170 */ "insert_cmd ::= REPLACE", - /* 171 */ "idlist_opt ::=", - /* 172 */ "idlist_opt ::= LP idlist RP", - /* 173 */ "idlist ::= idlist COMMA nm", - /* 174 */ "idlist ::= nm", - /* 175 */ "expr ::= LP expr RP", - /* 176 */ "expr ::= ID|INDEXED", - /* 177 */ "expr ::= JOIN_KW", - /* 178 */ "expr ::= nm DOT nm", - /* 179 */ "expr ::= nm DOT nm DOT nm", - /* 180 */ "term ::= NULL|FLOAT|BLOB", - /* 181 */ "term ::= STRING", - /* 182 */ "term ::= INTEGER", - /* 183 */ "expr ::= VARIABLE", - /* 184 */ "expr ::= expr COLLATE ID|STRING", - /* 185 */ "expr ::= CAST LP expr AS typetoken RP", - /* 186 */ "expr ::= ID|INDEXED LP distinct exprlist RP", - /* 187 */ "expr ::= ID|INDEXED LP STAR RP", - /* 188 */ "expr ::= ID|INDEXED LP distinct exprlist RP filter_over", - /* 189 */ "expr ::= ID|INDEXED LP STAR RP filter_over", - /* 190 */ "term ::= CTIME_KW", - /* 191 */ "expr ::= LP nexprlist COMMA expr RP", - /* 192 */ "expr ::= expr AND expr", - /* 193 */ "expr ::= expr OR expr", - /* 194 */ "expr ::= expr LT|GT|GE|LE expr", - /* 195 */ "expr ::= expr EQ|NE expr", - /* 196 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", - /* 197 */ "expr ::= expr PLUS|MINUS expr", - /* 198 */ "expr ::= expr STAR|SLASH|REM expr", - /* 199 */ "expr ::= expr CONCAT expr", - /* 200 */ "likeop ::= NOT LIKE_KW|MATCH", - /* 201 */ "expr ::= expr likeop expr", - /* 202 */ "expr ::= expr likeop expr ESCAPE expr", - /* 203 */ "expr ::= expr ISNULL|NOTNULL", - /* 204 */ "expr ::= expr NOT NULL", - /* 205 */ "expr ::= expr IS expr", - /* 206 */ "expr ::= expr IS NOT expr", - /* 207 */ "expr ::= NOT expr", - /* 208 */ "expr ::= BITNOT expr", - /* 209 */ "expr ::= PLUS|MINUS expr", - /* 210 */ "between_op ::= BETWEEN", - /* 211 */ "between_op ::= NOT BETWEEN", - /* 212 */ "expr ::= expr between_op expr AND expr", - /* 213 */ "in_op ::= IN", - /* 214 */ "in_op ::= NOT IN", - /* 215 */ "expr ::= expr in_op LP exprlist RP", - /* 216 */ "expr ::= LP select RP", - /* 217 */ "expr ::= expr in_op LP select RP", - /* 218 */ "expr ::= expr in_op nm dbnm paren_exprlist", - /* 219 */ "expr ::= EXISTS LP select RP", - /* 220 */ "expr ::= CASE case_operand case_exprlist case_else END", - /* 221 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", - /* 222 */ "case_exprlist ::= WHEN expr THEN expr", - /* 223 */ "case_else ::= ELSE expr", - /* 224 */ "case_else ::=", - /* 225 */ "case_operand ::= expr", - /* 226 */ "case_operand ::=", - /* 227 */ "exprlist ::=", - /* 228 */ "nexprlist ::= nexprlist COMMA expr", - /* 229 */ "nexprlist ::= expr", - /* 230 */ "paren_exprlist ::=", - /* 231 */ "paren_exprlist ::= LP exprlist RP", - /* 232 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt", - /* 233 */ "uniqueflag ::= UNIQUE", - /* 234 */ "uniqueflag ::=", - /* 235 */ "eidlist_opt ::=", - /* 236 */ "eidlist_opt ::= LP eidlist RP", - /* 237 */ "eidlist ::= eidlist COMMA nm collate sortorder", - /* 238 */ "eidlist ::= nm collate sortorder", - /* 239 */ "collate ::=", - /* 240 */ "collate ::= COLLATE ID|STRING", - /* 241 */ "cmd ::= DROP INDEX ifexists fullname", - /* 242 */ "cmd ::= VACUUM vinto", - /* 243 */ "cmd ::= VACUUM nm vinto", - /* 244 */ "vinto ::= INTO expr", - /* 245 */ "vinto ::=", - /* 246 */ "cmd ::= PRAGMA nm dbnm", - /* 247 */ "cmd ::= PRAGMA nm dbnm EQ nmnum", - /* 248 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP", - /* 249 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", - /* 250 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP", - /* 251 */ "plus_num ::= PLUS INTEGER|FLOAT", - /* 252 */ "minus_num ::= MINUS INTEGER|FLOAT", - /* 253 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END", - /* 254 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause", - /* 255 */ "trigger_time ::= BEFORE|AFTER", - /* 256 */ "trigger_time ::= INSTEAD OF", - /* 257 */ "trigger_time ::=", - /* 258 */ "trigger_event ::= DELETE|INSERT", - /* 259 */ "trigger_event ::= UPDATE", - /* 260 */ "trigger_event ::= UPDATE OF idlist", - /* 261 */ "when_clause ::=", - /* 262 */ "when_clause ::= WHEN expr", - /* 263 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", - /* 264 */ "trigger_cmd_list ::= trigger_cmd SEMI", - /* 265 */ "trnm ::= nm DOT nm", - /* 266 */ "tridxby ::= INDEXED BY nm", - /* 267 */ "tridxby ::= NOT INDEXED", - /* 268 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt", - /* 269 */ "trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt", - /* 270 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt", - /* 271 */ "trigger_cmd ::= scanpt select scanpt", - /* 272 */ "expr ::= RAISE LP IGNORE RP", - /* 273 */ "expr ::= RAISE LP raisetype COMMA nm RP", - /* 274 */ "raisetype ::= ROLLBACK", - /* 275 */ "raisetype ::= ABORT", - /* 276 */ "raisetype ::= FAIL", - /* 277 */ "cmd ::= DROP TRIGGER ifexists fullname", - /* 278 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", - /* 279 */ "cmd ::= DETACH database_kw_opt expr", - /* 280 */ "key_opt ::=", - /* 281 */ "key_opt ::= KEY expr", - /* 282 */ "cmd ::= REINDEX", - /* 283 */ "cmd ::= REINDEX nm dbnm", - /* 284 */ "cmd ::= ANALYZE", - /* 285 */ "cmd ::= ANALYZE nm dbnm", - /* 286 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", - /* 287 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist", - /* 288 */ "cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm", - /* 289 */ "add_column_fullname ::= fullname", - /* 290 */ "cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm", - /* 291 */ "cmd ::= create_vtab", - /* 292 */ "cmd ::= create_vtab LP vtabarglist RP", - /* 293 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm", - /* 294 */ "vtabarg ::=", - /* 295 */ "vtabargtoken ::= ANY", - /* 296 */ "vtabargtoken ::= lp anylist RP", - /* 297 */ "lp ::= LP", - /* 298 */ "with ::= WITH wqlist", - /* 299 */ "with ::= WITH RECURSIVE wqlist", - /* 300 */ "wqas ::= AS", - /* 301 */ "wqas ::= AS MATERIALIZED", - /* 302 */ "wqas ::= AS NOT MATERIALIZED", - /* 303 */ "wqitem ::= nm eidlist_opt wqas LP select RP", - /* 304 */ "wqlist ::= wqitem", - /* 305 */ "wqlist ::= wqlist COMMA wqitem", - /* 306 */ "windowdefn_list ::= windowdefn", - /* 307 */ "windowdefn_list ::= windowdefn_list COMMA windowdefn", - /* 308 */ "windowdefn ::= nm AS LP window RP", - /* 309 */ "window ::= PARTITION BY nexprlist orderby_opt frame_opt", - /* 310 */ "window ::= nm PARTITION BY nexprlist orderby_opt frame_opt", - /* 311 */ "window ::= ORDER BY sortlist frame_opt", - /* 312 */ "window ::= nm ORDER BY sortlist frame_opt", - /* 313 */ "window ::= frame_opt", - /* 314 */ "window ::= nm frame_opt", - /* 315 */ "frame_opt ::=", - /* 316 */ "frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt", - /* 317 */ "frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt", - /* 318 */ "range_or_rows ::= RANGE|ROWS|GROUPS", - /* 319 */ "frame_bound_s ::= frame_bound", - /* 320 */ "frame_bound_s ::= UNBOUNDED PRECEDING", - /* 321 */ "frame_bound_e ::= frame_bound", - /* 322 */ "frame_bound_e ::= UNBOUNDED FOLLOWING", - /* 323 */ "frame_bound ::= expr PRECEDING|FOLLOWING", - /* 324 */ "frame_bound ::= CURRENT ROW", - /* 325 */ "frame_exclude_opt ::=", - /* 326 */ "frame_exclude_opt ::= EXCLUDE frame_exclude", - /* 327 */ "frame_exclude ::= NO OTHERS", - /* 328 */ "frame_exclude ::= CURRENT ROW", - /* 329 */ "frame_exclude ::= GROUP|TIES", - /* 330 */ "window_clause ::= WINDOW windowdefn_list", - /* 331 */ "filter_over ::= filter_clause over_clause", - /* 332 */ "filter_over ::= over_clause", - /* 333 */ "filter_over ::= filter_clause", - /* 334 */ "over_clause ::= OVER LP window RP", - /* 335 */ "over_clause ::= OVER nm", - /* 336 */ "filter_clause ::= FILTER LP WHERE expr RP", - /* 337 */ "input ::= cmdlist", - /* 338 */ "cmdlist ::= cmdlist ecmd", - /* 339 */ "cmdlist ::= ecmd", - /* 340 */ "ecmd ::= SEMI", - /* 341 */ "ecmd ::= cmdx SEMI", - /* 342 */ "ecmd ::= explain cmdx SEMI", - /* 343 */ "trans_opt ::=", - /* 344 */ "trans_opt ::= TRANSACTION", - /* 345 */ "trans_opt ::= TRANSACTION nm", - /* 346 */ "savepoint_opt ::= SAVEPOINT", - /* 347 */ "savepoint_opt ::=", - /* 348 */ "cmd ::= create_table create_table_args", - /* 349 */ "columnlist ::= columnlist COMMA columnname carglist", - /* 350 */ "columnlist ::= columnname carglist", - /* 351 */ "nm ::= ID|INDEXED", - /* 352 */ "nm ::= STRING", - /* 353 */ "nm ::= JOIN_KW", - /* 354 */ "typetoken ::= typename", - /* 355 */ "typename ::= ID|STRING", - /* 356 */ "signed ::= plus_num", - /* 357 */ "signed ::= minus_num", - /* 358 */ "carglist ::= carglist ccons", - /* 359 */ "carglist ::=", - /* 360 */ "ccons ::= NULL onconf", - /* 361 */ "ccons ::= GENERATED ALWAYS AS generated", - /* 362 */ "ccons ::= AS generated", - /* 363 */ "conslist_opt ::= COMMA conslist", - /* 364 */ "conslist ::= conslist tconscomma tcons", - /* 365 */ "conslist ::= tcons", - /* 366 */ "tconscomma ::=", - /* 367 */ "defer_subclause_opt ::= defer_subclause", - /* 368 */ "resolvetype ::= raisetype", - /* 369 */ "selectnowith ::= oneselect", - /* 370 */ "oneselect ::= values", - /* 371 */ "sclp ::= selcollist COMMA", - /* 372 */ "as ::= ID|STRING", - /* 373 */ "returning ::=", - /* 374 */ "expr ::= term", - /* 375 */ "likeop ::= LIKE_KW|MATCH", - /* 376 */ "exprlist ::= nexprlist", - /* 377 */ "nmnum ::= plus_num", - /* 378 */ "nmnum ::= nm", - /* 379 */ "nmnum ::= ON", - /* 380 */ "nmnum ::= DELETE", - /* 381 */ "nmnum ::= DEFAULT", - /* 382 */ "plus_num ::= INTEGER|FLOAT", - /* 383 */ "foreach_clause ::=", - /* 384 */ "foreach_clause ::= FOR EACH ROW", - /* 385 */ "trnm ::= nm", - /* 386 */ "tridxby ::=", - /* 387 */ "database_kw_opt ::= DATABASE", - /* 388 */ "database_kw_opt ::=", - /* 389 */ "kwcolumn_opt ::=", - /* 390 */ "kwcolumn_opt ::= COLUMNKW", - /* 391 */ "vtabarglist ::= vtabarg", - /* 392 */ "vtabarglist ::= vtabarglist COMMA vtabarg", - /* 393 */ "vtabarg ::= vtabarg vtabargtoken", - /* 394 */ "anylist ::=", - /* 395 */ "anylist ::= anylist LP anylist RP", - /* 396 */ "anylist ::= anylist ANY", - /* 397 */ "with ::=", + /* 21 */ "table_option_set ::=", + /* 22 */ "table_option_set ::= table_option_set COMMA table_option", + /* 23 */ "table_option ::= WITHOUT nm", + /* 24 */ "table_option ::= nm", + /* 25 */ "columnname ::= nm typetoken", + /* 26 */ "typetoken ::=", + /* 27 */ "typetoken ::= typename LP signed RP", + /* 28 */ "typetoken ::= typename LP signed COMMA signed RP", + /* 29 */ "typename ::= typename ID|STRING", + /* 30 */ "scanpt ::=", + /* 31 */ "scantok ::=", + /* 32 */ "ccons ::= CONSTRAINT nm", + /* 33 */ "ccons ::= DEFAULT scantok term", + /* 34 */ "ccons ::= DEFAULT LP expr RP", + /* 35 */ "ccons ::= DEFAULT PLUS scantok term", + /* 36 */ "ccons ::= DEFAULT MINUS scantok term", + /* 37 */ "ccons ::= DEFAULT scantok ID|INDEXED", + /* 38 */ "ccons ::= NOT NULL onconf", + /* 39 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc", + /* 40 */ "ccons ::= UNIQUE onconf", + /* 41 */ "ccons ::= CHECK LP expr RP", + /* 42 */ "ccons ::= REFERENCES nm eidlist_opt refargs", + /* 43 */ "ccons ::= defer_subclause", + /* 44 */ "ccons ::= COLLATE ID|STRING", + /* 45 */ "generated ::= LP expr RP", + /* 46 */ "generated ::= LP expr RP ID", + /* 47 */ "autoinc ::=", + /* 48 */ "autoinc ::= AUTOINCR", + /* 49 */ "refargs ::=", + /* 50 */ "refargs ::= refargs refarg", + /* 51 */ "refarg ::= MATCH nm", + /* 52 */ "refarg ::= ON INSERT refact", + /* 53 */ "refarg ::= ON DELETE refact", + /* 54 */ "refarg ::= ON UPDATE refact", + /* 55 */ "refact ::= SET NULL", + /* 56 */ "refact ::= SET DEFAULT", + /* 57 */ "refact ::= CASCADE", + /* 58 */ "refact ::= RESTRICT", + /* 59 */ "refact ::= NO ACTION", + /* 60 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", + /* 61 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", + /* 62 */ "init_deferred_pred_opt ::=", + /* 63 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", + /* 64 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", + /* 65 */ "conslist_opt ::=", + /* 66 */ "tconscomma ::= COMMA", + /* 67 */ "tcons ::= CONSTRAINT nm", + /* 68 */ "tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf", + /* 69 */ "tcons ::= UNIQUE LP sortlist RP onconf", + /* 70 */ "tcons ::= CHECK LP expr RP onconf", + /* 71 */ "tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt", + /* 72 */ "defer_subclause_opt ::=", + /* 73 */ "onconf ::=", + /* 74 */ "onconf ::= ON CONFLICT resolvetype", + /* 75 */ "orconf ::=", + /* 76 */ "orconf ::= OR resolvetype", + /* 77 */ "resolvetype ::= IGNORE", + /* 78 */ "resolvetype ::= REPLACE", + /* 79 */ "cmd ::= DROP TABLE ifexists fullname", + /* 80 */ "ifexists ::= IF EXISTS", + /* 81 */ "ifexists ::=", + /* 82 */ "cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select", + /* 83 */ "cmd ::= DROP VIEW ifexists fullname", + /* 84 */ "cmd ::= select", + /* 85 */ "select ::= WITH wqlist selectnowith", + /* 86 */ "select ::= WITH RECURSIVE wqlist selectnowith", + /* 87 */ "select ::= selectnowith", + /* 88 */ "selectnowith ::= selectnowith multiselect_op oneselect", + /* 89 */ "multiselect_op ::= UNION", + /* 90 */ "multiselect_op ::= UNION ALL", + /* 91 */ "multiselect_op ::= EXCEPT|INTERSECT", + /* 92 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt", + /* 93 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt", + /* 94 */ "values ::= VALUES LP nexprlist RP", + /* 95 */ "values ::= values COMMA LP nexprlist RP", + /* 96 */ "distinct ::= DISTINCT", + /* 97 */ "distinct ::= ALL", + /* 98 */ "distinct ::=", + /* 99 */ "sclp ::=", + /* 100 */ "selcollist ::= sclp scanpt expr scanpt as", + /* 101 */ "selcollist ::= sclp scanpt STAR", + /* 102 */ "selcollist ::= sclp scanpt nm DOT STAR", + /* 103 */ "as ::= AS nm", + /* 104 */ "as ::=", + /* 105 */ "from ::=", + /* 106 */ "from ::= FROM seltablist", + /* 107 */ "stl_prefix ::= seltablist joinop", + /* 108 */ "stl_prefix ::=", + /* 109 */ "seltablist ::= stl_prefix nm dbnm as on_using", + /* 110 */ "seltablist ::= stl_prefix nm dbnm as indexed_by on_using", + /* 111 */ "seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using", + /* 112 */ "seltablist ::= stl_prefix LP select RP as on_using", + /* 113 */ "seltablist ::= stl_prefix LP seltablist RP as on_using", + /* 114 */ "dbnm ::=", + /* 115 */ "dbnm ::= DOT nm", + /* 116 */ "fullname ::= nm", + /* 117 */ "fullname ::= nm DOT nm", + /* 118 */ "xfullname ::= nm", + /* 119 */ "xfullname ::= nm DOT nm", + /* 120 */ "xfullname ::= nm DOT nm AS nm", + /* 121 */ "xfullname ::= nm AS nm", + /* 122 */ "joinop ::= COMMA|JOIN", + /* 123 */ "joinop ::= JOIN_KW JOIN", + /* 124 */ "joinop ::= JOIN_KW nm JOIN", + /* 125 */ "joinop ::= JOIN_KW nm nm JOIN", + /* 126 */ "on_using ::= ON expr", + /* 127 */ "on_using ::= USING LP idlist RP", + /* 128 */ "on_using ::=", + /* 129 */ "indexed_opt ::=", + /* 130 */ "indexed_by ::= INDEXED BY nm", + /* 131 */ "indexed_by ::= NOT INDEXED", + /* 132 */ "orderby_opt ::=", + /* 133 */ "orderby_opt ::= ORDER BY sortlist", + /* 134 */ "sortlist ::= sortlist COMMA expr sortorder nulls", + /* 135 */ "sortlist ::= expr sortorder nulls", + /* 136 */ "sortorder ::= ASC", + /* 137 */ "sortorder ::= DESC", + /* 138 */ "sortorder ::=", + /* 139 */ "nulls ::= NULLS FIRST", + /* 140 */ "nulls ::= NULLS LAST", + /* 141 */ "nulls ::=", + /* 142 */ "groupby_opt ::=", + /* 143 */ "groupby_opt ::= GROUP BY nexprlist", + /* 144 */ "having_opt ::=", + /* 145 */ "having_opt ::= HAVING expr", + /* 146 */ "limit_opt ::=", + /* 147 */ "limit_opt ::= LIMIT expr", + /* 148 */ "limit_opt ::= LIMIT expr OFFSET expr", + /* 149 */ "limit_opt ::= LIMIT expr COMMA expr", + /* 150 */ "cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret", + /* 151 */ "where_opt ::=", + /* 152 */ "where_opt ::= WHERE expr", + /* 153 */ "where_opt_ret ::=", + /* 154 */ "where_opt_ret ::= WHERE expr", + /* 155 */ "where_opt_ret ::= RETURNING selcollist", + /* 156 */ "where_opt_ret ::= WHERE expr RETURNING selcollist", + /* 157 */ "cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret", + /* 158 */ "setlist ::= setlist COMMA nm EQ expr", + /* 159 */ "setlist ::= setlist COMMA LP idlist RP EQ expr", + /* 160 */ "setlist ::= nm EQ expr", + /* 161 */ "setlist ::= LP idlist RP EQ expr", + /* 162 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert", + /* 163 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning", + /* 164 */ "upsert ::=", + /* 165 */ "upsert ::= RETURNING selcollist", + /* 166 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert", + /* 167 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert", + /* 168 */ "upsert ::= ON CONFLICT DO NOTHING returning", + /* 169 */ "upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning", + /* 170 */ "returning ::= RETURNING selcollist", + /* 171 */ "insert_cmd ::= INSERT orconf", + /* 172 */ "insert_cmd ::= REPLACE", + /* 173 */ "idlist_opt ::=", + /* 174 */ "idlist_opt ::= LP idlist RP", + /* 175 */ "idlist ::= idlist COMMA nm", + /* 176 */ "idlist ::= nm", + /* 177 */ "expr ::= LP expr RP", + /* 178 */ "expr ::= ID|INDEXED", + /* 179 */ "expr ::= JOIN_KW", + /* 180 */ "expr ::= nm DOT nm", + /* 181 */ "expr ::= nm DOT nm DOT nm", + /* 182 */ "term ::= NULL|FLOAT|BLOB", + /* 183 */ "term ::= STRING", + /* 184 */ "term ::= INTEGER", + /* 185 */ "expr ::= VARIABLE", + /* 186 */ "expr ::= expr COLLATE ID|STRING", + /* 187 */ "expr ::= CAST LP expr AS typetoken RP", + /* 188 */ "expr ::= ID|INDEXED LP distinct exprlist RP", + /* 189 */ "expr ::= ID|INDEXED LP STAR RP", + /* 190 */ "expr ::= ID|INDEXED LP distinct exprlist RP filter_over", + /* 191 */ "expr ::= ID|INDEXED LP STAR RP filter_over", + /* 192 */ "term ::= CTIME_KW", + /* 193 */ "expr ::= LP nexprlist COMMA expr RP", + /* 194 */ "expr ::= expr AND expr", + /* 195 */ "expr ::= expr OR expr", + /* 196 */ "expr ::= expr LT|GT|GE|LE expr", + /* 197 */ "expr ::= expr EQ|NE expr", + /* 198 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", + /* 199 */ "expr ::= expr PLUS|MINUS expr", + /* 200 */ "expr ::= expr STAR|SLASH|REM expr", + /* 201 */ "expr ::= expr CONCAT expr", + /* 202 */ "likeop ::= NOT LIKE_KW|MATCH", + /* 203 */ "expr ::= expr likeop expr", + /* 204 */ "expr ::= expr likeop expr ESCAPE expr", + /* 205 */ "expr ::= expr ISNULL|NOTNULL", + /* 206 */ "expr ::= expr NOT NULL", + /* 207 */ "expr ::= expr IS expr", + /* 208 */ "expr ::= expr IS NOT expr", + /* 209 */ "expr ::= expr IS NOT DISTINCT FROM expr", + /* 210 */ "expr ::= expr IS DISTINCT FROM expr", + /* 211 */ "expr ::= NOT expr", + /* 212 */ "expr ::= BITNOT expr", + /* 213 */ "expr ::= PLUS|MINUS expr", + /* 214 */ "expr ::= expr PTR expr", + /* 215 */ "between_op ::= BETWEEN", + /* 216 */ "between_op ::= NOT BETWEEN", + /* 217 */ "expr ::= expr between_op expr AND expr", + /* 218 */ "in_op ::= IN", + /* 219 */ "in_op ::= NOT IN", + /* 220 */ "expr ::= expr in_op LP exprlist RP", + /* 221 */ "expr ::= LP select RP", + /* 222 */ "expr ::= expr in_op LP select RP", + /* 223 */ "expr ::= expr in_op nm dbnm paren_exprlist", + /* 224 */ "expr ::= EXISTS LP select RP", + /* 225 */ "expr ::= CASE case_operand case_exprlist case_else END", + /* 226 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", + /* 227 */ "case_exprlist ::= WHEN expr THEN expr", + /* 228 */ "case_else ::= ELSE expr", + /* 229 */ "case_else ::=", + /* 230 */ "case_operand ::= expr", + /* 231 */ "case_operand ::=", + /* 232 */ "exprlist ::=", + /* 233 */ "nexprlist ::= nexprlist COMMA expr", + /* 234 */ "nexprlist ::= expr", + /* 235 */ "paren_exprlist ::=", + /* 236 */ "paren_exprlist ::= LP exprlist RP", + /* 237 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt", + /* 238 */ "uniqueflag ::= UNIQUE", + /* 239 */ "uniqueflag ::=", + /* 240 */ "eidlist_opt ::=", + /* 241 */ "eidlist_opt ::= LP eidlist RP", + /* 242 */ "eidlist ::= eidlist COMMA nm collate sortorder", + /* 243 */ "eidlist ::= nm collate sortorder", + /* 244 */ "collate ::=", + /* 245 */ "collate ::= COLLATE ID|STRING", + /* 246 */ "cmd ::= DROP INDEX ifexists fullname", + /* 247 */ "cmd ::= VACUUM vinto", + /* 248 */ "cmd ::= VACUUM nm vinto", + /* 249 */ "vinto ::= INTO expr", + /* 250 */ "vinto ::=", + /* 251 */ "cmd ::= PRAGMA nm dbnm", + /* 252 */ "cmd ::= PRAGMA nm dbnm EQ nmnum", + /* 253 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP", + /* 254 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", + /* 255 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP", + /* 256 */ "plus_num ::= PLUS INTEGER|FLOAT", + /* 257 */ "minus_num ::= MINUS INTEGER|FLOAT", + /* 258 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END", + /* 259 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause", + /* 260 */ "trigger_time ::= BEFORE|AFTER", + /* 261 */ "trigger_time ::= INSTEAD OF", + /* 262 */ "trigger_time ::=", + /* 263 */ "trigger_event ::= DELETE|INSERT", + /* 264 */ "trigger_event ::= UPDATE", + /* 265 */ "trigger_event ::= UPDATE OF idlist", + /* 266 */ "when_clause ::=", + /* 267 */ "when_clause ::= WHEN expr", + /* 268 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", + /* 269 */ "trigger_cmd_list ::= trigger_cmd SEMI", + /* 270 */ "trnm ::= nm DOT nm", + /* 271 */ "tridxby ::= INDEXED BY nm", + /* 272 */ "tridxby ::= NOT INDEXED", + /* 273 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt", + /* 274 */ "trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt", + /* 275 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt", + /* 276 */ "trigger_cmd ::= scanpt select scanpt", + /* 277 */ "expr ::= RAISE LP IGNORE RP", + /* 278 */ "expr ::= RAISE LP raisetype COMMA nm RP", + /* 279 */ "raisetype ::= ROLLBACK", + /* 280 */ "raisetype ::= ABORT", + /* 281 */ "raisetype ::= FAIL", + /* 282 */ "cmd ::= DROP TRIGGER ifexists fullname", + /* 283 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", + /* 284 */ "cmd ::= DETACH database_kw_opt expr", + /* 285 */ "key_opt ::=", + /* 286 */ "key_opt ::= KEY expr", + /* 287 */ "cmd ::= REINDEX", + /* 288 */ "cmd ::= REINDEX nm dbnm", + /* 289 */ "cmd ::= ANALYZE", + /* 290 */ "cmd ::= ANALYZE nm dbnm", + /* 291 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", + /* 292 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist", + /* 293 */ "cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm", + /* 294 */ "add_column_fullname ::= fullname", + /* 295 */ "cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm", + /* 296 */ "cmd ::= create_vtab", + /* 297 */ "cmd ::= create_vtab LP vtabarglist RP", + /* 298 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm", + /* 299 */ "vtabarg ::=", + /* 300 */ "vtabargtoken ::= ANY", + /* 301 */ "vtabargtoken ::= lp anylist RP", + /* 302 */ "lp ::= LP", + /* 303 */ "with ::= WITH wqlist", + /* 304 */ "with ::= WITH RECURSIVE wqlist", + /* 305 */ "wqas ::= AS", + /* 306 */ "wqas ::= AS MATERIALIZED", + /* 307 */ "wqas ::= AS NOT MATERIALIZED", + /* 308 */ "wqitem ::= nm eidlist_opt wqas LP select RP", + /* 309 */ "wqlist ::= wqitem", + /* 310 */ "wqlist ::= wqlist COMMA wqitem", + /* 311 */ "windowdefn_list ::= windowdefn", + /* 312 */ "windowdefn_list ::= windowdefn_list COMMA windowdefn", + /* 313 */ "windowdefn ::= nm AS LP window RP", + /* 314 */ "window ::= PARTITION BY nexprlist orderby_opt frame_opt", + /* 315 */ "window ::= nm PARTITION BY nexprlist orderby_opt frame_opt", + /* 316 */ "window ::= ORDER BY sortlist frame_opt", + /* 317 */ "window ::= nm ORDER BY sortlist frame_opt", + /* 318 */ "window ::= frame_opt", + /* 319 */ "window ::= nm frame_opt", + /* 320 */ "frame_opt ::=", + /* 321 */ "frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt", + /* 322 */ "frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt", + /* 323 */ "range_or_rows ::= RANGE|ROWS|GROUPS", + /* 324 */ "frame_bound_s ::= frame_bound", + /* 325 */ "frame_bound_s ::= UNBOUNDED PRECEDING", + /* 326 */ "frame_bound_e ::= frame_bound", + /* 327 */ "frame_bound_e ::= UNBOUNDED FOLLOWING", + /* 328 */ "frame_bound ::= expr PRECEDING|FOLLOWING", + /* 329 */ "frame_bound ::= CURRENT ROW", + /* 330 */ "frame_exclude_opt ::=", + /* 331 */ "frame_exclude_opt ::= EXCLUDE frame_exclude", + /* 332 */ "frame_exclude ::= NO OTHERS", + /* 333 */ "frame_exclude ::= CURRENT ROW", + /* 334 */ "frame_exclude ::= GROUP|TIES", + /* 335 */ "window_clause ::= WINDOW windowdefn_list", + /* 336 */ "filter_over ::= filter_clause over_clause", + /* 337 */ "filter_over ::= over_clause", + /* 338 */ "filter_over ::= filter_clause", + /* 339 */ "over_clause ::= OVER LP window RP", + /* 340 */ "over_clause ::= OVER nm", + /* 341 */ "filter_clause ::= FILTER LP WHERE expr RP", + /* 342 */ "input ::= cmdlist", + /* 343 */ "cmdlist ::= cmdlist ecmd", + /* 344 */ "cmdlist ::= ecmd", + /* 345 */ "ecmd ::= SEMI", + /* 346 */ "ecmd ::= cmdx SEMI", + /* 347 */ "ecmd ::= explain cmdx SEMI", + /* 348 */ "trans_opt ::=", + /* 349 */ "trans_opt ::= TRANSACTION", + /* 350 */ "trans_opt ::= TRANSACTION nm", + /* 351 */ "savepoint_opt ::= SAVEPOINT", + /* 352 */ "savepoint_opt ::=", + /* 353 */ "cmd ::= create_table create_table_args", + /* 354 */ "table_option_set ::= table_option", + /* 355 */ "columnlist ::= columnlist COMMA columnname carglist", + /* 356 */ "columnlist ::= columnname carglist", + /* 357 */ "nm ::= ID|INDEXED", + /* 358 */ "nm ::= STRING", + /* 359 */ "nm ::= JOIN_KW", + /* 360 */ "typetoken ::= typename", + /* 361 */ "typename ::= ID|STRING", + /* 362 */ "signed ::= plus_num", + /* 363 */ "signed ::= minus_num", + /* 364 */ "carglist ::= carglist ccons", + /* 365 */ "carglist ::=", + /* 366 */ "ccons ::= NULL onconf", + /* 367 */ "ccons ::= GENERATED ALWAYS AS generated", + /* 368 */ "ccons ::= AS generated", + /* 369 */ "conslist_opt ::= COMMA conslist", + /* 370 */ "conslist ::= conslist tconscomma tcons", + /* 371 */ "conslist ::= tcons", + /* 372 */ "tconscomma ::=", + /* 373 */ "defer_subclause_opt ::= defer_subclause", + /* 374 */ "resolvetype ::= raisetype", + /* 375 */ "selectnowith ::= oneselect", + /* 376 */ "oneselect ::= values", + /* 377 */ "sclp ::= selcollist COMMA", + /* 378 */ "as ::= ID|STRING", + /* 379 */ "indexed_opt ::= indexed_by", + /* 380 */ "returning ::=", + /* 381 */ "expr ::= term", + /* 382 */ "likeop ::= LIKE_KW|MATCH", + /* 383 */ "exprlist ::= nexprlist", + /* 384 */ "nmnum ::= plus_num", + /* 385 */ "nmnum ::= nm", + /* 386 */ "nmnum ::= ON", + /* 387 */ "nmnum ::= DELETE", + /* 388 */ "nmnum ::= DEFAULT", + /* 389 */ "plus_num ::= INTEGER|FLOAT", + /* 390 */ "foreach_clause ::=", + /* 391 */ "foreach_clause ::= FOR EACH ROW", + /* 392 */ "trnm ::= nm", + /* 393 */ "tridxby ::=", + /* 394 */ "database_kw_opt ::= DATABASE", + /* 395 */ "database_kw_opt ::=", + /* 396 */ "kwcolumn_opt ::=", + /* 397 */ "kwcolumn_opt ::= COLUMNKW", + /* 398 */ "vtabarglist ::= vtabarg", + /* 399 */ "vtabarglist ::= vtabarglist COMMA vtabarg", + /* 400 */ "vtabarg ::= vtabarg vtabargtoken", + /* 401 */ "anylist ::=", + /* 402 */ "anylist ::= anylist LP anylist RP", + /* 403 */ "anylist ::= anylist ANY", + /* 404 */ "with ::=", }; #endif /* NDEBUG */ @@ -159294,99 +165843,97 @@ static void yy_destructor( ** inside the C code. */ /********* Begin destructor definitions ***************************************/ - case 202: /* select */ - case 236: /* selectnowith */ - case 237: /* oneselect */ - case 249: /* values */ + case 204: /* select */ + case 239: /* selectnowith */ + case 240: /* oneselect */ + case 252: /* values */ { -sqlite3SelectDelete(pParse->db, (yypminor->yy307)); +sqlite3SelectDelete(pParse->db, (yypminor->yy47)); } break; - case 213: /* term */ - case 214: /* expr */ - case 243: /* where_opt */ - case 245: /* having_opt */ - case 257: /* on_opt */ - case 264: /* where_opt_ret */ - case 275: /* case_operand */ - case 277: /* case_else */ - case 280: /* vinto */ - case 287: /* when_clause */ - case 292: /* key_opt */ - case 308: /* filter_clause */ + case 216: /* term */ + case 217: /* expr */ + case 246: /* where_opt */ + case 248: /* having_opt */ + case 267: /* where_opt_ret */ + case 278: /* case_operand */ + case 280: /* case_else */ + case 283: /* vinto */ + case 290: /* when_clause */ + case 295: /* key_opt */ + case 311: /* filter_clause */ { -sqlite3ExprDelete(pParse->db, (yypminor->yy602)); +sqlite3ExprDelete(pParse->db, (yypminor->yy528)); } break; - case 218: /* eidlist_opt */ - case 228: /* sortlist */ - case 229: /* eidlist */ - case 241: /* selcollist */ - case 244: /* groupby_opt */ - case 246: /* orderby_opt */ - case 250: /* nexprlist */ - case 251: /* sclp */ - case 259: /* exprlist */ - case 265: /* setlist */ - case 274: /* paren_exprlist */ - case 276: /* case_exprlist */ - case 307: /* part_opt */ + case 221: /* eidlist_opt */ + case 231: /* sortlist */ + case 232: /* eidlist */ + case 244: /* selcollist */ + case 247: /* groupby_opt */ + case 249: /* orderby_opt */ + case 253: /* nexprlist */ + case 254: /* sclp */ + case 261: /* exprlist */ + case 268: /* setlist */ + case 277: /* paren_exprlist */ + case 279: /* case_exprlist */ + case 310: /* part_opt */ { -sqlite3ExprListDelete(pParse->db, (yypminor->yy338)); +sqlite3ExprListDelete(pParse->db, (yypminor->yy322)); } break; - case 235: /* fullname */ - case 242: /* from */ - case 253: /* seltablist */ - case 254: /* stl_prefix */ - case 260: /* xfullname */ + case 238: /* fullname */ + case 245: /* from */ + case 256: /* seltablist */ + case 257: /* stl_prefix */ + case 262: /* xfullname */ { -sqlite3SrcListDelete(pParse->db, (yypminor->yy291)); +sqlite3SrcListDelete(pParse->db, (yypminor->yy131)); } break; - case 238: /* wqlist */ + case 241: /* wqlist */ { -sqlite3WithDelete(pParse->db, (yypminor->yy195)); +sqlite3WithDelete(pParse->db, (yypminor->yy521)); } break; - case 248: /* window_clause */ - case 303: /* windowdefn_list */ + case 251: /* window_clause */ + case 306: /* windowdefn_list */ { -sqlite3WindowListDelete(pParse->db, (yypminor->yy19)); +sqlite3WindowListDelete(pParse->db, (yypminor->yy41)); } break; - case 258: /* using_opt */ - case 261: /* idlist */ - case 267: /* idlist_opt */ + case 263: /* idlist */ + case 270: /* idlist_opt */ { -sqlite3IdListDelete(pParse->db, (yypminor->yy288)); +sqlite3IdListDelete(pParse->db, (yypminor->yy254)); } break; - case 270: /* filter_over */ - case 304: /* windowdefn */ - case 305: /* window */ - case 306: /* frame_opt */ - case 309: /* over_clause */ + case 273: /* filter_over */ + case 307: /* windowdefn */ + case 308: /* window */ + case 309: /* frame_opt */ + case 312: /* over_clause */ { -sqlite3WindowDelete(pParse->db, (yypminor->yy19)); +sqlite3WindowDelete(pParse->db, (yypminor->yy41)); } break; - case 283: /* trigger_cmd_list */ - case 288: /* trigger_cmd */ + case 286: /* trigger_cmd_list */ + case 291: /* trigger_cmd */ { -sqlite3DeleteTriggerStep(pParse->db, (yypminor->yy483)); +sqlite3DeleteTriggerStep(pParse->db, (yypminor->yy33)); } break; - case 285: /* trigger_event */ + case 288: /* trigger_event */ { -sqlite3IdListDelete(pParse->db, (yypminor->yy50).b); +sqlite3IdListDelete(pParse->db, (yypminor->yy180).b); } break; - case 311: /* frame_bound */ - case 312: /* frame_bound_s */ - case 313: /* frame_bound_e */ + case 314: /* frame_bound */ + case 315: /* frame_bound_s */ + case 316: /* frame_bound_e */ { -sqlite3ExprDelete(pParse->db, (yypminor->yy113).pExpr); +sqlite3ExprDelete(pParse->db, (yypminor->yy595).pExpr); } break; /********* End destructor definitions *****************************************/ @@ -159677,404 +166224,411 @@ static void yy_shift( /* For rule J, yyRuleInfoLhs[J] contains the symbol on the left-hand side ** of that rule */ static const YYCODETYPE yyRuleInfoLhs[] = { - 187, /* (0) explain ::= EXPLAIN */ - 187, /* (1) explain ::= EXPLAIN QUERY PLAN */ - 186, /* (2) cmdx ::= cmd */ - 188, /* (3) cmd ::= BEGIN transtype trans_opt */ - 189, /* (4) transtype ::= */ - 189, /* (5) transtype ::= DEFERRED */ - 189, /* (6) transtype ::= IMMEDIATE */ - 189, /* (7) transtype ::= EXCLUSIVE */ - 188, /* (8) cmd ::= COMMIT|END trans_opt */ - 188, /* (9) cmd ::= ROLLBACK trans_opt */ - 188, /* (10) cmd ::= SAVEPOINT nm */ - 188, /* (11) cmd ::= RELEASE savepoint_opt nm */ - 188, /* (12) cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */ - 193, /* (13) create_table ::= createkw temp TABLE ifnotexists nm dbnm */ - 195, /* (14) createkw ::= CREATE */ - 197, /* (15) ifnotexists ::= */ - 197, /* (16) ifnotexists ::= IF NOT EXISTS */ - 196, /* (17) temp ::= TEMP */ - 196, /* (18) temp ::= */ - 194, /* (19) create_table_args ::= LP columnlist conslist_opt RP table_options */ - 194, /* (20) create_table_args ::= AS select */ - 201, /* (21) table_options ::= */ - 201, /* (22) table_options ::= WITHOUT nm */ - 203, /* (23) columnname ::= nm typetoken */ - 205, /* (24) typetoken ::= */ - 205, /* (25) typetoken ::= typename LP signed RP */ - 205, /* (26) typetoken ::= typename LP signed COMMA signed RP */ - 206, /* (27) typename ::= typename ID|STRING */ - 210, /* (28) scanpt ::= */ - 211, /* (29) scantok ::= */ - 212, /* (30) ccons ::= CONSTRAINT nm */ - 212, /* (31) ccons ::= DEFAULT scantok term */ - 212, /* (32) ccons ::= DEFAULT LP expr RP */ - 212, /* (33) ccons ::= DEFAULT PLUS scantok term */ - 212, /* (34) ccons ::= DEFAULT MINUS scantok term */ - 212, /* (35) ccons ::= DEFAULT scantok ID|INDEXED */ - 212, /* (36) ccons ::= NOT NULL onconf */ - 212, /* (37) ccons ::= PRIMARY KEY sortorder onconf autoinc */ - 212, /* (38) ccons ::= UNIQUE onconf */ - 212, /* (39) ccons ::= CHECK LP expr RP */ - 212, /* (40) ccons ::= REFERENCES nm eidlist_opt refargs */ - 212, /* (41) ccons ::= defer_subclause */ - 212, /* (42) ccons ::= COLLATE ID|STRING */ - 221, /* (43) generated ::= LP expr RP */ - 221, /* (44) generated ::= LP expr RP ID */ - 217, /* (45) autoinc ::= */ - 217, /* (46) autoinc ::= AUTOINCR */ - 219, /* (47) refargs ::= */ - 219, /* (48) refargs ::= refargs refarg */ - 222, /* (49) refarg ::= MATCH nm */ - 222, /* (50) refarg ::= ON INSERT refact */ - 222, /* (51) refarg ::= ON DELETE refact */ - 222, /* (52) refarg ::= ON UPDATE refact */ - 223, /* (53) refact ::= SET NULL */ - 223, /* (54) refact ::= SET DEFAULT */ - 223, /* (55) refact ::= CASCADE */ - 223, /* (56) refact ::= RESTRICT */ - 223, /* (57) refact ::= NO ACTION */ - 220, /* (58) defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ - 220, /* (59) defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ - 224, /* (60) init_deferred_pred_opt ::= */ - 224, /* (61) init_deferred_pred_opt ::= INITIALLY DEFERRED */ - 224, /* (62) init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ - 200, /* (63) conslist_opt ::= */ - 226, /* (64) tconscomma ::= COMMA */ - 227, /* (65) tcons ::= CONSTRAINT nm */ - 227, /* (66) tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ - 227, /* (67) tcons ::= UNIQUE LP sortlist RP onconf */ - 227, /* (68) tcons ::= CHECK LP expr RP onconf */ - 227, /* (69) tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ - 230, /* (70) defer_subclause_opt ::= */ - 215, /* (71) onconf ::= */ - 215, /* (72) onconf ::= ON CONFLICT resolvetype */ - 231, /* (73) orconf ::= */ - 231, /* (74) orconf ::= OR resolvetype */ - 232, /* (75) resolvetype ::= IGNORE */ - 232, /* (76) resolvetype ::= REPLACE */ - 188, /* (77) cmd ::= DROP TABLE ifexists fullname */ - 234, /* (78) ifexists ::= IF EXISTS */ - 234, /* (79) ifexists ::= */ - 188, /* (80) cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ - 188, /* (81) cmd ::= DROP VIEW ifexists fullname */ - 188, /* (82) cmd ::= select */ - 202, /* (83) select ::= WITH wqlist selectnowith */ - 202, /* (84) select ::= WITH RECURSIVE wqlist selectnowith */ - 202, /* (85) select ::= selectnowith */ - 236, /* (86) selectnowith ::= selectnowith multiselect_op oneselect */ - 239, /* (87) multiselect_op ::= UNION */ - 239, /* (88) multiselect_op ::= UNION ALL */ - 239, /* (89) multiselect_op ::= EXCEPT|INTERSECT */ - 237, /* (90) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ - 237, /* (91) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ - 249, /* (92) values ::= VALUES LP nexprlist RP */ - 249, /* (93) values ::= values COMMA LP nexprlist RP */ - 240, /* (94) distinct ::= DISTINCT */ - 240, /* (95) distinct ::= ALL */ - 240, /* (96) distinct ::= */ - 251, /* (97) sclp ::= */ - 241, /* (98) selcollist ::= sclp scanpt expr scanpt as */ - 241, /* (99) selcollist ::= sclp scanpt STAR */ - 241, /* (100) selcollist ::= sclp scanpt nm DOT STAR */ - 252, /* (101) as ::= AS nm */ - 252, /* (102) as ::= */ - 242, /* (103) from ::= */ - 242, /* (104) from ::= FROM seltablist */ - 254, /* (105) stl_prefix ::= seltablist joinop */ - 254, /* (106) stl_prefix ::= */ - 253, /* (107) seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */ - 253, /* (108) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */ - 253, /* (109) seltablist ::= stl_prefix LP select RP as on_opt using_opt */ - 253, /* (110) seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */ - 198, /* (111) dbnm ::= */ - 198, /* (112) dbnm ::= DOT nm */ - 235, /* (113) fullname ::= nm */ - 235, /* (114) fullname ::= nm DOT nm */ - 260, /* (115) xfullname ::= nm */ - 260, /* (116) xfullname ::= nm DOT nm */ - 260, /* (117) xfullname ::= nm DOT nm AS nm */ - 260, /* (118) xfullname ::= nm AS nm */ - 255, /* (119) joinop ::= COMMA|JOIN */ - 255, /* (120) joinop ::= JOIN_KW JOIN */ - 255, /* (121) joinop ::= JOIN_KW nm JOIN */ - 255, /* (122) joinop ::= JOIN_KW nm nm JOIN */ - 257, /* (123) on_opt ::= ON expr */ - 257, /* (124) on_opt ::= */ - 256, /* (125) indexed_opt ::= */ - 256, /* (126) indexed_opt ::= INDEXED BY nm */ - 256, /* (127) indexed_opt ::= NOT INDEXED */ - 258, /* (128) using_opt ::= USING LP idlist RP */ - 258, /* (129) using_opt ::= */ - 246, /* (130) orderby_opt ::= */ - 246, /* (131) orderby_opt ::= ORDER BY sortlist */ - 228, /* (132) sortlist ::= sortlist COMMA expr sortorder nulls */ - 228, /* (133) sortlist ::= expr sortorder nulls */ - 216, /* (134) sortorder ::= ASC */ - 216, /* (135) sortorder ::= DESC */ - 216, /* (136) sortorder ::= */ - 262, /* (137) nulls ::= NULLS FIRST */ - 262, /* (138) nulls ::= NULLS LAST */ - 262, /* (139) nulls ::= */ - 244, /* (140) groupby_opt ::= */ - 244, /* (141) groupby_opt ::= GROUP BY nexprlist */ - 245, /* (142) having_opt ::= */ - 245, /* (143) having_opt ::= HAVING expr */ - 247, /* (144) limit_opt ::= */ - 247, /* (145) limit_opt ::= LIMIT expr */ - 247, /* (146) limit_opt ::= LIMIT expr OFFSET expr */ - 247, /* (147) limit_opt ::= LIMIT expr COMMA expr */ - 188, /* (148) cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */ - 243, /* (149) where_opt ::= */ - 243, /* (150) where_opt ::= WHERE expr */ - 264, /* (151) where_opt_ret ::= */ - 264, /* (152) where_opt_ret ::= WHERE expr */ - 264, /* (153) where_opt_ret ::= RETURNING selcollist */ - 264, /* (154) where_opt_ret ::= WHERE expr RETURNING selcollist */ - 188, /* (155) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */ - 265, /* (156) setlist ::= setlist COMMA nm EQ expr */ - 265, /* (157) setlist ::= setlist COMMA LP idlist RP EQ expr */ - 265, /* (158) setlist ::= nm EQ expr */ - 265, /* (159) setlist ::= LP idlist RP EQ expr */ - 188, /* (160) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ - 188, /* (161) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */ - 268, /* (162) upsert ::= */ - 268, /* (163) upsert ::= RETURNING selcollist */ - 268, /* (164) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */ - 268, /* (165) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */ - 268, /* (166) upsert ::= ON CONFLICT DO NOTHING returning */ - 268, /* (167) upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */ - 269, /* (168) returning ::= RETURNING selcollist */ - 266, /* (169) insert_cmd ::= INSERT orconf */ - 266, /* (170) insert_cmd ::= REPLACE */ - 267, /* (171) idlist_opt ::= */ - 267, /* (172) idlist_opt ::= LP idlist RP */ - 261, /* (173) idlist ::= idlist COMMA nm */ - 261, /* (174) idlist ::= nm */ - 214, /* (175) expr ::= LP expr RP */ - 214, /* (176) expr ::= ID|INDEXED */ - 214, /* (177) expr ::= JOIN_KW */ - 214, /* (178) expr ::= nm DOT nm */ - 214, /* (179) expr ::= nm DOT nm DOT nm */ - 213, /* (180) term ::= NULL|FLOAT|BLOB */ - 213, /* (181) term ::= STRING */ - 213, /* (182) term ::= INTEGER */ - 214, /* (183) expr ::= VARIABLE */ - 214, /* (184) expr ::= expr COLLATE ID|STRING */ - 214, /* (185) expr ::= CAST LP expr AS typetoken RP */ - 214, /* (186) expr ::= ID|INDEXED LP distinct exprlist RP */ - 214, /* (187) expr ::= ID|INDEXED LP STAR RP */ - 214, /* (188) expr ::= ID|INDEXED LP distinct exprlist RP filter_over */ - 214, /* (189) expr ::= ID|INDEXED LP STAR RP filter_over */ - 213, /* (190) term ::= CTIME_KW */ - 214, /* (191) expr ::= LP nexprlist COMMA expr RP */ - 214, /* (192) expr ::= expr AND expr */ - 214, /* (193) expr ::= expr OR expr */ - 214, /* (194) expr ::= expr LT|GT|GE|LE expr */ - 214, /* (195) expr ::= expr EQ|NE expr */ - 214, /* (196) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ - 214, /* (197) expr ::= expr PLUS|MINUS expr */ - 214, /* (198) expr ::= expr STAR|SLASH|REM expr */ - 214, /* (199) expr ::= expr CONCAT expr */ - 271, /* (200) likeop ::= NOT LIKE_KW|MATCH */ - 214, /* (201) expr ::= expr likeop expr */ - 214, /* (202) expr ::= expr likeop expr ESCAPE expr */ - 214, /* (203) expr ::= expr ISNULL|NOTNULL */ - 214, /* (204) expr ::= expr NOT NULL */ - 214, /* (205) expr ::= expr IS expr */ - 214, /* (206) expr ::= expr IS NOT expr */ - 214, /* (207) expr ::= NOT expr */ - 214, /* (208) expr ::= BITNOT expr */ - 214, /* (209) expr ::= PLUS|MINUS expr */ - 272, /* (210) between_op ::= BETWEEN */ - 272, /* (211) between_op ::= NOT BETWEEN */ - 214, /* (212) expr ::= expr between_op expr AND expr */ - 273, /* (213) in_op ::= IN */ - 273, /* (214) in_op ::= NOT IN */ - 214, /* (215) expr ::= expr in_op LP exprlist RP */ - 214, /* (216) expr ::= LP select RP */ - 214, /* (217) expr ::= expr in_op LP select RP */ - 214, /* (218) expr ::= expr in_op nm dbnm paren_exprlist */ - 214, /* (219) expr ::= EXISTS LP select RP */ - 214, /* (220) expr ::= CASE case_operand case_exprlist case_else END */ - 276, /* (221) case_exprlist ::= case_exprlist WHEN expr THEN expr */ - 276, /* (222) case_exprlist ::= WHEN expr THEN expr */ - 277, /* (223) case_else ::= ELSE expr */ - 277, /* (224) case_else ::= */ - 275, /* (225) case_operand ::= expr */ - 275, /* (226) case_operand ::= */ - 259, /* (227) exprlist ::= */ - 250, /* (228) nexprlist ::= nexprlist COMMA expr */ - 250, /* (229) nexprlist ::= expr */ - 274, /* (230) paren_exprlist ::= */ - 274, /* (231) paren_exprlist ::= LP exprlist RP */ - 188, /* (232) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ - 278, /* (233) uniqueflag ::= UNIQUE */ - 278, /* (234) uniqueflag ::= */ - 218, /* (235) eidlist_opt ::= */ - 218, /* (236) eidlist_opt ::= LP eidlist RP */ - 229, /* (237) eidlist ::= eidlist COMMA nm collate sortorder */ - 229, /* (238) eidlist ::= nm collate sortorder */ - 279, /* (239) collate ::= */ - 279, /* (240) collate ::= COLLATE ID|STRING */ - 188, /* (241) cmd ::= DROP INDEX ifexists fullname */ - 188, /* (242) cmd ::= VACUUM vinto */ - 188, /* (243) cmd ::= VACUUM nm vinto */ - 280, /* (244) vinto ::= INTO expr */ - 280, /* (245) vinto ::= */ - 188, /* (246) cmd ::= PRAGMA nm dbnm */ - 188, /* (247) cmd ::= PRAGMA nm dbnm EQ nmnum */ - 188, /* (248) cmd ::= PRAGMA nm dbnm LP nmnum RP */ - 188, /* (249) cmd ::= PRAGMA nm dbnm EQ minus_num */ - 188, /* (250) cmd ::= PRAGMA nm dbnm LP minus_num RP */ - 208, /* (251) plus_num ::= PLUS INTEGER|FLOAT */ - 209, /* (252) minus_num ::= MINUS INTEGER|FLOAT */ - 188, /* (253) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ - 282, /* (254) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ - 284, /* (255) trigger_time ::= BEFORE|AFTER */ - 284, /* (256) trigger_time ::= INSTEAD OF */ - 284, /* (257) trigger_time ::= */ - 285, /* (258) trigger_event ::= DELETE|INSERT */ - 285, /* (259) trigger_event ::= UPDATE */ - 285, /* (260) trigger_event ::= UPDATE OF idlist */ - 287, /* (261) when_clause ::= */ - 287, /* (262) when_clause ::= WHEN expr */ - 283, /* (263) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ - 283, /* (264) trigger_cmd_list ::= trigger_cmd SEMI */ - 289, /* (265) trnm ::= nm DOT nm */ - 290, /* (266) tridxby ::= INDEXED BY nm */ - 290, /* (267) tridxby ::= NOT INDEXED */ - 288, /* (268) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */ - 288, /* (269) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ - 288, /* (270) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */ - 288, /* (271) trigger_cmd ::= scanpt select scanpt */ - 214, /* (272) expr ::= RAISE LP IGNORE RP */ - 214, /* (273) expr ::= RAISE LP raisetype COMMA nm RP */ - 233, /* (274) raisetype ::= ROLLBACK */ - 233, /* (275) raisetype ::= ABORT */ - 233, /* (276) raisetype ::= FAIL */ - 188, /* (277) cmd ::= DROP TRIGGER ifexists fullname */ - 188, /* (278) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ - 188, /* (279) cmd ::= DETACH database_kw_opt expr */ - 292, /* (280) key_opt ::= */ - 292, /* (281) key_opt ::= KEY expr */ - 188, /* (282) cmd ::= REINDEX */ - 188, /* (283) cmd ::= REINDEX nm dbnm */ - 188, /* (284) cmd ::= ANALYZE */ - 188, /* (285) cmd ::= ANALYZE nm dbnm */ - 188, /* (286) cmd ::= ALTER TABLE fullname RENAME TO nm */ - 188, /* (287) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ - 188, /* (288) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */ - 293, /* (289) add_column_fullname ::= fullname */ - 188, /* (290) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ - 188, /* (291) cmd ::= create_vtab */ - 188, /* (292) cmd ::= create_vtab LP vtabarglist RP */ - 295, /* (293) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ - 297, /* (294) vtabarg ::= */ - 298, /* (295) vtabargtoken ::= ANY */ - 298, /* (296) vtabargtoken ::= lp anylist RP */ - 299, /* (297) lp ::= LP */ - 263, /* (298) with ::= WITH wqlist */ - 263, /* (299) with ::= WITH RECURSIVE wqlist */ - 302, /* (300) wqas ::= AS */ - 302, /* (301) wqas ::= AS MATERIALIZED */ - 302, /* (302) wqas ::= AS NOT MATERIALIZED */ - 301, /* (303) wqitem ::= nm eidlist_opt wqas LP select RP */ - 238, /* (304) wqlist ::= wqitem */ - 238, /* (305) wqlist ::= wqlist COMMA wqitem */ - 303, /* (306) windowdefn_list ::= windowdefn */ - 303, /* (307) windowdefn_list ::= windowdefn_list COMMA windowdefn */ - 304, /* (308) windowdefn ::= nm AS LP window RP */ - 305, /* (309) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ - 305, /* (310) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ - 305, /* (311) window ::= ORDER BY sortlist frame_opt */ - 305, /* (312) window ::= nm ORDER BY sortlist frame_opt */ - 305, /* (313) window ::= frame_opt */ - 305, /* (314) window ::= nm frame_opt */ - 306, /* (315) frame_opt ::= */ - 306, /* (316) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ - 306, /* (317) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ - 310, /* (318) range_or_rows ::= RANGE|ROWS|GROUPS */ - 312, /* (319) frame_bound_s ::= frame_bound */ - 312, /* (320) frame_bound_s ::= UNBOUNDED PRECEDING */ - 313, /* (321) frame_bound_e ::= frame_bound */ - 313, /* (322) frame_bound_e ::= UNBOUNDED FOLLOWING */ - 311, /* (323) frame_bound ::= expr PRECEDING|FOLLOWING */ - 311, /* (324) frame_bound ::= CURRENT ROW */ - 314, /* (325) frame_exclude_opt ::= */ - 314, /* (326) frame_exclude_opt ::= EXCLUDE frame_exclude */ - 315, /* (327) frame_exclude ::= NO OTHERS */ - 315, /* (328) frame_exclude ::= CURRENT ROW */ - 315, /* (329) frame_exclude ::= GROUP|TIES */ - 248, /* (330) window_clause ::= WINDOW windowdefn_list */ - 270, /* (331) filter_over ::= filter_clause over_clause */ - 270, /* (332) filter_over ::= over_clause */ - 270, /* (333) filter_over ::= filter_clause */ - 309, /* (334) over_clause ::= OVER LP window RP */ - 309, /* (335) over_clause ::= OVER nm */ - 308, /* (336) filter_clause ::= FILTER LP WHERE expr RP */ - 183, /* (337) input ::= cmdlist */ - 184, /* (338) cmdlist ::= cmdlist ecmd */ - 184, /* (339) cmdlist ::= ecmd */ - 185, /* (340) ecmd ::= SEMI */ - 185, /* (341) ecmd ::= cmdx SEMI */ - 185, /* (342) ecmd ::= explain cmdx SEMI */ - 190, /* (343) trans_opt ::= */ - 190, /* (344) trans_opt ::= TRANSACTION */ - 190, /* (345) trans_opt ::= TRANSACTION nm */ - 192, /* (346) savepoint_opt ::= SAVEPOINT */ - 192, /* (347) savepoint_opt ::= */ - 188, /* (348) cmd ::= create_table create_table_args */ - 199, /* (349) columnlist ::= columnlist COMMA columnname carglist */ - 199, /* (350) columnlist ::= columnname carglist */ - 191, /* (351) nm ::= ID|INDEXED */ - 191, /* (352) nm ::= STRING */ - 191, /* (353) nm ::= JOIN_KW */ - 205, /* (354) typetoken ::= typename */ - 206, /* (355) typename ::= ID|STRING */ - 207, /* (356) signed ::= plus_num */ - 207, /* (357) signed ::= minus_num */ - 204, /* (358) carglist ::= carglist ccons */ - 204, /* (359) carglist ::= */ - 212, /* (360) ccons ::= NULL onconf */ - 212, /* (361) ccons ::= GENERATED ALWAYS AS generated */ - 212, /* (362) ccons ::= AS generated */ - 200, /* (363) conslist_opt ::= COMMA conslist */ - 225, /* (364) conslist ::= conslist tconscomma tcons */ - 225, /* (365) conslist ::= tcons */ - 226, /* (366) tconscomma ::= */ - 230, /* (367) defer_subclause_opt ::= defer_subclause */ - 232, /* (368) resolvetype ::= raisetype */ - 236, /* (369) selectnowith ::= oneselect */ - 237, /* (370) oneselect ::= values */ - 251, /* (371) sclp ::= selcollist COMMA */ - 252, /* (372) as ::= ID|STRING */ - 269, /* (373) returning ::= */ - 214, /* (374) expr ::= term */ - 271, /* (375) likeop ::= LIKE_KW|MATCH */ - 259, /* (376) exprlist ::= nexprlist */ - 281, /* (377) nmnum ::= plus_num */ - 281, /* (378) nmnum ::= nm */ - 281, /* (379) nmnum ::= ON */ - 281, /* (380) nmnum ::= DELETE */ - 281, /* (381) nmnum ::= DEFAULT */ - 208, /* (382) plus_num ::= INTEGER|FLOAT */ - 286, /* (383) foreach_clause ::= */ - 286, /* (384) foreach_clause ::= FOR EACH ROW */ - 289, /* (385) trnm ::= nm */ - 290, /* (386) tridxby ::= */ - 291, /* (387) database_kw_opt ::= DATABASE */ - 291, /* (388) database_kw_opt ::= */ - 294, /* (389) kwcolumn_opt ::= */ - 294, /* (390) kwcolumn_opt ::= COLUMNKW */ - 296, /* (391) vtabarglist ::= vtabarg */ - 296, /* (392) vtabarglist ::= vtabarglist COMMA vtabarg */ - 297, /* (393) vtabarg ::= vtabarg vtabargtoken */ - 300, /* (394) anylist ::= */ - 300, /* (395) anylist ::= anylist LP anylist RP */ - 300, /* (396) anylist ::= anylist ANY */ - 263, /* (397) with ::= */ + 189, /* (0) explain ::= EXPLAIN */ + 189, /* (1) explain ::= EXPLAIN QUERY PLAN */ + 188, /* (2) cmdx ::= cmd */ + 190, /* (3) cmd ::= BEGIN transtype trans_opt */ + 191, /* (4) transtype ::= */ + 191, /* (5) transtype ::= DEFERRED */ + 191, /* (6) transtype ::= IMMEDIATE */ + 191, /* (7) transtype ::= EXCLUSIVE */ + 190, /* (8) cmd ::= COMMIT|END trans_opt */ + 190, /* (9) cmd ::= ROLLBACK trans_opt */ + 190, /* (10) cmd ::= SAVEPOINT nm */ + 190, /* (11) cmd ::= RELEASE savepoint_opt nm */ + 190, /* (12) cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */ + 195, /* (13) create_table ::= createkw temp TABLE ifnotexists nm dbnm */ + 197, /* (14) createkw ::= CREATE */ + 199, /* (15) ifnotexists ::= */ + 199, /* (16) ifnotexists ::= IF NOT EXISTS */ + 198, /* (17) temp ::= TEMP */ + 198, /* (18) temp ::= */ + 196, /* (19) create_table_args ::= LP columnlist conslist_opt RP table_option_set */ + 196, /* (20) create_table_args ::= AS select */ + 203, /* (21) table_option_set ::= */ + 203, /* (22) table_option_set ::= table_option_set COMMA table_option */ + 205, /* (23) table_option ::= WITHOUT nm */ + 205, /* (24) table_option ::= nm */ + 206, /* (25) columnname ::= nm typetoken */ + 208, /* (26) typetoken ::= */ + 208, /* (27) typetoken ::= typename LP signed RP */ + 208, /* (28) typetoken ::= typename LP signed COMMA signed RP */ + 209, /* (29) typename ::= typename ID|STRING */ + 213, /* (30) scanpt ::= */ + 214, /* (31) scantok ::= */ + 215, /* (32) ccons ::= CONSTRAINT nm */ + 215, /* (33) ccons ::= DEFAULT scantok term */ + 215, /* (34) ccons ::= DEFAULT LP expr RP */ + 215, /* (35) ccons ::= DEFAULT PLUS scantok term */ + 215, /* (36) ccons ::= DEFAULT MINUS scantok term */ + 215, /* (37) ccons ::= DEFAULT scantok ID|INDEXED */ + 215, /* (38) ccons ::= NOT NULL onconf */ + 215, /* (39) ccons ::= PRIMARY KEY sortorder onconf autoinc */ + 215, /* (40) ccons ::= UNIQUE onconf */ + 215, /* (41) ccons ::= CHECK LP expr RP */ + 215, /* (42) ccons ::= REFERENCES nm eidlist_opt refargs */ + 215, /* (43) ccons ::= defer_subclause */ + 215, /* (44) ccons ::= COLLATE ID|STRING */ + 224, /* (45) generated ::= LP expr RP */ + 224, /* (46) generated ::= LP expr RP ID */ + 220, /* (47) autoinc ::= */ + 220, /* (48) autoinc ::= AUTOINCR */ + 222, /* (49) refargs ::= */ + 222, /* (50) refargs ::= refargs refarg */ + 225, /* (51) refarg ::= MATCH nm */ + 225, /* (52) refarg ::= ON INSERT refact */ + 225, /* (53) refarg ::= ON DELETE refact */ + 225, /* (54) refarg ::= ON UPDATE refact */ + 226, /* (55) refact ::= SET NULL */ + 226, /* (56) refact ::= SET DEFAULT */ + 226, /* (57) refact ::= CASCADE */ + 226, /* (58) refact ::= RESTRICT */ + 226, /* (59) refact ::= NO ACTION */ + 223, /* (60) defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ + 223, /* (61) defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ + 227, /* (62) init_deferred_pred_opt ::= */ + 227, /* (63) init_deferred_pred_opt ::= INITIALLY DEFERRED */ + 227, /* (64) init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ + 202, /* (65) conslist_opt ::= */ + 229, /* (66) tconscomma ::= COMMA */ + 230, /* (67) tcons ::= CONSTRAINT nm */ + 230, /* (68) tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ + 230, /* (69) tcons ::= UNIQUE LP sortlist RP onconf */ + 230, /* (70) tcons ::= CHECK LP expr RP onconf */ + 230, /* (71) tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ + 233, /* (72) defer_subclause_opt ::= */ + 218, /* (73) onconf ::= */ + 218, /* (74) onconf ::= ON CONFLICT resolvetype */ + 234, /* (75) orconf ::= */ + 234, /* (76) orconf ::= OR resolvetype */ + 235, /* (77) resolvetype ::= IGNORE */ + 235, /* (78) resolvetype ::= REPLACE */ + 190, /* (79) cmd ::= DROP TABLE ifexists fullname */ + 237, /* (80) ifexists ::= IF EXISTS */ + 237, /* (81) ifexists ::= */ + 190, /* (82) cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ + 190, /* (83) cmd ::= DROP VIEW ifexists fullname */ + 190, /* (84) cmd ::= select */ + 204, /* (85) select ::= WITH wqlist selectnowith */ + 204, /* (86) select ::= WITH RECURSIVE wqlist selectnowith */ + 204, /* (87) select ::= selectnowith */ + 239, /* (88) selectnowith ::= selectnowith multiselect_op oneselect */ + 242, /* (89) multiselect_op ::= UNION */ + 242, /* (90) multiselect_op ::= UNION ALL */ + 242, /* (91) multiselect_op ::= EXCEPT|INTERSECT */ + 240, /* (92) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ + 240, /* (93) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ + 252, /* (94) values ::= VALUES LP nexprlist RP */ + 252, /* (95) values ::= values COMMA LP nexprlist RP */ + 243, /* (96) distinct ::= DISTINCT */ + 243, /* (97) distinct ::= ALL */ + 243, /* (98) distinct ::= */ + 254, /* (99) sclp ::= */ + 244, /* (100) selcollist ::= sclp scanpt expr scanpt as */ + 244, /* (101) selcollist ::= sclp scanpt STAR */ + 244, /* (102) selcollist ::= sclp scanpt nm DOT STAR */ + 255, /* (103) as ::= AS nm */ + 255, /* (104) as ::= */ + 245, /* (105) from ::= */ + 245, /* (106) from ::= FROM seltablist */ + 257, /* (107) stl_prefix ::= seltablist joinop */ + 257, /* (108) stl_prefix ::= */ + 256, /* (109) seltablist ::= stl_prefix nm dbnm as on_using */ + 256, /* (110) seltablist ::= stl_prefix nm dbnm as indexed_by on_using */ + 256, /* (111) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using */ + 256, /* (112) seltablist ::= stl_prefix LP select RP as on_using */ + 256, /* (113) seltablist ::= stl_prefix LP seltablist RP as on_using */ + 200, /* (114) dbnm ::= */ + 200, /* (115) dbnm ::= DOT nm */ + 238, /* (116) fullname ::= nm */ + 238, /* (117) fullname ::= nm DOT nm */ + 262, /* (118) xfullname ::= nm */ + 262, /* (119) xfullname ::= nm DOT nm */ + 262, /* (120) xfullname ::= nm DOT nm AS nm */ + 262, /* (121) xfullname ::= nm AS nm */ + 258, /* (122) joinop ::= COMMA|JOIN */ + 258, /* (123) joinop ::= JOIN_KW JOIN */ + 258, /* (124) joinop ::= JOIN_KW nm JOIN */ + 258, /* (125) joinop ::= JOIN_KW nm nm JOIN */ + 259, /* (126) on_using ::= ON expr */ + 259, /* (127) on_using ::= USING LP idlist RP */ + 259, /* (128) on_using ::= */ + 264, /* (129) indexed_opt ::= */ + 260, /* (130) indexed_by ::= INDEXED BY nm */ + 260, /* (131) indexed_by ::= NOT INDEXED */ + 249, /* (132) orderby_opt ::= */ + 249, /* (133) orderby_opt ::= ORDER BY sortlist */ + 231, /* (134) sortlist ::= sortlist COMMA expr sortorder nulls */ + 231, /* (135) sortlist ::= expr sortorder nulls */ + 219, /* (136) sortorder ::= ASC */ + 219, /* (137) sortorder ::= DESC */ + 219, /* (138) sortorder ::= */ + 265, /* (139) nulls ::= NULLS FIRST */ + 265, /* (140) nulls ::= NULLS LAST */ + 265, /* (141) nulls ::= */ + 247, /* (142) groupby_opt ::= */ + 247, /* (143) groupby_opt ::= GROUP BY nexprlist */ + 248, /* (144) having_opt ::= */ + 248, /* (145) having_opt ::= HAVING expr */ + 250, /* (146) limit_opt ::= */ + 250, /* (147) limit_opt ::= LIMIT expr */ + 250, /* (148) limit_opt ::= LIMIT expr OFFSET expr */ + 250, /* (149) limit_opt ::= LIMIT expr COMMA expr */ + 190, /* (150) cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */ + 246, /* (151) where_opt ::= */ + 246, /* (152) where_opt ::= WHERE expr */ + 267, /* (153) where_opt_ret ::= */ + 267, /* (154) where_opt_ret ::= WHERE expr */ + 267, /* (155) where_opt_ret ::= RETURNING selcollist */ + 267, /* (156) where_opt_ret ::= WHERE expr RETURNING selcollist */ + 190, /* (157) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */ + 268, /* (158) setlist ::= setlist COMMA nm EQ expr */ + 268, /* (159) setlist ::= setlist COMMA LP idlist RP EQ expr */ + 268, /* (160) setlist ::= nm EQ expr */ + 268, /* (161) setlist ::= LP idlist RP EQ expr */ + 190, /* (162) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ + 190, /* (163) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */ + 271, /* (164) upsert ::= */ + 271, /* (165) upsert ::= RETURNING selcollist */ + 271, /* (166) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */ + 271, /* (167) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */ + 271, /* (168) upsert ::= ON CONFLICT DO NOTHING returning */ + 271, /* (169) upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */ + 272, /* (170) returning ::= RETURNING selcollist */ + 269, /* (171) insert_cmd ::= INSERT orconf */ + 269, /* (172) insert_cmd ::= REPLACE */ + 270, /* (173) idlist_opt ::= */ + 270, /* (174) idlist_opt ::= LP idlist RP */ + 263, /* (175) idlist ::= idlist COMMA nm */ + 263, /* (176) idlist ::= nm */ + 217, /* (177) expr ::= LP expr RP */ + 217, /* (178) expr ::= ID|INDEXED */ + 217, /* (179) expr ::= JOIN_KW */ + 217, /* (180) expr ::= nm DOT nm */ + 217, /* (181) expr ::= nm DOT nm DOT nm */ + 216, /* (182) term ::= NULL|FLOAT|BLOB */ + 216, /* (183) term ::= STRING */ + 216, /* (184) term ::= INTEGER */ + 217, /* (185) expr ::= VARIABLE */ + 217, /* (186) expr ::= expr COLLATE ID|STRING */ + 217, /* (187) expr ::= CAST LP expr AS typetoken RP */ + 217, /* (188) expr ::= ID|INDEXED LP distinct exprlist RP */ + 217, /* (189) expr ::= ID|INDEXED LP STAR RP */ + 217, /* (190) expr ::= ID|INDEXED LP distinct exprlist RP filter_over */ + 217, /* (191) expr ::= ID|INDEXED LP STAR RP filter_over */ + 216, /* (192) term ::= CTIME_KW */ + 217, /* (193) expr ::= LP nexprlist COMMA expr RP */ + 217, /* (194) expr ::= expr AND expr */ + 217, /* (195) expr ::= expr OR expr */ + 217, /* (196) expr ::= expr LT|GT|GE|LE expr */ + 217, /* (197) expr ::= expr EQ|NE expr */ + 217, /* (198) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ + 217, /* (199) expr ::= expr PLUS|MINUS expr */ + 217, /* (200) expr ::= expr STAR|SLASH|REM expr */ + 217, /* (201) expr ::= expr CONCAT expr */ + 274, /* (202) likeop ::= NOT LIKE_KW|MATCH */ + 217, /* (203) expr ::= expr likeop expr */ + 217, /* (204) expr ::= expr likeop expr ESCAPE expr */ + 217, /* (205) expr ::= expr ISNULL|NOTNULL */ + 217, /* (206) expr ::= expr NOT NULL */ + 217, /* (207) expr ::= expr IS expr */ + 217, /* (208) expr ::= expr IS NOT expr */ + 217, /* (209) expr ::= expr IS NOT DISTINCT FROM expr */ + 217, /* (210) expr ::= expr IS DISTINCT FROM expr */ + 217, /* (211) expr ::= NOT expr */ + 217, /* (212) expr ::= BITNOT expr */ + 217, /* (213) expr ::= PLUS|MINUS expr */ + 217, /* (214) expr ::= expr PTR expr */ + 275, /* (215) between_op ::= BETWEEN */ + 275, /* (216) between_op ::= NOT BETWEEN */ + 217, /* (217) expr ::= expr between_op expr AND expr */ + 276, /* (218) in_op ::= IN */ + 276, /* (219) in_op ::= NOT IN */ + 217, /* (220) expr ::= expr in_op LP exprlist RP */ + 217, /* (221) expr ::= LP select RP */ + 217, /* (222) expr ::= expr in_op LP select RP */ + 217, /* (223) expr ::= expr in_op nm dbnm paren_exprlist */ + 217, /* (224) expr ::= EXISTS LP select RP */ + 217, /* (225) expr ::= CASE case_operand case_exprlist case_else END */ + 279, /* (226) case_exprlist ::= case_exprlist WHEN expr THEN expr */ + 279, /* (227) case_exprlist ::= WHEN expr THEN expr */ + 280, /* (228) case_else ::= ELSE expr */ + 280, /* (229) case_else ::= */ + 278, /* (230) case_operand ::= expr */ + 278, /* (231) case_operand ::= */ + 261, /* (232) exprlist ::= */ + 253, /* (233) nexprlist ::= nexprlist COMMA expr */ + 253, /* (234) nexprlist ::= expr */ + 277, /* (235) paren_exprlist ::= */ + 277, /* (236) paren_exprlist ::= LP exprlist RP */ + 190, /* (237) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ + 281, /* (238) uniqueflag ::= UNIQUE */ + 281, /* (239) uniqueflag ::= */ + 221, /* (240) eidlist_opt ::= */ + 221, /* (241) eidlist_opt ::= LP eidlist RP */ + 232, /* (242) eidlist ::= eidlist COMMA nm collate sortorder */ + 232, /* (243) eidlist ::= nm collate sortorder */ + 282, /* (244) collate ::= */ + 282, /* (245) collate ::= COLLATE ID|STRING */ + 190, /* (246) cmd ::= DROP INDEX ifexists fullname */ + 190, /* (247) cmd ::= VACUUM vinto */ + 190, /* (248) cmd ::= VACUUM nm vinto */ + 283, /* (249) vinto ::= INTO expr */ + 283, /* (250) vinto ::= */ + 190, /* (251) cmd ::= PRAGMA nm dbnm */ + 190, /* (252) cmd ::= PRAGMA nm dbnm EQ nmnum */ + 190, /* (253) cmd ::= PRAGMA nm dbnm LP nmnum RP */ + 190, /* (254) cmd ::= PRAGMA nm dbnm EQ minus_num */ + 190, /* (255) cmd ::= PRAGMA nm dbnm LP minus_num RP */ + 211, /* (256) plus_num ::= PLUS INTEGER|FLOAT */ + 212, /* (257) minus_num ::= MINUS INTEGER|FLOAT */ + 190, /* (258) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ + 285, /* (259) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ + 287, /* (260) trigger_time ::= BEFORE|AFTER */ + 287, /* (261) trigger_time ::= INSTEAD OF */ + 287, /* (262) trigger_time ::= */ + 288, /* (263) trigger_event ::= DELETE|INSERT */ + 288, /* (264) trigger_event ::= UPDATE */ + 288, /* (265) trigger_event ::= UPDATE OF idlist */ + 290, /* (266) when_clause ::= */ + 290, /* (267) when_clause ::= WHEN expr */ + 286, /* (268) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ + 286, /* (269) trigger_cmd_list ::= trigger_cmd SEMI */ + 292, /* (270) trnm ::= nm DOT nm */ + 293, /* (271) tridxby ::= INDEXED BY nm */ + 293, /* (272) tridxby ::= NOT INDEXED */ + 291, /* (273) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */ + 291, /* (274) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ + 291, /* (275) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */ + 291, /* (276) trigger_cmd ::= scanpt select scanpt */ + 217, /* (277) expr ::= RAISE LP IGNORE RP */ + 217, /* (278) expr ::= RAISE LP raisetype COMMA nm RP */ + 236, /* (279) raisetype ::= ROLLBACK */ + 236, /* (280) raisetype ::= ABORT */ + 236, /* (281) raisetype ::= FAIL */ + 190, /* (282) cmd ::= DROP TRIGGER ifexists fullname */ + 190, /* (283) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ + 190, /* (284) cmd ::= DETACH database_kw_opt expr */ + 295, /* (285) key_opt ::= */ + 295, /* (286) key_opt ::= KEY expr */ + 190, /* (287) cmd ::= REINDEX */ + 190, /* (288) cmd ::= REINDEX nm dbnm */ + 190, /* (289) cmd ::= ANALYZE */ + 190, /* (290) cmd ::= ANALYZE nm dbnm */ + 190, /* (291) cmd ::= ALTER TABLE fullname RENAME TO nm */ + 190, /* (292) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ + 190, /* (293) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */ + 296, /* (294) add_column_fullname ::= fullname */ + 190, /* (295) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ + 190, /* (296) cmd ::= create_vtab */ + 190, /* (297) cmd ::= create_vtab LP vtabarglist RP */ + 298, /* (298) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ + 300, /* (299) vtabarg ::= */ + 301, /* (300) vtabargtoken ::= ANY */ + 301, /* (301) vtabargtoken ::= lp anylist RP */ + 302, /* (302) lp ::= LP */ + 266, /* (303) with ::= WITH wqlist */ + 266, /* (304) with ::= WITH RECURSIVE wqlist */ + 305, /* (305) wqas ::= AS */ + 305, /* (306) wqas ::= AS MATERIALIZED */ + 305, /* (307) wqas ::= AS NOT MATERIALIZED */ + 304, /* (308) wqitem ::= nm eidlist_opt wqas LP select RP */ + 241, /* (309) wqlist ::= wqitem */ + 241, /* (310) wqlist ::= wqlist COMMA wqitem */ + 306, /* (311) windowdefn_list ::= windowdefn */ + 306, /* (312) windowdefn_list ::= windowdefn_list COMMA windowdefn */ + 307, /* (313) windowdefn ::= nm AS LP window RP */ + 308, /* (314) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ + 308, /* (315) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ + 308, /* (316) window ::= ORDER BY sortlist frame_opt */ + 308, /* (317) window ::= nm ORDER BY sortlist frame_opt */ + 308, /* (318) window ::= frame_opt */ + 308, /* (319) window ::= nm frame_opt */ + 309, /* (320) frame_opt ::= */ + 309, /* (321) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ + 309, /* (322) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ + 313, /* (323) range_or_rows ::= RANGE|ROWS|GROUPS */ + 315, /* (324) frame_bound_s ::= frame_bound */ + 315, /* (325) frame_bound_s ::= UNBOUNDED PRECEDING */ + 316, /* (326) frame_bound_e ::= frame_bound */ + 316, /* (327) frame_bound_e ::= UNBOUNDED FOLLOWING */ + 314, /* (328) frame_bound ::= expr PRECEDING|FOLLOWING */ + 314, /* (329) frame_bound ::= CURRENT ROW */ + 317, /* (330) frame_exclude_opt ::= */ + 317, /* (331) frame_exclude_opt ::= EXCLUDE frame_exclude */ + 318, /* (332) frame_exclude ::= NO OTHERS */ + 318, /* (333) frame_exclude ::= CURRENT ROW */ + 318, /* (334) frame_exclude ::= GROUP|TIES */ + 251, /* (335) window_clause ::= WINDOW windowdefn_list */ + 273, /* (336) filter_over ::= filter_clause over_clause */ + 273, /* (337) filter_over ::= over_clause */ + 273, /* (338) filter_over ::= filter_clause */ + 312, /* (339) over_clause ::= OVER LP window RP */ + 312, /* (340) over_clause ::= OVER nm */ + 311, /* (341) filter_clause ::= FILTER LP WHERE expr RP */ + 185, /* (342) input ::= cmdlist */ + 186, /* (343) cmdlist ::= cmdlist ecmd */ + 186, /* (344) cmdlist ::= ecmd */ + 187, /* (345) ecmd ::= SEMI */ + 187, /* (346) ecmd ::= cmdx SEMI */ + 187, /* (347) ecmd ::= explain cmdx SEMI */ + 192, /* (348) trans_opt ::= */ + 192, /* (349) trans_opt ::= TRANSACTION */ + 192, /* (350) trans_opt ::= TRANSACTION nm */ + 194, /* (351) savepoint_opt ::= SAVEPOINT */ + 194, /* (352) savepoint_opt ::= */ + 190, /* (353) cmd ::= create_table create_table_args */ + 203, /* (354) table_option_set ::= table_option */ + 201, /* (355) columnlist ::= columnlist COMMA columnname carglist */ + 201, /* (356) columnlist ::= columnname carglist */ + 193, /* (357) nm ::= ID|INDEXED */ + 193, /* (358) nm ::= STRING */ + 193, /* (359) nm ::= JOIN_KW */ + 208, /* (360) typetoken ::= typename */ + 209, /* (361) typename ::= ID|STRING */ + 210, /* (362) signed ::= plus_num */ + 210, /* (363) signed ::= minus_num */ + 207, /* (364) carglist ::= carglist ccons */ + 207, /* (365) carglist ::= */ + 215, /* (366) ccons ::= NULL onconf */ + 215, /* (367) ccons ::= GENERATED ALWAYS AS generated */ + 215, /* (368) ccons ::= AS generated */ + 202, /* (369) conslist_opt ::= COMMA conslist */ + 228, /* (370) conslist ::= conslist tconscomma tcons */ + 228, /* (371) conslist ::= tcons */ + 229, /* (372) tconscomma ::= */ + 233, /* (373) defer_subclause_opt ::= defer_subclause */ + 235, /* (374) resolvetype ::= raisetype */ + 239, /* (375) selectnowith ::= oneselect */ + 240, /* (376) oneselect ::= values */ + 254, /* (377) sclp ::= selcollist COMMA */ + 255, /* (378) as ::= ID|STRING */ + 264, /* (379) indexed_opt ::= indexed_by */ + 272, /* (380) returning ::= */ + 217, /* (381) expr ::= term */ + 274, /* (382) likeop ::= LIKE_KW|MATCH */ + 261, /* (383) exprlist ::= nexprlist */ + 284, /* (384) nmnum ::= plus_num */ + 284, /* (385) nmnum ::= nm */ + 284, /* (386) nmnum ::= ON */ + 284, /* (387) nmnum ::= DELETE */ + 284, /* (388) nmnum ::= DEFAULT */ + 211, /* (389) plus_num ::= INTEGER|FLOAT */ + 289, /* (390) foreach_clause ::= */ + 289, /* (391) foreach_clause ::= FOR EACH ROW */ + 292, /* (392) trnm ::= nm */ + 293, /* (393) tridxby ::= */ + 294, /* (394) database_kw_opt ::= DATABASE */ + 294, /* (395) database_kw_opt ::= */ + 297, /* (396) kwcolumn_opt ::= */ + 297, /* (397) kwcolumn_opt ::= COLUMNKW */ + 299, /* (398) vtabarglist ::= vtabarg */ + 299, /* (399) vtabarglist ::= vtabarglist COMMA vtabarg */ + 300, /* (400) vtabarg ::= vtabarg vtabargtoken */ + 303, /* (401) anylist ::= */ + 303, /* (402) anylist ::= anylist LP anylist RP */ + 303, /* (403) anylist ::= anylist ANY */ + 266, /* (404) with ::= */ }; /* For rule J, yyRuleInfoNRhs[J] contains the negative of the number @@ -160099,385 +166653,392 @@ static const signed char yyRuleInfoNRhs[] = { -3, /* (16) ifnotexists ::= IF NOT EXISTS */ -1, /* (17) temp ::= TEMP */ 0, /* (18) temp ::= */ - -5, /* (19) create_table_args ::= LP columnlist conslist_opt RP table_options */ + -5, /* (19) create_table_args ::= LP columnlist conslist_opt RP table_option_set */ -2, /* (20) create_table_args ::= AS select */ - 0, /* (21) table_options ::= */ - -2, /* (22) table_options ::= WITHOUT nm */ - -2, /* (23) columnname ::= nm typetoken */ - 0, /* (24) typetoken ::= */ - -4, /* (25) typetoken ::= typename LP signed RP */ - -6, /* (26) typetoken ::= typename LP signed COMMA signed RP */ - -2, /* (27) typename ::= typename ID|STRING */ - 0, /* (28) scanpt ::= */ - 0, /* (29) scantok ::= */ - -2, /* (30) ccons ::= CONSTRAINT nm */ - -3, /* (31) ccons ::= DEFAULT scantok term */ - -4, /* (32) ccons ::= DEFAULT LP expr RP */ - -4, /* (33) ccons ::= DEFAULT PLUS scantok term */ - -4, /* (34) ccons ::= DEFAULT MINUS scantok term */ - -3, /* (35) ccons ::= DEFAULT scantok ID|INDEXED */ - -3, /* (36) ccons ::= NOT NULL onconf */ - -5, /* (37) ccons ::= PRIMARY KEY sortorder onconf autoinc */ - -2, /* (38) ccons ::= UNIQUE onconf */ - -4, /* (39) ccons ::= CHECK LP expr RP */ - -4, /* (40) ccons ::= REFERENCES nm eidlist_opt refargs */ - -1, /* (41) ccons ::= defer_subclause */ - -2, /* (42) ccons ::= COLLATE ID|STRING */ - -3, /* (43) generated ::= LP expr RP */ - -4, /* (44) generated ::= LP expr RP ID */ - 0, /* (45) autoinc ::= */ - -1, /* (46) autoinc ::= AUTOINCR */ - 0, /* (47) refargs ::= */ - -2, /* (48) refargs ::= refargs refarg */ - -2, /* (49) refarg ::= MATCH nm */ - -3, /* (50) refarg ::= ON INSERT refact */ - -3, /* (51) refarg ::= ON DELETE refact */ - -3, /* (52) refarg ::= ON UPDATE refact */ - -2, /* (53) refact ::= SET NULL */ - -2, /* (54) refact ::= SET DEFAULT */ - -1, /* (55) refact ::= CASCADE */ - -1, /* (56) refact ::= RESTRICT */ - -2, /* (57) refact ::= NO ACTION */ - -3, /* (58) defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ - -2, /* (59) defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ - 0, /* (60) init_deferred_pred_opt ::= */ - -2, /* (61) init_deferred_pred_opt ::= INITIALLY DEFERRED */ - -2, /* (62) init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ - 0, /* (63) conslist_opt ::= */ - -1, /* (64) tconscomma ::= COMMA */ - -2, /* (65) tcons ::= CONSTRAINT nm */ - -7, /* (66) tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ - -5, /* (67) tcons ::= UNIQUE LP sortlist RP onconf */ - -5, /* (68) tcons ::= CHECK LP expr RP onconf */ - -10, /* (69) tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ - 0, /* (70) defer_subclause_opt ::= */ - 0, /* (71) onconf ::= */ - -3, /* (72) onconf ::= ON CONFLICT resolvetype */ - 0, /* (73) orconf ::= */ - -2, /* (74) orconf ::= OR resolvetype */ - -1, /* (75) resolvetype ::= IGNORE */ - -1, /* (76) resolvetype ::= REPLACE */ - -4, /* (77) cmd ::= DROP TABLE ifexists fullname */ - -2, /* (78) ifexists ::= IF EXISTS */ - 0, /* (79) ifexists ::= */ - -9, /* (80) cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ - -4, /* (81) cmd ::= DROP VIEW ifexists fullname */ - -1, /* (82) cmd ::= select */ - -3, /* (83) select ::= WITH wqlist selectnowith */ - -4, /* (84) select ::= WITH RECURSIVE wqlist selectnowith */ - -1, /* (85) select ::= selectnowith */ - -3, /* (86) selectnowith ::= selectnowith multiselect_op oneselect */ - -1, /* (87) multiselect_op ::= UNION */ - -2, /* (88) multiselect_op ::= UNION ALL */ - -1, /* (89) multiselect_op ::= EXCEPT|INTERSECT */ - -9, /* (90) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ - -10, /* (91) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ - -4, /* (92) values ::= VALUES LP nexprlist RP */ - -5, /* (93) values ::= values COMMA LP nexprlist RP */ - -1, /* (94) distinct ::= DISTINCT */ - -1, /* (95) distinct ::= ALL */ - 0, /* (96) distinct ::= */ - 0, /* (97) sclp ::= */ - -5, /* (98) selcollist ::= sclp scanpt expr scanpt as */ - -3, /* (99) selcollist ::= sclp scanpt STAR */ - -5, /* (100) selcollist ::= sclp scanpt nm DOT STAR */ - -2, /* (101) as ::= AS nm */ - 0, /* (102) as ::= */ - 0, /* (103) from ::= */ - -2, /* (104) from ::= FROM seltablist */ - -2, /* (105) stl_prefix ::= seltablist joinop */ - 0, /* (106) stl_prefix ::= */ - -7, /* (107) seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */ - -9, /* (108) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */ - -7, /* (109) seltablist ::= stl_prefix LP select RP as on_opt using_opt */ - -7, /* (110) seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */ - 0, /* (111) dbnm ::= */ - -2, /* (112) dbnm ::= DOT nm */ - -1, /* (113) fullname ::= nm */ - -3, /* (114) fullname ::= nm DOT nm */ - -1, /* (115) xfullname ::= nm */ - -3, /* (116) xfullname ::= nm DOT nm */ - -5, /* (117) xfullname ::= nm DOT nm AS nm */ - -3, /* (118) xfullname ::= nm AS nm */ - -1, /* (119) joinop ::= COMMA|JOIN */ - -2, /* (120) joinop ::= JOIN_KW JOIN */ - -3, /* (121) joinop ::= JOIN_KW nm JOIN */ - -4, /* (122) joinop ::= JOIN_KW nm nm JOIN */ - -2, /* (123) on_opt ::= ON expr */ - 0, /* (124) on_opt ::= */ - 0, /* (125) indexed_opt ::= */ - -3, /* (126) indexed_opt ::= INDEXED BY nm */ - -2, /* (127) indexed_opt ::= NOT INDEXED */ - -4, /* (128) using_opt ::= USING LP idlist RP */ - 0, /* (129) using_opt ::= */ - 0, /* (130) orderby_opt ::= */ - -3, /* (131) orderby_opt ::= ORDER BY sortlist */ - -5, /* (132) sortlist ::= sortlist COMMA expr sortorder nulls */ - -3, /* (133) sortlist ::= expr sortorder nulls */ - -1, /* (134) sortorder ::= ASC */ - -1, /* (135) sortorder ::= DESC */ - 0, /* (136) sortorder ::= */ - -2, /* (137) nulls ::= NULLS FIRST */ - -2, /* (138) nulls ::= NULLS LAST */ - 0, /* (139) nulls ::= */ - 0, /* (140) groupby_opt ::= */ - -3, /* (141) groupby_opt ::= GROUP BY nexprlist */ - 0, /* (142) having_opt ::= */ - -2, /* (143) having_opt ::= HAVING expr */ - 0, /* (144) limit_opt ::= */ - -2, /* (145) limit_opt ::= LIMIT expr */ - -4, /* (146) limit_opt ::= LIMIT expr OFFSET expr */ - -4, /* (147) limit_opt ::= LIMIT expr COMMA expr */ - -6, /* (148) cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */ - 0, /* (149) where_opt ::= */ - -2, /* (150) where_opt ::= WHERE expr */ - 0, /* (151) where_opt_ret ::= */ - -2, /* (152) where_opt_ret ::= WHERE expr */ - -2, /* (153) where_opt_ret ::= RETURNING selcollist */ - -4, /* (154) where_opt_ret ::= WHERE expr RETURNING selcollist */ - -9, /* (155) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */ - -5, /* (156) setlist ::= setlist COMMA nm EQ expr */ - -7, /* (157) setlist ::= setlist COMMA LP idlist RP EQ expr */ - -3, /* (158) setlist ::= nm EQ expr */ - -5, /* (159) setlist ::= LP idlist RP EQ expr */ - -7, /* (160) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ - -8, /* (161) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */ - 0, /* (162) upsert ::= */ - -2, /* (163) upsert ::= RETURNING selcollist */ - -12, /* (164) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */ - -9, /* (165) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */ - -5, /* (166) upsert ::= ON CONFLICT DO NOTHING returning */ - -8, /* (167) upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */ - -2, /* (168) returning ::= RETURNING selcollist */ - -2, /* (169) insert_cmd ::= INSERT orconf */ - -1, /* (170) insert_cmd ::= REPLACE */ - 0, /* (171) idlist_opt ::= */ - -3, /* (172) idlist_opt ::= LP idlist RP */ - -3, /* (173) idlist ::= idlist COMMA nm */ - -1, /* (174) idlist ::= nm */ - -3, /* (175) expr ::= LP expr RP */ - -1, /* (176) expr ::= ID|INDEXED */ - -1, /* (177) expr ::= JOIN_KW */ - -3, /* (178) expr ::= nm DOT nm */ - -5, /* (179) expr ::= nm DOT nm DOT nm */ - -1, /* (180) term ::= NULL|FLOAT|BLOB */ - -1, /* (181) term ::= STRING */ - -1, /* (182) term ::= INTEGER */ - -1, /* (183) expr ::= VARIABLE */ - -3, /* (184) expr ::= expr COLLATE ID|STRING */ - -6, /* (185) expr ::= CAST LP expr AS typetoken RP */ - -5, /* (186) expr ::= ID|INDEXED LP distinct exprlist RP */ - -4, /* (187) expr ::= ID|INDEXED LP STAR RP */ - -6, /* (188) expr ::= ID|INDEXED LP distinct exprlist RP filter_over */ - -5, /* (189) expr ::= ID|INDEXED LP STAR RP filter_over */ - -1, /* (190) term ::= CTIME_KW */ - -5, /* (191) expr ::= LP nexprlist COMMA expr RP */ - -3, /* (192) expr ::= expr AND expr */ - -3, /* (193) expr ::= expr OR expr */ - -3, /* (194) expr ::= expr LT|GT|GE|LE expr */ - -3, /* (195) expr ::= expr EQ|NE expr */ - -3, /* (196) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ - -3, /* (197) expr ::= expr PLUS|MINUS expr */ - -3, /* (198) expr ::= expr STAR|SLASH|REM expr */ - -3, /* (199) expr ::= expr CONCAT expr */ - -2, /* (200) likeop ::= NOT LIKE_KW|MATCH */ - -3, /* (201) expr ::= expr likeop expr */ - -5, /* (202) expr ::= expr likeop expr ESCAPE expr */ - -2, /* (203) expr ::= expr ISNULL|NOTNULL */ - -3, /* (204) expr ::= expr NOT NULL */ - -3, /* (205) expr ::= expr IS expr */ - -4, /* (206) expr ::= expr IS NOT expr */ - -2, /* (207) expr ::= NOT expr */ - -2, /* (208) expr ::= BITNOT expr */ - -2, /* (209) expr ::= PLUS|MINUS expr */ - -1, /* (210) between_op ::= BETWEEN */ - -2, /* (211) between_op ::= NOT BETWEEN */ - -5, /* (212) expr ::= expr between_op expr AND expr */ - -1, /* (213) in_op ::= IN */ - -2, /* (214) in_op ::= NOT IN */ - -5, /* (215) expr ::= expr in_op LP exprlist RP */ - -3, /* (216) expr ::= LP select RP */ - -5, /* (217) expr ::= expr in_op LP select RP */ - -5, /* (218) expr ::= expr in_op nm dbnm paren_exprlist */ - -4, /* (219) expr ::= EXISTS LP select RP */ - -5, /* (220) expr ::= CASE case_operand case_exprlist case_else END */ - -5, /* (221) case_exprlist ::= case_exprlist WHEN expr THEN expr */ - -4, /* (222) case_exprlist ::= WHEN expr THEN expr */ - -2, /* (223) case_else ::= ELSE expr */ - 0, /* (224) case_else ::= */ - -1, /* (225) case_operand ::= expr */ - 0, /* (226) case_operand ::= */ - 0, /* (227) exprlist ::= */ - -3, /* (228) nexprlist ::= nexprlist COMMA expr */ - -1, /* (229) nexprlist ::= expr */ - 0, /* (230) paren_exprlist ::= */ - -3, /* (231) paren_exprlist ::= LP exprlist RP */ - -12, /* (232) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ - -1, /* (233) uniqueflag ::= UNIQUE */ - 0, /* (234) uniqueflag ::= */ - 0, /* (235) eidlist_opt ::= */ - -3, /* (236) eidlist_opt ::= LP eidlist RP */ - -5, /* (237) eidlist ::= eidlist COMMA nm collate sortorder */ - -3, /* (238) eidlist ::= nm collate sortorder */ - 0, /* (239) collate ::= */ - -2, /* (240) collate ::= COLLATE ID|STRING */ - -4, /* (241) cmd ::= DROP INDEX ifexists fullname */ - -2, /* (242) cmd ::= VACUUM vinto */ - -3, /* (243) cmd ::= VACUUM nm vinto */ - -2, /* (244) vinto ::= INTO expr */ - 0, /* (245) vinto ::= */ - -3, /* (246) cmd ::= PRAGMA nm dbnm */ - -5, /* (247) cmd ::= PRAGMA nm dbnm EQ nmnum */ - -6, /* (248) cmd ::= PRAGMA nm dbnm LP nmnum RP */ - -5, /* (249) cmd ::= PRAGMA nm dbnm EQ minus_num */ - -6, /* (250) cmd ::= PRAGMA nm dbnm LP minus_num RP */ - -2, /* (251) plus_num ::= PLUS INTEGER|FLOAT */ - -2, /* (252) minus_num ::= MINUS INTEGER|FLOAT */ - -5, /* (253) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ - -11, /* (254) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ - -1, /* (255) trigger_time ::= BEFORE|AFTER */ - -2, /* (256) trigger_time ::= INSTEAD OF */ - 0, /* (257) trigger_time ::= */ - -1, /* (258) trigger_event ::= DELETE|INSERT */ - -1, /* (259) trigger_event ::= UPDATE */ - -3, /* (260) trigger_event ::= UPDATE OF idlist */ - 0, /* (261) when_clause ::= */ - -2, /* (262) when_clause ::= WHEN expr */ - -3, /* (263) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ - -2, /* (264) trigger_cmd_list ::= trigger_cmd SEMI */ - -3, /* (265) trnm ::= nm DOT nm */ - -3, /* (266) tridxby ::= INDEXED BY nm */ - -2, /* (267) tridxby ::= NOT INDEXED */ - -9, /* (268) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */ - -8, /* (269) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ - -6, /* (270) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */ - -3, /* (271) trigger_cmd ::= scanpt select scanpt */ - -4, /* (272) expr ::= RAISE LP IGNORE RP */ - -6, /* (273) expr ::= RAISE LP raisetype COMMA nm RP */ - -1, /* (274) raisetype ::= ROLLBACK */ - -1, /* (275) raisetype ::= ABORT */ - -1, /* (276) raisetype ::= FAIL */ - -4, /* (277) cmd ::= DROP TRIGGER ifexists fullname */ - -6, /* (278) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ - -3, /* (279) cmd ::= DETACH database_kw_opt expr */ - 0, /* (280) key_opt ::= */ - -2, /* (281) key_opt ::= KEY expr */ - -1, /* (282) cmd ::= REINDEX */ - -3, /* (283) cmd ::= REINDEX nm dbnm */ - -1, /* (284) cmd ::= ANALYZE */ - -3, /* (285) cmd ::= ANALYZE nm dbnm */ - -6, /* (286) cmd ::= ALTER TABLE fullname RENAME TO nm */ - -7, /* (287) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ - -6, /* (288) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */ - -1, /* (289) add_column_fullname ::= fullname */ - -8, /* (290) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ - -1, /* (291) cmd ::= create_vtab */ - -4, /* (292) cmd ::= create_vtab LP vtabarglist RP */ - -8, /* (293) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ - 0, /* (294) vtabarg ::= */ - -1, /* (295) vtabargtoken ::= ANY */ - -3, /* (296) vtabargtoken ::= lp anylist RP */ - -1, /* (297) lp ::= LP */ - -2, /* (298) with ::= WITH wqlist */ - -3, /* (299) with ::= WITH RECURSIVE wqlist */ - -1, /* (300) wqas ::= AS */ - -2, /* (301) wqas ::= AS MATERIALIZED */ - -3, /* (302) wqas ::= AS NOT MATERIALIZED */ - -6, /* (303) wqitem ::= nm eidlist_opt wqas LP select RP */ - -1, /* (304) wqlist ::= wqitem */ - -3, /* (305) wqlist ::= wqlist COMMA wqitem */ - -1, /* (306) windowdefn_list ::= windowdefn */ - -3, /* (307) windowdefn_list ::= windowdefn_list COMMA windowdefn */ - -5, /* (308) windowdefn ::= nm AS LP window RP */ - -5, /* (309) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ - -6, /* (310) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ - -4, /* (311) window ::= ORDER BY sortlist frame_opt */ - -5, /* (312) window ::= nm ORDER BY sortlist frame_opt */ - -1, /* (313) window ::= frame_opt */ - -2, /* (314) window ::= nm frame_opt */ - 0, /* (315) frame_opt ::= */ - -3, /* (316) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ - -6, /* (317) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ - -1, /* (318) range_or_rows ::= RANGE|ROWS|GROUPS */ - -1, /* (319) frame_bound_s ::= frame_bound */ - -2, /* (320) frame_bound_s ::= UNBOUNDED PRECEDING */ - -1, /* (321) frame_bound_e ::= frame_bound */ - -2, /* (322) frame_bound_e ::= UNBOUNDED FOLLOWING */ - -2, /* (323) frame_bound ::= expr PRECEDING|FOLLOWING */ - -2, /* (324) frame_bound ::= CURRENT ROW */ - 0, /* (325) frame_exclude_opt ::= */ - -2, /* (326) frame_exclude_opt ::= EXCLUDE frame_exclude */ - -2, /* (327) frame_exclude ::= NO OTHERS */ - -2, /* (328) frame_exclude ::= CURRENT ROW */ - -1, /* (329) frame_exclude ::= GROUP|TIES */ - -2, /* (330) window_clause ::= WINDOW windowdefn_list */ - -2, /* (331) filter_over ::= filter_clause over_clause */ - -1, /* (332) filter_over ::= over_clause */ - -1, /* (333) filter_over ::= filter_clause */ - -4, /* (334) over_clause ::= OVER LP window RP */ - -2, /* (335) over_clause ::= OVER nm */ - -5, /* (336) filter_clause ::= FILTER LP WHERE expr RP */ - -1, /* (337) input ::= cmdlist */ - -2, /* (338) cmdlist ::= cmdlist ecmd */ - -1, /* (339) cmdlist ::= ecmd */ - -1, /* (340) ecmd ::= SEMI */ - -2, /* (341) ecmd ::= cmdx SEMI */ - -3, /* (342) ecmd ::= explain cmdx SEMI */ - 0, /* (343) trans_opt ::= */ - -1, /* (344) trans_opt ::= TRANSACTION */ - -2, /* (345) trans_opt ::= TRANSACTION nm */ - -1, /* (346) savepoint_opt ::= SAVEPOINT */ - 0, /* (347) savepoint_opt ::= */ - -2, /* (348) cmd ::= create_table create_table_args */ - -4, /* (349) columnlist ::= columnlist COMMA columnname carglist */ - -2, /* (350) columnlist ::= columnname carglist */ - -1, /* (351) nm ::= ID|INDEXED */ - -1, /* (352) nm ::= STRING */ - -1, /* (353) nm ::= JOIN_KW */ - -1, /* (354) typetoken ::= typename */ - -1, /* (355) typename ::= ID|STRING */ - -1, /* (356) signed ::= plus_num */ - -1, /* (357) signed ::= minus_num */ - -2, /* (358) carglist ::= carglist ccons */ - 0, /* (359) carglist ::= */ - -2, /* (360) ccons ::= NULL onconf */ - -4, /* (361) ccons ::= GENERATED ALWAYS AS generated */ - -2, /* (362) ccons ::= AS generated */ - -2, /* (363) conslist_opt ::= COMMA conslist */ - -3, /* (364) conslist ::= conslist tconscomma tcons */ - -1, /* (365) conslist ::= tcons */ - 0, /* (366) tconscomma ::= */ - -1, /* (367) defer_subclause_opt ::= defer_subclause */ - -1, /* (368) resolvetype ::= raisetype */ - -1, /* (369) selectnowith ::= oneselect */ - -1, /* (370) oneselect ::= values */ - -2, /* (371) sclp ::= selcollist COMMA */ - -1, /* (372) as ::= ID|STRING */ - 0, /* (373) returning ::= */ - -1, /* (374) expr ::= term */ - -1, /* (375) likeop ::= LIKE_KW|MATCH */ - -1, /* (376) exprlist ::= nexprlist */ - -1, /* (377) nmnum ::= plus_num */ - -1, /* (378) nmnum ::= nm */ - -1, /* (379) nmnum ::= ON */ - -1, /* (380) nmnum ::= DELETE */ - -1, /* (381) nmnum ::= DEFAULT */ - -1, /* (382) plus_num ::= INTEGER|FLOAT */ - 0, /* (383) foreach_clause ::= */ - -3, /* (384) foreach_clause ::= FOR EACH ROW */ - -1, /* (385) trnm ::= nm */ - 0, /* (386) tridxby ::= */ - -1, /* (387) database_kw_opt ::= DATABASE */ - 0, /* (388) database_kw_opt ::= */ - 0, /* (389) kwcolumn_opt ::= */ - -1, /* (390) kwcolumn_opt ::= COLUMNKW */ - -1, /* (391) vtabarglist ::= vtabarg */ - -3, /* (392) vtabarglist ::= vtabarglist COMMA vtabarg */ - -2, /* (393) vtabarg ::= vtabarg vtabargtoken */ - 0, /* (394) anylist ::= */ - -4, /* (395) anylist ::= anylist LP anylist RP */ - -2, /* (396) anylist ::= anylist ANY */ - 0, /* (397) with ::= */ + 0, /* (21) table_option_set ::= */ + -3, /* (22) table_option_set ::= table_option_set COMMA table_option */ + -2, /* (23) table_option ::= WITHOUT nm */ + -1, /* (24) table_option ::= nm */ + -2, /* (25) columnname ::= nm typetoken */ + 0, /* (26) typetoken ::= */ + -4, /* (27) typetoken ::= typename LP signed RP */ + -6, /* (28) typetoken ::= typename LP signed COMMA signed RP */ + -2, /* (29) typename ::= typename ID|STRING */ + 0, /* (30) scanpt ::= */ + 0, /* (31) scantok ::= */ + -2, /* (32) ccons ::= CONSTRAINT nm */ + -3, /* (33) ccons ::= DEFAULT scantok term */ + -4, /* (34) ccons ::= DEFAULT LP expr RP */ + -4, /* (35) ccons ::= DEFAULT PLUS scantok term */ + -4, /* (36) ccons ::= DEFAULT MINUS scantok term */ + -3, /* (37) ccons ::= DEFAULT scantok ID|INDEXED */ + -3, /* (38) ccons ::= NOT NULL onconf */ + -5, /* (39) ccons ::= PRIMARY KEY sortorder onconf autoinc */ + -2, /* (40) ccons ::= UNIQUE onconf */ + -4, /* (41) ccons ::= CHECK LP expr RP */ + -4, /* (42) ccons ::= REFERENCES nm eidlist_opt refargs */ + -1, /* (43) ccons ::= defer_subclause */ + -2, /* (44) ccons ::= COLLATE ID|STRING */ + -3, /* (45) generated ::= LP expr RP */ + -4, /* (46) generated ::= LP expr RP ID */ + 0, /* (47) autoinc ::= */ + -1, /* (48) autoinc ::= AUTOINCR */ + 0, /* (49) refargs ::= */ + -2, /* (50) refargs ::= refargs refarg */ + -2, /* (51) refarg ::= MATCH nm */ + -3, /* (52) refarg ::= ON INSERT refact */ + -3, /* (53) refarg ::= ON DELETE refact */ + -3, /* (54) refarg ::= ON UPDATE refact */ + -2, /* (55) refact ::= SET NULL */ + -2, /* (56) refact ::= SET DEFAULT */ + -1, /* (57) refact ::= CASCADE */ + -1, /* (58) refact ::= RESTRICT */ + -2, /* (59) refact ::= NO ACTION */ + -3, /* (60) defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ + -2, /* (61) defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ + 0, /* (62) init_deferred_pred_opt ::= */ + -2, /* (63) init_deferred_pred_opt ::= INITIALLY DEFERRED */ + -2, /* (64) init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ + 0, /* (65) conslist_opt ::= */ + -1, /* (66) tconscomma ::= COMMA */ + -2, /* (67) tcons ::= CONSTRAINT nm */ + -7, /* (68) tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ + -5, /* (69) tcons ::= UNIQUE LP sortlist RP onconf */ + -5, /* (70) tcons ::= CHECK LP expr RP onconf */ + -10, /* (71) tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ + 0, /* (72) defer_subclause_opt ::= */ + 0, /* (73) onconf ::= */ + -3, /* (74) onconf ::= ON CONFLICT resolvetype */ + 0, /* (75) orconf ::= */ + -2, /* (76) orconf ::= OR resolvetype */ + -1, /* (77) resolvetype ::= IGNORE */ + -1, /* (78) resolvetype ::= REPLACE */ + -4, /* (79) cmd ::= DROP TABLE ifexists fullname */ + -2, /* (80) ifexists ::= IF EXISTS */ + 0, /* (81) ifexists ::= */ + -9, /* (82) cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ + -4, /* (83) cmd ::= DROP VIEW ifexists fullname */ + -1, /* (84) cmd ::= select */ + -3, /* (85) select ::= WITH wqlist selectnowith */ + -4, /* (86) select ::= WITH RECURSIVE wqlist selectnowith */ + -1, /* (87) select ::= selectnowith */ + -3, /* (88) selectnowith ::= selectnowith multiselect_op oneselect */ + -1, /* (89) multiselect_op ::= UNION */ + -2, /* (90) multiselect_op ::= UNION ALL */ + -1, /* (91) multiselect_op ::= EXCEPT|INTERSECT */ + -9, /* (92) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ + -10, /* (93) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ + -4, /* (94) values ::= VALUES LP nexprlist RP */ + -5, /* (95) values ::= values COMMA LP nexprlist RP */ + -1, /* (96) distinct ::= DISTINCT */ + -1, /* (97) distinct ::= ALL */ + 0, /* (98) distinct ::= */ + 0, /* (99) sclp ::= */ + -5, /* (100) selcollist ::= sclp scanpt expr scanpt as */ + -3, /* (101) selcollist ::= sclp scanpt STAR */ + -5, /* (102) selcollist ::= sclp scanpt nm DOT STAR */ + -2, /* (103) as ::= AS nm */ + 0, /* (104) as ::= */ + 0, /* (105) from ::= */ + -2, /* (106) from ::= FROM seltablist */ + -2, /* (107) stl_prefix ::= seltablist joinop */ + 0, /* (108) stl_prefix ::= */ + -5, /* (109) seltablist ::= stl_prefix nm dbnm as on_using */ + -6, /* (110) seltablist ::= stl_prefix nm dbnm as indexed_by on_using */ + -8, /* (111) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using */ + -6, /* (112) seltablist ::= stl_prefix LP select RP as on_using */ + -6, /* (113) seltablist ::= stl_prefix LP seltablist RP as on_using */ + 0, /* (114) dbnm ::= */ + -2, /* (115) dbnm ::= DOT nm */ + -1, /* (116) fullname ::= nm */ + -3, /* (117) fullname ::= nm DOT nm */ + -1, /* (118) xfullname ::= nm */ + -3, /* (119) xfullname ::= nm DOT nm */ + -5, /* (120) xfullname ::= nm DOT nm AS nm */ + -3, /* (121) xfullname ::= nm AS nm */ + -1, /* (122) joinop ::= COMMA|JOIN */ + -2, /* (123) joinop ::= JOIN_KW JOIN */ + -3, /* (124) joinop ::= JOIN_KW nm JOIN */ + -4, /* (125) joinop ::= JOIN_KW nm nm JOIN */ + -2, /* (126) on_using ::= ON expr */ + -4, /* (127) on_using ::= USING LP idlist RP */ + 0, /* (128) on_using ::= */ + 0, /* (129) indexed_opt ::= */ + -3, /* (130) indexed_by ::= INDEXED BY nm */ + -2, /* (131) indexed_by ::= NOT INDEXED */ + 0, /* (132) orderby_opt ::= */ + -3, /* (133) orderby_opt ::= ORDER BY sortlist */ + -5, /* (134) sortlist ::= sortlist COMMA expr sortorder nulls */ + -3, /* (135) sortlist ::= expr sortorder nulls */ + -1, /* (136) sortorder ::= ASC */ + -1, /* (137) sortorder ::= DESC */ + 0, /* (138) sortorder ::= */ + -2, /* (139) nulls ::= NULLS FIRST */ + -2, /* (140) nulls ::= NULLS LAST */ + 0, /* (141) nulls ::= */ + 0, /* (142) groupby_opt ::= */ + -3, /* (143) groupby_opt ::= GROUP BY nexprlist */ + 0, /* (144) having_opt ::= */ + -2, /* (145) having_opt ::= HAVING expr */ + 0, /* (146) limit_opt ::= */ + -2, /* (147) limit_opt ::= LIMIT expr */ + -4, /* (148) limit_opt ::= LIMIT expr OFFSET expr */ + -4, /* (149) limit_opt ::= LIMIT expr COMMA expr */ + -6, /* (150) cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */ + 0, /* (151) where_opt ::= */ + -2, /* (152) where_opt ::= WHERE expr */ + 0, /* (153) where_opt_ret ::= */ + -2, /* (154) where_opt_ret ::= WHERE expr */ + -2, /* (155) where_opt_ret ::= RETURNING selcollist */ + -4, /* (156) where_opt_ret ::= WHERE expr RETURNING selcollist */ + -9, /* (157) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */ + -5, /* (158) setlist ::= setlist COMMA nm EQ expr */ + -7, /* (159) setlist ::= setlist COMMA LP idlist RP EQ expr */ + -3, /* (160) setlist ::= nm EQ expr */ + -5, /* (161) setlist ::= LP idlist RP EQ expr */ + -7, /* (162) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ + -8, /* (163) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */ + 0, /* (164) upsert ::= */ + -2, /* (165) upsert ::= RETURNING selcollist */ + -12, /* (166) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */ + -9, /* (167) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */ + -5, /* (168) upsert ::= ON CONFLICT DO NOTHING returning */ + -8, /* (169) upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */ + -2, /* (170) returning ::= RETURNING selcollist */ + -2, /* (171) insert_cmd ::= INSERT orconf */ + -1, /* (172) insert_cmd ::= REPLACE */ + 0, /* (173) idlist_opt ::= */ + -3, /* (174) idlist_opt ::= LP idlist RP */ + -3, /* (175) idlist ::= idlist COMMA nm */ + -1, /* (176) idlist ::= nm */ + -3, /* (177) expr ::= LP expr RP */ + -1, /* (178) expr ::= ID|INDEXED */ + -1, /* (179) expr ::= JOIN_KW */ + -3, /* (180) expr ::= nm DOT nm */ + -5, /* (181) expr ::= nm DOT nm DOT nm */ + -1, /* (182) term ::= NULL|FLOAT|BLOB */ + -1, /* (183) term ::= STRING */ + -1, /* (184) term ::= INTEGER */ + -1, /* (185) expr ::= VARIABLE */ + -3, /* (186) expr ::= expr COLLATE ID|STRING */ + -6, /* (187) expr ::= CAST LP expr AS typetoken RP */ + -5, /* (188) expr ::= ID|INDEXED LP distinct exprlist RP */ + -4, /* (189) expr ::= ID|INDEXED LP STAR RP */ + -6, /* (190) expr ::= ID|INDEXED LP distinct exprlist RP filter_over */ + -5, /* (191) expr ::= ID|INDEXED LP STAR RP filter_over */ + -1, /* (192) term ::= CTIME_KW */ + -5, /* (193) expr ::= LP nexprlist COMMA expr RP */ + -3, /* (194) expr ::= expr AND expr */ + -3, /* (195) expr ::= expr OR expr */ + -3, /* (196) expr ::= expr LT|GT|GE|LE expr */ + -3, /* (197) expr ::= expr EQ|NE expr */ + -3, /* (198) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ + -3, /* (199) expr ::= expr PLUS|MINUS expr */ + -3, /* (200) expr ::= expr STAR|SLASH|REM expr */ + -3, /* (201) expr ::= expr CONCAT expr */ + -2, /* (202) likeop ::= NOT LIKE_KW|MATCH */ + -3, /* (203) expr ::= expr likeop expr */ + -5, /* (204) expr ::= expr likeop expr ESCAPE expr */ + -2, /* (205) expr ::= expr ISNULL|NOTNULL */ + -3, /* (206) expr ::= expr NOT NULL */ + -3, /* (207) expr ::= expr IS expr */ + -4, /* (208) expr ::= expr IS NOT expr */ + -6, /* (209) expr ::= expr IS NOT DISTINCT FROM expr */ + -5, /* (210) expr ::= expr IS DISTINCT FROM expr */ + -2, /* (211) expr ::= NOT expr */ + -2, /* (212) expr ::= BITNOT expr */ + -2, /* (213) expr ::= PLUS|MINUS expr */ + -3, /* (214) expr ::= expr PTR expr */ + -1, /* (215) between_op ::= BETWEEN */ + -2, /* (216) between_op ::= NOT BETWEEN */ + -5, /* (217) expr ::= expr between_op expr AND expr */ + -1, /* (218) in_op ::= IN */ + -2, /* (219) in_op ::= NOT IN */ + -5, /* (220) expr ::= expr in_op LP exprlist RP */ + -3, /* (221) expr ::= LP select RP */ + -5, /* (222) expr ::= expr in_op LP select RP */ + -5, /* (223) expr ::= expr in_op nm dbnm paren_exprlist */ + -4, /* (224) expr ::= EXISTS LP select RP */ + -5, /* (225) expr ::= CASE case_operand case_exprlist case_else END */ + -5, /* (226) case_exprlist ::= case_exprlist WHEN expr THEN expr */ + -4, /* (227) case_exprlist ::= WHEN expr THEN expr */ + -2, /* (228) case_else ::= ELSE expr */ + 0, /* (229) case_else ::= */ + -1, /* (230) case_operand ::= expr */ + 0, /* (231) case_operand ::= */ + 0, /* (232) exprlist ::= */ + -3, /* (233) nexprlist ::= nexprlist COMMA expr */ + -1, /* (234) nexprlist ::= expr */ + 0, /* (235) paren_exprlist ::= */ + -3, /* (236) paren_exprlist ::= LP exprlist RP */ + -12, /* (237) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ + -1, /* (238) uniqueflag ::= UNIQUE */ + 0, /* (239) uniqueflag ::= */ + 0, /* (240) eidlist_opt ::= */ + -3, /* (241) eidlist_opt ::= LP eidlist RP */ + -5, /* (242) eidlist ::= eidlist COMMA nm collate sortorder */ + -3, /* (243) eidlist ::= nm collate sortorder */ + 0, /* (244) collate ::= */ + -2, /* (245) collate ::= COLLATE ID|STRING */ + -4, /* (246) cmd ::= DROP INDEX ifexists fullname */ + -2, /* (247) cmd ::= VACUUM vinto */ + -3, /* (248) cmd ::= VACUUM nm vinto */ + -2, /* (249) vinto ::= INTO expr */ + 0, /* (250) vinto ::= */ + -3, /* (251) cmd ::= PRAGMA nm dbnm */ + -5, /* (252) cmd ::= PRAGMA nm dbnm EQ nmnum */ + -6, /* (253) cmd ::= PRAGMA nm dbnm LP nmnum RP */ + -5, /* (254) cmd ::= PRAGMA nm dbnm EQ minus_num */ + -6, /* (255) cmd ::= PRAGMA nm dbnm LP minus_num RP */ + -2, /* (256) plus_num ::= PLUS INTEGER|FLOAT */ + -2, /* (257) minus_num ::= MINUS INTEGER|FLOAT */ + -5, /* (258) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ + -11, /* (259) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ + -1, /* (260) trigger_time ::= BEFORE|AFTER */ + -2, /* (261) trigger_time ::= INSTEAD OF */ + 0, /* (262) trigger_time ::= */ + -1, /* (263) trigger_event ::= DELETE|INSERT */ + -1, /* (264) trigger_event ::= UPDATE */ + -3, /* (265) trigger_event ::= UPDATE OF idlist */ + 0, /* (266) when_clause ::= */ + -2, /* (267) when_clause ::= WHEN expr */ + -3, /* (268) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ + -2, /* (269) trigger_cmd_list ::= trigger_cmd SEMI */ + -3, /* (270) trnm ::= nm DOT nm */ + -3, /* (271) tridxby ::= INDEXED BY nm */ + -2, /* (272) tridxby ::= NOT INDEXED */ + -9, /* (273) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */ + -8, /* (274) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ + -6, /* (275) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */ + -3, /* (276) trigger_cmd ::= scanpt select scanpt */ + -4, /* (277) expr ::= RAISE LP IGNORE RP */ + -6, /* (278) expr ::= RAISE LP raisetype COMMA nm RP */ + -1, /* (279) raisetype ::= ROLLBACK */ + -1, /* (280) raisetype ::= ABORT */ + -1, /* (281) raisetype ::= FAIL */ + -4, /* (282) cmd ::= DROP TRIGGER ifexists fullname */ + -6, /* (283) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ + -3, /* (284) cmd ::= DETACH database_kw_opt expr */ + 0, /* (285) key_opt ::= */ + -2, /* (286) key_opt ::= KEY expr */ + -1, /* (287) cmd ::= REINDEX */ + -3, /* (288) cmd ::= REINDEX nm dbnm */ + -1, /* (289) cmd ::= ANALYZE */ + -3, /* (290) cmd ::= ANALYZE nm dbnm */ + -6, /* (291) cmd ::= ALTER TABLE fullname RENAME TO nm */ + -7, /* (292) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ + -6, /* (293) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */ + -1, /* (294) add_column_fullname ::= fullname */ + -8, /* (295) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ + -1, /* (296) cmd ::= create_vtab */ + -4, /* (297) cmd ::= create_vtab LP vtabarglist RP */ + -8, /* (298) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ + 0, /* (299) vtabarg ::= */ + -1, /* (300) vtabargtoken ::= ANY */ + -3, /* (301) vtabargtoken ::= lp anylist RP */ + -1, /* (302) lp ::= LP */ + -2, /* (303) with ::= WITH wqlist */ + -3, /* (304) with ::= WITH RECURSIVE wqlist */ + -1, /* (305) wqas ::= AS */ + -2, /* (306) wqas ::= AS MATERIALIZED */ + -3, /* (307) wqas ::= AS NOT MATERIALIZED */ + -6, /* (308) wqitem ::= nm eidlist_opt wqas LP select RP */ + -1, /* (309) wqlist ::= wqitem */ + -3, /* (310) wqlist ::= wqlist COMMA wqitem */ + -1, /* (311) windowdefn_list ::= windowdefn */ + -3, /* (312) windowdefn_list ::= windowdefn_list COMMA windowdefn */ + -5, /* (313) windowdefn ::= nm AS LP window RP */ + -5, /* (314) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ + -6, /* (315) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ + -4, /* (316) window ::= ORDER BY sortlist frame_opt */ + -5, /* (317) window ::= nm ORDER BY sortlist frame_opt */ + -1, /* (318) window ::= frame_opt */ + -2, /* (319) window ::= nm frame_opt */ + 0, /* (320) frame_opt ::= */ + -3, /* (321) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ + -6, /* (322) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ + -1, /* (323) range_or_rows ::= RANGE|ROWS|GROUPS */ + -1, /* (324) frame_bound_s ::= frame_bound */ + -2, /* (325) frame_bound_s ::= UNBOUNDED PRECEDING */ + -1, /* (326) frame_bound_e ::= frame_bound */ + -2, /* (327) frame_bound_e ::= UNBOUNDED FOLLOWING */ + -2, /* (328) frame_bound ::= expr PRECEDING|FOLLOWING */ + -2, /* (329) frame_bound ::= CURRENT ROW */ + 0, /* (330) frame_exclude_opt ::= */ + -2, /* (331) frame_exclude_opt ::= EXCLUDE frame_exclude */ + -2, /* (332) frame_exclude ::= NO OTHERS */ + -2, /* (333) frame_exclude ::= CURRENT ROW */ + -1, /* (334) frame_exclude ::= GROUP|TIES */ + -2, /* (335) window_clause ::= WINDOW windowdefn_list */ + -2, /* (336) filter_over ::= filter_clause over_clause */ + -1, /* (337) filter_over ::= over_clause */ + -1, /* (338) filter_over ::= filter_clause */ + -4, /* (339) over_clause ::= OVER LP window RP */ + -2, /* (340) over_clause ::= OVER nm */ + -5, /* (341) filter_clause ::= FILTER LP WHERE expr RP */ + -1, /* (342) input ::= cmdlist */ + -2, /* (343) cmdlist ::= cmdlist ecmd */ + -1, /* (344) cmdlist ::= ecmd */ + -1, /* (345) ecmd ::= SEMI */ + -2, /* (346) ecmd ::= cmdx SEMI */ + -3, /* (347) ecmd ::= explain cmdx SEMI */ + 0, /* (348) trans_opt ::= */ + -1, /* (349) trans_opt ::= TRANSACTION */ + -2, /* (350) trans_opt ::= TRANSACTION nm */ + -1, /* (351) savepoint_opt ::= SAVEPOINT */ + 0, /* (352) savepoint_opt ::= */ + -2, /* (353) cmd ::= create_table create_table_args */ + -1, /* (354) table_option_set ::= table_option */ + -4, /* (355) columnlist ::= columnlist COMMA columnname carglist */ + -2, /* (356) columnlist ::= columnname carglist */ + -1, /* (357) nm ::= ID|INDEXED */ + -1, /* (358) nm ::= STRING */ + -1, /* (359) nm ::= JOIN_KW */ + -1, /* (360) typetoken ::= typename */ + -1, /* (361) typename ::= ID|STRING */ + -1, /* (362) signed ::= plus_num */ + -1, /* (363) signed ::= minus_num */ + -2, /* (364) carglist ::= carglist ccons */ + 0, /* (365) carglist ::= */ + -2, /* (366) ccons ::= NULL onconf */ + -4, /* (367) ccons ::= GENERATED ALWAYS AS generated */ + -2, /* (368) ccons ::= AS generated */ + -2, /* (369) conslist_opt ::= COMMA conslist */ + -3, /* (370) conslist ::= conslist tconscomma tcons */ + -1, /* (371) conslist ::= tcons */ + 0, /* (372) tconscomma ::= */ + -1, /* (373) defer_subclause_opt ::= defer_subclause */ + -1, /* (374) resolvetype ::= raisetype */ + -1, /* (375) selectnowith ::= oneselect */ + -1, /* (376) oneselect ::= values */ + -2, /* (377) sclp ::= selcollist COMMA */ + -1, /* (378) as ::= ID|STRING */ + -1, /* (379) indexed_opt ::= indexed_by */ + 0, /* (380) returning ::= */ + -1, /* (381) expr ::= term */ + -1, /* (382) likeop ::= LIKE_KW|MATCH */ + -1, /* (383) exprlist ::= nexprlist */ + -1, /* (384) nmnum ::= plus_num */ + -1, /* (385) nmnum ::= nm */ + -1, /* (386) nmnum ::= ON */ + -1, /* (387) nmnum ::= DELETE */ + -1, /* (388) nmnum ::= DEFAULT */ + -1, /* (389) plus_num ::= INTEGER|FLOAT */ + 0, /* (390) foreach_clause ::= */ + -3, /* (391) foreach_clause ::= FOR EACH ROW */ + -1, /* (392) trnm ::= nm */ + 0, /* (393) tridxby ::= */ + -1, /* (394) database_kw_opt ::= DATABASE */ + 0, /* (395) database_kw_opt ::= */ + 0, /* (396) kwcolumn_opt ::= */ + -1, /* (397) kwcolumn_opt ::= COLUMNKW */ + -1, /* (398) vtabarglist ::= vtabarg */ + -3, /* (399) vtabarglist ::= vtabarglist COMMA vtabarg */ + -2, /* (400) vtabarg ::= vtabarg vtabargtoken */ + 0, /* (401) anylist ::= */ + -4, /* (402) anylist ::= anylist LP anylist RP */ + -2, /* (403) anylist ::= anylist ANY */ + 0, /* (404) with ::= */ }; static void yy_accept(yyParser*); /* Forward Declaration */ @@ -160529,16 +167090,16 @@ static YYACTIONTYPE yy_reduce( { sqlite3FinishCoding(pParse); } break; case 3: /* cmd ::= BEGIN transtype trans_opt */ -{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy60);} +{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy394);} break; case 4: /* transtype ::= */ -{yymsp[1].minor.yy60 = TK_DEFERRED;} +{yymsp[1].minor.yy394 = TK_DEFERRED;} break; case 5: /* transtype ::= DEFERRED */ case 6: /* transtype ::= IMMEDIATE */ yytestcase(yyruleno==6); case 7: /* transtype ::= EXCLUSIVE */ yytestcase(yyruleno==7); - case 318: /* range_or_rows ::= RANGE|ROWS|GROUPS */ yytestcase(yyruleno==318); -{yymsp[0].minor.yy60 = yymsp[0].major; /*A-overwrites-X*/} + case 323: /* range_or_rows ::= RANGE|ROWS|GROUPS */ yytestcase(yyruleno==323); +{yymsp[0].minor.yy394 = yymsp[0].major; /*A-overwrites-X*/} break; case 8: /* cmd ::= COMMIT|END trans_opt */ case 9: /* cmd ::= ROLLBACK trans_opt */ yytestcase(yyruleno==9); @@ -160561,7 +167122,7 @@ static YYACTIONTYPE yy_reduce( break; case 13: /* create_table ::= createkw temp TABLE ifnotexists nm dbnm */ { - sqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy60,0,0,yymsp[-2].minor.yy60); + sqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy394,0,0,yymsp[-2].minor.yy394); } break; case 14: /* createkw ::= CREATE */ @@ -160569,96 +167130,112 @@ static YYACTIONTYPE yy_reduce( break; case 15: /* ifnotexists ::= */ case 18: /* temp ::= */ yytestcase(yyruleno==18); - case 21: /* table_options ::= */ yytestcase(yyruleno==21); - case 45: /* autoinc ::= */ yytestcase(yyruleno==45); - case 60: /* init_deferred_pred_opt ::= */ yytestcase(yyruleno==60); - case 70: /* defer_subclause_opt ::= */ yytestcase(yyruleno==70); - case 79: /* ifexists ::= */ yytestcase(yyruleno==79); - case 96: /* distinct ::= */ yytestcase(yyruleno==96); - case 239: /* collate ::= */ yytestcase(yyruleno==239); -{yymsp[1].minor.yy60 = 0;} + case 47: /* autoinc ::= */ yytestcase(yyruleno==47); + case 62: /* init_deferred_pred_opt ::= */ yytestcase(yyruleno==62); + case 72: /* defer_subclause_opt ::= */ yytestcase(yyruleno==72); + case 81: /* ifexists ::= */ yytestcase(yyruleno==81); + case 98: /* distinct ::= */ yytestcase(yyruleno==98); + case 244: /* collate ::= */ yytestcase(yyruleno==244); +{yymsp[1].minor.yy394 = 0;} break; case 16: /* ifnotexists ::= IF NOT EXISTS */ -{yymsp[-2].minor.yy60 = 1;} +{yymsp[-2].minor.yy394 = 1;} break; case 17: /* temp ::= TEMP */ - case 46: /* autoinc ::= AUTOINCR */ yytestcase(yyruleno==46); -{yymsp[0].minor.yy60 = 1;} +{yymsp[0].minor.yy394 = pParse->db->init.busy==0;} break; - case 19: /* create_table_args ::= LP columnlist conslist_opt RP table_options */ + case 19: /* create_table_args ::= LP columnlist conslist_opt RP table_option_set */ { - sqlite3EndTable(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,yymsp[0].minor.yy60,0); + sqlite3EndTable(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,yymsp[0].minor.yy285,0); } break; case 20: /* create_table_args ::= AS select */ { - sqlite3EndTable(pParse,0,0,0,yymsp[0].minor.yy307); - sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy307); + sqlite3EndTable(pParse,0,0,0,yymsp[0].minor.yy47); + sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy47); } break; - case 22: /* table_options ::= WITHOUT nm */ + case 21: /* table_option_set ::= */ +{yymsp[1].minor.yy285 = 0;} + break; + case 22: /* table_option_set ::= table_option_set COMMA table_option */ +{yylhsminor.yy285 = yymsp[-2].minor.yy285|yymsp[0].minor.yy285;} + yymsp[-2].minor.yy285 = yylhsminor.yy285; + break; + case 23: /* table_option ::= WITHOUT nm */ { if( yymsp[0].minor.yy0.n==5 && sqlite3_strnicmp(yymsp[0].minor.yy0.z,"rowid",5)==0 ){ - yymsp[-1].minor.yy60 = TF_WithoutRowid | TF_NoVisibleRowid; + yymsp[-1].minor.yy285 = TF_WithoutRowid | TF_NoVisibleRowid; }else{ - yymsp[-1].minor.yy60 = 0; + yymsp[-1].minor.yy285 = 0; sqlite3ErrorMsg(pParse, "unknown table option: %.*s", yymsp[0].minor.yy0.n, yymsp[0].minor.yy0.z); } } break; - case 23: /* columnname ::= nm typetoken */ -{sqlite3AddColumn(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);} + case 24: /* table_option ::= nm */ +{ + if( yymsp[0].minor.yy0.n==6 && sqlite3_strnicmp(yymsp[0].minor.yy0.z,"strict",6)==0 ){ + yylhsminor.yy285 = TF_Strict; + }else{ + yylhsminor.yy285 = 0; + sqlite3ErrorMsg(pParse, "unknown table option: %.*s", yymsp[0].minor.yy0.n, yymsp[0].minor.yy0.z); + } +} + yymsp[0].minor.yy285 = yylhsminor.yy285; break; - case 24: /* typetoken ::= */ - case 63: /* conslist_opt ::= */ yytestcase(yyruleno==63); - case 102: /* as ::= */ yytestcase(yyruleno==102); + case 25: /* columnname ::= nm typetoken */ +{sqlite3AddColumn(pParse,yymsp[-1].minor.yy0,yymsp[0].minor.yy0);} + break; + case 26: /* typetoken ::= */ + case 65: /* conslist_opt ::= */ yytestcase(yyruleno==65); + case 104: /* as ::= */ yytestcase(yyruleno==104); {yymsp[1].minor.yy0.n = 0; yymsp[1].minor.yy0.z = 0;} break; - case 25: /* typetoken ::= typename LP signed RP */ + case 27: /* typetoken ::= typename LP signed RP */ { yymsp[-3].minor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-3].minor.yy0.z); } break; - case 26: /* typetoken ::= typename LP signed COMMA signed RP */ + case 28: /* typetoken ::= typename LP signed COMMA signed RP */ { yymsp[-5].minor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-5].minor.yy0.z); } break; - case 27: /* typename ::= typename ID|STRING */ + case 29: /* typename ::= typename ID|STRING */ {yymsp[-1].minor.yy0.n=yymsp[0].minor.yy0.n+(int)(yymsp[0].minor.yy0.z-yymsp[-1].minor.yy0.z);} break; - case 28: /* scanpt ::= */ + case 30: /* scanpt ::= */ { assert( yyLookahead!=YYNOCODE ); - yymsp[1].minor.yy528 = yyLookaheadToken.z; + yymsp[1].minor.yy522 = yyLookaheadToken.z; } break; - case 29: /* scantok ::= */ + case 31: /* scantok ::= */ { assert( yyLookahead!=YYNOCODE ); yymsp[1].minor.yy0 = yyLookaheadToken; } break; - case 30: /* ccons ::= CONSTRAINT nm */ - case 65: /* tcons ::= CONSTRAINT nm */ yytestcase(yyruleno==65); + case 32: /* ccons ::= CONSTRAINT nm */ + case 67: /* tcons ::= CONSTRAINT nm */ yytestcase(yyruleno==67); {pParse->constraintName = yymsp[0].minor.yy0;} break; - case 31: /* ccons ::= DEFAULT scantok term */ -{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy602,yymsp[-1].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);} + case 33: /* ccons ::= DEFAULT scantok term */ +{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy528,yymsp[-1].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);} break; - case 32: /* ccons ::= DEFAULT LP expr RP */ -{sqlite3AddDefaultValue(pParse,yymsp[-1].minor.yy602,yymsp[-2].minor.yy0.z+1,yymsp[0].minor.yy0.z);} + case 34: /* ccons ::= DEFAULT LP expr RP */ +{sqlite3AddDefaultValue(pParse,yymsp[-1].minor.yy528,yymsp[-2].minor.yy0.z+1,yymsp[0].minor.yy0.z);} break; - case 33: /* ccons ::= DEFAULT PLUS scantok term */ -{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy602,yymsp[-2].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);} + case 35: /* ccons ::= DEFAULT PLUS scantok term */ +{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy528,yymsp[-2].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);} break; - case 34: /* ccons ::= DEFAULT MINUS scantok term */ + case 36: /* ccons ::= DEFAULT MINUS scantok term */ { - Expr *p = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy602, 0); + Expr *p = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy528, 0); sqlite3AddDefaultValue(pParse,p,yymsp[-2].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]); } break; - case 35: /* ccons ::= DEFAULT scantok ID|INDEXED */ + case 37: /* ccons ::= DEFAULT scantok ID|INDEXED */ { Expr *p = tokenExpr(pParse, TK_STRING, yymsp[0].minor.yy0); if( p ){ @@ -160668,305 +167245,316 @@ static YYACTIONTYPE yy_reduce( sqlite3AddDefaultValue(pParse,p,yymsp[0].minor.yy0.z,yymsp[0].minor.yy0.z+yymsp[0].minor.yy0.n); } break; - case 36: /* ccons ::= NOT NULL onconf */ -{sqlite3AddNotNull(pParse, yymsp[0].minor.yy60);} + case 38: /* ccons ::= NOT NULL onconf */ +{sqlite3AddNotNull(pParse, yymsp[0].minor.yy394);} break; - case 37: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */ -{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy60,yymsp[0].minor.yy60,yymsp[-2].minor.yy60);} + case 39: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */ +{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy394,yymsp[0].minor.yy394,yymsp[-2].minor.yy394);} break; - case 38: /* ccons ::= UNIQUE onconf */ -{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy60,0,0,0,0, + case 40: /* ccons ::= UNIQUE onconf */ +{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy394,0,0,0,0, SQLITE_IDXTYPE_UNIQUE);} break; - case 39: /* ccons ::= CHECK LP expr RP */ -{sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy602,yymsp[-2].minor.yy0.z,yymsp[0].minor.yy0.z);} + case 41: /* ccons ::= CHECK LP expr RP */ +{sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy528,yymsp[-2].minor.yy0.z,yymsp[0].minor.yy0.z);} break; - case 40: /* ccons ::= REFERENCES nm eidlist_opt refargs */ -{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy338,yymsp[0].minor.yy60);} + case 42: /* ccons ::= REFERENCES nm eidlist_opt refargs */ +{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy322,yymsp[0].minor.yy394);} break; - case 41: /* ccons ::= defer_subclause */ -{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy60);} + case 43: /* ccons ::= defer_subclause */ +{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy394);} break; - case 42: /* ccons ::= COLLATE ID|STRING */ + case 44: /* ccons ::= COLLATE ID|STRING */ {sqlite3AddCollateType(pParse, &yymsp[0].minor.yy0);} break; - case 43: /* generated ::= LP expr RP */ -{sqlite3AddGenerated(pParse,yymsp[-1].minor.yy602,0);} + case 45: /* generated ::= LP expr RP */ +{sqlite3AddGenerated(pParse,yymsp[-1].minor.yy528,0);} break; - case 44: /* generated ::= LP expr RP ID */ -{sqlite3AddGenerated(pParse,yymsp[-2].minor.yy602,&yymsp[0].minor.yy0);} + case 46: /* generated ::= LP expr RP ID */ +{sqlite3AddGenerated(pParse,yymsp[-2].minor.yy528,&yymsp[0].minor.yy0);} break; - case 47: /* refargs ::= */ -{ yymsp[1].minor.yy60 = OE_None*0x0101; /* EV: R-19803-45884 */} + case 48: /* autoinc ::= AUTOINCR */ +{yymsp[0].minor.yy394 = 1;} break; - case 48: /* refargs ::= refargs refarg */ -{ yymsp[-1].minor.yy60 = (yymsp[-1].minor.yy60 & ~yymsp[0].minor.yy615.mask) | yymsp[0].minor.yy615.value; } + case 49: /* refargs ::= */ +{ yymsp[1].minor.yy394 = OE_None*0x0101; /* EV: R-19803-45884 */} break; - case 49: /* refarg ::= MATCH nm */ -{ yymsp[-1].minor.yy615.value = 0; yymsp[-1].minor.yy615.mask = 0x000000; } + case 50: /* refargs ::= refargs refarg */ +{ yymsp[-1].minor.yy394 = (yymsp[-1].minor.yy394 & ~yymsp[0].minor.yy231.mask) | yymsp[0].minor.yy231.value; } break; - case 50: /* refarg ::= ON INSERT refact */ -{ yymsp[-2].minor.yy615.value = 0; yymsp[-2].minor.yy615.mask = 0x000000; } + case 51: /* refarg ::= MATCH nm */ +{ yymsp[-1].minor.yy231.value = 0; yymsp[-1].minor.yy231.mask = 0x000000; } break; - case 51: /* refarg ::= ON DELETE refact */ -{ yymsp[-2].minor.yy615.value = yymsp[0].minor.yy60; yymsp[-2].minor.yy615.mask = 0x0000ff; } + case 52: /* refarg ::= ON INSERT refact */ +{ yymsp[-2].minor.yy231.value = 0; yymsp[-2].minor.yy231.mask = 0x000000; } break; - case 52: /* refarg ::= ON UPDATE refact */ -{ yymsp[-2].minor.yy615.value = yymsp[0].minor.yy60<<8; yymsp[-2].minor.yy615.mask = 0x00ff00; } + case 53: /* refarg ::= ON DELETE refact */ +{ yymsp[-2].minor.yy231.value = yymsp[0].minor.yy394; yymsp[-2].minor.yy231.mask = 0x0000ff; } break; - case 53: /* refact ::= SET NULL */ -{ yymsp[-1].minor.yy60 = OE_SetNull; /* EV: R-33326-45252 */} + case 54: /* refarg ::= ON UPDATE refact */ +{ yymsp[-2].minor.yy231.value = yymsp[0].minor.yy394<<8; yymsp[-2].minor.yy231.mask = 0x00ff00; } break; - case 54: /* refact ::= SET DEFAULT */ -{ yymsp[-1].minor.yy60 = OE_SetDflt; /* EV: R-33326-45252 */} + case 55: /* refact ::= SET NULL */ +{ yymsp[-1].minor.yy394 = OE_SetNull; /* EV: R-33326-45252 */} break; - case 55: /* refact ::= CASCADE */ -{ yymsp[0].minor.yy60 = OE_Cascade; /* EV: R-33326-45252 */} + case 56: /* refact ::= SET DEFAULT */ +{ yymsp[-1].minor.yy394 = OE_SetDflt; /* EV: R-33326-45252 */} break; - case 56: /* refact ::= RESTRICT */ -{ yymsp[0].minor.yy60 = OE_Restrict; /* EV: R-33326-45252 */} + case 57: /* refact ::= CASCADE */ +{ yymsp[0].minor.yy394 = OE_Cascade; /* EV: R-33326-45252 */} break; - case 57: /* refact ::= NO ACTION */ -{ yymsp[-1].minor.yy60 = OE_None; /* EV: R-33326-45252 */} + case 58: /* refact ::= RESTRICT */ +{ yymsp[0].minor.yy394 = OE_Restrict; /* EV: R-33326-45252 */} break; - case 58: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ -{yymsp[-2].minor.yy60 = 0;} + case 59: /* refact ::= NO ACTION */ +{ yymsp[-1].minor.yy394 = OE_None; /* EV: R-33326-45252 */} break; - case 59: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ - case 74: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==74); - case 169: /* insert_cmd ::= INSERT orconf */ yytestcase(yyruleno==169); -{yymsp[-1].minor.yy60 = yymsp[0].minor.yy60;} + case 60: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ +{yymsp[-2].minor.yy394 = 0;} break; - case 61: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ - case 78: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==78); - case 211: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==211); - case 214: /* in_op ::= NOT IN */ yytestcase(yyruleno==214); - case 240: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==240); -{yymsp[-1].minor.yy60 = 1;} + case 61: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ + case 76: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==76); + case 171: /* insert_cmd ::= INSERT orconf */ yytestcase(yyruleno==171); +{yymsp[-1].minor.yy394 = yymsp[0].minor.yy394;} break; - case 62: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ -{yymsp[-1].minor.yy60 = 0;} + case 63: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ + case 80: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==80); + case 216: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==216); + case 219: /* in_op ::= NOT IN */ yytestcase(yyruleno==219); + case 245: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==245); +{yymsp[-1].minor.yy394 = 1;} break; - case 64: /* tconscomma ::= COMMA */ + case 64: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ +{yymsp[-1].minor.yy394 = 0;} + break; + case 66: /* tconscomma ::= COMMA */ {pParse->constraintName.n = 0;} break; - case 66: /* tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ -{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy338,yymsp[0].minor.yy60,yymsp[-2].minor.yy60,0);} + case 68: /* tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ +{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy322,yymsp[0].minor.yy394,yymsp[-2].minor.yy394,0);} break; - case 67: /* tcons ::= UNIQUE LP sortlist RP onconf */ -{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy338,yymsp[0].minor.yy60,0,0,0,0, + case 69: /* tcons ::= UNIQUE LP sortlist RP onconf */ +{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy322,yymsp[0].minor.yy394,0,0,0,0, SQLITE_IDXTYPE_UNIQUE);} break; - case 68: /* tcons ::= CHECK LP expr RP onconf */ -{sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy602,yymsp[-3].minor.yy0.z,yymsp[-1].minor.yy0.z);} + case 70: /* tcons ::= CHECK LP expr RP onconf */ +{sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy528,yymsp[-3].minor.yy0.z,yymsp[-1].minor.yy0.z);} break; - case 69: /* tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ + case 71: /* tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ { - sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy338, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy338, yymsp[-1].minor.yy60); - sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy60); + sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy322, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy322, yymsp[-1].minor.yy394); + sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy394); } break; - case 71: /* onconf ::= */ - case 73: /* orconf ::= */ yytestcase(yyruleno==73); -{yymsp[1].minor.yy60 = OE_Default;} + case 73: /* onconf ::= */ + case 75: /* orconf ::= */ yytestcase(yyruleno==75); +{yymsp[1].minor.yy394 = OE_Default;} break; - case 72: /* onconf ::= ON CONFLICT resolvetype */ -{yymsp[-2].minor.yy60 = yymsp[0].minor.yy60;} + case 74: /* onconf ::= ON CONFLICT resolvetype */ +{yymsp[-2].minor.yy394 = yymsp[0].minor.yy394;} break; - case 75: /* resolvetype ::= IGNORE */ -{yymsp[0].minor.yy60 = OE_Ignore;} + case 77: /* resolvetype ::= IGNORE */ +{yymsp[0].minor.yy394 = OE_Ignore;} break; - case 76: /* resolvetype ::= REPLACE */ - case 170: /* insert_cmd ::= REPLACE */ yytestcase(yyruleno==170); -{yymsp[0].minor.yy60 = OE_Replace;} + case 78: /* resolvetype ::= REPLACE */ + case 172: /* insert_cmd ::= REPLACE */ yytestcase(yyruleno==172); +{yymsp[0].minor.yy394 = OE_Replace;} break; - case 77: /* cmd ::= DROP TABLE ifexists fullname */ + case 79: /* cmd ::= DROP TABLE ifexists fullname */ { - sqlite3DropTable(pParse, yymsp[0].minor.yy291, 0, yymsp[-1].minor.yy60); + sqlite3DropTable(pParse, yymsp[0].minor.yy131, 0, yymsp[-1].minor.yy394); } break; - case 80: /* cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ + case 82: /* cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ { - sqlite3CreateView(pParse, &yymsp[-8].minor.yy0, &yymsp[-4].minor.yy0, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy338, yymsp[0].minor.yy307, yymsp[-7].minor.yy60, yymsp[-5].minor.yy60); + sqlite3CreateView(pParse, &yymsp[-8].minor.yy0, &yymsp[-4].minor.yy0, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy322, yymsp[0].minor.yy47, yymsp[-7].minor.yy394, yymsp[-5].minor.yy394); } break; - case 81: /* cmd ::= DROP VIEW ifexists fullname */ + case 83: /* cmd ::= DROP VIEW ifexists fullname */ { - sqlite3DropTable(pParse, yymsp[0].minor.yy291, 1, yymsp[-1].minor.yy60); + sqlite3DropTable(pParse, yymsp[0].minor.yy131, 1, yymsp[-1].minor.yy394); } break; - case 82: /* cmd ::= select */ + case 84: /* cmd ::= select */ { SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0, 0}; - sqlite3Select(pParse, yymsp[0].minor.yy307, &dest); - sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy307); + sqlite3Select(pParse, yymsp[0].minor.yy47, &dest); + sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy47); } break; - case 83: /* select ::= WITH wqlist selectnowith */ -{yymsp[-2].minor.yy307 = attachWithToSelect(pParse,yymsp[0].minor.yy307,yymsp[-1].minor.yy195);} + case 85: /* select ::= WITH wqlist selectnowith */ +{yymsp[-2].minor.yy47 = attachWithToSelect(pParse,yymsp[0].minor.yy47,yymsp[-1].minor.yy521);} break; - case 84: /* select ::= WITH RECURSIVE wqlist selectnowith */ -{yymsp[-3].minor.yy307 = attachWithToSelect(pParse,yymsp[0].minor.yy307,yymsp[-1].minor.yy195);} + case 86: /* select ::= WITH RECURSIVE wqlist selectnowith */ +{yymsp[-3].minor.yy47 = attachWithToSelect(pParse,yymsp[0].minor.yy47,yymsp[-1].minor.yy521);} break; - case 85: /* select ::= selectnowith */ + case 87: /* select ::= selectnowith */ { - Select *p = yymsp[0].minor.yy307; + Select *p = yymsp[0].minor.yy47; if( p ){ parserDoubleLinkSelect(pParse, p); } - yymsp[0].minor.yy307 = p; /*A-overwrites-X*/ + yymsp[0].minor.yy47 = p; /*A-overwrites-X*/ } break; - case 86: /* selectnowith ::= selectnowith multiselect_op oneselect */ + case 88: /* selectnowith ::= selectnowith multiselect_op oneselect */ { - Select *pRhs = yymsp[0].minor.yy307; - Select *pLhs = yymsp[-2].minor.yy307; + Select *pRhs = yymsp[0].minor.yy47; + Select *pLhs = yymsp[-2].minor.yy47; if( pRhs && pRhs->pPrior ){ SrcList *pFrom; Token x; x.n = 0; parserDoubleLinkSelect(pParse, pRhs); - pFrom = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&x,pRhs,0,0); + pFrom = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&x,pRhs,0); pRhs = sqlite3SelectNew(pParse,0,pFrom,0,0,0,0,0,0); } if( pRhs ){ - pRhs->op = (u8)yymsp[-1].minor.yy60; + pRhs->op = (u8)yymsp[-1].minor.yy394; pRhs->pPrior = pLhs; if( ALWAYS(pLhs) ) pLhs->selFlags &= ~SF_MultiValue; pRhs->selFlags &= ~SF_MultiValue; - if( yymsp[-1].minor.yy60!=TK_ALL ) pParse->hasCompound = 1; + if( yymsp[-1].minor.yy394!=TK_ALL ) pParse->hasCompound = 1; }else{ sqlite3SelectDelete(pParse->db, pLhs); } - yymsp[-2].minor.yy307 = pRhs; + yymsp[-2].minor.yy47 = pRhs; } break; - case 87: /* multiselect_op ::= UNION */ - case 89: /* multiselect_op ::= EXCEPT|INTERSECT */ yytestcase(yyruleno==89); -{yymsp[0].minor.yy60 = yymsp[0].major; /*A-overwrites-OP*/} + case 89: /* multiselect_op ::= UNION */ + case 91: /* multiselect_op ::= EXCEPT|INTERSECT */ yytestcase(yyruleno==91); +{yymsp[0].minor.yy394 = yymsp[0].major; /*A-overwrites-OP*/} break; - case 88: /* multiselect_op ::= UNION ALL */ -{yymsp[-1].minor.yy60 = TK_ALL;} + case 90: /* multiselect_op ::= UNION ALL */ +{yymsp[-1].minor.yy394 = TK_ALL;} break; - case 90: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ + case 92: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ { - yymsp[-8].minor.yy307 = sqlite3SelectNew(pParse,yymsp[-6].minor.yy338,yymsp[-5].minor.yy291,yymsp[-4].minor.yy602,yymsp[-3].minor.yy338,yymsp[-2].minor.yy602,yymsp[-1].minor.yy338,yymsp[-7].minor.yy60,yymsp[0].minor.yy602); + yymsp[-8].minor.yy47 = sqlite3SelectNew(pParse,yymsp[-6].minor.yy322,yymsp[-5].minor.yy131,yymsp[-4].minor.yy528,yymsp[-3].minor.yy322,yymsp[-2].minor.yy528,yymsp[-1].minor.yy322,yymsp[-7].minor.yy394,yymsp[0].minor.yy528); } break; - case 91: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ + case 93: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ { - yymsp[-9].minor.yy307 = sqlite3SelectNew(pParse,yymsp[-7].minor.yy338,yymsp[-6].minor.yy291,yymsp[-5].minor.yy602,yymsp[-4].minor.yy338,yymsp[-3].minor.yy602,yymsp[-1].minor.yy338,yymsp[-8].minor.yy60,yymsp[0].minor.yy602); - if( yymsp[-9].minor.yy307 ){ - yymsp[-9].minor.yy307->pWinDefn = yymsp[-2].minor.yy19; + yymsp[-9].minor.yy47 = sqlite3SelectNew(pParse,yymsp[-7].minor.yy322,yymsp[-6].minor.yy131,yymsp[-5].minor.yy528,yymsp[-4].minor.yy322,yymsp[-3].minor.yy528,yymsp[-1].minor.yy322,yymsp[-8].minor.yy394,yymsp[0].minor.yy528); + if( yymsp[-9].minor.yy47 ){ + yymsp[-9].minor.yy47->pWinDefn = yymsp[-2].minor.yy41; }else{ - sqlite3WindowListDelete(pParse->db, yymsp[-2].minor.yy19); + sqlite3WindowListDelete(pParse->db, yymsp[-2].minor.yy41); } } break; - case 92: /* values ::= VALUES LP nexprlist RP */ + case 94: /* values ::= VALUES LP nexprlist RP */ { - yymsp[-3].minor.yy307 = sqlite3SelectNew(pParse,yymsp[-1].minor.yy338,0,0,0,0,0,SF_Values,0); + yymsp[-3].minor.yy47 = sqlite3SelectNew(pParse,yymsp[-1].minor.yy322,0,0,0,0,0,SF_Values,0); } break; - case 93: /* values ::= values COMMA LP nexprlist RP */ + case 95: /* values ::= values COMMA LP nexprlist RP */ { - Select *pRight, *pLeft = yymsp[-4].minor.yy307; - pRight = sqlite3SelectNew(pParse,yymsp[-1].minor.yy338,0,0,0,0,0,SF_Values|SF_MultiValue,0); + Select *pRight, *pLeft = yymsp[-4].minor.yy47; + pRight = sqlite3SelectNew(pParse,yymsp[-1].minor.yy322,0,0,0,0,0,SF_Values|SF_MultiValue,0); if( ALWAYS(pLeft) ) pLeft->selFlags &= ~SF_MultiValue; if( pRight ){ pRight->op = TK_ALL; pRight->pPrior = pLeft; - yymsp[-4].minor.yy307 = pRight; + yymsp[-4].minor.yy47 = pRight; }else{ - yymsp[-4].minor.yy307 = pLeft; + yymsp[-4].minor.yy47 = pLeft; } } break; - case 94: /* distinct ::= DISTINCT */ -{yymsp[0].minor.yy60 = SF_Distinct;} + case 96: /* distinct ::= DISTINCT */ +{yymsp[0].minor.yy394 = SF_Distinct;} break; - case 95: /* distinct ::= ALL */ -{yymsp[0].minor.yy60 = SF_All;} + case 97: /* distinct ::= ALL */ +{yymsp[0].minor.yy394 = SF_All;} break; - case 97: /* sclp ::= */ - case 130: /* orderby_opt ::= */ yytestcase(yyruleno==130); - case 140: /* groupby_opt ::= */ yytestcase(yyruleno==140); - case 227: /* exprlist ::= */ yytestcase(yyruleno==227); - case 230: /* paren_exprlist ::= */ yytestcase(yyruleno==230); - case 235: /* eidlist_opt ::= */ yytestcase(yyruleno==235); -{yymsp[1].minor.yy338 = 0;} + case 99: /* sclp ::= */ + case 132: /* orderby_opt ::= */ yytestcase(yyruleno==132); + case 142: /* groupby_opt ::= */ yytestcase(yyruleno==142); + case 232: /* exprlist ::= */ yytestcase(yyruleno==232); + case 235: /* paren_exprlist ::= */ yytestcase(yyruleno==235); + case 240: /* eidlist_opt ::= */ yytestcase(yyruleno==240); +{yymsp[1].minor.yy322 = 0;} break; - case 98: /* selcollist ::= sclp scanpt expr scanpt as */ + case 100: /* selcollist ::= sclp scanpt expr scanpt as */ { - yymsp[-4].minor.yy338 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy338, yymsp[-2].minor.yy602); - if( yymsp[0].minor.yy0.n>0 ) sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy338, &yymsp[0].minor.yy0, 1); - sqlite3ExprListSetSpan(pParse,yymsp[-4].minor.yy338,yymsp[-3].minor.yy528,yymsp[-1].minor.yy528); + yymsp[-4].minor.yy322 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy322, yymsp[-2].minor.yy528); + if( yymsp[0].minor.yy0.n>0 ) sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy322, &yymsp[0].minor.yy0, 1); + sqlite3ExprListSetSpan(pParse,yymsp[-4].minor.yy322,yymsp[-3].minor.yy522,yymsp[-1].minor.yy522); } break; - case 99: /* selcollist ::= sclp scanpt STAR */ + case 101: /* selcollist ::= sclp scanpt STAR */ { Expr *p = sqlite3Expr(pParse->db, TK_ASTERISK, 0); - yymsp[-2].minor.yy338 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy338, p); + yymsp[-2].minor.yy322 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy322, p); } break; - case 100: /* selcollist ::= sclp scanpt nm DOT STAR */ + case 102: /* selcollist ::= sclp scanpt nm DOT STAR */ { Expr *pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0); - Expr *pLeft = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1); + Expr *pLeft = tokenExpr(pParse, TK_ID, yymsp[-2].minor.yy0); Expr *pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); - yymsp[-4].minor.yy338 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy338, pDot); + yymsp[-4].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy322, pDot); } break; - case 101: /* as ::= AS nm */ - case 112: /* dbnm ::= DOT nm */ yytestcase(yyruleno==112); - case 251: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==251); - case 252: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==252); + case 103: /* as ::= AS nm */ + case 115: /* dbnm ::= DOT nm */ yytestcase(yyruleno==115); + case 256: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==256); + case 257: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==257); {yymsp[-1].minor.yy0 = yymsp[0].minor.yy0;} break; - case 103: /* from ::= */ - case 106: /* stl_prefix ::= */ yytestcase(yyruleno==106); -{yymsp[1].minor.yy291 = 0;} + case 105: /* from ::= */ + case 108: /* stl_prefix ::= */ yytestcase(yyruleno==108); +{yymsp[1].minor.yy131 = 0;} break; - case 104: /* from ::= FROM seltablist */ + case 106: /* from ::= FROM seltablist */ { - yymsp[-1].minor.yy291 = yymsp[0].minor.yy291; - sqlite3SrcListShiftJoinType(yymsp[-1].minor.yy291); + yymsp[-1].minor.yy131 = yymsp[0].minor.yy131; + sqlite3SrcListShiftJoinType(pParse,yymsp[-1].minor.yy131); } break; - case 105: /* stl_prefix ::= seltablist joinop */ + case 107: /* stl_prefix ::= seltablist joinop */ { - if( ALWAYS(yymsp[-1].minor.yy291 && yymsp[-1].minor.yy291->nSrc>0) ) yymsp[-1].minor.yy291->a[yymsp[-1].minor.yy291->nSrc-1].fg.jointype = (u8)yymsp[0].minor.yy60; + if( ALWAYS(yymsp[-1].minor.yy131 && yymsp[-1].minor.yy131->nSrc>0) ) yymsp[-1].minor.yy131->a[yymsp[-1].minor.yy131->nSrc-1].fg.jointype = (u8)yymsp[0].minor.yy394; } break; - case 107: /* seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */ + case 109: /* seltablist ::= stl_prefix nm dbnm as on_using */ { - yymsp[-6].minor.yy291 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy291,&yymsp[-5].minor.yy0,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,0,yymsp[-1].minor.yy602,yymsp[0].minor.yy288); - sqlite3SrcListIndexedBy(pParse, yymsp[-6].minor.yy291, &yymsp[-2].minor.yy0); + yymsp[-4].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-4].minor.yy131,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy561); } break; - case 108: /* seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */ + case 110: /* seltablist ::= stl_prefix nm dbnm as indexed_by on_using */ { - yymsp[-8].minor.yy291 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-8].minor.yy291,&yymsp[-7].minor.yy0,&yymsp[-6].minor.yy0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy602,yymsp[0].minor.yy288); - sqlite3SrcListFuncArgs(pParse, yymsp[-8].minor.yy291, yymsp[-4].minor.yy338); + yymsp[-5].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy131,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,0,&yymsp[0].minor.yy561); + sqlite3SrcListIndexedBy(pParse, yymsp[-5].minor.yy131, &yymsp[-1].minor.yy0); } break; - case 109: /* seltablist ::= stl_prefix LP select RP as on_opt using_opt */ + case 111: /* seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using */ { - yymsp[-6].minor.yy291 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy291,0,0,&yymsp[-2].minor.yy0,yymsp[-4].minor.yy307,yymsp[-1].minor.yy602,yymsp[0].minor.yy288); + yymsp[-7].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-7].minor.yy131,&yymsp[-6].minor.yy0,&yymsp[-5].minor.yy0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy561); + sqlite3SrcListFuncArgs(pParse, yymsp[-7].minor.yy131, yymsp[-3].minor.yy322); +} + break; + case 112: /* seltablist ::= stl_prefix LP select RP as on_using */ +{ + yymsp[-5].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy131,0,0,&yymsp[-1].minor.yy0,yymsp[-3].minor.yy47,&yymsp[0].minor.yy561); } break; - case 110: /* seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */ + case 113: /* seltablist ::= stl_prefix LP seltablist RP as on_using */ { - if( yymsp[-6].minor.yy291==0 && yymsp[-2].minor.yy0.n==0 && yymsp[-1].minor.yy602==0 && yymsp[0].minor.yy288==0 ){ - yymsp[-6].minor.yy291 = yymsp[-4].minor.yy291; - }else if( yymsp[-4].minor.yy291->nSrc==1 ){ - yymsp[-6].minor.yy291 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy291,0,0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy602,yymsp[0].minor.yy288); - if( yymsp[-6].minor.yy291 ){ - SrcItem *pNew = &yymsp[-6].minor.yy291->a[yymsp[-6].minor.yy291->nSrc-1]; - SrcItem *pOld = yymsp[-4].minor.yy291->a; + if( yymsp[-5].minor.yy131==0 && yymsp[-1].minor.yy0.n==0 && yymsp[0].minor.yy561.pOn==0 && yymsp[0].minor.yy561.pUsing==0 ){ + yymsp[-5].minor.yy131 = yymsp[-3].minor.yy131; + }else if( yymsp[-3].minor.yy131->nSrc==1 ){ + yymsp[-5].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy131,0,0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy561); + if( yymsp[-5].minor.yy131 ){ + SrcItem *pNew = &yymsp[-5].minor.yy131->a[yymsp[-5].minor.yy131->nSrc-1]; + SrcItem *pOld = yymsp[-3].minor.yy131->a; pNew->zName = pOld->zName; pNew->zDatabase = pOld->zDatabase; pNew->pSelect = pOld->pSelect; + if( pNew->pSelect && (pNew->pSelect->selFlags & SF_NestedFrom)!=0 ){ + pNew->fg.isNestedFrom = 1; + } if( pOld->fg.isTabFunc ){ pNew->u1.pFuncArg = pOld->u1.pFuncArg; pOld->u1.pFuncArg = 0; @@ -160976,267 +167564,277 @@ static YYACTIONTYPE yy_reduce( pOld->zName = pOld->zDatabase = 0; pOld->pSelect = 0; } - sqlite3SrcListDelete(pParse->db, yymsp[-4].minor.yy291); + sqlite3SrcListDelete(pParse->db, yymsp[-3].minor.yy131); }else{ Select *pSubquery; - sqlite3SrcListShiftJoinType(yymsp[-4].minor.yy291); - pSubquery = sqlite3SelectNew(pParse,0,yymsp[-4].minor.yy291,0,0,0,0,SF_NestedFrom,0); - yymsp[-6].minor.yy291 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy291,0,0,&yymsp[-2].minor.yy0,pSubquery,yymsp[-1].minor.yy602,yymsp[0].minor.yy288); + sqlite3SrcListShiftJoinType(pParse,yymsp[-3].minor.yy131); + pSubquery = sqlite3SelectNew(pParse,0,yymsp[-3].minor.yy131,0,0,0,0,SF_NestedFrom,0); + yymsp[-5].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy131,0,0,&yymsp[-1].minor.yy0,pSubquery,&yymsp[0].minor.yy561); } } break; - case 111: /* dbnm ::= */ - case 125: /* indexed_opt ::= */ yytestcase(yyruleno==125); + case 114: /* dbnm ::= */ + case 129: /* indexed_opt ::= */ yytestcase(yyruleno==129); {yymsp[1].minor.yy0.z=0; yymsp[1].minor.yy0.n=0;} break; - case 113: /* fullname ::= nm */ + case 116: /* fullname ::= nm */ { - yylhsminor.yy291 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); - if( IN_RENAME_OBJECT && yylhsminor.yy291 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy291->a[0].zName, &yymsp[0].minor.yy0); + yylhsminor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); + if( IN_RENAME_OBJECT && yylhsminor.yy131 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy131->a[0].zName, &yymsp[0].minor.yy0); } - yymsp[0].minor.yy291 = yylhsminor.yy291; + yymsp[0].minor.yy131 = yylhsminor.yy131; break; - case 114: /* fullname ::= nm DOT nm */ + case 117: /* fullname ::= nm DOT nm */ { - yylhsminor.yy291 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); - if( IN_RENAME_OBJECT && yylhsminor.yy291 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy291->a[0].zName, &yymsp[0].minor.yy0); + yylhsminor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); + if( IN_RENAME_OBJECT && yylhsminor.yy131 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy131->a[0].zName, &yymsp[0].minor.yy0); } - yymsp[-2].minor.yy291 = yylhsminor.yy291; + yymsp[-2].minor.yy131 = yylhsminor.yy131; break; - case 115: /* xfullname ::= nm */ -{yymsp[0].minor.yy291 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); /*A-overwrites-X*/} + case 118: /* xfullname ::= nm */ +{yymsp[0].minor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); /*A-overwrites-X*/} break; - case 116: /* xfullname ::= nm DOT nm */ -{yymsp[-2].minor.yy291 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/} + case 119: /* xfullname ::= nm DOT nm */ +{yymsp[-2].minor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/} break; - case 117: /* xfullname ::= nm DOT nm AS nm */ + case 120: /* xfullname ::= nm DOT nm AS nm */ { - yymsp[-4].minor.yy291 = sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,&yymsp[-2].minor.yy0); /*A-overwrites-X*/ - if( yymsp[-4].minor.yy291 ) yymsp[-4].minor.yy291->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0); + yymsp[-4].minor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,&yymsp[-2].minor.yy0); /*A-overwrites-X*/ + if( yymsp[-4].minor.yy131 ) yymsp[-4].minor.yy131->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0); } break; - case 118: /* xfullname ::= nm AS nm */ + case 121: /* xfullname ::= nm AS nm */ { - yymsp[-2].minor.yy291 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,0); /*A-overwrites-X*/ - if( yymsp[-2].minor.yy291 ) yymsp[-2].minor.yy291->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0); + yymsp[-2].minor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,0); /*A-overwrites-X*/ + if( yymsp[-2].minor.yy131 ) yymsp[-2].minor.yy131->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0); } break; - case 119: /* joinop ::= COMMA|JOIN */ -{ yymsp[0].minor.yy60 = JT_INNER; } + case 122: /* joinop ::= COMMA|JOIN */ +{ yymsp[0].minor.yy394 = JT_INNER; } break; - case 120: /* joinop ::= JOIN_KW JOIN */ -{yymsp[-1].minor.yy60 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); /*X-overwrites-A*/} + case 123: /* joinop ::= JOIN_KW JOIN */ +{yymsp[-1].minor.yy394 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); /*X-overwrites-A*/} break; - case 121: /* joinop ::= JOIN_KW nm JOIN */ -{yymsp[-2].minor.yy60 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); /*X-overwrites-A*/} + case 124: /* joinop ::= JOIN_KW nm JOIN */ +{yymsp[-2].minor.yy394 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); /*X-overwrites-A*/} break; - case 122: /* joinop ::= JOIN_KW nm nm JOIN */ -{yymsp[-3].minor.yy60 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);/*X-overwrites-A*/} + case 125: /* joinop ::= JOIN_KW nm nm JOIN */ +{yymsp[-3].minor.yy394 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);/*X-overwrites-A*/} break; - case 123: /* on_opt ::= ON expr */ - case 143: /* having_opt ::= HAVING expr */ yytestcase(yyruleno==143); - case 150: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==150); - case 152: /* where_opt_ret ::= WHERE expr */ yytestcase(yyruleno==152); - case 223: /* case_else ::= ELSE expr */ yytestcase(yyruleno==223); - case 244: /* vinto ::= INTO expr */ yytestcase(yyruleno==244); -{yymsp[-1].minor.yy602 = yymsp[0].minor.yy602;} + case 126: /* on_using ::= ON expr */ +{yymsp[-1].minor.yy561.pOn = yymsp[0].minor.yy528; yymsp[-1].minor.yy561.pUsing = 0;} break; - case 124: /* on_opt ::= */ - case 142: /* having_opt ::= */ yytestcase(yyruleno==142); - case 144: /* limit_opt ::= */ yytestcase(yyruleno==144); - case 149: /* where_opt ::= */ yytestcase(yyruleno==149); - case 151: /* where_opt_ret ::= */ yytestcase(yyruleno==151); - case 224: /* case_else ::= */ yytestcase(yyruleno==224); - case 226: /* case_operand ::= */ yytestcase(yyruleno==226); - case 245: /* vinto ::= */ yytestcase(yyruleno==245); -{yymsp[1].minor.yy602 = 0;} + case 127: /* on_using ::= USING LP idlist RP */ +{yymsp[-3].minor.yy561.pOn = 0; yymsp[-3].minor.yy561.pUsing = yymsp[-1].minor.yy254;} break; - case 126: /* indexed_opt ::= INDEXED BY nm */ + case 128: /* on_using ::= */ +{yymsp[1].minor.yy561.pOn = 0; yymsp[1].minor.yy561.pUsing = 0;} + break; + case 130: /* indexed_by ::= INDEXED BY nm */ {yymsp[-2].minor.yy0 = yymsp[0].minor.yy0;} break; - case 127: /* indexed_opt ::= NOT INDEXED */ + case 131: /* indexed_by ::= NOT INDEXED */ {yymsp[-1].minor.yy0.z=0; yymsp[-1].minor.yy0.n=1;} break; - case 128: /* using_opt ::= USING LP idlist RP */ -{yymsp[-3].minor.yy288 = yymsp[-1].minor.yy288;} + case 133: /* orderby_opt ::= ORDER BY sortlist */ + case 143: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==143); +{yymsp[-2].minor.yy322 = yymsp[0].minor.yy322;} break; - case 129: /* using_opt ::= */ - case 171: /* idlist_opt ::= */ yytestcase(yyruleno==171); -{yymsp[1].minor.yy288 = 0;} - break; - case 131: /* orderby_opt ::= ORDER BY sortlist */ - case 141: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==141); -{yymsp[-2].minor.yy338 = yymsp[0].minor.yy338;} - break; - case 132: /* sortlist ::= sortlist COMMA expr sortorder nulls */ + case 134: /* sortlist ::= sortlist COMMA expr sortorder nulls */ { - yymsp[-4].minor.yy338 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy338,yymsp[-2].minor.yy602); - sqlite3ExprListSetSortOrder(yymsp[-4].minor.yy338,yymsp[-1].minor.yy60,yymsp[0].minor.yy60); + yymsp[-4].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy322,yymsp[-2].minor.yy528); + sqlite3ExprListSetSortOrder(yymsp[-4].minor.yy322,yymsp[-1].minor.yy394,yymsp[0].minor.yy394); } break; - case 133: /* sortlist ::= expr sortorder nulls */ + case 135: /* sortlist ::= expr sortorder nulls */ { - yymsp[-2].minor.yy338 = sqlite3ExprListAppend(pParse,0,yymsp[-2].minor.yy602); /*A-overwrites-Y*/ - sqlite3ExprListSetSortOrder(yymsp[-2].minor.yy338,yymsp[-1].minor.yy60,yymsp[0].minor.yy60); + yymsp[-2].minor.yy322 = sqlite3ExprListAppend(pParse,0,yymsp[-2].minor.yy528); /*A-overwrites-Y*/ + sqlite3ExprListSetSortOrder(yymsp[-2].minor.yy322,yymsp[-1].minor.yy394,yymsp[0].minor.yy394); } break; - case 134: /* sortorder ::= ASC */ -{yymsp[0].minor.yy60 = SQLITE_SO_ASC;} + case 136: /* sortorder ::= ASC */ +{yymsp[0].minor.yy394 = SQLITE_SO_ASC;} break; - case 135: /* sortorder ::= DESC */ -{yymsp[0].minor.yy60 = SQLITE_SO_DESC;} + case 137: /* sortorder ::= DESC */ +{yymsp[0].minor.yy394 = SQLITE_SO_DESC;} break; - case 136: /* sortorder ::= */ - case 139: /* nulls ::= */ yytestcase(yyruleno==139); -{yymsp[1].minor.yy60 = SQLITE_SO_UNDEFINED;} + case 138: /* sortorder ::= */ + case 141: /* nulls ::= */ yytestcase(yyruleno==141); +{yymsp[1].minor.yy394 = SQLITE_SO_UNDEFINED;} break; - case 137: /* nulls ::= NULLS FIRST */ -{yymsp[-1].minor.yy60 = SQLITE_SO_ASC;} + case 139: /* nulls ::= NULLS FIRST */ +{yymsp[-1].minor.yy394 = SQLITE_SO_ASC;} break; - case 138: /* nulls ::= NULLS LAST */ -{yymsp[-1].minor.yy60 = SQLITE_SO_DESC;} + case 140: /* nulls ::= NULLS LAST */ +{yymsp[-1].minor.yy394 = SQLITE_SO_DESC;} break; - case 145: /* limit_opt ::= LIMIT expr */ -{yymsp[-1].minor.yy602 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy602,0);} + case 144: /* having_opt ::= */ + case 146: /* limit_opt ::= */ yytestcase(yyruleno==146); + case 151: /* where_opt ::= */ yytestcase(yyruleno==151); + case 153: /* where_opt_ret ::= */ yytestcase(yyruleno==153); + case 229: /* case_else ::= */ yytestcase(yyruleno==229); + case 231: /* case_operand ::= */ yytestcase(yyruleno==231); + case 250: /* vinto ::= */ yytestcase(yyruleno==250); +{yymsp[1].minor.yy528 = 0;} break; - case 146: /* limit_opt ::= LIMIT expr OFFSET expr */ -{yymsp[-3].minor.yy602 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[-2].minor.yy602,yymsp[0].minor.yy602);} + case 145: /* having_opt ::= HAVING expr */ + case 152: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==152); + case 154: /* where_opt_ret ::= WHERE expr */ yytestcase(yyruleno==154); + case 228: /* case_else ::= ELSE expr */ yytestcase(yyruleno==228); + case 249: /* vinto ::= INTO expr */ yytestcase(yyruleno==249); +{yymsp[-1].minor.yy528 = yymsp[0].minor.yy528;} break; - case 147: /* limit_opt ::= LIMIT expr COMMA expr */ -{yymsp[-3].minor.yy602 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy602,yymsp[-2].minor.yy602);} + case 147: /* limit_opt ::= LIMIT expr */ +{yymsp[-1].minor.yy528 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy528,0);} break; - case 148: /* cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */ + case 148: /* limit_opt ::= LIMIT expr OFFSET expr */ +{yymsp[-3].minor.yy528 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[-2].minor.yy528,yymsp[0].minor.yy528);} + break; + case 149: /* limit_opt ::= LIMIT expr COMMA expr */ +{yymsp[-3].minor.yy528 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy528,yymsp[-2].minor.yy528);} + break; + case 150: /* cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */ { - sqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy291, &yymsp[-1].minor.yy0); - sqlite3DeleteFrom(pParse,yymsp[-2].minor.yy291,yymsp[0].minor.yy602,0,0); + sqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy131, &yymsp[-1].minor.yy0); + sqlite3DeleteFrom(pParse,yymsp[-2].minor.yy131,yymsp[0].minor.yy528,0,0); } break; - case 153: /* where_opt_ret ::= RETURNING selcollist */ -{sqlite3AddReturning(pParse,yymsp[0].minor.yy338); yymsp[-1].minor.yy602 = 0;} + case 155: /* where_opt_ret ::= RETURNING selcollist */ +{sqlite3AddReturning(pParse,yymsp[0].minor.yy322); yymsp[-1].minor.yy528 = 0;} break; - case 154: /* where_opt_ret ::= WHERE expr RETURNING selcollist */ -{sqlite3AddReturning(pParse,yymsp[0].minor.yy338); yymsp[-3].minor.yy602 = yymsp[-2].minor.yy602;} + case 156: /* where_opt_ret ::= WHERE expr RETURNING selcollist */ +{sqlite3AddReturning(pParse,yymsp[0].minor.yy322); yymsp[-3].minor.yy528 = yymsp[-2].minor.yy528;} break; - case 155: /* cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */ + case 157: /* cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */ { - sqlite3SrcListIndexedBy(pParse, yymsp[-5].minor.yy291, &yymsp[-4].minor.yy0); - sqlite3ExprListCheckLength(pParse,yymsp[-2].minor.yy338,"set list"); - yymsp[-5].minor.yy291 = sqlite3SrcListAppendList(pParse, yymsp[-5].minor.yy291, yymsp[-1].minor.yy291); - sqlite3Update(pParse,yymsp[-5].minor.yy291,yymsp[-2].minor.yy338,yymsp[0].minor.yy602,yymsp[-6].minor.yy60,0,0,0); -} - break; - case 156: /* setlist ::= setlist COMMA nm EQ expr */ -{ - yymsp[-4].minor.yy338 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy338, yymsp[0].minor.yy602); - sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy338, &yymsp[-2].minor.yy0, 1); -} - break; - case 157: /* setlist ::= setlist COMMA LP idlist RP EQ expr */ -{ - yymsp[-6].minor.yy338 = sqlite3ExprListAppendVector(pParse, yymsp[-6].minor.yy338, yymsp[-3].minor.yy288, yymsp[0].minor.yy602); -} - break; - case 158: /* setlist ::= nm EQ expr */ -{ - yylhsminor.yy338 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy602); - sqlite3ExprListSetName(pParse, yylhsminor.yy338, &yymsp[-2].minor.yy0, 1); -} - yymsp[-2].minor.yy338 = yylhsminor.yy338; - break; - case 159: /* setlist ::= LP idlist RP EQ expr */ -{ - yymsp[-4].minor.yy338 = sqlite3ExprListAppendVector(pParse, 0, yymsp[-3].minor.yy288, yymsp[0].minor.yy602); -} - break; - case 160: /* cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ -{ - sqlite3Insert(pParse, yymsp[-3].minor.yy291, yymsp[-1].minor.yy307, yymsp[-2].minor.yy288, yymsp[-5].minor.yy60, yymsp[0].minor.yy178); -} - break; - case 161: /* cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */ -{ - sqlite3Insert(pParse, yymsp[-4].minor.yy291, 0, yymsp[-3].minor.yy288, yymsp[-6].minor.yy60, 0); -} - break; - case 162: /* upsert ::= */ -{ yymsp[1].minor.yy178 = 0; } - break; - case 163: /* upsert ::= RETURNING selcollist */ -{ yymsp[-1].minor.yy178 = 0; sqlite3AddReturning(pParse,yymsp[0].minor.yy338); } - break; - case 164: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */ -{ yymsp[-11].minor.yy178 = sqlite3UpsertNew(pParse->db,yymsp[-8].minor.yy338,yymsp[-6].minor.yy602,yymsp[-2].minor.yy338,yymsp[-1].minor.yy602,yymsp[0].minor.yy178);} - break; - case 165: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */ -{ yymsp[-8].minor.yy178 = sqlite3UpsertNew(pParse->db,yymsp[-5].minor.yy338,yymsp[-3].minor.yy602,0,0,yymsp[0].minor.yy178); } - break; - case 166: /* upsert ::= ON CONFLICT DO NOTHING returning */ -{ yymsp[-4].minor.yy178 = sqlite3UpsertNew(pParse->db,0,0,0,0,0); } - break; - case 167: /* upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */ -{ yymsp[-7].minor.yy178 = sqlite3UpsertNew(pParse->db,0,0,yymsp[-2].minor.yy338,yymsp[-1].minor.yy602,0);} - break; - case 168: /* returning ::= RETURNING selcollist */ -{sqlite3AddReturning(pParse,yymsp[0].minor.yy338);} - break; - case 172: /* idlist_opt ::= LP idlist RP */ -{yymsp[-2].minor.yy288 = yymsp[-1].minor.yy288;} - break; - case 173: /* idlist ::= idlist COMMA nm */ -{yymsp[-2].minor.yy288 = sqlite3IdListAppend(pParse,yymsp[-2].minor.yy288,&yymsp[0].minor.yy0);} - break; - case 174: /* idlist ::= nm */ -{yymsp[0].minor.yy288 = sqlite3IdListAppend(pParse,0,&yymsp[0].minor.yy0); /*A-overwrites-Y*/} - break; - case 175: /* expr ::= LP expr RP */ -{yymsp[-2].minor.yy602 = yymsp[-1].minor.yy602;} - break; - case 176: /* expr ::= ID|INDEXED */ - case 177: /* expr ::= JOIN_KW */ yytestcase(yyruleno==177); -{yymsp[0].minor.yy602=tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); /*A-overwrites-X*/} - break; - case 178: /* expr ::= nm DOT nm */ -{ - Expr *temp1 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1); - Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[0].minor.yy0, 1); - if( IN_RENAME_OBJECT ){ - sqlite3RenameTokenMap(pParse, (void*)temp2, &yymsp[0].minor.yy0); - sqlite3RenameTokenMap(pParse, (void*)temp1, &yymsp[-2].minor.yy0); + sqlite3SrcListIndexedBy(pParse, yymsp[-5].minor.yy131, &yymsp[-4].minor.yy0); + sqlite3ExprListCheckLength(pParse,yymsp[-2].minor.yy322,"set list"); + if( yymsp[-1].minor.yy131 ){ + SrcList *pFromClause = yymsp[-1].minor.yy131; + if( pFromClause->nSrc>1 ){ + Select *pSubquery; + Token as; + pSubquery = sqlite3SelectNew(pParse,0,pFromClause,0,0,0,0,SF_NestedFrom,0); + as.n = 0; + as.z = 0; + pFromClause = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&as,pSubquery,0); + } + yymsp[-5].minor.yy131 = sqlite3SrcListAppendList(pParse, yymsp[-5].minor.yy131, pFromClause); } - yylhsminor.yy602 = sqlite3PExpr(pParse, TK_DOT, temp1, temp2); + sqlite3Update(pParse,yymsp[-5].minor.yy131,yymsp[-2].minor.yy322,yymsp[0].minor.yy528,yymsp[-6].minor.yy394,0,0,0); } - yymsp[-2].minor.yy602 = yylhsminor.yy602; break; - case 179: /* expr ::= nm DOT nm DOT nm */ + case 158: /* setlist ::= setlist COMMA nm EQ expr */ { - Expr *temp1 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-4].minor.yy0, 1); - Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1); - Expr *temp3 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[0].minor.yy0, 1); + yymsp[-4].minor.yy322 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy322, yymsp[0].minor.yy528); + sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy322, &yymsp[-2].minor.yy0, 1); +} + break; + case 159: /* setlist ::= setlist COMMA LP idlist RP EQ expr */ +{ + yymsp[-6].minor.yy322 = sqlite3ExprListAppendVector(pParse, yymsp[-6].minor.yy322, yymsp[-3].minor.yy254, yymsp[0].minor.yy528); +} + break; + case 160: /* setlist ::= nm EQ expr */ +{ + yylhsminor.yy322 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy528); + sqlite3ExprListSetName(pParse, yylhsminor.yy322, &yymsp[-2].minor.yy0, 1); +} + yymsp[-2].minor.yy322 = yylhsminor.yy322; + break; + case 161: /* setlist ::= LP idlist RP EQ expr */ +{ + yymsp[-4].minor.yy322 = sqlite3ExprListAppendVector(pParse, 0, yymsp[-3].minor.yy254, yymsp[0].minor.yy528); +} + break; + case 162: /* cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ +{ + sqlite3Insert(pParse, yymsp[-3].minor.yy131, yymsp[-1].minor.yy47, yymsp[-2].minor.yy254, yymsp[-5].minor.yy394, yymsp[0].minor.yy444); +} + break; + case 163: /* cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */ +{ + sqlite3Insert(pParse, yymsp[-4].minor.yy131, 0, yymsp[-3].minor.yy254, yymsp[-6].minor.yy394, 0); +} + break; + case 164: /* upsert ::= */ +{ yymsp[1].minor.yy444 = 0; } + break; + case 165: /* upsert ::= RETURNING selcollist */ +{ yymsp[-1].minor.yy444 = 0; sqlite3AddReturning(pParse,yymsp[0].minor.yy322); } + break; + case 166: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */ +{ yymsp[-11].minor.yy444 = sqlite3UpsertNew(pParse->db,yymsp[-8].minor.yy322,yymsp[-6].minor.yy528,yymsp[-2].minor.yy322,yymsp[-1].minor.yy528,yymsp[0].minor.yy444);} + break; + case 167: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */ +{ yymsp[-8].minor.yy444 = sqlite3UpsertNew(pParse->db,yymsp[-5].minor.yy322,yymsp[-3].minor.yy528,0,0,yymsp[0].minor.yy444); } + break; + case 168: /* upsert ::= ON CONFLICT DO NOTHING returning */ +{ yymsp[-4].minor.yy444 = sqlite3UpsertNew(pParse->db,0,0,0,0,0); } + break; + case 169: /* upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */ +{ yymsp[-7].minor.yy444 = sqlite3UpsertNew(pParse->db,0,0,yymsp[-2].minor.yy322,yymsp[-1].minor.yy528,0);} + break; + case 170: /* returning ::= RETURNING selcollist */ +{sqlite3AddReturning(pParse,yymsp[0].minor.yy322);} + break; + case 173: /* idlist_opt ::= */ +{yymsp[1].minor.yy254 = 0;} + break; + case 174: /* idlist_opt ::= LP idlist RP */ +{yymsp[-2].minor.yy254 = yymsp[-1].minor.yy254;} + break; + case 175: /* idlist ::= idlist COMMA nm */ +{yymsp[-2].minor.yy254 = sqlite3IdListAppend(pParse,yymsp[-2].minor.yy254,&yymsp[0].minor.yy0);} + break; + case 176: /* idlist ::= nm */ +{yymsp[0].minor.yy254 = sqlite3IdListAppend(pParse,0,&yymsp[0].minor.yy0); /*A-overwrites-Y*/} + break; + case 177: /* expr ::= LP expr RP */ +{yymsp[-2].minor.yy528 = yymsp[-1].minor.yy528;} + break; + case 178: /* expr ::= ID|INDEXED */ + case 179: /* expr ::= JOIN_KW */ yytestcase(yyruleno==179); +{yymsp[0].minor.yy528=tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); /*A-overwrites-X*/} + break; + case 180: /* expr ::= nm DOT nm */ +{ + Expr *temp1 = tokenExpr(pParse,TK_ID,yymsp[-2].minor.yy0); + Expr *temp2 = tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); + yylhsminor.yy528 = sqlite3PExpr(pParse, TK_DOT, temp1, temp2); +} + yymsp[-2].minor.yy528 = yylhsminor.yy528; + break; + case 181: /* expr ::= nm DOT nm DOT nm */ +{ + Expr *temp1 = tokenExpr(pParse,TK_ID,yymsp[-4].minor.yy0); + Expr *temp2 = tokenExpr(pParse,TK_ID,yymsp[-2].minor.yy0); + Expr *temp3 = tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); Expr *temp4 = sqlite3PExpr(pParse, TK_DOT, temp2, temp3); if( IN_RENAME_OBJECT ){ - sqlite3RenameTokenMap(pParse, (void*)temp3, &yymsp[0].minor.yy0); - sqlite3RenameTokenMap(pParse, (void*)temp2, &yymsp[-2].minor.yy0); + sqlite3RenameTokenRemap(pParse, 0, temp1); } - yylhsminor.yy602 = sqlite3PExpr(pParse, TK_DOT, temp1, temp4); + yylhsminor.yy528 = sqlite3PExpr(pParse, TK_DOT, temp1, temp4); } - yymsp[-4].minor.yy602 = yylhsminor.yy602; + yymsp[-4].minor.yy528 = yylhsminor.yy528; break; - case 180: /* term ::= NULL|FLOAT|BLOB */ - case 181: /* term ::= STRING */ yytestcase(yyruleno==181); -{yymsp[0].minor.yy602=tokenExpr(pParse,yymsp[0].major,yymsp[0].minor.yy0); /*A-overwrites-X*/} + case 182: /* term ::= NULL|FLOAT|BLOB */ + case 183: /* term ::= STRING */ yytestcase(yyruleno==183); +{yymsp[0].minor.yy528=tokenExpr(pParse,yymsp[0].major,yymsp[0].minor.yy0); /*A-overwrites-X*/} break; - case 182: /* term ::= INTEGER */ + case 184: /* term ::= INTEGER */ { - yylhsminor.yy602 = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &yymsp[0].minor.yy0, 1); + yylhsminor.yy528 = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &yymsp[0].minor.yy0, 1); + if( yylhsminor.yy528 ) yylhsminor.yy528->w.iOfst = (int)(yymsp[0].minor.yy0.z - pParse->zTail); } - yymsp[0].minor.yy602 = yylhsminor.yy602; + yymsp[0].minor.yy528 = yylhsminor.yy528; break; - case 183: /* expr ::= VARIABLE */ + case 185: /* expr ::= VARIABLE */ { if( !(yymsp[0].minor.yy0.z[0]=='#' && sqlite3Isdigit(yymsp[0].minor.yy0.z[1])) ){ u32 n = yymsp[0].minor.yy0.n; - yymsp[0].minor.yy602 = tokenExpr(pParse, TK_VARIABLE, yymsp[0].minor.yy0); - sqlite3ExprAssignVarNumber(pParse, yymsp[0].minor.yy602, n); + yymsp[0].minor.yy528 = tokenExpr(pParse, TK_VARIABLE, yymsp[0].minor.yy0); + sqlite3ExprAssignVarNumber(pParse, yymsp[0].minor.yy528, n); }else{ /* When doing a nested parse, one can include terms in an expression ** that look like this: #1 #2 ... These terms refer to registers @@ -161245,159 +167843,179 @@ static YYACTIONTYPE yy_reduce( assert( t.n>=2 ); if( pParse->nested==0 ){ sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &t); - yymsp[0].minor.yy602 = 0; + yymsp[0].minor.yy528 = 0; }else{ - yymsp[0].minor.yy602 = sqlite3PExpr(pParse, TK_REGISTER, 0, 0); - if( yymsp[0].minor.yy602 ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy602->iTable); + yymsp[0].minor.yy528 = sqlite3PExpr(pParse, TK_REGISTER, 0, 0); + if( yymsp[0].minor.yy528 ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy528->iTable); } } } break; - case 184: /* expr ::= expr COLLATE ID|STRING */ + case 186: /* expr ::= expr COLLATE ID|STRING */ { - yymsp[-2].minor.yy602 = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy602, &yymsp[0].minor.yy0, 1); + yymsp[-2].minor.yy528 = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy528, &yymsp[0].minor.yy0, 1); } break; - case 185: /* expr ::= CAST LP expr AS typetoken RP */ + case 187: /* expr ::= CAST LP expr AS typetoken RP */ { - yymsp[-5].minor.yy602 = sqlite3ExprAlloc(pParse->db, TK_CAST, &yymsp[-1].minor.yy0, 1); - sqlite3ExprAttachSubtrees(pParse->db, yymsp[-5].minor.yy602, yymsp[-3].minor.yy602, 0); + yymsp[-5].minor.yy528 = sqlite3ExprAlloc(pParse->db, TK_CAST, &yymsp[-1].minor.yy0, 1); + sqlite3ExprAttachSubtrees(pParse->db, yymsp[-5].minor.yy528, yymsp[-3].minor.yy528, 0); } break; - case 186: /* expr ::= ID|INDEXED LP distinct exprlist RP */ + case 188: /* expr ::= ID|INDEXED LP distinct exprlist RP */ { - yylhsminor.yy602 = sqlite3ExprFunction(pParse, yymsp[-1].minor.yy338, &yymsp[-4].minor.yy0, yymsp[-2].minor.yy60); + yylhsminor.yy528 = sqlite3ExprFunction(pParse, yymsp[-1].minor.yy322, &yymsp[-4].minor.yy0, yymsp[-2].minor.yy394); } - yymsp[-4].minor.yy602 = yylhsminor.yy602; + yymsp[-4].minor.yy528 = yylhsminor.yy528; break; - case 187: /* expr ::= ID|INDEXED LP STAR RP */ + case 189: /* expr ::= ID|INDEXED LP STAR RP */ { - yylhsminor.yy602 = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0, 0); + yylhsminor.yy528 = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0, 0); } - yymsp[-3].minor.yy602 = yylhsminor.yy602; + yymsp[-3].minor.yy528 = yylhsminor.yy528; break; - case 188: /* expr ::= ID|INDEXED LP distinct exprlist RP filter_over */ + case 190: /* expr ::= ID|INDEXED LP distinct exprlist RP filter_over */ { - yylhsminor.yy602 = sqlite3ExprFunction(pParse, yymsp[-2].minor.yy338, &yymsp[-5].minor.yy0, yymsp[-3].minor.yy60); - sqlite3WindowAttach(pParse, yylhsminor.yy602, yymsp[0].minor.yy19); + yylhsminor.yy528 = sqlite3ExprFunction(pParse, yymsp[-2].minor.yy322, &yymsp[-5].minor.yy0, yymsp[-3].minor.yy394); + sqlite3WindowAttach(pParse, yylhsminor.yy528, yymsp[0].minor.yy41); } - yymsp[-5].minor.yy602 = yylhsminor.yy602; + yymsp[-5].minor.yy528 = yylhsminor.yy528; break; - case 189: /* expr ::= ID|INDEXED LP STAR RP filter_over */ + case 191: /* expr ::= ID|INDEXED LP STAR RP filter_over */ { - yylhsminor.yy602 = sqlite3ExprFunction(pParse, 0, &yymsp[-4].minor.yy0, 0); - sqlite3WindowAttach(pParse, yylhsminor.yy602, yymsp[0].minor.yy19); + yylhsminor.yy528 = sqlite3ExprFunction(pParse, 0, &yymsp[-4].minor.yy0, 0); + sqlite3WindowAttach(pParse, yylhsminor.yy528, yymsp[0].minor.yy41); } - yymsp[-4].minor.yy602 = yylhsminor.yy602; + yymsp[-4].minor.yy528 = yylhsminor.yy528; break; - case 190: /* term ::= CTIME_KW */ + case 192: /* term ::= CTIME_KW */ { - yylhsminor.yy602 = sqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0, 0); + yylhsminor.yy528 = sqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0, 0); } - yymsp[0].minor.yy602 = yylhsminor.yy602; + yymsp[0].minor.yy528 = yylhsminor.yy528; break; - case 191: /* expr ::= LP nexprlist COMMA expr RP */ + case 193: /* expr ::= LP nexprlist COMMA expr RP */ { - ExprList *pList = sqlite3ExprListAppend(pParse, yymsp[-3].minor.yy338, yymsp[-1].minor.yy602); - yymsp[-4].minor.yy602 = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); - if( yymsp[-4].minor.yy602 ){ - yymsp[-4].minor.yy602->x.pList = pList; + ExprList *pList = sqlite3ExprListAppend(pParse, yymsp[-3].minor.yy322, yymsp[-1].minor.yy528); + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); + if( yymsp[-4].minor.yy528 ){ + yymsp[-4].minor.yy528->x.pList = pList; if( ALWAYS(pList->nExpr) ){ - yymsp[-4].minor.yy602->flags |= pList->a[0].pExpr->flags & EP_Propagate; + yymsp[-4].minor.yy528->flags |= pList->a[0].pExpr->flags & EP_Propagate; } }else{ sqlite3ExprListDelete(pParse->db, pList); } } break; - case 192: /* expr ::= expr AND expr */ -{yymsp[-2].minor.yy602=sqlite3ExprAnd(pParse,yymsp[-2].minor.yy602,yymsp[0].minor.yy602);} + case 194: /* expr ::= expr AND expr */ +{yymsp[-2].minor.yy528=sqlite3ExprAnd(pParse,yymsp[-2].minor.yy528,yymsp[0].minor.yy528);} break; - case 193: /* expr ::= expr OR expr */ - case 194: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==194); - case 195: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==195); - case 196: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==196); - case 197: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==197); - case 198: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==198); - case 199: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==199); -{yymsp[-2].minor.yy602=sqlite3PExpr(pParse,yymsp[-1].major,yymsp[-2].minor.yy602,yymsp[0].minor.yy602);} + case 195: /* expr ::= expr OR expr */ + case 196: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==196); + case 197: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==197); + case 198: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==198); + case 199: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==199); + case 200: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==200); + case 201: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==201); +{yymsp[-2].minor.yy528=sqlite3PExpr(pParse,yymsp[-1].major,yymsp[-2].minor.yy528,yymsp[0].minor.yy528);} break; - case 200: /* likeop ::= NOT LIKE_KW|MATCH */ + case 202: /* likeop ::= NOT LIKE_KW|MATCH */ {yymsp[-1].minor.yy0=yymsp[0].minor.yy0; yymsp[-1].minor.yy0.n|=0x80000000; /*yymsp[-1].minor.yy0-overwrite-yymsp[0].minor.yy0*/} break; - case 201: /* expr ::= expr likeop expr */ + case 203: /* expr ::= expr likeop expr */ { ExprList *pList; int bNot = yymsp[-1].minor.yy0.n & 0x80000000; yymsp[-1].minor.yy0.n &= 0x7fffffff; - pList = sqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy602); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy602); - yymsp[-2].minor.yy602 = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0, 0); - if( bNot ) yymsp[-2].minor.yy602 = sqlite3PExpr(pParse, TK_NOT, yymsp[-2].minor.yy602, 0); - if( yymsp[-2].minor.yy602 ) yymsp[-2].minor.yy602->flags |= EP_InfixFunc; + pList = sqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy528); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy528); + yymsp[-2].minor.yy528 = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0, 0); + if( bNot ) yymsp[-2].minor.yy528 = sqlite3PExpr(pParse, TK_NOT, yymsp[-2].minor.yy528, 0); + if( yymsp[-2].minor.yy528 ) yymsp[-2].minor.yy528->flags |= EP_InfixFunc; } break; - case 202: /* expr ::= expr likeop expr ESCAPE expr */ + case 204: /* expr ::= expr likeop expr ESCAPE expr */ { ExprList *pList; int bNot = yymsp[-3].minor.yy0.n & 0x80000000; yymsp[-3].minor.yy0.n &= 0x7fffffff; - pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy602); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy602); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy602); - yymsp[-4].minor.yy602 = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy0, 0); - if( bNot ) yymsp[-4].minor.yy602 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy602, 0); - if( yymsp[-4].minor.yy602 ) yymsp[-4].minor.yy602->flags |= EP_InfixFunc; + pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy528); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy528); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy528); + yymsp[-4].minor.yy528 = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy0, 0); + if( bNot ) yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy528, 0); + if( yymsp[-4].minor.yy528 ) yymsp[-4].minor.yy528->flags |= EP_InfixFunc; } break; - case 203: /* expr ::= expr ISNULL|NOTNULL */ -{yymsp[-1].minor.yy602 = sqlite3PExpr(pParse,yymsp[0].major,yymsp[-1].minor.yy602,0);} + case 205: /* expr ::= expr ISNULL|NOTNULL */ +{yymsp[-1].minor.yy528 = sqlite3PExpr(pParse,yymsp[0].major,yymsp[-1].minor.yy528,0);} break; - case 204: /* expr ::= expr NOT NULL */ -{yymsp[-2].minor.yy602 = sqlite3PExpr(pParse,TK_NOTNULL,yymsp[-2].minor.yy602,0);} + case 206: /* expr ::= expr NOT NULL */ +{yymsp[-2].minor.yy528 = sqlite3PExpr(pParse,TK_NOTNULL,yymsp[-2].minor.yy528,0);} break; - case 205: /* expr ::= expr IS expr */ + case 207: /* expr ::= expr IS expr */ { - yymsp[-2].minor.yy602 = sqlite3PExpr(pParse,TK_IS,yymsp[-2].minor.yy602,yymsp[0].minor.yy602); - binaryToUnaryIfNull(pParse, yymsp[0].minor.yy602, yymsp[-2].minor.yy602, TK_ISNULL); + yymsp[-2].minor.yy528 = sqlite3PExpr(pParse,TK_IS,yymsp[-2].minor.yy528,yymsp[0].minor.yy528); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy528, yymsp[-2].minor.yy528, TK_ISNULL); } break; - case 206: /* expr ::= expr IS NOT expr */ + case 208: /* expr ::= expr IS NOT expr */ { - yymsp[-3].minor.yy602 = sqlite3PExpr(pParse,TK_ISNOT,yymsp[-3].minor.yy602,yymsp[0].minor.yy602); - binaryToUnaryIfNull(pParse, yymsp[0].minor.yy602, yymsp[-3].minor.yy602, TK_NOTNULL); + yymsp[-3].minor.yy528 = sqlite3PExpr(pParse,TK_ISNOT,yymsp[-3].minor.yy528,yymsp[0].minor.yy528); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy528, yymsp[-3].minor.yy528, TK_NOTNULL); } break; - case 207: /* expr ::= NOT expr */ - case 208: /* expr ::= BITNOT expr */ yytestcase(yyruleno==208); -{yymsp[-1].minor.yy602 = sqlite3PExpr(pParse, yymsp[-1].major, yymsp[0].minor.yy602, 0);/*A-overwrites-B*/} - break; - case 209: /* expr ::= PLUS|MINUS expr */ + case 209: /* expr ::= expr IS NOT DISTINCT FROM expr */ { - yymsp[-1].minor.yy602 = sqlite3PExpr(pParse, yymsp[-1].major==TK_PLUS ? TK_UPLUS : TK_UMINUS, yymsp[0].minor.yy602, 0); + yymsp[-5].minor.yy528 = sqlite3PExpr(pParse,TK_IS,yymsp[-5].minor.yy528,yymsp[0].minor.yy528); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy528, yymsp[-5].minor.yy528, TK_ISNULL); +} + break; + case 210: /* expr ::= expr IS DISTINCT FROM expr */ +{ + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse,TK_ISNOT,yymsp[-4].minor.yy528,yymsp[0].minor.yy528); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy528, yymsp[-4].minor.yy528, TK_NOTNULL); +} + break; + case 211: /* expr ::= NOT expr */ + case 212: /* expr ::= BITNOT expr */ yytestcase(yyruleno==212); +{yymsp[-1].minor.yy528 = sqlite3PExpr(pParse, yymsp[-1].major, yymsp[0].minor.yy528, 0);/*A-overwrites-B*/} + break; + case 213: /* expr ::= PLUS|MINUS expr */ +{ + yymsp[-1].minor.yy528 = sqlite3PExpr(pParse, yymsp[-1].major==TK_PLUS ? TK_UPLUS : TK_UMINUS, yymsp[0].minor.yy528, 0); /*A-overwrites-B*/ } break; - case 210: /* between_op ::= BETWEEN */ - case 213: /* in_op ::= IN */ yytestcase(yyruleno==213); -{yymsp[0].minor.yy60 = 0;} - break; - case 212: /* expr ::= expr between_op expr AND expr */ + case 214: /* expr ::= expr PTR expr */ { - ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy602); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy602); - yymsp[-4].minor.yy602 = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy602, 0); - if( yymsp[-4].minor.yy602 ){ - yymsp[-4].minor.yy602->x.pList = pList; + ExprList *pList = sqlite3ExprListAppend(pParse, 0, yymsp[-2].minor.yy528); + pList = sqlite3ExprListAppend(pParse, pList, yymsp[0].minor.yy528); + yylhsminor.yy528 = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0, 0); +} + yymsp[-2].minor.yy528 = yylhsminor.yy528; + break; + case 215: /* between_op ::= BETWEEN */ + case 218: /* in_op ::= IN */ yytestcase(yyruleno==218); +{yymsp[0].minor.yy394 = 0;} + break; + case 217: /* expr ::= expr between_op expr AND expr */ +{ + ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy528); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy528); + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy528, 0); + if( yymsp[-4].minor.yy528 ){ + yymsp[-4].minor.yy528->x.pList = pList; }else{ sqlite3ExprListDelete(pParse->db, pList); } - if( yymsp[-3].minor.yy60 ) yymsp[-4].minor.yy602 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy602, 0); + if( yymsp[-3].minor.yy394 ) yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy528, 0); } break; - case 215: /* expr ::= expr in_op LP exprlist RP */ + case 220: /* expr ::= expr in_op LP exprlist RP */ { - if( yymsp[-1].minor.yy338==0 ){ + if( yymsp[-1].minor.yy322==0 ){ /* Expressions of the form ** ** expr1 IN () @@ -161406,197 +168024,206 @@ static YYACTIONTYPE yy_reduce( ** simplify to constants 0 (false) and 1 (true), respectively, ** regardless of the value of expr1. */ - sqlite3ExprUnmapAndDelete(pParse, yymsp[-4].minor.yy602); - yymsp[-4].minor.yy602 = sqlite3Expr(pParse->db, TK_INTEGER, yymsp[-3].minor.yy60 ? "1" : "0"); - }else if( yymsp[-1].minor.yy338->nExpr==1 && sqlite3ExprIsConstant(yymsp[-1].minor.yy338->a[0].pExpr) ){ - Expr *pRHS = yymsp[-1].minor.yy338->a[0].pExpr; - yymsp[-1].minor.yy338->a[0].pExpr = 0; - sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy338); - pRHS = sqlite3PExpr(pParse, TK_UPLUS, pRHS, 0); - yymsp[-4].minor.yy602 = sqlite3PExpr(pParse, TK_EQ, yymsp[-4].minor.yy602, pRHS); - if( yymsp[-3].minor.yy60 ) yymsp[-4].minor.yy602 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy602, 0); + sqlite3ExprUnmapAndDelete(pParse, yymsp[-4].minor.yy528); + yymsp[-4].minor.yy528 = sqlite3Expr(pParse->db, TK_STRING, yymsp[-3].minor.yy394 ? "true" : "false"); + if( yymsp[-4].minor.yy528 ) sqlite3ExprIdToTrueFalse(yymsp[-4].minor.yy528); }else{ - yymsp[-4].minor.yy602 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy602, 0); - if( yymsp[-4].minor.yy602 ){ - yymsp[-4].minor.yy602->x.pList = yymsp[-1].minor.yy338; - sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy602); + Expr *pRHS = yymsp[-1].minor.yy322->a[0].pExpr; + if( yymsp[-1].minor.yy322->nExpr==1 && sqlite3ExprIsConstant(pRHS) && yymsp[-4].minor.yy528->op!=TK_VECTOR ){ + yymsp[-1].minor.yy322->a[0].pExpr = 0; + sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy322); + pRHS = sqlite3PExpr(pParse, TK_UPLUS, pRHS, 0); + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_EQ, yymsp[-4].minor.yy528, pRHS); }else{ - sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy338); + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy528, 0); + if( yymsp[-4].minor.yy528==0 ){ + sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy322); + }else if( yymsp[-4].minor.yy528->pLeft->op==TK_VECTOR ){ + int nExpr = yymsp[-4].minor.yy528->pLeft->x.pList->nExpr; + Select *pSelectRHS = sqlite3ExprListToValues(pParse, nExpr, yymsp[-1].minor.yy322); + if( pSelectRHS ){ + parserDoubleLinkSelect(pParse, pSelectRHS); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy528, pSelectRHS); + } + }else{ + yymsp[-4].minor.yy528->x.pList = yymsp[-1].minor.yy322; + sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy528); + } } - if( yymsp[-3].minor.yy60 ) yymsp[-4].minor.yy602 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy602, 0); + if( yymsp[-3].minor.yy394 ) yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy528, 0); } } break; - case 216: /* expr ::= LP select RP */ + case 221: /* expr ::= LP select RP */ { - yymsp[-2].minor.yy602 = sqlite3PExpr(pParse, TK_SELECT, 0, 0); - sqlite3PExprAddSelect(pParse, yymsp[-2].minor.yy602, yymsp[-1].minor.yy307); + yymsp[-2].minor.yy528 = sqlite3PExpr(pParse, TK_SELECT, 0, 0); + sqlite3PExprAddSelect(pParse, yymsp[-2].minor.yy528, yymsp[-1].minor.yy47); } break; - case 217: /* expr ::= expr in_op LP select RP */ + case 222: /* expr ::= expr in_op LP select RP */ { - yymsp[-4].minor.yy602 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy602, 0); - sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy602, yymsp[-1].minor.yy307); - if( yymsp[-3].minor.yy60 ) yymsp[-4].minor.yy602 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy602, 0); + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy528, 0); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy528, yymsp[-1].minor.yy47); + if( yymsp[-3].minor.yy394 ) yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy528, 0); } break; - case 218: /* expr ::= expr in_op nm dbnm paren_exprlist */ + case 223: /* expr ::= expr in_op nm dbnm paren_exprlist */ { SrcList *pSrc = sqlite3SrcListAppend(pParse, 0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0); Select *pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0); - if( yymsp[0].minor.yy338 ) sqlite3SrcListFuncArgs(pParse, pSelect ? pSrc : 0, yymsp[0].minor.yy338); - yymsp[-4].minor.yy602 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy602, 0); - sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy602, pSelect); - if( yymsp[-3].minor.yy60 ) yymsp[-4].minor.yy602 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy602, 0); + if( yymsp[0].minor.yy322 ) sqlite3SrcListFuncArgs(pParse, pSelect ? pSrc : 0, yymsp[0].minor.yy322); + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy528, 0); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy528, pSelect); + if( yymsp[-3].minor.yy394 ) yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy528, 0); } break; - case 219: /* expr ::= EXISTS LP select RP */ + case 224: /* expr ::= EXISTS LP select RP */ { Expr *p; - p = yymsp[-3].minor.yy602 = sqlite3PExpr(pParse, TK_EXISTS, 0, 0); - sqlite3PExprAddSelect(pParse, p, yymsp[-1].minor.yy307); + p = yymsp[-3].minor.yy528 = sqlite3PExpr(pParse, TK_EXISTS, 0, 0); + sqlite3PExprAddSelect(pParse, p, yymsp[-1].minor.yy47); } break; - case 220: /* expr ::= CASE case_operand case_exprlist case_else END */ + case 225: /* expr ::= CASE case_operand case_exprlist case_else END */ { - yymsp[-4].minor.yy602 = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy602, 0); - if( yymsp[-4].minor.yy602 ){ - yymsp[-4].minor.yy602->x.pList = yymsp[-1].minor.yy602 ? sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy338,yymsp[-1].minor.yy602) : yymsp[-2].minor.yy338; - sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy602); + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy528, 0); + if( yymsp[-4].minor.yy528 ){ + yymsp[-4].minor.yy528->x.pList = yymsp[-1].minor.yy528 ? sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy322,yymsp[-1].minor.yy528) : yymsp[-2].minor.yy322; + sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy528); }else{ - sqlite3ExprListDelete(pParse->db, yymsp[-2].minor.yy338); - sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy602); + sqlite3ExprListDelete(pParse->db, yymsp[-2].minor.yy322); + sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy528); } } break; - case 221: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ + case 226: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ { - yymsp[-4].minor.yy338 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy338, yymsp[-2].minor.yy602); - yymsp[-4].minor.yy338 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy338, yymsp[0].minor.yy602); + yymsp[-4].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy322, yymsp[-2].minor.yy528); + yymsp[-4].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy322, yymsp[0].minor.yy528); } break; - case 222: /* case_exprlist ::= WHEN expr THEN expr */ + case 227: /* case_exprlist ::= WHEN expr THEN expr */ { - yymsp[-3].minor.yy338 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy602); - yymsp[-3].minor.yy338 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy338, yymsp[0].minor.yy602); + yymsp[-3].minor.yy322 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy528); + yymsp[-3].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy322, yymsp[0].minor.yy528); } break; - case 225: /* case_operand ::= expr */ -{yymsp[0].minor.yy602 = yymsp[0].minor.yy602; /*A-overwrites-X*/} + case 230: /* case_operand ::= expr */ +{yymsp[0].minor.yy528 = yymsp[0].minor.yy528; /*A-overwrites-X*/} break; - case 228: /* nexprlist ::= nexprlist COMMA expr */ -{yymsp[-2].minor.yy338 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy338,yymsp[0].minor.yy602);} + case 233: /* nexprlist ::= nexprlist COMMA expr */ +{yymsp[-2].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy322,yymsp[0].minor.yy528);} break; - case 229: /* nexprlist ::= expr */ -{yymsp[0].minor.yy338 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy602); /*A-overwrites-Y*/} + case 234: /* nexprlist ::= expr */ +{yymsp[0].minor.yy322 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy528); /*A-overwrites-Y*/} break; - case 231: /* paren_exprlist ::= LP exprlist RP */ - case 236: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==236); -{yymsp[-2].minor.yy338 = yymsp[-1].minor.yy338;} + case 236: /* paren_exprlist ::= LP exprlist RP */ + case 241: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==241); +{yymsp[-2].minor.yy322 = yymsp[-1].minor.yy322;} break; - case 232: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ + case 237: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ { sqlite3CreateIndex(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, - sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy338, yymsp[-10].minor.yy60, - &yymsp[-11].minor.yy0, yymsp[0].minor.yy602, SQLITE_SO_ASC, yymsp[-8].minor.yy60, SQLITE_IDXTYPE_APPDEF); + sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy322, yymsp[-10].minor.yy394, + &yymsp[-11].minor.yy0, yymsp[0].minor.yy528, SQLITE_SO_ASC, yymsp[-8].minor.yy394, SQLITE_IDXTYPE_APPDEF); if( IN_RENAME_OBJECT && pParse->pNewIndex ){ sqlite3RenameTokenMap(pParse, pParse->pNewIndex->zName, &yymsp[-4].minor.yy0); } } break; - case 233: /* uniqueflag ::= UNIQUE */ - case 275: /* raisetype ::= ABORT */ yytestcase(yyruleno==275); -{yymsp[0].minor.yy60 = OE_Abort;} + case 238: /* uniqueflag ::= UNIQUE */ + case 280: /* raisetype ::= ABORT */ yytestcase(yyruleno==280); +{yymsp[0].minor.yy394 = OE_Abort;} break; - case 234: /* uniqueflag ::= */ -{yymsp[1].minor.yy60 = OE_None;} + case 239: /* uniqueflag ::= */ +{yymsp[1].minor.yy394 = OE_None;} break; - case 237: /* eidlist ::= eidlist COMMA nm collate sortorder */ + case 242: /* eidlist ::= eidlist COMMA nm collate sortorder */ { - yymsp[-4].minor.yy338 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy338, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy60, yymsp[0].minor.yy60); + yymsp[-4].minor.yy322 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy322, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy394, yymsp[0].minor.yy394); } break; - case 238: /* eidlist ::= nm collate sortorder */ + case 243: /* eidlist ::= nm collate sortorder */ { - yymsp[-2].minor.yy338 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy60, yymsp[0].minor.yy60); /*A-overwrites-Y*/ + yymsp[-2].minor.yy322 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy394, yymsp[0].minor.yy394); /*A-overwrites-Y*/ } break; - case 241: /* cmd ::= DROP INDEX ifexists fullname */ -{sqlite3DropIndex(pParse, yymsp[0].minor.yy291, yymsp[-1].minor.yy60);} + case 246: /* cmd ::= DROP INDEX ifexists fullname */ +{sqlite3DropIndex(pParse, yymsp[0].minor.yy131, yymsp[-1].minor.yy394);} break; - case 242: /* cmd ::= VACUUM vinto */ -{sqlite3Vacuum(pParse,0,yymsp[0].minor.yy602);} + case 247: /* cmd ::= VACUUM vinto */ +{sqlite3Vacuum(pParse,0,yymsp[0].minor.yy528);} break; - case 243: /* cmd ::= VACUUM nm vinto */ -{sqlite3Vacuum(pParse,&yymsp[-1].minor.yy0,yymsp[0].minor.yy602);} + case 248: /* cmd ::= VACUUM nm vinto */ +{sqlite3Vacuum(pParse,&yymsp[-1].minor.yy0,yymsp[0].minor.yy528);} break; - case 246: /* cmd ::= PRAGMA nm dbnm */ + case 251: /* cmd ::= PRAGMA nm dbnm */ {sqlite3Pragma(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,0,0);} break; - case 247: /* cmd ::= PRAGMA nm dbnm EQ nmnum */ + case 252: /* cmd ::= PRAGMA nm dbnm EQ nmnum */ {sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,0);} break; - case 248: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */ + case 253: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */ {sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,0);} break; - case 249: /* cmd ::= PRAGMA nm dbnm EQ minus_num */ + case 254: /* cmd ::= PRAGMA nm dbnm EQ minus_num */ {sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,1);} break; - case 250: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */ + case 255: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */ {sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,1);} break; - case 253: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ + case 258: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ { Token all; all.z = yymsp[-3].minor.yy0.z; all.n = (int)(yymsp[0].minor.yy0.z - yymsp[-3].minor.yy0.z) + yymsp[0].minor.yy0.n; - sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy483, &all); + sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy33, &all); } break; - case 254: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ + case 259: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ { - sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy60, yymsp[-4].minor.yy50.a, yymsp[-4].minor.yy50.b, yymsp[-2].minor.yy291, yymsp[0].minor.yy602, yymsp[-10].minor.yy60, yymsp[-8].minor.yy60); + sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy394, yymsp[-4].minor.yy180.a, yymsp[-4].minor.yy180.b, yymsp[-2].minor.yy131, yymsp[0].minor.yy528, yymsp[-10].minor.yy394, yymsp[-8].minor.yy394); yymsp[-10].minor.yy0 = (yymsp[-6].minor.yy0.n==0?yymsp[-7].minor.yy0:yymsp[-6].minor.yy0); /*A-overwrites-T*/ } break; - case 255: /* trigger_time ::= BEFORE|AFTER */ -{ yymsp[0].minor.yy60 = yymsp[0].major; /*A-overwrites-X*/ } + case 260: /* trigger_time ::= BEFORE|AFTER */ +{ yymsp[0].minor.yy394 = yymsp[0].major; /*A-overwrites-X*/ } break; - case 256: /* trigger_time ::= INSTEAD OF */ -{ yymsp[-1].minor.yy60 = TK_INSTEAD;} + case 261: /* trigger_time ::= INSTEAD OF */ +{ yymsp[-1].minor.yy394 = TK_INSTEAD;} break; - case 257: /* trigger_time ::= */ -{ yymsp[1].minor.yy60 = TK_BEFORE; } + case 262: /* trigger_time ::= */ +{ yymsp[1].minor.yy394 = TK_BEFORE; } break; - case 258: /* trigger_event ::= DELETE|INSERT */ - case 259: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==259); -{yymsp[0].minor.yy50.a = yymsp[0].major; /*A-overwrites-X*/ yymsp[0].minor.yy50.b = 0;} + case 263: /* trigger_event ::= DELETE|INSERT */ + case 264: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==264); +{yymsp[0].minor.yy180.a = yymsp[0].major; /*A-overwrites-X*/ yymsp[0].minor.yy180.b = 0;} break; - case 260: /* trigger_event ::= UPDATE OF idlist */ -{yymsp[-2].minor.yy50.a = TK_UPDATE; yymsp[-2].minor.yy50.b = yymsp[0].minor.yy288;} + case 265: /* trigger_event ::= UPDATE OF idlist */ +{yymsp[-2].minor.yy180.a = TK_UPDATE; yymsp[-2].minor.yy180.b = yymsp[0].minor.yy254;} break; - case 261: /* when_clause ::= */ - case 280: /* key_opt ::= */ yytestcase(yyruleno==280); -{ yymsp[1].minor.yy602 = 0; } + case 266: /* when_clause ::= */ + case 285: /* key_opt ::= */ yytestcase(yyruleno==285); +{ yymsp[1].minor.yy528 = 0; } break; - case 262: /* when_clause ::= WHEN expr */ - case 281: /* key_opt ::= KEY expr */ yytestcase(yyruleno==281); -{ yymsp[-1].minor.yy602 = yymsp[0].minor.yy602; } + case 267: /* when_clause ::= WHEN expr */ + case 286: /* key_opt ::= KEY expr */ yytestcase(yyruleno==286); +{ yymsp[-1].minor.yy528 = yymsp[0].minor.yy528; } break; - case 263: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ + case 268: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ { - assert( yymsp[-2].minor.yy483!=0 ); - yymsp[-2].minor.yy483->pLast->pNext = yymsp[-1].minor.yy483; - yymsp[-2].minor.yy483->pLast = yymsp[-1].minor.yy483; + assert( yymsp[-2].minor.yy33!=0 ); + yymsp[-2].minor.yy33->pLast->pNext = yymsp[-1].minor.yy33; + yymsp[-2].minor.yy33->pLast = yymsp[-1].minor.yy33; } break; - case 264: /* trigger_cmd_list ::= trigger_cmd SEMI */ + case 269: /* trigger_cmd_list ::= trigger_cmd SEMI */ { - assert( yymsp[-1].minor.yy483!=0 ); - yymsp[-1].minor.yy483->pLast = yymsp[-1].minor.yy483; + assert( yymsp[-1].minor.yy33!=0 ); + yymsp[-1].minor.yy33->pLast = yymsp[-1].minor.yy33; } break; - case 265: /* trnm ::= nm DOT nm */ + case 270: /* trnm ::= nm DOT nm */ { yymsp[-2].minor.yy0 = yymsp[0].minor.yy0; sqlite3ErrorMsg(pParse, @@ -161604,364 +168231,370 @@ static YYACTIONTYPE yy_reduce( "statements within triggers"); } break; - case 266: /* tridxby ::= INDEXED BY nm */ + case 271: /* tridxby ::= INDEXED BY nm */ { sqlite3ErrorMsg(pParse, "the INDEXED BY clause is not allowed on UPDATE or DELETE statements " "within triggers"); } break; - case 267: /* tridxby ::= NOT INDEXED */ + case 272: /* tridxby ::= NOT INDEXED */ { sqlite3ErrorMsg(pParse, "the NOT INDEXED clause is not allowed on UPDATE or DELETE statements " "within triggers"); } break; - case 268: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */ -{yylhsminor.yy483 = sqlite3TriggerUpdateStep(pParse, &yymsp[-6].minor.yy0, yymsp[-2].minor.yy291, yymsp[-3].minor.yy338, yymsp[-1].minor.yy602, yymsp[-7].minor.yy60, yymsp[-8].minor.yy0.z, yymsp[0].minor.yy528);} - yymsp[-8].minor.yy483 = yylhsminor.yy483; + case 273: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */ +{yylhsminor.yy33 = sqlite3TriggerUpdateStep(pParse, &yymsp[-6].minor.yy0, yymsp[-2].minor.yy131, yymsp[-3].minor.yy322, yymsp[-1].minor.yy528, yymsp[-7].minor.yy394, yymsp[-8].minor.yy0.z, yymsp[0].minor.yy522);} + yymsp[-8].minor.yy33 = yylhsminor.yy33; break; - case 269: /* trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ + case 274: /* trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ { - yylhsminor.yy483 = sqlite3TriggerInsertStep(pParse,&yymsp[-4].minor.yy0,yymsp[-3].minor.yy288,yymsp[-2].minor.yy307,yymsp[-6].minor.yy60,yymsp[-1].minor.yy178,yymsp[-7].minor.yy528,yymsp[0].minor.yy528);/*yylhsminor.yy483-overwrites-yymsp[-6].minor.yy60*/ + yylhsminor.yy33 = sqlite3TriggerInsertStep(pParse,&yymsp[-4].minor.yy0,yymsp[-3].minor.yy254,yymsp[-2].minor.yy47,yymsp[-6].minor.yy394,yymsp[-1].minor.yy444,yymsp[-7].minor.yy522,yymsp[0].minor.yy522);/*yylhsminor.yy33-overwrites-yymsp[-6].minor.yy394*/ } - yymsp[-7].minor.yy483 = yylhsminor.yy483; + yymsp[-7].minor.yy33 = yylhsminor.yy33; break; - case 270: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */ -{yylhsminor.yy483 = sqlite3TriggerDeleteStep(pParse, &yymsp[-3].minor.yy0, yymsp[-1].minor.yy602, yymsp[-5].minor.yy0.z, yymsp[0].minor.yy528);} - yymsp[-5].minor.yy483 = yylhsminor.yy483; + case 275: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */ +{yylhsminor.yy33 = sqlite3TriggerDeleteStep(pParse, &yymsp[-3].minor.yy0, yymsp[-1].minor.yy528, yymsp[-5].minor.yy0.z, yymsp[0].minor.yy522);} + yymsp[-5].minor.yy33 = yylhsminor.yy33; break; - case 271: /* trigger_cmd ::= scanpt select scanpt */ -{yylhsminor.yy483 = sqlite3TriggerSelectStep(pParse->db, yymsp[-1].minor.yy307, yymsp[-2].minor.yy528, yymsp[0].minor.yy528); /*yylhsminor.yy483-overwrites-yymsp[-1].minor.yy307*/} - yymsp[-2].minor.yy483 = yylhsminor.yy483; + case 276: /* trigger_cmd ::= scanpt select scanpt */ +{yylhsminor.yy33 = sqlite3TriggerSelectStep(pParse->db, yymsp[-1].minor.yy47, yymsp[-2].minor.yy522, yymsp[0].minor.yy522); /*yylhsminor.yy33-overwrites-yymsp[-1].minor.yy47*/} + yymsp[-2].minor.yy33 = yylhsminor.yy33; break; - case 272: /* expr ::= RAISE LP IGNORE RP */ + case 277: /* expr ::= RAISE LP IGNORE RP */ { - yymsp[-3].minor.yy602 = sqlite3PExpr(pParse, TK_RAISE, 0, 0); - if( yymsp[-3].minor.yy602 ){ - yymsp[-3].minor.yy602->affExpr = OE_Ignore; + yymsp[-3].minor.yy528 = sqlite3PExpr(pParse, TK_RAISE, 0, 0); + if( yymsp[-3].minor.yy528 ){ + yymsp[-3].minor.yy528->affExpr = OE_Ignore; } } break; - case 273: /* expr ::= RAISE LP raisetype COMMA nm RP */ + case 278: /* expr ::= RAISE LP raisetype COMMA nm RP */ { - yymsp[-5].minor.yy602 = sqlite3ExprAlloc(pParse->db, TK_RAISE, &yymsp[-1].minor.yy0, 1); - if( yymsp[-5].minor.yy602 ) { - yymsp[-5].minor.yy602->affExpr = (char)yymsp[-3].minor.yy60; + yymsp[-5].minor.yy528 = sqlite3ExprAlloc(pParse->db, TK_RAISE, &yymsp[-1].minor.yy0, 1); + if( yymsp[-5].minor.yy528 ) { + yymsp[-5].minor.yy528->affExpr = (char)yymsp[-3].minor.yy394; } } break; - case 274: /* raisetype ::= ROLLBACK */ -{yymsp[0].minor.yy60 = OE_Rollback;} + case 279: /* raisetype ::= ROLLBACK */ +{yymsp[0].minor.yy394 = OE_Rollback;} break; - case 276: /* raisetype ::= FAIL */ -{yymsp[0].minor.yy60 = OE_Fail;} + case 281: /* raisetype ::= FAIL */ +{yymsp[0].minor.yy394 = OE_Fail;} break; - case 277: /* cmd ::= DROP TRIGGER ifexists fullname */ + case 282: /* cmd ::= DROP TRIGGER ifexists fullname */ { - sqlite3DropTrigger(pParse,yymsp[0].minor.yy291,yymsp[-1].minor.yy60); + sqlite3DropTrigger(pParse,yymsp[0].minor.yy131,yymsp[-1].minor.yy394); } break; - case 278: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ + case 283: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ { - sqlite3Attach(pParse, yymsp[-3].minor.yy602, yymsp[-1].minor.yy602, yymsp[0].minor.yy602); + sqlite3Attach(pParse, yymsp[-3].minor.yy528, yymsp[-1].minor.yy528, yymsp[0].minor.yy528); } break; - case 279: /* cmd ::= DETACH database_kw_opt expr */ + case 284: /* cmd ::= DETACH database_kw_opt expr */ { - sqlite3Detach(pParse, yymsp[0].minor.yy602); + sqlite3Detach(pParse, yymsp[0].minor.yy528); } break; - case 282: /* cmd ::= REINDEX */ + case 287: /* cmd ::= REINDEX */ {sqlite3Reindex(pParse, 0, 0);} break; - case 283: /* cmd ::= REINDEX nm dbnm */ + case 288: /* cmd ::= REINDEX nm dbnm */ {sqlite3Reindex(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} break; - case 284: /* cmd ::= ANALYZE */ + case 289: /* cmd ::= ANALYZE */ {sqlite3Analyze(pParse, 0, 0);} break; - case 285: /* cmd ::= ANALYZE nm dbnm */ + case 290: /* cmd ::= ANALYZE nm dbnm */ {sqlite3Analyze(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} break; - case 286: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ + case 291: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ { - sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy291,&yymsp[0].minor.yy0); + sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy131,&yymsp[0].minor.yy0); } break; - case 287: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ + case 292: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ { yymsp[-1].minor.yy0.n = (int)(pParse->sLastToken.z-yymsp[-1].minor.yy0.z) + pParse->sLastToken.n; sqlite3AlterFinishAddColumn(pParse, &yymsp[-1].minor.yy0); } break; - case 288: /* cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */ + case 293: /* cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */ { - sqlite3AlterDropColumn(pParse, yymsp[-3].minor.yy291, &yymsp[0].minor.yy0); + sqlite3AlterDropColumn(pParse, yymsp[-3].minor.yy131, &yymsp[0].minor.yy0); } break; - case 289: /* add_column_fullname ::= fullname */ + case 294: /* add_column_fullname ::= fullname */ { disableLookaside(pParse); - sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy291); + sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy131); } break; - case 290: /* cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ + case 295: /* cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ { - sqlite3AlterRenameColumn(pParse, yymsp[-5].minor.yy291, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0); + sqlite3AlterRenameColumn(pParse, yymsp[-5].minor.yy131, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0); } break; - case 291: /* cmd ::= create_vtab */ + case 296: /* cmd ::= create_vtab */ {sqlite3VtabFinishParse(pParse,0);} break; - case 292: /* cmd ::= create_vtab LP vtabarglist RP */ + case 297: /* cmd ::= create_vtab LP vtabarglist RP */ {sqlite3VtabFinishParse(pParse,&yymsp[0].minor.yy0);} break; - case 293: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ + case 298: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ { - sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy60); + sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy394); } break; - case 294: /* vtabarg ::= */ + case 299: /* vtabarg ::= */ {sqlite3VtabArgInit(pParse);} break; - case 295: /* vtabargtoken ::= ANY */ - case 296: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==296); - case 297: /* lp ::= LP */ yytestcase(yyruleno==297); + case 300: /* vtabargtoken ::= ANY */ + case 301: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==301); + case 302: /* lp ::= LP */ yytestcase(yyruleno==302); {sqlite3VtabArgExtend(pParse,&yymsp[0].minor.yy0);} break; - case 298: /* with ::= WITH wqlist */ - case 299: /* with ::= WITH RECURSIVE wqlist */ yytestcase(yyruleno==299); -{ sqlite3WithPush(pParse, yymsp[0].minor.yy195, 1); } + case 303: /* with ::= WITH wqlist */ + case 304: /* with ::= WITH RECURSIVE wqlist */ yytestcase(yyruleno==304); +{ sqlite3WithPush(pParse, yymsp[0].minor.yy521, 1); } break; - case 300: /* wqas ::= AS */ -{yymsp[0].minor.yy570 = M10d_Any;} + case 305: /* wqas ::= AS */ +{yymsp[0].minor.yy516 = M10d_Any;} break; - case 301: /* wqas ::= AS MATERIALIZED */ -{yymsp[-1].minor.yy570 = M10d_Yes;} + case 306: /* wqas ::= AS MATERIALIZED */ +{yymsp[-1].minor.yy516 = M10d_Yes;} break; - case 302: /* wqas ::= AS NOT MATERIALIZED */ -{yymsp[-2].minor.yy570 = M10d_No;} + case 307: /* wqas ::= AS NOT MATERIALIZED */ +{yymsp[-2].minor.yy516 = M10d_No;} break; - case 303: /* wqitem ::= nm eidlist_opt wqas LP select RP */ + case 308: /* wqitem ::= nm eidlist_opt wqas LP select RP */ { - yymsp[-5].minor.yy607 = sqlite3CteNew(pParse, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy338, yymsp[-1].minor.yy307, yymsp[-3].minor.yy570); /*A-overwrites-X*/ + yymsp[-5].minor.yy385 = sqlite3CteNew(pParse, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy322, yymsp[-1].minor.yy47, yymsp[-3].minor.yy516); /*A-overwrites-X*/ } break; - case 304: /* wqlist ::= wqitem */ + case 309: /* wqlist ::= wqitem */ { - yymsp[0].minor.yy195 = sqlite3WithAdd(pParse, 0, yymsp[0].minor.yy607); /*A-overwrites-X*/ + yymsp[0].minor.yy521 = sqlite3WithAdd(pParse, 0, yymsp[0].minor.yy385); /*A-overwrites-X*/ } break; - case 305: /* wqlist ::= wqlist COMMA wqitem */ + case 310: /* wqlist ::= wqlist COMMA wqitem */ { - yymsp[-2].minor.yy195 = sqlite3WithAdd(pParse, yymsp[-2].minor.yy195, yymsp[0].minor.yy607); + yymsp[-2].minor.yy521 = sqlite3WithAdd(pParse, yymsp[-2].minor.yy521, yymsp[0].minor.yy385); } break; - case 306: /* windowdefn_list ::= windowdefn */ -{ yylhsminor.yy19 = yymsp[0].minor.yy19; } - yymsp[0].minor.yy19 = yylhsminor.yy19; + case 311: /* windowdefn_list ::= windowdefn */ +{ yylhsminor.yy41 = yymsp[0].minor.yy41; } + yymsp[0].minor.yy41 = yylhsminor.yy41; break; - case 307: /* windowdefn_list ::= windowdefn_list COMMA windowdefn */ + case 312: /* windowdefn_list ::= windowdefn_list COMMA windowdefn */ { - assert( yymsp[0].minor.yy19!=0 ); - sqlite3WindowChain(pParse, yymsp[0].minor.yy19, yymsp[-2].minor.yy19); - yymsp[0].minor.yy19->pNextWin = yymsp[-2].minor.yy19; - yylhsminor.yy19 = yymsp[0].minor.yy19; + assert( yymsp[0].minor.yy41!=0 ); + sqlite3WindowChain(pParse, yymsp[0].minor.yy41, yymsp[-2].minor.yy41); + yymsp[0].minor.yy41->pNextWin = yymsp[-2].minor.yy41; + yylhsminor.yy41 = yymsp[0].minor.yy41; } - yymsp[-2].minor.yy19 = yylhsminor.yy19; + yymsp[-2].minor.yy41 = yylhsminor.yy41; break; - case 308: /* windowdefn ::= nm AS LP window RP */ + case 313: /* windowdefn ::= nm AS LP window RP */ { - if( ALWAYS(yymsp[-1].minor.yy19) ){ - yymsp[-1].minor.yy19->zName = sqlite3DbStrNDup(pParse->db, yymsp[-4].minor.yy0.z, yymsp[-4].minor.yy0.n); + if( ALWAYS(yymsp[-1].minor.yy41) ){ + yymsp[-1].minor.yy41->zName = sqlite3DbStrNDup(pParse->db, yymsp[-4].minor.yy0.z, yymsp[-4].minor.yy0.n); } - yylhsminor.yy19 = yymsp[-1].minor.yy19; + yylhsminor.yy41 = yymsp[-1].minor.yy41; } - yymsp[-4].minor.yy19 = yylhsminor.yy19; + yymsp[-4].minor.yy41 = yylhsminor.yy41; break; - case 309: /* window ::= PARTITION BY nexprlist orderby_opt frame_opt */ + case 314: /* window ::= PARTITION BY nexprlist orderby_opt frame_opt */ { - yymsp[-4].minor.yy19 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy19, yymsp[-2].minor.yy338, yymsp[-1].minor.yy338, 0); + yymsp[-4].minor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, yymsp[-2].minor.yy322, yymsp[-1].minor.yy322, 0); } break; - case 310: /* window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ + case 315: /* window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ { - yylhsminor.yy19 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy19, yymsp[-2].minor.yy338, yymsp[-1].minor.yy338, &yymsp[-5].minor.yy0); + yylhsminor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, yymsp[-2].minor.yy322, yymsp[-1].minor.yy322, &yymsp[-5].minor.yy0); } - yymsp[-5].minor.yy19 = yylhsminor.yy19; + yymsp[-5].minor.yy41 = yylhsminor.yy41; break; - case 311: /* window ::= ORDER BY sortlist frame_opt */ + case 316: /* window ::= ORDER BY sortlist frame_opt */ { - yymsp[-3].minor.yy19 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy19, 0, yymsp[-1].minor.yy338, 0); + yymsp[-3].minor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, 0, yymsp[-1].minor.yy322, 0); } break; - case 312: /* window ::= nm ORDER BY sortlist frame_opt */ + case 317: /* window ::= nm ORDER BY sortlist frame_opt */ { - yylhsminor.yy19 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy19, 0, yymsp[-1].minor.yy338, &yymsp[-4].minor.yy0); + yylhsminor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, 0, yymsp[-1].minor.yy322, &yymsp[-4].minor.yy0); } - yymsp[-4].minor.yy19 = yylhsminor.yy19; + yymsp[-4].minor.yy41 = yylhsminor.yy41; break; - case 313: /* window ::= frame_opt */ - case 332: /* filter_over ::= over_clause */ yytestcase(yyruleno==332); + case 318: /* window ::= frame_opt */ + case 337: /* filter_over ::= over_clause */ yytestcase(yyruleno==337); { - yylhsminor.yy19 = yymsp[0].minor.yy19; + yylhsminor.yy41 = yymsp[0].minor.yy41; } - yymsp[0].minor.yy19 = yylhsminor.yy19; + yymsp[0].minor.yy41 = yylhsminor.yy41; break; - case 314: /* window ::= nm frame_opt */ + case 319: /* window ::= nm frame_opt */ { - yylhsminor.yy19 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy19, 0, 0, &yymsp[-1].minor.yy0); + yylhsminor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, 0, 0, &yymsp[-1].minor.yy0); } - yymsp[-1].minor.yy19 = yylhsminor.yy19; + yymsp[-1].minor.yy41 = yylhsminor.yy41; break; - case 315: /* frame_opt ::= */ + case 320: /* frame_opt ::= */ { - yymsp[1].minor.yy19 = sqlite3WindowAlloc(pParse, 0, TK_UNBOUNDED, 0, TK_CURRENT, 0, 0); + yymsp[1].minor.yy41 = sqlite3WindowAlloc(pParse, 0, TK_UNBOUNDED, 0, TK_CURRENT, 0, 0); } break; - case 316: /* frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ + case 321: /* frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ { - yylhsminor.yy19 = sqlite3WindowAlloc(pParse, yymsp[-2].minor.yy60, yymsp[-1].minor.yy113.eType, yymsp[-1].minor.yy113.pExpr, TK_CURRENT, 0, yymsp[0].minor.yy570); + yylhsminor.yy41 = sqlite3WindowAlloc(pParse, yymsp[-2].minor.yy394, yymsp[-1].minor.yy595.eType, yymsp[-1].minor.yy595.pExpr, TK_CURRENT, 0, yymsp[0].minor.yy516); } - yymsp[-2].minor.yy19 = yylhsminor.yy19; + yymsp[-2].minor.yy41 = yylhsminor.yy41; break; - case 317: /* frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ + case 322: /* frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ { - yylhsminor.yy19 = sqlite3WindowAlloc(pParse, yymsp[-5].minor.yy60, yymsp[-3].minor.yy113.eType, yymsp[-3].minor.yy113.pExpr, yymsp[-1].minor.yy113.eType, yymsp[-1].minor.yy113.pExpr, yymsp[0].minor.yy570); + yylhsminor.yy41 = sqlite3WindowAlloc(pParse, yymsp[-5].minor.yy394, yymsp[-3].minor.yy595.eType, yymsp[-3].minor.yy595.pExpr, yymsp[-1].minor.yy595.eType, yymsp[-1].minor.yy595.pExpr, yymsp[0].minor.yy516); } - yymsp[-5].minor.yy19 = yylhsminor.yy19; + yymsp[-5].minor.yy41 = yylhsminor.yy41; break; - case 319: /* frame_bound_s ::= frame_bound */ - case 321: /* frame_bound_e ::= frame_bound */ yytestcase(yyruleno==321); -{yylhsminor.yy113 = yymsp[0].minor.yy113;} - yymsp[0].minor.yy113 = yylhsminor.yy113; + case 324: /* frame_bound_s ::= frame_bound */ + case 326: /* frame_bound_e ::= frame_bound */ yytestcase(yyruleno==326); +{yylhsminor.yy595 = yymsp[0].minor.yy595;} + yymsp[0].minor.yy595 = yylhsminor.yy595; break; - case 320: /* frame_bound_s ::= UNBOUNDED PRECEDING */ - case 322: /* frame_bound_e ::= UNBOUNDED FOLLOWING */ yytestcase(yyruleno==322); - case 324: /* frame_bound ::= CURRENT ROW */ yytestcase(yyruleno==324); -{yylhsminor.yy113.eType = yymsp[-1].major; yylhsminor.yy113.pExpr = 0;} - yymsp[-1].minor.yy113 = yylhsminor.yy113; + case 325: /* frame_bound_s ::= UNBOUNDED PRECEDING */ + case 327: /* frame_bound_e ::= UNBOUNDED FOLLOWING */ yytestcase(yyruleno==327); + case 329: /* frame_bound ::= CURRENT ROW */ yytestcase(yyruleno==329); +{yylhsminor.yy595.eType = yymsp[-1].major; yylhsminor.yy595.pExpr = 0;} + yymsp[-1].minor.yy595 = yylhsminor.yy595; break; - case 323: /* frame_bound ::= expr PRECEDING|FOLLOWING */ -{yylhsminor.yy113.eType = yymsp[0].major; yylhsminor.yy113.pExpr = yymsp[-1].minor.yy602;} - yymsp[-1].minor.yy113 = yylhsminor.yy113; + case 328: /* frame_bound ::= expr PRECEDING|FOLLOWING */ +{yylhsminor.yy595.eType = yymsp[0].major; yylhsminor.yy595.pExpr = yymsp[-1].minor.yy528;} + yymsp[-1].minor.yy595 = yylhsminor.yy595; break; - case 325: /* frame_exclude_opt ::= */ -{yymsp[1].minor.yy570 = 0;} + case 330: /* frame_exclude_opt ::= */ +{yymsp[1].minor.yy516 = 0;} break; - case 326: /* frame_exclude_opt ::= EXCLUDE frame_exclude */ -{yymsp[-1].minor.yy570 = yymsp[0].minor.yy570;} + case 331: /* frame_exclude_opt ::= EXCLUDE frame_exclude */ +{yymsp[-1].minor.yy516 = yymsp[0].minor.yy516;} break; - case 327: /* frame_exclude ::= NO OTHERS */ - case 328: /* frame_exclude ::= CURRENT ROW */ yytestcase(yyruleno==328); -{yymsp[-1].minor.yy570 = yymsp[-1].major; /*A-overwrites-X*/} + case 332: /* frame_exclude ::= NO OTHERS */ + case 333: /* frame_exclude ::= CURRENT ROW */ yytestcase(yyruleno==333); +{yymsp[-1].minor.yy516 = yymsp[-1].major; /*A-overwrites-X*/} break; - case 329: /* frame_exclude ::= GROUP|TIES */ -{yymsp[0].minor.yy570 = yymsp[0].major; /*A-overwrites-X*/} + case 334: /* frame_exclude ::= GROUP|TIES */ +{yymsp[0].minor.yy516 = yymsp[0].major; /*A-overwrites-X*/} break; - case 330: /* window_clause ::= WINDOW windowdefn_list */ -{ yymsp[-1].minor.yy19 = yymsp[0].minor.yy19; } + case 335: /* window_clause ::= WINDOW windowdefn_list */ +{ yymsp[-1].minor.yy41 = yymsp[0].minor.yy41; } break; - case 331: /* filter_over ::= filter_clause over_clause */ + case 336: /* filter_over ::= filter_clause over_clause */ { - yymsp[0].minor.yy19->pFilter = yymsp[-1].minor.yy602; - yylhsminor.yy19 = yymsp[0].minor.yy19; -} - yymsp[-1].minor.yy19 = yylhsminor.yy19; - break; - case 333: /* filter_over ::= filter_clause */ -{ - yylhsminor.yy19 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); - if( yylhsminor.yy19 ){ - yylhsminor.yy19->eFrmType = TK_FILTER; - yylhsminor.yy19->pFilter = yymsp[0].minor.yy602; + if( yymsp[0].minor.yy41 ){ + yymsp[0].minor.yy41->pFilter = yymsp[-1].minor.yy528; }else{ - sqlite3ExprDelete(pParse->db, yymsp[0].minor.yy602); + sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy528); + } + yylhsminor.yy41 = yymsp[0].minor.yy41; +} + yymsp[-1].minor.yy41 = yylhsminor.yy41; + break; + case 338: /* filter_over ::= filter_clause */ +{ + yylhsminor.yy41 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); + if( yylhsminor.yy41 ){ + yylhsminor.yy41->eFrmType = TK_FILTER; + yylhsminor.yy41->pFilter = yymsp[0].minor.yy528; + }else{ + sqlite3ExprDelete(pParse->db, yymsp[0].minor.yy528); } } - yymsp[0].minor.yy19 = yylhsminor.yy19; + yymsp[0].minor.yy41 = yylhsminor.yy41; break; - case 334: /* over_clause ::= OVER LP window RP */ + case 339: /* over_clause ::= OVER LP window RP */ { - yymsp[-3].minor.yy19 = yymsp[-1].minor.yy19; - assert( yymsp[-3].minor.yy19!=0 ); + yymsp[-3].minor.yy41 = yymsp[-1].minor.yy41; + assert( yymsp[-3].minor.yy41!=0 ); } break; - case 335: /* over_clause ::= OVER nm */ + case 340: /* over_clause ::= OVER nm */ { - yymsp[-1].minor.yy19 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); - if( yymsp[-1].minor.yy19 ){ - yymsp[-1].minor.yy19->zName = sqlite3DbStrNDup(pParse->db, yymsp[0].minor.yy0.z, yymsp[0].minor.yy0.n); + yymsp[-1].minor.yy41 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); + if( yymsp[-1].minor.yy41 ){ + yymsp[-1].minor.yy41->zName = sqlite3DbStrNDup(pParse->db, yymsp[0].minor.yy0.z, yymsp[0].minor.yy0.n); } } break; - case 336: /* filter_clause ::= FILTER LP WHERE expr RP */ -{ yymsp[-4].minor.yy602 = yymsp[-1].minor.yy602; } + case 341: /* filter_clause ::= FILTER LP WHERE expr RP */ +{ yymsp[-4].minor.yy528 = yymsp[-1].minor.yy528; } break; default: - /* (337) input ::= cmdlist */ yytestcase(yyruleno==337); - /* (338) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==338); - /* (339) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=339); - /* (340) ecmd ::= SEMI */ yytestcase(yyruleno==340); - /* (341) ecmd ::= cmdx SEMI */ yytestcase(yyruleno==341); - /* (342) ecmd ::= explain cmdx SEMI (NEVER REDUCES) */ assert(yyruleno!=342); - /* (343) trans_opt ::= */ yytestcase(yyruleno==343); - /* (344) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==344); - /* (345) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==345); - /* (346) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==346); - /* (347) savepoint_opt ::= */ yytestcase(yyruleno==347); - /* (348) cmd ::= create_table create_table_args */ yytestcase(yyruleno==348); - /* (349) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==349); - /* (350) columnlist ::= columnname carglist */ yytestcase(yyruleno==350); - /* (351) nm ::= ID|INDEXED */ yytestcase(yyruleno==351); - /* (352) nm ::= STRING */ yytestcase(yyruleno==352); - /* (353) nm ::= JOIN_KW */ yytestcase(yyruleno==353); - /* (354) typetoken ::= typename */ yytestcase(yyruleno==354); - /* (355) typename ::= ID|STRING */ yytestcase(yyruleno==355); - /* (356) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=356); - /* (357) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=357); - /* (358) carglist ::= carglist ccons */ yytestcase(yyruleno==358); - /* (359) carglist ::= */ yytestcase(yyruleno==359); - /* (360) ccons ::= NULL onconf */ yytestcase(yyruleno==360); - /* (361) ccons ::= GENERATED ALWAYS AS generated */ yytestcase(yyruleno==361); - /* (362) ccons ::= AS generated */ yytestcase(yyruleno==362); - /* (363) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==363); - /* (364) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==364); - /* (365) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=365); - /* (366) tconscomma ::= */ yytestcase(yyruleno==366); - /* (367) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=367); - /* (368) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=368); - /* (369) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=369); - /* (370) oneselect ::= values */ yytestcase(yyruleno==370); - /* (371) sclp ::= selcollist COMMA */ yytestcase(yyruleno==371); - /* (372) as ::= ID|STRING */ yytestcase(yyruleno==372); - /* (373) returning ::= */ yytestcase(yyruleno==373); - /* (374) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=374); - /* (375) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==375); - /* (376) exprlist ::= nexprlist */ yytestcase(yyruleno==376); - /* (377) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=377); - /* (378) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=378); - /* (379) nmnum ::= ON */ yytestcase(yyruleno==379); - /* (380) nmnum ::= DELETE */ yytestcase(yyruleno==380); - /* (381) nmnum ::= DEFAULT */ yytestcase(yyruleno==381); - /* (382) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==382); - /* (383) foreach_clause ::= */ yytestcase(yyruleno==383); - /* (384) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==384); - /* (385) trnm ::= nm */ yytestcase(yyruleno==385); - /* (386) tridxby ::= */ yytestcase(yyruleno==386); - /* (387) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==387); - /* (388) database_kw_opt ::= */ yytestcase(yyruleno==388); - /* (389) kwcolumn_opt ::= */ yytestcase(yyruleno==389); - /* (390) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==390); - /* (391) vtabarglist ::= vtabarg */ yytestcase(yyruleno==391); - /* (392) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==392); - /* (393) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==393); - /* (394) anylist ::= */ yytestcase(yyruleno==394); - /* (395) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==395); - /* (396) anylist ::= anylist ANY */ yytestcase(yyruleno==396); - /* (397) with ::= */ yytestcase(yyruleno==397); + /* (342) input ::= cmdlist */ yytestcase(yyruleno==342); + /* (343) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==343); + /* (344) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=344); + /* (345) ecmd ::= SEMI */ yytestcase(yyruleno==345); + /* (346) ecmd ::= cmdx SEMI */ yytestcase(yyruleno==346); + /* (347) ecmd ::= explain cmdx SEMI (NEVER REDUCES) */ assert(yyruleno!=347); + /* (348) trans_opt ::= */ yytestcase(yyruleno==348); + /* (349) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==349); + /* (350) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==350); + /* (351) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==351); + /* (352) savepoint_opt ::= */ yytestcase(yyruleno==352); + /* (353) cmd ::= create_table create_table_args */ yytestcase(yyruleno==353); + /* (354) table_option_set ::= table_option (OPTIMIZED OUT) */ assert(yyruleno!=354); + /* (355) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==355); + /* (356) columnlist ::= columnname carglist */ yytestcase(yyruleno==356); + /* (357) nm ::= ID|INDEXED */ yytestcase(yyruleno==357); + /* (358) nm ::= STRING */ yytestcase(yyruleno==358); + /* (359) nm ::= JOIN_KW */ yytestcase(yyruleno==359); + /* (360) typetoken ::= typename */ yytestcase(yyruleno==360); + /* (361) typename ::= ID|STRING */ yytestcase(yyruleno==361); + /* (362) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=362); + /* (363) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=363); + /* (364) carglist ::= carglist ccons */ yytestcase(yyruleno==364); + /* (365) carglist ::= */ yytestcase(yyruleno==365); + /* (366) ccons ::= NULL onconf */ yytestcase(yyruleno==366); + /* (367) ccons ::= GENERATED ALWAYS AS generated */ yytestcase(yyruleno==367); + /* (368) ccons ::= AS generated */ yytestcase(yyruleno==368); + /* (369) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==369); + /* (370) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==370); + /* (371) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=371); + /* (372) tconscomma ::= */ yytestcase(yyruleno==372); + /* (373) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=373); + /* (374) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=374); + /* (375) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=375); + /* (376) oneselect ::= values */ yytestcase(yyruleno==376); + /* (377) sclp ::= selcollist COMMA */ yytestcase(yyruleno==377); + /* (378) as ::= ID|STRING */ yytestcase(yyruleno==378); + /* (379) indexed_opt ::= indexed_by (OPTIMIZED OUT) */ assert(yyruleno!=379); + /* (380) returning ::= */ yytestcase(yyruleno==380); + /* (381) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=381); + /* (382) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==382); + /* (383) exprlist ::= nexprlist */ yytestcase(yyruleno==383); + /* (384) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=384); + /* (385) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=385); + /* (386) nmnum ::= ON */ yytestcase(yyruleno==386); + /* (387) nmnum ::= DELETE */ yytestcase(yyruleno==387); + /* (388) nmnum ::= DEFAULT */ yytestcase(yyruleno==388); + /* (389) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==389); + /* (390) foreach_clause ::= */ yytestcase(yyruleno==390); + /* (391) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==391); + /* (392) trnm ::= nm */ yytestcase(yyruleno==392); + /* (393) tridxby ::= */ yytestcase(yyruleno==393); + /* (394) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==394); + /* (395) database_kw_opt ::= */ yytestcase(yyruleno==395); + /* (396) kwcolumn_opt ::= */ yytestcase(yyruleno==396); + /* (397) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==397); + /* (398) vtabarglist ::= vtabarg */ yytestcase(yyruleno==398); + /* (399) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==399); + /* (400) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==400); + /* (401) anylist ::= */ yytestcase(yyruleno==401); + /* (402) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==402); + /* (403) anylist ::= anylist ANY */ yytestcase(yyruleno==403); + /* (404) with ::= */ yytestcase(yyruleno==404); break; /********** End reduce actions ************************************************/ }; @@ -162119,8 +168752,8 @@ SQLITE_PRIVATE void sqlite3Parser( yyact = yy_find_shift_action((YYCODETYPE)yymajor,yyact); if( yyact >= YY_MIN_REDUCE ){ unsigned int yyruleno = yyact - YY_MIN_REDUCE; /* Reduce by this rule */ - assert( yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ); #ifndef NDEBUG + assert( yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ); if( yyTraceFILE ){ int yysize = yyRuleInfoNRhs[yyruleno]; if( yysize ){ @@ -162218,14 +168851,13 @@ SQLITE_PRIVATE void sqlite3Parser( yy_destructor(yypParser, (YYCODETYPE)yymajor, &yyminorunion); yymajor = YYNOCODE; }else{ - while( yypParser->yytos >= yypParser->yystack - && (yyact = yy_find_reduce_action( - yypParser->yytos->stateno, - YYERRORSYMBOL)) > YY_MAX_SHIFTREDUCE - ){ + while( yypParser->yytos > yypParser->yystack ){ + yyact = yy_find_reduce_action(yypParser->yytos->stateno, + YYERRORSYMBOL); + if( yyact<=YY_MAX_SHIFTREDUCE ) break; yy_pop_parser_stack(yypParser); } - if( yypParser->yytos < yypParser->yystack || yymajor==0 ){ + if( yypParser->yytos <= yypParser->yystack || yymajor==0 ){ yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); yy_parse_failed(yypParser); #ifndef YYNOERRORRECOVERY @@ -162365,6 +168997,7 @@ SQLITE_PRIVATE int sqlite3ParserFallback(int iToken){ #define CC_ID 27 /* unicode characters usable in IDs */ #define CC_ILLEGAL 28 /* Illegal character */ #define CC_NUL 29 /* 0x00 */ +#define CC_BOM 30 /* First byte of UTF8 BOM: 0xEF 0xBB 0xBF */ static const unsigned char aiClass[] = { #ifdef SQLITE_ASCII @@ -162377,14 +169010,14 @@ static const unsigned char aiClass[] = { /* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 9, 28, 28, 28, 2, /* 6x */ 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 28, 10, 28, 25, 28, -/* 8x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* 9x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Ax */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Bx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Cx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Dx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Ex */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Fx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 +/* 8x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* 9x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Ax */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Bx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Cx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Dx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Ex */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 30, +/* Fx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27 #endif #ifdef SQLITE_EBCDIC /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */ @@ -163084,6 +169717,9 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ for(i=2; (c=z[i])!=0 && c!='\n'; i++){} *tokenType = TK_SPACE; /* IMP: R-22934-25134 */ return i; + }else if( z[1]=='>' ){ + *tokenType = TK_PTR; + return 2 + (z[2]=='>'); } *tokenType = TK_MINUS; return 1; @@ -163330,6 +169966,14 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ i = 1; break; } + case CC_BOM: { + if( z[1]==0xbb && z[2]==0xbf ){ + *tokenType = TK_SPACE; + return 3; + } + i = 1; + break; + } case CC_NUL: { *tokenType = TK_ILLEGAL; return 0; @@ -163345,13 +169989,9 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ } /* -** Run the parser on the given SQL string. The parser structure is -** passed in. An SQLITE_ status code is returned. If an error occurs -** then an and attempt is made to write an error message into -** memory obtained from sqlite3_malloc() and to make *pzErrMsg point to that -** error message. +** Run the parser on the given SQL string. */ -SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ +SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql){ int nErr = 0; /* Number of errors encountered */ void *pEngine; /* The LEMON-generated LALR(1) parser */ int n = 0; /* Length of the next token token */ @@ -163359,6 +169999,7 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr int lastTokenParsed = -1; /* type of the previous token */ sqlite3 *db = pParse->db; /* The database connection */ int mxSqlLen; /* Max length of an SQL string */ + Parse *pParentParse = 0; /* Outer parse context, if any */ #ifdef sqlite3Parser_ENGINEALWAYSONSTACK yyParser sEngine; /* Space to hold the Lemon-generated Parser object */ #endif @@ -163371,7 +170012,6 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr } pParse->rc = SQLITE_OK; pParse->zTail = zSql; - assert( pzErrMsg!=0 ); #ifdef SQLITE_DEBUG if( db->flags & SQLITE_ParserTrace ){ printf("parser: [[[%s]]]\n", zSql); @@ -163394,13 +170034,14 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr assert( pParse->pNewTrigger==0 ); assert( pParse->nVar==0 ); assert( pParse->pVList==0 ); - pParse->pParentParse = db->pParse; + pParentParse = db->pParse; db->pParse = pParse; while( 1 ){ n = sqlite3GetToken((u8*)zSql, &tokenType); mxSqlLen -= n; if( mxSqlLen<0 ){ pParse->rc = SQLITE_TOOBIG; + pParse->nErr++; break; } #ifndef SQLITE_OMIT_WINDOWFUNC @@ -163414,6 +170055,7 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr #endif /* SQLITE_OMIT_WINDOWFUNC */ if( AtomicLoad(&db->u1.isInterrupted) ){ pParse->rc = SQLITE_INTERRUPT; + pParse->nErr++; break; } if( tokenType==TK_SPACE ){ @@ -163443,7 +170085,10 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr tokenType = analyzeFilterKeyword((const u8*)&zSql[6], lastTokenParsed); #endif /* SQLITE_OMIT_WINDOWFUNC */ }else{ - sqlite3ErrorMsg(pParse, "unrecognized token: \"%.*s\"", n, zSql); + Token x; + x.z = zSql; + x.n = n; + sqlite3ErrorMsg(pParse, "unrecognized token: \"%T\"", &x); break; } } @@ -163471,46 +170116,30 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr if( db->mallocFailed ){ pParse->rc = SQLITE_NOMEM_BKPT; } - if( pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE && pParse->zErrMsg==0 ){ - pParse->zErrMsg = sqlite3MPrintf(db, "%s", sqlite3ErrStr(pParse->rc)); - } - assert( pzErrMsg!=0 ); - if( pParse->zErrMsg ){ - *pzErrMsg = pParse->zErrMsg; - sqlite3_log(pParse->rc, "%s in \"%s\"", - *pzErrMsg, pParse->zTail); - pParse->zErrMsg = 0; + if( pParse->zErrMsg || (pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE) ){ + if( pParse->zErrMsg==0 ){ + pParse->zErrMsg = sqlite3MPrintf(db, "%s", sqlite3ErrStr(pParse->rc)); + } + sqlite3_log(pParse->rc, "%s in \"%s\"", pParse->zErrMsg, pParse->zTail); nErr++; } pParse->zTail = zSql; - if( pParse->pVdbe && pParse->nErr>0 && pParse->nested==0 ){ - sqlite3VdbeDelete(pParse->pVdbe); - pParse->pVdbe = 0; - } -#ifndef SQLITE_OMIT_SHARED_CACHE - if( pParse->nested==0 ){ - sqlite3DbFree(db, pParse->aTableLock); - pParse->aTableLock = 0; - pParse->nTableLock = 0; - } -#endif #ifndef SQLITE_OMIT_VIRTUALTABLE sqlite3_free(pParse->apVtabLock); #endif - if( !IN_SPECIAL_PARSE ){ + if( pParse->pNewTable && !IN_SPECIAL_PARSE ){ /* If the pParse->declareVtab flag is set, do not delete any table ** structure built up in pParse->pNewTable. The calling code (see vtab.c) ** will take responsibility for freeing the Table structure. */ sqlite3DeleteTable(db, pParse->pNewTable); } - if( !IN_RENAME_OBJECT ){ + if( pParse->pNewTrigger && !IN_RENAME_OBJECT ){ sqlite3DeleteTrigger(db, pParse->pNewTrigger); } - sqlite3DbFree(db, pParse->pVList); - db->pParse = pParse->pParentParse; - pParse->pParentParse = 0; + if( pParse->pVList ) sqlite3DbFreeNN(db, pParse->pVList); + db->pParse = pParentParse; assert( nErr==0 || pParse->rc!=SQLITE_OK ); return nErr; } @@ -164091,9 +170720,6 @@ SQLITE_PRIVATE int sqlite3Fts2Init(sqlite3*); #ifdef SQLITE_ENABLE_FTS5 SQLITE_PRIVATE int sqlite3Fts5Init(sqlite3*); #endif -#ifdef SQLITE_ENABLE_JSON1 -SQLITE_PRIVATE int sqlite3Json1Init(sqlite3*); -#endif #ifdef SQLITE_ENABLE_STMTVTAB SQLITE_PRIVATE int sqlite3StmtVtabInit(sqlite3*); #endif @@ -164128,8 +170754,8 @@ static int (*const sqlite3BuiltinExtensions[])(sqlite3*) = { sqlite3DbstatRegister, #endif sqlite3TestExtInit, -#ifdef SQLITE_ENABLE_JSON1 - sqlite3Json1Init, +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) + sqlite3JsonTableFunctions, #endif #ifdef SQLITE_ENABLE_STMTVTAB sqlite3StmtVtabInit, @@ -164346,7 +170972,7 @@ SQLITE_API int sqlite3_initialize(void){ sqlite3GlobalConfig.isPCacheInit = 1; rc = sqlite3OsInit(); } -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE if( rc==SQLITE_OK ){ rc = sqlite3MemdbInit(); } @@ -164761,12 +171387,12 @@ SQLITE_API int sqlite3_config(int op, ...){ } #endif /* SQLITE_ENABLE_SORTER_REFERENCES */ -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE case SQLITE_CONFIG_MEMDB_MAXSIZE: { sqlite3GlobalConfig.mxMemdbSize = va_arg(ap, sqlite3_int64); break; } -#endif /* SQLITE_ENABLE_DESERIALIZE */ +#endif /* SQLITE_OMIT_DESERIALIZE */ default: { rc = SQLITE_ERROR; @@ -165127,7 +171753,7 @@ SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3 *db, sqlite3_int64 iRowid) /* ** Return the number of changes in the most recent call to sqlite3_exec(). */ -SQLITE_API int sqlite3_changes(sqlite3 *db){ +SQLITE_API sqlite3_int64 sqlite3_changes64(sqlite3 *db){ #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ (void)SQLITE_MISUSE_BKPT; @@ -165136,11 +171762,14 @@ SQLITE_API int sqlite3_changes(sqlite3 *db){ #endif return db->nChange; } +SQLITE_API int sqlite3_changes(sqlite3 *db){ + return (int)sqlite3_changes64(db); +} /* ** Return the number of changes since the database handle was opened. */ -SQLITE_API int sqlite3_total_changes(sqlite3 *db){ +SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3 *db){ #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ (void)SQLITE_MISUSE_BKPT; @@ -165149,6 +171778,9 @@ SQLITE_API int sqlite3_total_changes(sqlite3 *db){ #endif return db->nTotalChange; } +SQLITE_API int sqlite3_total_changes(sqlite3 *db){ + return (int)sqlite3_total_changes64(db); +} /* ** Close all open savepoints. This function only manipulates fields of the @@ -165173,7 +171805,9 @@ SQLITE_PRIVATE void sqlite3CloseSavepoints(sqlite3 *db){ ** with SQLITE_ANY as the encoding. */ static void functionDestroy(sqlite3 *db, FuncDef *p){ - FuncDestructor *pDestructor = p->u.pDestructor; + FuncDestructor *pDestructor; + assert( (p->funcFlags & SQLITE_FUNC_BUILTIN)==0 ); + pDestructor = p->u.pDestructor; if( pDestructor ){ pDestructor->nRef--; if( pDestructor->nRef==0 ){ @@ -165277,7 +171911,7 @@ static int sqlite3Close(sqlite3 *db, int forceZombie){ /* Convert the connection into a zombie and then close it. */ - db->magic = SQLITE_MAGIC_ZOMBIE; + db->eOpenState = SQLITE_STATE_ZOMBIE; sqlite3LeaveMutexAndCloseZombie(db); return SQLITE_OK; } @@ -165315,7 +171949,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3 *db, const char *zSchema){ /* ** Two variations on the public interface for closing a database ** connection. The sqlite3_close() version returns SQLITE_BUSY and -** leaves the connection option if there are unfinalized prepared +** leaves the connection open if there are unfinalized prepared ** statements or unfinished sqlite3_backups. The sqlite3_close_v2() ** version forces the connection to become a zombie if there are ** unclosed resources, and arranges for deallocation when the last @@ -165341,7 +171975,7 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ ** or if the connection has not yet been closed by sqlite3_close_v2(), ** then just leave the mutex and return. */ - if( db->magic!=SQLITE_MAGIC_ZOMBIE || connectionIsBusy(db) ){ + if( db->eOpenState!=SQLITE_STATE_ZOMBIE || connectionIsBusy(db) ){ sqlite3_mutex_leave(db->mutex); return; } @@ -165427,7 +172061,7 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ sqlite3_free(db->auth.zAuthPW); #endif - db->magic = SQLITE_MAGIC_ERROR; + db->eOpenState = SQLITE_STATE_ERROR; /* The temp-database schema is allocated differently from the other schema ** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()). @@ -165436,8 +172070,11 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ ** structure? */ sqlite3DbFree(db, db->aDb[1].pSchema); + if( db->xAutovacDestr ){ + db->xAutovacDestr(db->pAutovacPagesArg); + } sqlite3_mutex_leave(db->mutex); - db->magic = SQLITE_MAGIC_CLOSED; + db->eOpenState = SQLITE_STATE_CLOSED; sqlite3_mutex_free(db->mutex); assert( sqlite3LookasideUsed(db,0)==0 ); if( db->lookaside.bMalloced ){ @@ -165490,7 +172127,7 @@ SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3 *db, int tripCode){ /* Any deferred constraint violations have now been resolved. */ db->nDeferredCons = 0; db->nDeferredImmCons = 0; - db->flags &= ~(u64)SQLITE_DeferFKs; + db->flags &= ~(u64)(SQLITE_DeferFKs|SQLITE_CorruptRdOnly); /* If one has been configured, invoke the rollback-hook callback */ if( db->xRollbackCallback && (inTrans || !db->autoCommit) ){ @@ -165825,7 +172462,7 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){ */ SQLITE_API void sqlite3_interrupt(sqlite3 *db){ #ifdef SQLITE_ENABLE_API_ARMOR - if( !sqlite3SafetyCheckOk(db) && (db==0 || db->magic!=SQLITE_MAGIC_ZOMBIE) ){ + if( !sqlite3SafetyCheckOk(db) && (db==0 || db->eOpenState!=SQLITE_STATE_ZOMBIE) ){ (void)SQLITE_MISUSE_BKPT; return; } @@ -165854,7 +172491,6 @@ SQLITE_PRIVATE int sqlite3CreateFunc( FuncDestructor *pDestructor ){ FuncDef *p; - int nName; int extraFlags; assert( sqlite3_mutex_held(db->mutex) ); @@ -165864,7 +172500,7 @@ SQLITE_PRIVATE int sqlite3CreateFunc( || ((xFinal==0)!=(xStep==0)) /* Both or neither of xFinal and xStep */ || ((xValue==0)!=(xInverse==0)) /* Both or neither of xValue, xInverse */ || (nArg<-1 || nArg>SQLITE_MAX_FUNCTION_ARG) - || (255<(nName = sqlite3Strlen30( zFunctionName))) + || (255nRef==0 ){ - assert( rc!=SQLITE_OK ); + assert( rc!=SQLITE_OK || (xStep==0 && xFinal==0) ); xDestroy(p); sqlite3_free(pArg); } @@ -166323,6 +172974,34 @@ SQLITE_API void *sqlite3_preupdate_hook( } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ +/* +** Register a function to be invoked prior to each autovacuum that +** determines the number of pages to vacuum. +*/ +SQLITE_API int sqlite3_autovacuum_pages( + sqlite3 *db, /* Attach the hook to this database */ + unsigned int (*xCallback)(void*,const char*,u32,u32,u32), + void *pArg, /* Argument to the function */ + void (*xDestructor)(void*) /* Destructor for pArg */ +){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + if( xDestructor ) xDestructor(pArg); + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_mutex_enter(db->mutex); + if( db->xAutovacDestr ){ + db->xAutovacDestr(db->pAutovacPagesArg); + } + db->xAutovacPages = xCallback; + db->pAutovacPagesArg = pArg; + db->xAutovacDestr = xDestructor; + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + + #ifndef SQLITE_OMIT_WAL /* ** The sqlite3_wal_hook() callback registered by sqlite3_wal_autocheckpoint(). @@ -166585,6 +173264,19 @@ SQLITE_API const char *sqlite3_errmsg(sqlite3 *db){ return z; } +/* +** Return the byte offset of the most recent error +*/ +SQLITE_API int sqlite3_error_offset(sqlite3 *db){ + int iOffset = -1; + if( db && sqlite3SafetyCheckSickOrOk(db) && db->errCode ){ + sqlite3_mutex_enter(db->mutex); + iOffset = db->errByteOffset; + sqlite3_mutex_leave(db->mutex); + } + return iOffset; +} + #ifndef SQLITE_OMIT_UTF16 /* ** Return UTF-16 encoded English language explanation of the most recent @@ -166845,6 +173537,8 @@ SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ if( newLimit>=0 ){ /* IMP: R-52476-28732 */ if( newLimit>aHardLimit[limitId] ){ newLimit = aHardLimit[limitId]; /* IMP: R-51463-25634 */ + }else if( newLimit<1 && limitId==SQLITE_LIMIT_LENGTH ){ + newLimit = 1; } db->aLimit[limitId] = newLimit; } @@ -167116,7 +173810,7 @@ SQLITE_PRIVATE int sqlite3ParseUri( */ static const char *uriParameter(const char *zFilename, const char *zParam){ zFilename += sqlite3Strlen30(zFilename) + 1; - while( zFilename[0] ){ + while( ALWAYS(zFilename!=0) && zFilename[0] ){ int x = strcmp(zFilename, zParam); zFilename += sqlite3Strlen30(zFilename) + 1; if( x==0 ) return zFilename; @@ -167176,8 +173870,8 @@ static int openDatabase( ** dealt with in the previous code block. Besides these, the only ** valid input flags for sqlite3_open_v2() are SQLITE_OPEN_READONLY, ** SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE, SQLITE_OPEN_SHAREDCACHE, - ** SQLITE_OPEN_PRIVATECACHE, and some reserved bits. Silently mask - ** off all other flags. + ** SQLITE_OPEN_PRIVATECACHE, SQLITE_OPEN_EXRESCODE, and some reserved + ** bits. Silently mask off all other flags. */ flags &= ~( SQLITE_OPEN_DELETEONCLOSE | SQLITE_OPEN_EXCLUSIVE | @@ -167212,9 +173906,9 @@ static int openDatabase( } } sqlite3_mutex_enter(db->mutex); - db->errMask = 0xff; + db->errMask = (flags & SQLITE_OPEN_EXRESCODE)!=0 ? 0xffffffff : 0xff; db->nDb = 2; - db->magic = SQLITE_MAGIC_BUSY; + db->eOpenState = SQLITE_STATE_BUSY; db->aDb = db->aDbStatic; db->lookaside.bDisable = 1; db->lookaside.sz = 0; @@ -167226,7 +173920,15 @@ static int openDatabase( db->nextAutovac = -1; db->szMmap = sqlite3GlobalConfig.szMmap; db->nextPagesize = 0; + db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */ +#ifdef SQLITE_ENABLE_SORTER_MMAP + /* Beginning with version 3.37.0, using the VFS xFetch() API to memory-map + ** the temporary files used to do external sorts (see code in vdbesort.c) + ** is disabled. It can still be used either by defining + ** SQLITE_ENABLE_SORTER_MMAP at compile time or by using the + ** SQLITE_TESTCTRL_SORTER_MMAP test-control at runtime. */ db->nMaxSorterMmap = 0x7FFFFFFF; +#endif db->flags |= SQLITE_ShortColNames | SQLITE_EnableTrigger | SQLITE_EnableView @@ -167374,7 +174076,7 @@ static int openDatabase( db->aDb[1].zDbSName = "temp"; db->aDb[1].safety_level = PAGER_SYNCHRONOUS_OFF; - db->magic = SQLITE_MAGIC_OPEN; + db->eOpenState = SQLITE_STATE_OPEN; if( db->mallocFailed ){ goto opendb_out; } @@ -167436,12 +174138,12 @@ opendb_out: sqlite3_mutex_leave(db->mutex); } rc = sqlite3_errcode(db); - assert( db!=0 || rc==SQLITE_NOMEM ); - if( rc==SQLITE_NOMEM ){ + assert( db!=0 || (rc&0xff)==SQLITE_NOMEM ); + if( (rc&0xff)==SQLITE_NOMEM ){ sqlite3_close(db); db = 0; }else if( rc!=SQLITE_OK ){ - db->magic = SQLITE_MAGIC_SICK; + db->eOpenState = SQLITE_STATE_SICK; } *ppDb = db; #ifdef SQLITE_ENABLE_SQLLOG @@ -167452,7 +174154,7 @@ opendb_out: } #endif sqlite3_free_filename(zOpen); - return rc & 0xff; + return rc; } @@ -167752,7 +174454,7 @@ SQLITE_API int sqlite3_table_column_metadata( /* Locate the table in question */ pTab = sqlite3FindTable(db, zTableName, zDbName); - if( !pTab || pTab->pSelect ){ + if( !pTab || IsView(pTab) ){ pTab = 0; goto error_out; } @@ -167763,7 +174465,7 @@ SQLITE_API int sqlite3_table_column_metadata( }else{ for(iCol=0; iColnCol; iCol++){ pCol = &pTab->aCol[iCol]; - if( 0==sqlite3StrICmp(pCol->zName, zColumnName) ){ + if( 0==sqlite3StrICmp(pCol->zCnName, zColumnName) ){ break; } } @@ -167790,7 +174492,7 @@ SQLITE_API int sqlite3_table_column_metadata( */ if( pCol ){ zDataType = sqlite3ColumnType(pCol,0); - zCollSeq = pCol->zColl; + zCollSeq = sqlite3ColumnColl(pCol); notnull = pCol->notNull!=0; primarykey = (pCol->colFlags & COLFLAG_PRIMKEY)!=0; autoinc = pTab->iPKey==iCol && (pTab->tabFlags & TF_Autoincrement)!=0; @@ -167997,12 +174699,16 @@ SQLITE_API int sqlite3_test_control(int op, ...){ ** sqlite3_test_control(). */ case SQLITE_TESTCTRL_FAULT_INSTALL: { - /* MSVC is picky about pulling func ptrs from va lists. - ** http://support.microsoft.com/kb/47961 + /* A bug in MSVC prevents it from understanding pointers to functions + ** types in the second argument to va_arg(). Work around the problem + ** using a typedef. + ** http://support.microsoft.com/kb/47961 <-- dead hyperlink + ** Search at http://web.archive.org/ to find the 2015-03-16 archive + ** of the link above to see the original text. ** sqlite3GlobalConfig.xTestCallback = va_arg(ap, int(*)(int)); */ - typedef int(*TESTCALLBACKFUNC_t)(int); - sqlite3GlobalConfig.xTestCallback = va_arg(ap, TESTCALLBACKFUNC_t); + typedef int(*sqlite3FaultFuncType)(int); + sqlite3GlobalConfig.xTestCallback = va_arg(ap, sqlite3FaultFuncType); rc = sqlite3FaultSim(0); break; } @@ -168061,6 +174767,28 @@ SQLITE_API int sqlite3_test_control(int op, ...){ volatile int x = 0; assert( /*side-effects-ok*/ (x = va_arg(ap,int))!=0 ); rc = x; +#if defined(SQLITE_DEBUG) + /* Invoke these debugging routines so that the compiler does not + ** issue "defined but not used" warnings. */ + if( x==9999 ){ + sqlite3ShowExpr(0); + sqlite3ShowExpr(0); + sqlite3ShowExprList(0); + sqlite3ShowIdList(0); + sqlite3ShowSrcList(0); + sqlite3ShowWith(0); + sqlite3ShowUpsert(0); + sqlite3ShowTriggerStep(0); + sqlite3ShowTriggerStepList(0); + sqlite3ShowTrigger(0); + sqlite3ShowTriggerList(0); +#ifndef SQLITE_OMIT_WINDOWFUNC + sqlite3ShowWindow(0); + sqlite3ShowWinFunc(0); +#endif + sqlite3ShowSelect(0); + } +#endif break; } @@ -168129,13 +174857,27 @@ SQLITE_API int sqlite3_test_control(int op, ...){ break; } - /* sqlite3_test_control(SQLITE_TESTCTRL_LOCALTIME_FAULT, int onoff); + /* sqlite3_test_control(SQLITE_TESTCTRL_LOCALTIME_FAULT, onoff, xAlt); ** - ** If parameter onoff is non-zero, subsequent calls to localtime() - ** and its variants fail. If onoff is zero, undo this setting. + ** If parameter onoff is 1, subsequent calls to localtime() fail. + ** If 2, then invoke xAlt() instead of localtime(). If 0, normal + ** processing. + ** + ** xAlt arguments are void pointers, but they really want to be: + ** + ** int xAlt(const time_t*, struct tm*); + ** + ** xAlt should write results in to struct tm object of its 2nd argument + ** and return zero on success, or return non-zero on failure. */ case SQLITE_TESTCTRL_LOCALTIME_FAULT: { sqlite3GlobalConfig.bLocaltimeFault = va_arg(ap, int); + if( sqlite3GlobalConfig.bLocaltimeFault==2 ){ + typedef int(*sqlite3LocaltimeType)(const void*,void*); + sqlite3GlobalConfig.xAltLocaltime = va_arg(ap, sqlite3LocaltimeType); + }else{ + sqlite3GlobalConfig.xAltLocaltime = 0; + } break; } @@ -168240,12 +174982,16 @@ SQLITE_API int sqlite3_test_control(int op, ...){ */ case SQLITE_TESTCTRL_IMPOSTER: { sqlite3 *db = va_arg(ap, sqlite3*); + int iDb; sqlite3_mutex_enter(db->mutex); - db->init.iDb = sqlite3FindDbName(db, va_arg(ap,const char*)); - db->init.busy = db->init.imposterTable = va_arg(ap,int); - db->init.newTnum = va_arg(ap,int); - if( db->init.busy==0 && db->init.newTnum>0 ){ - sqlite3ResetAllSchemasOfConnection(db); + iDb = sqlite3FindDbName(db, va_arg(ap,const char*)); + if( iDb>=0 ){ + db->init.iDb = iDb; + db->init.busy = db->init.imposterTable = va_arg(ap,int); + db->init.newTnum = va_arg(ap,int); + if( db->init.busy==0 && db->init.newTnum>0 ){ + sqlite3ResetAllSchemasOfConnection(db); + } } sqlite3_mutex_leave(db->mutex); break; @@ -168304,8 +175050,8 @@ SQLITE_API int sqlite3_test_control(int op, ...){ ** ** "ptr" is a pointer to a u32. ** - ** op==0 Store the current sqlite3SelectTrace in *ptr - ** op==1 Set sqlite3SelectTrace to the value *ptr + ** op==0 Store the current sqlite3TreeTrace in *ptr + ** op==1 Set sqlite3TreeTrace to the value *ptr ** op==3 Store the current sqlite3WhereTrace in *ptr ** op==3 Set sqlite3WhereTrace to the value *ptr */ @@ -168313,13 +175059,65 @@ SQLITE_API int sqlite3_test_control(int op, ...){ int opTrace = va_arg(ap, int); u32 *ptr = va_arg(ap, u32*); switch( opTrace ){ - case 0: *ptr = sqlite3SelectTrace; break; - case 1: sqlite3SelectTrace = *ptr; break; - case 2: *ptr = sqlite3WhereTrace; break; - case 3: sqlite3WhereTrace = *ptr; break; + case 0: *ptr = sqlite3TreeTrace; break; + case 1: sqlite3TreeTrace = *ptr; break; + case 2: *ptr = sqlite3WhereTrace; break; + case 3: sqlite3WhereTrace = *ptr; break; } break; } + + /* sqlite3_test_control(SQLITE_TESTCTRL_LOGEST, + ** double fIn, // Input value + ** int *pLogEst, // sqlite3LogEstFromDouble(fIn) + ** u64 *pInt, // sqlite3LogEstToInt(*pLogEst) + ** int *pLogEst2 // sqlite3LogEst(*pInt) + ** ); + ** + ** Test access for the LogEst conversion routines. + */ + case SQLITE_TESTCTRL_LOGEST: { + double rIn = va_arg(ap, double); + LogEst rLogEst = sqlite3LogEstFromDouble(rIn); + int *pI1 = va_arg(ap,int*); + u64 *pU64 = va_arg(ap,u64*); + int *pI2 = va_arg(ap,int*); + *pI1 = rLogEst; + *pU64 = sqlite3LogEstToInt(rLogEst); + *pI2 = sqlite3LogEst(*pU64); + break; + } + + +#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_WSD) + /* sqlite3_test_control(SQLITE_TESTCTRL_TUNE, id, *piValue) + ** + ** If "id" is an integer between 1 and SQLITE_NTUNE then set the value + ** of the id-th tuning parameter to *piValue. If "id" is between -1 + ** and -SQLITE_NTUNE, then write the current value of the (-id)-th + ** tuning parameter into *piValue. + ** + ** Tuning parameters are for use during transient development builds, + ** to help find the best values for constants in the query planner. + ** Access tuning parameters using the Tuning(ID) macro. Set the + ** parameters in the CLI using ".testctrl tune ID VALUE". + ** + ** Transient use only. Tuning parameters should not be used in + ** checked-in code. + */ + case SQLITE_TESTCTRL_TUNE: { + int id = va_arg(ap, int); + int *piValue = va_arg(ap, int*); + if( id>0 && id<=SQLITE_NTUNE ){ + Tuning(id) = *piValue; + }else if( id<0 && id>=-SQLITE_NTUNE ){ + *piValue = Tuning(-id); + }else{ + rc = SQLITE_NOTFOUND; + } + break; + } +#endif } va_end(ap); #endif /* SQLITE_UNTESTABLE */ @@ -168427,7 +175225,7 @@ SQLITE_API const char *sqlite3_uri_key(const char *zFilename, int N){ if( zFilename==0 || N<0 ) return 0; zFilename = databaseName(zFilename); zFilename += sqlite3Strlen30(zFilename) + 1; - while( zFilename[0] && (N--)>0 ){ + while( ALWAYS(zFilename) && zFilename[0] && (N--)>0 ){ zFilename += sqlite3Strlen30(zFilename) + 1; zFilename += sqlite3Strlen30(zFilename) + 1; } @@ -168470,12 +175268,14 @@ SQLITE_API sqlite3_int64 sqlite3_uri_int64( ** corruption. */ SQLITE_API const char *sqlite3_filename_database(const char *zFilename){ + if( zFilename==0 ) return 0; return databaseName(zFilename); } SQLITE_API const char *sqlite3_filename_journal(const char *zFilename){ + if( zFilename==0 ) return 0; zFilename = databaseName(zFilename); zFilename += sqlite3Strlen30(zFilename) + 1; - while( zFilename[0] ){ + while( ALWAYS(zFilename) && zFilename[0] ){ zFilename += sqlite3Strlen30(zFilename) + 1; zFilename += sqlite3Strlen30(zFilename) + 1; } @@ -168486,7 +175286,7 @@ SQLITE_API const char *sqlite3_filename_wal(const char *zFilename){ return 0; #else zFilename = sqlite3_filename_journal(zFilename); - zFilename += sqlite3Strlen30(zFilename) + 1; + if( zFilename ) zFilename += sqlite3Strlen30(zFilename) + 1; return zFilename; #endif } @@ -168499,6 +175299,24 @@ SQLITE_PRIVATE Btree *sqlite3DbNameToBtree(sqlite3 *db, const char *zDbName){ return iDb<0 ? 0 : db->aDb[iDb].pBt; } +/* +** Return the name of the N-th database schema. Return NULL if N is out +** of range. +*/ +SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + if( N<0 || N>=db->nDb ){ + return 0; + }else{ + return db->aDb[N].zDbSName; + } +} + /* ** Return the filename of the database associated with a database ** connection. @@ -169762,7 +176580,7 @@ SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem(const Fts3Hash *, const voi ** is used for assert() conditions that are true only if it can be ** guranteed that the database is not corrupt. */ -#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) +#ifdef SQLITE_DEBUG SQLITE_API extern int sqlite3_fts3_may_be_corrupt; # define assert_fts3_nc(x) assert(sqlite3_fts3_may_be_corrupt || (x)) #else @@ -169779,17 +176597,18 @@ SQLITE_API extern int sqlite3_fts3_may_be_corrupt; ** Macros indicating that conditional expressions are always true or ** false. */ -#ifdef SQLITE_COVERAGE_TEST -# define ALWAYS(x) (1) -# define NEVER(X) (0) -#elif defined(SQLITE_DEBUG) -# define ALWAYS(x) sqlite3Fts3Always((x)!=0) -# define NEVER(x) sqlite3Fts3Never((x)!=0) -SQLITE_PRIVATE int sqlite3Fts3Always(int b); -SQLITE_PRIVATE int sqlite3Fts3Never(int b); +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) #else -# define ALWAYS(x) (x) -# define NEVER(x) (x) +# define ALWAYS(X) (X) +# define NEVER(X) (X) #endif /* @@ -170248,6 +177067,7 @@ SQLITE_PRIVATE void sqlite3Fts3ExprFree(Fts3Expr *); SQLITE_PRIVATE int sqlite3Fts3ExprInitTestInterface(sqlite3 *db, Fts3Hash*); SQLITE_PRIVATE int sqlite3Fts3InitTerm(sqlite3 *db); #endif +SQLITE_PRIVATE void *sqlite3Fts3MallocZero(i64 nByte); SQLITE_PRIVATE int sqlite3Fts3OpenTokenizer(sqlite3_tokenizer *, int, const char *, int, sqlite3_tokenizer_cursor ** @@ -170267,7 +177087,7 @@ SQLITE_PRIVATE int sqlite3Fts3MsrOvfl(Fts3Cursor *, Fts3MultiSegReader *, int *) SQLITE_PRIVATE int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr); /* fts3_tokenize_vtab.c */ -SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3*, Fts3Hash *); +SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3*, Fts3Hash *, void(*xDestroy)(void*)); /* fts3_unicode2.c (functions generated by parsing unicode text files) */ #ifndef SQLITE_DISABLE_FTS3_UNICODE @@ -170300,25 +177120,26 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeIsdiacritic(int); SQLITE_EXTENSION_INIT1 #endif +typedef struct Fts3HashWrapper Fts3HashWrapper; +struct Fts3HashWrapper { + Fts3Hash hash; /* Hash table */ + int nRef; /* Number of pointers to this object */ +}; + static int fts3EvalNext(Fts3Cursor *pCsr); static int fts3EvalStart(Fts3Cursor *pCsr); static int fts3TermSegReaderCursor( Fts3Cursor *, const char *, int, int, Fts3MultiSegReader **); -#ifndef SQLITE_AMALGAMATION -# if defined(SQLITE_DEBUG) -SQLITE_PRIVATE int sqlite3Fts3Always(int b) { assert( b ); return b; } -SQLITE_PRIVATE int sqlite3Fts3Never(int b) { assert( !b ); return b; } -# endif -#endif - /* ** This variable is set to false when running tests for which the on disk ** structures should not be corrupt. Otherwise, true. If it is false, extra ** assert() conditions in the fts3 code are activated - conditions that are ** only true if it is guaranteed that the fts3 database is not corrupt. */ +#ifdef SQLITE_DEBUG SQLITE_API int sqlite3_fts3_may_be_corrupt = 1; +#endif /* ** Write a 64-bit variable-length integer to memory starting at p[0]. @@ -171169,7 +177990,7 @@ static int fts3InitVtab( sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */ char **pzErr /* Write any error message here */ ){ - Fts3Hash *pHash = (Fts3Hash *)pAux; + Fts3Hash *pHash = &((Fts3HashWrapper*)pAux)->hash; Fts3Table *p = 0; /* Pointer to allocated vtab */ int rc = SQLITE_OK; /* Return code */ int i; /* Iterator variable */ @@ -171889,7 +178710,7 @@ static int fts3ScanInteriorNode( char *zBuffer = 0; /* Buffer to load terms into */ i64 nAlloc = 0; /* Size of allocated buffer */ int isFirstTerm = 1; /* True when processing first term on page */ - sqlite3_int64 iChild; /* Block id of child node to descend to */ + u64 iChild; /* Block id of child node to descend to */ int nBuffer = 0; /* Total term size */ /* Skip over the 'height' varint that occurs at the start of every @@ -171905,8 +178726,8 @@ static int fts3ScanInteriorNode( ** table, then there are always 20 bytes of zeroed padding following the ** nNode bytes of content (see sqlite3Fts3ReadBlock() for details). */ - zCsr += sqlite3Fts3GetVarint(zCsr, &iChild); - zCsr += sqlite3Fts3GetVarint(zCsr, &iChild); + zCsr += sqlite3Fts3GetVarintU(zCsr, &iChild); + zCsr += sqlite3Fts3GetVarintU(zCsr, &iChild); if( zCsr>zEnd ){ return FTS_CORRUPT_VTAB; } @@ -171959,20 +178780,20 @@ static int fts3ScanInteriorNode( */ cmp = memcmp(zTerm, zBuffer, (nBuffer>nTerm ? nTerm : nBuffer)); if( piFirst && (cmp<0 || (cmp==0 && nBuffer>nTerm)) ){ - *piFirst = iChild; + *piFirst = (i64)iChild; piFirst = 0; } if( piLast && cmp<0 ){ - *piLast = iChild; + *piLast = (i64)iChild; piLast = 0; } iChild++; }; - if( piFirst ) *piFirst = iChild; - if( piLast ) *piLast = iChild; + if( piFirst ) *piFirst = (i64)iChild; + if( piLast ) *piLast = (i64)iChild; finish_scan: sqlite3_free(zBuffer); @@ -173578,14 +180399,20 @@ static int fts3SetHasStat(Fts3Table *p){ */ static int fts3BeginMethod(sqlite3_vtab *pVtab){ Fts3Table *p = (Fts3Table*)pVtab; + int rc; UNUSED_PARAMETER(pVtab); assert( p->pSegments==0 ); assert( p->nPendingData==0 ); assert( p->inTransaction!=1 ); - TESTONLY( p->inTransaction = 1 ); - TESTONLY( p->mxSavepoint = -1; ); p->nLeafAdd = 0; - return fts3SetHasStat(p); + rc = fts3SetHasStat(p); +#ifdef SQLITE_DEBUG + if( rc==SQLITE_OK ){ + p->inTransaction = 1; + p->mxSavepoint = -1; + } +#endif + return rc; } /* @@ -173998,9 +180825,12 @@ static const sqlite3_module fts3Module = { ** allocated for the tokenizer hash table. */ static void hashDestroy(void *p){ - Fts3Hash *pHash = (Fts3Hash *)p; - sqlite3Fts3HashClear(pHash); - sqlite3_free(pHash); + Fts3HashWrapper *pHash = (Fts3HashWrapper *)p; + pHash->nRef--; + if( pHash->nRef<=0 ){ + sqlite3Fts3HashClear(&pHash->hash); + sqlite3_free(pHash); + } } /* @@ -174030,7 +180860,7 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule(sqlite3_tokenizer_module const */ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){ int rc = SQLITE_OK; - Fts3Hash *pHash = 0; + Fts3HashWrapper *pHash = 0; const sqlite3_tokenizer_module *pSimple = 0; const sqlite3_tokenizer_module *pPorter = 0; #ifndef SQLITE_DISABLE_FTS3_UNICODE @@ -174058,23 +180888,24 @@ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){ sqlite3Fts3PorterTokenizerModule(&pPorter); /* Allocate and initialize the hash-table used to store tokenizers. */ - pHash = sqlite3_malloc(sizeof(Fts3Hash)); + pHash = sqlite3_malloc(sizeof(Fts3HashWrapper)); if( !pHash ){ rc = SQLITE_NOMEM; }else{ - sqlite3Fts3HashInit(pHash, FTS3_HASH_STRING, 1); + sqlite3Fts3HashInit(&pHash->hash, FTS3_HASH_STRING, 1); + pHash->nRef = 0; } /* Load the built-in tokenizers into the hash table */ if( rc==SQLITE_OK ){ - if( sqlite3Fts3HashInsert(pHash, "simple", 7, (void *)pSimple) - || sqlite3Fts3HashInsert(pHash, "porter", 7, (void *)pPorter) + if( sqlite3Fts3HashInsert(&pHash->hash, "simple", 7, (void *)pSimple) + || sqlite3Fts3HashInsert(&pHash->hash, "porter", 7, (void *)pPorter) #ifndef SQLITE_DISABLE_FTS3_UNICODE - || sqlite3Fts3HashInsert(pHash, "unicode61", 10, (void *)pUnicode) + || sqlite3Fts3HashInsert(&pHash->hash, "unicode61", 10, (void *)pUnicode) #endif #ifdef SQLITE_ENABLE_ICU - || (pIcu && sqlite3Fts3HashInsert(pHash, "icu", 4, (void *)pIcu)) + || (pIcu && sqlite3Fts3HashInsert(&pHash->hash, "icu", 4, (void *)pIcu)) #endif ){ rc = SQLITE_NOMEM; @@ -174083,7 +180914,7 @@ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){ #ifdef SQLITE_TEST if( rc==SQLITE_OK ){ - rc = sqlite3Fts3ExprInitTestInterface(db, pHash); + rc = sqlite3Fts3ExprInitTestInterface(db, &pHash->hash); } #endif @@ -174092,23 +180923,26 @@ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){ ** module with sqlite. */ if( SQLITE_OK==rc - && SQLITE_OK==(rc = sqlite3Fts3InitHashTable(db, pHash, "fts3_tokenizer")) + && SQLITE_OK==(rc=sqlite3Fts3InitHashTable(db,&pHash->hash,"fts3_tokenizer")) && SQLITE_OK==(rc = sqlite3_overload_function(db, "snippet", -1)) && SQLITE_OK==(rc = sqlite3_overload_function(db, "offsets", 1)) && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", 1)) && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", 2)) && SQLITE_OK==(rc = sqlite3_overload_function(db, "optimize", 1)) ){ + pHash->nRef++; rc = sqlite3_create_module_v2( db, "fts3", &fts3Module, (void *)pHash, hashDestroy ); if( rc==SQLITE_OK ){ + pHash->nRef++; rc = sqlite3_create_module_v2( - db, "fts4", &fts3Module, (void *)pHash, 0 + db, "fts4", &fts3Module, (void *)pHash, hashDestroy ); } if( rc==SQLITE_OK ){ - rc = sqlite3Fts3InitTok(db, (void *)pHash); + pHash->nRef++; + rc = sqlite3Fts3InitTok(db, (void *)pHash, hashDestroy); } return rc; } @@ -174117,7 +180951,7 @@ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){ /* An error has occurred. Delete the hash table and return the error code. */ assert( rc!=SQLITE_OK ); if( pHash ){ - sqlite3Fts3HashClear(pHash); + sqlite3Fts3HashClear(&pHash->hash); sqlite3_free(pHash); } return rc; @@ -174286,8 +181120,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ char *aPoslist = 0; /* Position list for deferred tokens */ int nPoslist = 0; /* Number of bytes in aPoslist */ int iPrev = -1; /* Token number of previous deferred token */ - - assert( pPhrase->doclist.bFreeList==0 ); + char *aFree = (pPhrase->doclist.bFreeList ? pPhrase->doclist.pList : 0); for(iToken=0; iTokennToken; iToken++){ Fts3PhraseToken *pToken = &pPhrase->aToken[iToken]; @@ -174301,6 +181134,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ if( pList==0 ){ sqlite3_free(aPoslist); + sqlite3_free(aFree); pPhrase->doclist.pList = 0; pPhrase->doclist.nList = 0; return SQLITE_OK; @@ -174321,6 +181155,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ nPoslist = (int)(aOut - aPoslist); if( nPoslist==0 ){ sqlite3_free(aPoslist); + sqlite3_free(aFree); pPhrase->doclist.pList = 0; pPhrase->doclist.nList = 0; return SQLITE_OK; @@ -174353,13 +181188,14 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ nDistance = iPrev - nMaxUndeferred; } - aOut = (char *)sqlite3_malloc(nPoslist+8); + aOut = (char *)sqlite3Fts3MallocZero(nPoslist+FTS3_BUFFER_PADDING); if( !aOut ){ sqlite3_free(aPoslist); return SQLITE_NOMEM; } pPhrase->doclist.pList = aOut; + assert( p1 && p2 ); if( fts3PoslistPhraseMerge(&aOut, nDistance, 0, 1, &p1, &p2) ){ pPhrase->doclist.bFreeList = 1; pPhrase->doclist.nList = (int)(aOut - pPhrase->doclist.pList); @@ -174372,6 +181208,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ } } + if( pPhrase->doclist.pList!=aFree ) sqlite3_free(aFree); return SQLITE_OK; } #endif /* SQLITE_DISABLE_FTS4_DEFERRED */ @@ -174464,7 +181301,7 @@ SQLITE_PRIVATE void sqlite3Fts3DoclistPrev( assert( nDoclist>0 ); assert( *pbEof==0 ); - assert( p || *piDocid==0 ); + assert_fts3_nc( p || *piDocid==0 ); assert( !p || (p>aDoclist && p<&aDoclist[nDoclist]) ); if( p==0 ){ @@ -175114,16 +181951,15 @@ static int fts3EvalStart(Fts3Cursor *pCsr){ #ifndef SQLITE_DISABLE_FTS4_DEFERRED if( rc==SQLITE_OK && nToken>1 && pTab->bFts4 ){ Fts3TokenAndCost *aTC; - Fts3Expr **apOr; aTC = (Fts3TokenAndCost *)sqlite3_malloc64( sizeof(Fts3TokenAndCost) * nToken + sizeof(Fts3Expr *) * nOr * 2 ); - apOr = (Fts3Expr **)&aTC[nToken]; if( !aTC ){ rc = SQLITE_NOMEM; }else{ + Fts3Expr **apOr = (Fts3Expr **)&aTC[nToken]; int ii; Fts3TokenAndCost *pTC = aTC; Fts3Expr **ppOr = apOr; @@ -175329,8 +182165,8 @@ static void fts3EvalNextRow( Fts3Expr *pRight = pExpr->pRight; sqlite3_int64 iCmp = DOCID_CMP(pLeft->iDocid, pRight->iDocid); - assert( pLeft->bStart || pLeft->iDocid==pRight->iDocid ); - assert( pRight->bStart || pLeft->iDocid==pRight->iDocid ); + assert_fts3_nc( pLeft->bStart || pLeft->iDocid==pRight->iDocid ); + assert_fts3_nc( pRight->bStart || pLeft->iDocid==pRight->iDocid ); if( pRight->bEof || (pLeft->bEof==0 && iCmp<0) ){ fts3EvalNextRow(pCsr, pLeft, pRc); @@ -175547,11 +182383,10 @@ static int fts3EvalTestExpr( default: { #ifndef SQLITE_DISABLE_FTS4_DEFERRED - if( pCsr->pDeferred - && (pExpr->iDocid==pCsr->iPrevId || pExpr->bDeferred) - ){ + if( pCsr->pDeferred && (pExpr->bDeferred || ( + pExpr->iDocid==pCsr->iPrevId && pExpr->pPhrase->doclist.pList + ))){ Fts3Phrase *pPhrase = pExpr->pPhrase; - assert( pExpr->bDeferred || pPhrase->doclist.bFreeList==0 ); if( pExpr->bDeferred ){ fts3EvalInvalidatePoslist(pPhrase); } @@ -175968,6 +182803,9 @@ SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist( if( bEofSave==0 && pNear->iDocid==iDocid ) break; } assert( rc!=SQLITE_OK || pPhrase->bIncr==0 ); + if( rc==SQLITE_OK && pNear->bEof!=bEofSave ){ + rc = FTS_CORRUPT_VTAB; + } } if( bTreeEof ){ while( rc==SQLITE_OK && !pNear->bEof ){ @@ -176390,6 +183228,7 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){ if( fts3auxGrowStatArray(pCsr, 2) ) return SQLITE_NOMEM; memset(pCsr->aStat, 0, sizeof(struct Fts3auxColstats) * pCsr->nStat); iCol = 0; + rc = SQLITE_OK; while( iaStat[iCol+1].nDoc++; eState = 2; @@ -176441,7 +183284,6 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){ } pCsr->iCol = 0; - rc = SQLITE_OK; }else{ pCsr->isEof = 1; } @@ -176499,6 +183341,7 @@ static int fts3auxFilterMethod( sqlite3Fts3SegReaderFinish(&pCsr->csr); sqlite3_free((void *)pCsr->filter.zTerm); sqlite3_free(pCsr->aStat); + sqlite3_free(pCsr->zStop); memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr); pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY; @@ -176769,7 +183612,7 @@ static int fts3isspace(char c){ ** zero the memory before returning a pointer to it. If unsuccessful, ** return NULL. */ -static void *fts3MallocZero(sqlite3_int64 nByte){ +SQLITE_PRIVATE void *sqlite3Fts3MallocZero(sqlite3_int64 nByte){ void *pRet = sqlite3_malloc64(nByte); if( pRet ) memset(pRet, 0, nByte); return pRet; @@ -176850,7 +183693,7 @@ static int getNextToken( rc = pModule->xNext(pCursor, &zToken, &nToken, &iStart, &iEnd, &iPosition); if( rc==SQLITE_OK ){ nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase) + nToken; - pRet = (Fts3Expr *)fts3MallocZero(nByte); + pRet = (Fts3Expr *)sqlite3Fts3MallocZero(nByte); if( !pRet ){ rc = SQLITE_NOMEM; }else{ @@ -177105,7 +183948,7 @@ static int getNextNode( if( fts3isspace(cNext) || cNext=='"' || cNext=='(' || cNext==')' || cNext==0 ){ - pRet = (Fts3Expr *)fts3MallocZero(sizeof(Fts3Expr)); + pRet = (Fts3Expr *)sqlite3Fts3MallocZero(sizeof(Fts3Expr)); if( !pRet ){ return SQLITE_NOMEM; } @@ -177284,7 +184127,7 @@ static int fts3ExprParse( && p->eType==FTSQUERY_PHRASE && pParse->isNot ){ /* Create an implicit NOT operator. */ - Fts3Expr *pNot = fts3MallocZero(sizeof(Fts3Expr)); + Fts3Expr *pNot = sqlite3Fts3MallocZero(sizeof(Fts3Expr)); if( !pNot ){ sqlite3Fts3ExprFree(p); rc = SQLITE_NOMEM; @@ -177318,7 +184161,7 @@ static int fts3ExprParse( /* Insert an implicit AND operator. */ Fts3Expr *pAnd; assert( pRet && pPrev ); - pAnd = fts3MallocZero(sizeof(Fts3Expr)); + pAnd = sqlite3Fts3MallocZero(sizeof(Fts3Expr)); if( !pAnd ){ sqlite3Fts3ExprFree(p); rc = SQLITE_NOMEM; @@ -180174,7 +187017,7 @@ static int fts3tokRowidMethod( ** Register the fts3tok module with database connection db. Return SQLITE_OK ** if successful or an error code if sqlite3_create_module() fails. */ -SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash){ +SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash, void(*xDestroy)(void*)){ static const sqlite3_module fts3tok_module = { 0, /* iVersion */ fts3tokConnectMethod, /* xCreate */ @@ -180203,7 +187046,9 @@ SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash){ }; int rc; /* Return code */ - rc = sqlite3_create_module(db, "fts3tokenize", &fts3tok_module, (void*)pHash); + rc = sqlite3_create_module_v2( + db, "fts3tokenize", &fts3tok_module, (void*)pHash, xDestroy + ); return rc; } @@ -181548,8 +188393,18 @@ static int fts3SegReaderNext( char *aCopy; PendingList *pList = (PendingList *)fts3HashData(pElem); int nCopy = pList->nData+1; - pReader->zTerm = (char *)fts3HashKey(pElem); - pReader->nTerm = fts3HashKeysize(pElem); + + int nTerm = fts3HashKeysize(pElem); + if( (nTerm+1)>pReader->nTermAlloc ){ + sqlite3_free(pReader->zTerm); + pReader->zTerm = (char*)sqlite3_malloc((nTerm+1)*2); + if( !pReader->zTerm ) return SQLITE_NOMEM; + pReader->nTermAlloc = (nTerm+1)*2; + } + memcpy(pReader->zTerm, fts3HashKey(pElem), nTerm); + pReader->zTerm[nTerm] = '\0'; + pReader->nTerm = nTerm; + aCopy = (char*)sqlite3_malloc(nCopy); if( !aCopy ) return SQLITE_NOMEM; memcpy(aCopy, pList->aData, nCopy); @@ -181802,9 +188657,7 @@ SQLITE_PRIVATE int sqlite3Fts3MsrOvfl( */ SQLITE_PRIVATE void sqlite3Fts3SegReaderFree(Fts3SegReader *pReader){ if( pReader ){ - if( !fts3SegReaderIsPending(pReader) ){ - sqlite3_free(pReader->zTerm); - } + sqlite3_free(pReader->zTerm); if( !fts3SegReaderIsRootOnly(pReader) ){ sqlite3_free(pReader->aNode); } @@ -182020,7 +188873,7 @@ static int fts3SegReaderCmp(Fts3SegReader *pLhs, Fts3SegReader *pRhs){ if( rc==0 ){ rc = pRhs->iIdx - pLhs->iIdx; } - assert( rc!=0 ); + assert_fts3_nc( rc!=0 ); return rc; } @@ -182216,8 +189069,8 @@ static int fts3PrefixCompress( int nNext /* Size of buffer zNext in bytes */ ){ int n; - UNUSED_PARAMETER(nNext); - for(n=0; nterm, nPrefix+nSuffix, &rc); - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK && ALWAYS(p->term.a!=0) ){ memcpy(&p->term.a[nPrefix], &p->aNode[p->iOff], nSuffix); p->term.n = nPrefix+nSuffix; p->iOff += nSuffix; @@ -184104,6 +190957,8 @@ static int fts3IncrmergePush( pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nPrefix); } pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nSuffix); + assert( nPrefix+nSuffix<=nTerm ); + assert( nPrefix>=0 ); memcpy(&pBlk->a[pBlk->n], &zTerm[nPrefix], nSuffix); pBlk->n += nSuffix; @@ -184226,6 +191081,7 @@ static int fts3IncrmergeAppend( pLeaf = &pWriter->aNodeWriter[0]; nPrefix = fts3PrefixCompress(pLeaf->key.a, pLeaf->key.n, zTerm, nTerm); nSuffix = nTerm - nPrefix; + if(nSuffix<=0 ) return FTS_CORRUPT_VTAB; nSpace = sqlite3Fts3VarintLen(nPrefix); nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix; @@ -184390,7 +191246,11 @@ static int fts3TermCmp( int nCmp = MIN(nLhs, nRhs); int res; - res = (nCmp ? memcmp(zLhs, zRhs, nCmp) : 0); + if( nCmp && ALWAYS(zLhs) && ALWAYS(zRhs) ){ + res = memcmp(zLhs, zRhs, nCmp); + }else{ + res = 0; + } if( res==0 ) res = nLhs - nRhs; return res; @@ -185034,7 +191894,7 @@ static int fts3IncrmergeHintLoad(Fts3Table *p, Blob *pHint){ if( aHint ){ blobGrowBuffer(pHint, nHint, &rc); if( rc==SQLITE_OK ){ - memcpy(pHint->a, aHint, nHint); + if( ALWAYS(pHint->a!=0) ) memcpy(pHint->a, aHint, nHint); pHint->n = nHint; } } @@ -186030,6 +192890,10 @@ SQLITE_PRIVATE int sqlite3Fts3Optimize(Fts3Table *p){ /* #include */ /* #include */ +#ifndef SQLITE_AMALGAMATION +typedef sqlite3_int64 i64; +#endif + /* ** Characters that may appear in the second argument to matchinfo(). */ @@ -186080,9 +192944,9 @@ struct SnippetIter { struct SnippetPhrase { int nToken; /* Number of tokens in phrase */ char *pList; /* Pointer to start of phrase position list */ - int iHead; /* Next value in position list */ + i64 iHead; /* Next value in position list */ char *pHead; /* Position list data following iHead */ - int iTail; /* Next value in trailing position list */ + i64 iTail; /* Next value in trailing position list */ char *pTail; /* Position list data following iTail */ }; @@ -186147,9 +193011,8 @@ static MatchinfoBuffer *fts3MIBufferNew(size_t nElem, const char *zMatchinfo){ + sizeof(MatchinfoBuffer); sqlite3_int64 nStr = strlen(zMatchinfo); - pRet = sqlite3_malloc64(nByte + nStr+1); + pRet = sqlite3Fts3MallocZero(nByte + nStr+1); if( pRet ){ - memset(pRet, 0, nByte); pRet->aMatchinfo[0] = (u8*)(&pRet->aMatchinfo[1]) - (u8*)pRet; pRet->aMatchinfo[1+nElem] = pRet->aMatchinfo[0] + sizeof(u32)*((int)nElem+1); @@ -186247,7 +193110,7 @@ SQLITE_PRIVATE void sqlite3Fts3MIBufferFree(MatchinfoBuffer *p){ ** After it returns, *piPos contains the value of the next element of the ** list and *pp is advanced to the following varint. */ -static void fts3GetDeltaPosition(char **pp, int *piPos){ +static void fts3GetDeltaPosition(char **pp, i64 *piPos){ int iVal; *pp += fts3GetVarint32(*pp, &iVal); *piPos += (iVal-2); @@ -186356,10 +193219,10 @@ static int fts3ExprPhraseCount(Fts3Expr *pExpr){ ** arguments so that it points to the first element with a value greater ** than or equal to parameter iNext. */ -static void fts3SnippetAdvance(char **ppIter, int *piIter, int iNext){ +static void fts3SnippetAdvance(char **ppIter, i64 *piIter, int iNext){ char *pIter = *ppIter; if( pIter ){ - int iIter = *piIter; + i64 iIter = *piIter; while( iIteraPhrase[i]; if( pPhrase->pTail ){ char *pCsr = pPhrase->pTail; - int iCsr = pPhrase->iTail; + i64 iCsr = pPhrase->iTail; while( iCsr<(iStart+pIter->nSnippet) && iCsr>=iStart ){ int j; @@ -186488,7 +193351,7 @@ static int fts3SnippetFindPositions(Fts3Expr *pExpr, int iPhrase, void *ctx){ rc = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol, &pCsr); assert( rc==SQLITE_OK || pCsr==0 ); if( pCsr ){ - int iFirst = 0; + i64 iFirst = 0; pPhrase->pList = pCsr; fts3GetDeltaPosition(&pCsr, &iFirst); if( iFirst<0 ){ @@ -186553,11 +193416,10 @@ static int fts3BestSnippet( ** the required space using malloc(). */ nByte = sizeof(SnippetPhrase) * nList; - sIter.aPhrase = (SnippetPhrase *)sqlite3_malloc64(nByte); + sIter.aPhrase = (SnippetPhrase *)sqlite3Fts3MallocZero(nByte); if( !sIter.aPhrase ){ return SQLITE_NOMEM; } - memset(sIter.aPhrase, 0, nByte); /* Initialize the contents of the SnippetIter object. Then iterate through ** the set of phrases in the expression to populate the aPhrase[] array. @@ -187121,10 +193983,12 @@ static int fts3MatchinfoLcsCb( ** position list for the next column. */ static int fts3LcsIteratorAdvance(LcsIterator *pIter){ - char *pRead = pIter->pRead; + char *pRead; sqlite3_int64 iRead; int rc = 0; + if( NEVER(pIter==0) ) return 1; + pRead = pIter->pRead; pRead += sqlite3Fts3GetVarint(pRead, &iRead); if( iRead==0 || iRead==1 ){ pRead = 0; @@ -187158,9 +194022,8 @@ static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){ /* Allocate and populate the array of LcsIterator objects. The array ** contains one element for each matchable phrase in the query. **/ - aIter = sqlite3_malloc64(sizeof(LcsIterator) * pCsr->nPhrase); + aIter = sqlite3Fts3MallocZero(sizeof(LcsIterator) * pCsr->nPhrase); if( !aIter ) return SQLITE_NOMEM; - memset(aIter, 0, sizeof(LcsIterator) * pCsr->nPhrase); (void)fts3ExprIterate(pCsr->pExpr, fts3MatchinfoLcsCb, (void*)aIter); for(i=0; inPhrase; i++){ @@ -187552,8 +194415,8 @@ typedef struct TermOffsetCtx TermOffsetCtx; struct TermOffset { char *pList; /* Position-list */ - int iPos; /* Position just read from pList */ - int iOff; /* Offset of this term from read positions */ + i64 iPos; /* Position just read from pList */ + i64 iOff; /* Offset of this term from read positions */ }; struct TermOffsetCtx { @@ -187572,7 +194435,7 @@ static int fts3ExprTermOffsetInit(Fts3Expr *pExpr, int iPhrase, void *ctx){ int nTerm; /* Number of tokens in phrase */ int iTerm; /* For looping through nTerm phrase terms */ char *pList; /* Pointer to position list for phrase */ - int iPos = 0; /* First position in position-list */ + i64 iPos = 0; /* First position in position-list */ int rc; UNUSED_PARAMETER(iPhrase); @@ -187621,7 +194484,7 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets( if( rc!=SQLITE_OK ) goto offsets_out; /* Allocate the array of TermOffset iterators. */ - sCtx.aTerm = (TermOffset *)sqlite3_malloc64(sizeof(TermOffset)*nToken); + sCtx.aTerm = (TermOffset *)sqlite3Fts3MallocZero(sizeof(TermOffset)*nToken); if( 0==sCtx.aTerm ){ rc = SQLITE_NOMEM; goto offsets_out; @@ -187642,13 +194505,13 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets( const char *zDoc; int nDoc; - /* Initialize the contents of sCtx.aTerm[] for column iCol. There is - ** no way that this operation can fail, so the return code from - ** fts3ExprIterate() can be discarded. + /* Initialize the contents of sCtx.aTerm[] for column iCol. This + ** operation may fail if the database contains corrupt records. */ sCtx.iCol = iCol; sCtx.iTerm = 0; - (void)fts3ExprIterate(pCsr->pExpr, fts3ExprTermOffsetInit, (void*)&sCtx); + rc = fts3ExprIterate(pCsr->pExpr, fts3ExprTermOffsetInit, (void*)&sCtx); + if( rc!=SQLITE_OK ) goto offsets_out; /* Retreive the text stored in column iCol. If an SQL NULL is stored ** in column iCol, jump immediately to the next iteration of the loop. @@ -188547,7 +195410,7 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int eRemoveDiacritic){ #endif /* !defined(SQLITE_DISABLE_FTS3_UNICODE) */ /************** End of fts3_unicode2.c ***************************************/ -/************** Begin file json1.c *******************************************/ +/************** Begin file json.c ********************************************/ /* ** 2015-08-12 ** @@ -188560,10 +195423,10 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int eRemoveDiacritic){ ** ****************************************************************************** ** -** This SQLite extension implements JSON functions. The interface is -** modeled after MySQL JSON functions: +** This SQLite JSON functions. ** -** https://dev.mysql.com/doc/refman/5.7/en/json.html +** This file began as an extension in ext/misc/json1.c in 2015. That +** extension proved so useful that it has now been moved into the core. ** ** For the time being, all JSON is stored as pure text. (We might add ** a JSONB type in the future which stores a binary encoding of JSON in @@ -188571,48 +195434,8 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int eRemoveDiacritic){ ** This implementation parses JSON text at 250 MB/s, so it is hard to see ** how JSONB might improve on that.) */ -#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_JSON1) -#if !defined(SQLITEINT_H) -/* #include "sqlite3ext.h" */ -#endif -SQLITE_EXTENSION_INIT1 -/* #include */ -/* #include */ -/* #include */ -/* #include */ - -/* Mark a function parameter as unused, to suppress nuisance compiler -** warnings. */ -#ifndef UNUSED_PARAM -# define UNUSED_PARAM(X) (void)(X) -#endif - -#ifndef LARGEST_INT64 -# define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32)) -# define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64) -#endif - -#ifndef deliberate_fall_through -# define deliberate_fall_through -#endif - -/* -** Versions of isspace(), isalnum() and isdigit() to which it is safe -** to pass signed char values. -*/ -#ifdef sqlite3Isdigit - /* Use the SQLite core versions if this routine is part of the - ** SQLite amalgamation */ -# define safe_isdigit(x) sqlite3Isdigit(x) -# define safe_isalnum(x) sqlite3Isalnum(x) -# define safe_isxdigit(x) sqlite3Isxdigit(x) -#else - /* Use the standard library for separate compilation */ -#include /* amalgamator: keep */ -# define safe_isdigit(x) isdigit((unsigned char)(x)) -# define safe_isalnum(x) isalnum((unsigned char)(x)) -# define safe_isxdigit(x) isxdigit((unsigned char)(x)) -#endif +#ifndef SQLITE_OMIT_JSON +/* #include "sqliteInt.h" */ /* ** Growing our own isspace() routine this way is twice as fast as @@ -188637,15 +195460,12 @@ static const char jsonIsSpace[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; -#define safe_isspace(x) (jsonIsSpace[(unsigned char)x]) +#define fast_isspace(x) (jsonIsSpace[(unsigned char)x]) -#ifndef SQLITE_AMALGAMATION - /* Unsigned integer types. These are already defined in the sqliteInt.h, - ** but the definitions need to be repeated for separate compilation. */ - typedef sqlite3_uint64 u64; - typedef unsigned int u32; - typedef unsigned short int u16; - typedef unsigned char u8; +#if !defined(SQLITE_DEBUG) && !defined(SQLITE_COVERAGE_TEST) +# define VVA(X) +#else +# define VVA(X) X #endif /* Objects */ @@ -188704,13 +195524,14 @@ static const char * const jsonType[] = { struct JsonNode { u8 eType; /* One of the JSON_ type values */ u8 jnFlags; /* JNODE flags */ + u8 eU; /* Which union element to use */ u32 n; /* Bytes of content, or number of sub-nodes */ union { - const char *zJContent; /* Content for INT, REAL, and STRING */ - u32 iAppend; /* More terms for ARRAY and OBJECT */ - u32 iKey; /* Key for ARRAY objects in json_tree() */ - u32 iReplace; /* Replacement content for JNODE_REPLACE */ - JsonNode *pPatch; /* Node chain of patch for JNODE_PATCH */ + const char *zJContent; /* 1: Content for INT, REAL, and STRING */ + u32 iAppend; /* 2: More terms for ARRAY and OBJECT */ + u32 iKey; /* 3: Key for ARRAY objects in json_tree() */ + u32 iReplace; /* 4: Replacement content for JNODE_REPLACE */ + JsonNode *pPatch; /* 5: Node chain of patch for JNODE_PATCH */ } u; }; @@ -188849,7 +195670,7 @@ static void jsonAppendSeparator(JsonString *p){ */ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ u32 i; - if( (N+p->nUsed+2 >= p->nAlloc) && jsonGrow(p,N+2)!=0 ) return; + if( zIn==0 || ((N+p->nUsed+2 >= p->nAlloc) && jsonGrow(p,N+2)!=0) ) return; p->zBuf[p->nUsed++] = '"'; for(i=0; ijnFlags & (JNODE_REPLACE|JNODE_PATCH) ){ - if( pNode->jnFlags & JNODE_REPLACE ){ + if( (pNode->jnFlags & JNODE_REPLACE)!=0 && ALWAYS(aReplace!=0) ){ + assert( pNode->eU==4 ); jsonAppendValue(pOut, aReplace[pNode->u.iReplace]); return; } + assert( pNode->eU==5 ); pNode = pNode->u.pPatch; } switch( pNode->eType ){ @@ -189011,6 +195835,7 @@ static void jsonRenderNode( } case JSON_STRING: { if( pNode->jnFlags & JNODE_RAW ){ + assert( pNode->eU==1 ); jsonAppendString(pOut, pNode->u.zJContent, pNode->n); break; } @@ -189018,6 +195843,7 @@ static void jsonRenderNode( } case JSON_REAL: case JSON_INT: { + assert( pNode->eU==1 ); jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); break; } @@ -189033,6 +195859,7 @@ static void jsonRenderNode( j += jsonNodeSize(&pNode[j]); } if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; + assert( pNode->eU==2 ); pNode = &pNode[pNode->u.iAppend]; j = 1; } @@ -189053,6 +195880,7 @@ static void jsonRenderNode( j += 1 + jsonNodeSize(&pNode[j+1]); } if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; + assert( pNode->eU==2 ); pNode = &pNode[pNode->u.iAppend]; j = 1; } @@ -189097,10 +195925,10 @@ static u8 jsonHexToInt(int h){ */ static u32 jsonHexToInt4(const char *z){ u32 v; - assert( safe_isxdigit(z[0]) ); - assert( safe_isxdigit(z[1]) ); - assert( safe_isxdigit(z[2]) ); - assert( safe_isxdigit(z[3]) ); + assert( sqlite3Isxdigit(z[0]) ); + assert( sqlite3Isxdigit(z[1]) ); + assert( sqlite3Isxdigit(z[2]) ); + assert( sqlite3Isxdigit(z[3]) ); v = (jsonHexToInt(z[0])<<12) + (jsonHexToInt(z[1])<<8) + (jsonHexToInt(z[2])<<4) @@ -189132,7 +195960,9 @@ static void jsonReturn( } case JSON_INT: { sqlite3_int64 i = 0; - const char *z = pNode->u.zJContent; + const char *z; + assert( pNode->eU==1 ); + z = pNode->u.zJContent; if( z[0]=='-' ){ z++; } while( z[0]>='0' && z[0]<='9' ){ unsigned v = *(z++) - '0'; @@ -189155,14 +195985,17 @@ static void jsonReturn( sqlite3_result_int64(pCtx, i); int_done: break; - int_as_real: i=0; /* no break */ deliberate_fall_through + int_as_real: ; /* no break */ deliberate_fall_through } case JSON_REAL: { double r; #ifdef SQLITE_AMALGAMATION - const char *z = pNode->u.zJContent; + const char *z; + assert( pNode->eU==1 ); + z = pNode->u.zJContent; sqlite3AtoF(z, &r, sqlite3Strlen30(z), SQLITE_UTF8); #else + assert( pNode->eU==1 ); r = strtod(pNode->u.zJContent, 0); #endif sqlite3_result_double(pCtx, r); @@ -189173,6 +196006,7 @@ static void jsonReturn( ** json_insert() and json_replace() and those routines do not ** call jsonReturn() */ if( pNode->jnFlags & JNODE_RAW ){ + assert( pNode->eU==1 ); sqlite3_result_text(pCtx, pNode->u.zJContent, pNode->n, SQLITE_TRANSIENT); }else @@ -189180,15 +196014,18 @@ static void jsonReturn( assert( (pNode->jnFlags & JNODE_RAW)==0 ); if( (pNode->jnFlags & JNODE_ESCAPE)==0 ){ /* JSON formatted without any backslash-escapes */ + assert( pNode->eU==1 ); sqlite3_result_text(pCtx, pNode->u.zJContent+1, pNode->n-2, SQLITE_TRANSIENT); }else{ /* Translate JSON formatted string into raw text */ u32 i; u32 n = pNode->n; - const char *z = pNode->u.zJContent; + const char *z; char *zOut; u32 j; + assert( pNode->eU==1 ); + z = pNode->u.zJContent; zOut = sqlite3_malloc( n+1 ); if( zOut==0 ){ sqlite3_result_error_nomem(pCtx); @@ -189309,12 +196146,13 @@ static int jsonParseAddNode( const char *zContent /* Content */ ){ JsonNode *p; - if( pParse->nNode>=pParse->nAlloc ){ + if( pParse->aNode==0 || pParse->nNode>=pParse->nAlloc ){ return jsonParseAddNodeExpand(pParse, eType, n, zContent); } p = &pParse->aNode[pParse->nNode]; p->eType = (u8)eType; p->jnFlags = 0; + VVA( p->eU = zContent ? 1 : 0 ); p->n = n; p->u.zJContent = zContent; return pParse->nNode++; @@ -189325,7 +196163,7 @@ static int jsonParseAddNode( */ static int jsonIs4Hex(const char *z){ int i; - for(i=0; i<4; i++) if( !safe_isxdigit(z[i]) ) return 0; + for(i=0; i<4; i++) if( !sqlite3Isxdigit(z[i]) ) return 0; return 1; } @@ -189344,13 +196182,13 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ int x; JsonNode *pNode; const char *z = pParse->zJson; - while( safe_isspace(z[i]) ){ i++; } + while( fast_isspace(z[i]) ){ i++; } if( (c = z[i])=='{' ){ /* Parse object */ iThis = jsonParseAddNode(pParse, JSON_OBJECT, 0, 0); if( iThis<0 ) return -1; for(j=i+1;;j++){ - while( safe_isspace(z[j]) ){ j++; } + while( fast_isspace(z[j]) ){ j++; } if( ++pParse->iDepth > JSON_MAX_DEPTH ) return -1; x = jsonParseValue(pParse, j); if( x<0 ){ @@ -189363,14 +196201,14 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ if( pNode->eType!=JSON_STRING ) return -1; pNode->jnFlags |= JNODE_LABEL; j = x; - while( safe_isspace(z[j]) ){ j++; } + while( fast_isspace(z[j]) ){ j++; } if( z[j]!=':' ) return -1; j++; x = jsonParseValue(pParse, j); pParse->iDepth--; if( x<0 ) return -1; j = x; - while( safe_isspace(z[j]) ){ j++; } + while( fast_isspace(z[j]) ){ j++; } c = z[j]; if( c==',' ) continue; if( c!='}' ) return -1; @@ -189382,8 +196220,9 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ /* Parse array */ iThis = jsonParseAddNode(pParse, JSON_ARRAY, 0, 0); if( iThis<0 ) return -1; + memset(&pParse->aNode[iThis].u, 0, sizeof(pParse->aNode[iThis].u)); for(j=i+1;;j++){ - while( safe_isspace(z[j]) ){ j++; } + while( fast_isspace(z[j]) ){ j++; } if( ++pParse->iDepth > JSON_MAX_DEPTH ) return -1; x = jsonParseValue(pParse, j); pParse->iDepth--; @@ -189392,7 +196231,7 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ return -1; } j = x; - while( safe_isspace(z[j]) ){ j++; } + while( fast_isspace(z[j]) ){ j++; } c = z[j]; if( c==',' ) continue; if( c!=']' ) return -1; @@ -189429,17 +196268,17 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ return j+1; }else if( c=='n' && strncmp(z+i,"null",4)==0 - && !safe_isalnum(z[i+4]) ){ + && !sqlite3Isalnum(z[i+4]) ){ jsonParseAddNode(pParse, JSON_NULL, 0, 0); return i+4; }else if( c=='t' && strncmp(z+i,"true",4)==0 - && !safe_isalnum(z[i+4]) ){ + && !sqlite3Isalnum(z[i+4]) ){ jsonParseAddNode(pParse, JSON_TRUE, 0, 0); return i+4; }else if( c=='f' && strncmp(z+i,"false",5)==0 - && !safe_isalnum(z[i+5]) ){ + && !sqlite3Isalnum(z[i+5]) ){ jsonParseAddNode(pParse, JSON_FALSE, 0, 0); return i+5; }else if( c=='-' || (c>='0' && c<='9') ){ @@ -189510,7 +196349,7 @@ static int jsonParse( if( pParse->oom ) i = -1; if( i>0 ){ assert( pParse->iDepth==0 ); - while( safe_isspace(zJson[i]) ) i++; + while( fast_isspace(zJson[i]) ) i++; if( zJson[i] ) i = -1; } if( i<=0 ){ @@ -189646,6 +196485,7 @@ static JsonParse *jsonParseCached( ** a match. */ static int jsonLabelCompare(JsonNode *pNode, const char *zKey, u32 nKey){ + assert( pNode->eU==1 ); if( pNode->jnFlags & JNODE_RAW ){ if( pNode->n!=nKey ) return 0; return strncmp(pNode->u.zJContent, zKey, nKey)==0; @@ -189692,14 +196532,15 @@ static JsonNode *jsonLookupStep( *pzErr = zPath; return 0; } + testcase( nKey==0 ); }else{ zKey = zPath; for(i=0; zPath[i] && zPath[i]!='.' && zPath[i]!='['; i++){} nKey = i; - } - if( nKey==0 ){ - *pzErr = zPath; - return 0; + if( nKey==0 ){ + *pzErr = zPath; + return 0; + } } j = 1; for(;;){ @@ -189711,6 +196552,7 @@ static JsonNode *jsonLookupStep( j += jsonNodeSize(&pRoot[j]); } if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; + assert( pRoot->eU==2 ); iRoot += pRoot->u.iAppend; pRoot = &pParse->aNode[iRoot]; j = 1; @@ -189725,8 +196567,10 @@ static JsonNode *jsonLookupStep( if( pParse->oom ) return 0; if( pNode ){ pRoot = &pParse->aNode[iRoot]; + assert( pRoot->eU==0 ); pRoot->u.iAppend = iStart - iRoot; pRoot->jnFlags |= JNODE_APPEND; + VVA( pRoot->eU = 2 ); pParse->aNode[iLabel].jnFlags |= JNODE_RAW; } return pNode; @@ -189734,7 +196578,7 @@ static JsonNode *jsonLookupStep( }else if( zPath[0]=='[' ){ i = 0; j = 1; - while( safe_isdigit(zPath[j]) ){ + while( sqlite3Isdigit(zPath[j]) ){ i = i*10 + zPath[j] - '0'; j++; } @@ -189749,18 +196593,19 @@ static JsonNode *jsonLookupStep( j += jsonNodeSize(&pBase[j]); } if( (pBase->jnFlags & JNODE_APPEND)==0 ) break; + assert( pBase->eU==2 ); iBase += pBase->u.iAppend; pBase = &pParse->aNode[iBase]; j = 1; } j = 2; - if( zPath[2]=='-' && safe_isdigit(zPath[3]) ){ + if( zPath[2]=='-' && sqlite3Isdigit(zPath[3]) ){ unsigned int x = 0; j = 3; do{ x = x*10 + zPath[j] - '0'; j++; - }while( safe_isdigit(zPath[j]) ); + }while( sqlite3Isdigit(zPath[j]) ); if( x>i ) return 0; i -= x; } @@ -189782,6 +196627,7 @@ static JsonNode *jsonLookupStep( j += jsonNodeSize(&pRoot[j]); } if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; + assert( pRoot->eU==2 ); iRoot += pRoot->u.iAppend; pRoot = &pParse->aNode[iRoot]; j = 1; @@ -189797,8 +196643,10 @@ static JsonNode *jsonLookupStep( if( pParse->oom ) return 0; if( pNode ){ pRoot = &pParse->aNode[iRoot]; + assert( pRoot->eU==0 ); pRoot->u.iAppend = iStart - iRoot; pRoot->jnFlags |= JNODE_APPEND; + VVA( pRoot->eU = 2 ); } return pNode; } @@ -189952,9 +196800,13 @@ static void jsonParseFunc( } jsonPrintf(100, &s,"node %3u: %7s n=%-4d up=%-4d", i, zType, x.aNode[i].n, x.aUp[i]); + assert( x.aNode[i].eU==0 || x.aNode[i].eU==1 ); if( x.aNode[i].u.zJContent!=0 ){ + assert( x.aNode[i].eU==1 ); jsonAppendRaw(&s, " ", 1); jsonAppendRaw(&s, x.aNode[i].u.zJContent, x.aNode[i].n); + }else{ + assert( x.aNode[i].eU==0 ); } jsonAppendRaw(&s, "\n", 1); } @@ -189972,7 +196824,7 @@ static void jsonTest1Func( int argc, sqlite3_value **argv ){ - UNUSED_PARAM(argc); + UNUSED_PARAMETER(argc); sqlite3_result_int(ctx, sqlite3_value_subtype(argv[0])==JSON_SUBTYPE); } #endif /* SQLITE_DEBUG */ @@ -189993,7 +196845,7 @@ static void jsonQuoteFunc( sqlite3_value **argv ){ JsonString jx; - UNUSED_PARAM(argc); + UNUSED_PARAMETER(argc); jsonInit(&jx, ctx); jsonAppendValue(&jx, argv[0]); @@ -190064,13 +196916,34 @@ static void jsonArrayLengthFunc( sqlite3_result_int64(ctx, n); } +/* +** Bit values for the flags passed into jsonExtractFunc() or +** jsonSetFunc() via the user-data value. +*/ +#define JSON_JSON 0x01 /* Result is always JSON */ +#define JSON_SQL 0x02 /* Result is always SQL */ +#define JSON_ABPATH 0x03 /* Allow abbreviated JSON path specs */ +#define JSON_ISSET 0x04 /* json_set(), not json_insert() */ + /* ** json_extract(JSON, PATH, ...) +** "->"(JSON,PATH) +** "->>"(JSON,PATH) ** -** Return the element described by PATH. Return NULL if there is no -** PATH element. If there are multiple PATHs, then return a JSON array -** with the result from each path. Throw an error if the JSON or any PATH -** is malformed. +** Return the element described by PATH. Return NULL if that PATH element +** is not found. +** +** If JSON_JSON is set or if more that one PATH argument is supplied then +** always return a JSON representation of the result. If JSON_SQL is set, +** then always return an SQL representation of the result. If neither flag +** is present and argc==2, then return JSON for objects and arrays and SQL +** for all other values. +** +** When multiple PATH arguments are supplied, the result is a JSON array +** containing the result of each PATH. +** +** Abbreviated JSON path expressions are allows if JSON_ABPATH, for +** compatibility with PG. */ static void jsonExtractFunc( sqlite3_context *ctx, @@ -190080,35 +196953,77 @@ static void jsonExtractFunc( JsonParse *p; /* The parse */ JsonNode *pNode; const char *zPath; + int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); JsonString jx; - int i; if( argc<2 ) return; p = jsonParseCached(ctx, argv, ctx); if( p==0 ) return; - jsonInit(&jx, ctx); - jsonAppendChar(&jx, '['); - for(i=1; inErr ) break; - if( argc>2 ){ + if( argc==2 ){ + /* With a single PATH argument */ + zPath = (const char*)sqlite3_value_text(argv[1]); + if( zPath==0 ) return; + if( flags & JSON_ABPATH ){ + if( zPath[0]!='$' ){ + /* The -> and ->> operators accept abbreviated PATH arguments. This + ** is mostly for compatibility with PostgreSQL, but also for + ** convenience. + ** + ** NUMBER ==> $[NUMBER] // PG compatible + ** LABEL ==> $.LABEL // PG compatible + ** [NUMBER] ==> $[NUMBER] // Not PG. Purely for convenience + */ + jsonInit(&jx, ctx); + if( sqlite3Isdigit(zPath[0]) ){ + jsonAppendRaw(&jx, "$[", 2); + jsonAppendRaw(&jx, zPath, (int)strlen(zPath)); + jsonAppendRaw(&jx, "]", 2); + }else{ + jsonAppendRaw(&jx, "$.", 1 + (zPath[0]!='[')); + jsonAppendRaw(&jx, zPath, (int)strlen(zPath)); + jsonAppendChar(&jx, 0); + } + pNode = jx.bErr ? 0 : jsonLookup(p, jx.zBuf, 0, ctx); + jsonReset(&jx); + }else{ + pNode = jsonLookup(p, zPath, 0, ctx); + } + if( pNode ){ + if( flags & JSON_JSON ){ + jsonReturnJson(pNode, ctx, 0); + }else{ + jsonReturn(pNode, ctx, 0); + sqlite3_result_subtype(ctx, 0); + } + } + }else{ + pNode = jsonLookup(p, zPath, 0, ctx); + if( p->nErr==0 && pNode ) jsonReturn(pNode, ctx, 0); + } + }else{ + /* Two or more PATH arguments results in a JSON array with each + ** element of the array being the value selected by one of the PATHs */ + int i; + jsonInit(&jx, ctx); + jsonAppendChar(&jx, '['); + for(i=1; inErr ) break; jsonAppendSeparator(&jx); if( pNode ){ jsonRenderNode(pNode, &jx, 0); }else{ jsonAppendRaw(&jx, "null", 4); } - }else if( pNode ){ - jsonReturn(pNode, ctx, 0); } + if( i==argc ){ + jsonAppendChar(&jx, ']'); + jsonResult(&jx); + sqlite3_result_subtype(ctx, JSON_SUBTYPE); + } + jsonReset(&jx); } - if( argc>2 && i==argc ){ - jsonAppendChar(&jx, ']'); - jsonResult(&jx); - sqlite3_result_subtype(ctx, JSON_SUBTYPE); - } - jsonReset(&jx); } /* This is the RFC 7396 MergePatch algorithm. @@ -190124,7 +197039,7 @@ static JsonNode *jsonMergePatch( if( pPatch->eType!=JSON_OBJECT ){ return pPatch; } - assert( iTarget>=0 && iTargetnNode ); + assert( iTargetnNode ); pTarget = &pParse->aNode[iTarget]; assert( (pPatch->jnFlags & JNODE_APPEND)==0 ); if( pTarget->eType!=JSON_OBJECT ){ @@ -190137,6 +197052,7 @@ static JsonNode *jsonMergePatch( const char *zKey; assert( pPatch[i].eType==JSON_STRING ); assert( pPatch[i].jnFlags & JNODE_LABEL ); + assert( pPatch[i].eU==1 ); nKey = pPatch[i].n; zKey = pPatch[i].u.zJContent; assert( (pPatch[i].jnFlags & JNODE_RAW)==0 ); @@ -190153,6 +197069,12 @@ static JsonNode *jsonMergePatch( if( pNew==0 ) return 0; pTarget = &pParse->aNode[iTarget]; if( pNew!=&pTarget[j+1] ){ + assert( pTarget[j+1].eU==0 + || pTarget[j+1].eU==1 + || pTarget[j+1].eU==2 ); + testcase( pTarget[j+1].eU==1 ); + testcase( pTarget[j+1].eU==2 ); + VVA( pTarget[j+1].eU = 5 ); pTarget[j+1].u.pPatch = pNew; pTarget[j+1].jnFlags |= JNODE_PATCH; } @@ -190168,9 +197090,14 @@ static JsonNode *jsonMergePatch( if( pParse->oom ) return 0; jsonRemoveAllNulls(pPatch); pTarget = &pParse->aNode[iTarget]; + assert( pParse->aNode[iRoot].eU==0 || pParse->aNode[iRoot].eU==2 ); + testcase( pParse->aNode[iRoot].eU==2 ); pParse->aNode[iRoot].jnFlags |= JNODE_APPEND; + VVA( pParse->aNode[iRoot].eU = 2 ); pParse->aNode[iRoot].u.iAppend = iStart - iRoot; iRoot = iStart; + assert( pParse->aNode[iPatch].eU==0 ); + VVA( pParse->aNode[iPatch].eU = 5 ); pParse->aNode[iPatch].jnFlags |= JNODE_PATCH; pParse->aNode[iPatch].u.pPatch = &pPatch[i+1]; } @@ -190192,7 +197119,7 @@ static void jsonPatchFunc( JsonParse y; /* The patch */ JsonNode *pResult; /* The result of the merge */ - UNUSED_PARAM(argc); + UNUSED_PARAMETER(argc); if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; if( jsonParse(&y, ctx, (const char*)sqlite3_value_text(argv[1])) ){ jsonParseReset(&x); @@ -190312,11 +197239,15 @@ static void jsonReplaceFunc( pNode = jsonLookup(&x, zPath, 0, ctx); if( x.nErr ) goto replace_err; if( pNode ){ + assert( pNode->eU==0 || pNode->eU==1 || pNode->eU==4 ); + testcase( pNode->eU!=0 && pNode->eU!=1 ); pNode->jnFlags |= (u8)JNODE_REPLACE; + VVA( pNode->eU = 4 ); pNode->u.iReplace = i + 1; } } if( x.aNode[0].jnFlags & JNODE_REPLACE ){ + assert( x.aNode[0].eU==4 ); sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]); }else{ jsonReturnJson(x.aNode, ctx, argv); @@ -190325,6 +197256,7 @@ replace_err: jsonParseReset(&x); } + /* ** json_set(JSON, PATH, VALUE, ...) ** @@ -190347,7 +197279,7 @@ static void jsonSetFunc( const char *zPath; u32 i; int bApnd; - int bIsSet = *(int*)sqlite3_user_data(ctx); + int bIsSet = sqlite3_user_data(ctx)!=0; if( argc<1 ) return; if( (argc&1)==0 ) { @@ -190366,11 +197298,15 @@ static void jsonSetFunc( }else if( x.nErr ){ goto jsonSetDone; }else if( pNode && (bApnd || bIsSet) ){ + testcase( pNode->eU!=0 && pNode->eU!=1 ); + assert( pNode->eU!=3 && pNode->eU!=5 ); + VVA( pNode->eU = 4 ); pNode->jnFlags |= (u8)JNODE_REPLACE; pNode->u.iReplace = i + 1; } } if( x.aNode[0].jnFlags & JNODE_REPLACE ){ + assert( x.aNode[0].eU==4 ); sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]); }else{ jsonReturnJson(x.aNode, ctx, argv); @@ -190383,8 +197319,8 @@ jsonSetDone: ** json_type(JSON) ** json_type(JSON, PATH) ** -** Return the top-level "type" of a JSON string. Throw an error if -** either the JSON or PATH inputs are not well-formed. +** Return the top-level "type" of a JSON string. json_type() raises an +** error if either the JSON or PATH inputs are not well-formed. */ static void jsonTypeFunc( sqlite3_context *ctx, @@ -190420,7 +197356,7 @@ static void jsonValidFunc( sqlite3_value **argv ){ JsonParse *p; /* The parse */ - UNUSED_PARAM(argc); + UNUSED_PARAMETER(argc); p = jsonParseCached(ctx, argv, 0); sqlite3_result_int(ctx, p!=0); } @@ -190440,7 +197376,7 @@ static void jsonArrayStep( sqlite3_value **argv ){ JsonString *pStr; - UNUSED_PARAM(argc); + UNUSED_PARAMETER(argc); pStr = (JsonString*)sqlite3_aggregate_context(ctx, sizeof(*pStr)); if( pStr ){ if( pStr->zBuf==0 ){ @@ -190448,8 +197384,8 @@ static void jsonArrayStep( jsonAppendChar(pStr, '['); }else if( pStr->nUsed>1 ){ jsonAppendChar(pStr, ','); - pStr->pCtx = ctx; } + pStr->pCtx = ctx; jsonAppendValue(pStr, argv[0]); } } @@ -190500,8 +197436,8 @@ static void jsonGroupInverse( char *z; char c; JsonString *pStr; - UNUSED_PARAM(argc); - UNUSED_PARAM(argv); + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(argv); pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); #ifdef NEVER /* pStr is always non-NULL since jsonArrayStep() or jsonObjectStep() will @@ -190509,11 +197445,7 @@ static void jsonGroupInverse( if( NEVER(!pStr) ) return; #endif z = pStr->zBuf; - for(i=1; (c = z[i])!=',' || inStr || nNest; i++){ - if( i>=pStr->nUsed ){ - pStr->nUsed = 1; - return; - } + for(i=1; inUsed && ((c = z[i])!=',' || inStr || nNest); i++){ if( c=='"' ){ inStr = !inStr; }else if( c=='\\' ){ @@ -190523,8 +197455,13 @@ static void jsonGroupInverse( if( c=='}' || c==']' ) nNest--; } } - pStr->nUsed -= i; - memmove(&z[1], &z[i+1], (size_t)pStr->nUsed-1); + if( inUsed ){ + pStr->nUsed -= i; + memmove(&z[1], &z[i+1], (size_t)pStr->nUsed-1); + z[pStr->nUsed] = 0; + }else{ + pStr->nUsed = 1; + } } #else # define jsonGroupInverse 0 @@ -190544,7 +197481,7 @@ static void jsonObjectStep( JsonString *pStr; const char *z; u32 n; - UNUSED_PARAM(argc); + UNUSED_PARAMETER(argc); pStr = (JsonString*)sqlite3_aggregate_context(ctx, sizeof(*pStr)); if( pStr ){ if( pStr->zBuf==0 ){ @@ -190552,8 +197489,8 @@ static void jsonObjectStep( jsonAppendChar(pStr, '{'); }else if( pStr->nUsed>1 ){ jsonAppendChar(pStr, ','); - pStr->pCtx = ctx; } + pStr->pCtx = ctx; z = (const char*)sqlite3_value_text(argv[0]); n = (u32)sqlite3_value_bytes(argv[0]); jsonAppendString(pStr, z, n); @@ -190635,10 +197572,10 @@ static int jsonEachConnect( #define JEACH_JSON 8 #define JEACH_ROOT 9 - UNUSED_PARAM(pzErr); - UNUSED_PARAM(argv); - UNUSED_PARAM(argc); - UNUSED_PARAM(pAux); + UNUSED_PARAMETER(pzErr); + UNUSED_PARAMETER(argv); + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(pAux); rc = sqlite3_declare_vtab(db, "CREATE TABLE x(key,value,type,atom,id,parent,fullkey,path," "json HIDDEN,root HIDDEN)"); @@ -190661,7 +197598,7 @@ static int jsonEachDisconnect(sqlite3_vtab *pVtab){ static int jsonEachOpenEach(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ JsonEachCursor *pCur; - UNUSED_PARAM(p); + UNUSED_PARAMETER(p); pCur = sqlite3_malloc( sizeof(*pCur) ); if( pCur==0 ) return SQLITE_NOMEM; memset(pCur, 0, sizeof(*pCur)); @@ -190720,6 +197657,9 @@ static int jsonEachNext(sqlite3_vtab_cursor *cur){ JsonNode *pUp = &p->sParse.aNode[iUp]; p->eType = pUp->eType; if( pUp->eType==JSON_ARRAY ){ + assert( pUp->eU==0 || pUp->eU==3 ); + testcase( pUp->eU==3 ); + VVA( pUp->eU = 3 ); if( iUp==p->i-1 ){ pUp->u.iKey = 0; }else{ @@ -190748,6 +197688,33 @@ static int jsonEachNext(sqlite3_vtab_cursor *cur){ return SQLITE_OK; } +/* Append an object label to the JSON Path being constructed +** in pStr. +*/ +static void jsonAppendObjectPathElement( + JsonString *pStr, + JsonNode *pNode +){ + int jj, nn; + const char *z; + assert( pNode->eType==JSON_STRING ); + assert( pNode->jnFlags & JNODE_LABEL ); + assert( pNode->eU==1 ); + z = pNode->u.zJContent; + nn = pNode->n; + assert( nn>=2 ); + assert( z[0]=='"' ); + assert( z[nn-1]=='"' ); + if( nn>2 && sqlite3Isalpha(z[1]) ){ + for(jj=2; jjsParse.aNode[i]; pUp = &p->sParse.aNode[iUp]; if( pUp->eType==JSON_ARRAY ){ + assert( pUp->eU==3 || (pUp->eU==0 && pUp->u.iKey==0) ); + testcase( pUp->eU==0 ); jsonPrintf(30, pStr, "[%d]", pUp->u.iKey); }else{ assert( pUp->eType==JSON_OBJECT ); if( (pNode->jnFlags & JNODE_LABEL)==0 ) pNode--; - assert( pNode->eType==JSON_STRING ); - assert( pNode->jnFlags & JNODE_LABEL ); - jsonPrintf(pNode->n+1, pStr, ".%.*s", pNode->n-2, pNode->u.zJContent+1); + jsonAppendObjectPathElement(pStr, pNode); } } @@ -190793,6 +197760,7 @@ static int jsonEachColumn( u32 iKey; if( p->bRecursive ){ if( p->iRowid==0 ) break; + assert( p->sParse.aNode[p->sParse.aUp[p->i]].eU==3 ); iKey = p->sParse.aNode[p->sParse.aUp[p->i]].u.iKey; }else{ iKey = p->iRowid; @@ -190842,7 +197810,7 @@ static int jsonEachColumn( if( p->eType==JSON_ARRAY ){ jsonPrintf(30, &x, "[%d]", p->iRowid); }else if( p->eType==JSON_OBJECT ){ - jsonPrintf(pThis->n, &x, ".%.*s", pThis->n-2, pThis->u.zJContent+1); + jsonAppendObjectPathElement(&x, pThis); } } jsonResult(&x); @@ -190900,7 +197868,7 @@ static int jsonEachBestIndex( /* This implementation assumes that JSON and ROOT are the last two ** columns in the table */ assert( JEACH_ROOT == JEACH_JSON+1 ); - UNUSED_PARAM(tab); + UNUSED_PARAMETER(tab); aIdx[0] = aIdx[1] = -1; pConstraint = pIdxInfo->aConstraint; for(i=0; inConstraint; i++, pConstraint++){ @@ -190909,6 +197877,7 @@ static int jsonEachBestIndex( if( pConstraint->iColumn < JEACH_JSON ) continue; iCol = pConstraint->iColumn - JEACH_JSON; assert( iCol==0 || iCol==1 ); + testcase( iCol==0 ); iMask = 1 << iCol; if( pConstraint->usable==0 ){ unusableMask |= iMask; @@ -190955,8 +197924,8 @@ static int jsonEachFilter( const char *zRoot = 0; sqlite3_int64 n; - UNUSED_PARAM(idxStr); - UNUSED_PARAM(argc); + UNUSED_PARAMETER(idxStr); + UNUSED_PARAMETER(argc); jsonEachCursorReset(p); if( idxNum==0 ) return SQLITE_OK; z = (const char*)sqlite3_value_text(argv[0]); @@ -191006,6 +197975,8 @@ static int jsonEachFilter( p->iBegin = p->i = (int)(pNode - p->sParse.aNode); p->eType = pNode->eType; if( p->eType>=JSON_ARRAY ){ + assert( pNode->eU==0 ); + VVA( pNode->eU = 3 ); pNode->u.iKey = 0; p->iEnd = p->i + pNode->n + 1; if( p->bRecursive ){ @@ -191079,108 +198050,68 @@ static sqlite3_module jsonTreeModule = { 0 /* xShadowName */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ +#endif /* !defined(SQLITE_OMIT_JSON) */ -/**************************************************************************** -** The following routines are the only publically visible identifiers in this -** file. Call the following routines in order to register the various SQL -** functions and the virtual table implemented by this file. -****************************************************************************/ - -SQLITE_PRIVATE int sqlite3Json1Init(sqlite3 *db){ - int rc = SQLITE_OK; - unsigned int i; - static const struct { - const char *zName; - int nArg; - int flag; - void (*xFunc)(sqlite3_context*,int,sqlite3_value**); - } aFunc[] = { - { "json", 1, 0, jsonRemoveFunc }, - { "json_array", -1, 0, jsonArrayFunc }, - { "json_array_length", 1, 0, jsonArrayLengthFunc }, - { "json_array_length", 2, 0, jsonArrayLengthFunc }, - { "json_extract", -1, 0, jsonExtractFunc }, - { "json_insert", -1, 0, jsonSetFunc }, - { "json_object", -1, 0, jsonObjectFunc }, - { "json_patch", 2, 0, jsonPatchFunc }, - { "json_quote", 1, 0, jsonQuoteFunc }, - { "json_remove", -1, 0, jsonRemoveFunc }, - { "json_replace", -1, 0, jsonReplaceFunc }, - { "json_set", -1, 1, jsonSetFunc }, - { "json_type", 1, 0, jsonTypeFunc }, - { "json_type", 2, 0, jsonTypeFunc }, - { "json_valid", 1, 0, jsonValidFunc }, - +/* +** Register JSON functions. +*/ +SQLITE_PRIVATE void sqlite3RegisterJsonFunctions(void){ +#ifndef SQLITE_OMIT_JSON + static FuncDef aJsonFunc[] = { + JFUNCTION(json, 1, 0, jsonRemoveFunc), + JFUNCTION(json_array, -1, 0, jsonArrayFunc), + JFUNCTION(json_array_length, 1, 0, jsonArrayLengthFunc), + JFUNCTION(json_array_length, 2, 0, jsonArrayLengthFunc), + JFUNCTION(json_extract, -1, 0, jsonExtractFunc), + JFUNCTION(->, 2, JSON_JSON, jsonExtractFunc), + JFUNCTION(->>, 2, JSON_SQL, jsonExtractFunc), + JFUNCTION(json_insert, -1, 0, jsonSetFunc), + JFUNCTION(json_object, -1, 0, jsonObjectFunc), + JFUNCTION(json_patch, 2, 0, jsonPatchFunc), + JFUNCTION(json_quote, 1, 0, jsonQuoteFunc), + JFUNCTION(json_remove, -1, 0, jsonRemoveFunc), + JFUNCTION(json_replace, -1, 0, jsonReplaceFunc), + JFUNCTION(json_set, -1, JSON_ISSET, jsonSetFunc), + JFUNCTION(json_type, 1, 0, jsonTypeFunc), + JFUNCTION(json_type, 2, 0, jsonTypeFunc), + JFUNCTION(json_valid, 1, 0, jsonValidFunc), #if SQLITE_DEBUG - /* DEBUG and TESTING functions */ - { "json_parse", 1, 0, jsonParseFunc }, - { "json_test1", 1, 0, jsonTest1Func }, + JFUNCTION(json_parse, 1, 0, jsonParseFunc), + JFUNCTION(json_test1, 1, 0, jsonTest1Func), #endif + WAGGREGATE(json_group_array, 1, 0, 0, + jsonArrayStep, jsonArrayFinal, jsonArrayValue, jsonGroupInverse, + SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS), + WAGGREGATE(json_group_object, 2, 0, 0, + jsonObjectStep, jsonObjectFinal, jsonObjectValue, jsonGroupInverse, + SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS) }; + sqlite3InsertBuiltinFuncs(aJsonFunc, ArraySize(aJsonFunc)); +#endif +} + +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) +/* +** Register the JSON table-valued functions +*/ +SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3 *db){ + int rc = SQLITE_OK; static const struct { - const char *zName; - int nArg; - void (*xStep)(sqlite3_context*,int,sqlite3_value**); - void (*xFinal)(sqlite3_context*); - void (*xValue)(sqlite3_context*); - } aAgg[] = { - { "json_group_array", 1, - jsonArrayStep, jsonArrayFinal, jsonArrayValue }, - { "json_group_object", 2, - jsonObjectStep, jsonObjectFinal, jsonObjectValue }, - }; -#ifndef SQLITE_OMIT_VIRTUALTABLE - static const struct { - const char *zName; - sqlite3_module *pModule; + const char *zName; + sqlite3_module *pModule; } aMod[] = { { "json_each", &jsonEachModule }, { "json_tree", &jsonTreeModule }, }; -#endif - static const int enc = - SQLITE_UTF8 | - SQLITE_DETERMINISTIC | - SQLITE_INNOCUOUS; - for(i=0; i */ /* #include */ @@ -191319,7 +198267,9 @@ struct Rtree { u8 nBytesPerCell; /* Bytes consumed per cell */ u8 inWrTrans; /* True if inside write transaction */ u8 nAux; /* # of auxiliary columns in %_rowid */ +#ifdef SQLITE_ENABLE_GEOPOLY u8 nAuxNotNull; /* Number of initial not-null aux columns */ +#endif #ifdef SQLITE_DEBUG u8 bCorrupt; /* Shadow table corruption detected */ #endif @@ -191601,7 +198551,12 @@ struct RtreeMatchArg { ** it is not, make it a no-op. */ #ifndef SQLITE_AMALGAMATION -# define testcase(X) +# if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG) + unsigned int sqlite3RtreeTestcase = 0; +# define testcase(X) if( X ){ sqlite3RtreeTestcase += __LINE__; } +# else +# define testcase(X) +# endif #endif /* @@ -191850,18 +198805,6 @@ static void nodeBlobReset(Rtree *pRtree){ } } -/* -** Check to see if pNode is the same as pParent or any of the parents -** of pParent. -*/ -static int nodeInParentChain(const RtreeNode *pNode, const RtreeNode *pParent){ - do{ - if( pNode==pParent ) return 1; - pParent = pParent->pParent; - }while( pParent ); - return 0; -} - /* ** Obtain a reference to an r-tree node. */ @@ -191878,14 +198821,7 @@ static int nodeAcquire( ** increase its reference count and return it. */ if( (pNode = nodeHashLookup(pRtree, iNode))!=0 ){ - if( pParent && !pNode->pParent ){ - if( nodeInParentChain(pNode, pParent) ){ - RTREE_IS_CORRUPT(pRtree); - return SQLITE_CORRUPT_VTAB; - } - pParent->nRef++; - pNode->pParent = pParent; - }else if( pParent && pNode->pParent && pParent!=pNode->pParent ){ + if( pParent && pParent!=pNode->pParent ){ RTREE_IS_CORRUPT(pRtree); return SQLITE_CORRUPT_VTAB; } @@ -191943,7 +198879,7 @@ static int nodeAcquire( ** are the leaves, and so on. If the depth as specified on the root node ** is greater than RTREE_MAX_DEPTH, the r-tree structure must be corrupt. */ - if( pNode && rc==SQLITE_OK && iNode==1 ){ + if( rc==SQLITE_OK && pNode && iNode==1 ){ pRtree->iDepth = readInt16(pNode->zData); if( pRtree->iDepth>RTREE_MAX_DEPTH ){ rc = SQLITE_CORRUPT_VTAB; @@ -192466,20 +199402,29 @@ static void rtreeNonleafConstraint( switch( p->op ){ case RTREE_TRUE: return; /* Always satisfied */ case RTREE_FALSE: break; /* Never satisfied */ - case RTREE_LE: - case RTREE_LT: case RTREE_EQ: + RTREE_DECODE_COORD(eInt, pCellData, val); + /* val now holds the lower bound of the coordinate pair */ + if( p->u.rValue>=val ){ + pCellData += 4; + RTREE_DECODE_COORD(eInt, pCellData, val); + /* val now holds the upper bound of the coordinate pair */ + if( p->u.rValue<=val ) return; + } + break; + case RTREE_LE: + case RTREE_LT: RTREE_DECODE_COORD(eInt, pCellData, val); /* val now holds the lower bound of the coordinate pair */ if( p->u.rValue>=val ) return; - if( p->op!=RTREE_EQ ) break; /* RTREE_LE and RTREE_LT end here */ - /* Fall through for the RTREE_EQ case */ + break; - default: /* RTREE_GT or RTREE_GE, or fallthrough of RTREE_EQ */ + default: pCellData += 4; RTREE_DECODE_COORD(eInt, pCellData, val); /* val now holds the upper bound of the coordinate pair */ if( p->u.rValue<=val ) return; + break; } *peWithin = NOT_WITHIN; } @@ -192549,11 +199494,12 @@ static int nodeRowidIndex( */ static int nodeParentIndex(Rtree *pRtree, RtreeNode *pNode, int *piIndex){ RtreeNode *pParent = pNode->pParent; - if( pParent ){ + if( ALWAYS(pParent) ){ return nodeRowidIndex(pRtree, pParent, pNode->iNode, piIndex); + }else{ + *piIndex = -1; + return SQLITE_OK; } - *piIndex = -1; - return SQLITE_OK; } /* @@ -192676,7 +199622,8 @@ static RtreeSearchPoint *rtreeSearchPointNew( pNew = rtreeEnqueue(pCur, rScore, iLevel); if( pNew==0 ) return 0; ii = (int)(pNew - pCur->aPoint) + 1; - if( iiaNode[ii]==0 ); pCur->aNode[ii] = pCur->aNode[0]; }else{ @@ -192737,7 +199684,7 @@ static void rtreeSearchPointPop(RtreeCursor *p){ if( p->bPoint ){ p->anQueue[p->sPoint.iLevel]--; p->bPoint = 0; - }else if( p->nPoint ){ + }else if( ALWAYS(p->nPoint) ){ p->anQueue[p->aPoint[0].iLevel]--; n = --p->nPoint; p->aPoint[0] = p->aPoint[n]; @@ -192878,7 +199825,7 @@ static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){ RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr); int rc = SQLITE_OK; RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc); - if( rc==SQLITE_OK && p ){ + if( rc==SQLITE_OK && ALWAYS(p) ){ *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell); } return rc; @@ -192896,7 +199843,7 @@ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc); if( rc ) return rc; - if( p==0 ) return SQLITE_OK; + if( NEVER(p==0) ) return SQLITE_OK; if( i==0 ){ sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell)); }else if( i<=pRtree->nDim2 ){ @@ -193095,8 +200042,11 @@ static int rtreeFilter( } if( rc==SQLITE_OK ){ RtreeSearchPoint *pNew; + assert( pCsr->bPoint==0 ); /* Due to the resetCursor() call above */ pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, (u8)(pRtree->iDepth+1)); - if( pNew==0 ) return SQLITE_NOMEM; + if( NEVER(pNew==0) ){ /* Because pCsr->bPoint was FALSE */ + return SQLITE_NOMEM; + } pNew->id = 1; pNew->iCell = 0; pNew->eWithin = PARTLY_WITHIN; @@ -193173,7 +200123,7 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii]; if( bMatch==0 && p->usable - && p->iColumn==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ + && p->iColumn<=0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ ){ /* We have an equality constraint on the rowid. Use strategy 1. */ int jj; @@ -193379,7 +200329,7 @@ static int ChooseLeaf( int nCell = NCELL(pNode); RtreeCell cell; - RtreeNode *pChild; + RtreeNode *pChild = 0; RtreeCell *aCell = 0; @@ -193426,12 +200376,19 @@ static int AdjustTree( ){ RtreeNode *p = pNode; int cnt = 0; + int rc; while( p->pParent ){ RtreeNode *pParent = p->pParent; RtreeCell cell; int iCell; - if( (++cnt)>1000 || nodeParentIndex(pRtree, p, &iCell) ){ + cnt++; + if( NEVER(cnt>100) ){ + RTREE_IS_CORRUPT(pRtree); + return SQLITE_CORRUPT_VTAB; + } + rc = nodeParentIndex(pRtree, p, &iCell); + if( NEVER(rc!=SQLITE_OK) ){ RTREE_IS_CORRUPT(pRtree); return SQLITE_CORRUPT_VTAB; } @@ -193720,12 +200677,17 @@ static int updateMapping( xSetMapping = ((iHeight==0)?rowidWrite:parentWrite); if( iHeight>0 ){ RtreeNode *pChild = nodeHashLookup(pRtree, iRowid); + RtreeNode *p; + for(p=pNode; p; p=p->pParent){ + if( p==pChild ) return SQLITE_CORRUPT_VTAB; + } if( pChild ){ nodeRelease(pRtree, pChild->pParent); nodeReference(pNode); pChild->pParent = pNode; } } + if( NEVER(pNode==0) ) return SQLITE_ERROR; return xSetMapping(pRtree, iRowid, pNode->iNode); } @@ -193815,11 +200777,12 @@ static int SplitNode( RtreeNode *pParent = pLeft->pParent; int iCell; rc = nodeParentIndex(pRtree, pLeft, &iCell); - if( rc==SQLITE_OK ){ + if( ALWAYS(rc==SQLITE_OK) ){ nodeOverwriteCell(pRtree, pParent, &leftbbox, iCell); rc = AdjustTree(pRtree, pParent, &leftbbox); + assert( rc==SQLITE_OK ); } - if( rc!=SQLITE_OK ){ + if( NEVER(rc!=SQLITE_OK) ){ goto splitnode_out; } } @@ -193894,7 +200857,7 @@ static int fixLeafParent(Rtree *pRtree, RtreeNode *pLeaf){ */ iNode = sqlite3_column_int64(pRtree->pReadParent, 0); for(pTest=pLeaf; pTest && pTest->iNode!=iNode; pTest=pTest->pParent); - if( !pTest ){ + if( pTest==0 ){ rc2 = nodeAcquire(pRtree, iNode, 0, &pChild->pParent); } } @@ -193925,6 +200888,7 @@ static int removeNode(Rtree *pRtree, RtreeNode *pNode, int iHeight){ pParent = pNode->pParent; pNode->pParent = 0; rc = deleteCell(pRtree, pParent, iCell, iHeight+1); + testcase( rc!=SQLITE_OK ); } rc2 = nodeRelease(pRtree, pParent); if( rc==SQLITE_OK ){ @@ -194147,7 +201111,7 @@ static int rtreeInsertCell( } }else{ rc = AdjustTree(pRtree, pNode, pCell); - if( rc==SQLITE_OK ){ + if( ALWAYS(rc==SQLITE_OK) ){ if( iHeight==0 ){ rc = rowidWrite(pRtree, pCell->iRowid, pNode->iNode); }else{ @@ -194253,7 +201217,7 @@ static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){ int rc2; RtreeNode *pChild = 0; i64 iChild = nodeGetRowid(pRtree, pRoot, 0); - rc = nodeAcquire(pRtree, iChild, pRoot, &pChild); + rc = nodeAcquire(pRtree, iChild, pRoot, &pChild); /* tag-20210916a */ if( rc==SQLITE_OK ){ rc = removeNode(pRtree, pChild, pRtree->iDepth-1); } @@ -194588,7 +201552,7 @@ static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){ char *zSql; sqlite3_stmt *p; int rc; - i64 nRow = 0; + i64 nRow = RTREE_MIN_ROWEST; rc = sqlite3_table_column_metadata( db, pRtree->zDb, "sqlite_stat1",0,0,0,0,0,0 @@ -194605,20 +201569,10 @@ static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){ if( rc==SQLITE_OK ){ if( sqlite3_step(p)==SQLITE_ROW ) nRow = sqlite3_column_int64(p, 0); rc = sqlite3_finalize(p); - }else if( rc!=SQLITE_NOMEM ){ - rc = SQLITE_OK; - } - - if( rc==SQLITE_OK ){ - if( nRow==0 ){ - pRtree->nRowEst = RTREE_DEFAULT_ROWEST; - }else{ - pRtree->nRowEst = MAX(nRow, RTREE_MIN_ROWEST); - } } sqlite3_free(zSql); } - + pRtree->nRowEst = MAX(nRow, RTREE_MIN_ROWEST); return rc; } @@ -194768,9 +201722,12 @@ static int rtreeSqlInit( sqlite3_str_appendf(p, "UPDATE \"%w\".\"%w_rowid\"SET ", zDb, zPrefix); for(ii=0; iinAux; ii++){ if( ii ) sqlite3_str_append(p, ",", 1); +#ifdef SQLITE_ENABLE_GEOPOLY if( iinAuxNotNull ){ sqlite3_str_appendf(p,"a%d=coalesce(?%d,a%d)",ii,ii+2,ii); - }else{ + }else +#endif + { sqlite3_str_appendf(p,"a%d=?%d",ii,ii+2); } } @@ -195035,6 +201992,7 @@ static void rtreenode(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){ tree.nDim2 = tree.nDim*2; tree.nBytesPerCell = 8 + 8 * tree.nDim; node.zData = (u8 *)sqlite3_value_blob(apArg[1]); + if( node.zData==0 ) return; nData = sqlite3_value_bytes(apArg[1]); if( nData<4 ) return; if( nDataz[0]) ) p->z++; + while( fast_isspace(p->z[0]) ) p->z++; return p->z[0]; } @@ -195859,11 +202821,16 @@ static GeoPoly *geopolyFuncParam( ){ GeoPoly *p = 0; int nByte; + testcase( pCtx==0 ); if( sqlite3_value_type(pVal)==SQLITE_BLOB && (nByte = sqlite3_value_bytes(pVal))>=(4+6*sizeof(GeoCoord)) ){ const unsigned char *a = sqlite3_value_blob(pVal); int nVertex; + if( a==0 ){ + if( pCtx ) sqlite3_result_error_nomem(pCtx); + return 0; + } nVertex = (a[1]<<16) + (a[2]<<8) + a[3]; if( (a[0]==0 || a[0]==1) && (nVertex*2*sizeof(GeoCoord) + 4)==(unsigned int)nByte @@ -196237,7 +203204,7 @@ static GeoPoly *geopolyBBox( aCoord[2].f = mnY; aCoord[3].f = mxY; } - }else{ + }else if( aCoord ){ memset(aCoord, 0, sizeof(RtreeCoord)*4); } return pOut; @@ -196688,11 +203655,11 @@ static int geopolyOverlap(GeoPoly *p1, GeoPoly *p2){ }else{ /* Remove a segment */ if( pActive==pThisEvent->pSeg ){ - pActive = pActive->pNext; + pActive = ALWAYS(pActive) ? pActive->pNext : 0; }else{ for(pSeg=pActive; pSeg; pSeg=pSeg->pNext){ if( pSeg->pNext==pThisEvent->pSeg ){ - pSeg->pNext = pSeg->pNext->pNext; + pSeg->pNext = ALWAYS(pSeg->pNext) ? pSeg->pNext->pNext : 0; break; } } @@ -196936,6 +203903,7 @@ static int geopolyFilter( RtreeCoord bbox[4]; RtreeConstraint *p; assert( argc==1 ); + assert( argv[0]!=0 ); geopolyBBox(0, argv[0], bbox, &rc); if( rc ){ goto geopoly_filter_end; @@ -197163,6 +204131,7 @@ static int geopolyUpdate( || !sqlite3_value_nochange(aData[2]) /* UPDATE _shape */ || oldRowid!=newRowid) /* Rowid change */ ){ + assert( aData[2]!=0 ); geopolyBBox(0, aData[2], cell.aCoord, &rc); if( rc ){ if( rc==SQLITE_ERROR ){ @@ -197245,7 +204214,7 @@ static int geopolyUpdate( sqlite3_free(p); nChange = 1; } - for(jj=1; jjnAux; jj++){ + for(jj=1; jjxGeom = 0; pGeomCtx->xQueryFunc = xQueryFunc; pGeomCtx->xDestructor = xDestructor; @@ -199087,6 +206059,13 @@ SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName); # define SWAP(TYPE,A,B) {TYPE t=A; A=B; B=t;} #endif +/* +** Name of the URI option that causes RBU to take an exclusive lock as +** part of the incremental checkpoint operation. +*/ +#define RBU_EXCLUSIVE_CHECKPOINT "rbu_exclusive_checkpoint" + + /* ** The rbu_state table is used to save the state of a partially applied ** update so that it can be resumed later. The table consists of integer @@ -200171,7 +207150,9 @@ static void rbuTableType( assert( p->rc==SQLITE_OK ); p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[0], &p->zErrmsg, sqlite3_mprintf( - "SELECT (sql LIKE 'create virtual%%'), rootpage" + "SELECT " + " (sql COLLATE nocase BETWEEN 'CREATE VIRTUAL' AND 'CREATE VIRTUAM')," + " rootpage" " FROM sqlite_schema" " WHERE name=%Q", zTab )); @@ -200531,7 +207512,7 @@ static char *rbuVacuumTableStart( ** the caller has to use an OFFSET clause to extract only the required ** rows from the sourct table, just as it does for an RBU update operation. */ -char *rbuVacuumIndexStart( +static char *rbuVacuumIndexStart( sqlite3rbu *p, /* RBU handle */ RbuObjIter *pIter /* RBU iterator object */ ){ @@ -200597,7 +207578,9 @@ char *rbuVacuumIndexStart( zSep = ""; for(iCol=0; iColnCol; iCol++){ const char *zQuoted = (const char*)sqlite3_column_text(pSel, iCol); - if( zQuoted[0]=='N' ){ + if( zQuoted==0 ){ + p->rc = SQLITE_NOMEM; + }else if( zQuoted[0]=='N' ){ bFailed = 1; break; } @@ -201702,7 +208685,7 @@ static RbuState *rbuLoadState(sqlite3rbu *p){ break; case RBU_STATE_OALSZ: - pRet->iOalSz = (u32)sqlite3_column_int64(pStmt, 1); + pRet->iOalSz = sqlite3_column_int64(pStmt, 1); break; case RBU_STATE_PHASEONESTEP: @@ -201729,13 +208712,19 @@ static RbuState *rbuLoadState(sqlite3rbu *p){ /* ** Open the database handle and attach the RBU database as "rbu". If an ** error occurs, leave an error code and message in the RBU handle. +** +** If argument dbMain is not NULL, then it is a database handle already +** open on the target database. Use this handle instead of opening a new +** one. */ -static void rbuOpenDatabase(sqlite3rbu *p, int *pbRetry){ +static void rbuOpenDatabase(sqlite3rbu *p, sqlite3 *dbMain, int *pbRetry){ assert( p->rc || (p->dbMain==0 && p->dbRbu==0) ); assert( p->rc || rbuIsVacuum(p) || p->zTarget!=0 ); + assert( dbMain==0 || rbuIsVacuum(p)==0 ); /* Open the RBU database */ p->dbRbu = rbuOpenDbhandle(p, p->zRbu, 1); + p->dbMain = dbMain; if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){ sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p); @@ -202101,15 +209090,31 @@ static void rbuCheckpointFrame(sqlite3rbu *p, RbuFrame *pFrame){ /* -** Take an EXCLUSIVE lock on the database file. +** Take an EXCLUSIVE lock on the database file. Return SQLITE_OK if +** successful, or an SQLite error code otherwise. */ -static void rbuLockDatabase(sqlite3rbu *p){ - sqlite3_file *pReal = p->pTargetFd->pReal; - assert( p->rc==SQLITE_OK ); - p->rc = pReal->pMethods->xLock(pReal, SQLITE_LOCK_SHARED); - if( p->rc==SQLITE_OK ){ - p->rc = pReal->pMethods->xLock(pReal, SQLITE_LOCK_EXCLUSIVE); +static int rbuLockDatabase(sqlite3 *db){ + int rc = SQLITE_OK; + sqlite3_file *fd = 0; + sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd); + + if( fd->pMethods ){ + rc = fd->pMethods->xLock(fd, SQLITE_LOCK_SHARED); + if( rc==SQLITE_OK ){ + rc = fd->pMethods->xLock(fd, SQLITE_LOCK_EXCLUSIVE); + } } + return rc; +} + +/* +** Return true if the database handle passed as the only argument +** was opened with the rbu_exclusive_checkpoint=1 URI parameter +** specified. Or false otherwise. +*/ +static int rbuExclusiveCheckpoint(sqlite3 *db){ + const char *zUri = sqlite3_db_filename(db, 0); + return sqlite3_uri_boolean(zUri, RBU_EXCLUSIVE_CHECKPOINT, 0); } #if defined(_WIN32_WCE) @@ -202167,18 +209172,24 @@ static void rbuMoveOalFile(sqlite3rbu *p){ ** In order to ensure that there are no database readers, an EXCLUSIVE ** lock is obtained here before the *-oal is moved to *-wal. */ - rbuLockDatabase(p); + sqlite3 *dbMain = 0; + rbuFileSuffix3(zBase, zWal); + rbuFileSuffix3(zBase, zOal); + + /* Re-open the databases. */ + rbuObjIterFinalize(&p->objiter); + sqlite3_close(p->dbRbu); + sqlite3_close(p->dbMain); + p->dbMain = 0; + p->dbRbu = 0; + + dbMain = rbuOpenDbhandle(p, p->zTarget, 1); + if( dbMain ){ + assert( p->rc==SQLITE_OK ); + p->rc = rbuLockDatabase(dbMain); + } + if( p->rc==SQLITE_OK ){ - rbuFileSuffix3(zBase, zWal); - rbuFileSuffix3(zBase, zOal); - - /* Re-open the databases. */ - rbuObjIterFinalize(&p->objiter); - sqlite3_close(p->dbRbu); - sqlite3_close(p->dbMain); - p->dbMain = 0; - p->dbRbu = 0; - #if defined(_WIN32_WCE) { LPWSTR zWideOal; @@ -202205,11 +209216,19 @@ static void rbuMoveOalFile(sqlite3rbu *p){ #else p->rc = rename(zOal, zWal) ? SQLITE_IOERR : SQLITE_OK; #endif + } - if( p->rc==SQLITE_OK ){ - rbuOpenDatabase(p, 0); - rbuSetupCheckpoint(p, 0); - } + if( p->rc!=SQLITE_OK + || rbuIsVacuum(p) + || rbuExclusiveCheckpoint(dbMain)==0 + ){ + sqlite3_close(dbMain); + dbMain = 0; + } + + if( p->rc==SQLITE_OK ){ + rbuOpenDatabase(p, dbMain, 0); + rbuSetupCheckpoint(p, 0); } } @@ -202960,9 +209979,9 @@ static sqlite3rbu *openRbuHandle( ** If this is the case, it will have been checkpointed and deleted ** when the handle was closed and a second attempt to open the ** database may succeed. */ - rbuOpenDatabase(p, &bRetry); + rbuOpenDatabase(p, 0, &bRetry); if( bRetry ){ - rbuOpenDatabase(p, 0); + rbuOpenDatabase(p, 0, 0); } } @@ -203057,6 +210076,14 @@ static sqlite3rbu *openRbuHandle( }else if( p->eStage==RBU_STAGE_MOVE ){ /* no-op */ }else if( p->eStage==RBU_STAGE_CKPT ){ + if( !rbuIsVacuum(p) && rbuExclusiveCheckpoint(p->dbMain) ){ + /* If the rbu_exclusive_checkpoint=1 URI parameter was specified + ** and an incremental checkpoint is being resumed, attempt an + ** exclusive lock on the db file. If this fails, so be it. */ + p->eStage = RBU_STAGE_DONE; + rbuLockDatabase(p->dbMain); + p->eStage = RBU_STAGE_CKPT; + } rbuSetupCheckpoint(p, pState); }else if( p->eStage==RBU_STAGE_DONE ){ p->rc = SQLITE_DONE; @@ -203094,7 +210121,6 @@ SQLITE_API sqlite3rbu *sqlite3rbu_open( const char *zState ){ if( zTarget==0 || zRbu==0 ){ return rbuMisuseError(); } - /* TODO: Check that zTarget and zRbu are non-NULL */ return openRbuHandle(zTarget, zRbu, zState); } @@ -203969,28 +210995,14 @@ static int rbuVfsOpen( rbu_file *pDb = rbuFindMaindb(pRbuVfs, zName, 0); if( pDb ){ if( pDb->pRbu && pDb->pRbu->eStage==RBU_STAGE_OAL ){ - /* This call is to open a *-wal file. Intead, open the *-oal. This - ** code ensures that the string passed to xOpen() is terminated by a - ** pair of '\0' bytes in case the VFS attempts to extract a URI - ** parameter from it. */ - const char *zBase = zName; - size_t nCopy; - char *zCopy; + /* This call is to open a *-wal file. Intead, open the *-oal. */ + size_t nOpen; if( rbuIsVacuum(pDb->pRbu) ){ - zBase = sqlite3_db_filename(pDb->pRbu->dbRbu, "main"); - zBase = sqlite3_filename_wal(zBase); - } - nCopy = strlen(zBase); - zCopy = sqlite3_malloc64(nCopy+2); - if( zCopy ){ - memcpy(zCopy, zBase, nCopy); - zCopy[nCopy-3] = 'o'; - zCopy[nCopy] = '\0'; - zCopy[nCopy+1] = '\0'; - zOpen = (const char*)(pFd->zDel = zCopy); - }else{ - rc = SQLITE_NOMEM; + zOpen = sqlite3_db_filename(pDb->pRbu->dbRbu, "main"); + zOpen = sqlite3_filename_wal(zOpen); } + nOpen = strlen(zOpen); + ((char*)zOpen)[nOpen-3] = 'o'; pFd->pRbu = pDb->pRbu; } pDb->pWalFd = pFd; @@ -204311,6 +211323,15 @@ SQLITE_API sqlite3_int64 sqlite3rbu_temp_size(sqlite3rbu *pRbu){ #if (defined(SQLITE_ENABLE_DBSTAT_VTAB) || defined(SQLITE_TEST)) \ && !defined(SQLITE_OMIT_VIRTUALTABLE) +/* +** The pager and btree modules arrange objects in memory so that there are +** always approximately 200 bytes of addressable memory following each page +** buffer. This way small buffer overreads caused by corrupt database pages +** do not cause undefined behaviour. This module pads each page buffer +** by the following number of bytes for the same purpose. +*/ +#define DBSTAT_PAGE_PADDING_BYTES 256 + /* ** Page paths: ** @@ -204378,9 +211399,8 @@ struct StatCell { /* Size information for a single btree page */ struct StatPage { u32 iPgno; /* Page number */ - DbPage *pPg; /* Page content */ + u8 *aPg; /* Page buffer from sqlite3_malloc() */ int iCell; /* Current cell */ - char *zPath; /* Path to this page */ /* Variables populated by statDecodePage(): */ @@ -204592,18 +211612,25 @@ static void statClearCells(StatPage *p){ } static void statClearPage(StatPage *p){ + u8 *aPg = p->aPg; statClearCells(p); - sqlite3PagerUnref(p->pPg); sqlite3_free(p->zPath); memset(p, 0, sizeof(StatPage)); + p->aPg = aPg; } static void statResetCsr(StatCursor *pCsr){ int i; - sqlite3_reset(pCsr->pStmt); + /* In some circumstances, specifically if an OOM has occurred, the call + ** to sqlite3_reset() may cause the pager to be reset (emptied). It is + ** important that statClearPage() is called to free any page refs before + ** this happens. dbsqlfuzz 9ed3e4e3816219d3509d711636c38542bf3f40b1. */ for(i=0; iaPage); i++){ statClearPage(&pCsr->aPage[i]); + sqlite3_free(pCsr->aPage[i].aPg); + pCsr->aPage[i].aPg = 0; } + sqlite3_reset(pCsr->pStmt); pCsr->iPage = 0; sqlite3_free(pCsr->zPath); pCsr->zPath = 0; @@ -204668,7 +211695,7 @@ static int statDecodePage(Btree *pBt, StatPage *p){ int isLeaf; int szPage; - u8 *aData = sqlite3PagerGetData(p->pPg); + u8 *aData = p->aPg; u8 *aHdr = &aData[p->iPgno==1 ? 100 : 0]; p->flags = aHdr[0]; @@ -204739,7 +211766,7 @@ static int statDecodePage(Btree *pBt, StatPage *p){ if( nPayload>(u32)nLocal ){ int j; int nOvfl = ((nPayload - nLocal) + nUsable-4 - 1) / (nUsable - 4); - if( iOff+nLocal>nUsable || nPayload>0x7fffffff ){ + if( iOff+nLocal+4>nUsable || nPayload>0x7fffffff ){ goto statPageIsCorrupt; } pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4); @@ -204798,6 +211825,38 @@ static void statSizeAndOffset(StatCursor *pCsr){ } } +/* +** Load a copy of the page data for page iPg into the buffer belonging +** to page object pPg. Allocate the buffer if necessary. Return SQLITE_OK +** if successful, or an SQLite error code otherwise. +*/ +static int statGetPage( + Btree *pBt, /* Load page from this b-tree */ + u32 iPg, /* Page number to load */ + StatPage *pPg /* Load page into this object */ +){ + int pgsz = sqlite3BtreeGetPageSize(pBt); + DbPage *pDbPage = 0; + int rc; + + if( pPg->aPg==0 ){ + pPg->aPg = (u8*)sqlite3_malloc(pgsz + DBSTAT_PAGE_PADDING_BYTES); + if( pPg->aPg==0 ){ + return SQLITE_NOMEM_BKPT; + } + memset(&pPg->aPg[pgsz], 0, DBSTAT_PAGE_PADDING_BYTES); + } + + rc = sqlite3PagerGet(sqlite3BtreePager(pBt), iPg, &pDbPage, 0); + if( rc==SQLITE_OK ){ + const u8 *a = sqlite3PagerGetData(pDbPage); + memcpy(pPg->aPg, a, pgsz); + sqlite3PagerUnref(pDbPage); + } + + return rc; +} + /* ** Move a DBSTAT cursor to the next entry. Normally, the next ** entry will be the next page, but in aggregated mode (pCsr->isAgg!=0), @@ -204816,7 +211875,7 @@ static int statNext(sqlite3_vtab_cursor *pCursor){ pCsr->zPath = 0; statNextRestart: - if( pCsr->aPage[0].pPg==0 ){ + if( pCsr->iPage<0 ){ /* Start measuring space on the next btree */ statResetCounts(pCsr); rc = sqlite3_step(pCsr->pStmt); @@ -204828,7 +211887,7 @@ statNextRestart: pCsr->isEof = 1; return sqlite3_reset(pCsr->pStmt); } - rc = sqlite3PagerGet(pPager, iRoot, &pCsr->aPage[0].pPg, 0); + rc = statGetPage(pBt, iRoot, &pCsr->aPage[0]); pCsr->aPage[0].iPgno = iRoot; pCsr->aPage[0].iCell = 0; if( !pCsr->isAgg ){ @@ -204879,9 +211938,8 @@ statNextRestart: if( !p->iRightChildPg || p->iCell>p->nCell ){ statClearPage(p); - if( pCsr->iPage>0 ){ - pCsr->iPage--; - }else if( pCsr->isAgg ){ + pCsr->iPage--; + if( pCsr->isAgg && pCsr->iPage<0 ){ /* label-statNext-done: When computing aggregate space usage over ** an entire btree, this is the exit point from this function */ return SQLITE_OK; @@ -204900,7 +211958,7 @@ statNextRestart: }else{ p[1].iPgno = p->aCell[p->iCell].iChildPg; } - rc = sqlite3PagerGet(pPager, p[1].iPgno, &p[1].pPg, 0); + rc = statGetPage(pBt, p[1].iPgno, &p[1]); pCsr->nPage++; p[1].iCell = 0; if( !pCsr->isAgg ){ @@ -205030,6 +212088,7 @@ static int statFilter( } if( rc==SQLITE_OK ){ + pCsr->iPage = -1; rc = statNext(pCursor); } return rc; @@ -205298,6 +212357,7 @@ static int dbpageBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ ){ pIdxInfo->orderByConsumed = 1; } + sqlite3VtabUsesAllSchemas(pIdxInfo); return SQLITE_OK; } @@ -205475,7 +212535,7 @@ static int dbpageUpdate( goto update_fail; } pBt = pTab->db->aDb[iDb].pBt; - if( pgno<1 || pBt==0 || pgno>(int)sqlite3BtreeLastPage(pBt) ){ + if( pgno<1 || pBt==0 || pgno>sqlite3BtreeLastPage(pBt) ){ zErr = "bad page number"; goto update_fail; } @@ -205603,6 +212663,7 @@ struct SessionHook { struct sqlite3_session { sqlite3 *db; /* Database handle session is attached to */ char *zDb; /* Name of database session is attached to */ + int bEnableSize; /* True if changeset_size() enabled */ int bEnable; /* True if currently recording */ int bIndirect; /* True if all changes are indirect */ int bAutoAttach; /* True to auto-attach tables */ @@ -205610,6 +212671,7 @@ struct sqlite3_session { void *pFilterCtx; /* First argument to pass to xTableFilter */ int (*xTableFilter)(void *pCtx, const char *zTab); i64 nMalloc; /* Number of bytes of data allocated */ + i64 nMaxChangesetSize; sqlite3_value *pZeroBlob; /* Value containing X'' */ sqlite3_session *pNext; /* Next session object on same db. */ SessionTable *pTable; /* List of attached tables */ @@ -205852,8 +212914,9 @@ struct SessionTable { ** this structure stored in a SessionTable.aChange[] hash table. */ struct SessionChange { - int op; /* One of UPDATE, DELETE, INSERT */ - int bIndirect; /* True if this change is "indirect" */ + u8 op; /* One of UPDATE, DELETE, INSERT */ + u8 bIndirect; /* True if this change is "indirect" */ + int nMaxSize; /* Max size of eventual changeset record */ int nRecord; /* Number of bytes in buffer aRecord[] */ u8 *aRecord; /* Buffer containing old.* record */ SessionChange *pNext; /* For hash-table collisions */ @@ -205978,7 +213041,7 @@ static int sessionSerializeValue( if( aBuf ){ sessionVarintPut(&aBuf[1], n); - if( n ) memcpy(&aBuf[nVarint + 1], z, n); + if( n>0 ) memcpy(&aBuf[nVarint + 1], z, n); } nByte = 1 + nVarint + n; @@ -206583,16 +213646,32 @@ static int sessionTableInfo( }else if( rc==SQLITE_ERROR ){ zPragma = sqlite3_mprintf(""); }else{ + *pazCol = 0; + *pabPK = 0; + *pnCol = 0; + if( pzTab ) *pzTab = 0; return rc; } }else{ zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis); } - if( !zPragma ) return SQLITE_NOMEM; + if( !zPragma ){ + *pazCol = 0; + *pabPK = 0; + *pnCol = 0; + if( pzTab ) *pzTab = 0; + return SQLITE_NOMEM; + } rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0); sqlite3_free(zPragma); - if( rc!=SQLITE_OK ) return rc; + if( rc!=SQLITE_OK ){ + *pazCol = 0; + *pabPK = 0; + *pnCol = 0; + if( pzTab ) *pzTab = 0; + return rc; + } nByte = nThis + 1; while( SQLITE_ROW==sqlite3_step(pStmt) ){ @@ -206682,6 +213761,12 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){ if( 0==sqlite3_stricmp("sqlite_stat1", pTab->zName) ){ pTab->bStat1 = 1; } + + if( pSession->bEnableSize ){ + pSession->nMaxChangesetSize += ( + 1 + sessionVarintLen(pTab->nCol) + pTab->nCol + strlen(pTab->zName)+1 + ); + } } } return (pSession->rc || pTab->abPK==0); @@ -206727,6 +213812,103 @@ static int sessionStat1Depth(void *pCtx){ return p->hook.xDepth(p->hook.pCtx); } +static int sessionUpdateMaxSize( + int op, + sqlite3_session *pSession, /* Session object pTab is attached to */ + SessionTable *pTab, /* Table that change applies to */ + SessionChange *pC /* Update pC->nMaxSize */ +){ + i64 nNew = 2; + if( pC->op==SQLITE_INSERT ){ + if( op!=SQLITE_DELETE ){ + int ii; + for(ii=0; iinCol; ii++){ + sqlite3_value *p = 0; + pSession->hook.xNew(pSession->hook.pCtx, ii, &p); + sessionSerializeValue(0, p, &nNew); + } + } + }else if( op==SQLITE_DELETE ){ + nNew += pC->nRecord; + if( sqlite3_preupdate_blobwrite(pSession->db)>=0 ){ + nNew += pC->nRecord; + } + }else{ + int ii; + u8 *pCsr = pC->aRecord; + for(ii=0; iinCol; ii++){ + int bChanged = 1; + int nOld = 0; + int eType; + sqlite3_value *p = 0; + pSession->hook.xNew(pSession->hook.pCtx, ii, &p); + if( p==0 ){ + return SQLITE_NOMEM; + } + + eType = *pCsr++; + switch( eType ){ + case SQLITE_NULL: + bChanged = sqlite3_value_type(p)!=SQLITE_NULL; + break; + + case SQLITE_FLOAT: + case SQLITE_INTEGER: { + if( eType==sqlite3_value_type(p) ){ + sqlite3_int64 iVal = sessionGetI64(pCsr); + if( eType==SQLITE_INTEGER ){ + bChanged = (iVal!=sqlite3_value_int64(p)); + }else{ + double dVal; + memcpy(&dVal, &iVal, 8); + bChanged = (dVal!=sqlite3_value_double(p)); + } + } + nOld = 8; + pCsr += 8; + break; + } + + default: { + int nByte; + nOld = sessionVarintGet(pCsr, &nByte); + pCsr += nOld; + nOld += nByte; + assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); + if( eType==sqlite3_value_type(p) + && nByte==sqlite3_value_bytes(p) + && (nByte==0 || 0==memcmp(pCsr, sqlite3_value_blob(p), nByte)) + ){ + bChanged = 0; + } + pCsr += nByte; + break; + } + } + + if( bChanged && pTab->abPK[ii] ){ + nNew = pC->nRecord + 2; + break; + } + + if( bChanged ){ + nNew += 1 + nOld; + sessionSerializeValue(0, p, &nNew); + }else if( pTab->abPK[ii] ){ + nNew += 2 + nOld; + }else{ + nNew += 2; + } + } + } + + if( nNew>pC->nMaxSize ){ + int nIncr = nNew - pC->nMaxSize; + pC->nMaxSize = nNew; + pSession->nMaxChangesetSize += nIncr; + } + return SQLITE_OK; +} /* ** This function is only called from with a pre-update-hook reporting a @@ -206800,7 +213982,6 @@ static void sessionPreupdateOneChange( /* Create a new change object containing all the old values (if ** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK ** values (if this is an INSERT). */ - SessionChange *pChange; /* New change object */ sqlite3_int64 nByte; /* Number of bytes to allocate */ int i; /* Used to iterate through columns */ @@ -206826,13 +214007,13 @@ static void sessionPreupdateOneChange( } /* Allocate the change object */ - pChange = (SessionChange *)sessionMalloc64(pSession, nByte); - if( !pChange ){ + pC = (SessionChange *)sessionMalloc64(pSession, nByte); + if( !pC ){ rc = SQLITE_NOMEM; goto error_out; }else{ - memset(pChange, 0, sizeof(SessionChange)); - pChange->aRecord = (u8 *)&pChange[1]; + memset(pC, 0, sizeof(SessionChange)); + pC->aRecord = (u8 *)&pC[1]; } /* Populate the change object. None of the preupdate_old(), @@ -206847,17 +214028,17 @@ static void sessionPreupdateOneChange( }else if( pTab->abPK[i] ){ pSession->hook.xNew(pSession->hook.pCtx, i, &p); } - sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte); + sessionSerializeValue(&pC->aRecord[nByte], p, &nByte); } /* Add the change to the hash-table */ if( pSession->bIndirect || pSession->hook.xDepth(pSession->hook.pCtx) ){ - pChange->bIndirect = 1; + pC->bIndirect = 1; } - pChange->nRecord = nByte; - pChange->op = op; - pChange->pNext = pTab->apChange[iHash]; - pTab->apChange[iHash] = pChange; + pC->nRecord = nByte; + pC->op = op; + pC->pNext = pTab->apChange[iHash]; + pTab->apChange[iHash] = pC; }else if( pC->bIndirect ){ /* If the existing change is considered "indirect", but this current @@ -206868,8 +214049,14 @@ static void sessionPreupdateOneChange( pC->bIndirect = 0; } } + + assert( rc==SQLITE_OK ); + if( pSession->bEnableSize ){ + rc = sessionUpdateMaxSize(op, pSession, pTab, pC); + } } + /* If an error has occurred, mark the session object as failed. */ error_out: if( pTab->bStat1 ){ @@ -206902,7 +214089,11 @@ static int sessionFindTable( ){ rc = sqlite3session_attach(pSession, zName); if( rc==SQLITE_OK ){ - for(pRet=pSession->pTable; pRet->pNext; pRet=pRet->pNext); + pRet = pSession->pTable; + while( ALWAYS(pRet) && pRet->pNext ){ + pRet = pRet->pNext; + } + assert( pRet!=0 ); assert( 0==sqlite3_strnicmp(pRet->zName, zName, nName+1) ); } } @@ -207424,13 +214615,29 @@ SQLITE_API int sqlite3session_attach( ** If successful, return zero. Otherwise, if an OOM condition is encountered, ** set *pRc to SQLITE_NOMEM and return non-zero. */ -static int sessionBufferGrow(SessionBuffer *p, size_t nByte, int *pRc){ - if( *pRc==SQLITE_OK && (size_t)(p->nAlloc-p->nBuf)nBuf + nByte; + if( *pRc==SQLITE_OK && nReq>p->nAlloc ){ u8 *aNew; i64 nNew = p->nAlloc ? p->nAlloc : 128; + do { nNew = nNew*2; - }while( (size_t)(nNew-p->nBuf)SESSION_MAX_BUFFER_SZ ){ + nNew = SESSION_MAX_BUFFER_SZ; + if( nNewaBuf, nNew); if( 0==aNew ){ @@ -207659,6 +214866,7 @@ static int sessionAppendUpdate( int i; /* Used to iterate through columns */ u8 *pCsr = p->aRecord; /* Used to iterate through old.* values */ + assert( abPK!=0 ); sessionAppendByte(pBuf, SQLITE_UPDATE, &rc); sessionAppendByte(pBuf, p->bIndirect, &rc); for(i=0; ipTable; rc==SQLITE_OK && pTab; pTab=pTab->pNext){ if( pTab->nEntry ){ const char *zName = pTab->zName; - int nCol; /* Number of columns in table */ - u8 *abPK; /* Primary key array */ + int nCol = 0; /* Number of columns in table */ + u8 *abPK = 0; /* Primary key array */ const char **azCol = 0; /* Table columns */ int i; /* Used to iterate through hash buckets */ sqlite3_stmt *pSel = 0; /* SELECT statement to query table pTab */ @@ -208021,6 +215231,7 @@ static int sessionGenerateChangeset( sessionAppendCol(&buf, pSel, iCol, &rc); } }else{ + assert( abPK!=0 ); /* Because sessionSelectStmt() returned ok */ rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, abPK); } }else if( p->op!=SQLITE_INSERT ){ @@ -208081,7 +215292,14 @@ SQLITE_API int sqlite3session_changeset( int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ void **ppChangeset /* OUT: Buffer containing changeset */ ){ - return sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset, ppChangeset); + int rc; + + if( pnChangeset==0 || ppChangeset==0 ) return SQLITE_MISUSE; + rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset,ppChangeset); + assert( rc || pnChangeset==0 + || pSession->bEnableSize==0 || *pnChangeset<=pSession->nMaxChangesetSize + ); + return rc; } /* @@ -208092,6 +215310,7 @@ SQLITE_API int sqlite3session_changeset_strm( int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ){ + if( xOutput==0 ) return SQLITE_MISUSE; return sessionGenerateChangeset(pSession, 0, xOutput, pOut, 0, 0); } @@ -208103,6 +215322,7 @@ SQLITE_API int sqlite3session_patchset_strm( int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ){ + if( xOutput==0 ) return SQLITE_MISUSE; return sessionGenerateChangeset(pSession, 1, xOutput, pOut, 0, 0); } @@ -208118,6 +215338,7 @@ SQLITE_API int sqlite3session_patchset( int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */ void **ppPatchset /* OUT: Buffer containing changeset */ ){ + if( pnPatchset==0 || ppPatchset==0 ) return SQLITE_MISUSE; return sessionGenerateChangeset(pSession, 1, 0, 0, pnPatchset, ppPatchset); } @@ -208173,6 +215394,39 @@ SQLITE_API sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession){ return pSession->nMalloc; } +/* +** Configure the session object passed as the first argument. +*/ +SQLITE_API int sqlite3session_object_config(sqlite3_session *pSession, int op, void *pArg){ + int rc = SQLITE_OK; + switch( op ){ + case SQLITE_SESSION_OBJCONFIG_SIZE: { + int iArg = *(int*)pArg; + if( iArg>=0 ){ + if( pSession->pTable ){ + rc = SQLITE_MISUSE; + }else{ + pSession->bEnableSize = (iArg!=0); + } + } + *(int*)pArg = pSession->bEnableSize; + break; + } + + default: + rc = SQLITE_MISUSE; + } + + return rc; +} + +/* +** Return the maximum size of sqlite3session_changeset() output. +*/ +SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession){ + return pSession->nMaxChangesetSize; +} + /* ** Do the work for either sqlite3changeset_start() or start_strm(). */ @@ -209048,11 +216302,11 @@ static int sessionChangesetInvert( } assert( rc==SQLITE_OK ); - if( pnInverted ){ + if( pnInverted && ALWAYS(ppInverted) ){ *pnInverted = sOut.nBuf; *ppInverted = sOut.aBuf; sOut.aBuf = 0; - }else if( sOut.nBuf>0 ){ + }else if( sOut.nBuf>0 && ALWAYS(xOutput!=0) ){ rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); } @@ -209508,7 +216762,7 @@ static int sessionBindRow( for(i=0; rc==SQLITE_OK && i0 ) rc = xOutput(pOut, buf.aBuf, buf.nBuf); - }else{ + }else if( ppOut ){ *ppOut = buf.aBuf; - *pnOut = buf.nBuf; + if( pnOut ) *pnOut = buf.nBuf; buf.aBuf = 0; } } @@ -211053,7 +218307,7 @@ static int sessionRebase( if( sOut.nBuf>0 ){ rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); } - }else{ + }else if( ppOut ){ *ppOut = (void*)sOut.aBuf; *pnOut = sOut.nBuf; sOut.aBuf = 0; @@ -211796,8 +219050,20 @@ typedef sqlite3_uint64 u64; #endif #define testcase(x) -#define ALWAYS(x) 1 -#define NEVER(x) 0 + +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +#else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +#endif #define MIN(x,y) (((x) < (y)) ? (x) : (y)) #define MAX(x,y) (((x) > (y)) ? (x) : (y)) @@ -211857,7 +219123,7 @@ SQLITE_API extern int sqlite3_fts5_may_be_corrupt; ** A version of memcmp() that does not cause asan errors if one of the pointer ** parameters is NULL and the number of bytes to compare is zero. */ -#define fts5Memcmp(s1, s2, n) ((n)==0 ? 0 : memcmp((s1), (s2), (n))) +#define fts5Memcmp(s1, s2, n) ((n)<=0 ? 0 : memcmp((s1), (s2), (n))) /* Mark a function parameter as unused, to suppress nuisance compiler ** warnings. */ @@ -212196,6 +219462,9 @@ static void sqlite3Fts5IndexCloseReader(Fts5Index*); */ static const char *sqlite3Fts5IterTerm(Fts5IndexIter*, int*); static int sqlite3Fts5IterNextScan(Fts5IndexIter*); +static void *sqlite3Fts5StructureRef(Fts5Index*); +static void sqlite3Fts5StructureRelease(void*); +static int sqlite3Fts5StructureTest(Fts5Index*, void*); /* @@ -212973,9 +220242,9 @@ struct fts5yyParser { }; typedef struct fts5yyParser fts5yyParser; +/* #include */ #ifndef NDEBUG /* #include */ -/* #include */ static FILE *fts5yyTraceFILE = 0; static char *fts5yyTracePrompt = 0; #endif /* NDEBUG */ @@ -213912,8 +221181,8 @@ static void sqlite3Fts5Parser( fts5yyact = fts5yy_find_shift_action((fts5YYCODETYPE)fts5yymajor,fts5yyact); if( fts5yyact >= fts5YY_MIN_REDUCE ){ unsigned int fts5yyruleno = fts5yyact - fts5YY_MIN_REDUCE; /* Reduce by this rule */ - assert( fts5yyruleno<(int)(sizeof(fts5yyRuleName)/sizeof(fts5yyRuleName[0])) ); #ifndef NDEBUG + assert( fts5yyruleno<(int)(sizeof(fts5yyRuleName)/sizeof(fts5yyRuleName[0])) ); if( fts5yyTraceFILE ){ int fts5yysize = fts5yyRuleInfoNRhs[fts5yyruleno]; if( fts5yysize ){ @@ -214011,14 +221280,13 @@ static void sqlite3Fts5Parser( fts5yy_destructor(fts5yypParser, (fts5YYCODETYPE)fts5yymajor, &fts5yyminorunion); fts5yymajor = fts5YYNOCODE; }else{ - while( fts5yypParser->fts5yytos >= fts5yypParser->fts5yystack - && (fts5yyact = fts5yy_find_reduce_action( - fts5yypParser->fts5yytos->stateno, - fts5YYERRORSYMBOL)) > fts5YY_MAX_SHIFTREDUCE - ){ + while( fts5yypParser->fts5yytos > fts5yypParser->fts5yystack ){ + fts5yyact = fts5yy_find_reduce_action(fts5yypParser->fts5yytos->stateno, + fts5YYERRORSYMBOL); + if( fts5yyact<=fts5YY_MAX_SHIFTREDUCE ) break; fts5yy_pop_parser_stack(fts5yypParser); } - if( fts5yypParser->fts5yytos < fts5yypParser->fts5yystack || fts5yymajor==0 ){ + if( fts5yypParser->fts5yytos <= fts5yypParser->fts5yystack || fts5yymajor==0 ){ fts5yy_destructor(fts5yypParser,(fts5YYCODETYPE)fts5yymajor,&fts5yyminorunion); fts5yy_parse_failed(fts5yypParser); #ifndef fts5YYNOERRORRECOVERY @@ -214881,7 +222149,6 @@ static void sqlite3Fts5BufferAppendBlob( u32 nData, const u8 *pData ){ - assert_nc( *pRc || nData>=0 ); if( nData ){ if( fts5BufferGrow(pRc, pBuf, nData) ) return; memcpy(&pBuf->p[pBuf->n], pData, nData); @@ -214991,7 +222258,7 @@ static int sqlite3Fts5PoslistNext64( return 1; }else{ i64 iOff = *piOff; - int iVal; + u32 iVal; fts5FastGetVarint32(a, i, iVal); if( iVal<=1 ){ if( iVal==0 ){ @@ -215000,15 +222267,19 @@ static int sqlite3Fts5PoslistNext64( } fts5FastGetVarint32(a, i, iVal); iOff = ((i64)iVal) << 32; + assert( iOff>=0 ); fts5FastGetVarint32(a, i, iVal); if( iVal<2 ){ /* This is a corrupt record. So stop parsing it here. */ *piOff = -1; return 1; } + *piOff = iOff + ((iVal-2) & 0x7FFFFFFF); + }else{ + *piOff = (iOff & (i64)0x7FFFFFFF<<32)+((iOff + (iVal-2)) & 0x7FFFFFFF); } - *piOff = iOff + ((iVal-2) & 0x7FFFFFFF); *pi = i; + assert_nc( *piOff>=iOff ); return 0; } } @@ -215047,14 +222318,16 @@ static void sqlite3Fts5PoslistSafeAppend( i64 *piPrev, i64 iPos ){ - static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32; - if( (iPos & colmask) != (*piPrev & colmask) ){ - pBuf->p[pBuf->n++] = 1; - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32)); - *piPrev = (iPos & colmask); + if( iPos>=*piPrev ){ + static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32; + if( (iPos & colmask) != (*piPrev & colmask) ){ + pBuf->p[pBuf->n++] = 1; + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32)); + *piPrev = (iPos & colmask); + } + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-*piPrev)+2); + *piPrev = iPos; } - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-*piPrev)+2); - *piPrev = iPos; } static int sqlite3Fts5PoslistWriterAppend( @@ -215756,7 +223029,7 @@ static int sqlite3Fts5ConfigParse( nByte = nArg * (sizeof(char*) + sizeof(u8)); pRet->azCol = (char**)sqlite3Fts5MallocZero(&rc, nByte); - pRet->abUnindexed = (u8*)&pRet->azCol[nArg]; + pRet->abUnindexed = pRet->azCol ? (u8*)&pRet->azCol[nArg] : 0; pRet->zDb = sqlite3Fts5Strndup(&rc, azArg[1], -1); pRet->zName = sqlite3Fts5Strndup(&rc, azArg[2], -1); pRet->bColumnsize = 1; @@ -215781,6 +223054,7 @@ static int sqlite3Fts5ConfigParse( z = fts5ConfigSkipWhitespace(z); if( z && *z=='=' ){ bOption = 1; + assert( zOne!=0 ); z++; if( bMustBeCol ) z = 0; } @@ -215797,7 +223071,11 @@ static int sqlite3Fts5ConfigParse( rc = SQLITE_ERROR; }else{ if( bOption ){ - rc = fts5ConfigParseSpecial(pGlobal, pRet, zOne, zTwo?zTwo:"", pzErr); + rc = fts5ConfigParseSpecial(pGlobal, pRet, + ALWAYS(zOne)?zOne:"", + zTwo?zTwo:"", + pzErr + ); }else{ rc = fts5ConfigParseColumn(pRet, zOne, zTwo, pzErr); zOne = 0; @@ -216315,6 +223593,7 @@ static void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...){ va_list ap; va_start(ap, zFmt); if( pParse->rc==SQLITE_OK ){ + assert( pParse->zErr==0 ); pParse->zErr = sqlite3_vmprintf(zFmt, ap); pParse->rc = SQLITE_ERROR; } @@ -216613,6 +223892,7 @@ static i64 fts5ExprSynonymRowid(Fts5ExprTerm *pTerm, int bDesc, int *pbEof){ int bRetValid = 0; Fts5ExprTerm *p; + assert( pTerm ); assert( pTerm->pSynonym ); assert( bDesc==0 || bDesc==1 ); for(p=pTerm; p; p=p->pSynonym){ @@ -217805,6 +225085,9 @@ static Fts5ExprNearset *sqlite3Fts5ParseNearset( }else{ if( pRet->nPhrase>0 ){ Fts5ExprPhrase *pLast = pRet->apPhrase[pRet->nPhrase-1]; + assert( pParse!=0 ); + assert( pParse->apPhrase!=0 ); + assert( pParse->nPhrase>=2 ); assert( pLast==pParse->apPhrase[pParse->nPhrase-2] ); if( pPhrase->nTerm==0 ){ fts5ExprPhraseFree(pPhrase); @@ -218053,7 +225336,7 @@ static int sqlite3Fts5ExprClonePhrase( sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase)); } - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK && ALWAYS(sCtx.pPhrase) ){ /* All the allocations succeeded. Put the expression object together. */ pNew->pIndex = pExpr->pIndex; pNew->pConfig = pExpr->pConfig; @@ -218324,9 +225607,8 @@ static void sqlite3Fts5ParseSetColset( ){ Fts5Colset *pFree = pColset; if( pParse->pConfig->eDetail==FTS5_DETAIL_NONE ){ - pParse->rc = SQLITE_ERROR; - pParse->zErr = sqlite3_mprintf( - "fts5: column queries are not supported (detail=none)" + sqlite3Fts5ParseError(pParse, + "fts5: column queries are not supported (detail=none)" ); }else{ fts5ParseSetColset(pParse, pExpr, pColset, &pFree); @@ -218500,13 +225782,10 @@ static Fts5ExprNode *sqlite3Fts5ParseNode( || pPhrase->nTerm>1 || (pPhrase->nTerm>0 && pPhrase->aTerm[0].bFirst) ){ - assert( pParse->rc==SQLITE_OK ); - pParse->rc = SQLITE_ERROR; - assert( pParse->zErr==0 ); - pParse->zErr = sqlite3_mprintf( + sqlite3Fts5ParseError(pParse, "fts5: %s queries are not supported (detail!=full)", pNear->nPhrase==1 ? "phrase": "NEAR" - ); + ); sqlite3_free(pRet); pRet = 0; } @@ -218592,6 +225871,7 @@ static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd( return pRet; } +#ifdef SQLITE_TEST static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){ sqlite3_int64 nByte = 0; Fts5ExprTerm *p; @@ -218958,12 +226238,14 @@ static void fts5ExprFold( sqlite3_result_int(pCtx, sqlite3Fts5UnicodeFold(iCode, bRemoveDiacritics)); } } +#endif /* ifdef SQLITE_TEST */ /* ** This is called during initialization to register the fts5_expr() scalar ** UDF with the SQLite handle passed as the only argument. */ static int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){ +#ifdef SQLITE_TEST struct Fts5ExprFunc { const char *z; void (*x)(sqlite3_context*,int,sqlite3_value**); @@ -218981,6 +226263,10 @@ static int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){ struct Fts5ExprFunc *p = &aFunc[i]; rc = sqlite3_create_function(db, p->z, -1, SQLITE_UTF8, pCtx, p->x, 0, 0); } +#else + int rc = SQLITE_OK; + UNUSED_PARAM2(pGlobal,db); +#endif /* Avoid warnings indicating that sqlite3Fts5ParserTrace() and ** sqlite3Fts5ParserFallback() are unused */ @@ -219031,6 +226317,15 @@ struct Fts5PoslistPopulator { int bMiss; }; +/* +** Clear the position lists associated with all phrases in the expression +** passed as the first argument. Argument bLive is true if the expression +** might be pointing to a real entry, otherwise it has just been reset. +** +** At present this function is only used for detail=col and detail=none +** fts5 tables. This implies that all phrases must be at most 1 token +** in size, as phrase matches are not supported without detail=full. +*/ static Fts5PoslistPopulator *sqlite3Fts5ExprClearPoslists(Fts5Expr *pExpr, int bLive){ Fts5PoslistPopulator *pRet; pRet = sqlite3_malloc64(sizeof(Fts5PoslistPopulator)*pExpr->nPhrase); @@ -219040,7 +226335,7 @@ static Fts5PoslistPopulator *sqlite3Fts5ExprClearPoslists(Fts5Expr *pExpr, int b for(i=0; inPhrase; i++){ Fts5Buffer *pBuf = &pExpr->apExprPhrase[i]->poslist; Fts5ExprNode *pNode = pExpr->apExprPhrase[i]->pNode; - assert( pExpr->apExprPhrase[i]->nTerm==1 ); + assert( pExpr->apExprPhrase[i]->nTerm<=1 ); if( bLive && (pBuf->n==0 || pNode->iRowid!=pExpr->pRoot->iRowid || pNode->bEof) ){ @@ -219591,7 +226886,7 @@ static int sqlite3Fts5HashWrite( p->bContent = 1; }else{ /* Append a new column value, if necessary */ - assert( iCol>=p->iCol ); + assert_nc( iCol>=p->iCol ); if( iCol!=p->iCol ){ if( pHash->eDetail==FTS5_DETAIL_FULL ){ pPtr[p->nData++] = 0x01; @@ -220092,7 +227387,7 @@ struct Fts5Index { sqlite3_stmt *pWriter; /* "INSERT ... %_data VALUES(?,?)" */ sqlite3_stmt *pDeleter; /* "DELETE FROM %_data ... id>=? AND id<=?" */ sqlite3_stmt *pIdxWriter; /* "INSERT ... %_idx VALUES(?,?,?,?)" */ - sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=? */ + sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=?" */ sqlite3_stmt *pIdxSelect; int nRead; /* Total number of blocks read */ @@ -220227,7 +227522,7 @@ struct Fts5SegIter { int iLeafPgno; /* Current leaf page number */ Fts5Data *pLeaf; /* Current leaf data */ Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */ - int iLeafOffset; /* Byte offset within current leaf */ + i64 iLeafOffset; /* Byte offset within current leaf */ /* Next method */ void (*xNext)(Fts5Index*, Fts5SegIter*, int*); @@ -220396,8 +227691,11 @@ static int fts5BufferCompareBlob( ** res = *pLeft - *pRight */ static int fts5BufferCompare(Fts5Buffer *pLeft, Fts5Buffer *pRight){ - int nCmp = MIN(pLeft->n, pRight->n); - int res = fts5Memcmp(pLeft->p, pRight->p, nCmp); + int nCmp, res; + nCmp = MIN(pLeft->n, pRight->n); + assert( nCmp<=0 || pLeft->p!=0 ); + assert( nCmp<=0 || pRight->p!=0 ); + res = fts5Memcmp(pLeft->p, pRight->p, nCmp); return (res==0 ? (pLeft->n - pRight->n) : res); } @@ -220493,6 +227791,7 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ return pRet; } + /* ** Release a reference to data record returned by an earlier call to ** fts5DataRead(). @@ -220617,6 +227916,58 @@ static void fts5StructureRef(Fts5Structure *pStruct){ pStruct->nRef++; } +static void *sqlite3Fts5StructureRef(Fts5Index *p){ + fts5StructureRef(p->pStruct); + return (void*)p->pStruct; +} +static void sqlite3Fts5StructureRelease(void *p){ + if( p ){ + fts5StructureRelease((Fts5Structure*)p); + } +} +static int sqlite3Fts5StructureTest(Fts5Index *p, void *pStruct){ + if( p->pStruct!=(Fts5Structure*)pStruct ){ + return SQLITE_ABORT; + } + return SQLITE_OK; +} + +/* +** Ensure that structure object (*pp) is writable. +** +** This function is a no-op if (*pRc) is not SQLITE_OK when it is called. If +** an error occurs, (*pRc) is set to an SQLite error code before returning. +*/ +static void fts5StructureMakeWritable(int *pRc, Fts5Structure **pp){ + Fts5Structure *p = *pp; + if( *pRc==SQLITE_OK && p->nRef>1 ){ + i64 nByte = sizeof(Fts5Structure)+(p->nLevel-1)*sizeof(Fts5StructureLevel); + Fts5Structure *pNew; + pNew = (Fts5Structure*)sqlite3Fts5MallocZero(pRc, nByte); + if( pNew ){ + int i; + memcpy(pNew, p, nByte); + for(i=0; inLevel; i++) pNew->aLevel[i].aSeg = 0; + for(i=0; inLevel; i++){ + Fts5StructureLevel *pLvl = &pNew->aLevel[i]; + nByte = sizeof(Fts5StructureSegment) * pNew->aLevel[i].nSeg; + pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(pRc, nByte); + if( pLvl->aSeg==0 ){ + for(i=0; inLevel; i++){ + sqlite3_free(pNew->aLevel[i].aSeg); + } + sqlite3_free(pNew); + return; + } + memcpy(pLvl->aSeg, p->aLevel[i].aSeg, nByte); + } + p->nRef--; + pNew->nRef = 1; + } + *pp = pNew; + } +} + /* ** Deserialize and return the structure record currently stored in serialized ** form within buffer pData/nData. @@ -220718,9 +228069,11 @@ static int fts5StructureDecode( } /* -** +** Add a level to the Fts5Structure.aLevel[] array of structure object +** (*ppStruct). */ static void fts5StructureAddLevel(int *pRc, Fts5Structure **ppStruct){ + fts5StructureMakeWritable(pRc, ppStruct); if( *pRc==SQLITE_OK ){ Fts5Structure *pStruct = *ppStruct; int nLevel = pStruct->nLevel; @@ -221407,7 +228760,7 @@ static void fts5SegIterLoadNPos(Fts5Index *p, Fts5SegIter *pIter){ static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){ u8 *a = pIter->pLeaf->p; /* Buffer to read data from */ - int iOff = pIter->iLeafOffset; + i64 iOff = pIter->iLeafOffset; ASSERT_SZLEAF_OK(pIter->pLeaf); if( iOff>=pIter->pLeaf->szLeaf ){ @@ -221440,7 +228793,7 @@ static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){ */ static void fts5SegIterLoadTerm(Fts5Index *p, Fts5SegIter *pIter, int nKeep){ u8 *a = pIter->pLeaf->p; /* Buffer to read data from */ - int iOff = pIter->iLeafOffset; /* Offset to read at */ + i64 iOff = pIter->iLeafOffset; /* Offset to read at */ int nNew; /* Bytes of new data */ iOff += fts5GetVarint32(&a[iOff], nNew); @@ -221514,6 +228867,7 @@ static void fts5SegIterInit( if( p->rc==SQLITE_OK ){ pIter->iLeafOffset = 4; + assert( pIter->pLeaf!=0 ); assert_nc( pIter->pLeaf->nn>4 ); assert_nc( fts5LeafFirstTermOff(pIter->pLeaf)==4 ); pIter->iPgidxOff = pIter->pLeaf->szLeaf+1; @@ -221616,8 +228970,12 @@ static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){ int iRowidOff; iRowidOff = fts5LeafFirstRowidOff(pNew); if( iRowidOff ){ - pIter->pLeaf = pNew; - pIter->iLeafOffset = iRowidOff; + if( iRowidOff>=pNew->szLeaf ){ + p->rc = FTS5_CORRUPT; + }else{ + pIter->pLeaf = pNew; + pIter->iLeafOffset = iRowidOff; + } } } @@ -221868,7 +229226,6 @@ static void fts5SegIterNext( ** this block is particularly performance critical, so equivalent ** code is inlined. */ int nSz; - assert( p->rc==SQLITE_OK ); assert_nc( pIter->iLeafOffset<=pIter->pLeaf->nn ); fts5FastGetVarint32(pIter->pLeaf->p, pIter->iLeafOffset, nSz); pIter->bDel = (nSz & 0x0001); @@ -221898,7 +229255,7 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ if( pDlidx ){ int iSegid = pIter->pSeg->iSegid; pgnoLast = fts5DlidxIterPgno(pDlidx); - pLast = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast)); + pLast = fts5LeafRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast)); }else{ Fts5Data *pLeaf = pIter->pLeaf; /* Current leaf data */ @@ -221925,7 +229282,7 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ ** forward to find the page containing the last rowid. */ for(pgno=pIter->iLeafPgno+1; !p->rc && pgno<=pSeg->pgnoLast; pgno++){ i64 iAbs = FTS5_SEGMENT_ROWID(pSeg->iSegid, pgno); - Fts5Data *pNew = fts5DataRead(p, iAbs); + Fts5Data *pNew = fts5LeafRead(p, iAbs); if( pNew ){ int iRowid, bTermless; iRowid = fts5LeafFirstRowidOff(pNew); @@ -221956,6 +229313,10 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ pIter->pLeaf = pLast; pIter->iLeafPgno = pgnoLast; iOff = fts5LeafFirstRowidOff(pLast); + if( iOff>pLast->szLeaf ){ + p->rc = FTS5_CORRUPT; + return; + } iOff += fts5GetVarint(&pLast->p[iOff], (u64*)&pIter->iRowid); pIter->iLeafOffset = iOff; @@ -221964,7 +229325,6 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ }else{ pIter->iEndofDoclist = fts5LeafFirstTermOff(pLast); } - } fts5SegIterReverseInitPage(p, pIter); @@ -222016,21 +229376,20 @@ static void fts5LeafSeek( Fts5SegIter *pIter, /* Iterator to seek */ const u8 *pTerm, int nTerm /* Term to search for */ ){ - int iOff; + u32 iOff; const u8 *a = pIter->pLeaf->p; - int szLeaf = pIter->pLeaf->szLeaf; - int n = pIter->pLeaf->nn; + u32 n = (u32)pIter->pLeaf->nn; u32 nMatch = 0; u32 nKeep = 0; u32 nNew = 0; u32 iTermOff; - int iPgidx; /* Current offset in pgidx */ + u32 iPgidx; /* Current offset in pgidx */ int bEndOfPage = 0; assert( p->rc==SQLITE_OK ); - iPgidx = szLeaf; + iPgidx = (u32)pIter->pLeaf->szLeaf; iPgidx += fts5GetVarint32(&a[iPgidx], iTermOff); iOff = iTermOff; if( iOff>n ){ @@ -222096,15 +229455,15 @@ static void fts5LeafSeek( if( pIter->pLeaf==0 ) return; a = pIter->pLeaf->p; if( fts5LeafIsTermless(pIter->pLeaf)==0 ){ - iPgidx = pIter->pLeaf->szLeaf; + iPgidx = (u32)pIter->pLeaf->szLeaf; iPgidx += fts5GetVarint32(&pIter->pLeaf->p[iPgidx], iOff); - if( iOff<4 || iOff>=pIter->pLeaf->szLeaf ){ + if( iOff<4 || (i64)iOff>=pIter->pLeaf->szLeaf ){ p->rc = FTS5_CORRUPT; return; }else{ nKeep = 0; iTermOff = iOff; - n = pIter->pLeaf->nn; + n = (u32)pIter->pLeaf->nn; iOff += fts5GetVarint32(&a[iOff], nNew); break; } @@ -222472,7 +229831,7 @@ static void fts5SegIterGotoPage( fts5SegIterNextPage(p, pIter); assert( p->rc!=SQLITE_OK || pIter->iLeafPgno==iLeafPgno ); - if( p->rc==SQLITE_OK ){ + if( p->rc==SQLITE_OK && ALWAYS(pIter->pLeaf!=0) ){ int iOff; u8 *a = pIter->pLeaf->p; int n = pIter->pLeaf->szLeaf; @@ -222904,7 +230263,11 @@ static void fts5SegiterPoslist( Fts5Colset *pColset, Fts5Buffer *pBuf ){ + assert( pBuf!=0 ); + assert( pSeg!=0 ); if( 0==fts5BufferGrow(&p->rc, pBuf, pSeg->nPos+FTS5_DATA_ZERO_PADDING) ){ + assert( pBuf->p!=0 ); + assert( pBuf->nSpace >= pBuf->n+pSeg->nPos+FTS5_DATA_ZERO_PADDING ); memset(&pBuf->p[pBuf->n+pSeg->nPos], 0, FTS5_DATA_ZERO_PADDING); if( pColset==0 ){ fts5ChunkIterate(p, pSeg, (void*)pBuf, fts5PoslistCallback); @@ -222980,7 +230343,7 @@ static void fts5IndexExtractColset( } fts5BufferSafeAppendBlob(&pIter->poslist, aCopy, p-aCopy); } - if( p==pEnd ){ + if( p>=pEnd ){ pIter->base.pData = pIter->poslist.p; pIter->base.nData = pIter->poslist.n; return; @@ -223128,6 +230491,7 @@ static void fts5IterSetOutputs_Full(Fts5Iter *pIter, Fts5SegIter *pSeg){ } static void fts5IterSetOutputCb(int *pRc, Fts5Iter *pIter){ + assert( pIter!=0 || (*pRc)!=SQLITE_OK ); if( *pRc==SQLITE_OK ){ Fts5Config *pConfig = pIter->pIndex->pConfig; if( pConfig->eDetail==FTS5_DETAIL_NONE ){ @@ -223199,7 +230563,10 @@ static void fts5MultiIterNew( } } *ppOut = pNew = fts5MultiIterAlloc(p, nSeg); - if( pNew==0 ) return; + if( pNew==0 ){ + assert( p->rc!=SQLITE_OK ); + goto fts5MultiIterNew_post_check; + } pNew->bRev = (0!=(flags & FTS5INDEX_QUERY_DESC)); pNew->bSkipEmpty = (0!=(flags & FTS5INDEX_QUERY_SKIPEMPTY)); pNew->pColset = pColset; @@ -223263,6 +230630,10 @@ static void fts5MultiIterNew( fts5MultiIterFree(pNew); *ppOut = 0; } + +fts5MultiIterNew_post_check: + assert( (*ppOut)!=0 || p->rc!=SQLITE_OK ); + return; } /* @@ -223310,7 +230681,8 @@ static void fts5MultiIterNew2( ** False otherwise. */ static int fts5MultiIterEof(Fts5Index *p, Fts5Iter *pIter){ - assert( p->rc + assert( pIter!=0 || p->rc!=SQLITE_OK ); + assert( p->rc!=SQLITE_OK || (pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf==0)==pIter->base.bEof ); return (p->rc || pIter->base.bEof); @@ -224114,6 +231486,7 @@ static void fts5IndexMergeLevel( ** and last leaf page number at the same time. */ fts5WriteFinish(p, &writer, &pSeg->pgnoLast); + assert( pIter!=0 || p->rc!=SQLITE_OK ); if( fts5MultiIterEof(p, pIter) ){ int i; @@ -224214,7 +231587,7 @@ static void fts5IndexAutomerge( Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */ int nLeaf /* Number of output leaves just written */ ){ - if( p->rc==SQLITE_OK && p->pConfig->nAutomerge>0 ){ + if( p->rc==SQLITE_OK && p->pConfig->nAutomerge>0 && ALWAYS((*ppStruct)!=0) ){ Fts5Structure *pStruct = *ppStruct; u64 nWrite; /* Initial value of write-counter */ int nWork; /* Number of work-quanta to perform */ @@ -224337,14 +231710,14 @@ static void fts5FlushOneHash(Fts5Index *p){ fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist); }else{ i64 iRowid = 0; - i64 iDelta = 0; + u64 iDelta = 0; int iOff = 0; /* The entire doclist will not fit on this leaf. The following ** loop iterates through the poslists that make up the current ** doclist. */ while( p->rc==SQLITE_OK && iOffaPos,(p)->iter.nPoslist,&(p)->iOff,&(p)->iPos); + sqlite3Fts5PoslistNext64((p)->aPos,(p)->iter.nPoslist,&(p)->iOff,&(p)->iPos) #define FTS5_MERGE_NLIST 16 PrefixMerger aMerger[FTS5_MERGE_NLIST]; PrefixMerger *pHead = 0; @@ -224875,7 +232248,8 @@ static void fts5MergePrefixLists( nTail = pHead->iter.nPoslist - pHead->iOff; /* WRITEPOSLISTSIZE */ - assert( tmp.n+nTail<=nTmp ); + assert_nc( tmp.n+nTail<=nTmp ); + assert( tmp.n+nTail<=nTmp+nMerge*10 ); if( tmp.n+nTail>nTmp-FTS5_DATA_ZERO_PADDING ){ if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT; break; @@ -225282,7 +232656,7 @@ static int sqlite3Fts5IndexQuery( if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){ int iIdx = 0; /* Index to search */ int iPrefixIdx = 0; /* +1 prefix index */ - if( nToken ) memcpy(&buf.p[1], pToken, nToken); + if( nToken>0 ) memcpy(&buf.p[1], pToken, nToken); /* Figure out which index to search and set iIdx accordingly. If this ** is a prefix query for which there is no prefix index, set iIdx to @@ -225323,11 +232697,15 @@ static int sqlite3Fts5IndexQuery( /* Scan multiple terms in the main index */ int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0; fts5SetupPrefixIter(p, bDesc, iPrefixIdx, buf.p, nToken+1, pColset,&pRet); - assert( p->rc!=SQLITE_OK || pRet->pColset==0 ); - fts5IterSetOutputCb(&p->rc, pRet); - if( p->rc==SQLITE_OK ){ - Fts5SegIter *pSeg = &pRet->aSeg[pRet->aFirst[1].iFirst]; - if( pSeg->pLeaf ) pRet->xSetOutputs(pRet, pSeg); + if( pRet==0 ){ + assert( p->rc!=SQLITE_OK ); + }else{ + assert( pRet->pColset==0 ); + fts5IterSetOutputCb(&p->rc, pRet); + if( p->rc==SQLITE_OK ){ + Fts5SegIter *pSeg = &pRet->aSeg[pRet->aFirst[1].iFirst]; + if( pSeg->pLeaf ) pRet->xSetOutputs(pRet, pSeg); + } } } @@ -225575,7 +232953,7 @@ static int fts5QueryCksum( Fts5IndexIter *pIter = 0; int rc = sqlite3Fts5IndexQuery(p, z, n, flags, 0, &pIter); - while( rc==SQLITE_OK && 0==sqlite3Fts5IterEof(pIter) ){ + while( rc==SQLITE_OK && ALWAYS(pIter!=0) && 0==sqlite3Fts5IterEof(pIter) ){ i64 rowid = pIter->iRowid; if( eDetail==FTS5_DETAIL_NONE ){ @@ -225940,6 +233318,7 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum Fts5Buffer poslist = {0,0,0}; /* Buffer used to hold a poslist */ Fts5Iter *pIter; /* Used to iterate through entire index */ Fts5Structure *pStruct; /* Index structure */ + int iLvl, iSeg; #ifdef SQLITE_DEBUG /* Used by extra internal tests only run if NDEBUG is not defined */ @@ -225950,15 +233329,16 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum /* Load the FTS index structure */ pStruct = fts5StructureRead(p); + if( pStruct==0 ){ + assert( p->rc!=SQLITE_OK ); + return fts5IndexReturn(p); + } /* Check that the internal nodes of each segment match the leaves */ - if( pStruct ){ - int iLvl, iSeg; - for(iLvl=0; iLvlnLevel; iLvl++){ - for(iSeg=0; iSegaLevel[iLvl].nSeg; iSeg++){ - Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; - fts5IndexIntegrityCheckSegment(p, pSeg); - } + for(iLvl=0; iLvlnLevel; iLvl++){ + for(iSeg=0; iSegaLevel[iLvl].nSeg; iSeg++){ + Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; + fts5IndexIntegrityCheckSegment(p, pSeg); } } @@ -226022,6 +233402,7 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum ** function only. */ +#ifdef SQLITE_TEST /* ** Decode a segment-data rowid from the %_data table. This function is ** the opposite of macro FTS5_SEGMENT_ROWID(). @@ -226044,7 +233425,9 @@ static void fts5DecodeRowid( *piSegid = (int)(iRowid & (((i64)1 << FTS5_DATA_ID_B) - 1)); } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){ int iSegid, iHeight, iPgno, bDlidx; /* Rowid compenents */ fts5DecodeRowid(iKey, &iSegid, &bDlidx, &iHeight, &iPgno); @@ -226062,7 +233445,9 @@ static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){ ); } } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST static void fts5DebugStructure( int *pRc, /* IN/OUT: error code */ Fts5Buffer *pBuf, @@ -226084,7 +233469,9 @@ static void fts5DebugStructure( sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}"); } } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST /* ** This is part of the fts5_decode() debugging aid. ** @@ -226109,7 +233496,9 @@ static void fts5DecodeStructure( fts5DebugStructure(pRc, pBuf, p); fts5StructureRelease(p); } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST /* ** This is part of the fts5_decode() debugging aid. ** @@ -226132,7 +233521,9 @@ static void fts5DecodeAverages( zSpace = " "; } } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST /* ** Buffer (a/n) is assumed to contain a list of serialized varints. Read ** each varint and append its string representation to buffer pBuf. Return @@ -226149,7 +233540,9 @@ static int fts5DecodePoslist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ } return iOff; } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST /* ** The start of buffer (a/n) contains the start of a doclist. The doclist ** may or may not finish within the buffer. This function appends a text @@ -226182,7 +233575,9 @@ static int fts5DecodeDoclist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ return iOff; } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST /* ** This function is part of the fts5_decode() debugging function. It is ** only ever used with detail=none tables. @@ -226223,7 +233618,9 @@ static void fts5DecodeRowidList( sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %lld%s", iRowid, zApp); } } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST /* ** The implementation of user-defined scalar function fts5_decode(). */ @@ -226432,7 +233829,9 @@ static void fts5DecodeFunction( } fts5BufferFree(&s); } +#endif /* SQLITE_TEST */ +#ifdef SQLITE_TEST /* ** The implementation of user-defined scalar function fts5_rowid(). */ @@ -226466,6 +233865,7 @@ static void fts5RowidFunction( } } } +#endif /* SQLITE_TEST */ /* ** This is called as part of registering the FTS5 module with database @@ -226476,6 +233876,7 @@ static void fts5RowidFunction( ** SQLite error code is returned instead. */ static int sqlite3Fts5IndexInit(sqlite3 *db){ +#ifdef SQLITE_TEST int rc = sqlite3_create_function( db, "fts5_decode", 2, SQLITE_UTF8, 0, fts5DecodeFunction, 0, 0 ); @@ -226493,6 +233894,10 @@ static int sqlite3Fts5IndexInit(sqlite3 *db){ ); } return rc; +#else + return SQLITE_OK; + UNUSED_PARAM(db); +#endif } @@ -226528,7 +233933,9 @@ static int sqlite3Fts5IndexReset(Fts5Index *p){ ** assert() conditions in the fts5 code are activated - conditions that are ** only true if it is guaranteed that the fts5 database is not corrupt. */ +#ifdef SQLITE_DEBUG SQLITE_API int sqlite3_fts5_may_be_corrupt = 1; +#endif typedef struct Fts5Auxdata Fts5Auxdata; @@ -227308,7 +234715,7 @@ static int fts5SorterNext(Fts5Cursor *pCsr){ rc = sqlite3_step(pSorter->pStmt); if( rc==SQLITE_DONE ){ rc = SQLITE_OK; - CsrFlagSet(pCsr, FTS5CSR_EOF); + CsrFlagSet(pCsr, FTS5CSR_EOF|FTS5CSR_REQUIRE_CONTENT); }else if( rc==SQLITE_ROW ){ const u8 *a; const u8 *aBlob; @@ -227878,7 +235285,8 @@ static int fts5FilterMethod( pTab->pStorage, fts5StmtType(pCsr), &pCsr->pStmt, &pTab->p.base.zErrMsg ); if( rc==SQLITE_OK ){ - if( pCsr->ePlan==FTS5_PLAN_ROWID ){ + if( pRowidEq!=0 ){ + assert( pCsr->ePlan==FTS5_PLAN_ROWID ); sqlite3_bind_value(pCsr->pStmt, 1, pRowidEq); }else{ sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iFirstRowid); @@ -228453,13 +235861,15 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){ nInst++; if( nInst>=pCsr->nInstAlloc ){ - pCsr->nInstAlloc = pCsr->nInstAlloc ? pCsr->nInstAlloc*2 : 32; + int nNewSize = pCsr->nInstAlloc ? pCsr->nInstAlloc*2 : 32; aInst = (int*)sqlite3_realloc64( - pCsr->aInst, pCsr->nInstAlloc*sizeof(int)*3 + pCsr->aInst, nNewSize*sizeof(int)*3 ); if( aInst ){ pCsr->aInst = aInst; + pCsr->nInstAlloc = nNewSize; }else{ + nInst--; rc = SQLITE_NOMEM; break; } @@ -229294,7 +236704,7 @@ static void fts5SourceIdFunc( ){ assert( nArg==0 ); UNUSED_PARAM2(nArg, apUnused); - sqlite3_result_text(pCtx, "fts5: 2021-04-02 15:20:15 5d4c65779dab868b285519b19e4cf9d451d50c6048f06f653aa701ec212df45e", -1, SQLITE_TRANSIENT); + sqlite3_result_text(pCtx, "fts5: 2022-09-05 11:02:23 4635f4a69c8c2a8df242b384a992aea71224e39a2ccab42d8c0b0602f1e826e8", -1, SQLITE_TRANSIENT); } /* @@ -229845,12 +237255,16 @@ static int fts5StorageDeleteFromIndex( if( pConfig->abUnindexed[iCol-1]==0 ){ const char *zText; int nText; + assert( pSeek==0 || apVal==0 ); + assert( pSeek!=0 || apVal!=0 ); if( pSeek ){ zText = (const char*)sqlite3_column_text(pSeek, iCol); nText = sqlite3_column_bytes(pSeek, iCol); - }else{ + }else if( ALWAYS(apVal) ){ zText = (const char*)sqlite3_value_text(apVal[iCol-1]); nText = sqlite3_value_bytes(apVal[iCol-1]); + }else{ + continue; } ctx.szCol = 0; rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT, @@ -230486,8 +237900,9 @@ static int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){ assert( p->pConfig->bColumnsize ); rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0); - if( rc==SQLITE_OK ){ + if( pLookup ){ int bCorrupt = 1; + assert( rc==SQLITE_OK ); sqlite3_bind_int64(pLookup, 1, iRowid); if( SQLITE_ROW==sqlite3_step(pLookup) ){ const u8 *aBlob = sqlite3_column_blob(pLookup, 0); @@ -230500,6 +237915,8 @@ static int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){ if( bCorrupt && rc==SQLITE_OK ){ rc = FTS5_CORRUPT; } + }else{ + assert( rc!=SQLITE_OK ); } return rc; @@ -233190,6 +240607,7 @@ struct Fts5VocabCursor { int bEof; /* True if this cursor is at EOF */ Fts5IndexIter *pIter; /* Term/rowid iterator object */ + void *pStruct; /* From sqlite3Fts5StructureRef() */ int nLeTerm; /* Size of zLeTerm in bytes */ char *zLeTerm; /* (term <= $zLeTerm) paramater, or NULL */ @@ -233503,7 +240921,7 @@ static int fts5VocabOpenMethod( } if( rc==SQLITE_OK ){ - int nByte = pFts5->pConfig->nCol * sizeof(i64)*2 + sizeof(Fts5VocabCursor); + i64 nByte = pFts5->pConfig->nCol * sizeof(i64)*2 + sizeof(Fts5VocabCursor); pCsr = (Fts5VocabCursor*)sqlite3Fts5MallocZero(&rc, nByte); } @@ -233523,6 +240941,8 @@ static int fts5VocabOpenMethod( static void fts5VocabResetCursor(Fts5VocabCursor *pCsr){ pCsr->rowid = 0; sqlite3Fts5IterClose(pCsr->pIter); + sqlite3Fts5StructureRelease(pCsr->pStruct); + pCsr->pStruct = 0; pCsr->pIter = 0; sqlite3_free(pCsr->zLeTerm); pCsr->nLeTerm = -1; @@ -233600,9 +241020,11 @@ static int fts5VocabInstanceNext(Fts5VocabCursor *pCsr){ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){ Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor; Fts5VocabTable *pTab = (Fts5VocabTable*)pCursor->pVtab; - int rc = SQLITE_OK; int nCol = pCsr->pFts5->pConfig->nCol; + int rc; + rc = sqlite3Fts5StructureTest(pCsr->pFts5->pIndex, pCsr->pStruct); + if( rc!=SQLITE_OK ) return rc; pCsr->rowid++; if( pTab->eType==FTS5_VOCAB_INSTANCE ){ @@ -233776,6 +241198,9 @@ static int fts5VocabFilterMethod( if( rc==SQLITE_OK ){ Fts5Index *pIndex = pCsr->pFts5->pIndex; rc = sqlite3Fts5IndexQuery(pIndex, zTerm, nTerm, f, 0, &pCsr->pIter); + if( rc==SQLITE_OK ){ + pCsr->pStruct = sqlite3Fts5StructureRef(pIndex); + } } if( rc==SQLITE_OK && eType==FTS5_VOCAB_INSTANCE ){ rc = fts5VocabInstanceNewTerm(pCsr); @@ -233950,6 +241375,16 @@ SQLITE_EXTENSION_INIT1 #ifndef SQLITE_OMIT_VIRTUALTABLE + +#define STMT_NUM_INTEGER_COLUMN 10 +typedef struct StmtRow StmtRow; +struct StmtRow { + sqlite3_int64 iRowid; /* Rowid value */ + char *zSql; /* column "sql" */ + int aCol[STMT_NUM_INTEGER_COLUMN+1]; /* all other column values */ + StmtRow *pNext; /* Next row to return */ +}; + /* stmt_vtab is a subclass of sqlite3_vtab which will ** serve as the underlying representation of a stmt virtual table */ @@ -233967,8 +241402,7 @@ typedef struct stmt_cursor stmt_cursor; struct stmt_cursor { sqlite3_vtab_cursor base; /* Base class - must be first */ sqlite3 *db; /* Database connection for this cursor */ - sqlite3_stmt *pStmt; /* Statement cursor is currently pointing at */ - sqlite3_int64 iRowid; /* The rowid */ + StmtRow *pRow; /* Current row */ }; /* @@ -234012,7 +241446,7 @@ static int stmtConnect( "CREATE TABLE x(sql,ncol,ro,busy,nscan,nsort,naidx,nstep," "reprep,run,mem)"); if( rc==SQLITE_OK ){ - pNew = sqlite3_malloc( sizeof(*pNew) ); + pNew = sqlite3_malloc64( sizeof(*pNew) ); *ppVtab = (sqlite3_vtab*)pNew; if( pNew==0 ) return SQLITE_NOMEM; memset(pNew, 0, sizeof(*pNew)); @@ -234034,7 +241468,7 @@ static int stmtDisconnect(sqlite3_vtab *pVtab){ */ static int stmtOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ stmt_cursor *pCur; - pCur = sqlite3_malloc( sizeof(*pCur) ); + pCur = sqlite3_malloc64( sizeof(*pCur) ); if( pCur==0 ) return SQLITE_NOMEM; memset(pCur, 0, sizeof(*pCur)); pCur->db = ((stmt_vtab*)p)->db; @@ -234042,10 +241476,21 @@ static int stmtOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ return SQLITE_OK; } +static void stmtCsrReset(stmt_cursor *pCur){ + StmtRow *pRow = 0; + StmtRow *pNext = 0; + for(pRow=pCur->pRow; pRow; pRow=pNext){ + pNext = pRow->pNext; + sqlite3_free(pRow); + } + pCur->pRow = 0; +} + /* ** Destructor for a stmt_cursor. */ static int stmtClose(sqlite3_vtab_cursor *cur){ + stmtCsrReset((stmt_cursor*)cur); sqlite3_free(cur); return SQLITE_OK; } @@ -234056,8 +241501,9 @@ static int stmtClose(sqlite3_vtab_cursor *cur){ */ static int stmtNext(sqlite3_vtab_cursor *cur){ stmt_cursor *pCur = (stmt_cursor*)cur; - pCur->iRowid++; - pCur->pStmt = sqlite3_next_stmt(pCur->db, pCur->pStmt); + StmtRow *pNext = pCur->pRow->pNext; + sqlite3_free(pCur->pRow); + pCur->pRow = pNext; return SQLITE_OK; } @@ -234071,39 +241517,11 @@ static int stmtColumn( int i /* Which column to return */ ){ stmt_cursor *pCur = (stmt_cursor*)cur; - switch( i ){ - case STMT_COLUMN_SQL: { - sqlite3_result_text(ctx, sqlite3_sql(pCur->pStmt), -1, SQLITE_TRANSIENT); - break; - } - case STMT_COLUMN_NCOL: { - sqlite3_result_int(ctx, sqlite3_column_count(pCur->pStmt)); - break; - } - case STMT_COLUMN_RO: { - sqlite3_result_int(ctx, sqlite3_stmt_readonly(pCur->pStmt)); - break; - } - case STMT_COLUMN_BUSY: { - sqlite3_result_int(ctx, sqlite3_stmt_busy(pCur->pStmt)); - break; - } - default: { - assert( i==STMT_COLUMN_MEM ); - i = SQLITE_STMTSTATUS_MEMUSED + - STMT_COLUMN_NSCAN - SQLITE_STMTSTATUS_FULLSCAN_STEP; - /* Fall thru */ - } - case STMT_COLUMN_NSCAN: - case STMT_COLUMN_NSORT: - case STMT_COLUMN_NAIDX: - case STMT_COLUMN_NSTEP: - case STMT_COLUMN_REPREP: - case STMT_COLUMN_RUN: { - sqlite3_result_int(ctx, sqlite3_stmt_status(pCur->pStmt, - i-STMT_COLUMN_NSCAN+SQLITE_STMTSTATUS_FULLSCAN_STEP, 0)); - break; - } + StmtRow *pRow = pCur->pRow; + if( i==STMT_COLUMN_SQL ){ + sqlite3_result_text(ctx, pRow->zSql, -1, SQLITE_TRANSIENT); + }else{ + sqlite3_result_int(ctx, pRow->aCol[i]); } return SQLITE_OK; } @@ -234114,7 +241532,7 @@ static int stmtColumn( */ static int stmtRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ stmt_cursor *pCur = (stmt_cursor*)cur; - *pRowid = pCur->iRowid; + *pRowid = pCur->pRow->iRowid; return SQLITE_OK; } @@ -234124,7 +241542,7 @@ static int stmtRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ */ static int stmtEof(sqlite3_vtab_cursor *cur){ stmt_cursor *pCur = (stmt_cursor*)cur; - return pCur->pStmt==0; + return pCur->pRow==0; } /* @@ -234139,9 +241557,53 @@ static int stmtFilter( int argc, sqlite3_value **argv ){ stmt_cursor *pCur = (stmt_cursor *)pVtabCursor; - pCur->pStmt = 0; - pCur->iRowid = 0; - return stmtNext(pVtabCursor); + sqlite3_stmt *p = 0; + sqlite3_int64 iRowid = 1; + StmtRow **ppRow = 0; + + stmtCsrReset(pCur); + ppRow = &pCur->pRow; + for(p=sqlite3_next_stmt(pCur->db, 0); p; p=sqlite3_next_stmt(pCur->db, p)){ + const char *zSql = sqlite3_sql(p); + sqlite3_int64 nSql = zSql ? strlen(zSql)+1 : 0; + StmtRow *pNew = (StmtRow*)sqlite3_malloc64(sizeof(StmtRow) + nSql); + + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(StmtRow)); + if( zSql ){ + pNew->zSql = (char*)&pNew[1]; + memcpy(pNew->zSql, zSql, nSql); + } + pNew->aCol[STMT_COLUMN_NCOL] = sqlite3_column_count(p); + pNew->aCol[STMT_COLUMN_RO] = sqlite3_stmt_readonly(p); + pNew->aCol[STMT_COLUMN_BUSY] = sqlite3_stmt_busy(p); + pNew->aCol[STMT_COLUMN_NSCAN] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_FULLSCAN_STEP, 0 + ); + pNew->aCol[STMT_COLUMN_NSORT] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_SORT, 0 + ); + pNew->aCol[STMT_COLUMN_NAIDX] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_AUTOINDEX, 0 + ); + pNew->aCol[STMT_COLUMN_NSTEP] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_VM_STEP, 0 + ); + pNew->aCol[STMT_COLUMN_REPREP] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_REPREPARE, 0 + ); + pNew->aCol[STMT_COLUMN_RUN] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_RUN, 0 + ); + pNew->aCol[STMT_COLUMN_MEM] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_MEMUSED, 0 + ); + pNew->iRowid = iRowid++; + *ppRow = pNew; + ppRow = &pNew->pNext; + } + + return SQLITE_OK; } /* @@ -234220,10 +241682,6 @@ SQLITE_API int sqlite3_stmt_init( #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */ /************** End of stmt.c ************************************************/ -#if __LINE__!=234223 -#undef SQLITE_SOURCE_ID -#define SQLITE_SOURCE_ID "2021-04-02 15:20:15 5d4c65779dab868b285519b19e4cf9d451d50c6048f06f653aa701ec212dalt2" -#endif /* Return the source-id for this library */ SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; } /************************** End of sqlite3.c ******************************/ diff --git a/TMessagesProj/jni/sqlite/sqlite3.h b/TMessagesProj/jni/sqlite/sqlite3.h index 21ab5f1aa..286833467 100644 --- a/TMessagesProj/jni/sqlite/sqlite3.h +++ b/TMessagesProj/jni/sqlite/sqlite3.h @@ -43,7 +43,30 @@ extern "C" { /* -** Provide the ability to override linkage features of the interface. +** Facilitate override of interface linkage and calling conventions. +** Be aware that these macros may not be used within this particular +** translation of the amalgamation and its associated header file. +** +** The SQLITE_EXTERN and SQLITE_API macros are used to instruct the +** compiler that the target identifier should have external linkage. +** +** The SQLITE_CDECL macro is used to set the calling convention for +** public functions that accept a variable number of arguments. +** +** The SQLITE_APICALL macro is used to set the calling convention for +** public functions that accept a fixed number of arguments. +** +** The SQLITE_STDCALL macro is no longer used and is now deprecated. +** +** The SQLITE_CALLBACK macro is used to set the calling convention for +** function pointers. +** +** The SQLITE_SYSAPI macro is used to set the calling convention for +** functions provided by the operating system. +** +** Currently, the SQLITE_CDECL, SQLITE_APICALL, SQLITE_CALLBACK, and +** SQLITE_SYSAPI macros are used only when building for environments +** that require non-default calling conventions. */ #ifndef SQLITE_EXTERN # define SQLITE_EXTERN extern @@ -123,9 +146,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.35.4" -#define SQLITE_VERSION_NUMBER 3035004 -#define SQLITE_SOURCE_ID "2021-04-02 15:20:15 5d4c65779dab868b285519b19e4cf9d451d50c6048f06f653aa701ec212df45e" +#define SQLITE_VERSION "3.39.3" +#define SQLITE_VERSION_NUMBER 3039003 +#define SQLITE_SOURCE_ID "2022-09-05 11:02:23 4635f4a69c8c2a8df242b384a992aea71224e39a2ccab42d8c0b0602f1e826e8" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -537,12 +560,13 @@ SQLITE_API int sqlite3_exec( #define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9<<8)) #define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT |(10<<8)) #define SQLITE_CONSTRAINT_PINNED (SQLITE_CONSTRAINT |(11<<8)) +#define SQLITE_CONSTRAINT_DATATYPE (SQLITE_CONSTRAINT |(12<<8)) #define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8)) #define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8)) #define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8)) #define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8)) #define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8)) -#define SQLITE_OK_SYMLINK (SQLITE_OK | (2<<8)) +#define SQLITE_OK_SYMLINK (SQLITE_OK | (2<<8)) /* internal use only */ /* ** CAPI3REF: Flags For File Open Operations @@ -550,6 +574,19 @@ SQLITE_API int sqlite3_exec( ** These bit values are intended for use in the ** 3rd parameter to the [sqlite3_open_v2()] interface and ** in the 4th parameter to the [sqlite3_vfs.xOpen] method. +** +** Only those flags marked as "Ok for sqlite3_open_v2()" may be +** used as the third argument to the [sqlite3_open_v2()] interface. +** The other flags have historically been ignored by sqlite3_open_v2(), +** though future versions of SQLite might change so that an error is +** raised if any of the disallowed bits are passed into sqlite3_open_v2(). +** Applications should not depend on the historical behavior. +** +** Note in particular that passing the SQLITE_OPEN_EXCLUSIVE flag into +** [sqlite3_open_v2()] does *not* cause the underlying database file +** to be opened using O_EXCL. Passing SQLITE_OPEN_EXCLUSIVE into +** [sqlite3_open_v2()] has historically be a no-op and might become an +** error in future versions of SQLite. */ #define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */ @@ -572,6 +609,7 @@ SQLITE_API int sqlite3_exec( #define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_WAL 0x00080000 /* VFS only */ #define SQLITE_OPEN_NOFOLLOW 0x01000000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_EXRESCODE 0x02000000 /* Extended result codes */ /* Reserved: 0x00F00000 */ /* Legacy compatibility: */ @@ -1128,6 +1166,23 @@ struct sqlite3_io_methods { ** file to the database file, but before the *-shm file is updated to ** record the fact that the pages have been checkpointed. ** +** +**
  • [[SQLITE_FCNTL_EXTERNAL_READER]] +** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect +** whether or not there is a database client in another process with a wal-mode +** transaction open on the database or not. It is only available on unix.The +** (void*) argument passed with this file-control should be a pointer to a +** value of type (int). The integer value is set to 1 if the database is a wal +** mode database and there exists at least one client in another process that +** currently has an SQL transaction open on the database. It is set to 0 if +** the database is not a wal-mode db, or if there is no such connection in any +** other process. This opcode cannot be used to detect transactions opened +** by clients within the current process, only within other processes. +** +** +**
  • [[SQLITE_FCNTL_CKSM_FILE]] +** Used by the cksmvfs VFS module only. +** */ #define SQLITE_FCNTL_LOCKSTATE 1 #define SQLITE_FCNTL_GET_LOCKPROXYFILE 2 @@ -1167,6 +1222,8 @@ struct sqlite3_io_methods { #define SQLITE_FCNTL_CKPT_DONE 37 #define SQLITE_FCNTL_RESERVE_BYTES 38 #define SQLITE_FCNTL_CKPT_START 39 +#define SQLITE_FCNTL_EXTERNAL_READER 40 +#define SQLITE_FCNTL_CKSM_FILE 41 /* deprecated names */ #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE @@ -2445,11 +2502,14 @@ SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64); ** CAPI3REF: Count The Number Of Rows Modified ** METHOD: sqlite3 ** -** ^This function returns the number of rows modified, inserted or +** ^These functions return the number of rows modified, inserted or ** deleted by the most recently completed INSERT, UPDATE or DELETE ** statement on the database connection specified by the only parameter. -** ^Executing any other type of SQL statement does not modify the value -** returned by this function. +** The two functions are identical except for the type of the return value +** and that if the number of rows modified by the most recent INSERT, UPDATE +** or DELETE is greater than the maximum value supported by type "int", then +** the return value of sqlite3_changes() is undefined. ^Executing any other +** type of SQL statement does not modify the value returned by these functions. ** ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are ** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], @@ -2498,16 +2558,21 @@ SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64); ** */ SQLITE_API int sqlite3_changes(sqlite3*); +SQLITE_API sqlite3_int64 sqlite3_changes64(sqlite3*); /* ** CAPI3REF: Total Number Of Rows Modified ** METHOD: sqlite3 ** -** ^This function returns the total number of rows inserted, modified or +** ^These functions return the total number of rows inserted, modified or ** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed ** since the database connection was opened, including those executed as -** part of trigger programs. ^Executing any other type of SQL statement -** does not affect the value returned by sqlite3_total_changes(). +** part of trigger programs. The two functions are identical except for the +** type of the return value and that if the number of rows modified by the +** connection exceeds the maximum value supported by type "int", then +** the return value of sqlite3_total_changes() is undefined. ^Executing +** any other type of SQL statement does not affect the value returned by +** sqlite3_total_changes(). ** ** ^Changes made as part of [foreign key actions] are included in the ** count, but those made as part of REPLACE constraint resolution are @@ -2535,6 +2600,7 @@ SQLITE_API int sqlite3_changes(sqlite3*); ** */ SQLITE_API int sqlite3_total_changes(sqlite3*); +SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3*); /* ** CAPI3REF: Interrupt A Long-Running Query @@ -3364,6 +3430,14 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** the default shared cache setting provided by ** [sqlite3_enable_shared_cache()].)^ ** +** [[OPEN_EXRESCODE]] ^(
    [SQLITE_OPEN_EXRESCODE]
    +**
    The database connection comes up in "extended result code mode". +** In other words, the database behaves has if +** [sqlite3_extended_result_codes(db,1)] where called on the database +** connection as soon as the connection is created. In addition to setting +** the extended result code mode, this flag also causes [sqlite3_open_v2()] +** to return an extended result code.
    +** ** [[OPEN_NOFOLLOW]] ^(
    [SQLITE_OPEN_NOFOLLOW]
    **
    The database filename is not allowed to be a symbolic link
    ** )^ @@ -3371,7 +3445,15 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** If the 3rd parameter to sqlite3_open_v2() is not one of the ** required combinations shown above optionally combined with other ** [SQLITE_OPEN_READONLY | SQLITE_OPEN_* bits] -** then the behavior is undefined. +** then the behavior is undefined. Historic versions of SQLite +** have silently ignored surplus bits in the flags parameter to +** sqlite3_open_v2(), however that behavior might not be carried through +** into future versions of SQLite and so applications should not rely +** upon it. Note in particular that the SQLITE_OPEN_EXCLUSIVE flag is a no-op +** for sqlite3_open_v2(). The SQLITE_OPEN_EXCLUSIVE does *not* cause +** the open to fail if the database already exists. The SQLITE_OPEN_EXCLUSIVE +** flag is intended for use by the [sqlite3_vfs|VFS interface] only, and not +** by sqlite3_open_v2(). ** ** ^The fourth parameter to sqlite3_open_v2() is the name of the ** [sqlite3_vfs] object that defines the operating system interface that @@ -3742,13 +3824,14 @@ SQLITE_API void sqlite3_free_filename(char*); ** sqlite3_extended_errcode() might change with each API call. ** Except, there are some interfaces that are guaranteed to never ** change the value of the error code. The error-code preserving -** interfaces are: +** interfaces include the following: ** **
      **
    • sqlite3_errcode() **
    • sqlite3_extended_errcode() **
    • sqlite3_errmsg() **
    • sqlite3_errmsg16() +**
    • sqlite3_error_offset() **
    ** ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language @@ -3763,6 +3846,13 @@ SQLITE_API void sqlite3_free_filename(char*); ** ^(Memory to hold the error message string is managed internally ** and must not be freed by the application)^. ** +** ^If the most recent error references a specific token in the input +** SQL, the sqlite3_error_offset() interface returns the byte offset +** of the start of that token. ^The byte offset returned by +** sqlite3_error_offset() assumes that the input SQL is UTF8. +** ^If the most recent error does not reference a specific token in the input +** SQL, then the sqlite3_error_offset() function returns -1. +** ** When the serialized [threading mode] is in use, it might be the ** case that a second error occurs on a separate thread in between ** the time of the first error and the call to these interfaces. @@ -3782,6 +3872,7 @@ SQLITE_API int sqlite3_extended_errcode(sqlite3 *db); SQLITE_API const char *sqlite3_errmsg(sqlite3*); SQLITE_API const void *sqlite3_errmsg16(sqlite3*); SQLITE_API const char *sqlite3_errstr(int); +SQLITE_API int sqlite3_error_offset(sqlite3 *db); /* ** CAPI3REF: Prepared Statement Object @@ -4139,12 +4230,17 @@ SQLITE_API int sqlite3_prepare16_v3( ** are managed by SQLite and are automatically freed when the prepared ** statement is finalized. ** ^The string returned by sqlite3_expanded_sql(P), on the other hand, -** is obtained from [sqlite3_malloc()] and must be free by the application +** is obtained from [sqlite3_malloc()] and must be freed by the application ** by passing it to [sqlite3_free()]. +** +** ^The sqlite3_normalized_sql() interface is only available if +** the [SQLITE_ENABLE_NORMALIZE] compile-time option is defined. */ SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt); SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt); +#ifdef SQLITE_ENABLE_NORMALIZE SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt); +#endif /* ** CAPI3REF: Determine If An SQL Statement Writes The Database @@ -4179,6 +4275,19 @@ SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt); ** [BEGIN] merely sets internal flags, but the [BEGIN|BEGIN IMMEDIATE] and ** [BEGIN|BEGIN EXCLUSIVE] commands do touch the database and so ** sqlite3_stmt_readonly() returns false for those commands. +** +** ^This routine returns false if there is any possibility that the +** statement might change the database file. ^A false return does +** not guarantee that the statement will change the database file. +** ^For example, an UPDATE statement might have a WHERE clause that +** makes it a no-op, but the sqlite3_stmt_readonly() result would still +** be false. ^Similarly, a CREATE TABLE IF NOT EXISTS statement is a +** read-only no-op if the table already exists, but +** sqlite3_stmt_readonly() still returns false for such a statement. +** +** ^If prepared statement X is an [EXPLAIN] or [EXPLAIN QUERY PLAN] +** statement, then sqlite3_stmt_readonly(X) returns the same value as +** if the EXPLAIN or EXPLAIN QUERY PLAN prefix were omitted. */ SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); @@ -4247,6 +4356,8 @@ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*); ** ** ^The sqlite3_value objects that are passed as parameters into the ** implementation of [application-defined SQL functions] are protected. +** ^The sqlite3_value objects returned by [sqlite3_vtab_rhs_value()] +** are protected. ** ^The sqlite3_value object returned by ** [sqlite3_column_value()] is unprotected. ** Unprotected sqlite3_value objects may only be used as arguments @@ -4348,18 +4459,22 @@ typedef struct sqlite3_context sqlite3_context; ** contain embedded NULs. The result of expressions involving strings ** with embedded NULs is undefined. ** -** ^The fifth argument to the BLOB and string binding interfaces -** is a destructor used to dispose of the BLOB or -** string after SQLite has finished with it. ^The destructor is called -** to dispose of the BLOB or string even if the call to the bind API fails, -** except the destructor is not called if the third parameter is a NULL -** pointer or the fourth parameter is negative. -** ^If the fifth argument is -** the special value [SQLITE_STATIC], then SQLite assumes that the -** information is in static, unmanaged space and does not need to be freed. -** ^If the fifth argument has the value [SQLITE_TRANSIENT], then -** SQLite makes its own private copy of the data immediately, before -** the sqlite3_bind_*() routine returns. +** ^The fifth argument to the BLOB and string binding interfaces controls +** or indicates the lifetime of the object referenced by the third parameter. +** These three options exist: +** ^ (1) A destructor to dispose of the BLOB or string after SQLite has finished +** with it may be passed. ^It is called to dispose of the BLOB or string even +** if the call to the bind API fails, except the destructor is not called if +** the third parameter is a NULL pointer or the fourth parameter is negative. +** ^ (2) The special constant, [SQLITE_STATIC], may be passsed to indicate that +** the application remains responsible for disposing of the object. ^In this +** case, the object and the provided pointer to it must remain valid until +** either the prepared statement is finalized or the same SQL parameter is +** bound to something else, whichever occurs sooner. +** ^ (3) The constant, [SQLITE_TRANSIENT], may be passed to indicate that the +** object is to be copied prior to the return from sqlite3_bind_*(). ^The +** object and pointer to it must remain valid until then. ^SQLite will then +** manage the lifetime of its private copy. ** ** ^The sixth argument to sqlite3_bind_text64() must be one of ** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE] @@ -4864,6 +4979,10 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** even empty strings, are always zero-terminated. ^The return ** value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer. ** +** ^Strings returned by sqlite3_column_text16() always have the endianness +** which is native to the platform, regardless of the text encoding set +** for the database. +** ** Warning: ^The object returned by [sqlite3_column_value()] is an ** [unprotected sqlite3_value] object. In a multithreaded environment, ** an unprotected sqlite3_value object may only be used safely with @@ -4877,7 +4996,7 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** [application-defined SQL functions] or [virtual tables], not within ** top-level application code. ** -** The these routines may attempt to convert the datatype of the result. +** These routines may attempt to convert the datatype of the result. ** ^For example, if the internal representation is FLOAT and a text result ** is requested, [sqlite3_snprintf()] is used internally to perform the ** conversion automatically. ^(The following table details the conversions @@ -4902,7 +5021,7 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** TEXT BLOB No change ** BLOB INTEGER [CAST] to INTEGER ** BLOB FLOAT [CAST] to REAL -** BLOB TEXT Add a zero terminator if needed +** BLOB TEXT [CAST] to TEXT, ensure zero terminator ** ** )^ ** @@ -5101,7 +5220,6 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); ** within VIEWs, TRIGGERs, CHECK constraints, generated column expressions, ** index expressions, or the WHERE clause of partial indexes. ** -** ** For best security, the [SQLITE_DIRECTONLY] flag is recommended for ** all application-defined SQL functions that do not need to be ** used inside of triggers, view, CHECK constraints, or other elements of @@ -5111,7 +5229,6 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); ** a database file to include invocations of the function with parameters ** chosen by the attacker, which the application will then execute when ** the database file is opened and read. -** ** ** ^(The fifth parameter is an arbitrary pointer. The implementation of the ** function can gain access to this pointer using [sqlite3_user_data()].)^ @@ -5476,7 +5593,8 @@ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*); ** object D and returns a pointer to that copy. ^The [sqlite3_value] returned ** is a [protected sqlite3_value] object even if the input is not. ** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a -** memory allocation fails. +** memory allocation fails. ^If V is a [pointer value], then the result +** of sqlite3_value_dup(V) is a NULL value. ** ** ^The sqlite3_value_free(V) interface frees an [sqlite3_value] object ** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer @@ -6158,6 +6276,28 @@ SQLITE_API int sqlite3_get_autocommit(sqlite3*); */ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); +/* +** CAPI3REF: Return The Schema Name For A Database Connection +** METHOD: sqlite3 +** +** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name +** for the N-th database on database connection D, or a NULL pointer of N is +** out of range. An N value of 0 means the main database file. An N of 1 is +** the "temp" schema. Larger values of N correspond to various ATTACH-ed +** databases. +** +** Space to hold the string that is returned by sqlite3_db_name() is managed +** by SQLite itself. The string might be deallocated by any operation that +** changes the schema, including [ATTACH] or [DETACH] or calls to +** [sqlite3_serialize()] or [sqlite3_deserialize()], even operations that +** occur on a different thread. Applications that need to +** remember the string long-term should make their own copy. Applications that +** are accessing the same database connection simultaneously on multiple +** threads should mutex-protect calls to this API and should make their own +** private copy of the result prior to releasing the mutex. +*/ +SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N); + /* ** CAPI3REF: Return The Filename For A Database Connection ** METHOD: sqlite3 @@ -6317,6 +6457,72 @@ SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); +/* +** CAPI3REF: Autovacuum Compaction Amount Callback +** METHOD: sqlite3 +** +** ^The sqlite3_autovacuum_pages(D,C,P,X) interface registers a callback +** function C that is invoked prior to each autovacuum of the database +** file. ^The callback is passed a copy of the generic data pointer (P), +** the schema-name of the attached database that is being autovacuumed, +** the the size of the database file in pages, the number of free pages, +** and the number of bytes per page, respectively. The callback should +** return the number of free pages that should be removed by the +** autovacuum. ^If the callback returns zero, then no autovacuum happens. +** ^If the value returned is greater than or equal to the number of +** free pages, then a complete autovacuum happens. +** +**

    ^If there are multiple ATTACH-ed database files that are being +** modified as part of a transaction commit, then the autovacuum pages +** callback is invoked separately for each file. +** +**

    The callback is not reentrant. The callback function should +** not attempt to invoke any other SQLite interface. If it does, bad +** things may happen, including segmentation faults and corrupt database +** files. The callback function should be a simple function that +** does some arithmetic on its input parameters and returns a result. +** +** ^The X parameter to sqlite3_autovacuum_pages(D,C,P,X) is an optional +** destructor for the P parameter. ^If X is not NULL, then X(P) is +** invoked whenever the database connection closes or when the callback +** is overwritten by another invocation of sqlite3_autovacuum_pages(). +** +**

    ^There is only one autovacuum pages callback per database connection. +** ^Each call to the sqlite3_autovacuum_pages() interface overrides all +** previous invocations for that database connection. ^If the callback +** argument (C) to sqlite3_autovacuum_pages(D,C,P,X) is a NULL pointer, +** then the autovacuum steps callback is cancelled. The return value +** from sqlite3_autovacuum_pages() is normally SQLITE_OK, but might +** be some other error code if something goes wrong. The current +** implementation will only return SQLITE_OK or SQLITE_MISUSE, but other +** return codes might be added in future releases. +** +**

    If no autovacuum pages callback is specified (the usual case) or +** a NULL pointer is provided for the callback, +** then the default behavior is to vacuum all free pages. So, in other +** words, the default behavior is the same as if the callback function +** were something like this: +** +**

    +**     unsigned int demonstration_autovac_pages_callback(
    +**       void *pClientData,
    +**       const char *zSchema,
    +**       unsigned int nDbPage,
    +**       unsigned int nFreePage,
    +**       unsigned int nBytePerPage
    +**     ){
    +**       return nFreePage;
    +**     }
    +** 
    +*/ +SQLITE_API int sqlite3_autovacuum_pages( + sqlite3 *db, + unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), + void*, + void(*)(void*) +); + + /* ** CAPI3REF: Data Change Notification Callbacks ** METHOD: sqlite3 @@ -6958,24 +7164,56 @@ struct sqlite3_index_info { ** ** These macros define the allowed values for the ** [sqlite3_index_info].aConstraint[].op field. Each value represents -** an operator that is part of a constraint term in the wHERE clause of +** an operator that is part of a constraint term in the WHERE clause of ** a query that uses a [virtual table]. +** +** ^The left-hand operand of the operator is given by the corresponding +** aConstraint[].iColumn field. ^An iColumn of -1 indicates the left-hand +** operand is the rowid. +** The SQLITE_INDEX_CONSTRAINT_LIMIT and SQLITE_INDEX_CONSTRAINT_OFFSET +** operators have no left-hand operand, and so for those operators the +** corresponding aConstraint[].iColumn is meaningless and should not be +** used. +** +** All operator values from SQLITE_INDEX_CONSTRAINT_FUNCTION through +** value 255 are reserved to represent functions that are overloaded +** by the [xFindFunction|xFindFunction method] of the virtual table +** implementation. +** +** The right-hand operands for each constraint might be accessible using +** the [sqlite3_vtab_rhs_value()] interface. Usually the right-hand +** operand is only available if it appears as a single constant literal +** in the input SQL. If the right-hand operand is another column or an +** expression (even a constant expression) or a parameter, then the +** sqlite3_vtab_rhs_value() probably will not be able to extract it. +** ^The SQLITE_INDEX_CONSTRAINT_ISNULL and +** SQLITE_INDEX_CONSTRAINT_ISNOTNULL operators have no right-hand operand +** and hence calls to sqlite3_vtab_rhs_value() for those operators will +** always return SQLITE_NOTFOUND. +** +** The collating sequence to be used for comparison can be found using +** the [sqlite3_vtab_collation()] interface. For most real-world virtual +** tables, the collating sequence of constraints does not matter (for example +** because the constraints are numeric) and so the sqlite3_vtab_collation() +** interface is no commonly needed. */ -#define SQLITE_INDEX_CONSTRAINT_EQ 2 -#define SQLITE_INDEX_CONSTRAINT_GT 4 -#define SQLITE_INDEX_CONSTRAINT_LE 8 -#define SQLITE_INDEX_CONSTRAINT_LT 16 -#define SQLITE_INDEX_CONSTRAINT_GE 32 -#define SQLITE_INDEX_CONSTRAINT_MATCH 64 -#define SQLITE_INDEX_CONSTRAINT_LIKE 65 -#define SQLITE_INDEX_CONSTRAINT_GLOB 66 -#define SQLITE_INDEX_CONSTRAINT_REGEXP 67 -#define SQLITE_INDEX_CONSTRAINT_NE 68 -#define SQLITE_INDEX_CONSTRAINT_ISNOT 69 -#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 -#define SQLITE_INDEX_CONSTRAINT_ISNULL 71 -#define SQLITE_INDEX_CONSTRAINT_IS 72 -#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150 +#define SQLITE_INDEX_CONSTRAINT_EQ 2 +#define SQLITE_INDEX_CONSTRAINT_GT 4 +#define SQLITE_INDEX_CONSTRAINT_LE 8 +#define SQLITE_INDEX_CONSTRAINT_LT 16 +#define SQLITE_INDEX_CONSTRAINT_GE 32 +#define SQLITE_INDEX_CONSTRAINT_MATCH 64 +#define SQLITE_INDEX_CONSTRAINT_LIKE 65 +#define SQLITE_INDEX_CONSTRAINT_GLOB 66 +#define SQLITE_INDEX_CONSTRAINT_REGEXP 67 +#define SQLITE_INDEX_CONSTRAINT_NE 68 +#define SQLITE_INDEX_CONSTRAINT_ISNOT 69 +#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 +#define SQLITE_INDEX_CONSTRAINT_ISNULL 71 +#define SQLITE_INDEX_CONSTRAINT_IS 72 +#define SQLITE_INDEX_CONSTRAINT_LIMIT 73 +#define SQLITE_INDEX_CONSTRAINT_OFFSET 74 +#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150 /* ** CAPI3REF: Register A Virtual Table Implementation @@ -7004,7 +7242,7 @@ struct sqlite3_index_info { ** destructor. ** ** ^If the third parameter (the pointer to the sqlite3_module object) is -** NULL then no new module is create and any existing modules with the +** NULL then no new module is created and any existing modules with the ** same name are dropped. ** ** See also: [sqlite3_drop_modules()] @@ -7779,7 +8017,9 @@ SQLITE_API int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS 29 #define SQLITE_TESTCTRL_SEEK_COUNT 30 #define SQLITE_TESTCTRL_TRACEFLAGS 31 -#define SQLITE_TESTCTRL_LAST 31 /* Largest TESTCTRL */ +#define SQLITE_TESTCTRL_TUNE 32 +#define SQLITE_TESTCTRL_LOGEST 33 +#define SQLITE_TESTCTRL_LAST 33 /* Largest TESTCTRL */ /* ** CAPI3REF: SQL Keyword Checking @@ -8302,6 +8542,16 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); ** The counter is incremented on the first [sqlite3_step()] call of each ** cycle. ** +** [[SQLITE_STMTSTATUS_FILTER_MISS]] +** [[SQLITE_STMTSTATUS_FILTER HIT]] +**
    SQLITE_STMTSTATUS_FILTER_HIT
    +** SQLITE_STMTSTATUS_FILTER_MISS
    +**
    ^SQLITE_STMTSTATUS_FILTER_HIT is the number of times that a join +** step was bypassed because a Bloom filter returned not-found. The +** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of +** times that the Bloom filter returned a find, and thus the join step +** had to be processed as normal. +** ** [[SQLITE_STMTSTATUS_MEMUSED]]
    SQLITE_STMTSTATUS_MEMUSED
    **
    ^This is the approximate number of bytes of heap memory ** used to store the prepared statement. ^This value is not actually @@ -8316,6 +8566,8 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); #define SQLITE_STMTSTATUS_VM_STEP 4 #define SQLITE_STMTSTATUS_REPREPARE 5 #define SQLITE_STMTSTATUS_RUN 6 +#define SQLITE_STMTSTATUS_FILTER_MISS 7 +#define SQLITE_STMTSTATUS_FILTER_HIT 8 #define SQLITE_STMTSTATUS_MEMUSED 99 /* @@ -8979,8 +9231,9 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); ** ** A single database handle may have at most a single write-ahead log callback ** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any -** previously registered write-ahead log callback. ^Note that the -** [sqlite3_wal_autocheckpoint()] interface and the +** previously registered write-ahead log callback. ^The return value is +** a copy of the third parameter from the previous call, if any, or 0. +** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the ** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will ** overwrite any prior [sqlite3_wal_hook()] settings. */ @@ -9283,19 +9536,276 @@ SQLITE_API int sqlite3_vtab_nochange(sqlite3_context*); /* ** CAPI3REF: Determine The Collation For a Virtual Table Constraint +** METHOD: sqlite3_index_info ** ** This function may only be called from within a call to the [xBestIndex] -** method of a [virtual table]. +** method of a [virtual table]. This function returns a pointer to a string +** that is the name of the appropriate collation sequence to use for text +** comparisons on the constraint identified by its arguments. ** -** The first argument must be the sqlite3_index_info object that is the -** first parameter to the xBestIndex() method. The second argument must be -** an index into the aConstraint[] array belonging to the sqlite3_index_info -** structure passed to xBestIndex. This function returns a pointer to a buffer -** containing the name of the collation sequence for the corresponding -** constraint. +** The first argument must be the pointer to the [sqlite3_index_info] object +** that is the first parameter to the xBestIndex() method. The second argument +** must be an index into the aConstraint[] array belonging to the +** sqlite3_index_info structure passed to xBestIndex. +** +** Important: +** The first parameter must be the same pointer that is passed into the +** xBestMethod() method. The first parameter may not be a pointer to a +** different [sqlite3_index_info] object, even an exact copy. +** +** The return value is computed as follows: +** +**
      +**
    1. If the constraint comes from a WHERE clause expression that contains +** a [COLLATE operator], then the name of the collation specified by +** that COLLATE operator is returned. +**

    2. If there is no COLLATE operator, but the column that is the subject +** of the constraint specifies an alternative collating sequence via +** a [COLLATE clause] on the column definition within the CREATE TABLE +** statement that was passed into [sqlite3_declare_vtab()], then the +** name of that alternative collating sequence is returned. +**

    3. Otherwise, "BINARY" is returned. +**

    */ SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_info*,int); +/* +** CAPI3REF: Determine if a virtual table query is DISTINCT +** METHOD: sqlite3_index_info +** +** This API may only be used from within an [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this +** interface from outside of xBestIndex() is undefined and probably harmful. +** +** ^The sqlite3_vtab_distinct() interface returns an integer between 0 and +** 3. The integer returned by sqlite3_vtab_distinct() +** gives the virtual table additional information about how the query +** planner wants the output to be ordered. As long as the virtual table +** can meet the ordering requirements of the query planner, it may set +** the "orderByConsumed" flag. +** +**
    1. +** ^If the sqlite3_vtab_distinct() interface returns 0, that means +** that the query planner needs the virtual table to return all rows in the +** sort order defined by the "nOrderBy" and "aOrderBy" fields of the +** [sqlite3_index_info] object. This is the default expectation. If the +** virtual table outputs all rows in sorted order, then it is always safe for +** the xBestIndex method to set the "orderByConsumed" flag, regardless of +** the return value from sqlite3_vtab_distinct(). +**

    2. +** ^(If the sqlite3_vtab_distinct() interface returns 1, that means +** that the query planner does not need the rows to be returned in sorted order +** as long as all rows with the same values in all columns identified by the +** "aOrderBy" field are adjacent.)^ This mode is used when the query planner +** is doing a GROUP BY. +**

    3. +** ^(If the sqlite3_vtab_distinct() interface returns 2, that means +** that the query planner does not need the rows returned in any particular +** order, as long as rows with the same values in all "aOrderBy" columns +** are adjacent.)^ ^(Furthermore, only a single row for each particular +** combination of values in the columns identified by the "aOrderBy" field +** needs to be returned.)^ ^It is always ok for two or more rows with the same +** values in all "aOrderBy" columns to be returned, as long as all such rows +** are adjacent. ^The virtual table may, if it chooses, omit extra rows +** that have the same value for all columns identified by "aOrderBy". +** ^However omitting the extra rows is optional. +** This mode is used for a DISTINCT query. +**

    4. +** ^(If the sqlite3_vtab_distinct() interface returns 3, that means +** that the query planner needs only distinct rows but it does need the +** rows to be sorted.)^ ^The virtual table implementation is free to omit +** rows that are identical in all aOrderBy columns, if it wants to, but +** it is not required to omit any rows. This mode is used for queries +** that have both DISTINCT and ORDER BY clauses. +**

    +** +** ^For the purposes of comparing virtual table output values to see if the +** values are same value for sorting purposes, two NULL values are considered +** to be the same. In other words, the comparison operator is "IS" +** (or "IS NOT DISTINCT FROM") and not "==". +** +** If a virtual table implementation is unable to meet the requirements +** specified above, then it must not set the "orderByConsumed" flag in the +** [sqlite3_index_info] object or an incorrect answer may result. +** +** ^A virtual table implementation is always free to return rows in any order +** it wants, as long as the "orderByConsumed" flag is not set. ^When the +** the "orderByConsumed" flag is unset, the query planner will add extra +** [bytecode] to ensure that the final results returned by the SQL query are +** ordered correctly. The use of the "orderByConsumed" flag and the +** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful +** use of the sqlite3_vtab_distinct() interface and the "orderByConsumed" +** flag might help queries against a virtual table to run faster. Being +** overly aggressive and setting the "orderByConsumed" flag when it is not +** valid to do so, on the other hand, might cause SQLite to return incorrect +** results. +*/ +SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info*); + +/* +** CAPI3REF: Identify and handle IN constraints in xBestIndex +** +** This interface may only be used from within an +** [xBestIndex|xBestIndex() method] of a [virtual table] implementation. +** The result of invoking this interface from any other context is +** undefined and probably harmful. +** +** ^(A constraint on a virtual table of the form +** "[IN operator|column IN (...)]" is +** communicated to the xBestIndex method as a +** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use +** this constraint, it must set the corresponding +** aConstraintUsage[].argvIndex to a postive integer. ^(Then, under +** the usual mode of handling IN operators, SQLite generates [bytecode] +** that invokes the [xFilter|xFilter() method] once for each value +** on the right-hand side of the IN operator.)^ Thus the virtual table +** only sees a single value from the right-hand side of the IN operator +** at a time. +** +** In some cases, however, it would be advantageous for the virtual +** table to see all values on the right-hand of the IN operator all at +** once. The sqlite3_vtab_in() interfaces facilitates this in two ways: +** +**
      +**
    1. +** ^A call to sqlite3_vtab_in(P,N,-1) will return true (non-zero) +** if and only if the [sqlite3_index_info|P->aConstraint][N] constraint +** is an [IN operator] that can be processed all at once. ^In other words, +** sqlite3_vtab_in() with -1 in the third argument is a mechanism +** by which the virtual table can ask SQLite if all-at-once processing +** of the IN operator is even possible. +** +**

    2. +** ^A call to sqlite3_vtab_in(P,N,F) with F==1 or F==0 indicates +** to SQLite that the virtual table does or does not want to process +** the IN operator all-at-once, respectively. ^Thus when the third +** parameter (F) is non-negative, this interface is the mechanism by +** which the virtual table tells SQLite how it wants to process the +** IN operator. +**

    +** +** ^The sqlite3_vtab_in(P,N,F) interface can be invoked multiple times +** within the same xBestIndex method call. ^For any given P,N pair, +** the return value from sqlite3_vtab_in(P,N,F) will always be the same +** within the same xBestIndex call. ^If the interface returns true +** (non-zero), that means that the constraint is an IN operator +** that can be processed all-at-once. ^If the constraint is not an IN +** operator or cannot be processed all-at-once, then the interface returns +** false. +** +** ^(All-at-once processing of the IN operator is selected if both of the +** following conditions are met: +** +**
      +**
    1. The P->aConstraintUsage[N].argvIndex value is set to a positive +** integer. This is how the virtual table tells SQLite that it wants to +** use the N-th constraint. +** +**

    2. The last call to sqlite3_vtab_in(P,N,F) for which F was +** non-negative had F>=1. +**

    )^ +** +** ^If either or both of the conditions above are false, then SQLite uses +** the traditional one-at-a-time processing strategy for the IN constraint. +** ^If both conditions are true, then the argvIndex-th parameter to the +** xFilter method will be an [sqlite3_value] that appears to be NULL, +** but which can be passed to [sqlite3_vtab_in_first()] and +** [sqlite3_vtab_in_next()] to find all values on the right-hand side +** of the IN constraint. +*/ +SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle); + +/* +** CAPI3REF: Find all elements on the right-hand side of an IN constraint. +** +** These interfaces are only useful from within the +** [xFilter|xFilter() method] of a [virtual table] implementation. +** The result of invoking these interfaces from any other context +** is undefined and probably harmful. +** +** The X parameter in a call to sqlite3_vtab_in_first(X,P) or +** sqlite3_vtab_in_next(X,P) must be one of the parameters to the +** xFilter method which invokes these routines, and specifically +** a parameter that was previously selected for all-at-once IN constraint +** processing use the [sqlite3_vtab_in()] interface in the +** [xBestIndex|xBestIndex method]. ^(If the X parameter is not +** an xFilter argument that was selected for all-at-once IN constraint +** processing, then these routines return [SQLITE_MISUSE])^ or perhaps +** exhibit some other undefined or harmful behavior. +** +** ^(Use these routines to access all values on the right-hand side +** of the IN constraint using code like the following: +** +**
    +**    for(rc=sqlite3_vtab_in_first(pList, &pVal);
    +**        rc==SQLITE_OK && pVal
    +**        rc=sqlite3_vtab_in_next(pList, &pVal)
    +**    ){
    +**      // do something with pVal
    +**    }
    +**    if( rc!=SQLITE_OK ){
    +**      // an error has occurred
    +**    }
    +** 
    )^ +** +** ^On success, the sqlite3_vtab_in_first(X,P) and sqlite3_vtab_in_next(X,P) +** routines return SQLITE_OK and set *P to point to the first or next value +** on the RHS of the IN constraint. ^If there are no more values on the +** right hand side of the IN constraint, then *P is set to NULL and these +** routines return [SQLITE_DONE]. ^The return value might be +** some other value, such as SQLITE_NOMEM, in the event of a malfunction. +** +** The *ppOut values returned by these routines are only valid until the +** next call to either of these routines or until the end of the xFilter +** method from which these routines were called. If the virtual table +** implementation needs to retain the *ppOut values for longer, it must make +** copies. The *ppOut values are [protected sqlite3_value|protected]. +*/ +SQLITE_API int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut); +SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut); + +/* +** CAPI3REF: Constraint values in xBestIndex() +** METHOD: sqlite3_index_info +** +** This API may only be used from within the [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this interface +** from outside of an xBestIndex method are undefined and probably harmful. +** +** ^When the sqlite3_vtab_rhs_value(P,J,V) interface is invoked from within +** the [xBestIndex] method of a [virtual table] implementation, with P being +** a copy of the [sqlite3_index_info] object pointer passed into xBestIndex and +** J being a 0-based index into P->aConstraint[], then this routine +** attempts to set *V to the value of the right-hand operand of +** that constraint if the right-hand operand is known. ^If the +** right-hand operand is not known, then *V is set to a NULL pointer. +** ^The sqlite3_vtab_rhs_value(P,J,V) interface returns SQLITE_OK if +** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V) +** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th +** constraint is not available. ^The sqlite3_vtab_rhs_value() interface +** can return an result code other than SQLITE_OK or SQLITE_NOTFOUND if +** something goes wrong. +** +** The sqlite3_vtab_rhs_value() interface is usually only successful if +** the right-hand operand of a constraint is a literal value in the original +** SQL statement. If the right-hand operand is an expression or a reference +** to some other column or a [host parameter], then sqlite3_vtab_rhs_value() +** will probably return [SQLITE_NOTFOUND]. +** +** ^(Some constraints, such as [SQLITE_INDEX_CONSTRAINT_ISNULL] and +** [SQLITE_INDEX_CONSTRAINT_ISNOTNULL], have no right-hand operand. For such +** constraints, sqlite3_vtab_rhs_value() always returns SQLITE_NOTFOUND.)^ +** +** ^The [sqlite3_value] object returned in *V is a protected sqlite3_value +** and remains valid for the duration of the xBestIndex method call. +** ^When xBestIndex returns, the sqlite3_value object returned by +** sqlite3_vtab_rhs_value() is automatically deallocated. +** +** The "_rhs_" in the name of this routine is an abbreviation for +** "Right-Hand Side". +*/ +SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); + /* ** CAPI3REF: Conflict resolution modes ** KEYWORDS: {conflict resolution mode} @@ -9531,6 +10041,15 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** triggers; or 2 for changes resulting from triggers called by top-level ** triggers; and so forth. ** +** When the [sqlite3_blob_write()] API is used to update a blob column, +** the pre-update hook is invoked with SQLITE_DELETE. This is because the +** in this case the new values are not available. In this case, when a +** callback made with op==SQLITE_DELETE is actuall a write using the +** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns +** the index of the column being written. In other cases, where the +** pre-update hook is being invoked for some other reason, including a +** regular DELETE, sqlite3_preupdate_blobwrite() returns -1. +** ** See also: [sqlite3_update_hook()] */ #if defined(SQLITE_ENABLE_PREUPDATE_HOOK) @@ -9551,6 +10070,7 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); SQLITE_API int sqlite3_preupdate_count(sqlite3 *); SQLITE_API int sqlite3_preupdate_depth(sqlite3 *); SQLITE_API int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); +SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *); #endif /* @@ -9789,8 +10309,8 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c ** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory ** allocation error occurs. ** -** This interface is only available if SQLite is compiled with the -** [SQLITE_ENABLE_DESERIALIZE] option. +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. */ SQLITE_API unsigned char *sqlite3_serialize( sqlite3 *db, /* The database connection */ @@ -9837,12 +10357,16 @@ SQLITE_API unsigned char *sqlite3_serialize( ** database is currently in a read transaction or is involved in a backup ** operation. ** +** It is not possible to deserialized into the TEMP database. If the +** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the +** function returns SQLITE_ERROR. +** ** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the ** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then ** [sqlite3_free()] is invoked on argument P prior to returning. ** -** This interface is only available if SQLite is compiled with the -** [SQLITE_ENABLE_DESERIALIZE] option. +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. */ SQLITE_API int sqlite3_deserialize( sqlite3 *db, /* The database connection */ @@ -10091,6 +10615,38 @@ SQLITE_API int sqlite3session_create( */ SQLITE_API void sqlite3session_delete(sqlite3_session *pSession); +/* +** CAPIREF: Conigure a Session Object +** METHOD: sqlite3_session +** +** This method is used to configure a session object after it has been +** created. At present the only valid value for the second parameter is +** [SQLITE_SESSION_OBJCONFIG_SIZE]. +** +** Arguments for sqlite3session_object_config() +** +** The following values may passed as the the 4th parameter to +** sqlite3session_object_config(). +** +**
    SQLITE_SESSION_OBJCONFIG_SIZE
    +** This option is used to set, clear or query the flag that enables +** the [sqlite3session_changeset_size()] API. Because it imposes some +** computational overhead, this API is disabled by default. Argument +** pArg must point to a value of type (int). If the value is initially +** 0, then the sqlite3session_changeset_size() API is disabled. If it +** is greater than 0, then the same API is enabled. Or, if the initial +** value is less than zero, no change is made. In all cases the (int) +** variable is set to 1 if the sqlite3session_changeset_size() API is +** enabled following the current call, or 0 otherwise. +** +** It is an error (SQLITE_MISUSE) to attempt to modify this setting after +** the first table has been attached to the session object. +*/ +SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg); + +/* +*/ +#define SQLITE_SESSION_OBJCONFIG_SIZE 1 /* ** CAPI3REF: Enable Or Disable A Session Object @@ -10335,6 +10891,22 @@ SQLITE_API int sqlite3session_changeset( void **ppChangeset /* OUT: Buffer containing changeset */ ); +/* +** CAPI3REF: Return An Upper-limit For The Size Of The Changeset +** METHOD: sqlite3_session +** +** By default, this function always returns 0. For it to return +** a useful result, the sqlite3_session object must have been configured +** to enable this API using sqlite3session_object_config() with the +** SQLITE_SESSION_OBJCONFIG_SIZE verb. +** +** When enabled, this function returns an upper limit, in bytes, for the size +** of the changeset that might be produced if sqlite3session_changeset() were +** called. The final changeset size might be equal to or smaller than the +** size in bytes returned by this function. +*/ +SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession); + /* ** CAPI3REF: Load The Difference Between Tables Into A Session ** METHOD: sqlite3_session diff --git a/TMessagesProj/jni/tgnet/ApiScheme.cpp b/TMessagesProj/jni/tgnet/ApiScheme.cpp index 994a4aebc..5e7483978 100644 --- a/TMessagesProj/jni/tgnet/ApiScheme.cpp +++ b/TMessagesProj/jni/tgnet/ApiScheme.cpp @@ -358,6 +358,17 @@ TL_restrictionReason *TL_restrictionReason::TLdeserialize(NativeByteBuffer *stre return result; } +TL_username *TL_username::TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, int32_t instanceNum, bool &error) { + if (TL_username::constructor != constructor) { + error = true; + if (LOGS_ENABLED) DEBUG_FATAL("can't parse magic %x in TL_username", constructor); + return nullptr; + } + TL_username *result = new TL_username(); + result->readParams(stream, instanceNum, error); + return result; +} + void TL_restrictionReason::readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error) { platform = stream->readString(&error); reason = stream->readString(&error); @@ -371,6 +382,21 @@ void TL_restrictionReason::serializeToStream(NativeByteBuffer *stream) { stream->writeString(text); } +void TL_username::readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error) { + flags = stream->readInt32(&error); + editable = (flags & 1) != 0; + active = (flags & 2) != 0; + username = stream->readString(&error); +} + +void TL_username::serializeToStream(NativeByteBuffer *stream) { + stream->writeInt32(constructor); + flags = editable ? (flags | 1) : (flags &~ 1); + flags = active ? (flags | 2) : (flags &~ 2); + stream->writeInt32(flags); + stream->writeString(username); +} + User *User::TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, int32_t instanceNum, bool &error) { User *result = nullptr; switch (constructor) { @@ -400,6 +426,7 @@ void TL_userEmpty::serializeToStream(NativeByteBuffer *stream) { void TL_user::readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error) { flags = stream->readInt32(&error); + flags2 = stream->readInt32(&error); id = stream->readInt64(&error); if ((flags & 1) != 0) { access_hash = stream->readInt64(&error); @@ -447,6 +474,22 @@ void TL_user::readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &er if ((flags & 4194304) != 0) { lang_code = stream->readString(&error); } + if ((flags2 & 1) != 0) { + uint32_t magic = stream->readUint32(&error); + if (magic != 0x1cb5c415) { + error = true; + if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic, got %x", magic); + return; + } + int32_t count = stream->readInt32(&error); + for (int32_t a = 0; a < count; a++) { + TL_username *object = TL_username::TLdeserialize(stream, stream->readUint32(&error), instanceNum, error); + if (object == nullptr) { + return; + } + usernames.push_back(std::unique_ptr(object)); + } + } } void TL_user::serializeToStream(NativeByteBuffer *stream) { diff --git a/TMessagesProj/jni/tgnet/ApiScheme.h b/TMessagesProj/jni/tgnet/ApiScheme.h index bed653fc9..8f8a046e2 100644 --- a/TMessagesProj/jni/tgnet/ApiScheme.h +++ b/TMessagesProj/jni/tgnet/ApiScheme.h @@ -301,6 +301,20 @@ public: void serializeToStream(NativeByteBuffer *stream); }; +class TL_username : public TLObject { + +public: + static const uint32_t constructor = 0xb4073647; + int32_t flags; + bool editable; + bool active; + std::string username; + + static TL_username *TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, int32_t instanceNum, bool &error); + void readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error); + void serializeToStream(NativeByteBuffer *stream); +}; + class User : public TLObject { public: @@ -313,10 +327,12 @@ public: std::unique_ptr photo; std::unique_ptr status; int32_t flags; + int32_t flags2; int32_t bot_info_version; std::vector> restriction_reason; std::string bot_inline_placeholder; std::string lang_code; + std::vector> usernames; static User *TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, int32_t instanceNum, bool &error); }; @@ -333,7 +349,7 @@ public: class TL_user : public User { public: - static const uint32_t constructor = 0x5d99adee; + static const uint32_t constructor = 0x8f97c628; void readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error); void serializeToStream(NativeByteBuffer *stream); @@ -777,5 +793,4 @@ public: void serializeToStream(NativeByteBuffer *stream); }; - #endif diff --git a/TMessagesProj/jni/tgnet/ConnectionsManager.cpp b/TMessagesProj/jni/tgnet/ConnectionsManager.cpp index 6fdfd8a48..bd0b37803 100644 --- a/TMessagesProj/jni/tgnet/ConnectionsManager.cpp +++ b/TMessagesProj/jni/tgnet/ConnectionsManager.cpp @@ -1299,7 +1299,7 @@ void ConnectionsManager::processServerResponse(TLObject *message, int64_t messag int32_t waitTime = 2; static std::string floodWait = "FLOOD_WAIT_"; static std::string slowmodeWait = "SLOWMODE_WAIT_"; - discardResponse = true; + discardResponse = (request->requestFlags & RequestFlagIgnoreFloodWait) == 0; if (error->error_message.find(floodWait) != std::string::npos) { std::string num = error->error_message.substr(floodWait.size(), error->error_message.size() - floodWait.size()); waitTime = atoi(num.c_str()); diff --git a/TMessagesProj/jni/tgnet/Defines.h b/TMessagesProj/jni/tgnet/Defines.h index ed2e8a97a..20a1583fe 100644 --- a/TMessagesProj/jni/tgnet/Defines.h +++ b/TMessagesProj/jni/tgnet/Defines.h @@ -169,7 +169,8 @@ enum RequestFlag { RequestFlagInvokeAfter = 64, RequestFlagNeedQuickAck = 128, RequestFlagUseUnboundKey = 256, - RequestFlagResendAfter = 512 + RequestFlagResendAfter = 512, + RequestFlagIgnoreFloodWait = 1024 }; inline std::string to_string_int32(int32_t value) { diff --git a/TMessagesProj/src/main/assets/currencies.json b/TMessagesProj/src/main/assets/currencies.json index 732c551bf..25205f658 100644 --- a/TMessagesProj/src/main/assets/currencies.json +++ b/TMessagesProj/src/main/assets/currencies.json @@ -1 +1 @@ -{"AED":{"code":"AED","title":"United Arab Emirates Dirham","symbol":"AED","native":"د.إ.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"367","max_amount":"3673104"},"AFN":{"code":"AFN","title":"Afghan Afghani","symbol":"AFN","native":"؋","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"8943","max_amount":"89437089"},"ALL":{"code":"ALL","title":"Albanian Lek","symbol":"ALL","native":"Lek","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":false,"exp":2,"min_amount":"11415","max_amount":"114153729"},"AMD":{"code":"AMD","title":"Armenian Dram","symbol":"AMD","native":"դր.","thousands_sep":",","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"42405","max_amount":"424059434"},"ARS":{"code":"ARS","title":"Argentine Peso","symbol":"ARS","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"12287","max_amount":"122871702"},"AUD":{"code":"AUD","title":"Australian Dollar","symbol":"AU$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"144","max_amount":"1442793"},"AZN":{"code":"AZN","title":"Azerbaijani Manat","symbol":"AZN","native":"ман.","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"170","max_amount":"1703970"},"BAM":{"code":"BAM","title":"Bosnia & Herzegovina Convertible Mark","symbol":"BAM","native":"KM","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"186","max_amount":"1862873"},"BDT":{"code":"BDT","title":"Bangladeshi Taka","symbol":"BDT","native":"৳","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"9313","max_amount":"93138585"},"BGN":{"code":"BGN","title":"Bulgarian Lev","symbol":"BGN","native":"лв.","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"186","max_amount":"1862844"},"BND":{"code":"BND","title":"Brunei Dollar","symbol":"BND","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"138","max_amount":"1388608"},"BOB":{"code":"BOB","title":"Bolivian Boliviano","symbol":"BOB","native":"Bs","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"689","max_amount":"6898943"},"BRL":{"code":"BRL","title":"Brazilian Real","symbol":"R$","native":"R$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"515","max_amount":"5153919"},"CAD":{"code":"CAD","title":"Canadian Dollar","symbol":"CA$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"130","max_amount":"1303495"},"CHF":{"code":"CHF","title":"Swiss Franc","symbol":"CHF","native":"CHF","thousands_sep":"'","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"97","max_amount":"970848"},"CLP":{"code":"CLP","title":"Chilean Peso","symbol":"CLP","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":0,"min_amount":"876","max_amount":"8768039"},"CNY":{"code":"CNY","title":"Chinese Renminbi Yuan","symbol":"CN¥","native":"CN¥","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"671","max_amount":"6716704"},"COP":{"code":"COP","title":"Colombian Peso","symbol":"COP","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"390589","max_amount":"3905895800"},"CRC":{"code":"CRC","title":"Costa Rican Colón","symbol":"CRC","native":"₡","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"68499","max_amount":"684998571"},"CZK":{"code":"CZK","title":"Czech Koruna","symbol":"CZK","native":"Kč","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"2355","max_amount":"23555404"},"DKK":{"code":"DKK","title":"Danish Krone","symbol":"DKK","native":"kr","thousands_sep":"","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"708","max_amount":"7087040"},"DOP":{"code":"DOP","title":"Dominican Peso","symbol":"DOP","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"5485","max_amount":"54850938"},"DZD":{"code":"DZD","title":"Algerian Dinar","symbol":"DZD","native":"د.ج.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"14626","max_amount":"146263454"},"EGP":{"code":"EGP","title":"Egyptian Pound","symbol":"EGP","native":"ج.م.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"1874","max_amount":"18741785"},"EUR":{"code":"EUR","title":"Euro","symbol":"€","native":"€","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"95","max_amount":"952804"},"GBP":{"code":"GBP","title":"British Pound","symbol":"£","native":"£","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"81","max_amount":"817962"},"GEL":{"code":"GEL","title":"Georgian Lari","symbol":"GEL","native":"GEL","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"292","max_amount":"2925040"},"GTQ":{"code":"GTQ","title":"Guatemalan Quetzal","symbol":"GTQ","native":"Q","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"775","max_amount":"7750738"},"HKD":{"code":"HKD","title":"Hong Kong Dollar","symbol":"HK$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"785","max_amount":"7850055"},"HNL":{"code":"HNL","title":"Honduran Lempira","symbol":"HNL","native":"L","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"2461","max_amount":"24617583"},"HRK":{"code":"HRK","title":"Croatian Kuna","symbol":"HRK","native":"kn","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"716","max_amount":"7164904"},"HUF":{"code":"HUF","title":"Hungarian Forint","symbol":"HUF","native":"Ft","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"38098","max_amount":"380982504"},"IDR":{"code":"IDR","title":"Indonesian Rupiah","symbol":"IDR","native":"Rp","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"1483885","max_amount":"14838850000"},"ILS":{"code":"ILS","title":"Israeli New Sheqel","symbol":"₪","native":"₪","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"346","max_amount":"3462015"},"INR":{"code":"INR","title":"Indian Rupee","symbol":"₹","native":"₹","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"7795","max_amount":"77951504"},"ISK":{"code":"ISK","title":"Icelandic Króna","symbol":"ISK","native":"kr","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":0,"min_amount":"131","max_amount":"1311903"},"JMD":{"code":"JMD","title":"Jamaican Dollar","symbol":"JMD","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"15254","max_amount":"152544052"},"JPY":{"code":"JPY","title":"Japanese Yen","symbol":"¥","native":"¥","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":0,"min_amount":"134","max_amount":"1349925"},"KES":{"code":"KES","title":"Kenyan Shilling","symbol":"KES","native":"Ksh","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"11763","max_amount":"117638156"},"KGS":{"code":"KGS","title":"Kyrgyzstani Som","symbol":"KGS","native":"KGS","thousands_sep":" ","decimal_sep":"-","symbol_left":false,"space_between":true,"exp":2,"min_amount":"7950","max_amount":"79501104"},"KRW":{"code":"KRW","title":"South Korean Won","symbol":"₩","native":"₩","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":0,"min_amount":"1291","max_amount":"12915037"},"KZT":{"code":"KZT","title":"Kazakhstani Tenge","symbol":"KZT","native":"₸","thousands_sep":" ","decimal_sep":"-","symbol_left":true,"space_between":false,"exp":2,"min_amount":"44915","max_amount":"449157063"},"LBP":{"code":"LBP","title":"Lebanese Pound","symbol":"LBP","native":"ل.ل.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"151528","max_amount":"1515287170"},"LKR":{"code":"LKR","title":"Sri Lankan Rupee","symbol":"LKR","native":"රු.","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"35972","max_amount":"359728355"},"MAD":{"code":"MAD","title":"Moroccan Dirham","symbol":"MAD","native":"د.م.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"1009","max_amount":"10093437"},"MDL":{"code":"MDL","title":"Moldovan Leu","symbol":"MDL","native":"MDL","thousands_sep":",","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1928","max_amount":"19284027"},"MNT":{"code":"MNT","title":"Mongolian Tögrög","symbol":"MNT","native":"MNT","thousands_sep":" ","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"311718","max_amount":"3117185198"},"MUR":{"code":"MUR","title":"Mauritian Rupee","symbol":"MUR","native":"MUR","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"4495","max_amount":"44956663"},"MVR":{"code":"MVR","title":"Maldivian Rufiyaa","symbol":"MVR","native":"MVR","thousands_sep":",","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1540","max_amount":"15403741"},"MXN":{"code":"MXN","title":"Mexican Peso","symbol":"MX$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"2033","max_amount":"20338950"},"MYR":{"code":"MYR","title":"Malaysian Ringgit","symbol":"MYR","native":"RM","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"440","max_amount":"4402039"},"MZN":{"code":"MZN","title":"Mozambican Metical","symbol":"MZN","native":"MTn","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"6383","max_amount":"63830377"},"NGN":{"code":"NGN","title":"Nigerian Naira","symbol":"NGN","native":"₦","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"41537","max_amount":"415370377"},"NIO":{"code":"NIO","title":"Nicaraguan Córdoba","symbol":"NIO","native":"C$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"3592","max_amount":"35928184"},"NOK":{"code":"NOK","title":"Norwegian Krone","symbol":"NOK","native":"kr","thousands_sep":" ","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"1001","max_amount":"10013205"},"NPR":{"code":"NPR","title":"Nepalese Rupee","symbol":"NPR","native":"नेरू","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"12511","max_amount":"125113820"},"NZD":{"code":"NZD","title":"New Zealand Dollar","symbol":"NZ$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"158","max_amount":"1585666"},"PAB":{"code":"PAB","title":"Panamanian Balboa","symbol":"PAB","native":"B\/.","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"100","max_amount":"1002000"},"PEN":{"code":"PEN","title":"Peruvian Nuevo Sol","symbol":"PEN","native":"S\/.","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"371","max_amount":"3718830"},"PHP":{"code":"PHP","title":"Philippine Peso","symbol":"PHP","native":"₱","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"5376","max_amount":"53760375"},"PKR":{"code":"PKR","title":"Pakistani Rupee","symbol":"PKR","native":"₨","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"20967","max_amount":"209674540"},"PLN":{"code":"PLN","title":"Polish Złoty","symbol":"PLN","native":"zł","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"445","max_amount":"4457704"},"PYG":{"code":"PYG","title":"Paraguayan Guaraní","symbol":"PYG","native":"₲","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":0,"min_amount":"6873","max_amount":"68730355"},"QAR":{"code":"QAR","title":"Qatari Riyal","symbol":"QAR","native":"ر.ق.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"364","max_amount":"3641038"},"RON":{"code":"RON","title":"Romanian Leu","symbol":"RON","native":"RON","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"471","max_amount":"4711804"},"RSD":{"code":"RSD","title":"Serbian Dinar","symbol":"RSD","native":"дин.","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"11195","max_amount":"111950662"},"RUB":{"code":"RUB","title":"Russian Ruble","symbol":"RUB","native":"руб.","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"5749","max_amount":"57499904"},"SAR":{"code":"SAR","title":"Saudi Riyal","symbol":"SAR","native":"ر.س.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"375","max_amount":"3751352"},"SEK":{"code":"SEK","title":"Swedish Krona","symbol":"SEK","native":"kr","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1022","max_amount":"10229465"},"SGD":{"code":"SGD","title":"Singapore Dollar","symbol":"SGD","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"139","max_amount":"1390604"},"THB":{"code":"THB","title":"Thai Baht","symbol":"฿","native":"฿","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"3523","max_amount":"35232038"},"TJS":{"code":"TJS","title":"Tajikistani Somoni","symbol":"TJS","native":"TJS","thousands_sep":" ","decimal_sep":";","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1106","max_amount":"11060101"},"TRY":{"code":"TRY","title":"Turkish Lira","symbol":"TRY","native":"TL","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1732","max_amount":"17328404"},"TTD":{"code":"TTD","title":"Trinidad and Tobago Dollar","symbol":"TTD","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"680","max_amount":"6808077"},"TWD":{"code":"TWD","title":"New Taiwan Dollar","symbol":"NT$","native":"NT$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"2971","max_amount":"29716504"},"TZS":{"code":"TZS","title":"Tanzanian Shilling","symbol":"TZS","native":"TSh","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"233672","max_amount":"2336727307"},"UAH":{"code":"UAH","title":"Ukrainian Hryvnia","symbol":"UAH","native":"₴","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":false,"exp":2,"min_amount":"2960","max_amount":"29603677"},"UGX":{"code":"UGX","title":"Ugandan Shilling","symbol":"UGX","native":"USh","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":0,"min_amount":"3747","max_amount":"37475950"},"USD":{"code":"USD","title":"United States Dollar","symbol":"$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"100","max_amount":1000000},"UYU":{"code":"UYU","title":"Uruguayan Peso","symbol":"UYU","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"4018","max_amount":"40180970"},"UZS":{"code":"UZS","title":"Uzbekistani Som","symbol":"UZS","native":"UZS","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1099173","max_amount":"10991732546"},"VND":{"code":"VND","title":"Vietnamese Đồng","symbol":"₫","native":"₫","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":0,"min_amount":"23230","max_amount":"232300000"},"YER":{"code":"YER","title":"Yemeni Rial","symbol":"YER","native":"ر.ي.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"25025","max_amount":"250250364"},"ZAR":{"code":"ZAR","title":"South African Rand","symbol":"ZAR","native":"R","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"1602","max_amount":"16028160"}} \ No newline at end of file +{"AED":{"code":"AED","title":"United Arab Emirates Dirham","symbol":"AED","native":"د.إ.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"367","max_amount":"3673095"},"AFN":{"code":"AFN","title":"Afghan Afghani","symbol":"AFN","native":"؋","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"8729","max_amount":"87299487"},"ALL":{"code":"ALL","title":"Albanian Lek","symbol":"ALL","native":"Lek","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":false,"exp":2,"min_amount":"11842","max_amount":"118421187"},"AMD":{"code":"AMD","title":"Armenian Dram","symbol":"AMD","native":"դր.","thousands_sep":",","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"40943","max_amount":"409436718"},"ARS":{"code":"ARS","title":"Argentine Peso","symbol":"ARS","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"14706","max_amount":"147065012"},"AUD":{"code":"AUD","title":"Australian Dollar","symbol":"AU$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"154","max_amount":"1541535"},"AZN":{"code":"AZN","title":"Azerbaijani Manat","symbol":"AZN","native":"ман.","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"170","max_amount":"1702860"},"BAM":{"code":"BAM","title":"Bosnia & Herzegovina Convertible Mark","symbol":"BAM","native":"KM","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"199","max_amount":"1990677"},"BDT":{"code":"BDT","title":"Bangladeshi Taka","symbol":"BDT","native":"৳","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"10009","max_amount":"100094046"},"BGN":{"code":"BGN","title":"Bulgarian Lev","symbol":"BGN","native":"лв.","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"199","max_amount":"1993852"},"BND":{"code":"BND","title":"Brunei Dollar","symbol":"BND","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"142","max_amount":"1421883"},"BOB":{"code":"BOB","title":"Bolivian Boliviano","symbol":"BOB","native":"Bs","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"683","max_amount":"6832607"},"BRL":{"code":"BRL","title":"Brazilian Real","symbol":"R$","native":"R$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"539","max_amount":"5397297"},"BYN":{"code":"BYN","title":"Belarusian ruble","symbol":"BYN","native":"BYN","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"249","max_amount":"2495801"},"CAD":{"code":"CAD","title":"Canadian Dollar","symbol":"CA$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"137","max_amount":"1371480"},"CHF":{"code":"CHF","title":"Swiss Franc","symbol":"CHF","native":"CHF","thousands_sep":"'","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"97","max_amount":"976430"},"CLP":{"code":"CLP","title":"Chilean Peso","symbol":"CLP","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":0,"min_amount":"963","max_amount":"9637500"},"CNY":{"code":"CNY","title":"Chinese Renminbi Yuan","symbol":"CN¥","native":"CN¥","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"711","max_amount":"7113300"},"COP":{"code":"COP","title":"Colombian Peso","symbol":"COP","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"452870","max_amount":"4528700000"},"CRC":{"code":"CRC","title":"Costa Rican Colón","symbol":"CRC","native":"₡","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"62506","max_amount":"625061070"},"CZK":{"code":"CZK","title":"Czech Koruna","symbol":"CZK","native":"Kč","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"2503","max_amount":"25036400"},"DKK":{"code":"DKK","title":"Danish Krone","symbol":"DKK","native":"kr","thousands_sep":"","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"758","max_amount":"7587520"},"DOP":{"code":"DOP","title":"Dominican Peso","symbol":"DOP","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"5275","max_amount":"52757308"},"DZD":{"code":"DZD","title":"Algerian Dinar","symbol":"DZD","native":"د.ج.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"14035","max_amount":"140351015"},"EGP":{"code":"EGP","title":"Egyptian Pound","symbol":"EGP","native":"ج.م.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"1954","max_amount":"19544296"},"ETB":{"code":"ETB","title":"Ethiopian Birr","symbol":"ETB","native":"ብር","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"5224","max_amount":"52247069"},"EUR":{"code":"EUR","title":"Euro","symbol":"€","native":"€","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"102","max_amount":"1020295"},"GBP":{"code":"GBP","title":"British Pound","symbol":"£","native":"£","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"90","max_amount":"901955"},"GEL":{"code":"GEL","title":"Georgian Lari","symbol":"GEL","native":"GEL","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"283","max_amount":"2830146"},"GTQ":{"code":"GTQ","title":"Guatemalan Quetzal","symbol":"GTQ","native":"Q","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"778","max_amount":"7781323"},"HKD":{"code":"HKD","title":"Hong Kong Dollar","symbol":"HK$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"784","max_amount":"7849850"},"HNL":{"code":"HNL","title":"Honduran Lempira","symbol":"HNL","native":"L","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"2441","max_amount":"24413729"},"HRK":{"code":"HRK","title":"Croatian Kuna","symbol":"HRK","native":"kn","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"768","max_amount":"7681499"},"HUF":{"code":"HUF","title":"Hungarian Forint","symbol":"HUF","native":"Ft","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"42967","max_amount":"429670063"},"IDR":{"code":"IDR","title":"Indonesian Rupiah","symbol":"IDR","native":"Rp","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"1523720","max_amount":"15237200000"},"ILS":{"code":"ILS","title":"Israeli New Sheqel","symbol":"₪","native":"₪","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"356","max_amount":"3564105"},"INR":{"code":"INR","title":"Indian Rupee","symbol":"₹","native":"₹","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"8155","max_amount":"81553202"},"ISK":{"code":"ISK","title":"Icelandic Króna","symbol":"ISK","native":"kr","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":0,"min_amount":"143","max_amount":"1431502"},"JMD":{"code":"JMD","title":"Jamaican Dollar","symbol":"JMD","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"15013","max_amount":"150131044"},"JPY":{"code":"JPY","title":"Japanese Yen","symbol":"¥","native":"¥","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":0,"min_amount":"144","max_amount":"1447309"},"KES":{"code":"KES","title":"Kenyan Shilling","symbol":"KES","native":"Ksh","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"12080","max_amount":"120800507"},"KGS":{"code":"KGS","title":"Kyrgyzstani Som","symbol":"KGS","native":"KGS","thousands_sep":" ","decimal_sep":"-","symbol_left":false,"space_between":true,"exp":2,"min_amount":"8018","max_amount":"80182900"},"KRW":{"code":"KRW","title":"South Korean Won","symbol":"₩","native":"₩","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":0,"min_amount":"1430","max_amount":"14308699"},"KZT":{"code":"KZT","title":"Kazakhstani Tenge","symbol":"KZT","native":"₸","thousands_sep":" ","decimal_sep":"-","symbol_left":true,"space_between":false,"exp":2,"min_amount":"47186","max_amount":"471867112"},"LBP":{"code":"LBP","title":"Lebanese Pound","symbol":"LBP","native":"ل.ل.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"150999","max_amount":"1509998778"},"LKR":{"code":"LKR","title":"Sri Lankan Rupee","symbol":"LKR","native":"රු.","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"35546","max_amount":"355469651"},"MAD":{"code":"MAD","title":"Moroccan Dirham","symbol":"MAD","native":"د.م.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"1093","max_amount":"10938136"},"MDL":{"code":"MDL","title":"Moldovan Leu","symbol":"MDL","native":"MDL","thousands_sep":",","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1926","max_amount":"19264439"},"MNT":{"code":"MNT","title":"Mongolian Tögrög","symbol":"MNT","native":"MNT","thousands_sep":" ","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"322444","max_amount":"3224442056"},"MUR":{"code":"MUR","title":"Mauritian Rupee","symbol":"MUR","native":"MUR","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"4514","max_amount":"45142537"},"MVR":{"code":"MVR","title":"Maldivian Rufiyaa","symbol":"MVR","native":"MVR","thousands_sep":",","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1545","max_amount":"15459951"},"MXN":{"code":"MXN","title":"Mexican Peso","symbol":"MX$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"2018","max_amount":"20186299"},"MYR":{"code":"MYR","title":"Malaysian Ringgit","symbol":"MYR","native":"RM","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"464","max_amount":"4641025"},"MZN":{"code":"MZN","title":"Mozambican Metical","symbol":"MZN","native":"MTn","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"6382","max_amount":"63829680"},"NGN":{"code":"NGN","title":"Nigerian Naira","symbol":"NGN","native":"₦","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"43203","max_amount":"432039540"},"NIO":{"code":"NIO","title":"Nicaraguan Córdoba","symbol":"NIO","native":"C$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"3556","max_amount":"35566412"},"NOK":{"code":"NOK","title":"Norwegian Krone","symbol":"NOK","native":"kr","thousands_sep":" ","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"1072","max_amount":"10724875"},"NPR":{"code":"NPR","title":"Nepalese Rupee","symbol":"NPR","native":"नेरू","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"12954","max_amount":"129546657"},"NZD":{"code":"NZD","title":"New Zealand Dollar","symbol":"NZ$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"174","max_amount":"1748790"},"PAB":{"code":"PAB","title":"Panamanian Balboa","symbol":"PAB","native":"B\/.","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"98","max_amount":"988722"},"PEN":{"code":"PEN","title":"Peruvian Nuevo Sol","symbol":"PEN","native":"S\/.","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"390","max_amount":"3902208"},"PHP":{"code":"PHP","title":"Philippine Peso","symbol":"PHP","native":"₱","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"5865","max_amount":"58658497"},"PKR":{"code":"PKR","title":"Pakistani Rupee","symbol":"PKR","native":"₨","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"22642","max_amount":"226426805"},"PLN":{"code":"PLN","title":"Polish Złoty","symbol":"PLN","native":"zł","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"495","max_amount":"4950459"},"PYG":{"code":"PYG","title":"Paraguayan Guaraní","symbol":"PYG","native":"₲","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":0,"min_amount":"6977","max_amount":"69772317"},"QAR":{"code":"QAR","title":"Qatari Riyal","symbol":"QAR","native":"ر.ق.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"364","max_amount":"3641013"},"RON":{"code":"RON","title":"Romanian Leu","symbol":"RON","native":"RON","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"504","max_amount":"5049800"},"RSD":{"code":"RSD","title":"Serbian Dinar","symbol":"RSD","native":"дин.","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"11968","max_amount":"119684977"},"RUB":{"code":"RUB","title":"Russian Ruble","symbol":"RUB","native":"₽","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"7146","max_amount":"71468755"},"SAR":{"code":"SAR","title":"Saudi Riyal","symbol":"SAR","native":"ر.س.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"375","max_amount":"3758389"},"SEK":{"code":"SEK","title":"Swedish Krona","symbol":"SEK","native":"kr","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1118","max_amount":"11187065"},"SGD":{"code":"SGD","title":"Singapore Dollar","symbol":"SGD","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"143","max_amount":"1433860"},"THB":{"code":"THB","title":"Thai Baht","symbol":"฿","native":"฿","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"3798","max_amount":"37980185"},"TJS":{"code":"TJS","title":"Tajikistani Somoni","symbol":"TJS","native":"TJS","thousands_sep":" ","decimal_sep":";","symbol_left":false,"space_between":true,"exp":2,"min_amount":"975","max_amount":"9753854"},"TRY":{"code":"TRY","title":"Turkish Lira","symbol":"TRY","native":"TL","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1853","max_amount":"18536199"},"TTD":{"code":"TTD","title":"Trinidad and Tobago Dollar","symbol":"TTD","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"672","max_amount":"6722681"},"TWD":{"code":"TWD","title":"New Taiwan Dollar","symbol":"NT$","native":"NT$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"3174","max_amount":"31744503"},"TZS":{"code":"TZS","title":"Tanzanian Shilling","symbol":"TZS","native":"TSh","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"233200","max_amount":"2332000058"},"UAH":{"code":"UAH","title":"Ukrainian Hryvnia","symbol":"UAH","native":"₴","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":false,"exp":2,"min_amount":"3633","max_amount":"36338830"},"UGX":{"code":"UGX","title":"Ugandan Shilling","symbol":"UGX","native":"USh","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":0,"min_amount":"3816","max_amount":"38166726"},"USD":{"code":"USD","title":"United States Dollar","symbol":"$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"100","max_amount":1000000},"UYU":{"code":"UYU","title":"Uruguayan Peso","symbol":"UYU","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"4080","max_amount":"40801205"},"UZS":{"code":"UZS","title":"Uzbekistani Som","symbol":"UZS","native":"UZS","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1089058","max_amount":"10890583014"},"VND":{"code":"VND","title":"Vietnamese Đồng","symbol":"₫","native":"₫","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":0,"min_amount":"23870","max_amount":"238700000"},"YER":{"code":"YER","title":"Yemeni Rial","symbol":"YER","native":"ر.ي.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"25031","max_amount":"250319621"},"ZAR":{"code":"ZAR","title":"South African Rand","symbol":"ZAR","native":"R","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"1801","max_amount":"18018198"}} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java index e955374f6..83547b5b8 100644 --- a/TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java +++ b/TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java @@ -4542,7 +4542,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, final int childCount = mChildHelper.getUnfilteredChildCount(); for (int i = 0; i < childCount; i++) { final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); - if (!holder.shouldIgnore()) { + if (holder != null && !holder.shouldIgnore()) { holder.clearOldPosition(); } } @@ -11645,7 +11645,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, */ @Deprecated public int getViewPosition() { - return mViewHolder.getPosition(); + return mViewHolder == null ? RecyclerView.NO_POSITION : mViewHolder.getPosition(); } /** @@ -11655,7 +11655,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, * @return the adapter position this view as of latest layout pass */ public int getViewLayoutPosition() { - return mViewHolder.getLayoutPosition(); + return mViewHolder == null ? RecyclerView.NO_POSITION : mViewHolder.getLayoutPosition(); } /** @@ -11667,7 +11667,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, * its up-to-date position cannot be calculated. */ public int getViewAdapterPosition() { - return mViewHolder.getAdapterPosition(); + return mViewHolder == null ? RecyclerView.NO_POSITION :mViewHolder.getAdapterPosition(); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java index 0f4c9d9e8..db1e17980 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java @@ -10,8 +10,6 @@ package org.telegram.messenger; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.Activity; @@ -55,6 +53,8 @@ import android.provider.CallLog; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.provider.Settings; +import android.system.ErrnoException; +import android.system.OsConstants; import android.telephony.TelephonyManager; import android.text.Layout; import android.text.Selection; @@ -66,6 +66,7 @@ import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.text.method.LinkMovementMethod; +import android.text.style.CharacterStyle; import android.text.style.ClickableSpan; import android.text.style.URLSpan; import android.text.util.Linkify; @@ -122,10 +123,10 @@ import org.telegram.messenger.utils.CustomHtml; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; -import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.TextDetailSettingsCell; import org.telegram.ui.Components.AlertsCreator; @@ -172,6 +173,7 @@ import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -179,6 +181,9 @@ import java.util.regex.Pattern; public class AndroidUtilities { public final static int LIGHT_STATUS_BAR_OVERLAY = 0x0f000000, DARK_STATUS_BAR_OVERLAY = 0x33000000; + public final static int REPLACING_TAG_TYPE_LINK = 0; + public final static int REPLACING_TAG_TYPE_BOLD = 1; + public final static String TYPEFACE_ROBOTO_MEDIUM = "fonts/rmedium.ttf"; private static final Hashtable typefaceCache = new Hashtable<>(); @@ -421,10 +426,10 @@ public class AndroidUtilities { } public static CharSequence replaceSingleTag(String str, Runnable runnable) { - return replaceSingleTag(str, null, runnable); + return replaceSingleTag(str, null, 0, runnable); } - public static CharSequence replaceSingleTag(String str, String colorKey, Runnable runnable) { + public static CharSequence replaceSingleTag(String str, String colorKey, int type, Runnable runnable) { int startIndex = str.indexOf("**"); int endIndex = str.indexOf("**", startIndex + 1); str = str.replace("**", ""); @@ -436,24 +441,36 @@ public class AndroidUtilities { } SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(str); if (index >= 0) { - spannableStringBuilder.setSpan(new ClickableSpan() { + if (type == REPLACING_TAG_TYPE_LINK) { + spannableStringBuilder.setSpan(new ClickableSpan() { - @Override - public void updateDrawState(@NonNull TextPaint ds) { - super.updateDrawState(ds); - ds.setUnderlineText(false); - if (colorKey != null) { - ds.setColor(Theme.getColor(colorKey)); + @Override + public void updateDrawState(@NonNull TextPaint ds) { + super.updateDrawState(ds); + ds.setUnderlineText(false); + if (colorKey != null) { + ds.setColor(Theme.getColor(colorKey)); + } } - } - @Override - public void onClick(@NonNull View view) { - if (runnable != null) { - runnable.run(); + @Override + public void onClick(@NonNull View view) { + if (runnable != null) { + runnable.run(); + } } - } - }, index, index + len, 0); + }, index, index + len, 0); + } else { + spannableStringBuilder.setSpan(new CharacterStyle() { + @Override + public void updateDrawState(TextPaint textPaint) { + textPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + int wasAlpha = textPaint.getAlpha(); + textPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText)); + textPaint.setAlpha(wasAlpha); + } + }, index, index + len, 0); + } } return spannableStringBuilder; } @@ -705,6 +722,9 @@ public class AndroidUtilities { } public static int calcBitmapColor(Bitmap bitmap) { + if (bitmap == null) { + return 0; + } try { Bitmap b = Bitmaps.createScaledBitmap(bitmap, 1, 1, true); if (b != null) { @@ -866,6 +886,30 @@ public class AndroidUtilities { })); } + public static void adjustHueColorMatrix(ColorMatrix cm, float value) { + value = cleanValue(value, 180f) / 180f * (float) Math.PI; + if (value == 0) { + return; + } + float cosVal = (float) Math.cos(value); + float sinVal = (float) Math.sin(value); + float lumR = 0.213f; + float lumG = 0.715f; + float lumB = 0.072f; + float[] mat = new float[] + { + lumR + cosVal * (1 - lumR) + sinVal * (-lumR), lumG + cosVal * (-lumG) + sinVal * (-lumG), lumB + cosVal * (-lumB) + sinVal * (1 - lumB), 0, 0, + lumR + cosVal * (-lumR) + sinVal * (0.143f), lumG + cosVal * (1 - lumG) + sinVal * (0.140f), lumB + cosVal * (-lumB) + sinVal * (-0.283f), 0, 0, + lumR + cosVal * (-lumR) + sinVal * (-(1 - lumR)), lumG + cosVal * (-lumG) + sinVal * (lumG), lumB + cosVal * (1 - lumB) + sinVal * (lumB), 0, 0, + 0f, 0f, 0f, 1f, 0f, + 0f, 0f, 0f, 0f, 1f}; + cm.postConcat(new ColorMatrix(mat)); + } + + protected static float cleanValue(float p_val, float p_limit) { + return Math.min(p_limit, Math.max(-p_limit, p_val)); + } + public static void multiplyBrightnessColorMatrix(ColorMatrix colorMatrix, float v) { if (colorMatrix == null) { return; @@ -2026,9 +2070,13 @@ public class AndroidUtilities { return ch == '-' || ch == '~'; } + public static boolean isTabletForce() { + return ApplicationLoader.applicationContext != null && ApplicationLoader.applicationContext.getResources().getBoolean(R.bool.isTablet); + } + public static boolean isTabletInternal() { if (isTablet == null) { - isTablet = ApplicationLoader.applicationContext != null && ApplicationLoader.applicationContext.getResources().getBoolean(R.bool.isTablet); + isTablet = isTabletForce(); } return isTablet; } @@ -2471,24 +2519,29 @@ public class AndroidUtilities { uptime + 5 < SharedConfig.lastPauseTime); } - public static void shakeView(final View view, final float x, final int num) { + public static void shakeView(final View view) { if (view == null) { return; } - if (num == 6) { - view.setTranslationX(0); - return; + final float N = 4; + Object animator = view.getTag(R.id.shake_animation); + if (animator instanceof ValueAnimator) { + ((ValueAnimator) animator).cancel(); } - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(ObjectAnimator.ofFloat(view, "translationX", dp(x))); - animatorSet.setDuration(50); - animatorSet.addListener(new AnimatorListenerAdapter() { + ValueAnimator va = ValueAnimator.ofFloat(0, 1); + va.addUpdateListener(anm -> { + float x = (float) anm.getAnimatedValue(); + view.setTranslationX((float) ((4 * x * (1 - x)) * Math.sin(N * (x * Math.PI)) * AndroidUtilities.dp(N))); + }); + va.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - shakeView(view, num == 5 ? 0 : -x, num + 1); + view.setTranslationX(0); } }); - animatorSet.start(); + va.setDuration(300); + va.start(); + view.setTag(R.id.shake_animation, va); } public static void shakeViewSpring(View view) { @@ -2505,13 +2558,28 @@ public class AndroidUtilities { public static void shakeViewSpring(View view, float shiftDp, Runnable endCallback) { int shift = dp(shiftDp); - new SpringAnimation(view, DynamicAnimation.TRANSLATION_X, 0) - .setSpring(new SpringForce(0).setStiffness(600f)) + if (view.getTag(R.id.spring_tag) != null) { + ((SpringAnimation)view.getTag(R.id.spring_tag)).cancel(); + } + Float wasX = (Float) view.getTag(R.id.spring_was_translation_x_tag); + if (wasX != null) { + view.setTranslationX(wasX); + } + view.setTag(R.id.spring_was_translation_x_tag, view.getTranslationX()); + + float translationX = view.getTranslationX(); + SpringAnimation springAnimation = new SpringAnimation(view, DynamicAnimation.TRANSLATION_X, translationX) + .setSpring(new SpringForce(translationX).setStiffness(600f)) .setStartVelocity(-shift * 100) .addEndListener((animation, canceled, value, velocity) -> { if (endCallback != null) endCallback.run(); - }) - .start(); + + view.setTranslationX(translationX); + view.setTag(R.id.spring_tag, null); + view.setTag(R.id.spring_was_translation_x_tag, null); + }); + view.setTag(R.id.spring_tag, springAnimation); + springAnimation.start(); } /*public static String ellipsize(String text, int maxLines, int maxWidth, TextPaint paint) { @@ -3117,7 +3185,11 @@ public class AndroidUtilities { parentFragment.presentFragment(new ThemePreviewActivity(themeInfo)); } else { AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + Map colorsReplacement = new HashMap<>(); + colorsReplacement.put("info1.**", parentFragment.getThemedColor(Theme.key_dialogTopBackground)); + colorsReplacement.put("info2.**", parentFragment.getThemedColor(Theme.key_dialogTopBackground)); + builder.setTopAnimation(R.raw.not_available, AlertsCreator.NEW_DENY_DIALOG_TOP_ICON_SIZE, false, parentFragment.getThemedColor(Theme.key_dialogTopBackground), colorsReplacement); + builder.setTopAnimationIsNew(true); builder.setMessage(LocaleController.getString("IncorrectTheme", R.string.IncorrectTheme)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); parentFragment.showDialog(builder.create()); @@ -3163,7 +3235,11 @@ public class AndroidUtilities { return; } AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + Map colorsReplacement = new HashMap<>(); + colorsReplacement.put("info1.**", parentFragment.getThemedColor(Theme.key_dialogTopBackground)); + colorsReplacement.put("info2.**", parentFragment.getThemedColor(Theme.key_dialogTopBackground)); + builder.setTopAnimation(R.raw.not_available, AlertsCreator.NEW_DENY_DIALOG_TOP_ICON_SIZE, false, parentFragment.getThemedColor(Theme.key_dialogTopBackground), colorsReplacement); + builder.setTopAnimationIsNew(true); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); builder.setMessage(LocaleController.formatString("NoHandleAppInstalled", R.string.NoHandleAppInstalled, message.getDocument().mime_type)); if (parentFragment != null) { @@ -3229,7 +3305,7 @@ public class AndroidUtilities { if (f == null || !f.exists()) { f = FileLoader.getInstance(message.currentAccount).getPathToMessage(message.messageOwner); } - String mimeType = message.type == 9 || message.type == 0 ? message.getMimeType() : null; + String mimeType = message.type == MessageObject.TYPE_FILE || message.type == MessageObject.TYPE_TEXT ? message.getMimeType() : null; return openForView(f, message.getFileName(), mimeType, activity, resourcesProvider); } @@ -4243,11 +4319,11 @@ public class AndroidUtilities { return false; } - public static void scrollToFragmentRow(ActionBarLayout parentLayout, String rowName) { + public static void scrollToFragmentRow(INavigationLayout parentLayout, String rowName) { if (parentLayout == null || rowName == null) { return; } - BaseFragment openingFragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 1); + BaseFragment openingFragment = parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 1); try { Field listViewField = openingFragment.getClass().getDeclaredField("listView"); listViewField.setAccessible(true); @@ -4304,6 +4380,9 @@ public class AndroidUtilities { } public static void updateImageViewImageAnimated(ImageView imageView, Drawable newIcon) { + if (imageView.getDrawable() == newIcon) { + return; + } ValueAnimator animator = ValueAnimator.ofFloat(0, 1).setDuration(150); AtomicBoolean changed = new AtomicBoolean(); animator.addUpdateListener(animation -> { @@ -4355,6 +4434,45 @@ public class AndroidUtilities { } } + public static void updateViewShow(View view, boolean show) { + updateViewShow(view, show, true, true); + } + + public static void updateViewShow(View view, boolean show, boolean scale, boolean animated) { + updateViewShow(view, show, scale, animated, null); + } + + public static void updateViewShow(View view, boolean show, boolean scale, boolean animated, Runnable onDone) { + if (view == null) { + return; + } + if (view.getParent() == null) { + animated = false; + } + + view.animate().setListener(null).cancel(); + if (!animated) { + view.setVisibility(show ? View.VISIBLE : View.GONE); + view.setTag(show ? 1 : null); + view.setAlpha(1f); + view.setScaleX(scale && !show ? 0f : 1f); + view.setScaleY(scale && !show ? 0f : 1f); + if (onDone != null) { + onDone.run(); + } + } else if (show) { + if (view.getVisibility() != View.VISIBLE) { + view.setVisibility(View.VISIBLE); + view.setAlpha(0f); + view.setScaleX(scale ? 0 : 1); + view.setScaleY(scale ? 0 : 1); + } + view.animate().alpha(1f).scaleY(1f).scaleX(1f).setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT).setDuration(340).withEndAction(onDone).start(); + } else { + view.animate().alpha(0).scaleY(scale ? 0 : 1).scaleX(scale ? 0 : 1).setListener(new HideViewAfterAnimation(view)).setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT).setDuration(340).withEndAction(onDone).start(); + } + } + public static long getPrefIntOrLong(SharedPreferences preferences, String key, long defaultValue) { try { return preferences.getLong(key, defaultValue); @@ -4471,4 +4589,29 @@ public class AndroidUtilities { } return (st > 0 || len < text.length()) ? text.subSequence(st, len) : text; } + + // detect Error NO SPaCe left on device :( + public static boolean isENOSPC(Exception e) { + return ( + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && + e instanceof IOException && + (e.getCause() instanceof ErrnoException && + ((ErrnoException) e.getCause()).errno == OsConstants.ENOSPC) || + (e.getMessage() != null && e.getMessage().equalsIgnoreCase("no space left on device")) + ); + } + + public static CharSequence replaceCharSequence(String what, CharSequence from, CharSequence obj) { + SpannableStringBuilder spannableStringBuilder; + if (from instanceof SpannableStringBuilder) { + spannableStringBuilder = (SpannableStringBuilder) from; + } else { + spannableStringBuilder = new SpannableStringBuilder(from); + } + int index = TextUtils.indexOf(from, what); + if (index >= 0) { + spannableStringBuilder.replace(index, index + what.length(), obj); + } + return spannableStringBuilder; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/BotWebViewVibrationEffect.java b/TMessagesProj/src/main/java/org/telegram/messenger/BotWebViewVibrationEffect.java index d1cbb0e9e..d689cbac4 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/BotWebViewVibrationEffect.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/BotWebViewVibrationEffect.java @@ -14,7 +14,8 @@ public enum BotWebViewVibrationEffect { NOTIFICATION_ERROR(new long[] {14,48,14,48,14,48,20}, new int[] {200,0,200,0,255,0,145}, new long[] {40,60,40,60,65,60,40}), NOTIFICATION_SUCCESS(new long[] {14,65,14}, new int[] {175,0,255}, new long[] {50,60,65}), NOTIFICATION_WARNING(new long[] {14,64,14}, new int[] {225,0,175}, new long[] {65,60,40}), - SELECTION_CHANGE(new long[] {1}, new int[] {65}, new long[] {30}); + SELECTION_CHANGE(new long[] {1}, new int[] {65}, new long[] {30}), + APP_ERROR(new long[] {30,10,150,10}, new int[] {0,100,0,100}, new long[] {40,60,40,60,65,60,40}); public final long[] timings; public final int[] amplitudes; @@ -39,4 +40,12 @@ public enum BotWebViewVibrationEffect { return (VibrationEffect) vibrationEffect; } + + public void vibrate() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + AndroidUtilities.getVibrator().vibrate(getVibrationEffectForOreo()); + } else { + AndroidUtilities.getVibrator().vibrate(fallbackTimings, -1); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java index bf34f3247..c576dff31 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java @@ -24,8 +24,8 @@ public class BuildVars { public static boolean USE_CLOUD_STRINGS = true; public static boolean CHECK_UPDATES = true; public static boolean NO_SCOPED_STORAGE = Build.VERSION.SDK_INT <= 29; - public static int BUILD_VERSION = 2808; - public static String BUILD_VERSION_STRING = "9.0.2"; + public static int BUILD_VERSION = 2885; + public static String BUILD_VERSION_STRING = "9.1.0"; public static int APP_ID = 4; public static String APP_HASH = "014b35b6184100b085b0d0572f9b5103"; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ChatObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/ChatObject.java index 307d36a2b..67ab4aa99 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ChatObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ChatObject.java @@ -25,6 +25,7 @@ import org.telegram.ui.GroupCallActivity; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -54,6 +55,7 @@ public class ChatObject { public static final int ACTION_EDIT_MESSAGES = 12; public static final int ACTION_DELETE_MESSAGES = 13; public static final int ACTION_MANAGE_CALLS = 14; + public static final int ACTION_MANAGE_TOPICS = 15; public final static int VIDEO_FRAME_NO_FRAME = 0; public final static int VIDEO_FRAME_REQUESTING = 1; @@ -76,6 +78,14 @@ public class ChatObject { return false; } + public static boolean isForum(int currentAccount, long dialogId) { + TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-dialogId); + if (chat != null) { + return chat.forum; + } + return false; + } + public static class Call { public final static int RECORD_TYPE_AUDIO = 0, RECORD_TYPE_VIDEO_PORTAIT = 1, @@ -1402,6 +1412,7 @@ public class ChatObject { case ACTION_EMBED_LINKS: case ACTION_SEND_POLLS: case ACTION_VIEW: + case ACTION_MANAGE_TOPICS: return true; } return false; @@ -1417,6 +1428,7 @@ public class ChatObject { case ACTION_EDIT_MESSAGES: case ACTION_DELETE_MESSAGES: case ACTION_BLOCK_USERS: + case ACTION_MANAGE_TOPICS: return true; } return false; @@ -1446,6 +1458,8 @@ public class ChatObject { return rights.send_polls; case ACTION_VIEW: return rights.view_messages; + case ACTION_MANAGE_TOPICS: + return rights.manage_topics; } return false; } @@ -1474,6 +1488,9 @@ public class ChatObject { case ACTION_PIN: value = chat.admin_rights.pin_messages; break; + case ACTION_MANAGE_TOPICS: + value = chat.admin_rights.manage_topics; + break; case ACTION_CHANGE_INFO: value = chat.admin_rights.change_info; break; @@ -1556,7 +1573,7 @@ public class ChatObject { } public static boolean canSendAsPeers(TLRPC.Chat chat) { - return ChatObject.isChannel(chat) && chat.megagroup && (!TextUtils.isEmpty(chat.username) || chat.has_geo || chat.has_link); + return ChatObject.isChannel(chat) && chat.megagroup && (ChatObject.isPublic(chat) || chat.has_geo || chat.has_link); } public static boolean isChannel(TLRPC.Chat chat) { @@ -1575,6 +1592,10 @@ public class ChatObject { return isChannel(chat) && !isMegagroup(chat); } + public static boolean isForum(TLRPC.Chat chat) { + return chat != null && chat.forum; + } + public static boolean isMegagroup(int currentAccount, long chatId) { TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(chatId); return ChatObject.isChannel(chat) && chat.megagroup; @@ -1664,6 +1685,33 @@ public class ChatObject { return canUserDoAction(chat, ACTION_PIN) || ChatObject.isChannel(chat) && !chat.megagroup && chat.admin_rights != null && chat.admin_rights.edit_messages; } + public static boolean canCreateTopic(TLRPC.Chat chat) { + return canUserDoAction(chat, ACTION_MANAGE_TOPICS); + } + + public static boolean canManageTopics(TLRPC.Chat chat) { + return canUserDoAdminAction(chat, ACTION_MANAGE_TOPICS); + } + + public static boolean canManageTopic(int currentAccount, TLRPC.Chat chat, TLRPC.TL_forumTopic topic) { + return canManageTopics(chat) || isMyTopic(currentAccount, topic); + } + public static boolean canManageTopic(int currentAccount, TLRPC.Chat chat, int topicId) { + return canManageTopics(chat) || isMyTopic(currentAccount, chat, topicId); + } + + public static boolean isMyTopic(int currentAccount, TLRPC.TL_forumTopic topic) { + return topic != null && (topic.my || topic.from_id instanceof TLRPC.TL_peerUser && topic.from_id.user_id == UserConfig.getInstance(currentAccount).clientUserId); + } + + public static boolean isMyTopic(int currentAccount, TLRPC.Chat chat, int topicId) { + return chat != null && chat.forum && isMyTopic(currentAccount, chat.id, topicId); + } + + public static boolean isMyTopic(int currentAccount, long chatId, int topicId) { + return isMyTopic(currentAccount, MessagesController.getInstance(currentAccount).getTopicsController().findTopic(chatId, topicId)); + } + public static boolean isChannel(long chatId, int currentAccount) { TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(chatId); return chat instanceof TLRPC.TL_channel || chat instanceof TLRPC.TL_channelForbidden; @@ -1697,6 +1745,7 @@ public class ChatObject { currentBannedRights += bannedRights.invite_users ? 1 : 0; currentBannedRights += bannedRights.change_info ? 1 : 0; currentBannedRights += bannedRights.pin_messages ? 1 : 0; + currentBannedRights += bannedRights.manage_topics ? 1 : 0; currentBannedRights += bannedRights.until_date; return currentBannedRights; } @@ -1709,6 +1758,53 @@ public class ChatObject { return hasPhoto(chat) ? chat.photo : null; } + public static String getPublicUsername(TLRPC.Chat chat) { + return getPublicUsername(chat, false); + } + + public static String getPublicUsername(TLRPC.Chat chat, boolean editable) { + if (chat == null) { + return null; + } + if (!TextUtils.isEmpty(chat.username) && !editable) { + return chat.username; + } + if (chat.usernames != null) { + for (int i = 0; i < chat.usernames.size(); ++i) { + TLRPC.TL_username u = chat.usernames.get(i); + if (u != null && (u.active && !editable || u.editable) && !TextUtils.isEmpty(u.username)) { + return u.username; + } + } + } + if (!TextUtils.isEmpty(chat.username) && editable && (chat.usernames == null || chat.usernames.size() <= 0)) { + return chat.username; + } + return null; + } + + public static boolean hasPublicLink(TLRPC.Chat chat, String username) { + if (chat == null) { + return false; + } + if (!TextUtils.isEmpty(chat.username)) { + return chat.username.equalsIgnoreCase(username); + } + if (chat.usernames != null) { + for (int i = 0; i < chat.usernames.size(); ++i) { + TLRPC.TL_username u = chat.usernames.get(i); + if (u != null && u.active && !TextUtils.isEmpty(u.username) && u.username.equalsIgnoreCase(username)) { + return true; + } + } + } + return false; + } + + public static boolean isPublic(TLRPC.Chat chat) { + return !TextUtils.isEmpty(getPublicUsername(chat)); + } + public static class VideoParticipant { public TLRPC.TL_groupCallParticipant participant; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ChatsWidgetService.java b/TMessagesProj/src/main/java/org/telegram/messenger/ChatsWidgetService.java index 049df5404..21aaa4340 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ChatsWidgetService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ChatsWidgetService.java @@ -242,7 +242,7 @@ class ChatsRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { } else { innerMessage = String.format("\uD83C\uDFAE %s", message.messageOwner.media.game.title); } - } else if (message.type == 14) { + } else if (message.type == MessageObject.TYPE_MUSIC) { if (Build.VERSION.SDK_INT >= 18) { innerMessage = String.format("\uD83C\uDFA7 \u2068%s - %s\u2069", message.getMusicAuthor(), message.getMusicTitle()); } else { @@ -299,7 +299,7 @@ class ChatsRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { messageString = "\uD83D\uDCCA " + mediaPoll.poll.question; } else if (message.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { messageString = "\uD83C\uDFAE " + message.messageOwner.media.game.title; - } else if (message.type == 14) { + } else if (message.type == MessageObject.TYPE_MUSIC) { messageString = String.format("\uD83C\uDFA7 %s - %s", message.getMusicAuthor(), message.getMusicTitle()); } else { messageString = message.messageText; @@ -326,7 +326,7 @@ class ChatsRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { if (dialog != null && dialog.unread_count > 0) { rv.setTextViewText(R.id.shortcut_widget_item_badge, String.format("%d", dialog.unread_count)); rv.setViewVisibility(R.id.shortcut_widget_item_badge, View.VISIBLE); - if (accountInstance.getMessagesController().isDialogMuted(dialog.id)) { + if (accountInstance.getMessagesController().isDialogMuted(dialog.id, 0)) { rv.setBoolean(R.id.shortcut_widget_item_badge, "setEnabled", false); rv.setInt(R.id.shortcut_widget_item_badge, "setBackgroundResource", R.drawable.widget_badge_muted_background); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java b/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java index 24ee3b6e7..06b79e162 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java @@ -24,6 +24,7 @@ import org.telegram.SQLite.SQLitePreparedStatement; import org.telegram.tgnet.NativeByteBuffer; import org.telegram.tgnet.TLRPC; import org.telegram.ui.Components.Bulletin; +import org.telegram.ui.LaunchActivity; import java.io.File; import java.lang.ref.WeakReference; @@ -1248,6 +1249,8 @@ public class DownloadController extends BaseController implements NotificationCe getNotificationCenter().postNotificationName(NotificationCenter.onDownloadingFilesChanged); if (reason == 0) { NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.showBulletin, Bulletin.TYPE_ERROR, LocaleController.formatString("MessageNotFound", R.string.MessageNotFound)); + } else if (reason == -1) { + LaunchActivity.checkFreeDiscSpaceStatic(2); } } }); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FeedWidgetService.java b/TMessagesProj/src/main/java/org/telegram/messenger/FeedWidgetService.java index 3e2c8bf88..447853fe5 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FeedWidgetService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FeedWidgetService.java @@ -78,7 +78,7 @@ class FeedRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory, N String name; RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.feed_widget_item); - if (messageObject.type == 0) { + if (messageObject.type == MessageObject.TYPE_TEXT) { rv.setTextViewText(R.id.feed_widget_item_text, messageObject.messageText); rv.setViewVisibility(R.id.feed_widget_item_text, View.VISIBLE); } else { @@ -143,7 +143,7 @@ class FeedRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory, N if (classGuid == 0) { classGuid = ConnectionsManager.generateClassGuid(); } - accountInstance.getMessagesController().loadMessages(dialogId, 0, false, 20, 0, 0, true, 0, classGuid, 0, 0, 0, 0, 0, 1); + accountInstance.getMessagesController().loadMessages(dialogId, 0, false, 20, 0, 0, true, 0, classGuid, 0, 0, 0, 0, 0, 1, false); }); try { countDownLatch.await(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java index 5ce7bca24..d72924dbc 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java @@ -12,6 +12,7 @@ import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.NativeByteBuffer; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.LaunchActivity; import java.io.File; import java.io.FileInputStream; @@ -525,7 +526,11 @@ public class FileLoadOperation { } } } catch (Exception e) { - FileLog.e(e); + if (AndroidUtilities.isENOSPC(e)) { + LaunchActivity.checkFreeDiscSpaceStatic(1); + } else { + FileLog.e(e); + } } totalTime += System.currentTimeMillis() - time; }); @@ -853,7 +858,11 @@ public class FileLoadOperation { } file.close(); } catch (Exception e) { - FileLog.e(e); + if (AndroidUtilities.isENOSPC(e)) { + LaunchActivity.checkFreeDiscSpaceStatic(1); + } else { + FileLog.e(e); + } } } @@ -913,7 +922,7 @@ public class FileLoadOperation { } preloadStream.seek(preloadStreamFileOffset); } catch (Exception e) { - FileLog.e(e); + FileLog.e(e, false); } if (!isPreloadVideoOperation && preloadedBytesRanges == null) { cacheFilePreload = null; @@ -1006,8 +1015,12 @@ public class FileLoadOperation { } } } catch (Exception e) { - FileLog.e(e); requestedBytesCount = downloadedBytes = 0; + if (AndroidUtilities.isENOSPC(e)) { + LaunchActivity.checkFreeDiscSpaceStatic(1); + } else { + FileLog.e(e); + } } } if (!isPreloadVideoOperation && downloadedBytes != 0 && totalBytesCount > 0) { @@ -1020,7 +1033,13 @@ public class FileLoadOperation { fileOutputStream.seek(downloadedBytes); } } catch (Exception e) { - FileLog.e(e, false); + if (AndroidUtilities.isENOSPC(e)) { + LaunchActivity.checkFreeDiscSpaceStatic(1); + onFail(true, -1); + return false; + } else { + FileLog.e(e, false); + } } if (fileOutputStream == null) { onFail(true, 0); @@ -1046,7 +1065,13 @@ public class FileLoadOperation { delegate.saveFilePath(pathSaveData, null); } } catch (Exception e) { - onFail(true, 0); + if (AndroidUtilities.isENOSPC(e)) { + LaunchActivity.checkFreeDiscSpaceStatic(1); + onFail(true, -1); + } else { + FileLog.e(e, false); + onFail(true, 0); + } } } return true; @@ -1473,7 +1498,7 @@ public class FileLoadOperation { protected boolean processRequestResult(RequestInfo requestInfo, TLRPC.TL_error error) { if (state != stateDownloading) { if (BuildVars.DEBUG_VERSION && state == stateFinished) { - FileLog.e(new Exception("trying to write to finished file " + fileName + " offset " + requestInfo.offset + " " + totalBytesCount)); + FileLog.e(new FileLog.IgnoreSentException("trying to write to finished file " + fileName + " offset " + requestInfo.offset + " " + totalBytesCount)); } return false; } @@ -1692,8 +1717,12 @@ public class FileLoadOperation { startDownloadRequest(); } } catch (Exception e) { - onFail(false, 0); - FileLog.e(e); + if (AndroidUtilities.isENOSPC(e)) { + onFail(false, -1); + } else { + FileLog.e(e); + onFail(false, 0); + } } } else { if (error.text.contains("FILE_MIGRATE_")) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java index dc1cd6af3..296df15e1 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java @@ -13,6 +13,7 @@ import android.util.SparseArray; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.LaunchActivity; import java.io.File; import java.io.FileOutputStream; @@ -736,6 +737,8 @@ public class FileLoader extends BaseController { if (document != null && parentObject instanceof MessageObject && reason == 0) { getDownloadController().onDownloadFail((MessageObject) parentObject, reason); + } else if (reason == -1) { + LaunchActivity.checkFreeDiscSpaceStatic(2); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java index 2bce31cc9..17b79d942 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java @@ -12,6 +12,7 @@ import android.util.Log; import org.telegram.messenger.time.FastDateFormat; import org.telegram.messenger.video.MediaCodecVideoConvertor; +import org.telegram.ui.LaunchActivity; import java.io.File; import java.io.FileOutputStream; @@ -228,7 +229,7 @@ public class FileLog { } private static boolean needSent(Throwable e) { - if (e instanceof InterruptedException || e instanceof MediaCodecVideoConvertor.ConversionCanceledException) { + if (e instanceof InterruptedException || e instanceof MediaCodecVideoConvertor.ConversionCanceledException || e instanceof IgnoreSentException) { return false; } return true; @@ -247,6 +248,9 @@ public class FileLog { getInstance().streamWriter.flush(); } catch (Exception e) { e.printStackTrace(); + if (AndroidUtilities.isENOSPC(e)) { + LaunchActivity.checkFreeDiscSpaceStatic(1); + } } }); } @@ -294,4 +298,12 @@ public class FileLog { } } } + + public static class IgnoreSentException extends Exception{ + + public IgnoreSentException(String e) { + super(e); + } + + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FilePathDatabase.java b/TMessagesProj/src/main/java/org/telegram/messenger/FilePathDatabase.java index 51b0bd306..6639c96ee 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FilePathDatabase.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FilePathDatabase.java @@ -43,10 +43,8 @@ public class FilePathDatabase { cacheFile = new File(filesDir, DATABASE_NAME + ".db"); shmCacheFile = new File(filesDir, DATABASE_NAME + ".db-shm"); - boolean createTable = false; - if (!cacheFile.exists()) { createTable = true; } @@ -145,20 +143,22 @@ public class FilePathDatabase { long time = System.currentTimeMillis(); dispatchQueue.postRunnable(() -> { - SQLiteCursor cursor = null; - try { - cursor = database.queryFinalized("SELECT path FROM paths WHERE document_id = " + documentId + " AND dc_id = " + dc + " AND type = " + type); - if (cursor.next()) { - res[0] = cursor.stringValue(0); - if (BuildVars.DEBUG_VERSION) { - FileLog.d("get file path id=" + documentId + " dc=" + dc + " type=" + type + " path=" + res[0]); + if (database != null) { + SQLiteCursor cursor = null; + try { + cursor = database.queryFinalized("SELECT path FROM paths WHERE document_id = " + documentId + " AND dc_id = " + dc + " AND type = " + type); + if (cursor.next()) { + res[0] = cursor.stringValue(0); + if (BuildVars.DEBUG_VERSION) { + FileLog.d("get file path id=" + documentId + " dc=" + dc + " type=" + type + " path=" + res[0]); + } + } + } catch (SQLiteException e) { + FileLog.e(e); + } finally { + if (cursor != null) { + cursor.dispose(); } - } - } catch (SQLiteException e) { - FileLog.e(e); - } finally { - if (cursor != null) { - cursor.dispose(); } } syncLatch.countDown(); @@ -169,6 +169,9 @@ public class FilePathDatabase { } return res[0]; } else { + if (database == null) { + return null; + } SQLiteCursor cursor = null; String res = null; try { @@ -195,6 +198,9 @@ public class FilePathDatabase { if (BuildVars.DEBUG_VERSION) { FileLog.d("put file path id=" + id + " dc=" + dc + " type=" + type + " path=" + path); } + if (database == null) { + return; + } SQLitePreparedStatement state = null; SQLitePreparedStatement deleteState = null; try { @@ -266,7 +272,7 @@ public class FilePathDatabase { dispatchQueue.postRunnable(() -> { try { database.executeFast("DELETE FROM paths WHERE 1").stepThis().dispose(); - } catch (SQLiteException e) { + } catch (Exception e) { FileLog.e(e); } }); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java index 5b2931d2b..15e1aef52 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java @@ -1519,7 +1519,7 @@ public class ImageLoader { } } } catch (Throwable e) { - FileLog.e(e); + FileLog.e(e, !(e instanceof FileNotFoundException)); } } Thread.interrupted(); @@ -1535,12 +1535,18 @@ public class ImageLoader { } private void loadLastFrame(RLottieDrawable lottieDrawable, int w, int h, boolean lastFrame) { - Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); + Bitmap bitmap; + Canvas canvas; if (lastFrame) { - canvas.scale(2f, 2f, w / 2f, h / 2f); + bitmap = Bitmap.createBitmap((int) (w * ImageReceiver.ReactionLastFrame.LAST_FRAME_SCALE), (int) (h * ImageReceiver.ReactionLastFrame.LAST_FRAME_SCALE), Bitmap.Config.ARGB_8888); + canvas = new Canvas(bitmap); + canvas.scale(2f, 2f, w * ImageReceiver.ReactionLastFrame.LAST_FRAME_SCALE / 2f, h * ImageReceiver.ReactionLastFrame.LAST_FRAME_SCALE / 2f); + } else { + bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + canvas = new Canvas(bitmap); } + AndroidUtilities.runOnUIThread(() -> { lottieDrawable.setOnFrameReadyRunnable(() -> { lottieDrawable.setOnFrameReadyRunnable(null); @@ -1553,8 +1559,13 @@ public class ImageLoader { } Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setFilterBitmap(true); - canvas.drawBitmap(currentBitmap, 0, 0, paint); - bitmapDrawable = new BitmapDrawable(bitmap); + if (lastFrame) { + canvas.drawBitmap(currentBitmap, (bitmap.getWidth() - currentBitmap.getWidth()) / 2f, (bitmap.getHeight() - currentBitmap.getHeight()) / 2f, paint); + bitmapDrawable = new ImageReceiver.ReactionLastFrame(bitmap); + } else { + canvas.drawBitmap(currentBitmap, 0, 0, paint); + bitmapDrawable = new BitmapDrawable(bitmap); + } } onPostExecute(bitmapDrawable); lottieDrawable.recycle(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLocation.java b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLocation.java index cdbb96d96..34f1b113c 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLocation.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLocation.java @@ -83,6 +83,17 @@ public class ImageLocation { return getForPhoto(photoSize, (TLRPC.Photo) object); } else if (object instanceof TLRPC.Document) { return getForDocument(photoSize, (TLRPC.Document) object); + } else if (object instanceof TLRPC.Message) { + return getForMessage(photoSize, (TLRPC.Message) object); + } + return null; + } + + public static ImageLocation getForMessage(TLRPC.PhotoSize photoSize, TLRPC.Message message) { + if (photoSize instanceof TLRPC.TL_photoStrippedSize || photoSize instanceof TLRPC.TL_photoPathSize) { + ImageLocation imageLocation = new ImageLocation(); + imageLocation.photoSize = photoSize; + return imageLocation; } return null; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java index eb795496b..268d6ca37 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java @@ -33,6 +33,7 @@ import androidx.annotation.Keep; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.Components.AnimatedFileDrawable; +import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.LoadingStickerDrawable; import org.telegram.ui.Components.RLottieDrawable; import org.telegram.ui.Components.RecyclableDrawable; @@ -177,6 +178,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg private int currentLayerNum; private int currentOpenedLayerFlags; + private int isLastFrame; private SetImageBackup setImageBackup; @@ -486,6 +488,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg if (staticThumbDrawable instanceof SvgHelper.SvgDrawable) { ((SvgHelper.SvgDrawable) staticThumbDrawable).setParent(this); } + updateDrawableRadius(staticThumbDrawable); ImageLoader.getInstance().cancelLoadingForImageReceiver(this, true); invalidate(); @@ -883,15 +886,19 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg if (drawable == null) { return; } - if ((hasRoundRadius() || gradientShader != null) && drawable instanceof BitmapDrawable) { - BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; - if (bitmapDrawable instanceof RLottieDrawable) { + if ((hasRoundRadius() || gradientShader != null) && (drawable instanceof BitmapDrawable || drawable instanceof AvatarDrawable)) { + if (drawable instanceof AvatarDrawable) { + ((AvatarDrawable) drawable).setRoundRadius(roundRadius[0]); + } else { + BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; + if (bitmapDrawable instanceof RLottieDrawable) { - } else if (bitmapDrawable instanceof AnimatedFileDrawable) { - AnimatedFileDrawable animatedFileDrawable = (AnimatedFileDrawable) drawable; - animatedFileDrawable.setRoundRadius(roundRadius); - } else if (bitmapDrawable.getBitmap() != null) { - setDrawableShader(drawable, new BitmapShader(bitmapDrawable.getBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)); + } else if (bitmapDrawable instanceof AnimatedFileDrawable) { + AnimatedFileDrawable animatedFileDrawable = (AnimatedFileDrawable) drawable; + animatedFileDrawable.setRoundRadius(roundRadius); + } else if (bitmapDrawable.getBitmap() != null) { + setDrawableShader(drawable, new BitmapShader(bitmapDrawable.getBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)); + } } } else { setDrawableShader(drawable, null); @@ -1027,6 +1034,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg RectF drawRegion; ColorFilter colorFilter; int[] roundRadius; + boolean reactionLastFrame = false; if (backgroundThreadDrawHolder != null) { imageX = backgroundThreadDrawHolder.imageX; imageY = backgroundThreadDrawHolder.imageY; @@ -1116,12 +1124,17 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg bitmapW = bitmap.getWidth(); bitmapH = bitmap.getHeight(); } + reactionLastFrame = bitmapDrawable instanceof ReactionLastFrame; } float realImageW = imageW - sideClip * 2; float realImageH = imageH - sideClip * 2; float scaleW = imageW == 0 ? 1.0f : (bitmapW / realImageW); float scaleH = imageH == 0 ? 1.0f : (bitmapH / realImageH); + if (reactionLastFrame) { + scaleW /= ReactionLastFrame.LAST_FRAME_SCALE; + scaleH /= ReactionLastFrame.LAST_FRAME_SCALE; + } if (shader != null && backgroundThreadDrawHolder == null) { if (isAspectFit) { float scale = Math.max(scaleW, scaleH); @@ -1192,7 +1205,11 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg } if (isVisible) { shaderMatrix.reset(); - shaderMatrix.setTranslate((int) (drawRegion.left + sideClip), (int) (drawRegion.top + sideClip)); + if (reactionLastFrame) { + shaderMatrix.setTranslate((int) (drawRegion.left + sideClip) - (drawRegion.width() * ReactionLastFrame.LAST_FRAME_SCALE - drawRegion.width()) / 2f, (int) (drawRegion.top + sideClip) - (drawRegion.height() * ReactionLastFrame.LAST_FRAME_SCALE - drawRegion.height()) / 2f); + } else { + shaderMatrix.setTranslate((int) (drawRegion.left + sideClip), (int) (drawRegion.top + sideClip)); + } if (orientation == 90) { shaderMatrix.preRotate(90); shaderMatrix.preTranslate(0, -drawRegion.width()); @@ -1242,7 +1259,13 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg if (isRoundRect) { try { if (roundRadius[0] == 0) { - canvas.drawRect(roundRect, roundPaint); + if (reactionLastFrame) { + AndroidUtilities.rectTmp.set(roundRect); + AndroidUtilities.rectTmp.inset(-(drawRegion.width() * ReactionLastFrame.LAST_FRAME_SCALE - drawRegion.width()) / 2f, -(drawRegion.height() * ReactionLastFrame.LAST_FRAME_SCALE - drawRegion.height()) / 2f); + canvas.drawRect(AndroidUtilities.rectTmp, roundPaint); + } else { + canvas.drawRect(roundRect, roundPaint); + } } else { canvas.drawRoundRect(roundRect, roundRadius[0], roundRadius[0], roundPaint); } @@ -2211,7 +2234,8 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg } if (currentThumbDrawable != null) { updateDrawableRadius(currentThumbDrawable); - } else if (staticThumbDrawable != null) { + } + if (staticThumbDrawable != null) { updateDrawableRadius(staticThumbDrawable); } } @@ -2922,4 +2946,13 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg } } } + + public static class ReactionLastFrame extends BitmapDrawable { + + public final static float LAST_FRAME_SCALE = 1.2f; + + public ReactionLastFrame(Bitmap bitmap) { + super(bitmap); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java b/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java index 8359b0375..adac600ff 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java @@ -1063,6 +1063,10 @@ public class LocaleController { } public static String formatPluralStringComma(String key, int plural) { + return formatPluralStringComma(key, plural, ','); + } + + public static String formatPluralStringComma(String key, int plural, char symbol) { try { if (key == null || key.length() == 0 || getInstance().currentPluralRules == null) { return "LOC_ERR:" + key; @@ -1071,7 +1075,7 @@ public class LocaleController { param = key + "_" + param; StringBuilder stringBuilder = new StringBuilder(String.format(Locale.US, "%d", plural)); for (int a = stringBuilder.length() - 3; a > 0; a -= 3) { - stringBuilder.insert(a, ','); + stringBuilder.insert(a, symbol); } String value = BuildVars.USE_CLOUD_STRINGS ? getInstance().localeValues.get(param) : null; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java index 1fab65a1c..f9333a72d 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java @@ -52,6 +52,7 @@ import android.provider.OpenableColumns; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.Log; import android.util.SparseArray; import android.view.HapticFeedbackConstants; import android.view.TextureView; @@ -520,6 +521,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, public static ArrayList allPhotoAlbums = new ArrayList<>(); private static Runnable broadcastPhotosRunnable; + public boolean isSilent = false; private boolean isPaused = false; private VideoPlayer audioPlayer = null; private VideoPlayer emojiSoundPlayer = null; @@ -1044,7 +1046,9 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, private void setPlayerVolume() { try { float volume; - if (audioFocus != AUDIO_NO_FOCUS_CAN_DUCK) { + if (isSilent) { + volume = 0; + } else if (audioFocus != AUDIO_NO_FOCUS_CAN_DUCK) { volume = VOLUME_NORMAL; } else { volume = VOLUME_DUCK; @@ -2126,12 +2130,13 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } return; } + //TODO topics if (!playlistEndReached[0]) { loadingPlaylist = true; - AccountInstance.getInstance(playingMessageObject.currentAccount).getMediaDataController().loadMedia(playingMessageObject.getDialogId(), 50, playlistMaxId[0], 0, MediaDataController.MEDIA_MUSIC, 1, playlistClassGuid, 0); + AccountInstance.getInstance(playingMessageObject.currentAccount).getMediaDataController().loadMedia(playingMessageObject.getDialogId(), 50, playlistMaxId[0], 0, MediaDataController.MEDIA_MUSIC, 0, 1, playlistClassGuid, 0); } else if (playlistMergeDialogId != 0 && !playlistEndReached[1]) { loadingPlaylist = true; - AccountInstance.getInstance(playingMessageObject.currentAccount).getMediaDataController().loadMedia(playlistMergeDialogId, 50, playlistMaxId[0], 0, MediaDataController.MEDIA_MUSIC, 1, playlistClassGuid, 0); + AccountInstance.getInstance(playingMessageObject.currentAccount).getMediaDataController().loadMedia(playlistMergeDialogId, 50, playlistMaxId[0], 0, MediaDataController.MEDIA_MUSIC, 0, 1, playlistClassGuid, 0); } } @@ -2443,6 +2448,10 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } } + public boolean isPiPShown() { + return pipRoundVideoView != null; + } + public void setCurrentVideoVisible(boolean visible) { if (currentAspectRatioFrameLayout == null) { return; @@ -2479,6 +2488,10 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } public void setTextureView(TextureView textureView, AspectRatioFrameLayout aspectRatioFrameLayout, FrameLayout container, boolean set) { + setTextureView(textureView, aspectRatioFrameLayout, container, set, false); + } + + public void setTextureView(TextureView textureView, AspectRatioFrameLayout aspectRatioFrameLayout, FrameLayout container, boolean set, boolean forcePip) { if (textureView == null) { return; } @@ -2494,6 +2507,16 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } isDrawingWasReady = aspectRatioFrameLayout != null && aspectRatioFrameLayout.isDrawingReady(); currentTextureView = textureView; + if (forcePip && pipRoundVideoView == null) { + try { + pipRoundVideoView = new PipRoundVideoView(); + AndroidUtilities.runOnUIThread(() -> { + pipRoundVideoView.show(baseActivity, () -> cleanupPlayer(true, true)); + }, 350); + } catch (Exception e) { + pipRoundVideoView = null; + } + } if (pipRoundVideoView != null) { videoPlayer.setTextureView(pipRoundVideoView.getTextureView()); } else { @@ -2839,6 +2862,9 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, private static long volumeBarLastTimeShown; public void checkVolumeBarUI() { + if (isSilent) { + return; + } try { final long now = System.currentTimeMillis(); if (Math.abs(now - volumeBarLastTimeShown) < 5000) { @@ -2874,9 +2900,14 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } public boolean playMessage(final MessageObject messageObject) { + return playMessage(messageObject, false); + } + + public boolean playMessage(final MessageObject messageObject, boolean silent) { if (messageObject == null) { return false; } + isSilent = silent; checkVolumeBarUI(); if ((audioPlayer != null || videoPlayer != null) && isSamePlayingMessage(messageObject)) { if (isPaused) { @@ -2958,6 +2989,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, int[] playCount = isVideo && messageObject.getDuration() <= 30 ? new int[]{1} : null; clearPlaylist(); videoPlayer = new VideoPlayer(); + videoPlayer.setLooping(silent); int tag = ++playerNum; videoPlayer.setDelegate(new VideoPlayer.VideoPlayerDelegate() { @Override @@ -3331,6 +3363,18 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, return true; } + + public void updateSilent(boolean value) { + isSilent = value; + if (videoPlayer != null) { + videoPlayer.setLooping(value); + } + setPlayerVolume(); + checkVolumeBarUI(); + if (playingMessageObject != null) { + NotificationCenter.getInstance(playingMessageObject.currentAccount).postNotificationName(NotificationCenter.messagePlayingPlayStateChanged, playingMessageObject != null ? playingMessageObject.getId() : 0); + } + } public AudioInfo getAudioInfo() { return audioInfo; @@ -3413,7 +3457,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, audioVolumeAnimator.removeAllListeners(); audioVolumeAnimator.cancel(); } - if (!messageObject.isVoice()) { + if (!messageObject.isVoice() && !messageObject.isRoundVideo()) { audioVolumeAnimator = ValueAnimator.ofFloat(audioVolume, 1f); audioVolumeAnimator.addUpdateListener(audioVolumeUpdateListener); audioVolumeAnimator.setDuration(300); @@ -3604,7 +3648,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } TLRPC.TL_messages_messages messagesRes = new TLRPC.TL_messages_messages(); messagesRes.messages.add(messageObject1.messageOwner); - MessagesStorage.getInstance(messageObject1.currentAccount).putMessages(messagesRes, messageObject1.getDialogId(), -1, 0, false, messageObject.scheduled); + MessagesStorage.getInstance(messageObject1.currentAccount).putMessages(messagesRes, messageObject1.getDialogId(), -1, 0, false, messageObject.scheduled, 0); ArrayList arrayList = new ArrayList<>(); arrayList.add(messageObject1); NotificationCenter.getInstance(messageObject1.currentAccount).postNotificationName(NotificationCenter.replaceMessagesObjects, messageObject1.getDialogId(), arrayList); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java index 35ecc2456..151a5576c 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java @@ -65,7 +65,6 @@ import org.telegram.ui.Components.Bulletin; import org.telegram.ui.Components.ChatThemeBottomSheet; import org.telegram.ui.Components.RLottieDrawable; import org.telegram.ui.Components.Reactions.ReactionsEffectOverlay; -import org.telegram.ui.Components.Reactions.ReactionsLayoutInBubble; import org.telegram.ui.Components.Reactions.ReactionsUtils; import org.telegram.ui.Components.StickerSetBulletinLayout; import org.telegram.ui.Components.StickersArchiveAlert; @@ -258,6 +257,7 @@ public class MediaDataController extends BaseController { private boolean loadingPremiumGiftStickers; private boolean loadingGenericAnimations; + private boolean loadingDefaultTopicIcons; private long loadFeaturedHash[] = new long[2]; private int loadFeaturedDate[] = new int[2]; @@ -598,12 +598,17 @@ public class MediaDataController extends BaseController { c.dispose(); } } - processLoadedReactions(reactions, hash, date, true); + List finalReactions = reactions; + int finalHash = hash; + int finalDate = date; + AndroidUtilities.runOnUIThread(() -> { + processLoadedReactions(finalReactions, finalHash, finalDate, true); + }); }); } else { TLRPC.TL_messages_getAvailableReactions req = new TLRPC.TL_messages_getAvailableReactions(); req.hash = force ? 0 : reactionsUpdateHash; - getConnectionsManager().sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { int date = (int) (System.currentTimeMillis() / 1000); if (response instanceof TLRPC.TL_messages_availableReactionsNotModified) { processLoadedReactions(null, 0, date, false); @@ -611,7 +616,7 @@ public class MediaDataController extends BaseController { TLRPC.TL_messages_availableReactions r = (TLRPC.TL_messages_availableReactions) response; processLoadedReactions(r.reactions, r.hash, date, false); } - }); + })); } } @@ -2048,7 +2053,7 @@ public class MediaDataController extends BaseController { private void processLoadStickersResponse(int type, TLRPC.TL_messages_allStickers res, Runnable onDone) { ArrayList newStickerArray = new ArrayList<>(); if (res.sets.isEmpty()) { - processLoadedStickers(type, newStickerArray, false, (int) (System.currentTimeMillis() / 1000), res.hash, onDone); + processLoadedStickers(type, newStickerArray, false, (int) (System.currentTimeMillis() / 1000), res.hash2, onDone); } else { LongSparseArray newStickerSets = new LongSparseArray<>(); for (int a = 0; a < res.sets.size(); a++) { @@ -2063,7 +2068,7 @@ public class MediaDataController extends BaseController { newStickerArray.add(oldSet); if (newStickerSets.size() == res.sets.size()) { - processLoadedStickers(type, newStickerArray, false, (int) (System.currentTimeMillis() / 1000), res.hash); + processLoadedStickers(type, newStickerArray, false, (int) (System.currentTimeMillis() / 1000), res.hash2); } continue; } @@ -2087,7 +2092,7 @@ public class MediaDataController extends BaseController { a1--; } } - processLoadedStickers(type, newStickerArray, false, (int) (System.currentTimeMillis() / 1000), res.hash); + processLoadedStickers(type, newStickerArray, false, (int) (System.currentTimeMillis() / 1000), res.hash2); } })); } @@ -2140,7 +2145,7 @@ public class MediaDataController extends BaseController { MediaDataController.getInstance(currentAccount).loadStickersByEmojiOrName(packName, false, true); } } - if (loadingGenericAnimations /*|| System.currentTimeMillis() - getUserConfig().lastUpdatedGenericAnimations < 86400000*/) { + if (loadingGenericAnimations || System.currentTimeMillis() - getUserConfig().lastUpdatedGenericAnimations < 86400000) { return; } loadingGenericAnimations = true; @@ -2162,6 +2167,39 @@ public class MediaDataController extends BaseController { })); } + public void checkDefaultTopicIcons() { + if (getUserConfig().defaultTopicIcons != null) { + String packName = getUserConfig().defaultTopicIcons; + TLRPC.TL_messages_stickerSet set = getStickerSetByName(packName); + if (set == null) { + set = getStickerSetByEmojiOrName(packName); + } + if (set == null) { + MediaDataController.getInstance(currentAccount).loadStickersByEmojiOrName(packName, false, true); + } + } + if (loadingDefaultTopicIcons || System.currentTimeMillis() - getUserConfig().lastUpdatedDefaultTopicIcons < 86400000) { + return; + } + loadingDefaultTopicIcons = true; + + TLRPC.TL_messages_getStickerSet req = new TLRPC.TL_messages_getStickerSet(); + req.stickerset = new TLRPC.TL_inputStickerSetEmojiDefaultTopicIcons(); + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + if (response instanceof TLRPC.TL_messages_stickerSet) { + TLRPC.TL_messages_stickerSet stickerSet = (TLRPC.TL_messages_stickerSet) response; + getUserConfig().defaultTopicIcons = stickerSet.set.short_name; + getUserConfig().lastUpdatedDefaultTopicIcons = System.currentTimeMillis(); + getUserConfig().saveConfig(false); + + processLoadedDiceStickers(getUserConfig().defaultTopicIcons, false, stickerSet, false, (int) (System.currentTimeMillis() / 1000)); + for (int i = 0; i < stickerSet.documents.size(); i++) { + preloadImage(ImageLocation.getForDocument(stickerSet.documents.get(i)), null); + } + } + })); + } + public void loadStickersByEmojiOrName(String name, boolean isEmoji, boolean cache) { if (loadingDiceStickerSets.contains(name) || isEmoji && diceStickerSetsByEmoji.get(name) != null) { return; @@ -2366,7 +2404,7 @@ public class MediaDataController extends BaseController { if (type == TYPE_FEATURED || type == TYPE_FEATURED_EMOJIPACKS) { final boolean emoji = type == TYPE_FEATURED_EMOJIPACKS; TLRPC.TL_messages_allStickers response = new TLRPC.TL_messages_allStickers(); - response.hash = loadFeaturedHash[emoji ? 1 : 0]; + response.hash2 = loadFeaturedHash[emoji ? 1 : 0]; for (int a = 0, size = featuredStickerSets[emoji ? 1 : 0].size(); a < size; a++) { response.sets.add(featuredStickerSets[emoji ? 1 : 0].get(a).set); } @@ -2482,9 +2520,12 @@ public class MediaDataController extends BaseController { } public static long getStickerSetId(TLRPC.Document document) { + if (document == null) { + return -1; + } for (int a = 0; a < document.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = document.attributes.get(a); - if (attribute instanceof TLRPC.TL_documentAttributeSticker) { + if (attribute instanceof TLRPC.TL_documentAttributeSticker || attribute instanceof TLRPC.TL_documentAttributeCustomEmoji) { if (attribute.stickerset instanceof TLRPC.TL_inputStickerSetID) { return attribute.stickerset.id; } @@ -3142,6 +3183,10 @@ public class MediaDataController extends BaseController { req.from_id = MessagesController.getInputPeer(chat); req.flags |= 1; } + if (replyMessageId != 0) { + req.top_msg_id = replyMessageId; + req.flags |= 2; + } req.filter = new TLRPC.TL_inputMessagesFilterEmpty(); mergeReqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (lastMergeDialogId == mergeDialogId) { @@ -3281,14 +3326,14 @@ public class MediaDataController extends BaseController { public final static int MEDIA_TYPES_COUNT = 8; - public void loadMedia(long dialogId, int count, int max_id, int min_id, int type, int fromCache, int classGuid, int requestIndex) { + public void loadMedia(long dialogId, int count, int max_id, int min_id, int type, int topicId, int fromCache, int classGuid, int requestIndex) { boolean isChannel = DialogObject.isChatDialog(dialogId) && ChatObject.isChannel(-dialogId, currentAccount); if (BuildVars.LOGS_ENABLED) { FileLog.d("load media did " + dialogId + " count = " + count + " max_id " + max_id + " type = " + type + " cache = " + fromCache + " classGuid = " + classGuid); } - if ((fromCache != 0 || DialogObject.isEncryptedDialog(dialogId))) { - loadMediaDatabase(dialogId, count, max_id, min_id, type, classGuid, isChannel, fromCache, requestIndex); + if (fromCache != 0 || DialogObject.isEncryptedDialog(dialogId)) { + loadMediaDatabase(dialogId, count, max_id, min_id, type, topicId, classGuid, isChannel, fromCache, requestIndex); } else { TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); req.limit = count; @@ -3318,6 +3363,10 @@ public class MediaDataController extends BaseController { } req.q = ""; req.peer = getMessagesController().getInputPeer(dialogId); + if (topicId != 0) { + req.top_msg_id = topicId; + req.flags |= 2; + } if (req.peer == null) { return; } @@ -3332,20 +3381,25 @@ public class MediaDataController extends BaseController { topReached = res.messages.size() == 0; } - processLoadedMedia(res, dialogId, count, max_id, min_id, type, 0, classGuid, isChannel, topReached, requestIndex); + processLoadedMedia(res, dialogId, count, max_id, min_id, type, topicId, 0, classGuid, isChannel, topReached, requestIndex); } }); getConnectionsManager().bindRequestToGuid(reqId, classGuid); } } - public void getMediaCounts(long dialogId, int classGuid) { + public void getMediaCounts(long dialogId, int topicId, int classGuid) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { int[] counts = new int[]{-1, -1, -1, -1, -1, -1, -1, -1}; int[] countsFinal = new int[]{-1, -1, -1, -1, -1, -1, -1, -1}; int[] old = new int[]{0, 0, 0, 0, 0, 0, 0, 0}; - SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT type, count, old FROM media_counts_v2 WHERE uid = %d", dialogId)); + SQLiteCursor cursor; + if (topicId != 0) { + cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT type, count, old FROM media_counts_topics WHERE uid = %d AND topic_id = %d", dialogId, topicId)); + } else { + cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT type, count, old FROM media_counts_v2 WHERE uid = %d", dialogId)); + } while (cursor.next()) { int type = cursor.intValue(0); if (type >= 0 && type < MEDIA_TYPES_COUNT) { @@ -3364,14 +3418,18 @@ public class MediaDataController extends BaseController { counts[a] = 0; } cursor.dispose(); - putMediaCountDatabase(dialogId, a, counts[a]); + putMediaCountDatabase(dialogId, topicId, a, counts[a]); } } - AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.mediaCountsDidLoad, dialogId, counts)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.mediaCountsDidLoad, dialogId, topicId, counts)); } else { boolean missing = false; TLRPC.TL_messages_getSearchCounters req = new TLRPC.TL_messages_getSearchCounters(); req.peer = getMessagesController().getInputPeer(dialogId); + if (topicId != 0) { + req.top_msg_id = topicId; + req.flags |= 1; + } for (int a = 0; a < counts.length; a++) { if (req.peer == null) { counts[a] = 0; @@ -3434,15 +3492,15 @@ public class MediaDataController extends BaseController { continue; } counts[type] = searchCounter.count; - putMediaCountDatabase(dialogId, type, counts[type]); + putMediaCountDatabase(dialogId, topicId, type, counts[type]); } } - AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.mediaCountsDidLoad, dialogId, counts)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.mediaCountsDidLoad, dialogId, topicId, counts)); }); getConnectionsManager().bindRequestToGuid(reqId, classGuid); } if (!missing) { - AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.mediaCountsDidLoad, dialogId, countsFinal)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.mediaCountsDidLoad, dialogId, topicId, countsFinal)); } } } catch (Exception e) { @@ -3451,9 +3509,9 @@ public class MediaDataController extends BaseController { }); } - public void getMediaCount(long dialogId, int type, int classGuid, boolean fromCache) { + public void getMediaCount(long dialogId, int topicId, int type, int classGuid, boolean fromCache) { if (fromCache || DialogObject.isEncryptedDialog(dialogId)) { - getMediaCountDatabase(dialogId, type, classGuid); + getMediaCountDatabase(dialogId, topicId, type, classGuid); } else { TLRPC.TL_messages_getSearchCounters req = new TLRPC.TL_messages_getSearchCounters(); if (type == MEDIA_PHOTOVIDEO) { @@ -3469,6 +3527,10 @@ public class MediaDataController extends BaseController { } else if (type == MEDIA_GIF) { req.filters.add(new TLRPC.TL_inputMessagesFilterGif()); } + if (topicId != 0) { + req.top_msg_id = topicId; + req.flags |= 1; + } req.peer = getMessagesController().getInputPeer(dialogId); if (req.peer == null) { return; @@ -3478,7 +3540,7 @@ public class MediaDataController extends BaseController { TLRPC.Vector res = (TLRPC.Vector) response; if (!res.objects.isEmpty()) { TLRPC.TL_messages_searchCounter counter = (TLRPC.TL_messages_searchCounter) res.objects.get(0); - processLoadedMediaCount(counter.count, dialogId, type, classGuid, false, 0); + processLoadedMediaCount(counter.count, dialogId, topicId, type, classGuid, false, 0); } } }); @@ -3551,20 +3613,24 @@ public class MediaDataController extends BaseController { } } - private void processLoadedMedia(TLRPC.messages_Messages res, long dialogId, int count, int max_id, int min_id, int type, int fromCache, int classGuid, boolean isChannel, boolean topReached, int requestIndex) { + private void processLoadedMedia(TLRPC.messages_Messages res, long dialogId, int count, int max_id, int min_id, int type, int topicId, int fromCache, int classGuid, boolean isChannel, boolean topReached, int requestIndex) { if (BuildVars.LOGS_ENABLED) { - FileLog.d("process load media did " + dialogId + " count = " + count + " max_id=" + max_id + " min_id=" + min_id + " type = " + type + " cache = " + fromCache + " classGuid = " + classGuid); + int messagesCount = 0; + if (res != null && res.messages != null) { + messagesCount = res.messages.size(); + } + FileLog.d("process load media messagesCount " + messagesCount + " did " + dialogId + " topicId " + topicId + " count = " + count + " max_id=" + max_id + " min_id=" + min_id + " type = " + type + " cache = " + fromCache + " classGuid = " + classGuid); } if (fromCache != 0 && ((res.messages.isEmpty() && min_id == 0) || (res.messages.size() <= 1 && min_id != 0)) && !DialogObject.isEncryptedDialog(dialogId)) { if (fromCache == 2) { return; } - loadMedia(dialogId, count, max_id, min_id, type, 0, classGuid, requestIndex); + loadMedia(dialogId, count, max_id, min_id, type, topicId, 0, classGuid, requestIndex); } else { if (fromCache == 0) { ImageLoader.saveMessagesThumbs(res.messages); getMessagesStorage().putUsersAndChats(res.users, res.chats, true, true); - putMediaDatabase(dialogId, type, res.messages, max_id, min_id, topReached); + putMediaDatabase(dialogId, topicId, type, res.messages, max_id, min_id, topReached); } Utilities.searchQueue.postRunnable(() -> { @@ -3592,31 +3658,40 @@ public class MediaDataController extends BaseController { } } - private void processLoadedMediaCount(int count, long dialogId, int type, int classGuid, boolean fromCache, int old) { + private void processLoadedMediaCount(int count, long dialogId, int topicId, int type, int classGuid, boolean fromCache, int old) { AndroidUtilities.runOnUIThread(() -> { boolean isEncryptedDialog = DialogObject.isEncryptedDialog(dialogId); boolean reload = fromCache && (count == -1 || count == 0 && type == 2) && !isEncryptedDialog; if (reload || old == 1 && !isEncryptedDialog) { - getMediaCount(dialogId, type, classGuid, false); + getMediaCount(dialogId, topicId, type, classGuid, false); } if (!reload) { if (!fromCache) { - putMediaCountDatabase(dialogId, type, count); + putMediaCountDatabase(dialogId, topicId, type, count); } - getNotificationCenter().postNotificationName(NotificationCenter.mediaCountDidLoad, dialogId, (fromCache && count == -1 ? 0 : count), fromCache, type); + getNotificationCenter().postNotificationName(NotificationCenter.mediaCountDidLoad, dialogId, topicId, (fromCache && count == -1 ? 0 : count), fromCache, type); } }); } - private void putMediaCountDatabase(long uid, int type, int count) { + private void putMediaCountDatabase(long uid, int topicId, int type, int count) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { - SQLitePreparedStatement state2 = getMessagesStorage().getDatabase().executeFast("REPLACE INTO media_counts_v2 VALUES(?, ?, ?, ?)"); + SQLitePreparedStatement state2; + if (topicId != 0) { + state2 = getMessagesStorage().getDatabase().executeFast("REPLACE INTO media_counts_topics VALUES(?, ?, ?, ?, ?)"); + } else { + state2 = getMessagesStorage().getDatabase().executeFast("REPLACE INTO media_counts_v2 VALUES(?, ?, ?, ?)"); + } + int pointer = 1; state2.requery(); - state2.bindLong(1, uid); - state2.bindInteger(2, type); - state2.bindInteger(3, count); - state2.bindInteger(4, 0); + state2.bindLong(pointer++, uid); + if (topicId != 0) { + state2.bindInteger(pointer++, topicId); + } + state2.bindInteger(pointer++, type); + state2.bindInteger(pointer++, count); + state2.bindInteger(pointer++, 0); state2.step(); state2.dispose(); } catch (Exception e) { @@ -3625,12 +3700,17 @@ public class MediaDataController extends BaseController { }); } - private void getMediaCountDatabase(long dialogId, int type, int classGuid) { + private void getMediaCountDatabase(long dialogId, int topicId, int type, int classGuid) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { int count = -1; int old = 0; - SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT count, old FROM media_counts_v2 WHERE uid = %d AND type = %d LIMIT 1", dialogId, type)); + SQLiteCursor cursor; + if (topicId != 0) { + cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT count, old FROM media_counts_topics WHERE uid = %d AND topic_id = %d AND type = %d LIMIT 1", dialogId, topicId, type)); + } else { + cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT count, old FROM media_counts_v2 WHERE uid = %d AND type = %d LIMIT 1", dialogId, type)); + } if (cursor.next()) { count = cursor.intValue(0); old = cursor.intValue(1); @@ -3644,17 +3724,17 @@ public class MediaDataController extends BaseController { cursor.dispose(); if (count != -1) { - putMediaCountDatabase(dialogId, type, count); + putMediaCountDatabase(dialogId, topicId, type, count); } } - processLoadedMediaCount(count, dialogId, type, classGuid, true, old); + processLoadedMediaCount(count, dialogId, topicId, type, classGuid, true, old); } catch (Exception e) { FileLog.e(e); } }); } - private void loadMediaDatabase(long uid, int count, int max_id, int min_id, int type, int classGuid, boolean isChannel, int fromCache, int requestIndex) { + private void loadMediaDatabase(long uid, int count, int max_id, int min_id, int type, int topicId, int classGuid, boolean isChannel, int fromCache, int requestIndex) { Runnable runnable = new Runnable() { @Override public void run() { @@ -3669,23 +3749,41 @@ public class MediaDataController extends BaseController { SQLiteDatabase database = getMessagesStorage().getDatabase(); boolean isEnd = false; boolean reverseMessages = false; + if (!DialogObject.isEncryptedDialog(uid)) { if (min_id == 0) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM media_holes_v2 WHERE uid = %d AND type = %d AND start IN (0, 1)", uid, type)); + if (topicId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM media_holes_topics WHERE uid = %d AND topic_id = %d AND type = %d AND start IN (0, 1)", uid, topicId, type)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM media_holes_v2 WHERE uid = %d AND type = %d AND start IN (0, 1)", uid, type)); + } if (cursor.next()) { isEnd = cursor.intValue(0) == 1; } else { cursor.dispose(); - cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM media_v4 WHERE uid = %d AND type = %d AND mid > 0", uid, type)); + if (topicId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM media_topics WHERE uid = %d AND topic_id = %d AND type = %d AND mid > 0", uid, topicId, type)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM media_v4 WHERE uid = %d AND type = %d AND mid > 0", uid, type)); + } if (cursor.next()) { int mid = cursor.intValue(0); if (mid != 0) { - SQLitePreparedStatement state = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); + SQLitePreparedStatement state; + if (topicId != 0) { + state = database.executeFast("REPLACE INTO media_holes_topics VALUES(?, ?, ?, ?, ?)"); + } else { + state = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); + } + int pointer = 1; state.requery(); - state.bindLong(1, uid); - state.bindInteger(2, type); - state.bindInteger(3, 0); - state.bindInteger(4, mid); + state.bindLong(pointer++, uid); + if (topicId != 0) { + state.bindInteger(pointer++, topicId); + } + state.bindInteger(pointer++, type); + state.bindInteger(pointer++, 0); + state.bindInteger(pointer++, mid); state.step(); state.dispose(); } @@ -3697,57 +3795,106 @@ public class MediaDataController extends BaseController { int holeMessageId = 0; if (max_id != 0) { int startHole = 0; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM media_holes_v2 WHERE uid = %d AND type = %d AND start <= %d ORDER BY end DESC LIMIT 1", uid, type, max_id)); + if (topicId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM media_holes_topics WHERE uid = %d AND topic_id = %d AND type = %d AND start <= %d ORDER BY end DESC LIMIT 1", uid, topicId, type, max_id)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM media_holes_v2 WHERE uid = %d AND type = %d AND start <= %d ORDER BY end DESC LIMIT 1", uid, type, max_id)); + } if (cursor.next()) { startHole = cursor.intValue(0); holeMessageId = cursor.intValue(1); } cursor.dispose(); - if (holeMessageId > 1) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v4 WHERE uid = %d AND mid > 0 AND mid < %d AND mid >= %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, max_id, holeMessageId, type, countToLoad)); - isEnd = false; + if (topicId != 0) { + if (holeMessageId > 1) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_topics WHERE uid = %d AND topic_id = %d AND mid > 0 AND mid < %d AND mid >= %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, topicId, max_id, holeMessageId, type, countToLoad)); + isEnd = false; + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_topics WHERE uid = %d AND topic_id = %d AND mid > 0 AND mid < %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, topicId, max_id, type, countToLoad)); + } } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v4 WHERE uid = %d AND mid > 0 AND mid < %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, max_id, type, countToLoad)); + if (holeMessageId > 1) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v4 WHERE uid = %d AND mid > 0 AND mid < %d AND mid >= %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, max_id, holeMessageId, type, countToLoad)); + isEnd = false; + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v4 WHERE uid = %d AND mid > 0 AND mid < %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, max_id, type, countToLoad)); + } } } else if (min_id != 0) { int startHole = 0; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM media_holes_v2 WHERE uid = %d AND type = %d AND end >= %d ORDER BY end ASC LIMIT 1", uid, type, min_id)); + if (topicId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM media_holes_topics WHERE uid = %d AND topic_id = %d AND type = %d AND end >= %d ORDER BY end ASC LIMIT 1", uid, topicId, type, min_id)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM media_holes_v2 WHERE uid = %d AND type = %d AND end >= %d ORDER BY end ASC LIMIT 1", uid, type, min_id)); + } if (cursor.next()) { startHole = cursor.intValue(0); holeMessageId = cursor.intValue(1); } cursor.dispose(); reverseMessages = true; - if (startHole > 1) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v4 WHERE uid = %d AND mid > 0 AND mid >= %d AND mid <= %d AND type = %d ORDER BY date ASC, mid ASC LIMIT %d", uid, min_id, startHole, type, countToLoad)); + if (topicId != 0) { + if (startHole > 1) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_topics WHERE uid = %d AND topic_id = %d AND mid > 0 AND mid >= %d AND mid <= %d AND type = %d ORDER BY date ASC, mid ASC LIMIT %d", uid, topicId, min_id, startHole, type, countToLoad)); + } else { + isEnd = true; + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_topics WHERE uid = %d AND topic_id = %d AND mid > 0 AND mid >= %d AND type = %d ORDER BY date ASC, mid ASC LIMIT %d", uid, topicId, min_id, type, countToLoad)); + } } else { - isEnd = true; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v4 WHERE uid = %d AND mid > 0 AND mid >= %d AND type = %d ORDER BY date ASC, mid ASC LIMIT %d", uid, min_id, type, countToLoad)); + if (startHole > 1) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v4 WHERE uid = %d AND mid > 0 AND mid >= %d AND mid <= %d AND type = %d ORDER BY date ASC, mid ASC LIMIT %d", uid, min_id, startHole, type, countToLoad)); + } else { + isEnd = true; + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v4 WHERE uid = %d AND mid > 0 AND mid >= %d AND type = %d ORDER BY date ASC, mid ASC LIMIT %d", uid, min_id, type, countToLoad)); + } } } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(end) FROM media_holes_v2 WHERE uid = %d AND type = %d", uid, type)); + if (topicId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(end) FROM media_holes_topics WHERE uid = %d AND topic_id = %d AND type = %d", uid, topicId, type)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(end) FROM media_holes_v2 WHERE uid = %d AND type = %d", uid, type)); + } if (cursor.next()) { holeMessageId = cursor.intValue(0); } cursor.dispose(); - if (holeMessageId > 1) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v4 WHERE uid = %d AND mid >= %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, holeMessageId, type, countToLoad)); + if (topicId != 0) { + if (holeMessageId > 1) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_topics WHERE uid = %d AND topic_id = %d AND mid >= %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, topicId, holeMessageId, type, countToLoad)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_topics WHERE uid = %d AND topic_id = %d AND mid > 0 AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, topicId, type, countToLoad)); + } } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v4 WHERE uid = %d AND mid > 0 AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, type, countToLoad)); + if (holeMessageId > 1) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v4 WHERE uid = %d AND mid >= %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, holeMessageId, type, countToLoad)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v4 WHERE uid = %d AND mid > 0 AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, type, countToLoad)); + } } } } else { isEnd = true; - if (max_id != 0) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_v4 as m LEFT JOIN randoms_v2 as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d AND type = %d ORDER BY m.mid ASC LIMIT %d", uid, max_id, type, countToLoad)); - } else if (min_id != 0) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_v4 as m LEFT JOIN randoms_v2 as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid < %d AND type = %d ORDER BY m.mid DESC LIMIT %d", uid, min_id, type, countToLoad)); + if (topicId != 0) { + if (max_id != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_topics as m LEFT JOIN randoms_v2 as r ON r.mid = m.mid WHERE m.uid = %d AND m.topic_id = %d AND m.mid > %d AND type = %d ORDER BY m.mid ASC LIMIT %d", uid, topicId, max_id, type, countToLoad)); + } else if (min_id != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_topics as m LEFT JOIN randoms_v2 as r ON r.mid = m.mid WHERE m.uid = %d AND m.topic_id = %d AND m.mid < %d AND type = %d ORDER BY m.mid DESC LIMIT %d", uid, topicId, min_id, type, countToLoad)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_topics as m LEFT JOIN randoms_v2 as r ON r.mid = m.mid WHERE m.uid = %d AND m.topic_id = %d AND type = %d ORDER BY m.mid ASC LIMIT %d", uid, topicId, type, countToLoad)); + } } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_v4 as m LEFT JOIN randoms_v2 as r ON r.mid = m.mid WHERE m.uid = %d AND type = %d ORDER BY m.mid ASC LIMIT %d", uid, type, countToLoad)); + if (max_id != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_v4 as m LEFT JOIN randoms_v2 as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d AND type = %d ORDER BY m.mid ASC LIMIT %d", uid, max_id, type, countToLoad)); + } else if (min_id != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_v4 as m LEFT JOIN randoms_v2 as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid < %d AND type = %d ORDER BY m.mid DESC LIMIT %d", uid, min_id, type, countToLoad)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_v4 as m LEFT JOIN randoms_v2 as r ON r.mid = m.mid WHERE m.uid = %d AND type = %d ORDER BY m.mid ASC LIMIT %d", uid, type, countToLoad)); + } } } + while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { @@ -3793,7 +3940,7 @@ public class MediaDataController extends BaseController { } finally { Runnable task = this; AndroidUtilities.runOnUIThread(() -> getMessagesStorage().completeTaskForGuid(task, classGuid)); - processLoadedMedia(res, uid, count, max_id, min_id, type, fromCache, classGuid, isChannel, topReached, requestIndex); + processLoadedMedia(res, uid, count, max_id, min_id, type, topicId, fromCache, classGuid, isChannel, topReached, requestIndex); } } }; @@ -3802,27 +3949,37 @@ public class MediaDataController extends BaseController { messagesStorage.bindTaskToGuid(runnable, classGuid); } - private void putMediaDatabase(long uid, int type, ArrayList messages, int max_id, int min_id, boolean topReached) { + private void putMediaDatabase(long uid, int topicId, int type, ArrayList messages, int max_id, int min_id, boolean topReached) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { if (min_id == 0 && (messages.isEmpty() || topReached)) { - getMessagesStorage().doneHolesInMedia(uid, max_id, type); + getMessagesStorage().doneHolesInMedia(uid, max_id, type, topicId); if (messages.isEmpty()) { return; } } getMessagesStorage().getDatabase().beginTransaction(); - SQLitePreparedStatement state2 = getMessagesStorage().getDatabase().executeFast("REPLACE INTO media_v4 VALUES(?, ?, ?, ?, ?)"); + SQLitePreparedStatement state2; + if (topicId != 0) { + state2 = getMessagesStorage().getDatabase().executeFast("REPLACE INTO media_topics VALUES(?, ?, ?, ?, ?, ?)"); + } else { + state2 = getMessagesStorage().getDatabase().executeFast("REPLACE INTO media_v4 VALUES(?, ?, ?, ?, ?)"); + } + for (TLRPC.Message message : messages) { if (canAddMessageToMedia(message)) { state2.requery(); NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); - state2.bindInteger(1, message.id); - state2.bindLong(2, uid); - state2.bindInteger(3, message.date); - state2.bindInteger(4, type); - state2.bindByteBuffer(5, data); + int pointer = 1; + state2.bindInteger(pointer++, message.id); + state2.bindLong(pointer++, uid); + if (topicId != 0) { + state2.bindInteger(pointer++, topicId); + } + state2.bindInteger(pointer++, message.date); + state2.bindInteger(pointer++, type); + state2.bindByteBuffer(pointer++, data); state2.step(); data.reuse(); } @@ -3831,11 +3988,11 @@ public class MediaDataController extends BaseController { if (!topReached || max_id != 0 || min_id != 0) { int minId = (topReached && min_id == 0) ? 1 : messages.get(messages.size() - 1).id; if (min_id != 0) { - getMessagesStorage().closeHolesInMedia(uid, minId, messages.get(0).id, type); + getMessagesStorage().closeHolesInMedia(uid, minId, messages.get(0).id, type, topicId); } else if (max_id != 0) { - getMessagesStorage().closeHolesInMedia(uid, minId, max_id, type); + getMessagesStorage().closeHolesInMedia(uid, minId, max_id, type, topicId); } else { - getMessagesStorage().closeHolesInMedia(uid, minId, Integer.MAX_VALUE, type); + getMessagesStorage().closeHolesInMedia(uid, minId, Integer.MAX_VALUE, type, topicId); } } getMessagesStorage().getDatabase().commitTransaction(); @@ -4932,7 +5089,7 @@ public class MediaDataController extends BaseController { } } - public void loadReplyMessagesForMessages(ArrayList messages, long dialogId, boolean scheduled, Runnable callback) { + public void loadReplyMessagesForMessages(ArrayList messages, long dialogId, boolean scheduled, int threadMessageId, Runnable callback) { if (DialogObject.isEncryptedDialog(dialogId)) { ArrayList replyMessages = new ArrayList<>(); LongSparseArray> replyMessageRandomOwners = new LongSparseArray<>(); @@ -5020,6 +5177,9 @@ public class MediaDataController extends BaseController { } if (messageObject.getId() > 0 && messageObject.isReply()) { int messageId = messageObject.messageOwner.reply_to.reply_to_msg_id; + if (messageId == threadMessageId) { + continue; + } long channelId = 0; if (messageObject.messageOwner.reply_to.reply_to_peer_id != null) { if (messageObject.messageOwner.reply_to.reply_to_peer_id.channel_id != 0) { @@ -5209,10 +5369,12 @@ public class MediaDataController extends BaseController { try { getMessagesStorage().getDatabase().beginTransaction(); SQLitePreparedStatement state; + SQLitePreparedStatement topicState = null; if (scheduled) { state = getMessagesStorage().getDatabase().executeFast("UPDATE scheduled_messages_v2 SET replydata = ?, reply_to_message_id = ? WHERE mid = ? AND uid = ?"); } else { state = getMessagesStorage().getDatabase().executeFast("UPDATE messages_v2 SET replydata = ?, reply_to_message_id = ? WHERE mid = ? AND uid = ?"); + topicState = getMessagesStorage().getDatabase().executeFast("UPDATE messages_topics SET replydata = ?, reply_to_message_id = ? WHERE mid = ? AND uid = ?"); } for (int a = 0; a < result.size(); a++) { TLRPC.Message message = result.get(a); @@ -5227,17 +5389,26 @@ public class MediaDataController extends BaseController { message.serializeToStream(data); for (int b = 0; b < messageObjects.size(); b++) { MessageObject messageObject = messageObjects.get(b); - state.requery(); - state.bindByteBuffer(1, data); - state.bindInteger(2, message.id); - state.bindInteger(3, messageObject.getId()); - state.bindLong(4, messageObject.getDialogId()); - state.step(); + for (int i = 0; i < 2; i++) { + SQLitePreparedStatement localState = i == 0 ? state : topicState; + if (localState == null) { + continue; + } + localState.requery(); + localState.bindByteBuffer(1, data); + localState.bindInteger(2, message.id); + localState.bindInteger(3, messageObject.getId()); + localState.bindLong(4, messageObject.getDialogId()); + localState.step(); + } } data.reuse(); } } state.dispose(); + if (topicState != null) { + topicState.dispose(); + } getMessagesStorage().getDatabase().commitTransaction(); } catch (Exception e) { FileLog.e(e); @@ -5931,13 +6102,17 @@ public class MediaDataController extends BaseController { } saveDraft(dialogId, threadId, draftMessage, replyToMessage, false); - if (threadId == 0) { + + if (threadId == 0 || ChatObject.isForum(currentAccount, dialogId)) { if (!DialogObject.isEncryptedDialog(dialogId)) { TLRPC.TL_messages_saveDraft req = new TLRPC.TL_messages_saveDraft(); req.peer = getMessagesController().getInputPeer(dialogId); if (req.peer == null) { return; } + if (threadId != 0) { + req.top_msg_id = threadId; + } req.message = draftMessage.message; req.no_webpage = draftMessage.no_webpage; req.reply_to_msg_id = draftMessage.reply_to_msg_id; @@ -6025,7 +6200,7 @@ public class MediaDataController extends BaseController { serializedData.cleanup(); } editor.commit(); - if (fromServer && threadId == 0) { + if (fromServer && (threadId == 0 || getMessagesController().isForum(dialogId))) { if (draft != null && draft.reply_to_msg_id != 0 && replyToMessage == null) { TLRPC.User user = null; TLRPC.Chat chat = null; @@ -7056,7 +7231,9 @@ public class MediaDataController extends BaseController { @Override public void run() { for (int i = 0; i < previewItems.size(); i++) { - previewItems.get(i).chatTheme.loadPreviewColors(0); + if (previewItems.get(i).chatTheme != null) { + previewItems.get(i).chatTheme.loadPreviewColors(0); + } } AndroidUtilities.runOnUIThread(() -> { defaultEmojiThemes.clear(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessageCustomParamsHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessageCustomParamsHelper.java index 33129ea8f..44f57a0fb 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessageCustomParamsHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessageCustomParamsHelper.java @@ -12,6 +12,7 @@ public class MessageCustomParamsHelper { !message.voiceTranscriptionOpen && !message.voiceTranscriptionFinal && !message.voiceTranscriptionRated && + !message.voiceTranscriptionForce && message.voiceTranscriptionId == 0 && !message.premiumEffectWasPlayed; } @@ -20,6 +21,7 @@ public class MessageCustomParamsHelper { toMessage.voiceTranscription = fromMessage.voiceTranscription; toMessage.voiceTranscriptionOpen = fromMessage.voiceTranscriptionOpen; toMessage.voiceTranscriptionFinal = fromMessage.voiceTranscriptionFinal; + toMessage.voiceTranscriptionForce = fromMessage.voiceTranscriptionForce; toMessage.voiceTranscriptionRated = fromMessage.voiceTranscriptionRated; toMessage.voiceTranscriptionId = fromMessage.voiceTranscriptionId; toMessage.premiumEffectWasPlayed = fromMessage.premiumEffectWasPlayed; @@ -66,11 +68,13 @@ public class MessageCustomParamsHelper { private Params_v1(TLRPC.Message message) { this.message = message; flags += message.voiceTranscription != null ? 1 : 0; + flags += message.voiceTranscriptionForce ? 2 : 0; } @Override public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(VERSION); + flags = message.voiceTranscriptionForce ? (flags | 2) : (flags &~ 2); stream.writeInt32(flags); if ((flags & 1) != 0) { stream.writeString(message.voiceTranscription); @@ -89,6 +93,7 @@ public class MessageCustomParamsHelper { if ((flags & 1) != 0) { message.voiceTranscription = stream.readString(exception); } + message.voiceTranscriptionForce = (flags & 2) != 0; message.voiceTranscriptionOpen = stream.readBool(exception); message.voiceTranscriptionFinal = stream.readBool(exception); message.voiceTranscriptionRated = stream.readBool(exception); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java index 2f98c549d..d6df1cfba 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java @@ -8,8 +8,6 @@ package org.telegram.messenger; -import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; @@ -30,6 +28,7 @@ import android.text.style.URLSpan; import android.text.util.Linkify; import android.util.Base64; +import androidx.annotation.IntDef; import androidx.collection.LongSparseArray; import org.telegram.PhoneFormat.PhoneFormat; @@ -43,6 +42,9 @@ import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.ChatMessageCell; import org.telegram.ui.Components.AnimatedEmojiDrawable; import org.telegram.ui.Components.AnimatedEmojiSpan; +import org.telegram.ui.Components.EmojiView; +import org.telegram.ui.Components.Forum.ForumBubbleDrawable; +import org.telegram.ui.Components.Forum.ForumUtilities; import org.telegram.ui.Components.Reactions.ReactionsLayoutInBubble; import org.telegram.ui.Components.Reactions.ReactionsUtils; import org.telegram.ui.Components.TextStyleSpan; @@ -60,6 +62,8 @@ import org.telegram.ui.Components.spoilers.SpoilerEffect; import java.io.BufferedReader; import java.io.File; import java.io.StringReader; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.net.URLEncoder; import java.util.AbstractMap; import java.util.ArrayList; @@ -80,13 +84,20 @@ public class MessageObject { public static final int MESSAGE_SEND_STATE_SEND_ERROR = 2; public static final int MESSAGE_SEND_STATE_EDITING = 3; + public static final int TYPE_TEXT = 0; public static final int TYPE_PHOTO = 1; public static final int TYPE_VOICE = 2; public static final int TYPE_VIDEO = 3; public static final int TYPE_GEO = 4; // TL_messageMediaGeo, TL_messageMediaVenue, TL_messageMediaGeoLive public static final int TYPE_ROUND_VIDEO = 5; + public static final int TYPE_GIF = 8; + public static final int TYPE_FILE = 9; + public static final int TYPE_ACTION_PHOTO = 11; + public static final int TYPE_CONTACT = 12; public static final int TYPE_STICKER = 13; + public static final int TYPE_MUSIC = 14; public static final int TYPE_ANIMATED_STICKER = 15; + public static final int TYPE_PHONE_CALL = 16; public static final int TYPE_POLL = 17; public static final int TYPE_GIFT_PREMIUM = 18; public static final int TYPE_EMOJIS = 19; @@ -104,9 +115,12 @@ public class MessageObject { public TLRPC.Message messageOwner; public TLRPC.Document emojiAnimatedSticker; public Long emojiAnimatedStickerId; + public boolean isTopicMainMessage; private boolean emojiAnimatedStickerLoading; public String emojiAnimatedStickerColor; public CharSequence messageText; + public CharSequence messageTextShort; + public CharSequence messageTextForReply; public CharSequence linkDescription; public CharSequence caption; public CharSequence youtubeDescription; @@ -158,6 +172,8 @@ public class MessageObject { public int sponsoredChannelPost; public TLRPC.ChatInvite sponsoredChatInvite; public String sponsoredChatInviteHash; + public boolean sponsoredShowPeerPhoto; + public boolean sponsoredRecommended; public String botStartParam; @@ -231,6 +247,7 @@ public class MessageObject { // forwarding preview params public boolean hideSendersName; public TLRPC.Peer sendAsPeer; + public ForumBubbleDrawable[] topicIconDrawable = new ForumBubbleDrawable[1]; static final String[] excludeWords = new String[]{ " vs. ", @@ -250,6 +267,8 @@ public class MessageObject { }; public Drawable customAvatarDrawable; + private byte[] randomWaveform; + public static boolean hasUnreadReactions(TLRPC.Message message) { if (message == null) { return false; @@ -281,6 +300,36 @@ public class MessageObject { return false; } + public static int getTopicId(TLRPC.Message message) { + if (message != null && message.action instanceof TLRPC.TL_messageActionTopicCreate) { + return message.id; + } + if (message == null || message.reply_to == null || !message.reply_to.forum_topic) { + return 0; + } + if (message instanceof TLRPC.TL_messageService && !(message.action instanceof TLRPC.TL_messageActionPinMessage)) { + int topicId = message.reply_to.reply_to_msg_id; + if (topicId == 0) { + topicId = message.reply_to.reply_to_top_id; + } + return topicId; + } else { + int topicId = message.reply_to.reply_to_top_id; + if (topicId == 0) { + topicId = message.reply_to.reply_to_msg_id; + } + return topicId; + } + } + + public static boolean isTopicActionMessage(MessageObject message) { + if (message == null || message.messageOwner == null) { + return false; + } + return message.messageOwner.action instanceof TLRPC.TL_messageActionTopicCreate || + message.messageOwner.action instanceof TLRPC.TL_messageActionTopicEdit; + } + public int getEmojiOnlyCount() { return emojiOnlyCount; } @@ -315,7 +364,7 @@ public class MessageObject { } } if (changed) { - MessagesStorage.getInstance(currentAccount).markMessageReactionsAsRead(messageOwner.dialog_id, messageOwner.id, true); + MessagesStorage.getInstance(currentAccount).markMessageReactionsAsRead(messageOwner.dialog_id, getTopicId(messageOwner), messageOwner.id, true); } } @@ -1190,13 +1239,12 @@ public class MessageObject { } if (emojiAnimatedSticker == null && emojiAnimatedStickerId == null) { generateLayout(null); + } else if (isSticker()) { + type = TYPE_STICKER; + } else if (isAnimatedSticker()) { + type = TYPE_ANIMATED_STICKER; } else { type = 1000; - if (isSticker()) { - type = TYPE_STICKER; - } else if (isAnimatedSticker()) { - type = TYPE_ANIMATED_STICKER; - } } } @@ -2153,6 +2201,112 @@ public class MessageObject { spannableStringBuilder.replace(i, i + "**new**".length(), newReactions); } messageText = spannableStringBuilder; + } else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionChangeUsernames) { + TLRPC.TL_channelAdminLogEventActionChangeUsernames log = (TLRPC.TL_channelAdminLogEventActionChangeUsernames) event.action; + + ArrayList oldUsernames = log.prev_value; + ArrayList newUsernames = log.new_value; + + messageText = null; + + if (oldUsernames != null && newUsernames != null) { + if (newUsernames.size() + 1 == oldUsernames.size()) { + String removed = null; + for (int i = 0; i < oldUsernames.size(); ++i) { + String username = oldUsernames.get(i); + if (!newUsernames.contains(username)) { + if (removed == null) { + removed = username; + } else { + removed = null; + break; + } + } + } + if (removed != null) { + messageText = replaceWithLink( + LocaleController.formatString("EventLogDeactivatedUsername", R.string.EventLogDeactivatedUsername, "@" + removed), + "un1", fromUser + ); + } + } else if (oldUsernames.size() + 1 == newUsernames.size()) { + String added = null; + for (int i = 0; i < newUsernames.size(); ++i) { + String username = newUsernames.get(i); + if (!oldUsernames.contains(username)) { + if (added == null) { + added = username; + } else { + added = null; + break; + } + } + } + if (added != null) { + messageText = replaceWithLink( + LocaleController.formatString("EventLogActivatedUsername", R.string.EventLogActivatedUsername, "@" + added), + "un1", fromUser + ); + } + } + } + + if (messageText == null) { + messageText = replaceWithLink( + LocaleController.formatString("EventLogChangeUsernames", R.string.EventLogChangeUsernames, getUsernamesString(oldUsernames), getUsernamesString(newUsernames)), + "un1", fromUser + ); + } + } else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionToggleForum) { + TLRPC.TL_channelAdminLogEventActionToggleForum toggleForum = (TLRPC.TL_channelAdminLogEventActionToggleForum) event.action; + if (toggleForum.new_value) { + messageText = replaceWithLink( + LocaleController.formatString("EventLogSwitchToForum", R.string.EventLogSwitchToForum), + "un1", fromUser + ); + } else { + messageText = replaceWithLink( + LocaleController.formatString("EventLogSwitchToGroup", R.string.EventLogSwitchToGroup), + "un1", fromUser + ); + } + } else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionCreateTopic) { + TLRPC.TL_channelAdminLogEventActionCreateTopic createTopic = (TLRPC.TL_channelAdminLogEventActionCreateTopic) event.action; + messageText = replaceWithLink( + LocaleController.formatString("EventLogCreateTopic", R.string.EventLogCreateTopic), + "un1", fromUser + ); + messageText = replaceWithLink(messageText, "un2", createTopic.topic); + } else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionEditTopic) { + TLRPC.TL_channelAdminLogEventActionEditTopic editTopic = (TLRPC.TL_channelAdminLogEventActionEditTopic) event.action; + messageText = replaceWithLink( + LocaleController.formatString("EventLogEditTopic", R.string.EventLogEditTopic), + "un1", fromUser + ); + messageText = replaceWithLink(messageText, "un2", editTopic.prev_topic); + messageText = replaceWithLink(messageText, "un3", editTopic.new_topic); + } else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionDeleteTopic) { + TLRPC.TL_channelAdminLogEventActionDeleteTopic deleteTopic = (TLRPC.TL_channelAdminLogEventActionDeleteTopic) event.action; + messageText = replaceWithLink( + LocaleController.getString("EventLogDeleteTopic", R.string.EventLogDeleteTopic), + "un1", fromUser + ); + messageText = replaceWithLink(messageText, "un2", deleteTopic.topic); + } else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionPinTopic) { + TLRPC.TL_channelAdminLogEventActionPinTopic pinTopic = (TLRPC.TL_channelAdminLogEventActionPinTopic) event.action; + if (pinTopic.new_topic instanceof TLRPC.TL_forumTopic && ((TLRPC.TL_forumTopic)pinTopic.new_topic).pinned) { + messageText = replaceWithLink( + LocaleController.formatString("EventLogPinTopic", R.string.EventLogPinTopic), + "un1", fromUser + ); + messageText = replaceWithLink(messageText, "un2", pinTopic.new_topic); + } else { + messageText = replaceWithLink( + LocaleController.formatString("EventLogUnpinTopic", R.string.EventLogUnpinTopic), + "un1", fromUser + ); + messageText = replaceWithLink(messageText, "un2", pinTopic.new_topic); + } } else { messageText = "unsupported " + event.action; } @@ -2266,6 +2420,21 @@ public class MessageObject { return LocaleController.getString("NoReactions", R.string.NoReactions); } + private String getUsernamesString(ArrayList usernames) { + if (usernames == null || usernames.size() == 0) { + return LocaleController.getString("UsernameEmpty", R.string.UsernameEmpty).toLowerCase(); + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < usernames.size(); ++i) { + sb.append("@"); + sb.append(usernames.get(i)); + if (i < usernames.size() - 1) { + sb.append(", "); + } + } + return sb.toString(); + } + private String getUserName(TLObject object, ArrayList entities, int offset) { String name; String username; @@ -2286,7 +2455,7 @@ public class MessageObject { } else { TLRPC.Chat chat = (TLRPC.Chat) object; name = chat.title; - username = chat.username; + username = ChatObject.getPublicUsername(chat); id = -chat.id; } if (offset >= 0) { @@ -2355,8 +2524,8 @@ public class MessageObject { fromUser = MessagesController.getInstance(currentAccount).getUser(messageOwner.from_id.user_id); } TLRPC.TL_game game = null; - if (replyMessageObject != null && replyMessageObject.getMedia(messageOwner) != null && replyMessageObject.getMedia(messageOwner).game != null) { - game = replyMessageObject.getMedia(messageOwner).game; + if (replyMessageObject != null && getMedia(replyMessageObject) != null && getMedia(replyMessageObject).game != null) { + game = getMedia(replyMessageObject).game; } if (game == null) { if (fromUser != null && fromUser.id == UserConfig.getInstance(currentAccount).getClientUserId()) { @@ -2395,11 +2564,11 @@ public class MessageObject { currency = ""; FileLog.e(e); } - if (replyMessageObject != null && replyMessageObject.getMedia(messageOwner) instanceof TLRPC.TL_messageMediaInvoice) { + if (replyMessageObject != null && getMedia(replyMessageObject) instanceof TLRPC.TL_messageMediaInvoice) { if (messageOwner.action.recurring_init) { - messageText = LocaleController.formatString(R.string.PaymentSuccessfullyPaidRecurrent, currency, name, replyMessageObject.getMedia(messageOwner).title); + messageText = LocaleController.formatString(R.string.PaymentSuccessfullyPaidRecurrent, currency, name, getMedia(replyMessageObject).title); } else { - messageText = LocaleController.formatString("PaymentSuccessfullyPaid", R.string.PaymentSuccessfullyPaid, currency, name, replyMessageObject.getMedia(messageOwner).title); + messageText = LocaleController.formatString("PaymentSuccessfullyPaid", R.string.PaymentSuccessfullyPaid, currency, name, getMedia(replyMessageObject).title); } } else { if (messageOwner.action.recurring_init) { @@ -2438,24 +2607,24 @@ public class MessageObject { messageText = replaceWithLink(LocaleController.getString("ActionPinnedRound", R.string.ActionPinnedRound), "un1", fromUser != null ? fromUser : chat); } else if ((replyMessageObject.isSticker() || replyMessageObject.isAnimatedSticker()) && !replyMessageObject.isAnimatedEmoji()) { messageText = replaceWithLink(LocaleController.getString("ActionPinnedSticker", R.string.ActionPinnedSticker), "un1", fromUser != null ? fromUser : chat); - } else if (replyMessageObject.getMedia(messageOwner) instanceof TLRPC.TL_messageMediaDocument) { + } else if (getMedia(replyMessageObject) instanceof TLRPC.TL_messageMediaDocument) { messageText = replaceWithLink(LocaleController.getString("ActionPinnedFile", R.string.ActionPinnedFile), "un1", fromUser != null ? fromUser : chat); - } else if (replyMessageObject.getMedia(messageOwner) instanceof TLRPC.TL_messageMediaGeo) { + } else if (getMedia(replyMessageObject) instanceof TLRPC.TL_messageMediaGeo) { messageText = replaceWithLink(LocaleController.getString("ActionPinnedGeo", R.string.ActionPinnedGeo), "un1", fromUser != null ? fromUser : chat); - } else if (replyMessageObject.getMedia(messageOwner) instanceof TLRPC.TL_messageMediaGeoLive) { + } else if (getMedia(replyMessageObject) instanceof TLRPC.TL_messageMediaGeoLive) { messageText = replaceWithLink(LocaleController.getString("ActionPinnedGeoLive", R.string.ActionPinnedGeoLive), "un1", fromUser != null ? fromUser : chat); - } else if (replyMessageObject.getMedia(messageOwner) instanceof TLRPC.TL_messageMediaContact) { + } else if (getMedia(replyMessageObject) instanceof TLRPC.TL_messageMediaContact) { messageText = replaceWithLink(LocaleController.getString("ActionPinnedContact", R.string.ActionPinnedContact), "un1", fromUser != null ? fromUser : chat); - } else if (replyMessageObject.getMedia(messageOwner) instanceof TLRPC.TL_messageMediaPoll) { - if (((TLRPC.TL_messageMediaPoll) replyMessageObject.getMedia(messageOwner)).poll.quiz) { + } else if (getMedia(replyMessageObject) instanceof TLRPC.TL_messageMediaPoll) { + if (((TLRPC.TL_messageMediaPoll) getMedia(replyMessageObject)).poll.quiz) { messageText = replaceWithLink(LocaleController.getString("ActionPinnedQuiz", R.string.ActionPinnedQuiz), "un1", fromUser != null ? fromUser : chat); } else { messageText = replaceWithLink(LocaleController.getString("ActionPinnedPoll", R.string.ActionPinnedPoll), "un1", fromUser != null ? fromUser : chat); } - } else if (replyMessageObject.getMedia(messageOwner) instanceof TLRPC.TL_messageMediaPhoto) { + } else if (getMedia(replyMessageObject) instanceof TLRPC.TL_messageMediaPhoto) { messageText = replaceWithLink(LocaleController.getString("ActionPinnedPhoto", R.string.ActionPinnedPhoto), "un1", fromUser != null ? fromUser : chat); - } else if (replyMessageObject.getMedia(messageOwner) instanceof TLRPC.TL_messageMediaGame) { - messageText = replaceWithLink(LocaleController.formatString("ActionPinnedGame", R.string.ActionPinnedGame, "\uD83C\uDFAE " + replyMessageObject.getMedia(messageOwner).game.title), "un1", fromUser != null ? fromUser : chat); + } else if (getMedia(replyMessageObject) instanceof TLRPC.TL_messageMediaGame) { + messageText = replaceWithLink(LocaleController.formatString("ActionPinnedGame", R.string.ActionPinnedGame, "\uD83C\uDFAE " + getMedia(replyMessageObject).game.title), "un1", fromUser != null ? fromUser : chat); messageText = Emoji.replaceEmoji(messageText, Theme.chat_msgTextPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); } else if (replyMessageObject.messageText != null && replyMessageObject.messageText.length() > 0) { CharSequence mess = AnimatedEmojiSpan.cloneSpans(replyMessageObject.messageText); @@ -3256,6 +3425,68 @@ public class MessageObject { generatePinMessageText(fromUser, chat); } else if (messageOwner.action instanceof TLRPC.TL_messageActionHistoryClear) { messageText = LocaleController.getString("HistoryCleared", R.string.HistoryCleared); + } else if (messageOwner.action instanceof TLRPC.TL_messageActionTopicCreate) { + messageText = LocaleController.getString("TopicCreated", R.string.TopicCreated); + + TLRPC.TL_messageActionTopicCreate createAction = (TLRPC.TL_messageActionTopicCreate) messageOwner.action; + TLRPC.TL_forumTopic forumTopic = new TLRPC.TL_forumTopic(); + forumTopic.icon_emoji_id = createAction.icon_emoji_id; + forumTopic.title = createAction.title; + forumTopic.icon_color = createAction.icon_color; + + messageTextShort = AndroidUtilities.replaceCharSequence("%s", LocaleController.getString("TopicWasCreatedAction", R.string.TopicWasCreatedAction), ForumUtilities.getTopicSpannedName(forumTopic, null)); + } else if (messageOwner.action instanceof TLRPC.TL_messageActionTopicEdit) { + TLRPC.TL_messageActionTopicEdit editAction = (TLRPC.TL_messageActionTopicEdit) messageOwner.action; + + String name = null; + if (fromUser != null) { + name = ContactsController.formatName(fromUser.first_name, fromUser.last_name); + } else if (fromChat != null) { + name = fromChat.title; + } + if (name != null) { + name = name.trim(); + } else { + name = "DELETED"; + } + + if ((messageOwner.action.flags & 4) > 0) { + if (((TLRPC.TL_messageActionTopicEdit) messageOwner.action).closed) { + messageText = LocaleController.formatString("TopicClosed2", R.string.TopicClosed2, name); + messageTextShort = LocaleController.getString("TopicClosed", R.string.TopicClosed); + } else { + messageText = LocaleController.formatString("TopicRestarted2", R.string.TopicRestarted2, name); + messageTextShort = LocaleController.getString("TopicRestarted", R.string.TopicRestarted); + } + } else { + if ((messageOwner.action.flags & 2) != 0 && (messageOwner.action.flags & 1) != 0) { + TLRPC.TL_forumTopic forumTopic = new TLRPC.TL_forumTopic(); + forumTopic.icon_emoji_id = editAction.icon_emoji_id; + forumTopic.title = editAction.title; + forumTopic.icon_color = ForumBubbleDrawable.serverSupportedColor[0]; + + CharSequence topicName = ForumUtilities.getTopicSpannedName(forumTopic, null, topicIconDrawable); + CharSequence str = AndroidUtilities.replaceCharSequence("%1$s", LocaleController.getString("TopicChangeIconAndTitleTo", R.string.TopicChangeIconAndTitleTo), name); + messageText = AndroidUtilities.replaceCharSequence("%2$s", str, topicName); + messageTextShort = LocaleController.getString("TopicRenamed", R.string.TopicRenamed); + messageTextForReply = AndroidUtilities.replaceCharSequence("%s", LocaleController.getString("TopicChangeIconAndTitleToInReply", R.string.TopicChangeIconAndTitleToInReply), topicName); + } else if ((messageOwner.action.flags & 2) != 0) { + TLRPC.TL_forumTopic forumTopic = new TLRPC.TL_forumTopic(); + forumTopic.icon_emoji_id = editAction.icon_emoji_id; + forumTopic.title = ""; + forumTopic.icon_color = ForumBubbleDrawable.serverSupportedColor[0]; + CharSequence topicName = ForumUtilities.getTopicSpannedName(forumTopic, null, topicIconDrawable); + CharSequence str = AndroidUtilities.replaceCharSequence("%1$s", LocaleController.getString("TopicIconChangedTo", R.string.TopicIconChangedTo), name); + messageText = AndroidUtilities.replaceCharSequence("%2$s", str, topicName); + messageTextShort = LocaleController.getString("TopicIconChanged", R.string.TopicIconChanged); + messageTextForReply = AndroidUtilities.replaceCharSequence("%s", LocaleController.getString("TopicIconChangedToInReply", R.string.TopicIconChangedToInReply), topicName); + } else if ((messageOwner.action.flags & 1) != 0) { + CharSequence str = AndroidUtilities.replaceCharSequence("%1$s", LocaleController.getString("TopicRenamedTo", R.string.TopicRenamedTo), name); + messageText = AndroidUtilities.replaceCharSequence("%2$s", str, editAction.title); + messageTextShort = LocaleController.getString("TopicRenamed", R.string.TopicRenamed); + messageTextForReply = AndroidUtilities.replaceCharSequence("%s", LocaleController.getString("TopicRenamedToInReply", R.string.TopicRenamedToInReply), editAction.title); + } + } } else if (messageOwner.action instanceof TLRPC.TL_messageActionGameScore) { generateGameMessageText(fromUser); } else if (messageOwner.action instanceof TLRPC.TL_messageActionPhoneCall) { @@ -3412,6 +3643,8 @@ public class MessageObject { } else if (getMedia(messageOwner) instanceof TLRPC.TL_messageMediaPhoto) { if (getMedia(messageOwner).ttl_seconds != 0 && !(messageOwner instanceof TLRPC.TL_message_secret)) { messageText = LocaleController.getString("AttachDestructingPhoto", R.string.AttachDestructingPhoto); + } else if (getGroupId() != 0) { + messageText = LocaleController.getString("Album", R.string.Album); } else { messageText = LocaleController.getString("AttachPhoto", R.string.AttachPhoto); } @@ -3483,6 +3716,13 @@ public class MessageObject { } } + public static TLRPC.MessageMedia getMedia(MessageObject messageObject) { + if (messageObject == null || messageObject.messageOwner == null) { + return null; + } + return getMedia(messageObject.messageOwner); + } + public static TLRPC.MessageMedia getMedia(TLRPC.Message messageOwner) { if (messageOwner.media != null && messageOwner.media.extended_media instanceof TLRPC.TL_messageExtendedMedia) { return ((TLRPC.TL_messageExtendedMedia) messageOwner.media.extended_media).media; @@ -3508,7 +3748,7 @@ public class MessageObject { isRoundVideoCached = 0; if (messageOwner instanceof TLRPC.TL_message || messageOwner instanceof TLRPC.TL_messageForwarded_old2) { if (isRestrictedMessage) { - type = 0; + type = TYPE_TEXT; } else if (emojiAnimatedSticker != null || emojiAnimatedStickerId != null) { if (isSticker()) { type = TYPE_STICKER; @@ -3518,9 +3758,9 @@ public class MessageObject { } else if (!isDice() && emojiOnlyCount >= 1 && !hasUnwrappedEmoji) { type = TYPE_EMOJIS; } else if (isMediaEmpty()) { - type = 0; + type = TYPE_TEXT; if (TextUtils.isEmpty(messageText) && eventId == 0) { - messageText = "Empty message"; + messageText = LocaleController.getString("EventLogOriginalCaptionEmpty", R.string.EventLogOriginalCaptionEmpty); } } else if (hasExtendedMediaPreview()) { type = TYPE_EXTENDED_MEDIA_PREVIEW; @@ -3551,43 +3791,43 @@ public class MessageObject { } else if (isVoice()) { type = TYPE_VOICE; } else if (isMusic()) { - type = 14; + type = TYPE_MUSIC; } else if (getMedia(messageOwner) instanceof TLRPC.TL_messageMediaContact) { - type = 12; + type = TYPE_CONTACT; } else if (getMedia(messageOwner) instanceof TLRPC.TL_messageMediaPoll) { type = TYPE_POLL; checkedVotes = new ArrayList<>(); } else if (getMedia(messageOwner) instanceof TLRPC.TL_messageMediaUnsupported) { - type = 0; + type = TYPE_TEXT; } else if (getMedia(messageOwner) instanceof TLRPC.TL_messageMediaDocument) { TLRPC.Document document = getDocument(); if (document != null && document.mime_type != null) { if (isGifDocument(document, hasValidGroupId())) { - type = 8; + type = TYPE_GIF; } else if (isSticker()) { type = TYPE_STICKER; } else if (isAnimatedSticker()) { type = TYPE_ANIMATED_STICKER; } else { - type = 9; + type = TYPE_FILE; } } else { - type = 9; + type = TYPE_FILE; } } else if (getMedia(messageOwner) instanceof TLRPC.TL_messageMediaGame) { - type = 0; + type = TYPE_TEXT; } else if (getMedia(messageOwner) instanceof TLRPC.TL_messageMediaInvoice) { - type = 0; + type = TYPE_TEXT; } } else if (messageOwner instanceof TLRPC.TL_messageService) { if (messageOwner.action instanceof TLRPC.TL_messageActionLoginUnknownLocation) { - type = 0; + type = TYPE_TEXT; } else if (messageOwner.action instanceof TLRPC.TL_messageActionGiftPremium) { contentType = 1; type = TYPE_GIFT_PREMIUM; } else if (messageOwner.action instanceof TLRPC.TL_messageActionChatEditPhoto || messageOwner.action instanceof TLRPC.TL_messageActionUserUpdatedPhoto) { contentType = 1; - type = 11; + type = TYPE_ACTION_PHOTO; } else if (messageOwner.action instanceof TLRPC.TL_messageEncryptedAction) { if (messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionScreenshotMessages || messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionSetMessageTTL) { contentType = 1; @@ -3600,7 +3840,7 @@ public class MessageObject { contentType = -1; type = -1; } else if (messageOwner.action instanceof TLRPC.TL_messageActionPhoneCall) { - type = 16; + type = TYPE_PHONE_CALL; } else { contentType = 1; type = 10; @@ -3613,7 +3853,7 @@ public class MessageObject { } public boolean checkLayout() { - if (type != 0 && type != TYPE_EMOJIS || messageOwner.peer_id == null || messageText == null || messageText.length() == 0) { + if (type != TYPE_TEXT && type != TYPE_EMOJIS || messageOwner.peer_id == null || messageText == null || messageText.length() == 0) { return false; } if (layoutCreated) { @@ -3990,30 +4230,33 @@ public class MessageObject { public static CharSequence replaceWithLink(CharSequence source, String param, TLObject object) { int start = TextUtils.indexOf(source, param); if (start >= 0) { - String name; + CharSequence name; String id; TLObject spanObject = null; if (object instanceof TLRPC.User) { - name = UserObject.getUserName((TLRPC.User) object); + name = UserObject.getUserName((TLRPC.User) object).replace('\n', ' '); id = "" + ((TLRPC.User) object).id; } else if (object instanceof TLRPC.Chat) { - name = ((TLRPC.Chat) object).title; + name = ((TLRPC.Chat) object).title.replace('\n', ' '); id = "" + -((TLRPC.Chat) object).id; } else if (object instanceof TLRPC.TL_game) { TLRPC.TL_game game = (TLRPC.TL_game) object; - name = game.title; + name = game.title.replace('\n', ' '); id = "game"; } else if (object instanceof TLRPC.TL_chatInviteExported) { TLRPC.TL_chatInviteExported invite = (TLRPC.TL_chatInviteExported) object; - name = invite.link; + name = invite.link.replace('\n', ' '); id = "invite"; spanObject = invite; + } else if (object instanceof TLRPC.ForumTopic) { + name = ForumUtilities.getTopicSpannedName((TLRPC.ForumTopic) object, null); + id = "topic"; + spanObject = object; } else { name = ""; id = "0"; } - name = name.replace('\n', ' '); - SpannableStringBuilder builder = new SpannableStringBuilder(TextUtils.replace(source, new String[]{param}, new String[]{name})); + SpannableStringBuilder builder = new SpannableStringBuilder(TextUtils.replace(source, new String[]{param}, new CharSequence[]{name})); URLSpanNoUnderlineBold span = new URLSpanNoUnderlineBold("" + id); span.setObject(spanObject); builder.setSpan(span, start, start + name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); @@ -4218,7 +4461,14 @@ public class MessageObject { } public boolean isVoiceTranscriptionOpen() { - return isVoice() && messageOwner != null && messageOwner.voiceTranscriptionOpen && messageOwner.voiceTranscription != null && (messageOwner.voiceTranscriptionFinal || TranscribeButton.isTranscribing(this)) && UserConfig.getInstance(currentAccount).isPremium(); + return ( + UserConfig.getInstance(currentAccount).isPremium() && + messageOwner != null && + (isVoice() || isRoundVideo() && TranscribeButton.isVideoTranscriptionOpen(this)) && + messageOwner.voiceTranscriptionOpen && + messageOwner.voiceTranscription != null && + (messageOwner.voiceTranscriptionFinal || TranscribeButton.isTranscribing(this)) + ); } public void generateCaption() { @@ -4821,15 +5071,18 @@ public class MessageObject { return true; } if (!isOut()) { - if (getMedia(messageOwner) instanceof TLRPC.TL_messageMediaGame || getMedia(messageOwner) instanceof TLRPC.TL_messageMediaInvoice && !hasExtendedMedia()) { + if (getMedia(messageOwner) instanceof TLRPC.TL_messageMediaGame || getMedia(messageOwner) instanceof TLRPC.TL_messageMediaInvoice && !hasExtendedMedia() || getMedia(messageOwner) instanceof TLRPC.TL_messageMediaWebPage) { return true; } TLRPC.Chat chat = messageOwner.peer_id != null && messageOwner.peer_id.channel_id != 0 ? getChat(null, null, messageOwner.peer_id.channel_id) : null; if (ChatObject.isChannel(chat) && chat.megagroup) { - return chat.username != null && chat.username.length() > 0 && !(getMedia(messageOwner) instanceof TLRPC.TL_messageMediaContact) && !(getMedia(messageOwner) instanceof TLRPC.TL_messageMediaGeo); + return ChatObject.isPublic(chat) && !(getMedia(messageOwner) instanceof TLRPC.TL_messageMediaContact) && !(getMedia(messageOwner) instanceof TLRPC.TL_messageMediaGeo); } } } else if (messageOwner.from_id instanceof TLRPC.TL_peerChannel || messageOwner.post) { + if (getMedia(messageOwner) instanceof TLRPC.TL_messageMediaWebPage && !isOutOwner()) { + return true; + } if (isSupergroup()) { return false; } @@ -4883,7 +5136,7 @@ public class MessageObject { } public void generateLayout(TLRPC.User fromUser) { - if (type != 0 && type != TYPE_EMOJIS || messageOwner.peer_id == null || TextUtils.isEmpty(messageText)) { + if (type != TYPE_TEXT && type != TYPE_EMOJIS || messageOwner.peer_id == null || TextUtils.isEmpty(messageText)) { return; } @@ -5205,7 +5458,7 @@ public class MessageObject { if (customAvatarDrawable != null) { return true; } - if (isSponsored() && isFromChat()) { + if (isSponsored() && (isFromChat() || sponsoredShowPeerPhoto)) { return true; } return !isSponsored() && (isFromUser() || isFromGroup() || eventId != 0 || messageOwner.fwd_from != null && messageOwner.fwd_from.saved_from_peer != null); @@ -5249,6 +5502,25 @@ public class MessageObject { } } + public static boolean peersEqual(TLRPC.Peer a, TLRPC.Peer b) { + if (a == null && b == null) { + return true; + } + if (a == null || b == null) { + return false; + } + if (a instanceof TLRPC.TL_peerChat && b instanceof TLRPC.TL_peerChat) { + return a.chat_id == b.chat_id; + } + if (a instanceof TLRPC.TL_peerChannel && b instanceof TLRPC.TL_peerChannel) { + return a.channel_id == b.channel_id; + } + if (a instanceof TLRPC.TL_peerUser && b instanceof TLRPC.TL_peerUser) { + return a.user_id == b.user_id; + } + return false; + } + public long getFromChatId() { return getFromChatId(messageOwner); } @@ -5552,7 +5824,7 @@ public class MessageObject { for (int a = 0; a < document.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = document.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeSticker || - attribute instanceof TLRPC.TL_documentAttributeCustomEmoji) { + attribute instanceof TLRPC.TL_documentAttributeCustomEmoji) { return "video/webm".equals(document.mime_type); } } @@ -5937,7 +6209,7 @@ public class MessageObject { } public int getApproximateHeight() { - if (type == 0) { + if (type == TYPE_TEXT) { int height = textHeight + (getMedia(messageOwner) instanceof TLRPC.TL_messageMediaWebPage && getMedia(messageOwner).webpage instanceof TLRPC.TL_webPage ? AndroidUtilities.dp(100) : 0); if (isReply()) { height += AndroidUtilities.dp(42); @@ -5947,17 +6219,17 @@ public class MessageObject { return AndroidUtilities.getPhotoSize(); } else if (type == TYPE_VOICE) { return AndroidUtilities.dp(72); - } else if (type == 12) { + } else if (type == TYPE_CONTACT) { return AndroidUtilities.dp(71); - } else if (type == 9) { + } else if (type == TYPE_FILE) { return AndroidUtilities.dp(100); - } else if (type == 4) { + } else if (type == TYPE_GEO) { return AndroidUtilities.dp(114); - } else if (type == 14) { + } else if (type == TYPE_MUSIC) { return AndroidUtilities.dp(82); } else if (type == 10) { return AndroidUtilities.dp(30); - } else if (type == 11 || type == TYPE_GIFT_PREMIUM) { + } else if (type == TYPE_ACTION_PHOTO || type == TYPE_GIFT_PREMIUM) { return AndroidUtilities.dp(50); } else if (type == TYPE_ROUND_VIDEO) { return AndroidUtilities.roundMessageSize; @@ -6460,7 +6732,7 @@ public class MessageObject { } public boolean canForwardMessage() { - return !(messageOwner instanceof TLRPC.TL_message_secret) && !needDrawBluredPreview() && !isLiveLocation() && type != 16 && !isSponsored() && !messageOwner.noforwards; + return !(messageOwner instanceof TLRPC.TL_message_secret) && !needDrawBluredPreview() && !isLiveLocation() && type != MessageObject.TYPE_PHONE_CALL && !isSponsored() && !messageOwner.noforwards; } public boolean canEditMedia() { @@ -6754,14 +7026,14 @@ public class MessageObject { } } } - if (!mediaExists && type == 8 || type == TYPE_VIDEO || type == 9 || type == TYPE_VOICE || type == 14 || type == TYPE_ROUND_VIDEO) { + if (!mediaExists && type == TYPE_GIF || type == TYPE_VIDEO || type == TYPE_FILE || type == TYPE_VOICE || type == TYPE_MUSIC || type == TYPE_ROUND_VIDEO) { if (messageOwner.attachPath != null && messageOwner.attachPath.length() > 0) { File f = new File(messageOwner.attachPath); attachPathExists = f.exists(); } if (!attachPathExists) { File file = FileLoader.getInstance(currentAccount).getPathToMessage(messageOwner, useFileDatabaseQueue); - if (type == 3 && needDrawBluredPreview()) { + if (type == TYPE_VIDEO && needDrawBluredPreview()) { mediaExists = new File(file.getAbsolutePath() + ".enc").exists(); } if (!mediaExists) { @@ -6777,13 +7049,13 @@ public class MessageObject { } else { mediaExists = FileLoader.getInstance(currentAccount).getPathToAttach(document, null, false, useFileDatabaseQueue).exists(); } - } else if (type == 0) { + } else if (type == MessageObject.TYPE_TEXT) { TLRPC.PhotoSize currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(photoThumbs, AndroidUtilities.getPhotoSize()); if (currentPhotoObject == null) { return; } mediaExists = FileLoader.getInstance(currentAccount).getPathToAttach(currentPhotoObject, null, true, useFileDatabaseQueue).exists(); - } else if (type == 11) { + } else if (type == MessageObject.TYPE_ACTION_PHOTO) { TLRPC.Photo photo = messageOwner.action.photo; if (photo == null || photo.video_sizes.isEmpty()) { return; @@ -7004,10 +7276,12 @@ public class MessageObject { int maxReactionsCount = MessagesController.getInstance(currentAccount).getMaxUserReactionsCount(); if (!choosenReactions.isEmpty() && (choosenReactions.contains(newReaction) || fromDoubleTap)) { - newReaction.chosen = false; - newReaction.count--; - if (newReaction.count <= 0) { - messageOwner.reactions.results.remove(newReaction); + if (newReaction != null) { + newReaction.chosen = false; + newReaction.count--; + if (newReaction.count <= 0) { + messageOwner.reactions.results.remove(newReaction); + } } if (messageOwner.reactions.can_see_list) { for (int i = 0; i < messageOwner.reactions.recent_reactions.size(); i++) { @@ -7093,4 +7367,29 @@ public class MessageObject { } return false; } + + public byte[] getWaveform() { + if (getDocument() == null) { + return null; + } + for (int a = 0; a < getDocument().attributes.size(); a++) { + TLRPC.DocumentAttribute attribute = getDocument().attributes.get(a); + if (attribute instanceof TLRPC.TL_documentAttributeAudio) { + if (attribute.waveform == null || attribute.waveform.length == 0) { + MediaController.getInstance().generateWaveform(this); + } + return attribute.waveform; + } + } + if (isRoundVideo()) { + if (randomWaveform == null) { + randomWaveform = new byte[120]; + for (int i = 0; i < randomWaveform.length; ++i) { + randomWaveform[i] = (byte) (255 * Math.random()); + } + } + return randomWaveform; + } + return null; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java index 2a74dac0a..eac9266f1 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java @@ -109,7 +109,7 @@ public class MessagesController extends BaseController implements NotificationCe public ConcurrentHashMap dialogs_read_inbox_max = new ConcurrentHashMap<>(100, 1.0f, 2); public ConcurrentHashMap dialogs_read_outbox_max = new ConcurrentHashMap<>(100, 1.0f, 2); public LongSparseArray dialogs_dict = new LongSparseArray<>(); - public LongSparseArray dialogMessage = new LongSparseArray<>(); + public LongSparseArray> dialogMessage = new LongSparseArray<>(); public LongSparseArray dialogMessagesByRandomIds = new LongSparseArray<>(); public LongSparseIntArray deletedHistory = new LongSparseIntArray(); public SparseArray dialogMessagesByIds = new SparseArray<>(); @@ -363,6 +363,7 @@ public class MessagesController extends BaseController implements NotificationCe public int reactionsUserMaxDefault; public int reactionsUserMaxPremium; public int reactionsInChatMax; + public int forumUpgradeParticipantsMin; public int uploadMaxFileParts; public int uploadMaxFilePartsPremium; @@ -376,6 +377,7 @@ public class MessagesController extends BaseController implements NotificationCe public volatile boolean ignoreSetOnline; public boolean premiumLocked; + public int transcribeButtonPressed; public List directPaymentsCurrency = new ArrayList<>(); @@ -384,13 +386,19 @@ public class MessagesController extends BaseController implements NotificationCe private long recentEmojiStatusUpdateRunnableTimeout, recentEmojiStatusUpdateRunnableTime; private Runnable recentEmojiStatusUpdateRunnable; private LongSparseArray emojiStatusUntilValues = new LongSparseArray<>(); + private TopicsController topicsController; - public void getNextReactionMention(long dialogId, int count, Consumer callback) { + public void getNextReactionMention(long dialogId, int topicId, int count, Consumer callback) { final MessagesStorage messagesStorage = getMessagesStorage(); messagesStorage.getStorageQueue().postRunnable(() -> { boolean needRequest = true; try { - SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT message_id FROM reaction_mentions WHERE state = 1 AND dialog_id = %d LIMIT 1", dialogId)); + SQLiteCursor cursor; + if (topicId != 0) { + cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT message_id FROM reaction_mentions_topics WHERE state = 1 AND dialog_id = %d AND topic_id = %d LIMIT 1", dialogId, topicId)); + } else { + cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT message_id FROM reaction_mentions WHERE state = 1 AND dialog_id = %d LIMIT 1", dialogId)); + } int messageId = 0; if (cursor.next()) { messageId = cursor.intValue(0); @@ -398,8 +406,7 @@ public class MessagesController extends BaseController implements NotificationCe } cursor.dispose(); if (messageId != 0) { - getMessagesStorage().markMessageReactionsAsRead(dialogId, messageId, false); - + getMessagesStorage().markMessageReactionsAsRead(dialogId, topicId, messageId, false); int finalMessageId = messageId; AndroidUtilities.runOnUIThread(() -> callback.accept(finalMessageId)); } @@ -495,6 +502,19 @@ public class MessagesController extends BaseController implements NotificationCe return !premiumLocked && currentUser.premium; } + public boolean didPressTranscribeButtonEnough() { + return transcribeButtonPressed >= 2; + } + + public void pressTranscribeButton() { + if (transcribeButtonPressed < 2) { + transcribeButtonPressed++; + if (mainPreferences != null) { + mainPreferences.edit().putInt("transcribeButtonPressed", transcribeButtonPressed).apply(); + } + } + } + public ArrayList filterPremiumStickers(ArrayList stickerSets) { if (!premiumLocked) { @@ -552,10 +572,34 @@ public class MessagesController extends BaseController implements NotificationCe return stickerSet; } - private class SponsoredMessagesInfo { - private ArrayList messages; - private long loadTime; - private boolean loading; + public TopicsController getTopicsController() { + return topicsController; + } + + public boolean isForum(long dialogId) { + TLRPC.Chat chatLocal = getChat(-dialogId); + return chatLocal != null && chatLocal.forum; + } + + public void markAllTopicsAsRead(long did) { + getMessagesStorage().loadTopics(did, topics -> { + AndroidUtilities.runOnUIThread(() -> { + if (topics != null) { + for (int i = 0; i < topics.size(); i++) { + TLRPC.TL_forumTopic topic = topics.get(i); + getMessagesController().markDialogAsRead(did, topic.top_message, 0, topic.topMessage.date, false, topic.id, 0, true, 0); + getMessagesStorage().updateRepliesMaxReadId(-did, topic.id, topic.top_message, 0, true); + } + } + }); + }); + } + + public class SponsoredMessagesInfo { + public ArrayList messages; + public Integer posts_between; + public long loadTime; + public boolean loading; } private class SendAsPeersInfo { @@ -682,7 +726,8 @@ public class MessagesController extends BaseController implements NotificationCe public static int UPDATE_MASK_REORDER = 131072; public static int UPDATE_MASK_EMOJI_INTERACTIONS = 262144; public static int UPDATE_MASK_EMOJI_STATUS = 524288; - public static int UPDATE_MASK_ALL = UPDATE_MASK_AVATAR | UPDATE_MASK_STATUS | UPDATE_MASK_NAME | UPDATE_MASK_CHAT_AVATAR | UPDATE_MASK_CHAT_NAME | UPDATE_MASK_CHAT_MEMBERS | UPDATE_MASK_USER_PRINT | UPDATE_MASK_USER_PHONE | UPDATE_MASK_READ_DIALOG_MESSAGE | UPDATE_MASK_PHONE; + public static int UPDATE_MASK_REACTIONS_READ = 1048576; + public static int UPDATE_MASK_ALL = UPDATE_MASK_AVATAR | UPDATE_MASK_STATUS | UPDATE_MASK_NAME | UPDATE_MASK_CHAT_AVATAR | UPDATE_MASK_CHAT_NAME | UPDATE_MASK_CHAT_MEMBERS | UPDATE_MASK_USER_PRINT | UPDATE_MASK_USER_PHONE | UPDATE_MASK_READ_DIALOG_MESSAGE | UPDATE_MASK_PHONE | UPDATE_MASK_REACTIONS_READ; public static int PROMO_TYPE_PROXY = 0; public static int PROMO_TYPE_PSA = 1; @@ -751,8 +796,9 @@ public class MessagesController extends BaseController implements NotificationCe MessagesController messagesController = accountInstance.getMessagesController(); ContactsController contactsController = accountInstance.getContactsController(); boolean skip = false; - if ((flags & DIALOG_FILTER_FLAG_EXCLUDE_MUTED) != 0 && messagesController.isDialogMuted(d.id) && d.unread_mentions_count == 0 || - (flags & DIALOG_FILTER_FLAG_EXCLUDE_READ) != 0 && d.unread_count == 0 && !d.unread_mark && d.unread_mentions_count == 0) { + + if ((flags & DIALOG_FILTER_FLAG_EXCLUDE_MUTED) != 0 && messagesController.isDialogMuted(d.id, 0) && d.unread_mentions_count == 0 || + (flags & DIALOG_FILTER_FLAG_EXCLUDE_READ) != 0 && messagesController.getDialogUnreadCount(d) == 0 && !d.unread_mark && d.unread_mentions_count == 0) { return false; } if (dialogId > 0) { @@ -1054,6 +1100,8 @@ public class MessagesController extends BaseController implements NotificationCe premiumInvoiceSlug = mainPreferences.getString("premiumInvoiceSlug", null); premiumBotUsername = mainPreferences.getString("premiumBotUsername", null); premiumLocked = mainPreferences.getBoolean("premiumLocked", false); + transcribeButtonPressed = mainPreferences.getInt("transcribeButtonPressed", 0); + forumUpgradeParticipantsMin = mainPreferences.getInt("forumUpgradeParticipantsMin", 200); BuildVars.GOOGLE_AUTH_CLIENT_ID = mainPreferences.getString("googleAuthClientId", BuildVars.GOOGLE_AUTH_CLIENT_ID); Set currencySet = mainPreferences.getStringSet("directPaymentsCurrency", null); @@ -1189,6 +1237,8 @@ public class MessagesController extends BaseController implements NotificationCe loadAppConfig(); }, 2000); } + + topicsController = new TopicsController(num); } @@ -1295,7 +1345,7 @@ public class MessagesController extends BaseController implements NotificationCe LongSparseArray new_dialogs_dict = new LongSparseArray<>(); SparseArray enc_chats_dict; - LongSparseArray new_dialogMessage = new LongSparseArray<>(); + LongSparseArray> new_dialogMessage = new LongSparseArray<>(); LongSparseArray usersDict = new LongSparseArray<>(); LongSparseArray chatsDict = new LongSparseArray<>(); @@ -1333,7 +1383,14 @@ public class MessagesController extends BaseController implements NotificationCe } MessageObject messageObject = new MessageObject(currentAccount, message, usersDict, chatsDict, false, false); newMessages.add(messageObject); - new_dialogMessage.put(messageObject.getDialogId(), messageObject); + long dialogId = messageObject.getDialogId(); + if (new_dialogMessage.containsKey(dialogId)) { + new_dialogMessage.get(dialogId).add(messageObject); + } else { + ArrayList arrayList = new ArrayList<>(1); + arrayList.add(messageObject); + new_dialogMessage.put(dialogId, arrayList); + } } getFileLoader().checkMediaExistance(newMessages); @@ -1352,9 +1409,18 @@ public class MessagesController extends BaseController implements NotificationCe promoDialog = d; } if (d.last_message_date == 0) { - MessageObject mess = new_dialogMessage.get(d.id); - if (mess != null) { - d.last_message_date = mess.messageOwner.date; + ArrayList arrayList = new_dialogMessage.get(d.id); + if (arrayList != null) { + int maxDate = Integer.MIN_VALUE; + for (int i = 0; i < arrayList.size(); ++i) { + MessageObject msg = arrayList.get(i); + if (msg != null && msg.messageOwner != null && maxDate < msg.messageOwner.date) { + maxDate = msg.messageOwner.date; + } + } + if (maxDate > Integer.MIN_VALUE) { + d.last_message_date = maxDate; + } } } if (DialogObject.isChannel(d)) { @@ -1454,37 +1520,91 @@ public class MessagesController extends BaseController implements NotificationCe for (int a = 0; a < new_dialogs_dict.size(); a++) { long key = new_dialogs_dict.keyAt(a); - TLRPC.Dialog value = new_dialogs_dict.valueAt(a); + TLRPC.Dialog newDialog = new_dialogs_dict.valueAt(a); TLRPC.Dialog currentDialog = dialogs_dict.get(key); - if (pinnedRemoteDialogs != null && pinnedRemoteDialogs.dialogs.contains(value)) { - if (value.draft instanceof TLRPC.TL_draftMessage) { - getMediaDataController().saveDraft(value.id, 0, value.draft, null, false); + if (pinnedRemoteDialogs != null && pinnedRemoteDialogs.dialogs.contains(newDialog)) { + if (newDialog.draft instanceof TLRPC.TL_draftMessage) { + getMediaDataController().saveDraft(newDialog.id, 0, newDialog.draft, null, false); } if (currentDialog != null) { - currentDialog.notify_settings = value.notify_settings; + currentDialog.notify_settings = newDialog.notify_settings; } } - MessageObject newMsg = new_dialogMessage.get(value.id); + ArrayList newMsgs = new_dialogMessage.get(newDialog.id); if (currentDialog == null) { - dialogs_dict.put(key, value); - dialogMessage.put(key, newMsg); - if (newMsg != null && newMsg.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.put(newMsg.getId(), newMsg); - if (newMsg.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.put(newMsg.messageOwner.random_id, newMsg); + dialogs_dict.put(key, newDialog); + dialogMessage.put(key, newMsgs); + if (newMsgs != null) { + for (int i = 0; i < newMsgs.size(); ++i) { + MessageObject msg = newMsgs.get(i); + if (msg != null && msg.messageOwner.peer_id.channel_id == 0) { + dialogMessagesByIds.put(msg.getId(), msg); + if (msg.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.put(msg.messageOwner.random_id, msg); + } + } } } } else { - currentDialog.pinned = value.pinned; - currentDialog.pinnedNum = value.pinnedNum; - MessageObject oldMsg = dialogMessage.get(key); - if (oldMsg != null && oldMsg.deleted || oldMsg == null || currentDialog.top_message > 0) { - if (value.top_message >= currentDialog.top_message) { - dialogs_dict.put(key, value); - dialogMessage.put(key, newMsg); - if (oldMsg != null) { + currentDialog.pinned = newDialog.pinned; + currentDialog.pinnedNum = newDialog.pinnedNum; + ArrayList oldMsgs = dialogMessage.get(key); + boolean oldMsgsDeleted = false; + for (int i = 0; oldMsgs != null && i < oldMsgs.size(); ++i) { + if (oldMsgs.get(i) != null && oldMsgs.get(i).deleted) { + oldMsgsDeleted = true; + break; + } + } + if (oldMsgsDeleted || oldMsgs == null || currentDialog.top_message > 0) { + if (newDialog.top_message >= currentDialog.top_message || (oldMsgs == null) != (newMsgs == null) || oldMsgs != null && newMsgs != null && oldMsgs.size() != newMsgs.size()) { + dialogs_dict.put(key, newDialog); + dialogMessage.put(key, newMsgs); + if (oldMsgs != null) { + for (int i = 0; i < oldMsgs.size(); ++i) { + MessageObject oldMsg = oldMsgs.get(i); + if (oldMsg == null) { + continue; + } + if (oldMsg.messageOwner.peer_id.channel_id == 0) { + dialogMessagesByIds.remove(oldMsg.getId()); + } + if (oldMsg.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.remove(oldMsg.messageOwner.random_id); + } + } + } + if (newMsgs != null) { + for (int i = 0; i < newMsgs.size(); ++i) { + MessageObject newMsg = newMsgs.get(i); + if (newMsg != null && newMsg.messageOwner.peer_id.channel_id == 0) { + for (int j = 0; oldMsgs != null && j < oldMsgs.size(); ++j) { + MessageObject oldMsg = oldMsgs.get(j); + if (oldMsg != null && oldMsg.getId() == newMsg.getId()) { + newMsg.deleted = oldMsg.deleted; + break; + } + } + dialogMessagesByIds.put(newMsg.getId(), newMsg); + if (newMsg.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.put(newMsg.messageOwner.random_id, newMsg); + } + } + } + } + } + } else { +// if (newMsg == null || newMsg.messageOwner.date > oldMsg.messageOwner.date) { + dialogs_dict.put(key, newDialog); + dialogMessage.put(key, newMsgs); + if (oldMsgs != null) { + for (int i = 0; i < oldMsgs.size(); ++i) { + MessageObject oldMsg = oldMsgs.get(i); + if (oldMsg == null) { + continue; + } if (oldMsg.messageOwner.peer_id.channel_id == 0) { dialogMessagesByIds.remove(oldMsg.getId()); } @@ -1492,37 +1612,24 @@ public class MessagesController extends BaseController implements NotificationCe dialogMessagesByRandomIds.remove(oldMsg.messageOwner.random_id); } } - if (newMsg != null && newMsg.messageOwner.peer_id.channel_id == 0) { - if (oldMsg != null && oldMsg.getId() == newMsg.getId()) { - newMsg.deleted = oldMsg.deleted; - } - dialogMessagesByIds.put(newMsg.getId(), newMsg); - if (newMsg.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.put(newMsg.messageOwner.random_id, newMsg); - } - } } - } else { - if (newMsg == null || newMsg.messageOwner.date > oldMsg.messageOwner.date) { - dialogs_dict.put(key, value); - dialogMessage.put(key, newMsg); - if (oldMsg.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.remove(oldMsg.getId()); - } - if (newMsg != null) { - if (oldMsg.getId() == newMsg.getId()) { - newMsg.deleted = oldMsg.deleted; - } - if (newMsg.messageOwner.peer_id.channel_id == 0) { + if (newMsgs != null) { + for (int i = 0; i < newMsgs.size(); ++i) { + MessageObject newMsg = newMsgs.get(i); + if (newMsg != null && newMsg.messageOwner.peer_id.channel_id == 0) { + for (int j = 0; oldMsgs != null && j < oldMsgs.size(); ++j) { + MessageObject oldMsg = oldMsgs.get(j); + if (oldMsg != null && oldMsg.getId() == newMsg.getId()) { + newMsg.deleted = oldMsg.deleted; + break; + } + } dialogMessagesByIds.put(newMsg.getId(), newMsg); if (newMsg.messageOwner.random_id != 0) { dialogMessagesByRandomIds.put(newMsg.messageOwner.random_id, newMsg); } } } - if (oldMsg.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.remove(oldMsg.messageOwner.random_id); - } } } } @@ -2500,6 +2607,17 @@ public class MessagesController extends BaseController implements NotificationCe } break; } + case "forum_upgrade_participants_min": { + if (value.value instanceof TLRPC.TL_jsonNumber) { + TLRPC.TL_jsonNumber number = (TLRPC.TL_jsonNumber) value.value; + if (number.value != forumUpgradeParticipantsMin) { + forumUpgradeParticipantsMin = (int) number.value; + editor.putInt("forumUpgradeParticipantsMin", forumUpgradeParticipantsMin); + changed = true; + } + } + break; + } } } if (changed) { @@ -3182,21 +3300,24 @@ public class MessagesController extends BaseController implements NotificationCe Integer msgId = (Integer) args[0]; Integer newMsgId = (Integer) args[1]; Long did = (Long) args[3]; - MessageObject obj = dialogMessage.get(did); - if (obj != null && (obj.getId() == msgId || obj.messageOwner.local_id == msgId)) { - obj.messageOwner.id = newMsgId; - obj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; + ArrayList dialogMessages = dialogMessage.get(did); + for (int i = 0; dialogMessages != null && i < dialogMessages.size(); ++i) { + MessageObject obj = dialogMessages.get(i); + if (obj != null && (obj.getId() == msgId || obj.messageOwner.local_id == msgId)) { + obj.messageOwner.id = newMsgId; + obj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; + } + obj = dialogMessagesByIds.get(msgId); + if (obj != null) { + dialogMessagesByIds.remove(msgId); + dialogMessagesByIds.put(newMsgId, obj); + } } TLRPC.Dialog dialog = dialogs_dict.get(did); if (dialog != null && dialog.top_message == msgId) { dialog.top_message = newMsgId; getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } - obj = dialogMessagesByIds.get(msgId); - if (obj != null) { - dialogMessagesByIds.remove(msgId); - dialogMessagesByIds.put(newMsgId, obj); - } if (DialogObject.isChatDialog(did)) { TLRPC.ChatFull chatFull = fullChats.get(-did); TLRPC.Chat chat = getChat(-did); @@ -3569,12 +3690,16 @@ public class MessagesController extends BaseController implements NotificationCe } public boolean putUser(TLRPC.User user, boolean fromCache) { + return putUser(user, fromCache, false); + } + + public boolean putUser(TLRPC.User user, boolean fromCache, boolean force) { if (user == null) { return false; } fromCache = fromCache && user.id / 1000 != 333 && user.id != 777000; TLRPC.User oldUser = users.get(user.id); - if (oldUser == user) { + if (oldUser == user && !force) { return false; } if (oldUser != null && !TextUtils.isEmpty(oldUser.username)) { @@ -3674,9 +3799,25 @@ public class MessagesController extends BaseController implements NotificationCe if (oldChat != null && !TextUtils.isEmpty(oldChat.username)) { objectsByUsernames.remove(oldChat.username.toLowerCase()); } + if (oldChat != null && oldChat.usernames != null) { + for (int i = 0; i < oldChat.usernames.size(); ++i) { + TLRPC.TL_username u = oldChat.usernames.get(i); + if (u != null && !TextUtils.isEmpty(u.username)) { + objectsByUsernames.remove(u.username.toLowerCase()); + } + } + } if (!TextUtils.isEmpty(chat.username)) { objectsByUsernames.put(chat.username.toLowerCase(), chat); } + if (chat.usernames != null) { + for (int i = 0; i < chat.usernames.size(); ++i) { + TLRPC.TL_username u = chat.usernames.get(i); + if (u != null && !TextUtils.isEmpty(u.username)) { + objectsByUsernames.put(u.username.toLowerCase(), chat); + } + } + } if (chat.min) { if (oldChat != null) { if (!fromCache) { @@ -3710,6 +3851,15 @@ public class MessagesController extends BaseController implements NotificationCe oldChat.participants_count = chat.participants_count; } addOrRemoveActiveVoiceChat(oldChat); + if (oldChat.forum != chat.forum) { + oldChat.forum = chat.forum; + if (oldChat.forum) { + oldChat.flags |= 1073741824; + } else { + oldChat.flags = oldChat.flags & ~1073741824; + } + getNotificationCenter().postNotificationName(NotificationCenter.chatSwithcedToForum, chat.id); + } } } else { chats.put(chat.id, chat); @@ -3789,6 +3939,9 @@ public class MessagesController extends BaseController implements NotificationCe } addOrRemoveActiveVoiceChat(chat); } + if (oldChat != null && oldChat.forum != chat.forum) { + getNotificationCenter().postNotificationName(NotificationCenter.chatSwithcedToForum, chat.id); + } } public void putChats(ArrayList chats, boolean fromCache) { @@ -4024,6 +4177,9 @@ public class MessagesController extends BaseController implements NotificationCe } public String getAdminRank(long chatId, long uid) { + if (chatId == uid) { + return ""; + } LongSparseArray array = channelAdmins.get(chatId); if (array == null) { return null; @@ -4085,9 +4241,9 @@ public class MessagesController extends BaseController implements NotificationCe public void loadFullChat(long chatId, int classGuid, boolean force) { boolean loaded = loadedFullChats.contains(chatId); - if (loadingFullChats.contains(chatId) || !force && loaded) { - return; - } +// if (loadingFullChats.contains(chatId) || !force && loaded) { +// return; +// } loadingFullChats.add(chatId); TLObject request; long dialogId = -chatId; @@ -4149,7 +4305,8 @@ public class MessagesController extends BaseController implements NotificationCe res.full_chat.inviterId = old.inviterId; } fullChats.put(chatId, res.full_chat); - applyDialogNotificationsSettings(-chatId, res.full_chat.notify_settings); + + applyDialogNotificationsSettings(-chatId, 0, res.full_chat.notify_settings); for (int a = 0; a < res.full_chat.bot_info.size(); a++) { TLRPC.BotInfo botInfo = res.full_chat.bot_info.get(a); getMediaDataController().putBotInfo(-chatId, botInfo); @@ -4220,7 +4377,7 @@ public class MessagesController extends BaseController implements NotificationCe AndroidUtilities.runOnUIThread(() -> { savePeerSettings(userFull.user.id, userFull.settings, false); - applyDialogNotificationsSettings(user.id, userFull.notify_settings); + applyDialogNotificationsSettings(user.id, 0, userFull.notify_settings); if (userFull.bot_info instanceof TLRPC.TL_botInfo) { userFull.bot_info.user_id = user.id; getMediaDataController().putBotInfo(user.id, userFull.bot_info); @@ -4348,7 +4505,7 @@ public class MessagesController extends BaseController implements NotificationCe } ImageLoader.saveMessagesThumbs(messagesRes.messages); - getMessagesStorage().putMessages(messagesRes, dialogId, -1, 0, false, scheduled); + getMessagesStorage().putMessages(messagesRes, dialogId, -1, 0, false, scheduled, 0); AndroidUtilities.runOnUIThread(() -> { ArrayList arrayList1 = reloadingMessages.get(dialogId); @@ -4358,21 +4515,24 @@ public class MessagesController extends BaseController implements NotificationCe reloadingMessages.remove(dialogId); } } - MessageObject dialogObj = dialogMessage.get(dialogId); - if (dialogObj != null) { - for (int a = 0; a < objects.size(); a++) { - MessageObject obj = objects.get(a); - if (dialogObj.getId() == obj.getId()) { - dialogMessage.put(dialogId, obj); - if (obj.messageOwner.peer_id.channel_id == 0) { - MessageObject obj2 = dialogMessagesByIds.get(obj.getId()); - dialogMessagesByIds.remove(obj.getId()); - if (obj2 != null) { - dialogMessagesByIds.put(obj2.getId(), obj2); + ArrayList dialogObjs = dialogMessage.get(dialogId); + if (dialogObjs != null) { + for (int i = 0; i < dialogObjs.size(); ++i) { + MessageObject dialogObj = dialogObjs.get(i); + for (int a = 0; a < objects.size(); a++) { + MessageObject obj = objects.get(a); + if (dialogObj.getId() == obj.getId()) { + dialogObjs.set(i, obj); + if (obj.messageOwner.peer_id.channel_id == 0) { + MessageObject obj2 = dialogMessagesByIds.get(obj.getId()); + dialogMessagesByIds.remove(obj.getId()); + if (obj2 != null) { + dialogMessagesByIds.put(obj2.getId(), obj2); + } } + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); + break; } - getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); - break; } } } @@ -5278,13 +5438,18 @@ public class MessagesController extends BaseController implements NotificationCe } public void markDialogMessageAsDeleted(long dialogId, ArrayList messages) { - MessageObject obj = dialogMessage.get(dialogId); - if (obj != null) { - for (int a = 0; a < messages.size(); a++) { - Integer id = messages.get(a); - if (obj.getId() == id) { - obj.deleted = true; - break; + ArrayList objs = dialogMessage.get(dialogId); + if (objs != null) { + for (int i = 0; i < objs.size(); ++i) { + MessageObject obj = objs.get(i); + if (obj != null) { + for (int a = 0; a < messages.size(); a++) { + Integer id = messages.get(a); + if (obj.getId() == id) { + obj.deleted = true; + break; + } + } } } } @@ -5761,8 +5926,8 @@ public class MessagesController extends BaseController implements NotificationCe getMessagesStorage().deleteDialog(did, onlyHistory); TLRPC.Dialog dialog = dialogs_dict.get(did); if (onlyHistory == 0 || onlyHistory == 3) { - getNotificationCenter().postNotificationName(NotificationCenter.dialogDeleted, did); - getNotificationsController().deleteNotificationChannel(did); + getNotificationCenter().postNotificationName(NotificationCenter.dialogDeleted, did, 0); + getNotificationsController().deleteNotificationChannel(did, 0); JoinCallAlert.processDeletedChat(currentAccount, did); } if (onlyHistory == 0) { @@ -5796,22 +5961,31 @@ public class MessagesController extends BaseController implements NotificationCe } if (!isPromoDialog) { int lastMessageId; - MessageObject object = dialogMessage.get(dialog.id); + ArrayList objects = dialogMessage.get(dialog.id); dialogMessage.remove(dialog.id); - if (object != null) { - lastMessageId = object.getId(); - if (object.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.remove(object.getId()); + if (objects != null && objects.size() > 0 && objects.get(0) != null) { + lastMessageId = objects.get(0).getId(); + for (int i = 0; i < objects.size(); ++i) { + MessageObject object = objects.get(i); + if (object != null && object.getId() > lastMessageId) { + lastMessageId = object.getId(); + } + if (object != null && object.messageOwner.peer_id.channel_id == 0) { + dialogMessagesByIds.remove(object.getId()); + } + if (object != null && object.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.remove(object.messageOwner.random_id); + } } } else { lastMessageId = dialog.top_message; - object = dialogMessagesByIds.get(dialog.top_message); + MessageObject object = dialogMessagesByIds.get(dialog.top_message); if (object != null && object.messageOwner.peer_id.channel_id == 0) { dialogMessagesByIds.remove(dialog.top_message); } - } - if (object != null && object.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.remove(object.messageOwner.random_id); + if (object != null && object.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.remove(object.messageOwner.random_id); + } } if (onlyHistory == 1 && !DialogObject.isEncryptedDialog(did) && lastMessageId > 0) { TLRPC.TL_messageService message = new TLRPC.TL_messageService(); @@ -5831,7 +6005,7 @@ public class MessagesController extends BaseController implements NotificationCe ArrayList arr = new ArrayList<>(); arr.add(message); updateInterfaceWithMessages(did, objArr, false); - getMessagesStorage().putMessages(arr, false, true, false, 0, false); + getMessagesStorage().putMessages(arr, false, true, false, 0, false, 0); } else { dialog.top_message = 0; } @@ -6620,7 +6794,15 @@ public class MessagesController extends BaseController implements NotificationCe chatsDict1.put(c.id, c); } MessageObject messageObject = new MessageObject(currentAccount, res2.messages.get(0), usersDict1, chatsDict1, false, true); - dialogMessage.put(did, messageObject); + ArrayList objects = dialogMessage.get(did); + if (objects == null) { + objects = new ArrayList<>(1); + } + if (objects.size() > 0 && objects.get(0) != null && objects.get(0).hasValidGroupId() && objects.get(0).getGroupIdForUse() != messageObject.getGroupIdForUse()) { + objects.clear(); + } + objects.add(messageObject); + dialogMessage.put(did, objects); if (promoDialog.last_message_date == 0) { promoDialog.last_message_date = messageObject.messageOwner.date; } @@ -7011,22 +7193,29 @@ public class MessagesController extends BaseController implements NotificationCe } } - public void loadMessages(long dialogId, long mergeDialogId, boolean loadInfo, int count, int max_id, int offset_date, boolean fromCache, int midDate, int classGuid, int load_type, int last_message_id, int mode, int threadMessageId, int replyFirstUnread, int loadIndex) { - loadMessages(dialogId, mergeDialogId, loadInfo, count, max_id, offset_date, fromCache, midDate, classGuid, load_type, last_message_id, mode, threadMessageId, loadIndex, threadMessageId != 0 ? replyFirstUnread : 0, 0, 0, false, 0); + public void loadMessages(long dialogId, long mergeDialogId, boolean loadInfo, int count, int max_id, int offset_date, boolean fromCache, int midDate, int classGuid, int load_type, int last_message_id, int mode, int threadMessageId, int replyFirstUnread, int loadIndex, boolean isTopic) { + loadMessages(dialogId, mergeDialogId, loadInfo, count, max_id, offset_date, fromCache, midDate, classGuid, load_type, last_message_id, mode, threadMessageId, loadIndex, threadMessageId != 0 ? replyFirstUnread : 0, 0, 0, false, 0, isTopic); } - public void loadMessages(long dialogId, long mergeDialogId, boolean loadInfo, int count, int max_id, int offset_date, boolean fromCache, int midDate, int classGuid, int load_type, int last_message_id, int mode, int threadMessageId, int loadIndex, int first_unread, int unread_count, int last_date, boolean queryFromServer, int mentionsCount) { - loadMessagesInternal(dialogId, mergeDialogId, loadInfo, count, max_id, offset_date, fromCache, midDate, classGuid, load_type, last_message_id, mode, threadMessageId, loadIndex, first_unread, unread_count, last_date, queryFromServer, mentionsCount, true, true); + public void loadMessages(long dialogId, long mergeDialogId, boolean loadInfo, int count, int max_id, int offset_date, boolean fromCache, int midDate, int classGuid, int load_type, int last_message_id, int mode, int threadMessageId, int loadIndex, int first_unread, int unread_count, int last_date, boolean queryFromServer, int mentionsCount, boolean isTopic) { + loadMessagesInternal(dialogId, mergeDialogId, loadInfo, count, max_id, offset_date, fromCache, midDate, classGuid, load_type, last_message_id, mode, threadMessageId, loadIndex, first_unread, unread_count, last_date, queryFromServer, mentionsCount, true, true, isTopic); } - private void loadMessagesInternal(long dialogId, long mergeDialogId, boolean loadInfo, int count, int max_id, int offset_date, boolean fromCache, int minDate, int classGuid, int load_type, int last_message_id, int mode, int threadMessageId, int loadIndex, int first_unread, int unread_count, int last_date, boolean queryFromServer, int mentionsCount, boolean loadDialog, boolean processMessages) { + private void loadMessagesInternal(long dialogId, long mergeDialogId, boolean loadInfo, int count, int max_id, int offset_date, boolean fromCache, int minDate, int classGuid, int load_type, int last_message_id, int mode, int threadMessageId, int loadIndex, int first_unread, int unread_count, int last_date, boolean queryFromServer, int mentionsCount, boolean loadDialog, boolean processMessages, boolean isTopic) { if (BuildVars.LOGS_ENABLED) { - FileLog.d("load messages in chat " + dialogId + " count " + count + " max_id " + max_id + " cache " + fromCache + " mindate = " + minDate + " guid " + classGuid + " load_type " + load_type + " last_message_id " + last_message_id + " mode " + mode + " index " + loadIndex + " firstUnread " + first_unread + " unread_count " + unread_count + " last_date " + last_date + " queryFromServer " + queryFromServer); + FileLog.d("load messages in chat " + dialogId + " count " + count + " max_id " + max_id + " cache " + fromCache + " mindate = " + minDate + " guid " + classGuid + " load_type " + load_type + " last_message_id " + last_message_id + " mode " + mode + " index " + loadIndex + " firstUnread " + first_unread + " unread_count " + unread_count + " last_date " + last_date + " queryFromServer " + queryFromServer + " isTopic " + isTopic); } - if (threadMessageId == 0 && mode != 2 && (fromCache || DialogObject.isEncryptedDialog(dialogId))) { - getMessagesStorage().getMessages(dialogId, mergeDialogId, loadInfo, count, max_id, offset_date, minDate, classGuid, load_type, mode == 1, threadMessageId, loadIndex, processMessages); + if ((threadMessageId == 0 || isTopic) && mode != 2 && (fromCache || DialogObject.isEncryptedDialog(dialogId))) { + getMessagesStorage().getMessages(dialogId, mergeDialogId, loadInfo, count, max_id, offset_date, minDate, classGuid, load_type, mode == 1, threadMessageId, loadIndex, processMessages, isTopic); } else { if (threadMessageId != 0) { + if (loadDialog && isTopic && load_type == 2 && last_message_id == 0) { + TLRPC.TL_forumTopic topic = topicsController.findTopic(-dialogId, threadMessageId); + if (topic != null) { + loadMessagesInternal(dialogId, mergeDialogId, loadInfo, count, max_id, offset_date, false, minDate, classGuid, load_type, topic.top_message, 0, threadMessageId, loadIndex, first_unread, topic.unread_count, last_date, queryFromServer, topic.unread_mentions_count, false, processMessages, isTopic); + return; + } + } if (mode != 0) { return; } @@ -7081,7 +7270,7 @@ public class MessagesController extends BaseController implements NotificationCe } } } - processLoadedMessages(res, res.messages.size(), dialogId, mergeDialogId, count, mid, offset_date, false, classGuid, fnid, last_message_id, unread_count, last_date, load_type, false, 0, threadMessageId, loadIndex, queryFromServer, mentionsCount, processMessages); + processLoadedMessages(res, res.messages.size(), dialogId, mergeDialogId, count, mid, offset_date, false, classGuid, fnid, last_message_id, unread_count, last_date, load_type, false, 0, threadMessageId, loadIndex, queryFromServer, mentionsCount, processMessages, isTopic); } else { AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.loadingMessagesFailed, classGuid, req, error)); } @@ -7110,7 +7299,7 @@ public class MessagesController extends BaseController implements NotificationCe } } } - processLoadedMessages(res, res.messages.size(), dialogId, mergeDialogId, count, mid, offset_date, false, classGuid, first_unread, last_message_id, unread_count, last_date, load_type, false, mode, threadMessageId, loadIndex, queryFromServer, mentionsCount, processMessages); + processLoadedMessages(res, res.messages.size(), dialogId, mergeDialogId, count, mid, offset_date, false, classGuid, first_unread, last_message_id, unread_count, last_date, load_type, false, mode, threadMessageId, loadIndex, queryFromServer, mentionsCount, processMessages, isTopic); } }); getConnectionsManager().bindRequestToGuid(reqId, classGuid); @@ -7137,7 +7326,7 @@ public class MessagesController extends BaseController implements NotificationCe getMessagesStorage().putDialogs(dialogs, 2); } - loadMessagesInternal(dialogId, mergeDialogId, loadInfo, count, max_id, offset_date, false, minDate, classGuid, load_type, dialog.top_message, 0, threadMessageId, loadIndex, first_unread, dialog.unread_count, last_date, queryFromServer, dialog.unread_mentions_count, false, processMessages); + loadMessagesInternal(dialogId, mergeDialogId, loadInfo, count, max_id, offset_date, false, minDate, classGuid, load_type, dialog.top_message, 0, threadMessageId, loadIndex, first_unread, dialog.unread_count, last_date, queryFromServer, dialog.unread_mentions_count, false, processMessages, isTopic); } } else { AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.loadingMessagesFailed, classGuid, req, error)); @@ -7185,7 +7374,7 @@ public class MessagesController extends BaseController implements NotificationCe } } } - processLoadedMessages(res, res.messages.size(), dialogId, mergeDialogId, count, mid, offset_date, false, classGuid, first_unread, last_message_id, unread_count, last_date, load_type, false, 0, threadMessageId, loadIndex, queryFromServer, mentionsCount, processMessages); + processLoadedMessages(res, res.messages.size(), dialogId, mergeDialogId, count, mid, offset_date, false, classGuid, first_unread, last_message_id, unread_count, last_date, load_type, false, 0, threadMessageId, loadIndex, queryFromServer, mentionsCount, processMessages, isTopic); } else { AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.loadingMessagesFailed, classGuid, req, error)); } @@ -7236,7 +7425,7 @@ public class MessagesController extends BaseController implements NotificationCe } } if (!messagesRes.messages.isEmpty()) { - getMessagesStorage().putMessages(messagesRes, dialogId, -2, 0, false, scheduled); + getMessagesStorage().putMessages(messagesRes, dialogId, -2, 0, false, scheduled, 0); getNotificationCenter().postNotificationName(NotificationCenter.replaceMessagesObjects, dialogId, arrayList1); } })); @@ -7244,10 +7433,11 @@ public class MessagesController extends BaseController implements NotificationCe } public void processLoadedMessages(TLRPC.messages_Messages messagesRes, int resCount, long dialogId, long mergeDialogId, int count, int max_id, int offset_date, boolean isCache, int classGuid, - int first_unread, int last_message_id, int unread_count, int last_date, int load_type, boolean isEnd, int mode, int threadMessageId, int loadIndex, boolean queryFromServer, int mentionsCount, boolean needProcess) { + int first_unread, int last_message_id, int unread_count, int last_date, int load_type, boolean isEnd, int mode, int threadMessageId, int loadIndex, boolean queryFromServer, int mentionsCount, boolean needProcess, boolean isTopic) { if (BuildVars.LOGS_ENABLED) { - FileLog.d("processLoadedMessages size " + messagesRes.messages.size() + " in chat " + dialogId + " count " + count + " max_id " + max_id + " cache " + isCache + " guid " + classGuid + " load_type " + load_type + " last_message_id " + last_message_id + " index " + loadIndex + " firstUnread " + first_unread + " unread_count " + unread_count + " last_date " + last_date + " queryFromServer " + queryFromServer); + FileLog.d("processLoadedMessages size " + messagesRes.messages.size() + " in chat " + dialogId + " count " + count + " max_id " + max_id + " cache " + isCache + " guid " + classGuid + " load_type " + load_type + " last_message_id " + last_message_id + " index " + loadIndex + " firstUnread " + first_unread + " unread_count " + unread_count + " last_date " + last_date + " queryFromServer " + queryFromServer + " isTopic" + isTopic); } + long startProcessTime = SystemClock.elapsedRealtime(); boolean createDialog = false; if (messagesRes instanceof TLRPC.TL_messages_channelMessages) { @@ -7276,7 +7466,7 @@ public class MessagesController extends BaseController implements NotificationCe if (mode == 1) { reload = ((SystemClock.elapsedRealtime() - lastScheduledServerQueryTime.get(dialogId, 0L)) > 60 * 1000); } else { - reload = resCount == 0 && (!isInitialLoading || (SystemClock.elapsedRealtime() - lastServerQueryTime.get(dialogId, 0L)) > 60 * 1000); + reload = resCount == 0 && (!isInitialLoading || (SystemClock.elapsedRealtime() - lastServerQueryTime.get(dialogId, 0L)) > 60 * 1000 || (isCache && isTopic)); if (mode == 0 && isCache && dialogId < 0 && !dialogs_dict.containsKey(dialogId) && (SystemClock.elapsedRealtime() - lastServerQueryTime.get(dialogId, 0L)) > 24 * 60 * 60 * 1000) { messagesRes.messages.clear(); reload = true; @@ -7303,7 +7493,7 @@ public class MessagesController extends BaseController implements NotificationCe lastServerQueryTime.put(dialogId, SystemClock.elapsedRealtime()); hash = 0; } - AndroidUtilities.runOnUIThread(() -> loadMessagesInternal(dialogId, mergeDialogId, false, count, load_type == 2 && queryFromServer ? first_unread : max_id, offset_date, false, hash, classGuid, load_type, last_message_id, mode, threadMessageId, loadIndex, first_unread, unread_count, last_date, queryFromServer, mentionsCount, true, needProcess)); + AndroidUtilities.runOnUIThread(() -> loadMessagesInternal(dialogId, mergeDialogId, false, count, load_type == 2 && queryFromServer ? first_unread : max_id, offset_date, false, hash, classGuid, load_type, last_message_id, mode, threadMessageId, loadIndex, first_unread, unread_count, last_date, queryFromServer, mentionsCount, true, needProcess, isTopic)); if (messagesRes.messages.isEmpty()) { return; } @@ -7354,8 +7544,8 @@ public class MessagesController extends BaseController implements NotificationCe } } } - if (threadMessageId == 0) { - getMessagesStorage().putMessages(messagesRes, dialogId, load_type, max_id, createDialog, mode == 1); + if (threadMessageId == 0 || isTopic) { + getMessagesStorage().putMessages(messagesRes, dialogId, load_type, max_id, createDialog, mode == 1, threadMessageId); } } @@ -7410,7 +7600,7 @@ public class MessagesController extends BaseController implements NotificationCe putUsers(messagesRes.users, isCache); putChats(messagesRes.chats, isCache); - if (messagesRes.animatedEmoji != null) { + if (messagesRes.animatedEmoji != null && needProcess) { AnimatedEmojiDrawable.getDocumentFetcher(currentAccount).processDocuments(messagesRes.animatedEmoji); } int first_unread_final; @@ -7436,7 +7626,7 @@ public class MessagesController extends BaseController implements NotificationCe if (!DialogObject.isEncryptedDialog(dialogId)) { int finalFirst_unread_final = first_unread_final; - getMediaDataController().loadReplyMessagesForMessages(objects, dialogId, mode == 1, () -> { + getMediaDataController().loadReplyMessagesForMessages(objects, dialogId, mode == 1, threadMessageId, () -> { if (!needProcess) { getNotificationCenter().postNotificationName(NotificationCenter.messagesDidLoadWithoutProcess, classGuid, resCount, isCache, isEnd, last_message_id); } else { @@ -7703,17 +7893,26 @@ public class MessagesController extends BaseController implements NotificationCe continue; } if (!DialogObject.isEncryptedDialog(dialog.id) && dialog.top_message > 0) { - MessageObject message = dialogMessage.get(dialog.id); - if (message != null && message.getId() > 0) { - req.offset_date = message.messageOwner.date; - req.offset_id = message.messageOwner.id; + ArrayList dialogMessages = dialogMessage.get(dialog.id); + MessageObject lastMessage = null; + if (dialogMessages != null) { + for (int i = 0; i < dialogMessages.size(); ++i) { + MessageObject message = dialogMessages.get(i); + if (message != null && (lastMessage == null || message.getId() > lastMessage.getId())) { + lastMessage = dialogMessages.get(i); + } + } + } + if (lastMessage != null && lastMessage.getId() > 0) { + req.offset_date = lastMessage.messageOwner.date; + req.offset_id = lastMessage.messageOwner.id; long id; - if (message.messageOwner.peer_id.channel_id != 0) { - id = -message.messageOwner.peer_id.channel_id; - } else if (message.messageOwner.peer_id.chat_id != 0) { - id = -message.messageOwner.peer_id.chat_id; + if (lastMessage.messageOwner.peer_id.channel_id != 0) { + id = -lastMessage.messageOwner.peer_id.channel_id; + } else if (lastMessage.messageOwner.peer_id.chat_id != 0) { + id = -lastMessage.messageOwner.peer_id.chat_id; } else { - id = message.messageOwner.peer_id.user_id; + id = lastMessage.messageOwner.peer_id.user_id; } req.offset_peer = getInputPeer(id); found = true; @@ -7823,7 +8022,7 @@ public class MessagesController extends BaseController implements NotificationCe editor.putInt("EnableChannel2", notify_settings.mute_until); } } - applySoundSettings(notify_settings.android_sound, editor, 0, type, false); + getNotificationsController().getNotificationsSettingsFacade().applySoundSettings(notify_settings.android_sound, editor, 0, 0, type, false); editor.commit(); if (loadingNotificationSettings == 0) { getUserConfig().notificationsSettingsLoaded = true; @@ -8004,7 +8203,7 @@ public class MessagesController extends BaseController implements NotificationCe resetDialogsAll.chats.addAll(resetDialogsPinned.chats); LongSparseArray new_dialogs_dict = new LongSparseArray<>(); - LongSparseArray new_dialogMessage = new LongSparseArray<>(); + LongSparseArray> new_dialogMessage = new LongSparseArray<>(); LongSparseArray usersDict = new LongSparseArray<>(); LongSparseArray chatsDict = new LongSparseArray<>(); @@ -8037,7 +8236,13 @@ public class MessagesController extends BaseController implements NotificationCe } } MessageObject messageObject = new MessageObject(currentAccount, message, usersDict, chatsDict, false, true); - new_dialogMessage.put(messageObject.getDialogId(), messageObject); + long did = messageObject.getDialogId(); + ArrayList arrayList = new_dialogMessage.get(did); + if (arrayList == null) { + arrayList = new ArrayList(1); + } + arrayList.add(messageObject); + new_dialogMessage.put(did, arrayList); } for (int a = 0; a < resetDialogsAll.dialogs.size(); a++) { @@ -8047,9 +8252,18 @@ public class MessagesController extends BaseController implements NotificationCe continue; } if (d.last_message_date == 0) { - MessageObject mess = new_dialogMessage.get(d.id); - if (mess != null) { - d.last_message_date = mess.messageOwner.date; + ArrayList messages = new_dialogMessage.get(d.id); + if (messages != null) { + int maxDate = Integer.MIN_VALUE; + for (int i = 0; i < messages.size(); ++i) { + MessageObject msg = messages.get(i); + if (msg != null && msg.messageOwner != null && msg.messageOwner.date > maxDate) { + maxDate = msg.messageOwner.date; + } + } + if (maxDate > Integer.MIN_VALUE) { + d.last_message_date = maxDate; + } } } if (DialogObject.isChannel(d)) { @@ -8110,7 +8324,7 @@ public class MessagesController extends BaseController implements NotificationCe } } - protected void completeDialogsReset(final TLRPC.messages_Dialogs dialogsRes, int messagesCount, int seq, int newPts, int date, int qts, LongSparseArray new_dialogs_dict, LongSparseArray new_dialogMessage, TLRPC.Message lastMessage) { + protected void completeDialogsReset(final TLRPC.messages_Dialogs dialogsRes, int messagesCount, int seq, int newPts, int date, int qts, LongSparseArray new_dialogs_dict, LongSparseArray> new_dialogMessage, TLRPC.Message lastMessage) { Utilities.stageQueue.postRunnable(() -> { gettingDifference = false; getMessagesStorage().setLastPtsValue(newPts); @@ -8133,14 +8347,19 @@ public class MessagesController extends BaseController implements NotificationCe TLRPC.Dialog oldDialog = allDialogs.get(a); if (!DialogObject.isEncryptedDialog(oldDialog.id)) { dialogs_dict.remove(oldDialog.id); - MessageObject messageObject = dialogMessage.get(oldDialog.id); + ArrayList messages = dialogMessage.get(oldDialog.id); dialogMessage.remove(oldDialog.id); - if (messageObject != null) { - if (messageObject.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.remove(messageObject.getId()); - } - if (messageObject.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.remove(messageObject.messageOwner.random_id); + if (messages != null) { + for (int i = 0; i < messages.size(); ++i) { + MessageObject message = messages.get(i); + if (message != null) { + if (message.messageOwner.peer_id.channel_id == 0) { + dialogMessagesByIds.remove(message.getId()); + } + if (message.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.remove(message.messageOwner.random_id); + } + } } } } @@ -8153,13 +8372,18 @@ public class MessagesController extends BaseController implements NotificationCe mediaDataController.saveDraft(value.id, 0, value.draft, null, false); } dialogs_dict.put(key, value); - MessageObject messageObject = new_dialogMessage.get(value.id); - dialogMessage.put(key, messageObject); - if (messageObject != null && messageObject.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.put(messageObject.getId(), messageObject); - dialogsLoadedTillDate = Math.min(dialogsLoadedTillDate, messageObject.messageOwner.date); - if (messageObject.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.put(messageObject.messageOwner.random_id, messageObject); + ArrayList messageObjects = new_dialogMessage.get(value.id); + dialogMessage.put(key, messageObjects); + if (messageObjects != null) { + for (int i = 0; i < messageObjects.size(); ++i) { + MessageObject messageObject = messageObjects.get(i); + if (messageObject != null && messageObject.messageOwner.peer_id.channel_id == 0) { + dialogMessagesByIds.put(messageObject.getId(), messageObject); + dialogsLoadedTillDate = Math.min(dialogsLoadedTillDate, messageObject.messageOwner.date); + if (messageObject.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.put(messageObject.messageOwner.random_id, messageObject); + } + } } } } @@ -8429,7 +8653,7 @@ public class MessagesController extends BaseController implements NotificationCe LongSparseArray new_dialogs_dict = new LongSparseArray<>(); SparseArray enc_chats_dict; - LongSparseArray new_dialogMessage = new LongSparseArray<>(); + LongSparseArray> new_dialogMessage = new LongSparseArray<>(); LongSparseArray usersDict = new LongSparseArray<>(); LongSparseArray chatsDict = new LongSparseArray<>(); @@ -8474,7 +8698,13 @@ public class MessagesController extends BaseController implements NotificationCe } MessageObject messageObject = new MessageObject(currentAccount, message, usersDict, chatsDict, false, false); newMessages.add(messageObject); - new_dialogMessage.put(messageObject.getDialogId(), messageObject); + long did = messageObject.getDialogId(); + ArrayList arrayList = new_dialogMessage.get(did); + if (arrayList == null) { + arrayList = new ArrayList(1); + } + arrayList.add(messageObject); + new_dialogMessage.put(did, arrayList); } getFileLoader().checkMediaExistance(newMessages); @@ -8554,9 +8784,18 @@ public class MessagesController extends BaseController implements NotificationCe promoDialog = d; } if (d.last_message_date == 0) { - MessageObject mess = new_dialogMessage.get(d.id); - if (mess != null) { - d.last_message_date = mess.messageOwner.date; + ArrayList messages = new_dialogMessage.get(d.id); + if (messages != null) { + int maxDate = Integer.MIN_VALUE; + for (int i = 0; i < messages.size(); ++i) { + MessageObject msg = messages.get(i); + if (msg != null && msg.messageOwner != null && msg.messageOwner.date > maxDate) { + maxDate = msg.messageOwner.date; + } + } + if (maxDate > Integer.MIN_VALUE) { + d.last_message_date = maxDate; + } } } boolean allowCheck = true; @@ -8643,6 +8882,7 @@ public class MessagesController extends BaseController implements NotificationCe } putUsers(dialogsRes.users, loadType == DIALOGS_LOAD_TYPE_CACHE); putChats(dialogsRes.chats, loadType == DIALOGS_LOAD_TYPE_CACHE); + if (encChats != null) { for (int a = 0; a < encChats.size(); a++) { TLRPC.EncryptedChat encryptedChat = encChats.get(a); @@ -8676,15 +8916,20 @@ public class MessagesController extends BaseController implements NotificationCe if (value.folder_id != folderId) { archivedDialogsCount++; } - MessageObject newMsg = new_dialogMessage.get(value.id); + ArrayList newMsgs = new_dialogMessage.get(value.id); if (currentDialog == null) { added = true; dialogs_dict.put(key, value); - dialogMessage.put(key, newMsg); - if (newMsg != null && newMsg.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.put(newMsg.getId(), newMsg); - if (newMsg.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.put(newMsg.messageOwner.random_id, newMsg); + dialogMessage.put(key, newMsgs); + if (newMsgs != null) { + for (int i = 0; i < newMsgs.size(); ++i) { + MessageObject newMsg = newMsgs.get(i); + if (newMsg != null && newMsg.messageOwner.peer_id.channel_id == 0) { + dialogMessagesByIds.put(newMsg.getId(), newMsg); + if (newMsg.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.put(newMsg.messageOwner.random_id, newMsg); + } + } } } } else { @@ -8693,12 +8938,62 @@ public class MessagesController extends BaseController implements NotificationCe } currentDialog.pinned = value.pinned; currentDialog.pinnedNum = value.pinnedNum; - MessageObject oldMsg = dialogMessage.get(key); - if (oldMsg != null && oldMsg.deleted || oldMsg == null || currentDialog.top_message > 0) { - if (value.top_message >= currentDialog.top_message) { + ArrayList oldMsgs = dialogMessage.get(key); + + boolean oldMsgsDeleted = false; + for (int i = 0; oldMsgs != null && i < oldMsgs.size(); ++i) { + if (oldMsgs.get(i) != null && oldMsgs.get(i).deleted) { + oldMsgsDeleted = true; + break; + } + } + if (oldMsgsDeleted || oldMsgs == null || currentDialog.top_message > 0) { + if (value.top_message >= currentDialog.top_message || (oldMsgs == null) != (newMsgs == null) || oldMsgs != null && newMsgs != null && oldMsgs.size() != newMsgs.size()) { dialogs_dict.put(key, value); - dialogMessage.put(key, newMsg); - if (oldMsg != null) { + dialogMessage.put(key, newMsgs); + if (oldMsgs != null) { + for (int i = 0; i < oldMsgs.size(); ++i) { + MessageObject oldMsg = oldMsgs.get(i); + if (oldMsg == null) { + continue; + } + if (oldMsg.messageOwner.peer_id.channel_id == 0) { + dialogMessagesByIds.remove(oldMsg.getId()); + } + if (oldMsg.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.remove(oldMsg.messageOwner.random_id); + } + } + } + if (newMsgs != null) { + for (int i = 0; i < newMsgs.size(); ++i) { + MessageObject newMsg = newMsgs.get(i); + if (newMsg != null && newMsg.messageOwner.peer_id.channel_id == 0) { + for (int j = 0; oldMsgs != null && j < oldMsgs.size(); ++j) { + MessageObject oldMsg = oldMsgs.get(j); + if (oldMsg != null && oldMsg.getId() == newMsg.getId()) { + newMsg.deleted = oldMsg.deleted; + break; + } + } + dialogMessagesByIds.put(newMsg.getId(), newMsg); + if (newMsg.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.put(newMsg.messageOwner.random_id, newMsg); + } + } + } + } + } + } else { +// if (newMsg == null && oldMs.getId() > 0 || newMsg != null && newMsg.messageOwner.date > oldMsg.messageOwner.date) + dialogs_dict.put(key, value); + dialogMessage.put(key, newMsgs); + if (oldMsgs != null) { + for (int i = 0; i < oldMsgs.size(); ++i) { + MessageObject oldMsg = oldMsgs.get(i); + if (oldMsg == null) { + continue; + } if (oldMsg.messageOwner.peer_id.channel_id == 0) { dialogMessagesByIds.remove(oldMsg.getId()); } @@ -8706,36 +9001,24 @@ public class MessagesController extends BaseController implements NotificationCe dialogMessagesByRandomIds.remove(oldMsg.messageOwner.random_id); } } - if (newMsg != null) { - if (oldMsg != null && oldMsg.getId() == newMsg.getId()) { - newMsg.deleted = oldMsg.deleted; - } - if (newMsg.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.put(newMsg.getId(), newMsg); - if (newMsg.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.put(newMsg.messageOwner.random_id, newMsg); - } - } - } } - } else { - if (newMsg == null && oldMsg.getId() > 0 || newMsg != null && newMsg.messageOwner.date > oldMsg.messageOwner.date) { - dialogs_dict.put(key, value); - dialogMessage.put(key, newMsg); - if (oldMsg.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.remove(oldMsg.getId()); - } - if (newMsg != null) { - if (newMsg.messageOwner.peer_id.channel_id == 0) { + if (newMsgs != null) { + for (int i = 0; i < newMsgs.size(); ++i) { + MessageObject newMsg = newMsgs.get(i); + if (newMsg != null && newMsg.messageOwner.peer_id.channel_id == 0) { + for (int j = 0; oldMsgs != null && j < oldMsgs.size(); ++j) { + MessageObject oldMsg = oldMsgs.get(j); + if (oldMsg != null && oldMsg.getId() == newMsg.getId()) { + newMsg.deleted = oldMsg.deleted; + break; + } + } dialogMessagesByIds.put(newMsg.getId(), newMsg); if (newMsg.messageOwner.random_id != 0) { dialogMessagesByRandomIds.put(newMsg.messageOwner.random_id, newMsg); } } } - if (oldMsg.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.remove(oldMsg.messageOwner.random_id); - } } } } @@ -8791,76 +9074,19 @@ public class MessagesController extends BaseController implements NotificationCe reloadDialogsReadValue(dialogsToReload, 0); } loadUnreadDialogs(); + if (dialogsRes.dialogs != null) { + for (int i = 0; i < dialogsRes.dialogs.size(); i++) { + if (isForum(dialogsRes.dialogs.get(i).id)) { + topicsController.preloadTopics(-dialogsRes.dialogs.get(i).id); + } + } + } }); }); } - private void applyDialogNotificationsSettings(long dialogId, TLRPC.PeerNotifySettings notify_settings) { - if (notify_settings == null) { - return; - } - int currentValue = notificationsPreferences.getInt("notify2_" + dialogId, -1); - int currentValue2 = notificationsPreferences.getInt("notifyuntil_" + dialogId, 0); - SharedPreferences.Editor editor = notificationsPreferences.edit(); - boolean updated = false; - TLRPC.Dialog dialog = dialogs_dict.get(dialogId); - if (dialog != null) { - dialog.notify_settings = notify_settings; - } - if ((notify_settings.flags & 2) != 0) { - editor.putBoolean("silent_" + dialogId, notify_settings.silent); - } else { - editor.remove("silent_" + dialogId); - } - if ((notify_settings.flags & 4) != 0) { - if (notify_settings.mute_until > getConnectionsManager().getCurrentTime()) { - int until = 0; - if (notify_settings.mute_until > getConnectionsManager().getCurrentTime() + 60 * 60 * 24 * 365) { - if (currentValue != 2) { - updated = true; - editor.putInt("notify2_" + dialogId, 2); - if (dialog != null) { - dialog.notify_settings.mute_until = Integer.MAX_VALUE; - } - } - } else { - if (currentValue != 3 || currentValue2 != notify_settings.mute_until) { - updated = true; - editor.putInt("notify2_" + dialogId, 3); - editor.putInt("notifyuntil_" + dialogId, notify_settings.mute_until); - if (dialog != null) { - dialog.notify_settings.mute_until = until; - } - } - until = notify_settings.mute_until; - } - getMessagesStorage().setDialogFlags(dialogId, ((long) until << 32) | 1); - getNotificationsController().removeNotificationsForDialog(dialogId); - } else { - if (currentValue != 0 && currentValue != 1) { - updated = true; - if (dialog != null) { - dialog.notify_settings.mute_until = 0; - } - editor.putInt("notify2_" + dialogId, 0); - } - getMessagesStorage().setDialogFlags(dialogId, 0); - } - } else { - if (currentValue != -1) { - updated = true; - if (dialog != null) { - dialog.notify_settings.mute_until = 0; - } - editor.remove("notify2_" + dialogId); - } - getMessagesStorage().setDialogFlags(dialogId, 0); - } - applySoundSettings(notify_settings.android_sound, editor, dialogId, 0, false); - editor.commit(); - if (updated) { - getNotificationCenter().postNotificationName(NotificationCenter.notificationsSettingsUpdated); - } + private void applyDialogNotificationsSettings(long dialogId, int topicId, TLRPC.PeerNotifySettings notify_settings) { + getNotificationsController().getNotificationsSettingsFacade().applyDialogNotificationsSettings(dialogId, topicId, notify_settings); } private void applyDialogsNotificationsSettings(ArrayList dialogs) { @@ -8868,35 +9094,9 @@ public class MessagesController extends BaseController implements NotificationCe for (int a = 0; a < dialogs.size(); a++) { TLRPC.Dialog dialog = dialogs.get(a); if (dialog.peer != null && dialog.notify_settings instanceof TLRPC.TL_peerNotifySettings) { - if (editor == null) { - editor = notificationsPreferences.edit(); - } - long dialogId = MessageObject.getPeerId(dialog.peer); - if ((dialog.notify_settings.flags & 2) != 0) { - editor.putBoolean("silent_" + dialogId, dialog.notify_settings.silent); - } else { - editor.remove("silent_" + dialogId); - } - if ((dialog.notify_settings.flags & 4) != 0) { - if (dialog.notify_settings.mute_until > getConnectionsManager().getCurrentTime()) { - if (dialog.notify_settings.mute_until > getConnectionsManager().getCurrentTime() + 60 * 60 * 24 * 365) { - editor.putInt("notify2_" + dialogId, 2); - dialog.notify_settings.mute_until = Integer.MAX_VALUE; - } else { - editor.putInt("notify2_" + dialogId, 3); - editor.putInt("notifyuntil_" + dialogId, dialog.notify_settings.mute_until); - } - } else { - editor.putInt("notify2_" + dialogId, 0); - } - } else { - editor.remove("notify2_" + dialogId); - } + getNotificationsController().getNotificationsSettingsFacade().setSettingsForDialog(dialog, dialog.notify_settings); } } - if (editor != null) { - editor.commit(); - } } public void reloadMentionsCountForChannel(TLRPC.InputPeer peer, long taskId) { @@ -8926,7 +9126,7 @@ public class MessagesController extends BaseController implements NotificationCe } else { newCount = res.messages.size(); } - getMessagesStorage().resetMentionsCount(-peer.channel_id, newCount); + getMessagesStorage().resetMentionsCount(-peer.channel_id, 0, newCount); } if (newTaskId != 0) { getMessagesStorage().removePendingTask(newTaskId); @@ -8971,7 +9171,7 @@ public class MessagesController extends BaseController implements NotificationCe FileLog.d("update dialog " + dialogId + " with new unread " + currentDialog.unread_count); } if (prevCount != 0 && currentDialog.unread_count == 0) { - if (!isDialogMuted(dialogId)) { + if (!isDialogMuted(dialogId, 0)) { unreadUnmutedDialogs--; } if (!filterDialogsChanged) { @@ -8983,7 +9183,7 @@ public class MessagesController extends BaseController implements NotificationCe } } } else if (prevCount == 0 && !currentDialog.unread_mark && currentDialog.unread_count != 0) { - if (!isDialogMuted(dialogId)) { + if (!isDialogMuted(dialogId, 0)) { unreadUnmutedDialogs++; } if (!filterDialogsChanged) { @@ -9005,7 +9205,7 @@ public class MessagesController extends BaseController implements NotificationCe if (currentDialog != null) { currentDialog.unread_mentions_count = dialogsMentionsToUpdate.valueAt(a); if (createdDialogMainThreadIds.contains(currentDialog.id)) { - getNotificationCenter().postNotificationName(NotificationCenter.updateMentionsCount, currentDialog.id, currentDialog.unread_mentions_count); + getNotificationCenter().postNotificationName(NotificationCenter.updateMentionsCount, currentDialog.id, 0, currentDialog.unread_mentions_count); } if (!filterDialogsChanged) { for (int b = 0; b < selectedDialogFilter.length; b++) { @@ -9104,7 +9304,7 @@ public class MessagesController extends BaseController implements NotificationCe dialogs.messages.addAll(res.messages); dialogs.count = 1; processDialogsUpdate(dialogs, null, false); - getMessagesStorage().putMessages(res.messages, true, true, false, getDownloadController().getAutodownloadMask(), true, false); + getMessagesStorage().putMessages(res.messages, true, true, false, getDownloadController().getAutodownloadMask(), true, false, 0); } else { AndroidUtilities.runOnUIThread(() -> { if (BuildVars.LOGS_ENABLED) { @@ -9139,7 +9339,7 @@ public class MessagesController extends BaseController implements NotificationCe public void processDialogsUpdate(final TLRPC.messages_Dialogs dialogsRes, ArrayList encChats, boolean fromCache) { Utilities.stageQueue.postRunnable(() -> { LongSparseArray new_dialogs_dict = new LongSparseArray<>(); - LongSparseArray new_dialogMessage = new LongSparseArray<>(); + LongSparseArray> new_dialogMessage = new LongSparseArray<>(); LongSparseArray usersDict = new LongSparseArray<>(dialogsRes.users.size()); LongSparseArray chatsDict = new LongSparseArray<>(dialogsRes.chats.size()); LongSparseIntArray dialogsToUpdate = new LongSparseIntArray(); @@ -9171,7 +9371,13 @@ public class MessagesController extends BaseController implements NotificationCe } MessageObject messageObject = new MessageObject(currentAccount, message, usersDict, chatsDict, false, false); newMessages.add(messageObject); - new_dialogMessage.put(messageObject.getDialogId(), messageObject); + long did = messageObject.getDialogId(); + ArrayList arrayList = new_dialogMessage.get(did); + if (arrayList == null) { + arrayList = new ArrayList(1); + } + arrayList.add(messageObject); + new_dialogMessage.put(did, arrayList); } getFileLoader().checkMediaExistance(newMessages); @@ -9192,9 +9398,18 @@ public class MessagesController extends BaseController implements NotificationCe } } if (d.last_message_date == 0) { - MessageObject mess = new_dialogMessage.get(d.id); - if (mess != null) { - d.last_message_date = mess.messageOwner.date; + ArrayList messages = new_dialogMessage.get(d.id); + if (messages != null) { + int maxDate = Integer.MIN_VALUE; + for (int i = 0; i < messages.size(); ++i) { + MessageObject msg = messages.get(i); + if (msg != null && msg.messageOwner != null && msg.messageOwner.date > maxDate) { + maxDate = msg.messageOwner.date; + } + } + if (maxDate > Integer.MIN_VALUE) { + d.last_message_date = maxDate; + } } } new_dialogs_dict.put(d.id, d); @@ -9230,7 +9445,7 @@ public class MessagesController extends BaseController implements NotificationCe } TLRPC.Dialog value = new_dialogs_dict.valueAt(a); TLRPC.Dialog currentDialog = dialogs_dict.get(key); - MessageObject newMsg = new_dialogMessage.get(value.id); + ArrayList newMsgs = new_dialogMessage.get(value.id); if (currentDialog == null) { if (BuildVars.LOGS_ENABLED) { FileLog.d("processDialogsUpdate dialog null"); @@ -9238,19 +9453,24 @@ public class MessagesController extends BaseController implements NotificationCe int offset = nextDialogsCacheOffset.get(value.folder_id, 0) + 1; nextDialogsCacheOffset.put(value.folder_id, offset); dialogs_dict.put(key, value); - dialogMessage.put(key, newMsg); - if (newMsg == null) { + dialogMessage.put(key, newMsgs); + if (newMsgs == null || newMsgs.size() <= 0) { if (fromCache) { checkLastDialogMessage(value, null, 0); } if (BuildVars.LOGS_ENABLED) { FileLog.d("processDialogsUpdate new message is null"); } - } else if (newMsg.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.put(newMsg.getId(), newMsg); - dialogsLoadedTillDate = Math.min(dialogsLoadedTillDate, newMsg.messageOwner.date); - if (newMsg.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.put(newMsg.messageOwner.random_id, newMsg); + } else { + for (int i = 0; i < newMsgs.size(); ++i) { + MessageObject newMsg = newMsgs.get(i); + if (newMsg != null && newMsg.messageOwner.peer_id.channel_id == 0) { + dialogMessagesByIds.put(newMsg.getId(), newMsg); + dialogsLoadedTillDate = Math.min(dialogsLoadedTillDate, newMsg.messageOwner.date); + if (newMsg.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.put(newMsg.messageOwner.random_id, newMsg); + } + } } if (BuildVars.LOGS_ENABLED) { FileLog.d("processDialogsUpdate new message not null"); @@ -9264,68 +9484,102 @@ public class MessagesController extends BaseController implements NotificationCe if (currentDialog.unread_mentions_count != value.unread_mentions_count) { currentDialog.unread_mentions_count = value.unread_mentions_count; if (createdDialogMainThreadIds.contains(currentDialog.id)) { - getNotificationCenter().postNotificationName(NotificationCenter.updateMentionsCount, currentDialog.id, currentDialog.unread_mentions_count); + getNotificationCenter().postNotificationName(NotificationCenter.updateMentionsCount, currentDialog.id, 0, currentDialog.unread_mentions_count); } } if (currentDialog.unread_reactions_count != value.unread_reactions_count) { currentDialog.unread_reactions_count = value.unread_reactions_count; - getNotificationCenter().postNotificationName(NotificationCenter.dialogsUnreadReactionsCounterChanged, currentDialog.id, currentDialog.unread_reactions_count, null); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsUnreadReactionsCounterChanged, currentDialog.id, 0, currentDialog.unread_reactions_count, null); + } + ArrayList oldMsgs = dialogMessage.get(key); + boolean oldMsgsDeleted = false; + for (int i = 0; oldMsgs != null && i < oldMsgs.size(); ++i) { + if (oldMsgs.get(i) != null && oldMsgs.get(i).deleted) { + oldMsgsDeleted = true; + break; + } } - MessageObject oldMsg = dialogMessage.get(key); if (BuildVars.LOGS_ENABLED) { - FileLog.d("processDialogsUpdate oldMsg " + oldMsg + " old top_message = " + currentDialog.top_message + " new top_message = " + value.top_message + " unread_count =" + currentDialog.unread_count + " fromCache=" + fromCache); - FileLog.d("processDialogsUpdate oldMsgDeleted " + (oldMsg != null && oldMsg.deleted)); + FileLog.d("processDialogsUpdate oldMsgs (count = " + (oldMsgs == null ? "null" : oldMsgs.size()) + ") old top_message = " + currentDialog.top_message + " new top_message = " + value.top_message + " unread_count =" + currentDialog.unread_count + " fromCache=" + fromCache); + FileLog.d("processDialogsUpdate oldMsgDeleted " + oldMsgsDeleted); } - if (oldMsg == null || currentDialog.top_message > 0) { - if (oldMsg != null && oldMsg.deleted || value.top_message > currentDialog.top_message) { + if (oldMsgs == null || currentDialog.top_message > 0) { + if (oldMsgsDeleted || value.top_message > currentDialog.top_message || (oldMsgs == null) != (newMsgs == null) || oldMsgs != null && newMsgs != null && oldMsgs.size() != newMsgs.size()) { dialogs_dict.put(key, value); - dialogMessage.put(key, newMsg); - if (oldMsg != null && oldMsg.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.remove(oldMsg.getId()); - if (oldMsg.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.remove(oldMsg.messageOwner.random_id); + dialogMessage.put(key, newMsgs); + for (int i = 0; oldMsgs != null && i < oldMsgs.size(); ++i) { + MessageObject oldMsg = oldMsgs.get(i); + if (oldMsg != null && oldMsg.messageOwner.peer_id.channel_id == 0) { + dialogMessagesByIds.remove(oldMsg.getId()); + if (oldMsg.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.remove(oldMsg.messageOwner.random_id); + } } } - if (newMsg != null) { - if (oldMsg != null && oldMsg.getId() == newMsg.getId()) { - newMsg.deleted = oldMsg.deleted; - } - if (newMsg.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.put(newMsg.getId(), newMsg); - dialogsLoadedTillDate = Math.min(dialogsLoadedTillDate, newMsg.messageOwner.date); - if (newMsg.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.put(newMsg.messageOwner.random_id, newMsg); + if (newMsgs != null) { + for (int i = 0; i < newMsgs.size(); ++i) { + MessageObject newMsg = newMsgs.get(i); + for (int j = 0; oldMsgs != null && j < oldMsgs.size(); ++j) { + MessageObject oldMsg = oldMsgs.get(j); + if (oldMsg != null && oldMsg.getId() == newMsg.getId()) { + newMsg.deleted = oldMsg.deleted; + break; + } + } + if (newMsg != null && newMsg.messageOwner.peer_id.channel_id == 0) { + dialogMessagesByIds.put(newMsg.getId(), newMsg); + dialogsLoadedTillDate = Math.min(dialogsLoadedTillDate, newMsg.messageOwner.date); + if (newMsg.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.put(newMsg.messageOwner.random_id, newMsg); + } } } } } - if (fromCache && newMsg == null) { + if (fromCache && newMsgs == null) { checkLastDialogMessage(value, null, 0); if (BuildVars.LOGS_ENABLED) { - FileLog.d("processDialogsUpdate new message is null"); + FileLog.d("processDialogsUpdate new messages are null"); } } } else { - if (oldMsg.deleted || newMsg == null || newMsg.messageOwner.date > oldMsg.messageOwner.date) { + if (oldMsgsDeleted || messagesMaxDate(newMsgs) > messagesMaxDate(oldMsgs)) { dialogs_dict.put(key, value); - dialogMessage.put(key, newMsg); - if (oldMsg.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.remove(oldMsg.getId()); - } - if (newMsg != null) { - if (oldMsg.getId() == newMsg.getId()) { - newMsg.deleted = oldMsg.deleted; - } - if (newMsg.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.put(newMsg.getId(), newMsg); - dialogsLoadedTillDate = Math.min(dialogsLoadedTillDate, newMsg.messageOwner.date); - if (newMsg.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.put(newMsg.messageOwner.random_id, newMsg); + dialogMessage.put(key, newMsgs); + if (oldMsgs != null) { + for (int i = 0; i < oldMsgs.size(); ++i) { + MessageObject oldMsg = oldMsgs.get(i); + if (oldMsg != null && oldMsg.messageOwner.peer_id.channel_id == 0) { + dialogMessagesByIds.remove(oldMsg.getId()); } } } - if (oldMsg.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.remove(oldMsg.messageOwner.random_id); + if (newMsgs != null) { + for (int i = 0; i < newMsgs.size(); ++i) { + MessageObject newMsg = newMsgs.get(i); + for (int j = 0; oldMsgs != null && j < oldMsgs.size(); ++j) { + MessageObject oldMsg = oldMsgs.get(j); + if (oldMsg != null && oldMsg.getId() == newMsg.getId()) { + newMsg.deleted = oldMsg.deleted; + break; + } + } + if (newMsg != null && newMsg.messageOwner.peer_id.channel_id == 0) { + dialogMessagesByIds.put(newMsg.getId(), newMsg); + dialogsLoadedTillDate = Math.min(dialogsLoadedTillDate, newMsg.messageOwner.date); + if (newMsg.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.put(newMsg.messageOwner.random_id, newMsg); + } + } + } + } + if (oldMsgs != null) { + for (int i = 0; i < oldMsgs.size(); ++i) { + MessageObject oldMsg = oldMsgs.get(i); + if (oldMsg.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.remove(oldMsg.messageOwner.random_id); + } + } } } } @@ -9347,6 +9601,17 @@ public class MessagesController extends BaseController implements NotificationCe }); } + private int messagesMaxDate(ArrayList messages) { + int maxDate = Integer.MIN_VALUE; + for (int i = 0; messages != null && i < messages.size(); ++i) { + MessageObject m = messages.get(i); + if (m != null && m.messageOwner != null && m.messageOwner.date > maxDate) { + maxDate = m.messageOwner.date; + } + } + return maxDate; + } + public void addToViewsQueue(MessageObject messageObject) { Utilities.stageQueue.postRunnable(() -> { long peer = messageObject.getDialogId(); @@ -9676,13 +9941,17 @@ public class MessagesController extends BaseController implements NotificationCe }); } - public void markMentionsAsRead(long dialogId) { + public void markMentionsAsRead(long dialogId, int topicId) { if (DialogObject.isEncryptedDialog(dialogId)) { return; } - getMessagesStorage().resetMentionsCount(dialogId, 0); + getMessagesStorage().resetMentionsCount(dialogId, topicId, 0); TLRPC.TL_messages_readMentions req = new TLRPC.TL_messages_readMentions(); req.peer = getInputPeer(dialogId); + if (topicId != 0) { + req.top_msg_id = topicId; + req.flags |= 1; + } getConnectionsManager().sendRequest(req, (response, error) -> { }); @@ -9725,7 +9994,7 @@ public class MessagesController extends BaseController implements NotificationCe getMessagesStorage().setDialogUnread(dialog.id, false); } if ((prevCount != 0 || wasUnread) && dialog.unread_count == 0) { - if (!isDialogMuted(dialogId)) { + if (!isDialogMuted(dialogId, 0)) { unreadUnmutedDialogs--; } for (int b = 0; b < selectedDialogFilter.length; b++) { @@ -9779,7 +10048,7 @@ public class MessagesController extends BaseController implements NotificationCe getMessagesStorage().setDialogUnread(dialog.id, false); } if ((prevCount != 0 || wasUnread) && dialog.unread_count == 0) { - if (!isDialogMuted(dialogId)) { + if (!isDialogMuted(dialogId, 0)) { unreadUnmutedDialogs--; } for (int b = 0; b < selectedDialogFilter.length; b++) { @@ -10132,6 +10401,18 @@ public class MessagesController extends BaseController implements NotificationCe }, ConnectionsManager.RequestFlagInvokeAfter); } + public void toggleChannelForum(long chatId, boolean enabled) { + TLRPC.TL_channels_toggleForum req = new TLRPC.TL_channels_toggleForum(); + req.channel = getInputChannel(chatId); + req.enabled = enabled; + getConnectionsManager().sendRequest(req, (response, error) -> { + if (response != null) { + processUpdates((TLRPC.Updates) response, false); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT)); + } + }, ConnectionsManager.RequestFlagInvokeAfter); + } + public void toggleChannelInvitesHistory(long chatId, boolean enabled) { TLRPC.TL_channels_togglePreHistoryHidden req = new TLRPC.TL_channels_togglePreHistoryHidden(); req.channel = getInputChannel(chatId); @@ -10159,25 +10440,52 @@ public class MessagesController extends BaseController implements NotificationCe }, ConnectionsManager.RequestFlagInvokeAfter); } - public void updateChannelUserName(long chatId, String userName) { + public void updateChannelUserName(BaseFragment fragment, long chatId, String userName, Runnable runnable, Runnable onError) { TLRPC.TL_channels_updateUsername req = new TLRPC.TL_channels_updateUsername(); req.channel = getInputChannel(chatId); req.username = userName; getConnectionsManager().sendRequest(req, (response, error) -> { - if (response instanceof TLRPC.TL_boolTrue) { + if (response instanceof TLRPC.TL_boolTrue || error != null && "USERNAME_NOT_MODIFIED".equals(error.text)) { AndroidUtilities.runOnUIThread(() -> { TLRPC.Chat chat = getChat(chatId); - if (userName.length() != 0) { - chat.flags |= TLRPC.CHAT_FLAG_IS_PUBLIC; - } else { - chat.flags &= ~TLRPC.CHAT_FLAG_IS_PUBLIC; + boolean hasMain = false; + if (chat.usernames != null) { + for (int i = 0; i < chat.usernames.size(); ++i) { + TLRPC.TL_username u = chat.usernames.get(i); + if (u != null && u.editable) { + hasMain = true; + u.username = userName; + break; + } + } + } + if (!hasMain) { + if (chat.usernames == null || chat.usernames.size() >= 1) { + if (chat.usernames == null) { + chat.usernames = new ArrayList<>(); + } + TLRPC.TL_username u = new TLRPC.TL_username(); + u.username = userName; + u.active = true; + u.editable = true; + chat.usernames.add(0, u); + } else { + chat.username = userName; + } } - chat.username = userName; ArrayList arrayList = new ArrayList<>(); arrayList.add(chat); getMessagesStorage().putUsersAndChats(null, arrayList, true, true); getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT); + if (runnable != null) { + runnable.run(); + } }); + } else { + AlertsCreator.processError(UserConfig.selectedAccount, error, fragment, req); + if (onError != null) { + onError.run(); + } } }, ConnectionsManager.RequestFlagInvokeAfter); } @@ -11198,7 +11506,7 @@ public class MessagesController extends BaseController implements NotificationCe if (!pushMessages.isEmpty()) { AndroidUtilities.runOnUIThread(() -> getNotificationsController().processNewMessages(pushMessages, true, false, null)); } - getMessagesStorage().putMessages(res.new_messages, true, false, false, getDownloadController().getAutodownloadMask(), false); + getMessagesStorage().putMessages(res.new_messages, true, false, false, getDownloadController().getAutodownloadMask(), false, 0); }); } @@ -11455,12 +11763,12 @@ public class MessagesController extends BaseController implements NotificationCe if (!pushMessages.isEmpty()) { AndroidUtilities.runOnUIThread(() -> getNotificationsController().processNewMessages(pushMessages, !(res instanceof TLRPC.TL_updates_differenceSlice), false, null)); } - getMessagesStorage().putMessages(res.new_messages, true, false, false, getDownloadController().getAutodownloadMask(), false); + getMessagesStorage().putMessages(res.new_messages, true, false, false, getDownloadController().getAutodownloadMask(), false, 0); for (int a = 0; a < messages.size(); a++) { long dialogId = messages.keyAt(a); ArrayList arr = messages.valueAt(a); - getMediaDataController().loadReplyMessagesForMessages(arr, dialogId, false, () -> { + getMediaDataController().loadReplyMessagesForMessages(arr, dialogId, false, 0, () -> { AndroidUtilities.runOnUIThread(() -> { updateInterfaceWithMessages(dialogId, arr, false); getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); @@ -11517,7 +11825,7 @@ public class MessagesController extends BaseController implements NotificationCe TLRPC.Dialog dialog = dialogs_dict.get(dialogId); if (dialog != null) { dialog.unread_mark = true; - if (dialog.unread_count == 0 && !isDialogMuted(dialogId)) { + if (dialog.unread_count == 0 && !isDialogMuted(dialogId, 0)) { unreadUnmutedDialogs++; } getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_READ_DIALOG_MESSAGE); @@ -11592,7 +11900,7 @@ public class MessagesController extends BaseController implements NotificationCe TLRPC.Dialog dialog = dialogs_dict.get(did); if (dialog != null && !dialog.unread_mark) { dialog.unread_mark = true; - if (dialog.unread_count == 0 && !isDialogMuted(did)) { + if (dialog.unread_count == 0 && !isDialogMuted(did, 0)) { unreadUnmutedDialogs++; } } @@ -11761,7 +12069,7 @@ public class MessagesController extends BaseController implements NotificationCe toCache.dialogs.addAll(res.dialogs); toCache.messages.addAll(res.messages); - LongSparseArray new_dialogMessage = new LongSparseArray<>(); + LongSparseArray> new_dialogMessage = new LongSparseArray<>(); LongSparseArray usersDict = new LongSparseArray<>(); LongSparseArray chatsDict = new LongSparseArray<>(); @@ -11790,7 +12098,13 @@ public class MessagesController extends BaseController implements NotificationCe } MessageObject messageObject = new MessageObject(currentAccount, message, usersDict, chatsDict, false, false); newMessages.add(messageObject); - new_dialogMessage.put(messageObject.getDialogId(), messageObject); + long did = messageObject.getDialogId(); + ArrayList arrayList = new_dialogMessage.get(did); + if (arrayList == null) { + arrayList = new ArrayList(); + } + arrayList.add(messageObject); + new_dialogMessage.put(did, arrayList); } getFileLoader().checkMediaExistance(newMessages); boolean firstIsFolder = !newPinnedDialogs.isEmpty() && newPinnedDialogs.get(0) instanceof TLRPC.TL_dialogFolder; @@ -11810,9 +12124,18 @@ public class MessagesController extends BaseController implements NotificationCe } } if (d.last_message_date == 0) { - MessageObject mess = new_dialogMessage.get(d.id); - if (mess != null) { - d.last_message_date = mess.messageOwner.date; + ArrayList messages = new_dialogMessage.get(d.id); + if (messages != null) { + int maxDate = Integer.MIN_VALUE; + for (int i = 0; i < messages.size(); ++i) { + MessageObject msg = messages.get(i); + if (msg != null && msg.messageOwner != null && msg.messageOwner.date > maxDate) { + maxDate = msg.messageOwner.date; + } + } + if (maxDate > Integer.MIN_VALUE) { + d.last_message_date = maxDate; + } } } @@ -11886,13 +12209,18 @@ public class MessagesController extends BaseController implements NotificationCe } else { added = true; dialogs_dict.put(dialog.id, dialog); - MessageObject messageObject = new_dialogMessage.get(dialog.id); - dialogMessage.put(dialog.id, messageObject); - if (messageObject != null && messageObject.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.put(messageObject.getId(), messageObject); - dialogsLoadedTillDate = Math.min(dialogsLoadedTillDate, messageObject.messageOwner.date); - if (messageObject.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.put(messageObject.messageOwner.random_id, messageObject); + ArrayList messageObjects = new_dialogMessage.get(dialog.id); + dialogMessage.put(dialog.id, messageObjects); + if (messageObjects != null) { + for (int i = 0; i < messageObjects.size(); ++i) { + MessageObject messageObject = messageObjects.get(i); + if (messageObject != null && messageObject.messageOwner.peer_id.channel_id == 0) { + dialogMessagesByIds.put(messageObject.getId(), messageObject); + dialogsLoadedTillDate = Math.min(dialogsLoadedTillDate, messageObject.messageOwner.date); + if (messageObject.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.put(messageObject.messageOwner.random_id, messageObject); + } + } } } } @@ -11952,7 +12280,7 @@ public class MessagesController extends BaseController implements NotificationCe pushMessages.add(obj); getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> getNotificationsController().processNewMessages(pushMessages, true, false, null))); - getMessagesStorage().putMessages(messagesArr, true, true, false, 0, false); + getMessagesStorage().putMessages(messagesArr, true, true, false, 0, false, 0); AndroidUtilities.runOnUIThread(() -> { updateInterfaceWithMessages(-chatId, pushMessages, false); @@ -11973,12 +12301,15 @@ public class MessagesController extends BaseController implements NotificationCe } } } else { - MessageObject obj = dialogMessage.get(-channelId); - if (obj != null) { - for (int b = 0, size2 = ids.size(); b < size2; b++) { - if (obj.getId() == ids.get(b)) { - obj.deleted = true; - break; + ArrayList objs = dialogMessage.get(-channelId); + if (objs != null) { + for (int i = 0; i < objs.size(); ++i) { + MessageObject obj = objs.get(i); + for (int b = 0, size2 = ids.size(); b < size2; b++) { + if (obj.getId() == ids.get(b)) { + obj.deleted = true; + break; + } } } } @@ -12048,7 +12379,7 @@ public class MessagesController extends BaseController implements NotificationCe MessageObject obj = new MessageObject(currentAccount, message, usersDict, true, false); pushMessages.add(obj); getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> getNotificationsController().processNewMessages(pushMessages, true, false, null))); - getMessagesStorage().putMessages(messagesArr, true, true, false, 0, false); + getMessagesStorage().putMessages(messagesArr, true, true, false, 0, false, 0); } else { pushMessages = null; } @@ -12186,6 +12517,8 @@ public class MessagesController extends BaseController implements NotificationCe return ((TLRPC.TL_updateDeleteChannelMessages) update).channel_id; } else if (update instanceof TLRPC.TL_updateReadChannelInbox) { return ((TLRPC.TL_updateReadChannelInbox) update).channel_id; + } else if (update instanceof TLRPC.TL_updateChannelPinnedTopic) { + return ((TLRPC.TL_updateChannelPinnedTopic) update).channel_id; } else if (update instanceof TLRPC.TL_updateReadChannelDiscussionInbox) { return ((TLRPC.TL_updateReadChannelDiscussionInbox) update).channel_id; } else if (update instanceof TLRPC.TL_updateReadChannelDiscussionOutbox) { @@ -12388,7 +12721,7 @@ public class MessagesController extends BaseController implements NotificationCe if (!obj.isOut()) { getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> getNotificationsController().processNewMessages(objArr, true, false, null))); } - getMessagesStorage().putMessages(arr, false, true, false, 0, false); + getMessagesStorage().putMessages(arr, false, true, false, 0, false, 0); } else if (getMessagesStorage().getLastPtsValue() != updates.pts) { if (BuildVars.LOGS_ENABLED) { FileLog.d("need get diff short message, pts: " + getMessagesStorage().getLastPtsValue() + " " + updates.pts + " count = " + updates.pts_count); @@ -12778,6 +13111,8 @@ public class MessagesController extends BaseController implements NotificationCe ArrayList tasks = null; ArrayList contactsIds = null; ArrayList messageThumbs = null; + HashMap topicsReadOutbox = null; + HashMap topicsReadInbox = null; ConcurrentHashMap usersDict; ConcurrentHashMap chatsDict; @@ -12824,7 +13159,7 @@ public class MessagesController extends BaseController implements NotificationCe } else { message = ((TLRPC.TL_updateNewChannelMessage) baseUpdate).message; if (BuildVars.LOGS_ENABLED) { - FileLog.d(baseUpdate + " channelId = " + message.peer_id.channel_id); + FileLog.d(baseUpdate + " channelId = " + message.peer_id.channel_id + " message_id = " + message.id); } if (!message.out && message.from_id instanceof TLRPC.TL_peerUser && message.from_id.user_id == getUserConfig().getClientUserId()) { message.out = true; @@ -12994,7 +13329,7 @@ public class MessagesController extends BaseController implements NotificationCe boolean isDialogCreated = createdDialogIds.contains(message.dialog_id); MessageObject obj = new MessageObject(currentAccount, message, usersDict, chatsDict, isDialogCreated, isDialogCreated); - if (obj.type == 11) { + if (obj.type == MessageObject.TYPE_ACTION_PHOTO) { interfaceUpdateMask |= UPDATE_MASK_CHAT_AVATAR; } else if (obj.type == 10) { interfaceUpdateMask |= UPDATE_MASK_CHAT_NAME; @@ -13535,7 +13870,12 @@ public class MessagesController extends BaseController implements NotificationCe markAsReadMessagesInbox.put(dialogId, update.max_id); stillUnreadMessagesCount.put(dialogId, update.still_unread_count); dialogs_read_inbox_max.put(dialogId, Math.max(value, update.max_id)); - FileLog.d("TL_updateReadChannelInbox " + dialogId + " new unread " + update.still_unread_count + " from get diff" + fromGetDifference); + FileLog.d("TL_updateReadChannelInbox " + dialogId + " new unread = " + update.still_unread_count + " max id = " + update.max_id + " from get diff " + fromGetDifference); + } else if (baseUpdate instanceof TLRPC.TL_updateChannelPinnedTopic) { + if (updatesOnMainThread == null) { + updatesOnMainThread = new ArrayList<>(); + } + updatesOnMainThread.add(baseUpdate); } else if (baseUpdate instanceof TLRPC.TL_updateReadChannelOutbox) { TLRPC.TL_updateReadChannelOutbox update = (TLRPC.TL_updateReadChannelOutbox) baseUpdate; if (BuildVars.LOGS_ENABLED) { @@ -13722,6 +14062,7 @@ public class MessagesController extends BaseController implements NotificationCe } ImageLoader.saveMessageThumbs(message); + AndroidUtilities.runOnUIThread(()-> getSendMessagesHelper().onMessageEdited(message)); boolean isDialogCreated = createdDialogIds.contains(message.dialog_id); MessageObject obj = new MessageObject(currentAccount, message, usersDict, chatsDict, isDialogCreated, isDialogCreated); @@ -13833,11 +14174,16 @@ public class MessagesController extends BaseController implements NotificationCe } else if (baseUpdate instanceof TLRPC.TL_updateMessageReactions) { TLRPC.TL_updateMessageReactions update = (TLRPC.TL_updateMessageReactions) baseUpdate; long dialogId = MessageObject.getPeerId(update.peer); + getMessagesStorage().updateMessageReactions(dialogId, update.msg_id, update.reactions); + if (update.updateUnreadState) { SparseBooleanArray sparseBooleanArray = new SparseBooleanArray(); sparseBooleanArray.put(update.msg_id, MessageObject.hasUnreadReactions(update.reactions)); - checkUnreadReactions(dialogId, sparseBooleanArray); + if (BuildVars.DEBUG_PRIVATE_VERSION) { + FileLog.d("check reactions for " + dialogId + " " + update.top_msg_id); + } + checkUnreadReactions(dialogId, update.top_msg_id, sparseBooleanArray); } if (updatesOnMainThread == null) { updatesOnMainThread = new ArrayList<>(); @@ -13907,11 +14253,26 @@ public class MessagesController extends BaseController implements NotificationCe } updatesOnMainThread.add(baseUpdate); } else if (baseUpdate instanceof TLRPC.TL_updateReadChannelDiscussionInbox) { + TLRPC.TL_updateReadChannelDiscussionInbox update = (TLRPC.TL_updateReadChannelDiscussionInbox) baseUpdate; + if (updatesOnMainThread == null) { + updatesOnMainThread = new ArrayList<>(); + } + if (topicsReadInbox == null) { + topicsReadInbox = new HashMap<>(); + } + MessagesStorage.TopicKey topicKey = MessagesStorage.TopicKey.of(-update.channel_id, update.top_msg_id); + topicsReadInbox.put(MessagesStorage.TopicKey.of(-update.channel_id, update.top_msg_id), Math.max(Utilities.getOrDefault(topicsReadInbox, topicKey, 0), update.read_max_id)); if (updatesOnMainThread == null) { updatesOnMainThread = new ArrayList<>(); } updatesOnMainThread.add(baseUpdate); } else if (baseUpdate instanceof TLRPC.TL_updateReadChannelDiscussionOutbox) { + TLRPC.TL_updateReadChannelDiscussionOutbox update = (TLRPC.TL_updateReadChannelDiscussionOutbox) baseUpdate; + if (topicsReadOutbox == null) { + topicsReadOutbox = new HashMap<>(); + } + MessagesStorage.TopicKey topicKey = MessagesStorage.TopicKey.of(-update.channel_id, update.top_msg_id); + topicsReadOutbox.put(MessagesStorage.TopicKey.of(-update.channel_id, update.top_msg_id), Math.max(Utilities.getOrDefault(topicsReadOutbox, topicKey, 0), update.read_max_id)); if (updatesOnMainThread == null) { updatesOnMainThread = new ArrayList<>(); } @@ -13970,12 +14331,12 @@ public class MessagesController extends BaseController implements NotificationCe } if (scheduledMessagesArr != null) { - getMessagesStorage().putMessages(scheduledMessagesArr, true, true, false, getDownloadController().getAutodownloadMask(), true); + getMessagesStorage().putMessages(scheduledMessagesArr, true, true, false, getDownloadController().getAutodownloadMask(), true, 0); } if (messagesArr != null) { getStatsController().incrementReceivedItemsCount(ApplicationLoader.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, messagesArr.size()); - getMessagesStorage().putMessages(messagesArr, true, true, false, getDownloadController().getAutodownloadMask(), false); + getMessagesStorage().putMessages(messagesArr, true, true, false, getDownloadController().getAutodownloadMask(), false, 0); } if (editingMessages != null) { for (int b = 0, size = editingMessages.size(); b < size; b++) { @@ -13984,10 +14345,13 @@ public class MessagesController extends BaseController implements NotificationCe for (int a = 0, size2 = messageObjects.size(); a < size2; a++) { messagesRes.messages.add(messageObjects.get(a).messageOwner); } - getMessagesStorage().putMessages(messagesRes, editingMessages.keyAt(b), -2, 0, false, false); + getMessagesStorage().putMessages(messagesRes, editingMessages.keyAt(b), -2, 0, false, false, 0); } LongSparseArray> editingMessagesFinal = editingMessages; - getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> getNotificationsController().processEditedMessages(editingMessagesFinal))); + getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> { + getNotificationsController().processEditedMessages(editingMessagesFinal); + getTopicsController().processEditedMessages(editingMessagesFinal); + })); } if (channelViews != null || channelForwards != null || channelReplies != null) { @@ -14091,19 +14455,33 @@ public class MessagesController extends BaseController implements NotificationCe currentUser.first_name = update.first_name; currentUser.last_name = update.last_name; } - if (!TextUtils.isEmpty(currentUser.username)) { - objectsByUsernames.remove(currentUser.username); + if (currentUser.usernames != null) { + for (int i = 0; i < currentUser.usernames.size(); ++i) { + TLRPC.TL_username username = currentUser.usernames.get(i); + if (username != null && !TextUtils.isEmpty(username.username)) { + objectsByUsernames.remove(username.username); + } + } } - if (TextUtils.isEmpty(update.username)) { - objectsByUsernames.put(update.username, currentUser); + for (int i = 0; i < update.usernames.size(); ++i) { + String username = update.usernames.get(i).username; + if (!TextUtils.isEmpty(username)) { + objectsByUsernames.put(username, currentUser); + } + } + if (update.usernames != null && update.usernames.size() > 1) { + currentUser.username = null; + currentUser.usernames = update.usernames; + } else { + currentUser.username = (update.usernames != null && update.usernames.size() == 1) ? update.usernames.get(0).username : null; + currentUser.usernames.clear(); } - currentUser.username = update.username; } TLRPC.User toDbUser = new TLRPC.TL_user(); toDbUser.id = update.user_id; toDbUser.first_name = update.first_name; toDbUser.last_name = update.last_name; - toDbUser.username = update.username; + toDbUser.username = (update.usernames != null && update.usernames.size() == 1) ? update.usernames.get(0).username : null; dbUsers.add(toDbUser); } else if (baseUpdate instanceof TLRPC.TL_updateDialogPinned) { TLRPC.TL_updateDialogPinned update = (TLRPC.TL_updateDialogPinned) baseUpdate; @@ -14183,58 +14561,30 @@ public class MessagesController extends BaseController implements NotificationCe editor = notificationsPreferences.edit(); } int currentTime1 = getConnectionsManager().getCurrentTime(); - if (update.peer instanceof TLRPC.TL_notifyPeer) { - TLRPC.TL_notifyPeer notifyPeer = (TLRPC.TL_notifyPeer) update.peer; + if (update.peer instanceof TLRPC.TL_notifyPeer || update.peer instanceof TLRPC.TL_notifyForumTopic) { long dialogId; - if (notifyPeer.peer.user_id != 0) { - dialogId = notifyPeer.peer.user_id; - } else if (notifyPeer.peer.chat_id != 0) { - dialogId = -notifyPeer.peer.chat_id; - } else { - dialogId = -notifyPeer.peer.channel_id; - } - TLRPC.Dialog dialog = dialogs_dict.get(dialogId); - if (dialog != null) { - dialog.notify_settings = update.notify_settings; - } - if ((update.notify_settings.flags & 2) != 0) { - editor.putBoolean("silent_" + dialogId, update.notify_settings.silent); - } else { - editor.remove("silent_" + dialogId); - } - if ((update.notify_settings.flags & 4) != 0) { - if (update.notify_settings.mute_until > currentTime1) { - int until = 0; - if (update.notify_settings.mute_until > currentTime1 + 60 * 60 * 24 * 365) { - editor.putInt("notify2_" + dialogId, 2); - if (dialog != null) { - update.notify_settings.mute_until = Integer.MAX_VALUE; - } - } else { - until = update.notify_settings.mute_until; - editor.putInt("notify2_" + dialogId, 3); - editor.putInt("notifyuntil_" + dialogId, update.notify_settings.mute_until); - if (dialog != null) { - update.notify_settings.mute_until = until; - } - } - getMessagesStorage().setDialogFlags(dialogId, ((long) until << 32) | 1); - getNotificationsController().removeNotificationsForDialog(dialogId); + int topicId = 0; + if (update.peer instanceof TLRPC.TL_notifyPeer) { + TLRPC.TL_notifyPeer notifyPeer = (TLRPC.TL_notifyPeer) update.peer; + if (notifyPeer.peer.user_id != 0) { + dialogId = notifyPeer.peer.user_id; + } else if (notifyPeer.peer.chat_id != 0) { + dialogId = -notifyPeer.peer.chat_id; } else { - if (dialog != null) { - update.notify_settings.mute_until = 0; - } - editor.putInt("notify2_" + dialogId, 0); - getMessagesStorage().setDialogFlags(dialogId, 0); + dialogId = -notifyPeer.peer.channel_id; } } else { - if (dialog != null) { - update.notify_settings.mute_until = 0; + TLRPC.TL_notifyForumTopic notifyPeer = (TLRPC.TL_notifyForumTopic) update.peer; + if (notifyPeer.peer.user_id != 0) { + dialogId = notifyPeer.peer.user_id; + } else if (notifyPeer.peer.chat_id != 0) { + dialogId = -notifyPeer.peer.chat_id; + } else { + dialogId = -notifyPeer.peer.channel_id; } - editor.remove("notify2_" + dialogId); - getMessagesStorage().setDialogFlags(dialogId, 0); + topicId = notifyPeer.top_msg_id; } - applySoundSettings(update.notify_settings.android_sound, editor, dialogId, 0, true); + getNotificationsController().getNotificationsSettingsFacade().applyDialogNotificationsSettings(dialogId, topicId, update.notify_settings); } else if (update.peer instanceof TLRPC.TL_notifyChats) { if ((update.notify_settings.flags & 1) != 0) { editor.putBoolean("EnablePreviewGroup", update.notify_settings.show_previews); @@ -14253,7 +14603,7 @@ public class MessagesController extends BaseController implements NotificationCe AndroidUtilities.runOnUIThread(() -> getNotificationsController().deleteNotificationChannelGlobal(NotificationsController.TYPE_GROUP)); } } - applySoundSettings(update.notify_settings.android_sound, editor, 0, NotificationsController.TYPE_GROUP, false); + getNotificationsController().getNotificationsSettingsFacade().applySoundSettings(update.notify_settings.android_sound, editor, 0, 0, NotificationsController.TYPE_GROUP, false); } else if (update.peer instanceof TLRPC.TL_notifyUsers) { if ((update.notify_settings.flags & 1) != 0) { editor.putBoolean("EnablePreviewAll", update.notify_settings.show_previews); @@ -14265,7 +14615,7 @@ public class MessagesController extends BaseController implements NotificationCe editor.remove("GlobalSoundPath"); }*/ } - applySoundSettings(update.notify_settings.android_sound, editor, 0, TYPE_PRIVATE, false); + getNotificationsController().getNotificationsSettingsFacade().applySoundSettings(update.notify_settings.android_sound, editor, 0, 0, TYPE_PRIVATE, false); if ((update.notify_settings.flags & 4) != 0) { if (notificationsPreferences.getInt("EnableAll2", 0) != update.notify_settings.mute_until) { editor.putInt("EnableAll2", update.notify_settings.mute_until); @@ -14291,7 +14641,7 @@ public class MessagesController extends BaseController implements NotificationCe AndroidUtilities.runOnUIThread(() -> getNotificationsController().deleteNotificationChannelGlobal(NotificationsController.TYPE_CHANNEL)); } } - applySoundSettings(update.notify_settings.android_sound, editor, 0, TYPE_CHANNEL, false); + getNotificationsController().getNotificationsSettingsFacade().applySoundSettings(update.notify_settings.android_sound, editor, 0, 0, TYPE_CHANNEL, false); } getMessagesStorage().updateMutedDialogsFiltersCounters(); } @@ -14402,7 +14752,8 @@ public class MessagesController extends BaseController implements NotificationCe } else { did = -peer.chat_id; } - getMediaDataController().saveDraft(did, 0, update.draft, null, true); + int threadId = update.top_msg_id; + getMediaDataController().saveDraft(did, threadId, update.draft, null, true); } else if (baseUpdate instanceof TLRPC.TL_updateReadFeaturedStickers) { getMediaDataController().markFeaturedStickersAsRead(false, false); } else if (baseUpdate instanceof TLRPC.TL_updateReadFeaturedEmojiStickers) { @@ -14410,7 +14761,18 @@ public class MessagesController extends BaseController implements NotificationCe } else if (baseUpdate instanceof TLRPC.TL_updateMoveStickerSetToTop) { TLRPC.TL_updateMoveStickerSetToTop update = (TLRPC.TL_updateMoveStickerSetToTop) baseUpdate; getMediaDataController().moveStickerSetToTop(update.stickerset, update.emojis, update.masks); - }else if (baseUpdate instanceof TLRPC.TL_updatePhoneCallSignalingData) { + } else if (baseUpdate instanceof TLRPC.TL_updateChannelPinnedTopic) { + TLRPC.TL_updateChannelPinnedTopic update = (TLRPC.TL_updateChannelPinnedTopic) baseUpdate; + + ArrayList topics = getTopicsController().getTopics(update.channel_id); + if (topics != null) { + for (int i = 0; i < topics.size(); ++i) { + topics.get(i).pinned = update.topic_id == topics.get(i).id; + } + } + + updateMask |= UPDATE_MASK_SELECT_DIALOG; + } else if (baseUpdate instanceof TLRPC.TL_updatePhoneCallSignalingData) { TLRPC.TL_updatePhoneCallSignalingData data = (TLRPC.TL_updatePhoneCallSignalingData) baseUpdate; VoIPService svc = VoIPService.getSharedInstance(); if (svc != null) { @@ -14538,7 +14900,7 @@ public class MessagesController extends BaseController implements NotificationCe TLRPC.Dialog dialog = dialogs_dict.get(did); if (dialog != null && dialog.unread_mark != update.unread) { dialog.unread_mark = update.unread; - if (dialog.unread_count == 0 && !isDialogMuted(did)) { + if (dialog.unread_count == 0 && !isDialogMuted(did, 0)) { if (dialog.unread_mark) { unreadUnmutedDialogs++; } else { @@ -14595,7 +14957,7 @@ public class MessagesController extends BaseController implements NotificationCe TLRPC.TL_updateReadChannelDiscussionInbox update = (TLRPC.TL_updateReadChannelDiscussionInbox) baseUpdate; getNotificationCenter().postNotificationName(NotificationCenter.threadMessagesRead, -update.channel_id, update.top_msg_id, update.read_max_id, 0); if ((update.flags & 1) != 0) { - getMessagesStorage().updateRepliesMaxReadId(update.broadcast_id, update.broadcast_post, update.read_max_id, true); + getMessagesStorage().updateRepliesMaxReadId(update.broadcast_id, update.broadcast_post, update.read_max_id, 0,true); getNotificationCenter().postNotificationName(NotificationCenter.commentsRead, update.broadcast_id, update.broadcast_post, update.read_max_id); } } else if (baseUpdate instanceof TLRPC.TL_updateReadChannelDiscussionOutbox) { @@ -14716,7 +15078,7 @@ public class MessagesController extends BaseController implements NotificationCe array.put(webpage.id, arrayList); } if (!arr.isEmpty()) { - getMessagesStorage().putMessages(arr, true, true, false, getDownloadController().getAutodownloadMask(), i == 1); + getMessagesStorage().putMessages(arr, true, true, false, getDownloadController().getAutodownloadMask(), i == 1, 0); getNotificationCenter().postNotificationName(NotificationCenter.replaceMessagesObjects, dialogId, arrayList); } } @@ -14776,28 +15138,31 @@ public class MessagesController extends BaseController implements NotificationCe } } if (dialogId > 0) { - checkUnreadReactions(dialogId, unreadReactions); + checkUnreadReactions(dialogId, 0, unreadReactions); } - MessageObject oldObject = dialogMessage.get(dialogId); - if (oldObject != null) { - for (int a = 0, size2 = arrayList.size(); a < size2; a++) { - MessageObject newMessage = arrayList.get(a); - if (oldObject.getId() == newMessage.getId()) { - dialogMessage.put(dialogId, newMessage); - if (newMessage.messageOwner.peer_id != null && newMessage.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.put(newMessage.getId(), newMessage); + ArrayList oldObjects = dialogMessage.get(dialogId); + if (oldObjects != null) { + for (int i = 0; i < oldObjects.size(); ++i) { + MessageObject oldObject = oldObjects.get(i); + for (int a = 0, size2 = arrayList.size(); a < size2; a++) { + MessageObject newMessage = arrayList.get(a); + if (oldObject.getId() == newMessage.getId()) { + oldObjects.set(i, newMessage); + if (newMessage.messageOwner.peer_id != null && newMessage.messageOwner.peer_id.channel_id == 0) { + dialogMessagesByIds.put(newMessage.getId(), newMessage); + } + updateDialogs = true; + break; + } else if (oldObject.getDialogId() == newMessage.getDialogId() && oldObject.messageOwner.action instanceof TLRPC.TL_messageActionPinMessage && oldObject.replyMessageObject != null && oldObject.replyMessageObject.getId() == newMessage.getId()) { + oldObject.replyMessageObject = newMessage; + oldObject.generatePinMessageText(null, null); + updateDialogs = true; + break; } - updateDialogs = true; - break; - } else if (oldObject.getDialogId() == newMessage.getDialogId() && oldObject.messageOwner.action instanceof TLRPC.TL_messageActionPinMessage && oldObject.replyMessageObject != null && oldObject.replyMessageObject.getId() == newMessage.getId()) { - oldObject.replyMessageObject = newMessage; - oldObject.generatePinMessageText(null, null); - updateDialogs = true; - break; } } } - getMediaDataController().loadReplyMessagesForMessages(arrayList, dialogId, false, null); + getMediaDataController().loadReplyMessagesForMessages(arrayList, dialogId, false, 0,null); getNotificationCenter().postNotificationName(NotificationCenter.replaceMessagesObjects, dialogId, arrayList, false); } } @@ -14849,10 +15214,15 @@ public class MessagesController extends BaseController implements NotificationCe int messageId = markAsReadMessagesInboxFinal.valueAt(b); TLRPC.Dialog dialog = dialogs_dict.get(key); if (dialog != null && dialog.top_message > 0 && dialog.top_message <= messageId) { - MessageObject obj = dialogMessage.get(dialog.id); - if (obj != null && !obj.isOut()) { - obj.setIsRead(); - updateMask |= UPDATE_MASK_READ_DIALOG_MESSAGE; + ArrayList objs = dialogMessage.get(dialog.id); + if (objs != null) { + for (int i = 0; i < objs.size(); ++i) { + MessageObject obj = objs.get(i); + if (obj != null && !obj.isOut()) { + obj.setIsRead(); + updateMask |= UPDATE_MASK_READ_DIALOG_MESSAGE; + } + } } } if (key != getUserConfig().getClientUserId()) { @@ -14867,11 +15237,20 @@ public class MessagesController extends BaseController implements NotificationCe long key = markAsReadMessagesOutboxFinal.keyAt(b); int messageId = markAsReadMessagesOutboxFinal.valueAt(b); TLRPC.Dialog dialog = dialogs_dict.get(key); + if (dialog != null && messageId > dialog.read_outbox_max_id) { + dialog.read_outbox_max_id = messageId; + updateMask |= UPDATE_MASK_READ_DIALOG_MESSAGE; + } if (dialog != null && dialog.top_message > 0 && dialog.top_message <= messageId) { - MessageObject obj = dialogMessage.get(dialog.id); - if (obj != null && obj.isOut()) { - obj.setIsRead(); - updateMask |= UPDATE_MASK_READ_DIALOG_MESSAGE; + ArrayList objs = dialogMessage.get(dialog.id); + if (objs != null) { + for (int i = 0; i < objs.size(); ++i) { + MessageObject obj = objs.get(i); + if (obj != null && obj.isOut()) { + obj.setIsRead(); + updateMask |= UPDATE_MASK_READ_DIALOG_MESSAGE; + } + } } } } @@ -14885,10 +15264,15 @@ public class MessagesController extends BaseController implements NotificationCe long dialogId = DialogObject.makeEncryptedDialogId(key); TLRPC.Dialog dialog = dialogs_dict.get(dialogId); if (dialog != null) { - MessageObject message = dialogMessage.get(dialogId); - if (message != null && message.messageOwner.date <= value) { - message.setIsRead(); - updateMask |= UPDATE_MASK_READ_DIALOG_MESSAGE; + ArrayList dialogMessages = dialogMessage.get(dialogId); + if (dialogMessages != null) { + for (int i = 0; i < dialogMessages.size(); ++i) { + MessageObject message = dialogMessages.get(i); + if (message != null && message.messageOwner.date <= value) { + message.setIsRead(); + updateMask |= UPDATE_MASK_READ_DIALOG_MESSAGE; + } + } } } } @@ -14920,12 +15304,17 @@ public class MessagesController extends BaseController implements NotificationCe } } } else { - MessageObject obj = dialogMessage.get(dialogId); - if (obj != null) { - for (int b = 0, size2 = arrayList.size(); b < size2; b++) { - if (obj.getId() == arrayList.get(b)) { - obj.deleted = true; - break; + ArrayList objs = dialogMessage.get(dialogId); + if (objs != null) { + for (int i = 0; i < objs.size(); ++i) { + MessageObject obj = objs.get(i); + if (obj != null) { + for (int b = 0, size2 = arrayList.size(); b < size2; b++) { + if (obj.getId() == arrayList.get(b)) { + obj.deleted = true; + break; + } + } } } } @@ -14950,11 +15339,14 @@ public class MessagesController extends BaseController implements NotificationCe int id = clearHistoryMessagesFinal.valueAt(a); long did = -key; getNotificationCenter().postNotificationName(NotificationCenter.historyCleared, did, id); - MessageObject obj = dialogMessage.get(did); - if (obj != null) { - if (obj.getId() <= id) { - obj.deleted = true; - break; + ArrayList objs = dialogMessage.get(did); + if (objs != null) { + for (int i = 0; i < objs.size(); ++i) { + MessageObject obj = objs.get(i); + if (obj != null && obj.getId() <= id) { + obj.deleted = true; + break; + } } } } @@ -14974,6 +15366,15 @@ public class MessagesController extends BaseController implements NotificationCe } getMessagesStorage().markMessagesAsRead(markAsReadMessagesInbox, markAsReadMessagesOutbox, markAsReadEncrypted, true); } + if (topicsReadInbox != null) { + for (MessagesStorage.TopicKey topicKey : topicsReadInbox.keySet()) { + getMessagesStorage().updateRepliesMaxReadId(-topicKey.dialogId, topicKey.topicId, topicsReadInbox.get(topicKey), -1, true); + } + } + if (topicsReadOutbox != null) { + getMessagesStorage().updateTopicsWithReadMessages(topicsReadOutbox); + getTopicsController().updateReadOutbox(topicsReadOutbox); + } if (markContentAsReadMessages != null) { int time = getConnectionsManager().getCurrentTime(); for (int a = 0, size = markContentAsReadMessages.size(); a < size; a++) { @@ -14990,6 +15391,7 @@ public class MessagesController extends BaseController implements NotificationCe ArrayList dialogIds = getMessagesStorage().markMessagesAsDeleted(key, arrayList, false, true, false); getMessagesStorage().updateDialogsWithDeletedMessages(key, -key, arrayList, dialogIds, false); }); + } } if (scheduledDeletedMessages != null) { @@ -15019,7 +15421,7 @@ public class MessagesController extends BaseController implements NotificationCe return true; } - public void checkUnreadReactions(long dialogId, SparseBooleanArray unreadReactions) { + public void checkUnreadReactions(long dialogId, int topicId, SparseBooleanArray unreadReactions) { getMessagesStorage().getStorageQueue().postRunnable(() -> { boolean needReload = false; boolean changed = false; @@ -15035,7 +15437,12 @@ public class MessagesController extends BaseController implements NotificationCe } SparseBooleanArray reactionsMentionsMessageIds = new SparseBooleanArray(); try { - SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT message_id, state FROM reaction_mentions WHERE message_id IN (%s) AND dialog_id = %d", stringBuilder.toString(), dialogId)); + SQLiteCursor cursor = null; + if (topicId != 0) { + cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT message_id, state FROM reaction_mentions WHERE message_id IN (%s) AND dialog_id = %d", stringBuilder, dialogId)); + } else { + cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT message_id, state FROM reaction_mentions_topics WHERE message_id IN (%s) AND dialog_id = %d AND topic_id = %d", stringBuilder, dialogId, topicId)); + } while (cursor.next()) { int messageId = cursor.intValue(0); boolean hasUnreadReactions = cursor.intValue(1) == 1; @@ -15063,67 +15470,101 @@ public class MessagesController extends BaseController implements NotificationCe SQLitePreparedStatement state = null; try { - state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO reaction_mentions VALUES(?, ?, ?)"); - state.requery(); - state.bindInteger(1, messageId); - state.bindInteger(2, hasUnreadReaction ? 1 : 0); - state.bindLong(3, dialogId); - state.step(); - state.dispose(); + if (topicId == 0) { + state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO reaction_mentions VALUES(?, ?, ?)"); + state.requery(); + state.bindInteger(1, messageId); + state.bindInteger(2, hasUnreadReaction ? 1 : 0); + state.bindLong(3, dialogId); + state.step(); + state.dispose(); + } else { + state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO reaction_mentions_topics VALUES(?, ?, ?, ?)"); + state.requery(); + state.bindInteger(1, messageId); + state.bindInteger(2, hasUnreadReaction ? 1 : 0); + state.bindLong(3, dialogId); + state.bindInteger(4, topicId); + state.step(); + state.dispose(); + } } catch (SQLiteException e) { e.printStackTrace(); } } if (needReload) { - TLRPC.TL_messages_getPeerDialogs req = new TLRPC.TL_messages_getPeerDialogs(); - TLRPC.TL_inputDialogPeer inputDialogPeer = new TLRPC.TL_inputDialogPeer(); - inputDialogPeer.peer = getInputPeer(dialogId); - req.peers.add(inputDialogPeer); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { - if (response != null) { - TLRPC.TL_messages_peerDialogs dialogs = (TLRPC.TL_messages_peerDialogs) response; - int count = dialogs.dialogs.size() == 0 ? 0 : dialogs.dialogs.get(0).unread_reactions_count; - AndroidUtilities.runOnUIThread(() -> { - TLRPC.Dialog dialog = dialogs_dict.get(dialogId); - if (dialog == null) { - getMessagesStorage().updateDialogUnreadReactions(dialogId, count, false); - return; - } - dialog.unread_reactions_count = count; - getMessagesStorage().updateUnreadReactionsCount(dialogId, count); - getNotificationCenter().postNotificationName(NotificationCenter.dialogsUnreadReactionsCounterChanged, dialogId, count, newUnreadMessages); - }); - } - }); + if (topicId == 0) { + TLRPC.TL_messages_getPeerDialogs req = new TLRPC.TL_messages_getPeerDialogs(); + TLRPC.TL_inputDialogPeer inputDialogPeer = new TLRPC.TL_inputDialogPeer(); + inputDialogPeer.peer = getInputPeer(dialogId); + req.peers.add(inputDialogPeer); + ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + if (response != null) { + TLRPC.TL_messages_peerDialogs dialogs = (TLRPC.TL_messages_peerDialogs) response; + int count = dialogs.dialogs.size() == 0 ? 0 : dialogs.dialogs.get(0).unread_reactions_count; + AndroidUtilities.runOnUIThread(() -> { + TLRPC.Dialog dialog = dialogs_dict.get(dialogId); + if (dialog == null) { + getMessagesStorage().updateDialogUnreadReactions(dialogId, 0, count, false); + return; + } + dialog.unread_reactions_count = count; + getMessagesStorage().updateUnreadReactionsCount(dialogId, topicId, count); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsUnreadReactionsCounterChanged, dialogId, topicId, count, newUnreadMessages); + }); + } + }); + } else { + TLRPC.TL_channels_getForumTopicsByID req = new TLRPC.TL_channels_getForumTopicsByID(); + req.topics.add(topicId); + req.channel = getMessagesController().getInputChannel(-dialogId); + ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + if (response != null) { + TLRPC.TL_messages_forumTopics topics = (TLRPC.TL_messages_forumTopics) response; + int count = topics.topics.size() == 0 ? 0 : topics.topics.get(0).unread_reactions_count; + AndroidUtilities.runOnUIThread(() -> { + getMessagesController().getTopicsController().updateReactionsUnread(dialogId, topicId, count, false); + getMessagesStorage().updateUnreadReactionsCount(dialogId, topicId, count); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsUnreadReactionsCounterChanged, dialogId, topicId, count, newUnreadMessages); + }); + } + }); + } } else if (changed) { int finalNewUnreadCount = newUnreadCount; AndroidUtilities.runOnUIThread(() -> { - TLRPC.Dialog dialog = dialogs_dict.get(dialogId); - if (dialog == null) { - getMessagesStorage().updateDialogUnreadReactions(dialogId, finalNewUnreadCount, true); - return; + if (topicId == 0) { + TLRPC.Dialog dialog = dialogs_dict.get(dialogId); + if (dialog == null) { + getMessagesStorage().updateDialogUnreadReactions(dialogId, 0, finalNewUnreadCount, true); + return; + } + dialog.unread_reactions_count += finalNewUnreadCount; + if (dialog.unread_reactions_count < 0) { + dialog.unread_reactions_count = 0; + } + getMessagesStorage().updateUnreadReactionsCount(dialogId, 0, dialog.unread_reactions_count); + } else { + int totalCount = getMessagesController().getTopicsController().updateReactionsUnread(dialogId, topicId, finalNewUnreadCount, true); + if (totalCount >= 0) { + getMessagesStorage().updateUnreadReactionsCount(dialogId, topicId, totalCount, true); + } } - dialog.unread_reactions_count += finalNewUnreadCount; - if (dialog.unread_reactions_count < 0) { - dialog.unread_reactions_count = 0; - } - getMessagesStorage().updateUnreadReactionsCount(dialogId, dialog.unread_reactions_count); - getNotificationCenter().postNotificationName(NotificationCenter.dialogsUnreadReactionsCounterChanged, dialogId, dialog.unread_reactions_count, newUnreadMessages); }); } }); } - public boolean isDialogMuted(long dialogId) { - return isDialogMuted(dialogId, null); + public boolean isDialogMuted(long dialogId, int topicId) { + return isDialogMuted(dialogId, topicId, null); } - public boolean isDialogNotificationsSoundEnabled(long dialogId) { - return notificationsPreferences.getBoolean("sound_enabled_" + dialogId, true); + public boolean isDialogNotificationsSoundEnabled(long dialogId, int topicId) { + return notificationsPreferences.getBoolean("sound_enabled_" + NotificationsController.getSharedPrefKey(dialogId, topicId), true); } - public boolean isDialogMuted(long dialogId, TLRPC.Chat chat) { - int mute_type = notificationsPreferences.getInt("notify2_" + dialogId, -1); + public boolean isDialogMuted(long dialogId, int topicId, TLRPC.Chat chat) { + int mute_type = notificationsPreferences.getInt("notify2_" + NotificationsController.getSharedPrefKey(dialogId, topicId), -1); if (mute_type == -1) { Boolean forceChannel; if (chat != null) { @@ -15131,12 +15572,16 @@ public class MessagesController extends BaseController implements NotificationCe } else { forceChannel = null; } - return !getNotificationsController().isGlobalNotificationsEnabled(dialogId, forceChannel); + if (topicId != 0) { + return isDialogMuted(dialogId, 0, chat); + } else { + return !getNotificationsController().isGlobalNotificationsEnabled(dialogId, forceChannel); + } } if (mute_type == 2) { return true; } else if (mute_type == 3) { - int mute_until = notificationsPreferences.getInt("notifyuntil_" + dialogId, 0); + int mute_until = notificationsPreferences.getInt("notifyuntil_" + NotificationsController.getSharedPrefKey(dialogId, topicId), 0); if (mute_until >= getConnectionsManager().getCurrentTime()) { return true; } @@ -15144,23 +15589,31 @@ public class MessagesController extends BaseController implements NotificationCe return false; } - public void markReactionsAsRead(long dialogId) { - TLRPC.Dialog dialog = dialogs_dict.get(dialogId); - if (dialog != null) { - dialog.unread_reactions_count = 0; + public void markReactionsAsRead(long dialogId, int topicId) { + if (topicId == 0) { + TLRPC.Dialog dialog = dialogs_dict.get(dialogId); + if (dialog != null) { + dialog.unread_reactions_count = 0; + } + } else { + topicsController.markAllReactionsAsRead(-dialogId, topicId); } - getMessagesStorage().updateUnreadReactionsCount(dialogId, 0); + getMessagesStorage().updateUnreadReactionsCount(dialogId, topicId, 0); TLRPC.TL_messages_readReactions req = new TLRPC.TL_messages_readReactions(); req.peer = getInputPeer(dialogId); + if (topicId != 0) { + req.top_msg_id = topicId; + } getConnectionsManager().sendRequest(req, (response, error) -> { }); + NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_REACTIONS_READ); } - public ArrayList getSponsoredMessages(long dialogId) { + public SponsoredMessagesInfo getSponsoredMessages(long dialogId) { SponsoredMessagesInfo info = sponsoredMessages.get(dialogId); if (info != null && (info.loading || Math.abs(SystemClock.elapsedRealtime() - info.loadTime) <= 5 * 60 * 1000)) { - return info.messages; + return info; } TLRPC.Chat chat = getChat(-dialogId); if (!ChatObject.isChannel(chat)) { @@ -15174,11 +15627,18 @@ public class MessagesController extends BaseController implements NotificationCe req.channel = getInputChannel(chat); getConnectionsManager().sendRequest(req, (response, error) -> { ArrayList result; - if (response != null) { - TLRPC.TL_messages_sponsoredMessages res = (TLRPC.TL_messages_sponsoredMessages) response; + Integer posts_between; + if (response instanceof TLRPC.messages_SponsoredMessages) { + TLRPC.messages_SponsoredMessages res = (TLRPC.messages_SponsoredMessages) response; if (res.messages.isEmpty()) { result = null; + posts_between = null; } else { + if (res instanceof TLRPC.TL_messages_sponsoredMessages && (res.flags & 0x1) > 0) { + posts_between = res.posts_between; + } else { + posts_between = null; + } result = new ArrayList<>(); AndroidUtilities.runOnUIThread(() -> { putUsers(res.users, false); @@ -15216,11 +15676,14 @@ public class MessagesController extends BaseController implements NotificationCe messageObject.sponsoredChannelPost = sponsoredMessage.channel_post; messageObject.sponsoredChatInvite = sponsoredMessage.chat_invite; messageObject.sponsoredChatInviteHash = sponsoredMessage.chat_invite_hash; + messageObject.sponsoredRecommended = sponsoredMessage.recommended; + messageObject.sponsoredShowPeerPhoto = sponsoredMessage.show_peer_photo; result.add(messageObject); } } } else { result = null; + posts_between = null; } AndroidUtilities.runOnUIThread(() -> { if (result == null) { @@ -15228,6 +15691,7 @@ public class MessagesController extends BaseController implements NotificationCe } else { infoFinal.loadTime = SystemClock.elapsedRealtime(); infoFinal.messages = result; + infoFinal.posts_between = posts_between; getNotificationCenter().postNotificationName(NotificationCenter.didLoadSponsoredMessages, dialogId, result); } }); @@ -15235,6 +15699,10 @@ public class MessagesController extends BaseController implements NotificationCe return null; } + public void clearSendAsPeers() { + sendAsPeers.clear(); + } + public TLRPC.TL_channels_sendAsPeers getSendAsPeers(long dialogId) { SendAsPeersInfo info = sendAsPeers.get(dialogId); if (info != null && (info.loading || Math.abs(SystemClock.elapsedRealtime() - info.loadTime) <= 5 * 60 * 1000)) { @@ -15413,7 +15881,7 @@ public class MessagesController extends BaseController implements NotificationCe } } } - getMediaDataController().loadReplyMessagesForMessages(messages, dialogId, scheduled, null); + getMediaDataController().loadReplyMessagesForMessages(messages, dialogId, scheduled, 0, null); getNotificationCenter().postNotificationName(NotificationCenter.didReceiveNewMessages, dialogId, messages, scheduled); if (lastMessage == null || scheduled) { @@ -15489,13 +15957,20 @@ public class MessagesController extends BaseController implements NotificationCe dialog.flags = ChatObject.isChannel(chat) ? 1 : 0; dialogs_dict.put(dialogId, dialog); allDialogs.add(dialog); - dialogMessage.put(dialogId, lastMessage); - if (lastMessage.messageOwner.peer_id.channel_id == 0) { - dialogMessagesByIds.put(lastMessage.getId(), lastMessage); - if (lastMessage.messageOwner.random_id != 0) { - dialogMessagesByRandomIds.put(lastMessage.messageOwner.random_id, lastMessage); + ArrayList arrayList = new ArrayList(); + for (int i = 0; i < messages.size(); ++i) { + MessageObject msg = messages.get(i); + if (msg != null && lastMessage != null && (msg.getId() == lastMessage.getId() || msg.hasValidGroupId() && lastMessage.hasValidGroupId() && msg.getGroupIdForUse() == lastMessage.getGroupIdForUse())) { + arrayList.add(msg); + if (msg.messageOwner.peer_id.channel_id == 0) { + dialogMessagesByIds.put(msg.getId(), msg); + if (msg.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.put(msg.messageOwner.random_id, msg); + } + } } } + dialogMessage.put(dialogId, arrayList); changed = true; TLRPC.Dialog dialogFinal = dialog; @@ -15526,7 +16001,14 @@ public class MessagesController extends BaseController implements NotificationCe dialog.top_message = lastMessage.getId(); dialog.last_message_date = lastMessage.messageOwner.date; changed = true; - dialogMessage.put(dialogId, lastMessage); + ArrayList arrayList = new ArrayList<>(1); + for (int i = 0; i < messages.size(); ++i) { + MessageObject msg = messages.get(i); + if (msg != null && lastMessage != null && (msg.getId() == lastMessage.getId() || msg.hasValidGroupId() && lastMessage.hasValidGroupId() && msg.getGroupIdForUse() == lastMessage.getGroupIdForUse())) { + arrayList.add(msg); + } + } + dialogMessage.put(dialogId, arrayList); if (lastMessage.messageOwner.peer_id.channel_id == 0) { dialogMessagesByIds.put(lastMessage.getId(), lastMessage); if (lastMessage.messageOwner.random_id != 0) { @@ -15650,9 +16132,20 @@ public class MessagesController extends BaseController implements NotificationCe for (int a = 0, N = allDialogs.size(); a < N; a++) { TLRPC.Dialog d = allDialogs.get(a); if (d instanceof TLRPC.TL_dialog) { - MessageObject messageObject = dialogMessage.get(d.id); - if (messageObject != null && messageObject.messageOwner.date < dialogsLoadedTillDate) { - continue; + ArrayList messageObjects = dialogMessage.get(d.id); + if (messageObjects != null) { + int maxDate = Integer.MIN_VALUE; + for (int i = 0; i < messageObjects.size(); ++i) { + MessageObject message = messageObjects.get(i); + if (message != null && message.messageOwner != null && message.messageOwner.date > maxDate) { + maxDate = message.messageOwner.date; + } + } + if (maxDate > Integer.MIN_VALUE) { + if (maxDate < dialogsLoadedTillDate) { + continue; + } + } } boolean canAddToForward = true; if (!DialogObject.isEncryptedDialog(d.id)) { @@ -15712,7 +16205,7 @@ public class MessagesController extends BaseController implements NotificationCe } } } - if ((d.unread_count != 0 || d.unread_mark) && !isDialogMuted(d.id)) { + if ((getDialogUnreadCount(d) != 0 || d.unread_mark) && !isDialogMuted(d.id, 0)) { unreadUnmutedDialogs++; } if (promoDialog != null && d.id == promoDialog.id && isLeftPromoChannel) { @@ -15785,13 +16278,18 @@ public class MessagesController extends BaseController implements NotificationCe return null; } - private static void showCantOpenAlert(BaseFragment fragment, String reason) { + public static void showCantOpenAlert(BaseFragment fragment, String reason) { if (fragment == null || fragment.getParentActivity() == null) { return; } - AlertDialog.Builder builder = new AlertDialog.Builder(fragment.getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + AlertDialog.Builder builder = new AlertDialog.Builder(fragment.getParentActivity(), fragment.getResourceProvider()); + builder.setTitle(LocaleController.getString(R.string.DialogNotAvailable)); + Map colorsReplacement = new HashMap<>(); + colorsReplacement.put("info1.**", fragment.getThemedColor(Theme.key_dialogTopBackground)); + colorsReplacement.put("info2.**", fragment.getThemedColor(Theme.key_dialogTopBackground)); + builder.setTopAnimation(R.raw.not_available, AlertsCreator.NEW_DENY_DIALOG_TOP_ICON_SIZE, false, fragment.getThemedColor(Theme.key_dialogTopBackground), colorsReplacement); + builder.setTopAnimationIsNew(true); + builder.setPositiveButton(LocaleController.getString(R.string.Close), null); builder.setMessage(reason); fragment.showDialog(builder.create()); } @@ -15958,6 +16456,9 @@ public class MessagesController extends BaseController implements NotificationCe } else { if (fragment.getParentActivity() != null) { try { + if (fragment instanceof ChatActivity) { + ((ChatActivity) fragment).shakeContent(); + } BulletinFactory.of(fragment).createErrorBulletin(LocaleController.getString("NoUsernameFound", R.string.NoUsernameFound)).show(); } catch (Exception e) { FileLog.e(e); @@ -16025,9 +16526,9 @@ public class MessagesController extends BaseController implements NotificationCe int lastMessageId = (int) args[4]; if ((size < count / 2 && !isEnd) && isCache) { if (finalMessageId != 0) { - loadMessagesInternal(dialogId, 0, false, count, finalMessageId, 0, false, 0, classGuid, 3, lastMessageId, 0, 0, -1, 0, 0, 0, false, 0, true, false); + loadMessagesInternal(dialogId, 0, false, count, finalMessageId, 0, false, 0, classGuid, 3, lastMessageId, 0, 0, -1, 0, 0, 0, false, 0, true, false, false); } else { - loadMessagesInternal(dialogId, 0, false, count, finalMessageId, 0, false, 0, classGuid, 2, lastMessageId, 0, 0, -1, 0, 0, 0, false, 0, true, false); + loadMessagesInternal(dialogId, 0, false, count, finalMessageId, 0, false, 0, classGuid, 2, lastMessageId, 0, 0, -1, 0, 0, 0, false, 0, true, false, false); } } else { getNotificationCenter().removeObserver(this, NotificationCenter.messagesDidLoadWithoutProcess); @@ -16051,9 +16552,9 @@ public class MessagesController extends BaseController implements NotificationCe getNotificationCenter().addObserver(delegate, NotificationCenter.loadingMessagesFailed); if (messageId != 0) { - loadMessagesInternal(dialogId, 0, true, count, finalMessageId, 0, true, 0, classGuid, 3, 0, 0, 0, -1, 0, 0, 0, false, 0, true, false); + loadMessagesInternal(dialogId, 0, true, count, finalMessageId, 0, true, 0, classGuid, 3, 0, 0, 0, -1, 0, 0, 0, false, 0, true, false, false); } else { - loadMessagesInternal(dialogId, 0, true, count, finalMessageId, 0, true, 0, classGuid, 2, 0, 0, 0, -1, 0, 0, 0, false, 0, true, false); + loadMessagesInternal(dialogId, 0, true, count, finalMessageId, 0, true, 0, classGuid, 2, 0, 0, 0, -1, 0, 0, 0, false, 0, true, false, false); } } @@ -16130,7 +16631,7 @@ public class MessagesController extends BaseController implements NotificationCe full.available_reactions = req.available_reactions; getMessagesStorage().updateChatInfo(full, false); } - AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.chatAvailableReactionsUpdated, chatId)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.chatAvailableReactionsUpdated, chatId, 0)); } }); } @@ -16185,57 +16686,6 @@ public class MessagesController extends BaseController implements NotificationCe } } - private void applySoundSettings(TLRPC.NotificationSound settings, SharedPreferences.Editor editor, long dialogId, int globalType, boolean serverUpdate) { - if (settings == null) { - return; - } - String soundPref; - String soundPathPref; - String soundDocPref; - if (dialogId != 0) { - soundPref = "sound_" + dialogId; - soundPathPref = "sound_path_" + dialogId; - soundDocPref = "sound_document_id_" + dialogId; - } else { - if (globalType == NotificationsController.TYPE_GROUP) { - soundPref = "GroupSound"; - soundDocPref = "GroupSoundDocId"; - soundPathPref = "GroupSoundPath"; - } else if (globalType == TYPE_PRIVATE) { - soundPref = "GlobalSound"; - soundDocPref = "GlobalSoundDocId"; - soundPathPref = "GlobalSoundPath"; - } else { - soundPref = "ChannelSound"; - soundDocPref = "ChannelSoundDocId"; - soundPathPref = "ChannelSoundPath"; - } - } - - if (settings instanceof TLRPC.TL_notificationSoundDefault) { - editor.putString(soundPref, "Default"); - editor.putString(soundPathPref, "Default"); - editor.remove(soundDocPref); - } else if (settings instanceof TLRPC.TL_notificationSoundNone) { - editor.putString(soundPref, "NoSound"); - editor.putString(soundPathPref, "NoSound"); - editor.remove(soundDocPref); - } else if (settings instanceof TLRPC.TL_notificationSoundLocal) { - TLRPC.TL_notificationSoundLocal localSound = (TLRPC.TL_notificationSoundLocal) settings; - editor.putString(soundPref, localSound.title); - editor.putString(soundPathPref, localSound.data); - editor.remove(soundDocPref); - } else if (settings instanceof TLRPC.TL_notificationSoundRingtone) { - TLRPC.TL_notificationSoundRingtone soundRingtone = (TLRPC.TL_notificationSoundRingtone) settings; - editor.putLong(soundDocPref, soundRingtone.id); - getMediaDataController().checkRingtones(); - if (serverUpdate && dialogId != 0) { - editor.putBoolean("custom_" + dialogId, true); - } - getMediaDataController().ringtoneDataStore.getDocument(soundRingtone.id); - } - } - public interface IsInChatCheckedCallback { void run(boolean isInChat, TLRPC.TL_chatAdminRights currentAdminRights, String rank); } @@ -16292,4 +16742,28 @@ public class MessagesController extends BaseController implements NotificationCe } } + public String getMutedString(long dialogId, int topicId) { + if (getMessagesController().isDialogMuted(dialogId, topicId)) { + int mute_until = notificationsPreferences.getInt("notifyuntil_" + NotificationsController.getSharedPrefKey(dialogId, topicId), 0); + if (mute_until >= getConnectionsManager().getCurrentTime()) { + return LocaleController.formatString("NotificationsMutedForHint", R.string.NotificationsMutedForHint, LocaleController.formatTTLString(mute_until)); + } + return LocaleController.getString(R.string.NotificationsMuted); + } else { + return LocaleController.getString(R.string.NotificationsUnmuted); + } + } + + public int getDialogUnreadCount(TLRPC.Dialog d) { + if (d == null) { + return 0; + } + int unreadCount = d.unread_count; + TLRPC.Chat chatLocal = getChat(-d.id); + if (chatLocal != null && chatLocal.forum) { + unreadCount = topicsController.getForumUnreadCount(-d.id)[0]; + } + return unreadCount; + } + } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java index 7611ad47b..7aecd408a 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java @@ -41,13 +41,16 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; public class MessagesStorage extends BaseController { @@ -90,10 +93,10 @@ public class MessagesStorage extends BaseController { } } - private final static int LAST_DB_VERSION = 101; + private final static int LAST_DB_VERSION = 106; private boolean databaseMigrationInProgress; public boolean showClearDatabaseAlert; - + private LongSparseIntArray dialogIsForum = new LongSparseIntArray(); public static MessagesStorage getInstance(int num) { MessagesStorage localInstance = Instance[num]; @@ -294,7 +297,7 @@ public class MessagesStorage extends BaseController { database.executeFast("CREATE INDEX IF NOT EXISTS uid_date_idx_scheduled_messages_v2 ON scheduled_messages_v2(uid, date);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS reply_to_idx_scheduled_messages_v2 ON scheduled_messages_v2(mid, reply_to_message_id);").stepThis().dispose(); - database.executeFast("CREATE TABLE messages_v2(mid INTEGER, uid INTEGER, read_state INTEGER, send_state INTEGER, date INTEGER, data BLOB, out INTEGER, ttl INTEGER, media INTEGER, replydata BLOB, imp INTEGER, mention INTEGER, forwards INTEGER, replies_data BLOB, thread_reply_id INTEGER, is_channel INTEGER, reply_to_message_id INTEGER, custom_params BLOB, PRIMARY KEY(mid, uid))").stepThis().dispose(); + database.executeFast("CREATE TABLE messages_v2(mid INTEGER, uid INTEGER, read_state INTEGER, send_state INTEGER, date INTEGER, data BLOB, out INTEGER, ttl INTEGER, media INTEGER, replydata BLOB, imp INTEGER, mention INTEGER, forwards INTEGER, replies_data BLOB, thread_reply_id INTEGER, is_channel INTEGER, reply_to_message_id INTEGER, custom_params BLOB, group_id INTEGER, PRIMARY KEY(mid, uid))").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_read_out_idx_messages_v2 ON messages_v2(uid, mid, read_state, out);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_date_mid_idx_messages_v2 ON messages_v2(uid, date, mid);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS mid_out_idx_messages_v2 ON messages_v2(mid, out);").stepThis().dispose(); @@ -303,6 +306,7 @@ public class MessagesStorage extends BaseController { database.executeFast("CREATE INDEX IF NOT EXISTS uid_mention_idx_messages_v2 ON messages_v2(uid, mention, read_state);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS is_channel_idx_messages_v2 ON messages_v2(mid, is_channel);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS reply_to_idx_messages_v2 ON messages_v2(mid, reply_to_message_id);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_groupid_messages_v2 ON messages_v2(uid, mid, group_id);").stepThis().dispose(); database.executeFast("CREATE TABLE download_queue(uid INTEGER, type INTEGER, date INTEGER, data BLOB, parent TEXT, PRIMARY KEY (uid, type));").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS type_date_idx_download_queue ON download_queue(type, date);").stepThis().dispose(); @@ -311,7 +315,7 @@ public class MessagesStorage extends BaseController { database.executeFast("CREATE TABLE user_phones_v7(key TEXT, phone TEXT, sphone TEXT, deleted INTEGER, PRIMARY KEY (key, phone))").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS sphone_deleted_idx_user_phones ON user_phones_v7(sphone, deleted);").stepThis().dispose(); - database.executeFast("CREATE TABLE dialogs(did INTEGER PRIMARY KEY, date INTEGER, unread_count INTEGER, last_mid INTEGER, inbox_max INTEGER, outbox_max INTEGER, last_mid_i INTEGER, unread_count_i INTEGER, pts INTEGER, date_i INTEGER, pinned INTEGER, flags INTEGER, folder_id INTEGER, data BLOB, unread_reactions INTEGER)").stepThis().dispose(); + database.executeFast("CREATE TABLE dialogs(did INTEGER PRIMARY KEY, date INTEGER, unread_count INTEGER, last_mid INTEGER, inbox_max INTEGER, outbox_max INTEGER, last_mid_i INTEGER, unread_count_i INTEGER, pts INTEGER, date_i INTEGER, pinned INTEGER, flags INTEGER, folder_id INTEGER, data BLOB, unread_reactions INTEGER, last_mid_group INTEGER)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS date_idx_dialogs ON dialogs(date);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS last_mid_idx_dialogs ON dialogs(last_mid);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS unread_count_idx_dialogs ON dialogs(unread_count);").stepThis().dispose(); @@ -409,6 +413,36 @@ public class MessagesStorage extends BaseController { database.executeFast("CREATE TABLE premium_promo(data BLOB, date INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE emoji_statuses(data BLOB, type INTEGER);").stepThis().dispose(); + + database.executeFast("CREATE TABLE messages_holes_topics(uid INTEGER, topic_id INTEGER, start INTEGER, end INTEGER, PRIMARY KEY(uid, topic_id, start));").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_end_messages_holes ON messages_holes_topics(uid, topic_id, end);").stepThis().dispose(); + + database.executeFast("CREATE TABLE messages_topics(mid INTEGER, uid INTEGER, topic_id INTEGER, read_state INTEGER, send_state INTEGER, date INTEGER, data BLOB, out INTEGER, ttl INTEGER, media INTEGER, replydata BLOB, imp INTEGER, mention INTEGER, forwards INTEGER, replies_data BLOB, thread_reply_id INTEGER, is_channel INTEGER, reply_to_message_id INTEGER, custom_params BLOB, PRIMARY KEY(mid, topic_id, uid))").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_read_out_idx_messages_topics ON messages_topics(uid, mid, read_state, out);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_date_mid_idx_messages_topics ON messages_topics(uid, date, mid);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS mid_out_idx_messages_topics ON messages_topics(mid, out);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS task_idx_messages_topics ON messages_topics(uid, out, read_state, ttl, date, send_state);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS send_state_idx_messages_topics ON messages_topics(mid, send_state, date);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_mention_idx_messages_topics ON messages_topics(uid, mention, read_state);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS is_channel_idx_messages_topics ON messages_topics(mid, is_channel);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS reply_to_idx_messages_topics ON messages_topics(mid, reply_to_message_id);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS mid_uid_messages_topics ON messages_topics(mid, uid);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS mid_uid_messages_topics ON messages_topics(mid, topic_id, uid);").stepThis().dispose(); + + database.executeFast("CREATE TABLE media_topics(mid INTEGER, uid INTEGER, topic_id INTEGER, date INTEGER, type INTEGER, data BLOB, PRIMARY KEY(mid, uid, topic_id, type))").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_type_date_idx_media_topics ON media_topics(uid, topic_id, mid, type, date);").stepThis().dispose(); + + database.executeFast("CREATE TABLE media_holes_topics(uid INTEGER, topic_id INTEGER, type INTEGER, start INTEGER, end INTEGER, PRIMARY KEY(uid, topic_id, type, start));").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_end_media_holes_topics ON media_holes_topics(uid, topic_id, type, end);").stepThis().dispose(); + + database.executeFast("CREATE TABLE topics(did INTEGER, topic_id INTEGER, data BLOB, top_message INTEGER, topic_message BLOB, unread_count INTEGER, max_read_id INTEGER, unread_mentions INTEGER, unread_reactions INTEGER, read_outbox INTEGER, pinned INTEGER, PRIMARY KEY(did, topic_id));").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS did_top_message_topics ON topics(did, top_message);").stepThis().dispose(); + + database.executeFast("CREATE TABLE media_counts_topics(uid INTEGER, topic_id INTEGER, type INTEGER, count INTEGER, old INTEGER, PRIMARY KEY(uid, topic_id, type))").stepThis().dispose(); + + database.executeFast("CREATE TABLE reaction_mentions_topics(message_id INTEGER, state INTEGER, dialog_id INTEGER, topic_id INTEGER, PRIMARY KEY(message_id, dialog_id, topic_id))").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS reaction_mentions_topics_did ON reaction_mentions_topics(dialog_id, topic_id);").stepThis().dispose(); + //version database.executeFast("PRAGMA user_version = " + LAST_DB_VERSION).stepThis().dispose(); } else { @@ -439,10 +473,10 @@ public class MessagesStorage extends BaseController { } cursor.dispose(); } catch (Exception e) { + FileLog.e(e); if (e.getMessage() != null && e.getMessage().contains("malformed")) { throw new RuntimeException("malformed"); } - FileLog.e(e); try { database.executeFast("CREATE TABLE IF NOT EXISTS params(id INTEGER PRIMARY KEY, seq INTEGER, pts INTEGER, date INTEGER, qts INTEGER, lsv INTEGER, sg INTEGER, pbytes BLOB)").stepThis().dispose(); database.executeFast("INSERT INTO params VALUES(1, 0, 0, 0, 0, 0, 0, NULL)").stepThis().dispose(); @@ -1620,6 +1654,67 @@ public class MessagesStorage extends BaseController { version = 101; } + if (version == 101) { + database.executeFast("ALTER TABLE messages_v2 ADD COLUMN group_id INTEGER default NULL").stepThis().dispose(); + database.executeFast("ALTER TABLE dialogs ADD COLUMN last_mid_group INTEGER default NULL").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_groupid_messages_v2 ON messages_v2(uid, mid, group_id);").stepThis().dispose(); + + database.executeFast("PRAGMA user_version = 102").stepThis().dispose(); + version = 102; + } + + if (version == 102) { + database.executeFast("CREATE TABLE messages_holes_topics(uid INTEGER, topic_id INTEGER, start INTEGER, end INTEGER, PRIMARY KEY(uid, topic_id, start));").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_end_messages_holes ON messages_holes_topics(uid, topic_id, end);").stepThis().dispose(); + + database.executeFast("CREATE TABLE messages_topics(mid INTEGER, uid INTEGER, topic_id INTEGER, read_state INTEGER, send_state INTEGER, date INTEGER, data BLOB, out INTEGER, ttl INTEGER, media INTEGER, replydata BLOB, imp INTEGER, mention INTEGER, forwards INTEGER, replies_data BLOB, thread_reply_id INTEGER, is_channel INTEGER, reply_to_message_id INTEGER, custom_params BLOB, PRIMARY KEY(mid, topic_id, uid))").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_read_out_idx_messages_topics ON messages_topics(uid, mid, read_state, out);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_date_mid_idx_messages_topics ON messages_topics(uid, date, mid);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS mid_out_idx_messages_topics ON messages_topics(mid, out);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS task_idx_messages_topics ON messages_topics(uid, out, read_state, ttl, date, send_state);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS send_state_idx_messages_topics ON messages_topics(mid, send_state, date);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_mention_idx_messages_topics ON messages_topics(uid, mention, read_state);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS is_channel_idx_messages_topics ON messages_topics(mid, is_channel);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS reply_to_idx_messages_topics ON messages_topics(mid, reply_to_message_id);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS mid_uid_messages_topics ON messages_topics(mid, uid);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS mid_uid_messages_topics ON messages_topics(mid, topic_id, uid);").stepThis().dispose(); + + database.executeFast("CREATE TABLE media_topics(mid INTEGER, uid INTEGER, topic_id INTEGER, date INTEGER, type INTEGER, data BLOB, PRIMARY KEY(mid, uid, topic_id, type))").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_type_date_idx_media_topics ON media_topics(uid, topic_id, mid, type, date);").stepThis().dispose(); + + database.executeFast("CREATE TABLE media_holes_topics(uid INTEGER, topic_id INTEGER, type INTEGER, start INTEGER, end INTEGER, PRIMARY KEY(uid, topic_id, type, start));").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_end_media_holes_topics ON media_holes_topics(uid, topic_id, type, end);").stepThis().dispose(); + + database.executeFast("CREATE TABLE topics(did INTEGER, topic_id INTEGER, data BLOB, top_message INTEGER, topic_message BLOB, unread_count INTEGER, max_read_id INTEGER, unread_mentions INTEGER, unread_reactions INTEGER, PRIMARY KEY(did, topic_id));").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS did_top_message_topics ON topics(did, top_message);").stepThis().dispose(); + + database.executeFast("PRAGMA user_version = 103").stepThis().dispose(); + version = 103; + } + + if (version == 103) { + database.executeFast("CREATE TABLE IF NOT EXISTS media_counts_topics(uid INTEGER, topic_id INTEGER, type INTEGER, count INTEGER, old INTEGER, PRIMARY KEY(uid, topic_id, type))").stepThis().dispose(); + database.executeFast("CREATE TABLE IF NOT EXISTS reaction_mentions_topics(message_id INTEGER, state INTEGER, dialog_id INTEGER, topic_id INTEGER, PRIMARY KEY(message_id, dialog_id, topic_id))").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS reaction_mentions_topics_did ON reaction_mentions_topics(dialog_id, topic_id);").stepThis().dispose(); + + database.executeFast("PRAGMA user_version = 104").stepThis().dispose(); + version = 104; + } + + if (version == 104) { + database.executeFast("ALTER TABLE topics ADD COLUMN read_outbox INTEGER default 0").stepThis().dispose(); + + database.executeFast("PRAGMA user_version = 105").stepThis().dispose(); + version = 105; + } + + if (version == 105) { + database.executeFast("ALTER TABLE topics ADD COLUMN pinned INTEGER default 0").stepThis().dispose(); + + database.executeFast("PRAGMA user_version = 106").stepThis().dispose(); + version = 106; + } + FileLog.d("MessagesStorage db migration finished"); AndroidUtilities.runOnUIThread(() -> { databaseMigrationInProgress = false; @@ -2142,10 +2237,20 @@ public class MessagesStorage extends BaseController { ArrayList dialogsToCleanup = new ArrayList<>(); database.executeFast("DELETE FROM reaction_mentions").stepThis().dispose(); + database.executeFast("DELETE FROM reaction_mentions_topics").stepThis().dispose(); database.executeFast("DELETE FROM downloading_documents").stepThis().dispose(); database.executeFast("DELETE FROM attach_menu_bots").stepThis().dispose(); database.executeFast("DELETE FROM animated_emoji").stepThis().dispose(); database.executeFast("DELETE FROM stickers_v2").stepThis().dispose(); + database.executeFast("DELETE FROM messages_holes_topics").stepThis().dispose(); + database.executeFast("DELETE FROM messages_topics").stepThis().dispose(); + database.executeFast("DELETE FROM topics").stepThis().dispose(); + database.executeFast("DELETE FROM media_holes_topics").stepThis().dispose(); + database.executeFast("DELETE FROM media_topics").stepThis().dispose(); + database.executeFast("DELETE FROM media_topics").stepThis().dispose(); + database.executeFast("DELETE FROM media_counts_topics").stepThis().dispose(); + database.executeFast("DELETE FROM chat_pinned_v2").stepThis().dispose(); + database.executeFast("DELETE FROM chat_pinned_count").stepThis().dispose(); cursor = database.queryFinalized("SELECT did FROM dialogs WHERE 1"); while (cursor.next()) { @@ -2219,6 +2324,8 @@ public class MessagesStorage extends BaseController { database.executeFast("PRAGMA journal_size_limit = 0").stepThis().dispose(); database.executeFast("VACUUM").stepThis().dispose(); database.executeFast("PRAGMA journal_size_limit = -1").stepThis().dispose(); + + getMessagesController().getTopicsController().databaseCleared(); } catch (Exception e) { FileLog.e(e); } finally { @@ -2242,6 +2349,412 @@ public class MessagesStorage extends BaseController { }); } + public void saveTopics(long dialogId, List topics, boolean replace, boolean useQueue) { + if (useQueue) { + storageQueue.postRunnable(() -> { + saveTopicsInternal(dialogId, topics, replace, true); + }); + } else { + saveTopicsInternal(dialogId, topics, replace, false); + } + } + + private void saveTopicsInternal(long dialogId, List topics, boolean replace, boolean inTransaction) { + SQLitePreparedStatement state = null; + try { + if (replace) { + database.executeFast("DELETE FROM topics WHERE did = " + dialogId).stepThis().dispose(); + } + state = database.executeFast("REPLACE INTO topics VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + if (inTransaction) { + database.beginTransaction(); + } + + for (int i = 0; i < topics.size(); i++) { + TLRPC.TL_forumTopic topic = topics.get(i); + state.requery(); + state.bindLong(1, dialogId); + state.bindInteger(2, topic.id); + NativeByteBuffer data = new NativeByteBuffer(topic.getObjectSize()); + topic.serializeToStream(data); + + state.bindByteBuffer(3, data); + state.bindInteger(4, topic.top_message); + + NativeByteBuffer messageData = new NativeByteBuffer(topic.topicStartMessage.getObjectSize()); + topic.topicStartMessage.serializeToStream(messageData); + state.bindByteBuffer(5, messageData); + state.bindInteger(6, topic.unread_count); + state.bindInteger(7, topic.read_inbox_max_id); + state.bindInteger(8, topic.unread_mentions_count); + state.bindInteger(9, topic.unread_reactions_count); + state.bindInteger(10, topic.read_outbox_max_id); + state.bindInteger(11, topic.pinned ? 1 : 0); + + state.step(); + messageData.reuse(); + data.reuse(); + } + + resetAllUnreadCounters(false); + + } catch (Exception e) { + FileLog.e(e); + + } finally { + if (state != null) { + state.dispose(); + } + database.commitTransaction(); + } + } + + public void updateTopicData(long dialogId, TLRPC.TL_forumTopic fromTopic, int flags) { + if (fromTopic == null) { + return; + } + storageQueue.postRunnable(() -> { + SQLitePreparedStatement state = null; + SQLiteCursor cursor = null; + try { + TLRPC.TL_forumTopic topicToUpdate = null; + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM topics WHERE did = %d AND topic_id = %d", dialogId, fromTopic.id)); + if (cursor.next()) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { + topicToUpdate = TLRPC.TL_forumTopic.TLdeserialize(data, data.readInt32(true), true); + data.reuse(); + } + } + cursor.dispose(); + cursor = null; + + if (topicToUpdate != null) { + if ((flags & TopicsController.TOPIC_FLAG_TITLE) != 0) { + topicToUpdate.title = fromTopic.title; + } + if ((flags & TopicsController.TOPIC_FLAG_ICON) != 0) { + topicToUpdate.icon_emoji_id = fromTopic.icon_emoji_id; + topicToUpdate.flags |= 1; + } + if ((flags & TopicsController.TOPIC_FLAG_PIN) != 0) { + topicToUpdate.pinned = fromTopic.pinned; + } + boolean pinned = topicToUpdate.pinned; + if ((flags & TopicsController.TOPIC_FLAG_CLOSE) != 0) { + topicToUpdate.closed = fromTopic.closed; + } + state = database.executeFast("UPDATE topics SET data = ?, pinned = ? WHERE did = ? AND topic_id = ?"); + database.beginTransaction(); + NativeByteBuffer data = new NativeByteBuffer(topicToUpdate.getObjectSize()); + topicToUpdate.serializeToStream(data); + state.bindByteBuffer(1, data); + state.bindInteger(2, pinned ? 1 : 0); + state.bindLong(3, dialogId); + state.bindInteger(4, topicToUpdate.id); + state.step(); + data.reuse(); + } + } catch (Exception e) { + FileLog.e(e); + } finally { + if (state != null) { + state.dispose(); + } + if (cursor != null) { + cursor.dispose(); + } + database.commitTransaction(); + } + }); + } + + public void loadTopics(long dialogId, Consumer> callback) { + storageQueue.postRunnable(() -> { + ArrayList topics = null; + SQLiteCursor cursor = null; + try { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT top_message, data, topic_message, unread_count, max_read_id, unread_mentions, unread_reactions, read_outbox FROM topics WHERE did = %d ORDER BY pinned DESC", dialogId)); + + SparseArray> topicsByTopMessageId = null; + HashSet topMessageIds = null; + while (cursor.next()) { + if (topics == null) { + topics = new ArrayList<>(); + topicsByTopMessageId = new SparseArray<>(); + topMessageIds = new HashSet<>(); + } + int topMessageId = cursor.intValue(0); + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { + TLRPC.TL_forumTopic topic = TLRPC.TL_forumTopic.TLdeserialize(data, data.readInt32(false), false); + if (topic != null) { + topic.top_message = topMessageId; + ArrayList topicsListByTopMessageId = topicsByTopMessageId.get(topMessageId); + if (topicsListByTopMessageId == null) { + topicsListByTopMessageId = new ArrayList<>(); + topicsByTopMessageId.put(topMessageId, topicsListByTopMessageId); + } + topicsListByTopMessageId.add(topic); + topMessageIds.add(topMessageId); + topics.add(topic); + + NativeByteBuffer data2 = cursor.byteBufferValue(2); + //if (data2 != null) { + topic.topicStartMessage = TLRPC.Message.TLdeserialize(data2, data2.readInt32(false), false); + if (data2 != null) { + data2.reuse(); + } + // } + topic.unread_count = cursor.intValue(3); + topic.read_inbox_max_id = cursor.intValue(4); + topic.unread_mentions_count = cursor.intValue(5); + topic.unread_reactions_count = cursor.intValue(6); + topic.read_outbox_max_id = cursor.intValue(7); + } + + data.reuse(); + } + } + ArrayList usersToLoad = new ArrayList<>(); + ArrayList chatsToLoad = new ArrayList<>(); + LongSparseArray>> replyMessageOwners = new LongSparseArray<>(); + LongSparseArray> dialogReplyMessagesIds = new LongSparseArray<>(); + + + if (topics != null && !topics.isEmpty()) { + SQLiteCursor cursor2 = database.queryFinalized("SELECT mid, data, replydata FROM messages_v2 WHERE uid = " + dialogId + " AND mid IN (" + TextUtils.join(",", topMessageIds) + ")"); + while (cursor2.next()) { + int messageId = cursor2.intValue(0); + NativeByteBuffer data = cursor2.byteBufferValue(1); + if (data != null) { + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + if (message != null) { + message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + } + data.reuse(); + + topMessageIds.remove(messageId); + ArrayList topicsList = topicsByTopMessageId.get(messageId); + if (topicsList != null) { + for (int i = 0; i < topicsList.size(); i++) { + topicsList.get(i).topMessage = message; + } + } + + addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad, null); + + try { + if (message != null && message.reply_to != null && message.reply_to.reply_to_msg_id != 0 && ( + message.action instanceof TLRPC.TL_messageActionPinMessage || + message.action instanceof TLRPC.TL_messageActionPaymentSent || + message.action instanceof TLRPC.TL_messageActionGameScore + )) { + if (!cursor2.isNull(2)) { + NativeByteBuffer data2 = cursor2.byteBufferValue(2); + if (data2 != null) { + message.replyMessage = TLRPC.Message.TLdeserialize(data2, data2.readInt32(false), false); + message.replyMessage.readAttachPath(data2, getUserConfig().clientUserId); + data2.reuse(); + if (message.replyMessage != null) { + addUsersAndChatsFromMessage(message.replyMessage, usersToLoad, chatsToLoad, null); + } + } + } + if (message.replyMessage == null) { + addReplyMessages(message, replyMessageOwners, dialogReplyMessagesIds); + } + } + } catch (Exception e) { + FileLog.e(e); + } + } + } + + ArrayList chats = new ArrayList<>(); + ArrayList users = new ArrayList<>(); + if (!chatsToLoad.isEmpty()) { + getChatsInternal(TextUtils.join(",", chatsToLoad), chats); + } + if (!usersToLoad.isEmpty()) { + getUsersInternal(TextUtils.join(",", usersToLoad), users); + } + + AndroidUtilities.runOnUIThread(() -> { + if (!users.isEmpty()) { + getMessagesController().putUsers(users, true); + } + if (!chats.isEmpty()) { + getMessagesController().putChats(chats, true); + } + }); + + cursor2.dispose(); + if (!topMessageIds.isEmpty()) { + cursor2 = database.queryFinalized("SELECT mid, data FROM messages_topics WHERE uid = " + dialogId + " AND mid IN (" + TextUtils.join(",", topMessageIds) + ")"); + try { + while (cursor2.next()) { + int messageId = cursor2.intValue(0); + NativeByteBuffer data = cursor2.byteBufferValue(1); + if (data != null) { + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + if (message != null) { + message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + } + data.reuse(); + + topMessageIds.remove(messageId); + ArrayList topicsList = topicsByTopMessageId.get(messageId); + if (topicsList != null) { + for (int i = 0; i < topicsList.size(); i++) { + topicsList.get(i).topMessage = message; + } + } + } + } + } catch (Exception e) { + FileLog.e(e); + } + } + + loadReplyMessages(replyMessageOwners, dialogReplyMessagesIds, usersToLoad, chatsToLoad, false); + loadGroupedMessagesForTopics(dialogId, topics); + } + + } catch (Exception e) { + FileLog.e(e); + } finally { + if (cursor != null) { + cursor.dispose(); + } + } + callback.accept(topics); + }); + } + + public void loadGroupedMessagesForTopicUpdates(ArrayList topics) { + if (topics == null) { + return; + } + try { + LongSparseArray> topicsByGroupedId = new LongSparseArray<>(); + + for (int i = 0; i < topics.size(); i++) { + if (topics.get(i).reloadTopic || topics.get(i).onlyCounters || topics.get(i).topMessage == null) { + continue; + } + long groupId = topics.get(i).topMessage.grouped_id; + if (groupId != 0) { + ArrayList array = topicsByGroupedId.get(groupId); + if (array == null) { + array = new ArrayList<>(); + topicsByGroupedId.put(groupId, array); + } + array.add(topics.get(i)); + } + } + for (int i = 0; i < topicsByGroupedId.size(); i++) { + long groupId = topicsByGroupedId.keyAt(i); + ArrayList topicsToUpdate = topicsByGroupedId.valueAt(i); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM messages_v2 WHERE uid = %s AND group_id = %s ORDER BY date DESC", topicsToUpdate.get(0).dialogId, groupId)); + + ArrayList messageObjects = null; + while (cursor.next()) { + NativeByteBuffer data = cursor.byteBufferValue(0); + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + if (message != null) { + message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + } + if (messageObjects == null) { + messageObjects = new ArrayList<>(); + } + messageObjects.add(new MessageObject(currentAccount, message, false, false)); + } + cursor.dispose(); + for (int k = 0; k < topicsToUpdate.size(); k++) { + topicsToUpdate.get(k).groupedMessages = messageObjects; + } + + } + } catch (Throwable e) { + FileLog.e(e); + } + } + + public void loadGroupedMessagesForTopics(long dialogId, ArrayList topics) { + if (topics == null) { + return; + } + try { + + LongSparseArray> topicsByGroupedId = new LongSparseArray<>(); + + for (int i = 0; i < topics.size(); i++) { + if (topics.get(i).topMessage == null) { + continue; + } + long groupId = topics.get(i).topMessage.grouped_id; + if (groupId != 0) { + ArrayList array = topicsByGroupedId.get(groupId); + if (array == null) { + array = new ArrayList<>(); + topicsByGroupedId.put(groupId, array); + } + array.add(topics.get(i)); + } + } + for (int i = 0; i < topicsByGroupedId.size(); i++) { + long groupId = topicsByGroupedId.keyAt(i); + ArrayList topicsToUpdate = topicsByGroupedId.valueAt(i); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM messages_v2 WHERE uid = %s AND group_id = %s ORDER BY date DESC", dialogId, groupId)); + + ArrayList messageObjects = null; + while (cursor.next()) { + NativeByteBuffer data = cursor.byteBufferValue(0); + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + if (message != null) { + message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + } + if (messageObjects == null) { + messageObjects = new ArrayList<>(); + } + messageObjects.add(new MessageObject(currentAccount, message, false, false)); + } + cursor.dispose(); + for (int k = 0; k < topicsToUpdate.size(); k++) { + topicsToUpdate.get(k).groupedMessages = messageObjects; + } + + } + } catch (Throwable e) { + FileLog.e(e); + } + + } + + public void removeTopic(long dialogId, int topicId) { + storageQueue.postRunnable(() -> { + try { + database.executeFast(String.format(Locale.US, "DELETE FROM topics WHERE did = %d AND topic_id = %d", dialogId, topicId)).stepThis().dispose(); + database.executeFast(String.format(Locale.US, "DELETE FROM messages_topics WHERE uid = %d AND topic_id = %d", dialogId, topicId)).stepThis().dispose(); + } catch (SQLiteException e) { + e.printStackTrace(); + } + }); + } + + public void updateTopicsWithReadMessages(HashMap topicsReadOutbox) { + storageQueue.postRunnable(() -> { + for (TopicKey topicKey : topicsReadOutbox.keySet()) { + int value = topicsReadOutbox.get(topicKey); + try { + database.executeFast(String.format(Locale.US, "UPDATE topics SET read_outbox = max((SELECT read_outbox FROM topics WHERE did = %d AND topic_id = %d), %d) WHERE did = %d AND topic_id = %d", topicKey.dialogId, topicKey.topicId, value, topicKey.dialogId, topicKey.topicId)).stepThis().dispose(); + } catch (SQLiteException e) { + FileLog.e(e); + } + } + }); + } private static class ReadDialog { public int lastMid; @@ -2329,9 +2842,10 @@ public class MessagesStorage extends BaseController { private TLRPC.messages_Dialogs loadDialogsByIds(String ids, ArrayList usersToLoad, ArrayList chatsToLoad, ArrayList encryptedToLoad) throws Exception { TLRPC.messages_Dialogs dialogs = new TLRPC.TL_messages_dialogs(); LongSparseArray replyMessageOwners = new LongSparseArray<>(); + LongSparseArray groupsToLoad = new LongSparseArray<>(); SQLiteCursor cursor = null; try { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, s.flags, m.date, d.pts, d.inbox_max, d.outbox_max, m.replydata, d.pinned, d.unread_count_i, d.flags, d.folder_id, d.data, d.unread_reactions FROM dialogs as d LEFT JOIN messages_v2 as m ON d.last_mid = m.mid AND d.did = m.uid LEFT JOIN dialog_settings as s ON d.did = s.did WHERE d.did IN (%s) ORDER BY d.pinned DESC, d.date DESC", ids)); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, s.flags, m.date, d.pts, d.inbox_max, d.outbox_max, m.replydata, d.pinned, d.unread_count_i, d.flags, d.folder_id, d.data, d.unread_reactions, d.last_mid_group FROM dialogs as d LEFT JOIN messages_v2 as m ON d.last_mid = m.mid AND d.did = m.uid LEFT JOIN dialog_settings as s ON d.did = s.did WHERE d.did IN (%s) ORDER BY d.pinned DESC, d.date DESC", ids)); while (cursor.next()) { long dialogId = cursor.longValue(0); TLRPC.Dialog dialog = new TLRPC.TL_dialog(); @@ -2359,6 +2873,9 @@ public class MessagesStorage extends BaseController { } dialog.folder_id = cursor.intValue(17); dialog.unread_reactions_count = cursor.intValue(19); + if (!cursor.isNull(20)) { + groupsToLoad.append(dialogId, cursor.longValue(20)); + } dialogs.dialogs.add(dialog); NativeByteBuffer data = cursor.byteBufferValue(4); @@ -2429,6 +2946,79 @@ public class MessagesStorage extends BaseController { cursor.dispose(); cursor = null; + if (!groupsToLoad.isEmpty()) { + StringBuilder whereClause = new StringBuilder(); + for (int i = 0; i < groupsToLoad.size(); ++i) { + whereClause.append("uid = ").append(groupsToLoad.keyAt(i)).append(" AND group_id = ").append(groupsToLoad.valueAt(i)); + if (i + 1 < groupsToLoad.size()) { + whereClause.append(" OR "); + } + } + cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, data, read_state, mid, send_state, date, replydata FROM messages_v2 WHERE %s ORDER BY date DESC", whereClause)); + int count = 0; + while (cursor.next()) { + count++; + long dialogId = cursor.longValue(0); + TLRPC.Dialog dialog = null; + for (int i = 0; i < dialogs.dialogs.size(); ++i) { + TLRPC.Dialog d = dialogs.dialogs.get(i); + if (d != null && d.id == dialogId) { + dialog = d; + break; + } + } + if (dialog == null) { + continue; + } + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + if (message != null) { + message.readAttachPath(data, getUserConfig().clientUserId); + data.reuse(); + MessageObject.setUnreadFlags(message, cursor.intValue(2)); + message.id = cursor.intValue(3); + int date = cursor.intValue(5); + if (date != 0) { + dialog.last_message_date = date; + } + message.send_state = cursor.intValue(4); + message.dialog_id = dialog.id; + dialogs.messages.add(message); + + addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad, null); + + try { + if (message.reply_to != null && message.reply_to.reply_to_msg_id != 0 && ( + message.action instanceof TLRPC.TL_messageActionPinMessage || + message.action instanceof TLRPC.TL_messageActionPaymentSent || + message.action instanceof TLRPC.TL_messageActionGameScore)) { + if (!cursor.isNull(6)) { + NativeByteBuffer data2 = cursor.byteBufferValue(6); + if (data2 != null) { + message.replyMessage = TLRPC.Message.TLdeserialize(data2, data2.readInt32(false), false); + message.replyMessage.readAttachPath(data2, getUserConfig().clientUserId); + data2.reuse(); + if (message.replyMessage != null) { + addUsersAndChatsFromMessage(message.replyMessage, usersToLoad, chatsToLoad, null); + } + } + } + if (message.replyMessage == null) { + replyMessageOwners.put(dialog.id, message); + } + } + } catch (Exception e) { + FileLog.e(e); + } + } else { + data.reuse(); + } + } + } + cursor.dispose(); + } + if (!replyMessageOwners.isEmpty()) { for (int a = 0, N = replyMessageOwners.size(); a < N; a++) { long dialogId = replyMessageOwners.keyAt(a); @@ -2641,13 +3231,31 @@ public class MessagesStorage extends BaseController { ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedToLoad = new ArrayList<>(); LongSparseIntArray dialogsByFolders = new LongSparseIntArray(); + + LongSparseIntArray forumUnreadCount = new LongSparseIntArray(); + cursor = database.queryFinalized("SELECT DISTINCT did FROM topics WHERE unread_count > 0 OR unread_mentions > 0"); + while (cursor.next()) { + long dialogId = cursor.longValue(0); + if (isForum(dialogId)) { + forumUnreadCount.put(dialogId, 1); + } + } cursor = database.queryFinalized("SELECT did, folder_id, unread_count, unread_count_i FROM dialogs WHERE unread_count > 0 OR flags > 0 UNION ALL " + "SELECT did, folder_id, unread_count, unread_count_i FROM dialogs WHERE unread_count_i > 0"); while (cursor.next()) { int folderId = cursor.intValue(1); long did = cursor.longValue(0); - int unread = cursor.intValue(2); - int mentions = cursor.intValue(3); + int unread; + int mentions = 0; + if (isForum(did)) { + unread = forumUnreadCount.get(did, 0); + if (unread == 0) { + continue; + } + } else { + unread = cursor.intValue(2); + mentions = cursor.intValue(3); + } if (unread > 0) { dialogsWithUnread.put(did, unread); } @@ -2685,7 +3293,7 @@ public class MessagesStorage extends BaseController { getUsersInternal(TextUtils.join(",", usersToLoad), users); for (int a = 0, N = users.size(); a < N; a++) { TLRPC.User user = users.get(a); - boolean muted = getMessagesController().isDialogMuted(user.id); + boolean muted = getMessagesController().isDialogMuted(user.id, 0); int idx1 = dialogsByFolders.get(user.id); int idx2 = muted ? 1 : 0; if (muted) { @@ -2721,7 +3329,7 @@ public class MessagesStorage extends BaseController { continue; } long did = DialogObject.makeEncryptedDialogId(encryptedChat.id); - boolean muted = getMessagesController().isDialogMuted(did); + boolean muted = getMessagesController().isDialogMuted(did, 0); int idx1 = dialogsByFolders.get(did); int idx2 = muted ? 1 : 0; if (muted) { @@ -2749,7 +3357,7 @@ public class MessagesStorage extends BaseController { dialogsWithMentions.remove(-chat.id); continue; } - boolean muted = getMessagesController().isDialogMuted(-chat.id, chat); + boolean muted = getMessagesController().isDialogMuted(-chat.id, 0, chat); int idx1 = dialogsByFolders.get(-chat.id); int idx2 = muted && dialogsWithMentions.indexOfKey(-chat.id) < 0 ? 1 : 0; if (muted) { @@ -4222,7 +4830,7 @@ public class MessagesStorage extends BaseController { }); } - public void resetDialogs(TLRPC.messages_Dialogs dialogsRes, int messagesCount, int seq, int newPts, int date, int qts, LongSparseArray new_dialogs_dict, LongSparseArray new_dialogMessage, TLRPC.Message lastMessage, int dialogsCount) { + public void resetDialogs(TLRPC.messages_Dialogs dialogsRes, int messagesCount, int seq, int newPts, int date, int qts, LongSparseArray new_dialogs_dict, LongSparseArray> new_dialogMessage, TLRPC.Message lastMessage, int dialogsCount) { storageQueue.postRunnable(() -> { SQLiteCursor cursor = null; try { @@ -4467,8 +5075,7 @@ public class MessagesStorage extends BaseController { cursor = null; deleteFromDownloadQueue(idsToDelete, true); if (!messages.isEmpty()) { - state = database.executeFast("REPLACE INTO messages_v2 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, 0, ?)"); - + state = database.executeFast("REPLACE INTO messages_v2 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, 0, ?, ?)"); for (int a = 0; a < messages.size(); a++) { TLRPC.Message message = messages.get(a); @@ -4518,6 +5125,11 @@ public class MessagesStorage extends BaseController { } else { state.bindNull(16); } + if ((message.flags & 131072) != 0) { + state.bindLong(17, message.grouped_id); + } else { + state.bindNull(17); + } state.step(); data.reuse(); if (repliesData != null) { @@ -4628,32 +5240,43 @@ public class MessagesStorage extends BaseController { SQLiteCursor cursor = null; try { database.beginTransaction(); - cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM messages_v2 WHERE mid = %d AND uid = %d", msgId, dialogId)); - if (cursor.next()) { - NativeByteBuffer data = cursor.byteBufferValue(0); - if (data != null) { - TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - if (message != null) { - message.readAttachPath(data, getUserConfig().clientUserId); - data.reuse(); - MessageObject.updateReactions(message, reactions); - SQLitePreparedStatement state = database.executeFast("UPDATE messages_v2 SET data = ? WHERE mid = ? AND uid = ?"); - NativeByteBuffer data2 = new NativeByteBuffer(message.getObjectSize()); - message.serializeToStream(data2); - state.requery(); - state.bindByteBuffer(1, data2); - state.bindInteger(2, msgId); - state.bindLong(3, dialogId); - state.step(); - data2.reuse(); - state.dispose(); - } else { - data.reuse(); + for (int i = 0; i < 2; i++) { + if (i == 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM messages_v2 WHERE mid = %d AND uid = %d", msgId, dialogId)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM messages_topics WHERE mid = %d AND uid = %d", msgId, dialogId)); + } + if (cursor.next()) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + if (message != null) { + message.readAttachPath(data, getUserConfig().clientUserId); + data.reuse(); + MessageObject.updateReactions(message, reactions); + SQLitePreparedStatement state; + if (i == 0) { + state = database.executeFast("UPDATE messages_v2 SET data = ? WHERE mid = ? AND uid = ?"); + } else { + state = database.executeFast("UPDATE messages_topics SET data = ? WHERE mid = ? AND uid = ?"); + } + NativeByteBuffer data2 = new NativeByteBuffer(message.getObjectSize()); + message.serializeToStream(data2); + state.requery(); + state.bindByteBuffer(1, data2); + state.bindInteger(2, msgId); + state.bindLong(3, dialogId); + state.step(); + data2.reuse(); + state.dispose(); + } else { + data.reuse(); + } } } + cursor.dispose(); + cursor = null; } - cursor.dispose(); - cursor = null; database.commitTransaction(); } catch (Exception e) { FileLog.e(e); @@ -4677,23 +5300,30 @@ public class MessagesStorage extends BaseController { message.voiceTranscriptionOpen = saveFromMessage.voiceTranscriptionOpen; message.voiceTranscriptionRated = saveFromMessage.voiceTranscriptionRated; message.voiceTranscriptionFinal = saveFromMessage.voiceTranscriptionFinal; + message.voiceTranscriptionForce = saveFromMessage.voiceTranscriptionForce; message.voiceTranscriptionId = saveFromMessage.voiceTranscriptionId; - state = database.executeFast("UPDATE messages_v2 SET custom_params = ? WHERE mid = ? AND uid = ?"); - state.requery(); - NativeByteBuffer nativeByteBuffer = MessageCustomParamsHelper.writeLocalParams(message); - if (nativeByteBuffer != null) { - state.bindByteBuffer(1, nativeByteBuffer); - } else { - state.bindNull(1); - } - state.bindInteger(2, msgId); - state.bindLong(3, dialogId); - state.step(); - state.dispose(); - state = null; - if (nativeByteBuffer != null) { - nativeByteBuffer.reuse(); + for (int i = 0; i < 2; i++) { + if (i == 0) { + state = database.executeFast("UPDATE messages_v2 SET custom_params = ? WHERE mid = ? AND uid = ?"); + } else { + state = database.executeFast("UPDATE messages_topics SET custom_params = ? WHERE mid = ? AND uid = ?"); + } + state.requery(); + NativeByteBuffer nativeByteBuffer = MessageCustomParamsHelper.writeLocalParams(message); + if (nativeByteBuffer != null) { + state.bindByteBuffer(1, nativeByteBuffer); + } else { + state.bindNull(1); + } + state.bindInteger(2, msgId); + state.bindLong(3, dialogId); + state.step(); + state.dispose(); + state = null; + if (nativeByteBuffer != null) { + nativeByteBuffer.reuse(); + } } database.commitTransaction(); } catch (Exception e) { @@ -4758,25 +5388,32 @@ public class MessagesStorage extends BaseController { message.voiceTranscriptionOpen = saveFromMessage.voiceTranscriptionOpen; message.voiceTranscriptionRated = saveFromMessage.voiceTranscriptionRated; message.voiceTranscriptionFinal = saveFromMessage.voiceTranscriptionFinal; + message.voiceTranscriptionForce = saveFromMessage.voiceTranscriptionForce; message.voiceTranscriptionId = saveFromMessage.voiceTranscriptionId; message.voiceTranscription = text; - state = database.executeFast("UPDATE messages_v2 SET custom_params = ? WHERE mid = ? AND uid = ?"); - state.requery(); - NativeByteBuffer nativeByteBuffer = MessageCustomParamsHelper.writeLocalParams(message); - if (nativeByteBuffer != null) { - state.bindByteBuffer(1, nativeByteBuffer); - } else { - state.bindNull(1); - } - state.bindInteger(2, messageId); - state.bindLong(3, dialogId); - state.step(); - state.dispose(); - state = null; - database.commitTransaction(); - if (nativeByteBuffer != null) { - nativeByteBuffer.reuse(); + for (int i = 0; i < 2; i++) { + if (i == 0) { + state = database.executeFast("UPDATE messages_v2 SET custom_params = ? WHERE mid = ? AND uid = ?"); + } else { + state = database.executeFast("UPDATE messages_topics SET custom_params = ? WHERE mid = ? AND uid = ?"); + } + state.requery(); + NativeByteBuffer nativeByteBuffer = MessageCustomParamsHelper.writeLocalParams(message); + if (nativeByteBuffer != null) { + state.bindByteBuffer(1, nativeByteBuffer); + } else { + state.bindNull(1); + } + state.bindInteger(2, messageId); + state.bindLong(3, dialogId); + state.step(); + state.dispose(); + state = null; + database.commitTransaction(); + if (nativeByteBuffer != null) { + nativeByteBuffer.reuse(); + } } } catch (Exception e) { FileLog.e(e); @@ -4799,23 +5436,29 @@ public class MessagesStorage extends BaseController { TLRPC.Message message = getMessageWithCustomParamsOnly(saveFromMessage.id, dialogId); MessageCustomParamsHelper.copyParams(saveFromMessage, message); - state = database.executeFast("UPDATE messages_v2 SET custom_params = ? WHERE mid = ? AND uid = ?"); - state.requery(); - NativeByteBuffer nativeByteBuffer = MessageCustomParamsHelper.writeLocalParams(message); - if (nativeByteBuffer != null) { - state.bindByteBuffer(1, nativeByteBuffer); - } else { - state.bindNull(1); + for (int i = 0; i < 2; i++) { + if (i == 0) { + state = database.executeFast("UPDATE messages_v2 SET custom_params = ? WHERE mid = ? AND uid = ?"); + } else { + state = database.executeFast("UPDATE messages_topics SET custom_params = ? WHERE mid = ? AND uid = ?"); + } + state.requery(); + NativeByteBuffer nativeByteBuffer = MessageCustomParamsHelper.writeLocalParams(message); + if (nativeByteBuffer != null) { + state.bindByteBuffer(1, nativeByteBuffer); + } else { + state.bindNull(1); + } + state.bindInteger(2, saveFromMessage.id); + state.bindLong(3, dialogId); + state.step(); + state.dispose(); + state = null; + if (nativeByteBuffer != null) { + nativeByteBuffer.reuse(); + } } - state.bindInteger(2, saveFromMessage.id); - state.bindLong(3, dialogId); - state.step(); - state.dispose(); - state = null; database.commitTransaction(); - if (nativeByteBuffer != null) { - nativeByteBuffer.reuse(); - } } catch (Exception e) { FileLog.e(e); } finally { @@ -4834,11 +5477,22 @@ public class MessagesStorage extends BaseController { SQLiteCursor cursor = null; try { cursor = database.queryFinalized("SELECT custom_params FROM messages_v2 WHERE mid = " + messageId + " AND uid = " + dialogId); + boolean read = false; if (cursor.next()) { MessageCustomParamsHelper.readLocalParams(message, cursor.byteBufferValue(0)); + read = true; } cursor.dispose(); cursor = null; + if (!read) { + cursor = database.queryFinalized("SELECT custom_params FROM messages_topics WHERE mid = " + messageId + " AND uid = " + dialogId); + if (cursor.next()) { + MessageCustomParamsHelper.readLocalParams(message, cursor.byteBufferValue(0)); + read = true; + } + cursor.dispose(); + cursor = null; + } } catch (SQLiteException e) { FileLog.e(e); } finally { @@ -4915,7 +5569,6 @@ public class MessagesStorage extends BaseController { SQLiteCursor cursor = null; try { database.executeFast(String.format(Locale.US, "UPDATE messages_v2 SET read_state = read_state | 2 WHERE mid = %d AND uid = %d", messageId, dialogId)).stepThis().dispose(); - cursor = database.queryFinalized("SELECT unread_count_i FROM dialogs WHERE did = " + did); int old_mentions_count = 0; if (cursor.next()) { @@ -4930,6 +5583,35 @@ public class MessagesStorage extends BaseController { updateFiltersReadCounter(null, sparseArray, true); } getMessagesController().processDialogsUpdateRead(null, sparseArray); + + database.executeFast(String.format(Locale.US, "UPDATE messages_topics SET read_state = read_state | 2 WHERE mid = %d AND uid = %d", messageId, dialogId)).stepThis().dispose(); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM messages_topics WHERE mid = %d AND uid = %d", messageId, dialogId)); + int topicId = 0; + while (cursor.next()) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); + topicId = MessageObject.getTopicId(message); + } + } + cursor.dispose(); + cursor = null; + + if (topicId != 0) { + int topicMentionsCount = 0; + cursor = database.queryFinalized(String.format(Locale.US, "SELECT unread_mentions FROM topics WHERE did = %d AND topic_id = %d", did, topicId)); + if (cursor.next()) { + topicMentionsCount = Math.max(0, cursor.intValue(0) - 1); + } + cursor.dispose(); + cursor = null; + + database.executeFast(String.format(Locale.US, "UPDATE topics SET unread_mentions = %d WHERE did = %d AND topic_id = %d",topicMentionsCount, dialogId, topicId)).stepThis().dispose(); + + getMessagesController().getTopicsController().updateMentionsUnread(dialogId, topicId, topicMentionsCount); + } + } catch (Exception e) { FileLog.e(e); } finally { @@ -4950,28 +5632,39 @@ public class MessagesStorage extends BaseController { }); } - public void resetMentionsCount(long did, int count) { + public void resetMentionsCount(long did, int topicId, int count) { storageQueue.postRunnable(() -> { SQLiteCursor cursor = null; try { - int prevUnreadCount = 0; - cursor = database.queryFinalized("SELECT unread_count_i FROM dialogs WHERE did = " + did); - if (cursor.next()) { - prevUnreadCount = cursor.intValue(0); - } - cursor.dispose(); - cursor = null; - if (prevUnreadCount != 0 || count != 0) { - if (count == 0) { - database.executeFast(String.format(Locale.US, "UPDATE messages_v2 SET read_state = read_state | 2 WHERE uid = %d AND mention = 1 AND read_state IN(0, 1)", did)).stepThis().dispose(); + if (topicId == 0) { + int prevUnreadCount = 0; + cursor = database.queryFinalized("SELECT unread_count_i FROM dialogs WHERE did = " + did); + if (cursor.next()) { + prevUnreadCount = cursor.intValue(0); } - database.executeFast(String.format(Locale.US, "UPDATE dialogs SET unread_count_i = %d WHERE did = %d", count, did)).stepThis().dispose(); - LongSparseIntArray sparseArray = new LongSparseIntArray(1); - sparseArray.put(did, count); - getMessagesController().processDialogsUpdateRead(null, sparseArray); - if (count == 0) { - updateFiltersReadCounter(null, sparseArray, true); + cursor.dispose(); + cursor = null; + if (prevUnreadCount != 0 || count != 0) { + if (count == 0) { + database.executeFast(String.format(Locale.US, "UPDATE messages_v2 SET read_state = read_state | 2 WHERE uid = %d AND mention = 1 AND read_state IN(0, 1)", did)).stepThis().dispose(); + } + database.executeFast(String.format(Locale.US, "UPDATE dialogs SET unread_count_i = %d WHERE did = %d", count, did)).stepThis().dispose(); + LongSparseIntArray sparseArray = new LongSparseIntArray(1); + sparseArray.put(did, count); + getMessagesController().processDialogsUpdateRead(null, sparseArray); + if (count == 0) { + updateFiltersReadCounter(null, sparseArray, true); + } } + } else { + database.executeFast(String.format(Locale.US, "UPDATE topics SET unread_mentions = %d WHERE did = %d AND topic_id = %d", count, did, topicId)).stepThis().dispose(); + TopicsController.TopicUpdate topicUpdate = new TopicsController.TopicUpdate(); + topicUpdate.dialogId = did; + topicUpdate.topicId = topicId; + topicUpdate.onlyCounters = true; + topicUpdate.unreadMentions = count; + topicUpdate.unreadCount = -1; + getMessagesController().getTopicsController().processUpdate(Collections.singletonList(topicUpdate)); } } catch (Exception e) { FileLog.e(e); @@ -5209,7 +5902,7 @@ public class MessagesStorage extends BaseController { getUsersInternal(TextUtils.join(",", usersToLoad), users); for (int a = 0, N = users.size(); a < N; a++) { TLRPC.User user = users.get(a); - boolean muted = getMessagesController().isDialogMuted(user.id); + boolean muted = getMessagesController().isDialogMuted(user.id, 0); int idx1 = dialogsByFolders.get(user.id); int idx2 = muted ? 1 : 0; if (muted) { @@ -5245,7 +5938,7 @@ public class MessagesStorage extends BaseController { continue; } long did = DialogObject.makeEncryptedDialogId(encryptedChat.id); - boolean muted = getMessagesController().isDialogMuted(did); + boolean muted = getMessagesController().isDialogMuted(did, 0); int idx1 = dialogsByFolders.get(did); int idx2 = muted ? 1 : 0; if (muted) { @@ -5271,7 +5964,7 @@ public class MessagesStorage extends BaseController { if (chat.migrated_to instanceof TLRPC.TL_inputChannel || ChatObject.isNotInChat(chat)) { continue; } - boolean muted = getMessagesController().isDialogMuted(-chat.id, chat); + boolean muted = getMessagesController().isDialogMuted(-chat.id, 0, chat); boolean hasUnread = dialogsWithUnread.indexOfKey(-chat.id) >= 0; boolean hasMention = dialogsWithMentions.indexOfKey(-chat.id) >= 0; int idx1 = dialogsByFolders.get(-chat.id); @@ -5866,6 +6559,11 @@ public class MessagesStorage extends BaseController { SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET unread_count = ? WHERE did = ?"); for (int a = 0; a < dialogsToUpdate.size(); a++) { long did = dialogsToUpdate.keyAt(a); + if (isForum(did)) { + dialogsToUpdate.removeAt(a); + a--; + continue; + } int prevUnreadCount = 0; int newCount = dialogsToUpdate.valueAt(a); SQLiteCursor cursor = database.queryFinalized("SELECT unread_count FROM dialogs WHERE did = " + did); @@ -5891,9 +6589,15 @@ public class MessagesStorage extends BaseController { if (dialogsToUpdateMentions.size() > 0) { SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET unread_count_i = ? WHERE did = ?"); for (int a = 0; a < dialogsToUpdateMentions.size(); a++) { + long did = dialogsToUpdateMentions.keyAt(a); + if (isForum(did)) { + dialogsToUpdateMentions.removeAt(a); + a--; + continue; + } state.requery(); state.bindInteger(1, dialogsToUpdateMentions.valueAt(a)); - state.bindLong(2, dialogsToUpdateMentions.keyAt(a)); + state.bindLong(2, did); state.step(); } state.dispose(); @@ -7027,7 +7731,8 @@ public class MessagesStorage extends BaseController { database.commitTransaction(); - if (prevUnreadCount != 0 && unreadCount == 0) { + //TODO topics maybe read all topics when all messages read + if (prevUnreadCount != 0 && unreadCount == 0 && !isForum(dialogId)) { LongSparseIntArray dialogsToUpdate = new LongSparseIntArray(); dialogsToUpdate.put(dialogId, unreadCount); updateFiltersReadCounter(dialogsToUpdate, null, true); @@ -7559,12 +8264,16 @@ public class MessagesStorage extends BaseController { return result[0]; } - public void getUnreadMention(long dialog_id, IntCallback callback) { + public void getUnreadMention(long dialog_id, int topicId, IntCallback callback) { storageQueue.postRunnable(() -> { SQLiteCursor cursor = null; try { int result; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT MIN(mid) FROM messages_v2 WHERE uid = %d AND mention = 1 AND read_state IN(0, 1)", dialog_id)); + if (topicId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT MIN(mid) FROM messages_topics WHERE uid = %d AND topic_id = %d AND mention = 1 AND read_state IN(0, 1)", dialog_id, topicId)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT MIN(mid) FROM messages_v2 WHERE uid = %d AND mention = 1 AND read_state IN(0, 1)", dialog_id)); + } if (cursor.next()) { result = cursor.intValue(0); } else { @@ -7603,7 +8312,7 @@ public class MessagesStorage extends BaseController { }); } - public Runnable getMessagesInternal(long dialogId, long mergeDialogId, int count, int max_id, int offset_date, int minDate, int classGuid, int load_type, boolean scheduled, int replyMessageId, int loadIndex, boolean processMessages) { + public Runnable getMessagesInternal(long dialogId, long mergeDialogId, int count, int max_id, int offset_date, int minDate, int classGuid, int load_type, boolean scheduled, int threadMessageId, int loadIndex, boolean processMessages, boolean isTopic) { TLRPC.TL_messages_messages res = new TLRPC.TL_messages_messages(); long currentUserId = getUserConfig().clientUserId; int count_unread = 0; @@ -7632,8 +8341,12 @@ public class MessagesStorage extends BaseController { LongSparseArray> dialogReplyMessagesIds = new LongSparseArray<>(); LongSparseArray> replyMessageRandomOwners = new LongSparseArray<>(); ArrayList replyMessageRandomIds = new ArrayList<>(); - String messageSelect = "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention, m.imp, m.forwards, m.replies_data, m.custom_params FROM messages_v2 as m LEFT JOIN randoms_v2 as r ON r.mid = m.mid AND r.uid = m.uid"; - + String messageSelect; + if (threadMessageId != 0) { + messageSelect = "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention, m.imp, m.forwards, m.replies_data, m.custom_params FROM messages_topics as m LEFT JOIN randoms_v2 as r ON r.mid = m.mid AND r.uid = m.uid"; + } else { + messageSelect = "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention, m.imp, m.forwards, m.replies_data, m.custom_params FROM messages_v2 as m LEFT JOIN randoms_v2 as r ON r.mid = m.mid AND r.uid = m.uid"; + } if (scheduled) { isEnd = true; cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.ttl FROM scheduled_messages_v2 as m LEFT JOIN randoms_v2 as r ON r.mid = m.mid AND r.uid = m.uid WHERE m.uid = %d ORDER BY m.date DESC", dialogId)); @@ -7698,32 +8411,58 @@ public class MessagesStorage extends BaseController { } else { if (!DialogObject.isEncryptedDialog(dialogId)) { if (load_type == 3 && minDate == 0) { - cursor = database.queryFinalized("SELECT inbox_max, unread_count, date, unread_count_i FROM dialogs WHERE did = " + dialogId); - if (cursor.next()) { - min_unread_id = Math.max(1, cursor.intValue(0)) + 1; - count_unread = cursor.intValue(1); - max_unread_date = cursor.intValue(2); - mentions_unread = cursor.intValue(3); - } - cursor.dispose(); - cursor = null; - } else if (load_type != 1 && load_type != 3 && load_type != 4 && minDate == 0) { - if (load_type == 2) { + if (threadMessageId == 0) { cursor = database.queryFinalized("SELECT inbox_max, unread_count, date, unread_count_i FROM dialogs WHERE did = " + dialogId); if (cursor.next()) { - messageMaxId = max_id_query = min_unread_id = Math.max(1, cursor.intValue(0)); + min_unread_id = Math.max(1, cursor.intValue(0)) + 1; count_unread = cursor.intValue(1); max_unread_date = cursor.intValue(2); mentions_unread = cursor.intValue(3); - queryFromServer = true; - if (dialogId == currentUserId) { - count_unread = 0; - } } cursor.dispose(); cursor = null; + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT unread_count, unread_mentions FROM topics WHERE did = %d AND topic_id = %d", dialogId, threadMessageId)); + if (cursor.next()) { + count_unread = cursor.intValue(0); + mentions_unread = cursor.intValue(1); + } + cursor.dispose(); + cursor = null; + } + } else if (load_type != 1 && load_type != 3 && load_type != 4 && minDate == 0) { + if (load_type == 2) { + if (threadMessageId == 0) { + cursor = database.queryFinalized("SELECT inbox_max, unread_count, date, unread_count_i FROM dialogs WHERE did = " + dialogId); + if (cursor.next()) { + messageMaxId = max_id_query = min_unread_id = Math.max(1, cursor.intValue(0)); + count_unread = cursor.intValue(1); + max_unread_date = cursor.intValue(2); + mentions_unread = cursor.intValue(3); + queryFromServer = true; + if (dialogId == currentUserId) { + count_unread = 0; + } + } + cursor.dispose(); + cursor = null; + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT max_read_id, unread_count, unread_mentions FROM topics WHERE did = %d AND topic_id = %d", dialogId, threadMessageId)); + if (cursor.next()) { + messageMaxId = max_id_query = min_unread_id = Math.max(1, cursor.intValue(0)); + count_unread = cursor.intValue(1); + mentions_unread = cursor.intValue(2); + } + cursor.dispose(); + cursor = null; + queryFromServer = true; + } if (!queryFromServer) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid), max(date) FROM messages_v2 WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid > 0", dialogId)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid), max(date) FROM messages_topics WHERE uid = %d AND topic_id = %d AND out = 0 AND read_state IN(0,2) AND mid > 0", dialogId, threadMessageId)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid), max(date) FROM messages_v2 WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid > 0", dialogId)); + } if (cursor.next()) { min_unread_id = cursor.intValue(0); max_unread_date = cursor.intValue(1); @@ -7731,7 +8470,11 @@ public class MessagesStorage extends BaseController { cursor.dispose(); cursor = null; if (min_unread_id != 0) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages_v2 WHERE uid = %d AND mid >= %d AND out = 0 AND read_state IN(0,2)", dialogId, min_unread_id)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages_topics WHERE uid = %d AND topic_id = %d AND mid >= %d AND out = 0 AND read_state IN(0,2)", dialogId, threadMessageId, min_unread_id)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages_v2 WHERE uid = %d AND mid >= %d AND out = 0 AND read_state IN(0,2)", dialogId, min_unread_id)); + } if (cursor.next()) { count_unread = cursor.intValue(0); } @@ -7740,14 +8483,22 @@ public class MessagesStorage extends BaseController { } } else if (max_id_query == 0) { int existingUnreadCount = 0; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages_v2 WHERE uid = %d AND mid > 0 AND out = 0 AND read_state IN(0,2)", dialogId)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages_topics WHERE uid = %d AND topic_id = %d AND mid > 0 AND out = 0 AND read_state IN(0,2)", dialogId, threadMessageId)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages_v2 WHERE uid = %d AND mid > 0 AND out = 0 AND read_state IN(0,2)", dialogId)); + } if (cursor.next()) { existingUnreadCount = cursor.intValue(0); } cursor.dispose(); cursor = null; if (existingUnreadCount == count_unread) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages_v2 WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid > 0", dialogId)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages_topics WHERE uid = %d AND topic_id = %d AND out = 0 AND read_state IN(0,2) AND mid > 0", dialogId, threadMessageId)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages_v2 WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid > 0", dialogId)); + } if (cursor.next()) { messageMaxId = max_id_query = min_unread_id = cursor.intValue(0); } @@ -7755,13 +8506,21 @@ public class MessagesStorage extends BaseController { cursor = null; } } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM messages_holes WHERE uid = %d AND start < %d AND end > %d", dialogId, max_id_query, max_id_query)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM messages_holes_topics WHERE uid = %d AND topic_id = %d AND start < %d AND end > %d", dialogId, threadMessageId, max_id_query, max_id_query)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM messages_holes WHERE uid = %d AND start < %d AND end > %d", dialogId, max_id_query, max_id_query)); + } boolean containMessage = !cursor.next(); cursor.dispose(); cursor = null; if (containMessage) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages_v2 WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid > %d", dialogId, max_id_query)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages_topics WHERE uid = %d AND topic_id = %d AND out = 0 AND read_state IN(0,2) AND mid > %d", dialogId, threadMessageId, max_id_query)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages_v2 WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid > %d", dialogId, max_id_query)); + } if (cursor.next()) { messageMaxId = max_id_query = cursor.intValue(0); } @@ -7787,21 +8546,39 @@ public class MessagesStorage extends BaseController { } } - cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start IN (0, 1)", dialogId)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes_topics WHERE uid = %d AND topic_id = %d AND start IN (0, 1)", dialogId, threadMessageId)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start IN (0, 1)", dialogId)); + } + if (cursor.next()) { isEnd = cursor.intValue(0) == 1; } else { cursor.dispose(); cursor = null; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages_v2 WHERE uid = %d AND mid > 0", dialogId)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages_topics WHERE uid = %d AND topic_id = %d AND mid > 0", dialogId, threadMessageId)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages_v2 WHERE uid = %d AND mid > 0", dialogId)); + } if (cursor.next()) { int mid = cursor.intValue(0); if (mid != 0) { - SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages_holes VALUES(?, ?, ?)"); + SQLitePreparedStatement state; + if (threadMessageId != 0) { + state = database.executeFast("REPLACE INTO messages_holes_topics VALUES(?, ?, ?, ?)"); + } else { + state = database.executeFast("REPLACE INTO messages_holes VALUES(?, ?, ?)"); + } + int pointer = 1; state.requery(); - state.bindLong(1, dialogId); - state.bindInteger(2, 0); - state.bindInteger(3, mid); + state.bindLong(pointer++, dialogId); + if (threadMessageId != 0) { + state.bindInteger(pointer++, threadMessageId); + } + state.bindInteger(pointer++, 0); + state.bindInteger(pointer++, mid); state.step(); state.dispose(); } @@ -7811,7 +8588,11 @@ public class MessagesStorage extends BaseController { cursor = null; if (load_type == 3 || load_type == 4 || queryFromServer && load_type == 2) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid) FROM messages_v2 WHERE uid = %d AND mid > 0", dialogId)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid) FROM messages_topics WHERE uid = %d AND topic_id = %d AND mid > 0", dialogId, threadMessageId)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid) FROM messages_v2 WHERE uid = %d AND mid > 0", dialogId)); + } if (cursor.next()) { last_message_id = cursor.intValue(0); } @@ -7822,7 +8603,11 @@ public class MessagesStorage extends BaseController { int startMid; int endMid; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid) FROM messages_v2 WHERE uid = %d AND date <= %d AND mid > 0", dialogId, offset_date)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid) FROM messages_topics WHERE uid = %d AND topic_id = %d AND date <= %d AND mid > 0", dialogId, threadMessageId, offset_date)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid) FROM messages_v2 WHERE uid = %d AND date <= %d AND mid > 0", dialogId, offset_date)); + } if (cursor.next()) { startMid = cursor.intValue(0); } else { @@ -7830,7 +8615,11 @@ public class MessagesStorage extends BaseController { } cursor.dispose(); cursor = null; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages_v2 WHERE uid = %d AND date >= %d AND mid > 0", dialogId, offset_date)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages_topics WHERE uid = %d AND topic_id = %d AND date >= %d AND mid > 0", dialogId, threadMessageId, offset_date)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages_v2 WHERE uid = %d AND date >= %d AND mid > 0", dialogId, offset_date)); + } if (cursor.next()) { endMid = cursor.intValue(0); } else { @@ -7842,14 +8631,22 @@ public class MessagesStorage extends BaseController { if (startMid == endMid) { max_id_query = startMid; } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start <= %d AND end > %d", dialogId, startMid, startMid)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes_topics WHERE uid = %d AND topic_id = %d AND start <= %d AND end > %d", dialogId, threadMessageId, startMid, startMid)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start <= %d AND end > %d", dialogId, startMid, startMid)); + } if (cursor.next()) { startMid = -1; } cursor.dispose(); cursor = null; if (startMid != -1) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start <= %d AND end > %d", dialogId, endMid, endMid)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes_topics WHERE uid = %d AND topic_id = %d AND start <= %d AND end > %d", dialogId, threadMessageId, endMid, endMid)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start <= %d AND end > %d", dialogId, endMid, endMid)); + } if (cursor.next()) { endMid = -1; } @@ -7866,7 +8663,11 @@ public class MessagesStorage extends BaseController { boolean containMessage = max_id_query != 0; if (containMessage) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start < %d AND end > %d", dialogId, max_id_query, max_id_query)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes_topics WHERE uid = %d AND topic_id AND start < %d AND end > %d", dialogId, threadMessageId, max_id_query, max_id_query)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start < %d AND end > %d", dialogId, max_id_query, max_id_query)); + } if (cursor.next()) { containMessage = false; } @@ -7877,13 +8678,21 @@ public class MessagesStorage extends BaseController { if (containMessage) { int holeMessageMaxId = 0; int holeMessageMinId = 1; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start >= %d ORDER BY start ASC LIMIT 1", dialogId, max_id_query)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes_topics WHERE uid = %d AND topic_id = %d AND start >= %d ORDER BY start ASC LIMIT 1", dialogId, threadMessageId, max_id_query)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start >= %d ORDER BY start ASC LIMIT 1", dialogId, max_id_query)); + } if (cursor.next()) { holeMessageMaxId = cursor.intValue(0); } cursor.dispose(); cursor = null; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT end FROM messages_holes WHERE uid = %d AND end <= %d ORDER BY end DESC LIMIT 1", dialogId, max_id_query)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT end FROM messages_holes_topics WHERE uid = %d AND topic_id = %d AND end <= %d ORDER BY end DESC LIMIT 1", dialogId, threadMessageId, max_id_query)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT end FROM messages_holes WHERE uid = %d AND end <= %d ORDER BY end DESC LIMIT 1", dialogId, max_id_query)); + } if (cursor.next()) { holeMessageMinId = cursor.intValue(0); } @@ -7893,16 +8702,30 @@ public class MessagesStorage extends BaseController { if (holeMessageMaxId == 0) { holeMessageMaxId = 1000000000; } - cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid <= %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + - "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid > %d AND (m.mid <= %d OR m.mid < 0) ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialogId, messageMaxId, holeMessageMinId, count_query / 2, dialogId, messageMaxId, holeMessageMaxId, count_query / 2)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.topic_id = %d AND m.mid <= %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + + "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.topic_id = %d AND m.mid > %d AND (m.mid <= %d OR m.mid < 0) ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialogId, threadMessageId, messageMaxId, holeMessageMinId, count_query / 2, dialogId, threadMessageId, messageMaxId, holeMessageMaxId, count_query / 2)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid <= %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + + "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid > %d AND (m.mid <= %d OR m.mid < 0) ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialogId, messageMaxId, holeMessageMinId, count_query / 2, dialogId, messageMaxId, holeMessageMaxId, count_query / 2)); + } } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + - "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialogId, messageMaxId, count_query / 2, dialogId, messageMaxId, count_query / 2)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.topic_id = %d AND m.mid <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + + "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.topic_id = %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialogId, threadMessageId, messageMaxId, count_query / 2, dialogId, threadMessageId, messageMaxId, count_query / 2)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + + "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialogId, messageMaxId, count_query / 2, dialogId, messageMaxId, count_query / 2)); + } } } else { if (load_type == 2) { int existingUnreadCount = 0; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages_v2 WHERE uid = %d AND mid != 0 AND out = 0 AND read_state IN(0,2)", dialogId)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages_topics WHERE uid = %d AND topic_id = %d AND mid != 0 AND out = 0 AND read_state IN(0,2)", dialogId, threadMessageId)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages_v2 WHERE uid = %d AND mid != 0 AND out = 0 AND read_state IN(0,2)", dialogId)); + } if (cursor.next()) { existingUnreadCount = cursor.intValue(0); } @@ -7910,43 +8733,81 @@ public class MessagesStorage extends BaseController { cursor = null; if (existingUnreadCount == count_unread) { unreadCountIsLocal = true; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + - "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialogId, messageMaxId, count_query / 2, dialogId, messageMaxId, count_query / 2)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.topic_id = %d AND m.mid <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + + "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.topic_id = %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialogId, threadMessageId, messageMaxId, count_query / 2, dialogId, threadMessageId, messageMaxId, count_query / 2)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + + "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialogId, messageMaxId, count_query / 2, dialogId, messageMaxId, count_query / 2)); + } } } } } else if (load_type == 1) { int holeMessageId = 0; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM messages_holes WHERE uid = %d AND (start >= %d AND start != 1 AND end != 1 OR start < %d AND end > %d) ORDER BY start ASC LIMIT 1", dialogId, max_id, max_id, max_id)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM messages_holes_topics WHERE uid = %d AND topic_id = %d AND (start >= %d AND start != 1 AND end != 1 OR start < %d AND end > %d) ORDER BY start ASC LIMIT 1", dialogId, threadMessageId, max_id, max_id, max_id)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM messages_holes WHERE uid = %d AND (start >= %d AND start != 1 AND end != 1 OR start < %d AND end > %d) ORDER BY start ASC LIMIT 1", dialogId, max_id, max_id, max_id)); + } if (cursor.next()) { holeMessageId = cursor.intValue(0); } cursor.dispose(); cursor = null; - if (holeMessageId != 0) { - cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date >= %d AND m.mid > %d AND m.mid <= %d ORDER BY m.date ASC, m.mid ASC LIMIT %d", dialogId, minDate, messageMaxId, holeMessageId, count_query)); + if (threadMessageId != 0) { + if (holeMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.topic_id = %d AND m.date >= %d AND m.mid > %d AND m.mid <= %d ORDER BY m.date ASC, m.mid ASC LIMIT %d", dialogId, threadMessageId, minDate, messageMaxId, holeMessageId, count_query)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.topic_id = %d AND m.date >= %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d", dialogId, threadMessageId, minDate, messageMaxId, count_query)); + } } else { - cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date >= %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d", dialogId, minDate, messageMaxId, count_query)); + if (holeMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date >= %d AND m.mid > %d AND m.mid <= %d ORDER BY m.date ASC, m.mid ASC LIMIT %d", dialogId, minDate, messageMaxId, holeMessageId, count_query)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date >= %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d", dialogId, minDate, messageMaxId, count_query)); + } } } else if (minDate != 0) { if (messageMaxId != 0) { int holeMessageId = 0; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT end FROM messages_holes WHERE uid = %d AND end <= %d ORDER BY end DESC LIMIT 1", dialogId, max_id)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT end FROM messages_holes_topics WHERE uid = %d AND topic_id = %d AND end <= %d ORDER BY end DESC LIMIT 1", dialogId, threadMessageId, max_id)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT end FROM messages_holes WHERE uid = %d AND end <= %d ORDER BY end DESC LIMIT 1", dialogId, max_id)); + } + if (cursor.next()) { holeMessageId = cursor.intValue(0); } cursor.dispose(); cursor = null; - if (holeMessageId != 0) { - cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date <= %d AND m.mid < %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d", dialogId, minDate, messageMaxId, holeMessageId, count_query)); + if (threadMessageId != 0) { + if (holeMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.topic_id = %d AND m.date <= %d AND m.mid < %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d", dialogId, threadMessageId, minDate, messageMaxId, holeMessageId, count_query)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.topic_id = %d AND m.date <= %d AND m.mid < %d ORDER BY m.date DESC, m.mid DESC LIMIT %d", dialogId, threadMessageId, minDate, messageMaxId, count_query)); + } } else { - cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date <= %d AND m.mid < %d ORDER BY m.date DESC, m.mid DESC LIMIT %d", dialogId, minDate, messageMaxId, count_query)); + if (holeMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date <= %d AND m.mid < %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d", dialogId, minDate, messageMaxId, holeMessageId, count_query)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date <= %d AND m.mid < %d ORDER BY m.date DESC, m.mid DESC LIMIT %d", dialogId, minDate, messageMaxId, count_query)); + } } } else { - cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialogId, minDate, offset_query, count_query)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.topic_id = %d AND m.date <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialogId, threadMessageId, minDate, offset_query, count_query)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialogId, minDate, offset_query, count_query)); + } } } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid) FROM messages_v2 WHERE uid = %d AND mid > 0", dialogId)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid) FROM messages_topics WHERE uid = %d AND topic_id = %d AND mid > 0", dialogId, threadMessageId)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid) FROM messages_v2 WHERE uid = %d AND mid > 0", dialogId)); + } if (cursor.next()) { last_message_id = cursor.intValue(0); } @@ -7954,16 +8815,28 @@ public class MessagesStorage extends BaseController { cursor = null; int holeMessageId = 0; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(end) FROM messages_holes WHERE uid = %d", dialogId)); + if (threadMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(end) FROM messages_holes_topics WHERE uid = %d AND topic_id = %d", dialogId, threadMessageId)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(end) FROM messages_holes WHERE uid = %d", dialogId)); + } if (cursor.next()) { holeMessageId = cursor.intValue(0); } cursor.dispose(); cursor = null; - if (holeMessageId != 0) { - cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialogId, holeMessageId, offset_query, count_query)); + if (threadMessageId != 0) { + if (holeMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.topic_id = %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialogId, threadMessageId, holeMessageId, offset_query, count_query)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.topic_id = %d ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialogId, threadMessageId, offset_query, count_query)); + } } else { - cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialogId, offset_query, count_query)); + if (holeMessageId != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialogId, holeMessageId, offset_query, count_query)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialogId, offset_query, count_query)); + } } } } else { @@ -8057,6 +8930,7 @@ public class MessagesStorage extends BaseController { int minId = Integer.MAX_VALUE; int maxId = Integer.MIN_VALUE; ArrayList messageIdsToFix = null; + if (cursor != null) { while (cursor.next()) { messagesCount++; @@ -8207,16 +9081,29 @@ public class MessagesStorage extends BaseController { } } if (mentions_unread != 0) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM messages_v2 WHERE uid = %d AND mention = 1 AND read_state IN(0, 1)", dialogId)); - if (cursor.next()) { - if (mentions_unread != cursor.intValue(0)) { + if (threadMessageId == 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM messages_v2 WHERE uid = %d AND mention = 1 AND read_state IN(0, 1)", dialogId)); + if (cursor.next()) { + if (mentions_unread != cursor.intValue(0)) { + mentions_unread *= -1; + } + } else { mentions_unread *= -1; } + cursor.dispose(); + cursor = null; } else { - mentions_unread *= -1; + cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM messages_topics WHERE uid = %d AND topic_id = %d AND mention = 1 AND read_state IN(0, 1)", dialogId, threadMessageId)); + if (cursor.next()) { + if (mentions_unread != cursor.intValue(0)) { + mentions_unread *= -1; + } + } else { + mentions_unread *= -1; + } + cursor.dispose(); + cursor = null; } - cursor.dispose(); - cursor = null; } } if (!replyMessageRandomOwners.isEmpty()) { @@ -8306,7 +9193,7 @@ public class MessagesStorage extends BaseController { }; } else {*/ int finalMessagesCount = scheduled ? res.messages.size() : messagesCount; - return () -> getMessagesController().processLoadedMessages(res, finalMessagesCount, dialogId, mergeDialogId, countQueryFinal, maxIdOverrideFinal, offset_date, true, classGuid, minUnreadIdFinal, lastMessageIdFinal, countUnreadFinal, maxUnreadDateFinal, load_type, isEndFinal, scheduled ? 1 : 0, replyMessageId, loadIndex, queryFromServerFinal, mentionsUnreadFinal, processMessages); + return () -> getMessagesController().processLoadedMessages(res, finalMessagesCount, dialogId, mergeDialogId, countQueryFinal, maxIdOverrideFinal, offset_date, true, classGuid, minUnreadIdFinal, lastMessageIdFinal, countUnreadFinal, maxUnreadDateFinal, load_type, isEndFinal, scheduled ? 1 : 0, threadMessageId, loadIndex, queryFromServerFinal, mentionsUnreadFinal, processMessages, isTopic); //} } @@ -8337,7 +9224,8 @@ public class MessagesStorage extends BaseController { } } - public void getMessages(long dialogId, long mergeDialogId, boolean loadInfo, int count, int max_id, int offset_date, int minDate, int classGuid, int load_type, boolean scheduled, int replyMessageId, int loadIndex, boolean processMessages) { + public void getMessages(long dialogId, long mergeDialogId, boolean loadInfo, int count, int max_id, int offset_date, int minDate, int classGuid, int load_type, boolean scheduled, int replyMessageId, int loadIndex, boolean processMessages, boolean isTopic) { + long startTime = System.currentTimeMillis(); storageQueue.postRunnable(() -> { /*if (loadInfo) { if (lowerId < 0) { @@ -8347,7 +9235,10 @@ public class MessagesStorage extends BaseController { } } }*/ - Utilities.stageQueue.postRunnable(getMessagesInternal(dialogId, mergeDialogId, count, max_id, offset_date, minDate, classGuid, load_type, scheduled, replyMessageId, loadIndex, processMessages)); + Runnable processMessagesRunnable = getMessagesInternal(dialogId, mergeDialogId, count, max_id, offset_date, minDate, classGuid, load_type, scheduled, replyMessageId, loadIndex, processMessages, isTopic); + Utilities.stageQueue.postRunnable(() -> { + processMessagesRunnable.run(); + }); }); } @@ -8429,7 +9320,7 @@ public class MessagesStorage extends BaseController { } } - public void putWidgetDialogs(int widgetId, ArrayList dids) { + public void putWidgetDialogs(int widgetId, ArrayList dids) { storageQueue.postRunnable(() -> { try { database.beginTransaction(); @@ -8443,7 +9334,7 @@ public class MessagesStorage extends BaseController { state.step(); } else { for (int a = 0, N = dids.size(); a < N; a++) { - long did = dids.get(a); + long did = dids.get(a).dialogId; state.requery(); state.bindInteger(1, widgetId); state.bindLong(2, did); @@ -8984,7 +9875,7 @@ public class MessagesStorage extends BaseController { data4.reuse(); data5.reuse(); if (dialog != null) { - state = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + state = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)"); state.bindLong(1, dialog.id); state.bindInteger(2, dialog.last_message_date); state.bindInteger(3, dialog.unread_count); @@ -9028,6 +9919,13 @@ public class MessagesStorage extends BaseController { str.append(";;;"); if (user.username != null && user.username.length() > 0) { str.append(user.username); + } else if (user.usernames != null && user.usernames.size() > 0) { + for (int i = 0; i < user.usernames.size(); ++i) { + TLRPC.TL_username u = user.usernames.get(i); + if (u != null && u.active) { + str.append(u.username).append(";;"); + } + } } return str.toString().toLowerCase(); } @@ -9039,7 +9937,7 @@ public class MessagesStorage extends BaseController { SQLitePreparedStatement state = database.executeFast("REPLACE INTO users VALUES(?, ?, ?, ?)"); for (int a = 0; a < users.size(); a++) { TLRPC.User user = users.get(a); - if (user.min) { + if (user != null && user.min) { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM users WHERE uid = %d", user.id)); if (cursor.next()) { try { @@ -9210,10 +10108,19 @@ public class MessagesStorage extends BaseController { state.bindByteBuffer(3, data); state.step(); data.reuse(); + dialogIsForum.put(-chat.id, chat.forum ? 1 : 0); } state.dispose(); } + private int malformedCleanupCount = 0; + public void checkMalformed(Exception e) { + if (e != null && e.getMessage() != null && e.getMessage().contains("malformed") && malformedCleanupCount < 3) { + malformedCleanupCount++; + cleanup(false); + } + } + public void getUsersInternal(String usersToLoad, ArrayList result) throws Exception { if (usersToLoad == null || usersToLoad.length() == 0 || result == null) { return; @@ -9234,6 +10141,7 @@ public class MessagesStorage extends BaseController { } } catch (Exception e) { FileLog.e(e); + checkMalformed(e); } } cursor.dispose(); @@ -9605,6 +10513,13 @@ public class MessagesStorage extends BaseController { database.executeFast("DELETE FROM media_v4 WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM messages_holes WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM media_holes_v2 WHERE uid = " + did).stepThis().dispose(); + + database.executeFast("DELETE FROM media_topics WHERE uid = " + did).stepThis().dispose(); + database.executeFast("DELETE FROM media_holes_topics WHERE uid = " + did).stepThis().dispose(); + database.executeFast("UPDATE media_counts_topics SET old = 1 WHERE uid = " + did).stepThis().dispose(); + database.executeFast("DELETE FROM messages_topics WHERE uid = " + did).stepThis().dispose(); + database.executeFast("DELETE FROM messages_holes_topics WHERE uid = " + did).stepThis().dispose(); + getMediaDataController().clearBotKeyboard(did, null); TLRPC.TL_messages_dialogs dialogs = new TLRPC.TL_messages_dialogs(); @@ -9629,6 +10544,7 @@ public class MessagesStorage extends BaseController { getMessagesController().generateJoinMessage(channelId, false); } } + getMessagesController().getTopicsController().reloadTopics(channelId); } catch (Exception e) { FileLog.e(e); } finally { @@ -9772,7 +10688,7 @@ public class MessagesStorage extends BaseController { }); } - private void updateRepliesMaxReadIdInternal(long chatId, int mid, int readMaxId) { + private void updateRepliesMaxReadIdInternal(long chatId, int mid, int readMaxId, int unreadCount) { SQLitePreparedStatement state = null; SQLiteCursor cursor = null; try { @@ -9802,6 +10718,41 @@ public class MessagesStorage extends BaseController { } state.dispose(); state = null; + + + cursor = database.queryFinalized(String.format(Locale.US, "SELECT max_read_id FROM topics WHERE did = %d AND topic_id = %d", -chatId, mid)); + boolean updateTopic = false; + if (cursor.next()) { + int currentMaxId = cursor.intValue(0); + if (readMaxId >= currentMaxId) { + updateTopic = true; + } + } + cursor.dispose(); + cursor = null; + + database.executeFast(String.format(Locale.US, "UPDATE messages_topics SET read_state = read_state | 1 WHERE uid = %d AND topic_id = %d AND mid <= %d AND read_state IN(0,2) AND out = 0", -chatId, mid, readMaxId)).stepThis().dispose(); + + if (unreadCount < 0) { + unreadCount = 0; + cursor = database.queryFinalized(String.format(Locale.US, "SELECT count(mid) FROM messages_topics WHERE uid = %d AND topic_id = %d AND mid > %d AND read_state IN(0,2) AND out = 0", -chatId, mid, readMaxId)); + if (cursor.next()) { + unreadCount = cursor.intValue(0); + } + cursor.dispose(); + cursor = null; + } + + if (updateTopic) { + database.executeFast(String.format(Locale.ENGLISH, "UPDATE topics SET max_read_id = %d, unread_count = %d WHERE did = %d AND topic_id = %d", readMaxId, unreadCount, -chatId, mid)).stepThis().dispose(); + + int finalUnreadCount = unreadCount; + AndroidUtilities.runOnUIThread(() -> { + getMessagesController().getTopicsController().updateMaxReadId(chatId, mid, readMaxId, finalUnreadCount); + }); + + resetForumBadgeIfNeed(-chatId); + } } catch (Exception e) { FileLog.e(e); } finally { @@ -9814,11 +10765,39 @@ public class MessagesStorage extends BaseController { } } - public void updateRepliesMaxReadId(long chatId, int mid, int readMaxId, boolean useQueue) { + //if all topics read mark all dialog as read + private void resetForumBadgeIfNeed(long dialogId) { + SQLiteCursor cursor = null; + try { + cursor = database.queryFinalized(String.format(Locale.ENGLISH, "SELECT topic_id FROM topics WHERE did = %d AND unread_count > 0", dialogId)); + + LongSparseIntArray dialogsToUpdate = null; + if (!cursor.next()) { + dialogsToUpdate = new LongSparseIntArray(); + dialogsToUpdate.put(dialogId, 0); + } + cursor.dispose(); + cursor = null; + + if (dialogsToUpdate != null) { + database.executeFast(String.format(Locale.ENGLISH, "UPDATE dialogs SET unread_count = 0, unread_count_i = 0 WHERE did = %d", dialogId)).stepThis().dispose(); + } + updateFiltersReadCounter(dialogsToUpdate, null, true); + getMessagesController().processDialogsUpdateRead(dialogsToUpdate, null); + } catch (Throwable e) { + FileLog.e(e); + } finally { + if (cursor != null) { + cursor.dispose(); + } + } + } + + public void updateRepliesMaxReadId(long chatId, int mid, int readMaxId, int unreadCount, boolean useQueue) { if (useQueue) { - storageQueue.postRunnable(() -> updateRepliesMaxReadIdInternal(chatId, mid, readMaxId)); + storageQueue.postRunnable(() -> updateRepliesMaxReadIdInternal(chatId, mid, readMaxId, unreadCount)); } else { - updateRepliesMaxReadIdInternal(chatId, mid, readMaxId); + updateRepliesMaxReadIdInternal(chatId, mid, readMaxId, unreadCount); } } @@ -9921,9 +10900,10 @@ public class MessagesStorage extends BaseController { } - private void putMessagesInternal(ArrayList messages, boolean withTransaction, boolean doNotUpdateDialogDate, int downloadMask, boolean ifNoLastMessage, boolean scheduled) { + private void putMessagesInternal(ArrayList messages, boolean withTransaction, boolean doNotUpdateDialogDate, int downloadMask, boolean ifNoLastMessage, boolean scheduled, int threadMessageId) { boolean databaseInTransaction = false; SQLitePreparedStatement state_messages = null; + SQLitePreparedStatement state_messages_topic = null; SQLitePreparedStatement state_randoms = null; SQLitePreparedStatement state_download = null; SQLitePreparedStatement state_webpage = null; @@ -9932,6 +10912,8 @@ public class MessagesStorage extends BaseController { SQLitePreparedStatement state_tasks = null; SQLitePreparedStatement state_dialogs_replace = null; SQLitePreparedStatement state_dialogs_update = null; + SQLitePreparedStatement state_topics_update = null; + SQLitePreparedStatement state_media_topics = null; SQLiteCursor cursor = null; try { if (scheduled) { @@ -10033,7 +11015,18 @@ public class MessagesStorage extends BaseController { LongSparseArray> dialogMessagesIdsMap = new LongSparseArray<>(); LongSparseArray> dialogMentionsIdsMap = new LongSparseArray<>(); - state_messages = database.executeFast("REPLACE INTO messages_v2 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, 0, ?)"); + HashMap topicsReadMax = new HashMap<>(); + HashMap topicsNewUnreadMessages = new HashMap<>(); + HashMap topicMessagesMap = new HashMap<>(); + HashMap> topicMentionsIdsMap = new HashMap<>(); + HashMap topicsMentions = new HashMap<>(); + SparseArray> mediaCountsTopics = new SparseArray<>(); + HashMap mediaIdsMapTopics = new HashMap<>(); + HashMap> messagesMediaIdsMapTopics = new HashMap<>(); + ArrayList createNewTopics = null; + + state_messages = database.executeFast("REPLACE INTO messages_v2 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, 0, ?, ?)"); + state_messages_topic = database.executeFast("REPLACE INTO messages_topics VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, 0, ?)"); state_media = null; state_randoms = database.executeFast("REPLACE INTO randoms_v2 VALUES(?, ?, ?)"); state_download = database.executeFast("REPLACE INTO download_queue VALUES(?, ?, ?, ?, ?)"); @@ -10047,6 +11040,8 @@ public class MessagesStorage extends BaseController { int messageId = message.id; MessageObject.getDialogId(message); + int topicId = MessageObject.getTopicId(message); + if (message.mentioned && message.media_unread) { ArrayList ids = dialogMentionsIdsMap.get(message.dialog_id); if (ids == null) { @@ -10054,9 +11049,18 @@ public class MessagesStorage extends BaseController { dialogMentionsIdsMap.put(message.dialog_id, ids); } ids.add(messageId); + if (topicId != 0) { + TopicKey topicKey = TopicKey.of(message.dialog_id, topicId); + ArrayList ids2 = topicMentionsIdsMap.get(topicKey); + if (ids2 == null) { + ids2 = new ArrayList<>(); + topicMentionsIdsMap.put(topicKey, ids2); + } + ids2.add(messageId); + } } - if (!(message.action instanceof TLRPC.TL_messageActionHistoryClear) && (!MessageObject.isOut(message) || message.from_scheduled) && (message.id > 0 || MessageObject.isUnread(message))) { + if (!(message.action instanceof TLRPC.TL_messageActionHistoryClear) && (!MessageObject.isOut(message) || message.from_scheduled || topicId != 0) && (message.id > 0 || MessageObject.isUnread(message)) && !(isForum(message.dialog_id) && topicId == 0)) { int currentMaxId = dialogsReadMax.get(message.dialog_id, -1); if (currentMaxId == -1) { cursor = database.queryFinalized("SELECT last_mid, inbox_max FROM dialogs WHERE did = " + message.dialog_id); @@ -10095,6 +11099,31 @@ public class MessagesStorage extends BaseController { ids.add(messageId); FileLog.d("addMessage = " + messageId); } + if (topicId != 0) { + TopicKey topicKey = TopicKey.of(message.dialog_id, topicId); + Integer value = topicsReadMax.get(topicKey); + int currentTopicMaxId = value == null ? -1 : value; + if (currentTopicMaxId == -1) { + cursor = database.queryFinalized("SELECT top_message FROM topics WHERE did = " + message.dialog_id + " AND topic_id = " + topicId); + if (cursor.next()) { + currentTopicMaxId = cursor.intValue(0); + } else { + currentTopicMaxId = 0; + } + cursor.dispose(); + cursor = null; + topicsReadMax.put(topicKey, currentTopicMaxId); + } + + if (currentTopicMaxId < message.id && message.unread && !message.out) { + Integer newUnread = topicsNewUnreadMessages.get(topicKey); + if (newUnread == null) { + newUnread = 0; + } + newUnread++; + topicsNewUnreadMessages.put(topicKey, newUnread); + } + } } if (MediaDataController.canAddMessageToMedia(message)) { if (mediaIdsMap == null) { @@ -10125,6 +11154,25 @@ public class MessagesStorage extends BaseController { dialogMediaTypes.put(message.dialog_id, mediaTypes); } mediaTypes.put(messageId, MediaDataController.getMediaType(message)); + if (topicId != 0) { + TopicKey topicKey = TopicKey.of(message.dialog_id, topicId); + StringBuilder messageMediaIdsTopics = mediaIdsMapTopics.get(topicKey); + if (messageMediaIdsTopics == null) { + messageMediaIdsTopics = new StringBuilder(); + mediaIdsMapTopics.put(topicKey, messageMediaIdsTopics); + } + if (messageMediaIdsTopics.length() > 0) { + messageMediaIdsTopics.append(","); + } + messageMediaIdsTopics.append(messageId); + + ids = messagesMediaIdsMapTopics.get(topicKey); + if (ids == null) { + ids = new ArrayList<>(); + messagesMediaIdsMapTopics.put(topicKey, ids); + } + ids.add(messageId); + } } if (isValidKeyboardToSave(message)) { TLRPC.Message oldMessage = botKeyboards.get(message.dialog_id); @@ -10143,14 +11191,14 @@ public class MessagesStorage extends BaseController { long dialogId = mediaIdsMap.keyAt(b); StringBuilder messageMediaIds = mediaIdsMap.valueAt(b); SparseIntArray mediaTypes = dialogMediaTypes.get(dialogId); - ArrayList messagesMediaIdsMap = dialogMessagesMediaIdsMap.get(dialogId); + ArrayList messagesMediaIds = dialogMessagesMediaIdsMap.get(dialogId); SparseIntArray mediaTypesChange = null; cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid, type FROM media_v4 WHERE mid IN(%s) AND uid = %d", messageMediaIds.toString(), dialogId)); while (cursor.next()) { int mid = cursor.intValue(0); int type = cursor.intValue(1); if (type == mediaTypes.get(mid)) { - messagesMediaIdsMap.remove((Integer) mid); + messagesMediaIds.remove((Integer) mid); } else { if (mediaTypesChange == null) { if (dialogsMediaTypesChange == null) { @@ -10171,9 +11219,9 @@ public class MessagesStorage extends BaseController { mediaCounts = new SparseArray<>(); } - for (int a = 0, N = messagesMediaIdsMap.size(); a < N; a++) { - int key = messagesMediaIdsMap.get(a); - int type = mediaTypes.get(key); + for (int a = 0, N = messagesMediaIds.size(); a < N; a++) { + int messageId = messagesMediaIds.get(a); + int type = mediaTypes.get(messageId); LongSparseIntArray counts = mediaCounts.get(type); int count; if (counts == null) { @@ -10189,7 +11237,7 @@ public class MessagesStorage extends BaseController { count++; counts.put(dialogId, count); if (mediaTypesChange != null) { - int previousType = mediaTypesChange.get(key, -1); + int previousType = mediaTypesChange.get(messageId, -1); if (previousType >= 0) { counts = mediaCounts.get(previousType); if (counts == null) { @@ -10209,6 +11257,91 @@ public class MessagesStorage extends BaseController { } } } + if (mediaIdsMapTopics != null) { + Iterator iterator = mediaIdsMapTopics.keySet().iterator(); + + while (iterator.hasNext()) { + TopicKey topicKey = iterator.next(); + ArrayList messagesIds = messagesMediaIdsMapTopics.get(topicKey); + StringBuilder messageIds = mediaIdsMapTopics.get(topicKey); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid, type FROM media_topics WHERE mid IN(%s) AND uid = %d AND topic_id = %d", messageIds.toString(), topicKey.dialogId, topicKey.topicId)); + + SparseIntArray mediaTypesChange = null; + + while (cursor.next()) { + SparseIntArray mediaTypes = dialogMediaTypes.get(topicKey.dialogId); + + int mid = cursor.intValue(0); + int type = cursor.intValue(1); + if (type == mediaTypes.get(mid)) { + messagesIds.remove((Integer) mid); + } else { + if (mediaTypesChange == null) { + if (dialogsMediaTypesChange == null) { + dialogsMediaTypesChange = new LongSparseArray<>(); + } + mediaTypesChange = dialogsMediaTypesChange.get(topicKey.dialogId); + if (mediaTypesChange == null) { + mediaTypesChange = new SparseIntArray(); + dialogsMediaTypesChange.put(topicKey.dialogId, mediaTypesChange); + } + } + mediaTypesChange.put(mid, type); + } + } + + SparseIntArray mediaTypes = dialogMediaTypes.get(topicKey.dialogId); + + if (mediaCountsTopics == null) { + mediaCountsTopics = new SparseArray<>(); + } + + for (int a = 0, N = messagesIds.size(); a < N; a++) { + int messageId = messagesIds.get(a); + int type = mediaTypes.get(messageId); + HashMap counts = mediaCountsTopics.get(type); + int count; + if (counts == null) { + counts = new HashMap<>(); + mediaCountsTopics.put(type, counts); + count = 0; + } else { + Integer v = counts.get(topicKey); + if (v == null) { + count = 0; + } else { + count = v; + } + } + + count++; + counts.put(topicKey, count); + if (mediaTypesChange != null) { + int previousType = mediaTypesChange.get(messageId, -1); + if (previousType >= 0) { + counts = mediaCountsTopics.get(previousType); + if (counts == null) { + counts = new HashMap<>(); + count = 0; + mediaCountsTopics.put(previousType, counts); + } else { + Integer v = counts.get(topicKey); + if (v == null) { + count = Integer.MIN_VALUE; + } else { + count = v; + } + } + if (count == Integer.MIN_VALUE) { + count = 0; + } + count--; + counts.put(topicKey, count); + } + } + } + } + } if (!messageIdsMap.isEmpty()) { for (int b = 0, N2 = messageIdsMap.size(); b < N2; b++) { @@ -10249,10 +11382,49 @@ public class MessagesStorage extends BaseController { } } + if (!topicMentionsIdsMap.isEmpty()) { + Iterator iterator = topicMentionsIdsMap.keySet().iterator(); + while (iterator.hasNext()) { + TopicKey topicKey = iterator.next(); + ArrayList messageIds = topicMentionsIdsMap.get(topicKey); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid FROM messages_topics WHERE mid IN(%s) AND uid = %d AND topic_id = %d", TextUtils.join(",", messageIds), topicKey.dialogId, topicKey.topicId)); + while (cursor.next()) { + Integer mid = cursor.intValue(0); + messageIds.remove(mid); + } + cursor.dispose(); + cursor = null; + + FileLog.d("new unread mentions " + topicKey.dialogId + " " + topicKey.topicId + " " + messageIds.size()); + topicsMentions.put(topicKey, messageIds.size()); + } + } + + if (dialogsMediaTypesChange != null) { + for (int i = 0; i < dialogsMediaTypesChange.size(); i++) { + long dialogId = dialogsMediaTypesChange.keyAt(i); + SparseIntArray messageIds = dialogsMediaTypesChange.valueAt(i); + StringBuilder messagesString = new StringBuilder(); + for (int k = 0; k < dialogsMediaTypesChange.size(); k++) { + int mid = messageIds.keyAt(k); + if (messagesString.length() != 0) { + messagesString.append(", "); + } + messagesString.append(mid); + } + database.executeFast(String.format(Locale.US, "DELETE FROM media_v4 WHERE mid IN(%s) AND uid = %d", messagesString.toString(), dialogId)).stepThis().dispose(); + database.executeFast(String.format(Locale.US, "DELETE FROM media_topics WHERE mid IN(%s) AND uid = %d", messagesString.toString(), dialogId)).stepThis().dispose(); + } + } + int downloadMediaMask = 0; for (int a = 0; a < messages.size(); a++) { TLRPC.Message message = messages.get(a); + if (message == null) { + continue; + } fixUnsupportedMedia(message); + int topicId = MessageObject.getTopicId(message); state_messages.requery(); int messageId = message.id; @@ -10267,12 +11439,25 @@ public class MessagesStorage extends BaseController { if (message.action instanceof TLRPC.TL_messageEncryptedAction && !(message.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionSetMessageTTL || message.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionScreenshotMessages)) { updateDialog = false; } else if (message.out) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid FROM messages_v2 WHERE mid = %d AND uid = %d", messageId, message.dialog_id)); - if (cursor.next()) { - updateDialog = false; +// cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid, group_id FROM messages_v2 WHERE mid = %d AND uid = %d", messageId, message.dialog_id)); +// if (cursor.next()) { +// updateDialog = false; +// } +// cursor.dispose(); +// cursor = null; + } + + if (message.action instanceof TLRPC.TL_messageActionTopicCreate && !MessageObject.isOut(message)) { + if (createNewTopics == null) { + createNewTopics = new ArrayList<>(); } - cursor.dispose(); - cursor = null; + createNewTopics.add(message); + } + if (message.action instanceof TLRPC.TL_messageActionTopicEdit) { + if (createNewTopics == null) { + createNewTopics = new ArrayList<>(); + } + createNewTopics.add(message); } if (updateDialog) { @@ -10280,51 +11465,89 @@ public class MessagesStorage extends BaseController { if (lastMessage == null || message.date > lastMessage.date || lastMessage.id > 0 && message.id > lastMessage.id || lastMessage.id < 0 && message.id < lastMessage.id) { messagesMap.put(message.dialog_id, message); } + if (topicId != 0) { + TopicKey topicKey = TopicKey.of(message.dialog_id, topicId); + lastMessage = topicMessagesMap.get(topicKey); + if (lastMessage == null || message.date > lastMessage.date || lastMessage.id > 0 && message.id > lastMessage.id || lastMessage.id < 0 && message.id < lastMessage.id) { + topicMessagesMap.put(topicKey, message); + } + } } - state_messages.bindInteger(1, messageId); - state_messages.bindLong(2, message.dialog_id); - state_messages.bindInteger(3, MessageObject.getUnreadFlags(message)); - state_messages.bindInteger(4, message.send_state); - state_messages.bindInteger(5, message.date); - state_messages.bindByteBuffer(6, data); - state_messages.bindInteger(7, (MessageObject.isOut(message) || message.from_scheduled ? 1 : 0)); - state_messages.bindInteger(8, message.ttl); - if ((message.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { - state_messages.bindInteger(9, message.views); - } else { - state_messages.bindInteger(9, getMessageMediaType(message)); + + for (int i = 0; i < 2; i++) { + boolean isTopic = i == 1; + if (threadMessageId != 0 && !isTopic) { + continue; + } + if (isTopic && topicId == 0) { + continue; + } + int pointer = 1; + SQLitePreparedStatement statement = isTopic ? state_messages_topic : state_messages; + + statement.requery(); + statement.bindInteger(pointer++, messageId); + statement.bindLong(pointer++, message.dialog_id); + if (isTopic) { + statement.bindLong(pointer++, topicId); + } + statement.bindInteger(pointer++, MessageObject.getUnreadFlags(message)); + statement.bindInteger(pointer++, message.send_state); + statement.bindInteger(pointer++, message.date); + statement.bindByteBuffer(pointer++, data); + statement.bindInteger(pointer++, (MessageObject.isOut(message) || message.from_scheduled ? 1 : 0)); + statement.bindInteger(pointer++, message.ttl); + if ((message.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { + statement.bindInteger(pointer++, message.views); + } else { + statement.bindInteger(pointer++, getMessageMediaType(message)); + } + int flags = 0; + if (message.stickerVerified == 0) { + flags |= 1; + } else if (message.stickerVerified == 2) { + flags |= 2; + } + statement.bindInteger(pointer++, flags); + statement.bindInteger(pointer++, message.mentioned ? 1 : 0); + statement.bindInteger(pointer++, message.forwards); + NativeByteBuffer repliesData = null; + if (message.replies != null) { + repliesData = new NativeByteBuffer(message.replies.getObjectSize()); + message.replies.serializeToStream(repliesData); + statement.bindByteBuffer(pointer++, repliesData); + } else { + statement.bindNull(pointer++); + } + if (message.reply_to != null) { + statement.bindInteger(pointer++, message.reply_to.reply_to_top_id != 0 ? message.reply_to.reply_to_top_id : message.reply_to.reply_to_msg_id); + } else { + statement.bindInteger(pointer++, 0); + } + statement.bindLong(pointer++, MessageObject.getChannelId(message)); + NativeByteBuffer customParams = MessageCustomParamsHelper.writeLocalParams(message); + if (customParams != null) { + statement.bindByteBuffer(pointer++, customParams); + } else { + statement.bindNull(pointer++); + } + if (!isTopic) { + if ((message.flags & 131072) != 0) { + statement.bindLong(pointer++, message.grouped_id); + } else { + statement.bindNull(pointer++); + } + } + statement.step(); + + if (repliesData != null) { + repliesData.reuse(); + } + if (customParams != null) { + customParams.reuse(); + } } - int flags = 0; - if (message.stickerVerified == 0) { - flags |= 1; - } else if (message.stickerVerified == 2) { - flags |= 2; - } - state_messages.bindInteger(10, flags); - state_messages.bindInteger(11, message.mentioned ? 1 : 0); - state_messages.bindInteger(12, message.forwards); - NativeByteBuffer repliesData = null; - if (message.replies != null) { - repliesData = new NativeByteBuffer(message.replies.getObjectSize()); - message.replies.serializeToStream(repliesData); - state_messages.bindByteBuffer(13, repliesData); - } else { - state_messages.bindNull(13); - } - if (message.reply_to != null) { - state_messages.bindInteger(14, message.reply_to.reply_to_top_id != 0 ? message.reply_to.reply_to_top_id : message.reply_to.reply_to_msg_id); - } else { - state_messages.bindInteger(14, 0); - } - state_messages.bindLong(15, MessageObject.getChannelId(message)); - NativeByteBuffer customParams = MessageCustomParamsHelper.writeLocalParams(message); - if (customParams != null) { - state_messages.bindByteBuffer(16, customParams); - } else { - state_messages.bindNull(16); - } - state_messages.step(); if (message.random_id != 0) { state_randoms.requery(); @@ -10345,6 +11568,21 @@ public class MessagesStorage extends BaseController { state_media.bindInteger(4, MediaDataController.getMediaType(message)); state_media.bindByteBuffer(5, data); state_media.step(); + state_media = null; + + if (topicId != 0) { + if (state_media_topics == null) { + state_media_topics = database.executeFast("REPLACE INTO media_topics VALUES(?, ?, ?, ?, ?, ?)"); + } + state_media_topics.requery(); + state_media_topics.bindInteger(1, messageId); + state_media_topics.bindLong(2, message.dialog_id); + state_media_topics.bindInteger(3, topicId); + state_media_topics.bindInteger(4, message.date); + state_media_topics.bindInteger(5, MediaDataController.getMediaType(message)); + state_media_topics.bindByteBuffer(6, data); + state_media_topics.step(); + } } if (message.ttl_period != 0 && message.id > 0) { @@ -10377,13 +11615,6 @@ public class MessagesStorage extends BaseController { state_webpage.bindLong(3, message.dialog_id); state_webpage.step(); } - - if (repliesData != null) { - repliesData.reuse(); - } - if (customParams != null) { - customParams.reuse(); - } data.reuse(); if (downloadMask != 0 && (message.peer_id.channel_id == 0 || message.post) && message.date >= getConnectionsManager().getCurrentTime() - 60 * 60 && getDownloadController().canDownloadMedia(message) == 1) { @@ -10451,6 +11682,7 @@ public class MessagesStorage extends BaseController { } } state_messages.dispose(); + state_messages_topic.dispose(); if (state_media != null) { state_media.dispose(); state_media = null; @@ -10471,8 +11703,16 @@ public class MessagesStorage extends BaseController { state_webpage.dispose(); state_webpage = null; - state_dialogs_replace = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - state_dialogs_update = database.executeFast("UPDATE dialogs SET date = ?, unread_count = ?, last_mid = ?, unread_count_i = ? WHERE did = ?"); + if (createNewTopics != null) { + for (int i = 0; i < createNewTopics.size(); i++) { + TLRPC.Message message = createNewTopics.get(i); + createOrEditTopic(message.dialog_id, message); + } + } + + state_dialogs_replace = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + state_dialogs_update = database.executeFast("UPDATE dialogs SET date = ?, unread_count = ?, last_mid = ?, last_mid_group = ?, unread_count_i = ? WHERE did = ?"); + state_topics_update = database.executeFast("UPDATE topics SET unread_count = ?, top_message = ?, unread_mentions = ? WHERE did = ? AND topic_id = ?"); ArrayList dids = new ArrayList<>(); for (int a = 0; a < messagesMap.size(); a++) { @@ -10519,11 +11759,13 @@ public class MessagesStorage extends BaseController { messageId = message.local_id; } } - if (old_unread_count == 0 && unread_count != 0) { - newMessagesCounts.put(key, unread_count); - } - if (old_mentions_count == 0 && mentions_count != 0) { - newMentionsCounts.put(key, mentions_count); + if (!isForum(key)) { + if (old_unread_count == 0 && unread_count != 0) { + newMessagesCounts.put(key, unread_count); + } + if (old_mentions_count == 0 && mentions_count != 0){ + newMentionsCounts.put(key, mentions_count); + } } dids.add(key); @@ -10532,8 +11774,13 @@ public class MessagesStorage extends BaseController { state_dialogs_update.bindInteger(1, message != null && (!doNotUpdateDialogDate || dialog_date == 0) ? message.date : dialog_date); state_dialogs_update.bindInteger(2, old_unread_count + unread_count); state_dialogs_update.bindInteger(3, messageId); - state_dialogs_update.bindInteger(4, old_mentions_count + mentions_count); - state_dialogs_update.bindLong(5, key); + if (message != null && (message.flags & 131072) != 0) { + state_dialogs_update.bindLong(4, message.grouped_id); + } else { + state_dialogs_update.bindNull(4); + } + state_dialogs_update.bindInteger(5, old_mentions_count + mentions_count); + state_dialogs_update.bindLong(6, key); state_dialogs_update.step(); } else { state_dialogs_replace.requery(); @@ -10552,6 +11799,11 @@ public class MessagesStorage extends BaseController { state_dialogs_replace.bindInteger(13, 0); state_dialogs_replace.bindNull(14); state_dialogs_replace.bindInteger(15, 0); + if (message != null && (message.flags & 131072) != 0) { + state_dialogs_replace.bindLong(16, message.grouped_id); + } else { + state_dialogs_replace.bindNull(16); + } state_dialogs_replace.step(); unknownDialogsIds.put(key, true); } @@ -10561,6 +11813,100 @@ public class MessagesStorage extends BaseController { state_dialogs_replace.dispose(); state_dialogs_replace = null; + + ArrayList topicUpdatesInUi = new ArrayList<>(); + Iterator iterator = topicMessagesMap.keySet().iterator(); + while (iterator.hasNext()) { + TopicKey topicKey = iterator.next(); + if (topicKey.dialogId == 0 || topicKey.topicId == 0) { + continue; + } + + TLRPC.Message message = topicMessagesMap.get(topicKey); + + cursor = database.queryFinalized("SELECT unread_count, top_message, unread_mentions FROM topics WHERE did = " + topicKey.dialogId + " AND topic_id = " + topicKey.topicId); + int oldUnreadCount = 0; + int oldMentions = 0; + int topMessage = 0; + int newMentions = 0; + int newUnreadMessages = 0; + + boolean exist = false; + if (cursor.next()) { + exist = true; + oldUnreadCount = cursor.intValue(0); + topMessage = cursor.intValue(1); + oldMentions = cursor.intValue(2); + } + cursor.dispose(); + cursor = null; + if (!exist) { + if (topicUpdatesInUi == null) { + topicUpdatesInUi = new ArrayList<>(); + } + TopicsController.TopicUpdate topicUpdate = new TopicsController.TopicUpdate(); + topicUpdate.dialogId = topicKey.dialogId; + topicUpdate.topicId = topicKey.topicId; + topicUpdate.reloadTopic = true; + topicUpdatesInUi.add(topicUpdate); + FileLog.d("unknown topic need reload" + topicKey.dialogId + " " + topicKey.topicId); + continue; + } + Integer newMessagesInteger = topicsNewUnreadMessages.get(topicKey); + Integer newMentionsInteger = topicsMentions.get(topicKey); + + int messageId = message != null ? message.id : topMessage; + if (message != null) { + if (message.local_id != 0) { + messageId = message.local_id; + } + } + if (newMessagesInteger != null) { + newUnreadMessages = newMessagesInteger; + } + + if (newMentionsInteger != null) { + newMentions = newMentionsInteger; + } + int newUnreadCount = oldUnreadCount + newUnreadMessages; + int newUnreadMentions = oldMentions + newMentions; + + state_topics_update.requery(); + state_topics_update.bindInteger(1, newUnreadCount); + state_topics_update.bindInteger(2, messageId); + state_topics_update.bindInteger(3, newUnreadMentions); + state_topics_update.bindLong(4, topicKey.dialogId); + state_topics_update.bindInteger(5, topicKey.topicId); + state_topics_update.step(); + + if (isForum(topicKey.dialogId)) { + if (oldUnreadCount == 0 && newUnreadCount != 0) { + newMessagesCounts.put(topicKey.dialogId, 1); + } + if (oldMentions == 0 && newUnreadMentions != 0){ + newMentionsCounts.put(topicKey.dialogId, newUnreadMentions); + } + } + + FileLog.d("update topic " + topicKey.dialogId + " " + topicKey.topicId + " " + (oldUnreadCount + newUnreadMessages) + " " + (oldMentions + newMentions)); + if (message != null) { + if (topicUpdatesInUi == null) { + topicUpdatesInUi = new ArrayList<>(); + } + TopicsController.TopicUpdate topicUpdate = new TopicsController.TopicUpdate(); + topicUpdate.dialogId = topicKey.dialogId; + topicUpdate.topicId = topicKey.topicId; + topicUpdate.topMessage = message; + topicUpdate.unreadMentions = oldMentions + newMentions; + topicUpdate.topMessageId = messageId; + topicUpdate.unreadCount = oldUnreadCount + newUnreadMessages; + topicUpdatesInUi.add(topicUpdate); + } + } + + state_topics_update.dispose(); + state_topics_update = null; + if (mediaCounts != null) { state_randoms = database.executeFast("REPLACE INTO media_counts_v2 VALUES(?, ?, ?, ?)"); for (int a = 0, N = mediaCounts.size(); a < N; a++) { @@ -10591,12 +11937,53 @@ public class MessagesStorage extends BaseController { state_randoms.dispose(); state_randoms = null; } + + if (mediaCountsTopics != null) { + state_randoms = database.executeFast("REPLACE INTO media_counts_topics VALUES(?, ?, ?, ?, ?)"); + for (int a = 0, N = mediaCountsTopics.size(); a < N; a++) { + int type = mediaCountsTopics.keyAt(a); + HashMap topicCountsMap = mediaCountsTopics.valueAt(a); + iterator = topicCountsMap.keySet().iterator(); + while (iterator.hasNext()) { + TopicKey topicKey = iterator.next(); + int count = -1; + int old = 0; + cursor = database.queryFinalized(String.format(Locale.US, "SELECT count, old FROM media_counts_topics WHERE uid = %d AND topic_id = %d AND type = %d LIMIT 1", topicKey.dialogId, topicKey.topicId, type)); + if (cursor.next()) { + count = cursor.intValue(0); + old = cursor.intValue(1); + } + cursor.dispose(); + cursor = null; + if (count != -1) { + state_randoms.requery(); + count += topicCountsMap.get(topicKey); + state_randoms.bindLong(1, topicKey.dialogId); + state_randoms.bindInteger(2, topicKey.topicId); + state_randoms.bindInteger(3, type); + state_randoms.bindInteger(4, Math.max(0, count)); + state_randoms.bindInteger(5, old); + state_randoms.step(); + } + + if (BuildVars.DEBUG_PRIVATE_VERSION) { + FileLog.d("update" + topicKey.dialogId + topicKey.topicId + " " + type + " " + count); + } + + } + } + state_randoms.dispose(); + state_randoms = null; + } + if (withTransaction) { database.commitTransaction(); databaseInTransaction = false; } updateFiltersReadCounter(newMessagesCounts, newMentionsCounts, false); + loadGroupedMessagesForTopicUpdates(topicUpdatesInUi); getMessagesController().processDialogsUpdateRead(messagesCounts, mentionCounts); + getMessagesController().getTopicsController().processUpdate(topicUpdatesInUi); if (downloadMediaMask != 0) { int downloadMediaMaskFinal = downloadMediaMask; @@ -10639,24 +12026,77 @@ public class MessagesStorage extends BaseController { if (state_dialogs_update != null) { state_dialogs_update.dispose(); } + if (state_topics_update != null) { + state_topics_update.dispose(); + } if (cursor != null) { cursor = null; } } } - public void putMessages(ArrayList messages, boolean withTransaction, boolean useQueue, boolean doNotUpdateDialogDate, int downloadMask, boolean scheduled) { - putMessages(messages, withTransaction, useQueue, doNotUpdateDialogDate, downloadMask, false, scheduled); + private void createOrEditTopic(long dialogId, TLRPC.Message message) { + TLRPC.TL_forumTopic forumTopic = new TLRPC.TL_forumTopic(); + + + forumTopic.topicStartMessage = message; + forumTopic.top_message = message.id; + forumTopic.topMessage = message; + forumTopic.from_id = getMessagesController().getPeer(getUserConfig().clientUserId); + forumTopic.notify_settings = new TLRPC.TL_peerNotifySettings(); + forumTopic.unread_count = 0; + + if (message.action instanceof TLRPC.TL_messageActionTopicCreate) { + TLRPC.TL_messageActionTopicCreate action = (TLRPC.TL_messageActionTopicCreate) message.action; + forumTopic.id = message.id; + forumTopic.icon_emoji_id = action.icon_emoji_id; + forumTopic.title = action.title; + forumTopic.icon_color = action.icon_color; + if (forumTopic.icon_emoji_id != 0) { + forumTopic.flags |= 1; + } + ArrayList topics = new ArrayList<>(); + topics.add(forumTopic); + saveTopics(dialogId, topics, false, false); + AndroidUtilities.runOnUIThread(() -> { + getMessagesController().getTopicsController().onTopicCreated(dialogId, forumTopic, false); + }); + } else if (message.action instanceof TLRPC.TL_messageActionTopicEdit) { + TLRPC.TL_messageActionTopicEdit action = (TLRPC.TL_messageActionTopicEdit) message.action; + forumTopic.id = MessageObject.getTopicId(message); + forumTopic.icon_emoji_id = action.icon_emoji_id; + forumTopic.title = action.title; + forumTopic.closed = action.closed; + int flags = 0; + if ((action.flags & 1) != 0) { + flags += TopicsController.TOPIC_FLAG_TITLE; + } + if ((action.flags & 2) != 0) { + flags += TopicsController.TOPIC_FLAG_ICON; + } + if ((action.flags & 4) != 0) { + flags += TopicsController.TOPIC_FLAG_CLOSE; + } + updateTopicData(dialogId, forumTopic, flags); + int finalFlags = flags; + AndroidUtilities.runOnUIThread(() -> { + getMessagesController().getTopicsController().updateTopicInUi(dialogId, forumTopic, finalFlags); + }); + } } - public void putMessages(ArrayList messages, boolean withTransaction, boolean useQueue, boolean doNotUpdateDialogDate, int downloadMask, boolean ifNoLastMessage, boolean scheduled) { + public void putMessages(ArrayList messages, boolean withTransaction, boolean useQueue, boolean doNotUpdateDialogDate, int downloadMask, boolean scheduled, int threadMessageId) { + putMessages(messages, withTransaction, useQueue, doNotUpdateDialogDate, downloadMask, false, scheduled, threadMessageId); + } + + public void putMessages(ArrayList messages, boolean withTransaction, boolean useQueue, boolean doNotUpdateDialogDate, int downloadMask, boolean ifNoLastMessage, boolean scheduled, int threadMessageId) { if (messages.size() == 0) { return; } if (useQueue) { - storageQueue.postRunnable(() -> putMessagesInternal(messages, withTransaction, doNotUpdateDialogDate, downloadMask, ifNoLastMessage, scheduled)); + storageQueue.postRunnable(() -> putMessagesInternal(messages, withTransaction, doNotUpdateDialogDate, downloadMask, ifNoLastMessage, scheduled, threadMessageId)); } else { - putMessagesInternal(messages, withTransaction, doNotUpdateDialogDate, downloadMask, ifNoLastMessage, scheduled); + putMessagesInternal(messages, withTransaction, doNotUpdateDialogDate, downloadMask, ifNoLastMessage, scheduled, threadMessageId); } } @@ -10668,6 +12108,7 @@ public class MessagesStorage extends BaseController { database.executeFast(String.format(Locale.US, "UPDATE scheduled_messages_v2 SET send_state = 2 WHERE mid = %d AND uid = %d", messageId, MessageObject.getDialogId(message))).stepThis().dispose(); } else { database.executeFast(String.format(Locale.US, "UPDATE messages_v2 SET send_state = 2 WHERE mid = %d AND uid = %d", messageId, MessageObject.getDialogId(message))).stepThis().dispose(); + database.executeFast(String.format(Locale.US, "UPDATE messages_topics SET send_state = 2 WHERE mid = %d AND uid = %d", messageId, MessageObject.getDialogId(message))).stepThis().dispose(); } } catch (Exception e) { FileLog.e(e); @@ -10791,6 +12232,7 @@ public class MessagesStorage extends BaseController { return null; } SQLitePreparedStatement state = null; + SQLitePreparedStatement state2 = null; if (oldMessageId == newId && date != 0) { try { if (scheduled == 0) { @@ -10802,12 +12244,23 @@ public class MessagesStorage extends BaseController { state.bindInteger(2, newId); state.bindLong(3, did); state.step(); + + if (scheduled == 0) { + state2 = database.executeFast("UPDATE messages_topics SET send_state = 0, date = ? WHERE mid = ? AND uid = ?"); + state2.bindInteger(1, date); + state2.bindInteger(2, newId); + state2.bindLong(3, did); + state2.step(); + } } catch (Exception e) { FileLog.e(e); } finally { if (state != null) { state.dispose(); } + if (state2 != null) { + state2.dispose(); + } } return new long[]{did, newId}; } else { @@ -10818,10 +12271,17 @@ public class MessagesStorage extends BaseController { state.bindInteger(2, oldMessageId); state.bindLong(3, did); state.step(); + + state2 = database.executeFast("UPDATE messages_topics SET mid = ?, send_state = 0 WHERE mid = ? AND uid = ?"); + state2.bindInteger(1, newId); + state2.bindInteger(2, oldMessageId); + state2.bindLong(3, did); + state2.step(); } catch (Exception e) { try { database.executeFast(String.format(Locale.US, "DELETE FROM messages_v2 WHERE mid = %d AND uid = %d", oldMessageId, did)).stepThis().dispose(); database.executeFast(String.format(Locale.US, "DELETE FROM messages_seq WHERE mid = %d", oldMessageId)).stepThis().dispose(); + database.executeFast(String.format(Locale.US, "DELETE FROM messages_topics WHERE mid = %d AND uid = %d", oldMessageId, did)).stepThis().dispose(); } catch (Exception e2) { FileLog.e(e2); } @@ -10830,6 +12290,10 @@ public class MessagesStorage extends BaseController { state.dispose(); state = null; } + if (state2 != null) { + state2.dispose(); + state2 = null; + } } try { @@ -10851,6 +12315,25 @@ public class MessagesStorage extends BaseController { } } + try { + state = database.executeFast("UPDATE media_topics SET mid = ? WHERE mid = ? AND uid = ?"); + state.bindInteger(1, newId); + state.bindInteger(2, oldMessageId); + state.bindLong(3, did); + state.step(); + } catch (Exception e) { + try { + database.executeFast(String.format(Locale.US, "DELETE FROM media_topics WHERE mid = %d AND uid = %d", oldMessageId, did)).stepThis().dispose(); + } catch (Exception e2) { + FileLog.e(e2); + } + } finally { + if (state != null) { + state.dispose(); + state = null; + } + } + try { state = database.executeFast("UPDATE dialogs SET last_mid = ? WHERE last_mid = ?"); state.bindInteger(1, newId); @@ -10963,6 +12446,7 @@ public class MessagesStorage extends BaseController { } } catch (Exception e) { FileLog.e(e); + checkMalformed(e); } finally { if (database != null) { database.commitTransaction(); @@ -11206,13 +12690,17 @@ public class MessagesStorage extends BaseController { broadcastScheduledMessagesChange(dialogsToUpdate.get(a)); } } else { - ArrayList temp = new ArrayList<>(messages); + ArrayList unknownMessages = new ArrayList<>(messages); + ArrayList unknownMessagesInTopics = new ArrayList<>(messages); LongSparseArray dialogsToUpdate = new LongSparseArray<>(); + HashMap topicsMessagesToUpdate = new HashMap<>(); LongSparseArray> messagesByDialogs = new LongSparseArray<>(); String ids = TextUtils.join(",", messages); ArrayList filesToDelete = new ArrayList<>(); ArrayList namesToDelete = new ArrayList<>(); ArrayList> idsToDelete = new ArrayList<>(); + ArrayList topicUpdatesInUi = null; + long currentUser = getUserConfig().getClientUserId(); if (dialogId != 0) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, data, read_state, out, mention, mid FROM messages_v2 WHERE mid IN(%s) AND uid = %d", ids, dialogId)); @@ -11224,7 +12712,7 @@ public class MessagesStorage extends BaseController { while (cursor.next()) { long did = cursor.longValue(0); int mid = cursor.intValue(5); - temp.remove((Integer) mid); + unknownMessages.remove((Integer) mid); ArrayList mids = messagesByDialogs.get(did); if (mids == null) { mids = new ArrayList<>(); @@ -11264,20 +12752,76 @@ public class MessagesStorage extends BaseController { cursor.dispose(); cursor = null; + ArrayList topicsToDelete = null; + if (dialogId < 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, data, read_state, out, mention, mid FROM messages_topics WHERE mid IN(%s) AND uid = %d", ids, dialogId)); + + try { + while (cursor.next()) { + long did = cursor.longValue(0); + int mid = cursor.intValue(5); + int topicId = 0; + unknownMessagesInTopics.remove((Integer) mid); + + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + message.readAttachPath(data, getUserConfig().clientUserId); + data.reuse(); + addFilesToDelete(message, filesToDelete, idsToDelete, namesToDelete, false); + if (message.action instanceof TLRPC.TL_messageActionTopicCreate) { + if (topicsToDelete == null) { + topicsToDelete = new ArrayList<>(); + } + topicsToDelete.add(TopicKey.of(did, message.id)); + } + topicId = MessageObject.getTopicId(message); + } + if (topicId != 0) { + TopicKey topicKey = TopicKey.of(dialogId, topicId); + + int read_state = cursor.intValue(2); + if (cursor.intValue(3) == 0) { + int[] count = topicsMessagesToUpdate.get(topicKey); + if (count == null) { + count = new int[2]; + topicsMessagesToUpdate.put(topicKey, count); + } + if (read_state < 2) { + count[1]++; + } + if (read_state == 0 || read_state == 2) { + count[0]++; + } + } + } + } + } catch (Exception e) { + FileLog.e(e); + } + cursor.dispose(); + cursor = null; + } + database.beginTransaction(); - for (int i = 0; i < 2; i++) { + for (int i = 0; i < 3; i++) { if (i == 0) { if (dialogId != 0) { state = getMessagesStorage().getDatabase().executeFast("UPDATE messages_v2 SET replydata = ? WHERE reply_to_message_id IN(?) AND uid = ?"); } else { state = getMessagesStorage().getDatabase().executeFast("UPDATE messages_v2 SET replydata = ? WHERE reply_to_message_id IN(?) AND is_channel = 0"); } - } else { + } else if (i == 1) { if (dialogId != 0) { state = getMessagesStorage().getDatabase().executeFast("UPDATE scheduled_messages_v2 SET replydata = ? WHERE reply_to_message_id IN(?) AND uid = ?"); } else { state = getMessagesStorage().getDatabase().executeFast("UPDATE scheduled_messages_v2 SET replydata = ? WHERE reply_to_message_id IN(?)"); } + } else { + if (dialogId == 0) { + continue; + } + state = getMessagesStorage().getDatabase().executeFast("UPDATE messages_topics SET replydata = ? WHERE reply_to_message_id IN(?) AND uid = ?"); } TLRPC.TL_messageEmpty emptyMessage = new TLRPC.TL_messageEmpty(); NativeByteBuffer data = new NativeByteBuffer(emptyMessage.getObjectSize()); @@ -11325,6 +12869,56 @@ public class MessagesStorage extends BaseController { state = null; } + if (!topicsMessagesToUpdate.isEmpty()) { + HashSet dialogsToCheck = null; + for (TopicKey topicKey : topicsMessagesToUpdate.keySet()) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT unread_count, unread_mentions FROM topics WHERE did = %d AND topic_id = %d", topicKey.dialogId, topicKey.topicId)); + int old_unread_count = 0; + int old_mentions_count = 0; + if (cursor.next()) { + old_unread_count = cursor.intValue(0); + old_mentions_count = cursor.intValue(1); + } + cursor.dispose(); + cursor = null; + + int[] counts = topicsMessagesToUpdate.get(topicKey); + int newUnreadCount = Math.max(0, old_unread_count - counts[0]); + int newUnreadMentionsCount = Math.max(0, old_mentions_count - counts[1]); + state = database.executeFast("UPDATE topics SET unread_count = ?, unread_mentions = ? WHERE did = ? AND topic_id = ?"); + state.requery(); + state.bindInteger(1, newUnreadCount); + state.bindInteger(2, newUnreadMentionsCount); + state.bindLong(3, topicKey.dialogId); + state.bindLong(4, topicKey.topicId); + state.step(); + state.dispose(); + state = null; + + if (newUnreadCount == 0) { + if (dialogsToCheck == null) { + dialogsToCheck = new HashSet<>(); + } + dialogsToCheck.add(topicKey.dialogId); + } + + TopicsController.TopicUpdate topicUpdate = new TopicsController.TopicUpdate(); + topicUpdate.dialogId = topicKey.dialogId; + topicUpdate.topicId = topicKey.topicId; + topicUpdate.unreadCount = newUnreadCount; + topicUpdate.onlyCounters = true; + if (topicUpdatesInUi == null) { + topicUpdatesInUi = new ArrayList<>(); + } + topicUpdatesInUi.add(topicUpdate); + } + if (dialogsToCheck != null) { + for (Long dialogToResert : dialogsToCheck) { + resetForumBadgeIfNeed(dialogToResert); + } + } + } + for (int a = 0, N = messagesByDialogs.size(); a < N; a++) { long did = messagesByDialogs.keyAt(a); ArrayList mids = messagesByDialogs.valueAt(a); @@ -11359,9 +12953,10 @@ public class MessagesStorage extends BaseController { cursor = null; } database.executeFast(String.format(Locale.US, "DELETE FROM messages_v2 WHERE mid IN(%s) AND uid = %d", ids, did)).stepThis().dispose(); + database.executeFast(String.format(Locale.US, "DELETE FROM messages_topics WHERE mid IN(%s) AND uid = %d", ids, did)).stepThis().dispose(); database.executeFast(String.format(Locale.US, "DELETE FROM polls_v2 WHERE mid IN(%s) AND uid = %d", ids, did)).stepThis().dispose(); database.executeFast(String.format(Locale.US, "DELETE FROM bot_keyboard WHERE mid IN(%s) AND uid = %d", ids, did)).stepThis().dispose(); - if (temp.isEmpty()) { + if (unknownMessages.isEmpty()) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, type FROM media_v4 WHERE mid IN(%s) AND uid = %d", ids, did)); SparseArray> mediaCounts = null; while (cursor.next()) { @@ -11417,22 +13012,101 @@ public class MessagesStorage extends BaseController { state = null; } } + if (unknownMessagesInTopics.isEmpty()) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, topic_id, type FROM media_topics WHERE mid IN(%s) AND uid = %d", ids, did)); + SparseArray> mediaCounts = null; + while (cursor.next()) { + long uid = cursor.longValue(0); + int topicId = cursor.intValue(1); + int type = cursor.intValue(2); + TopicKey topicKey = TopicKey.of(uid, topicId); + if (mediaCounts == null) { + mediaCounts = new SparseArray<>(); + } + HashMap counts = mediaCounts.get(type); + Integer count; + if (counts == null) { + counts = new HashMap<>(); + count = 0; + mediaCounts.put(type, counts); + } else { + count = counts.get(topicKey); + } + if (count == null) { + count = 0; + } + count++; + counts.put(topicKey, count); + } + cursor.dispose(); + cursor = null; + if (mediaCounts != null) { + state = database.executeFast("REPLACE INTO media_counts_topics VALUES(?, ?, ?, ?, ?)"); + for (int c = 0, N3 = mediaCounts.size(); c < N3; c++) { + int type = mediaCounts.keyAt(c); + HashMap value = mediaCounts.valueAt(c); + Iterator iterator = value.keySet().iterator(); + while (iterator.hasNext()) { + TopicKey topicKey = iterator.next(); + int count = -1; + int old = 0; + cursor = database.queryFinalized(String.format(Locale.US, "SELECT count, old FROM media_counts_topics WHERE uid = %d AND topic_id = %d AND type = %d LIMIT 1", topicKey.dialogId, topicKey.topicId, type)); + if (cursor.next()) { + count = cursor.intValue(0); + old = cursor.intValue(1); + } + cursor.dispose(); + if (count != -1) { + state.requery(); + count = Math.max(0, count - value.get(topicKey)); + state.bindLong(1, topicKey.dialogId); + state.bindLong(2, topicKey.topicId); + state.bindInteger(3, type); + state.bindInteger(4, count); + state.bindInteger(5, old); + state.step(); + } + } + } + state.dispose(); + state = null; + } + } database.executeFast(String.format(Locale.US, "DELETE FROM media_v4 WHERE mid IN(%s) AND uid = %d", ids, did)).stepThis().dispose(); + database.executeFast(String.format(Locale.US, "DELETE FROM media_topics WHERE mid IN(%s) AND uid = %d", ids, did)).stepThis().dispose(); } database.executeFast(String.format(Locale.US, "DELETE FROM messages_seq WHERE mid IN(%s)", ids)).stepThis().dispose(); - if (!temp.isEmpty()) { + if (!unknownMessages.isEmpty()) { if (dialogId == 0) { database.executeFast("UPDATE media_counts_v2 SET old = 1 WHERE 1").stepThis().dispose(); } else { database.executeFast(String.format(Locale.US, "UPDATE media_counts_v2 SET old = 1 WHERE uid = %d", dialogId)).stepThis().dispose(); } } + if (!unknownMessagesInTopics.isEmpty()) { + if (dialogId == 0) { + database.executeFast("UPDATE media_counts_topics SET old = 1 WHERE 1").stepThis().dispose(); + } else { + database.executeFast(String.format(Locale.US, "UPDATE media_counts_topics SET old = 1 WHERE uid = %d", dialogId)).stepThis().dispose(); + } + } getMediaDataController().clearBotKeyboard(0, messages); if (dialogsToUpdate.size() != 0) { resetAllUnreadCounters(false); } updateWidgets(dialogsIds); + + if (topicsToDelete != null) { + for (int i = 0; i < topicsToDelete.size(); i++) { + TopicKey topicKey = topicsToDelete.get(i); + database.executeFast(String.format(Locale.US, "DELETE FROM topics WHERE did = %d AND topic_id = %d", topicKey.dialogId, topicKey.topicId)).stepThis().dispose(); + } + getMessagesController().getTopicsController().onTopicsDeletedServerSide(topicsToDelete); + } + if (topicUpdatesInUi != null) { + getMessagesController().getTopicsController().processUpdate(topicUpdatesInUi); + } } return dialogsIds; } catch (Exception e) { @@ -11459,7 +13133,7 @@ public class MessagesStorage extends BaseController { if (!messages.isEmpty()) { if (channelId != 0) { dialogsToUpdate.add(-channelId); - state = database.executeFast("UPDATE dialogs SET last_mid = (SELECT mid FROM messages_v2 WHERE uid = ? AND date = (SELECT MAX(date) FROM messages_v2 WHERE uid = ?)) WHERE did = ?"); + state = database.executeFast("UPDATE dialogs SET (last_mid, last_mid_group) = (SELECT mid, group_id FROM messages_v2 WHERE uid = ? AND date = (SELECT MAX(date) FROM messages_v2 WHERE uid = ?)) WHERE did = ?"); } else { if (originalDialogId == 0) { String ids = TextUtils.join(",", messages); @@ -11472,7 +13146,7 @@ public class MessagesStorage extends BaseController { } else { dialogsToUpdate.add(originalDialogId); } - state = database.executeFast("UPDATE dialogs SET last_mid = (SELECT mid FROM messages_v2 WHERE uid = ? AND date = (SELECT MAX(date) FROM messages_v2 WHERE uid = ? AND date != 0)) WHERE did = ?"); + state = database.executeFast("UPDATE dialogs SET (last_mid, last_mid_group) = (SELECT mid, group_id FROM messages_v2 WHERE uid = ? AND date = (SELECT MAX(date) FROM messages_v2 WHERE uid = ? AND date != 0)) WHERE did = ?"); } database.beginTransaction(); for (int a = 0; a < dialogsToUpdate.size(); a++) { @@ -11504,7 +13178,9 @@ public class MessagesStorage extends BaseController { ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedToLoad = new ArrayList<>(); - cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, m.date, d.pts, d.inbox_max, d.outbox_max, d.pinned, d.unread_count_i, d.flags, d.folder_id, d.data, d.unread_reactions FROM dialogs as d LEFT JOIN messages_v2 as m ON d.last_mid = m.mid AND d.did = m.uid WHERE d.did IN(%s)", ids)); + LongSparseArray groupsToLoad = new LongSparseArray<>(); + + cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, m.date, d.pts, d.inbox_max, d.outbox_max, d.pinned, d.unread_count_i, d.flags, d.folder_id, d.data, d.unread_reactions, d.last_mid_group FROM dialogs as d LEFT JOIN messages_v2 as m ON d.last_mid = m.mid AND d.did = m.uid AND d.last_mid_group IS NULL WHERE d.did IN(%s)", ids)); while (cursor.next()) { long dialogId = cursor.longValue(0); TLRPC.Dialog dialog; @@ -11540,6 +13216,10 @@ public class MessagesStorage extends BaseController { dialog.folder_id = cursor.intValue(15); dialog.unread_reactions_count = cursor.intValue(17); + if (!cursor.isNull(18)) { + groupsToLoad.put(dialogId, cursor.longValue(18)); + } + dialogs.dialogs.add(dialog); NativeByteBuffer data = cursor.byteBufferValue(4); @@ -11582,6 +13262,49 @@ public class MessagesStorage extends BaseController { cursor.dispose(); cursor = null; + if (!groupsToLoad.isEmpty()) { + StringBuilder whereClause = new StringBuilder(); + for (int i = 0; i < groupsToLoad.size(); ++i) { + whereClause.append("uid = ").append(groupsToLoad.keyAt(i)).append(" AND group_id = ").append(groupsToLoad.valueAt(i)); + if (i + 1 < groupsToLoad.size()) { + whereClause.append(" OR "); + } + } + cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, data, read_state, mid, send_state, date, group_id FROM messages_v2 WHERE %s", whereClause)); + while (cursor.next()) { + long dialogId = cursor.longValue(0); + TLRPC.Dialog dialog = null; + for (int i = 0; i < dialogs.dialogs.size(); ++i) { + TLRPC.Dialog d = dialogs.dialogs.get(i); + if (d != null && d.id == dialogId) { + dialog = d; + break; + } + } + if (dialog == null) { + continue; + } + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + message.readAttachPath(data, getUserConfig().clientUserId); + data.reuse(); + MessageObject.setUnreadFlags(message, cursor.intValue(2)); + message.id = cursor.intValue(3); + message.send_state = cursor.intValue(4); + int date = cursor.intValue(5); + if (date != 0) { + dialog.last_message_date = date; + } + message.dialog_id = dialog.id; + dialogs.messages.add(message); + + addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad, null); + } + } + cursor.dispose(); + } + if (!encryptedToLoad.isEmpty()) { getEncryptedChatsInternal(TextUtils.join(",", encryptedToLoad), encryptedChats, usersToLoad); } @@ -11594,6 +13317,8 @@ public class MessagesStorage extends BaseController { getUsersInternal(TextUtils.join(",", usersToLoad), dialogs.users); } + getMessagesController().getTopicsController().updateTopicsWithDeletedMessages(originalDialogId, messages); + if (!dialogs.dialogs.isEmpty() || !encryptedChats.isEmpty()) { getMessagesController().processDialogsUpdate(dialogs, encryptedChats, true); } @@ -11738,8 +13463,10 @@ public class MessagesStorage extends BaseController { } database.executeFast(String.format(Locale.US, "DELETE FROM messages_v2 WHERE uid = %d AND mid <= %d", -channelId, mid)).stepThis().dispose(); + database.executeFast(String.format(Locale.US, "DELETE FROM messages_topics WHERE uid = %d AND mid <= %d", -channelId, mid)).stepThis().dispose(); database.executeFast(String.format(Locale.US, "DELETE FROM media_v4 WHERE uid = %d AND mid <= %d", -channelId, mid)).stepThis().dispose(); database.executeFast(String.format(Locale.US, "UPDATE media_counts_v2 SET old = 1 WHERE uid = %d", -channelId)).stepThis().dispose(); + database.executeFast(String.format(Locale.US, "UPDATE media_counts_topics SET old = 1 WHERE uid = %d", -channelId)).stepThis().dispose(); updateWidgets(dialogsIds); return dialogsIds; } catch (Exception e) { @@ -11780,19 +13507,35 @@ public class MessagesStorage extends BaseController { } } - private void doneHolesInTable(String table, long did, int max_id) throws Exception { - if (max_id == 0) { - database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d", did)).stepThis().dispose(); + private void doneHolesInTable(String table, long did, int max_id, int thread_message_id) throws Exception { + if (thread_message_id != 0) { + if (max_id == 0) { + database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d AND topic_id = %d", did, thread_message_id)).stepThis().dispose(); + } else { + database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d AND topic_id = %d AND start = 0", did, thread_message_id)).stepThis().dispose(); + } } else { - database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d AND start = 0", did)).stepThis().dispose(); + if (max_id == 0) { + database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d", did)).stepThis().dispose(); + } else { + database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d AND start = 0", did)).stepThis().dispose(); + } } SQLitePreparedStatement state = null; try { - state = database.executeFast("REPLACE INTO " + table + " VALUES(?, ?, ?)"); + if (thread_message_id != 0) { + state = database.executeFast("REPLACE INTO " + table + " VALUES(?, ?, ?, ?)"); + } else { + state = database.executeFast("REPLACE INTO " + table + " VALUES(?, ?, ?)"); + } state.requery(); - state.bindLong(1, did); - state.bindInteger(2, 1); - state.bindInteger(3, 1); + int pointer = 1; + state.bindLong(pointer++, did); + if (thread_message_id != 0) { + state.bindInteger(pointer++, thread_message_id); + } + state.bindInteger(pointer++, 1); + state.bindInteger(pointer++, 1); state.step(); } catch (Exception e) { throw e; @@ -11803,22 +13546,38 @@ public class MessagesStorage extends BaseController { } } - public void doneHolesInMedia(long did, int max_id, int type) throws Exception { + public void doneHolesInMedia(long did, int max_id, int type, int thread_message_id) throws Exception { if (type == -1) { - if (max_id == 0) { - database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d", did)).stepThis().dispose(); + if (thread_message_id != 0) { + if (max_id == 0) { + database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_topics WHERE uid = %d AND topic_id = %d", did, thread_message_id)).stepThis().dispose(); + } else { + database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_topics WHERE uid = %d AND topic_id = %d AND start = 0", did, thread_message_id)).stepThis().dispose(); + } } else { - database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND start = 0", did)).stepThis().dispose(); + if (max_id == 0) { + database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d", did)).stepThis().dispose(); + } else { + database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND start = 0", did)).stepThis().dispose(); + } } SQLitePreparedStatement state = null; try { - state = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); + if (thread_message_id != 0) { + state = database.executeFast("REPLACE INTO media_holes_topics VALUES(?, ?, ?, ?, ?)"); + } else { + state = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); + } for (int a = 0; a < MediaDataController.MEDIA_TYPES_COUNT; a++) { state.requery(); - state.bindLong(1, did); - state.bindInteger(2, a); - state.bindInteger(3, 1); - state.bindInteger(4, 1); + int pointer = 1; + state.bindLong(pointer++, did); + if (thread_message_id != 0) { + state.bindInteger(pointer++, thread_message_id); + } + state.bindInteger(pointer++, a); + state.bindInteger(pointer++, 1); + state.bindInteger(pointer++, 1); state.step(); } } catch (Exception e) { @@ -11830,19 +13589,35 @@ public class MessagesStorage extends BaseController { } } else { - if (max_id == 0) { - database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND type = %d", did, type)).stepThis().dispose(); + if (thread_message_id != 0) { + if (max_id == 0) { + database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_topics WHERE uid = %d AND topic_id = %d AND type = %d", did, thread_message_id, type)).stepThis().dispose(); + } else { + database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_topics WHERE uid = %d AND topic_id = %d AND type = %d AND start = 0", did, thread_message_id, type)).stepThis().dispose(); + } } else { - database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND type = %d AND start = 0", did, type)).stepThis().dispose(); + if (max_id == 0) { + database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND type = %d", did, type)).stepThis().dispose(); + } else { + database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND type = %d AND start = 0", did, type)).stepThis().dispose(); + } } SQLitePreparedStatement state = null; try { - state = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); + if (thread_message_id != 0) { + state = database.executeFast("REPLACE INTO media_holes_topics VALUES(?, ?, ?, ?, ?)"); + } else { + state = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); + } state.requery(); - state.bindLong(1, did); - state.bindInteger(2, type); - state.bindInteger(3, 1); - state.bindInteger(4, 1); + int pointer = 1; + state.bindLong(pointer++, did); + if (thread_message_id != 0) { + state.bindInteger(pointer++, thread_message_id); + } + state.bindInteger(pointer++, type); + state.bindInteger(pointer++, 1); + state.bindInteger(pointer++, 1); state.step(); state.dispose(); } catch (Exception e) { @@ -11873,15 +13648,23 @@ public class MessagesStorage extends BaseController { public int type; } - public void closeHolesInMedia(long did, int minId, int maxId, int type) { + public void closeHolesInMedia(long did, int minId, int maxId, int type, int topicId) { SQLiteCursor cursor = null; SQLitePreparedStatement state = null; try { boolean ok = false; - if (type < 0) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT type, start, end FROM media_holes_v2 WHERE uid = %d AND type >= 0 AND ((end >= %d AND end <= %d) OR (start >= %d AND start <= %d) OR (start >= %d AND end <= %d) OR (start <= %d AND end >= %d))", did, minId, maxId, minId, maxId, minId, maxId, minId, maxId)); + if (topicId != 0) { + if (type < 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT type, start, end FROM media_holes_topics WHERE uid = %d AND topic_id = %d AND type >= 0 AND ((end >= %d AND end <= %d) OR (start >= %d AND start <= %d) OR (start >= %d AND end <= %d) OR (start <= %d AND end >= %d))", did, topicId, minId, maxId, minId, maxId, minId, maxId, minId, maxId)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT type, start, end FROM media_holes_topics WHERE uid = %d AND topic_id = %d AND type = %d AND ((end >= %d AND end <= %d) OR (start >= %d AND start <= %d) OR (start >= %d AND end <= %d) OR (start <= %d AND end >= %d))", did, topicId, type, minId, maxId, minId, maxId, minId, maxId, minId, maxId)); + } } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT type, start, end FROM media_holes_v2 WHERE uid = %d AND type = %d AND ((end >= %d AND end <= %d) OR (start >= %d AND start <= %d) OR (start >= %d AND end <= %d) OR (start <= %d AND end >= %d))", did, type, minId, maxId, minId, maxId, minId, maxId, minId, maxId)); + if (type < 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT type, start, end FROM media_holes_v2 WHERE uid = %d AND type >= 0 AND ((end >= %d AND end <= %d) OR (start >= %d AND start <= %d) OR (start >= %d AND end <= %d) OR (start <= %d AND end >= %d))", did, minId, maxId, minId, maxId, minId, maxId, minId, maxId)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT type, start, end FROM media_holes_v2 WHERE uid = %d AND type = %d AND ((end >= %d AND end <= %d) OR (start >= %d AND start <= %d) OR (start >= %d AND end <= %d) OR (start <= %d AND end >= %d))", did, type, minId, maxId, minId, maxId, minId, maxId, minId, maxId)); + } } ArrayList holes = null; while (cursor.next()) { @@ -11902,11 +13685,19 @@ public class MessagesStorage extends BaseController { for (int a = 0; a < holes.size(); a++) { Hole hole = holes.get(a); if (maxId >= hole.end - 1 && minId <= hole.start + 1) { - database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND type = %d AND start = %d AND end = %d", did, hole.type, hole.start, hole.end)).stepThis().dispose(); + if (topicId != 0) { + database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_topics WHERE uid = %d AND topic_id = %d AND type = %d AND start = %d AND end = %d", did, topicId, hole.type, hole.start, hole.end)).stepThis().dispose(); + } else { + database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND type = %d AND start = %d AND end = %d", did, hole.type, hole.start, hole.end)).stepThis().dispose(); + } } else if (maxId >= hole.end - 1) { if (hole.end != minId) { try { - database.executeFast(String.format(Locale.US, "UPDATE media_holes_v2 SET end = %d WHERE uid = %d AND type = %d AND start = %d AND end = %d", minId, did, hole.type, hole.start, hole.end)).stepThis().dispose(); + if (topicId != 0) { + database.executeFast(String.format(Locale.US, "UPDATE media_holes_topics SET end = %d WHERE uid = %d AND topic_id = %d AND type = %d AND start = %d AND end = %d", minId, did, topicId, hole.type, hole.start, hole.end)).stepThis().dispose(); + } else { + database.executeFast(String.format(Locale.US, "UPDATE media_holes_v2 SET end = %d WHERE uid = %d AND type = %d AND start = %d AND end = %d", minId, did, hole.type, hole.start, hole.end)).stepThis().dispose(); + } } catch (Exception e) { FileLog.e(e, false); } @@ -11914,19 +13705,32 @@ public class MessagesStorage extends BaseController { } else if (minId <= hole.start + 1) { if (hole.start != maxId) { try { - database.executeFast(String.format(Locale.US, "UPDATE media_holes_v2 SET start = %d WHERE uid = %d AND type = %d AND start = %d AND end = %d", maxId, did, hole.type, hole.start, hole.end)).stepThis().dispose(); + if (topicId != 0) { + database.executeFast(String.format(Locale.US, "UPDATE media_holes_topics SET start = %d WHERE uid = %d AND topic_id = %d AND type = %d AND start = %d AND end = %d", maxId, did, topicId, hole.type, hole.start, hole.end)).stepThis().dispose(); + } else { + database.executeFast(String.format(Locale.US, "UPDATE media_holes_v2 SET start = %d WHERE uid = %d AND type = %d AND start = %d AND end = %d", maxId, did, hole.type, hole.start, hole.end)).stepThis().dispose(); + } } catch (Exception e) { FileLog.e(e, false); } } } else { - database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND type = %d AND start = %d AND end = %d", did, hole.type, hole.start, hole.end)).stepThis().dispose(); - state = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); + if (topicId != 0) { + database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_topics WHERE uid = %d AND topic_id = %d AND type = %d AND start = %d AND end = %d", did, topicId, hole.type, hole.start, hole.end)).stepThis().dispose(); + state = database.executeFast("REPLACE INTO media_holes_topics VALUES(?, ?, ?, ?, ?)"); + } else { + database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND type = %d AND start = %d AND end = %d", did, hole.type, hole.start, hole.end)).stepThis().dispose(); + state = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); + } state.requery(); - state.bindLong(1, did); - state.bindInteger(2, hole.type); - state.bindInteger(3, hole.start); - state.bindInteger(4, minId); + int pointer = 1; + state.bindLong(pointer++, did); + if (topicId != 0) { + state.bindInteger(pointer++, topicId); + } + state.bindInteger(pointer++, hole.type); + state.bindInteger(pointer++, hole.start); + state.bindInteger( pointer++, minId); state.step(); state.requery(); state.bindLong(1, did); @@ -11951,12 +13755,16 @@ public class MessagesStorage extends BaseController { } } - private void closeHolesInTable(String table, long did, int minId, int maxId) { + private void closeHolesInTable(String table, long did, int minId, int maxId, int thread_message_id) { SQLiteCursor cursor = null; SQLitePreparedStatement state = null; try { boolean ok = false; - cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM " + table + " WHERE uid = %d AND ((end >= %d AND end <= %d) OR (start >= %d AND start <= %d) OR (start >= %d AND end <= %d) OR (start <= %d AND end >= %d))", did, minId, maxId, minId, maxId, minId, maxId, minId, maxId)); + if (thread_message_id != 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM " + table + " WHERE uid = %d AND topic_id = %d AND ((end >= %d AND end <= %d) OR (start >= %d AND start <= %d) OR (start >= %d AND end <= %d) OR (start <= %d AND end >= %d))", did, thread_message_id, minId, maxId, minId, maxId, minId, maxId, minId, maxId)); + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM " + table + " WHERE uid = %d AND ((end >= %d AND end <= %d) OR (start >= %d AND start <= %d) OR (start >= %d AND end <= %d) OR (start <= %d AND end >= %d))", did, minId, maxId, minId, maxId, minId, maxId, minId, maxId)); + } ArrayList holes = null; while (cursor.next()) { if (holes == null) { @@ -11975,11 +13783,19 @@ public class MessagesStorage extends BaseController { for (int a = 0; a < holes.size(); a++) { Hole hole = holes.get(a); if (maxId >= hole.end - 1 && minId <= hole.start + 1) { - database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d AND start = %d AND end = %d", did, hole.start, hole.end)).stepThis().dispose(); + if (thread_message_id != 0) { + database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d AND topic_id = %d AND start = %d AND end = %d", did, thread_message_id, hole.start, hole.end)).stepThis().dispose(); + } else { + database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d AND start = %d AND end = %d", did, hole.start, hole.end)).stepThis().dispose(); + } } else if (maxId >= hole.end - 1) { if (hole.end != minId) { try { - database.executeFast(String.format(Locale.US, "UPDATE " + table + " SET end = %d WHERE uid = %d AND start = %d AND end = %d", minId, did, hole.start, hole.end)).stepThis().dispose(); + if (thread_message_id != 0) { + database.executeFast(String.format(Locale.US, "UPDATE " + table + " SET end = %d WHERE uid = %d AND topic_id = %d AND start = %d AND end = %d", minId, did, thread_message_id, hole.start, hole.end)).stepThis().dispose(); + } else { + database.executeFast(String.format(Locale.US, "UPDATE " + table + " SET end = %d WHERE uid = %d AND start = %d AND end = %d", minId, did, hole.start, hole.end)).stepThis().dispose(); + } } catch (Exception e) { FileLog.e(e, false); } @@ -11987,23 +13803,40 @@ public class MessagesStorage extends BaseController { } else if (minId <= hole.start + 1) { if (hole.start != maxId) { try { - database.executeFast(String.format(Locale.US, "UPDATE " + table + " SET start = %d WHERE uid = %d AND start = %d AND end = %d", maxId, did, hole.start, hole.end)).stepThis().dispose(); + if (thread_message_id != 0) { + database.executeFast(String.format(Locale.US, "UPDATE " + table + " SET start = %d WHERE uid = %d AND topic_id = %d AND start = %d AND end = %d", maxId, did, thread_message_id, hole.start, hole.end)).stepThis().dispose(); + } else { + database.executeFast(String.format(Locale.US, "UPDATE " + table + " SET start = %d WHERE uid = %d AND start = %d AND end = %d", maxId, did, hole.start, hole.end)).stepThis().dispose(); + } } catch (Exception e) { FileLog.e(e, false); } } } else { - database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d AND start = %d AND end = %d", did, hole.start, hole.end)).stepThis().dispose(); - state = database.executeFast("REPLACE INTO " + table + " VALUES(?, ?, ?)"); + if (thread_message_id != 0) { + database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d AND topic_id = %d AND start = %d AND end = %d", did, thread_message_id, hole.start, hole.end)).stepThis().dispose(); + state = database.executeFast("REPLACE INTO " + table + " VALUES(?, ?, ?, ?)"); + } else { + database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d AND start = %d AND end = %d", did, hole.start, hole.end)).stepThis().dispose(); + state = database.executeFast("REPLACE INTO " + table + " VALUES(?, ?, ?)"); + } + int pointer = 1; state.requery(); - state.bindLong(1, did); - state.bindInteger(2, hole.start); - state.bindInteger(3, minId); + state.bindLong(pointer++, did); + if (thread_message_id != 0) { + state.bindInteger(pointer++, thread_message_id); + } + state.bindInteger(pointer++, hole.start); + state.bindInteger(pointer++, minId); state.step(); state.requery(); - state.bindLong(1, did); - state.bindInteger(2, maxId); - state.bindInteger(3, hole.end); + pointer = 1; + state.bindLong(pointer++, did); + if (thread_message_id != 0) { + state.bindInteger(pointer++, thread_message_id); + } + state.bindInteger(pointer++, maxId); + state.bindInteger(pointer++, hole.end); state.step(); state.dispose(); state = null; @@ -12050,81 +13883,120 @@ public class MessagesStorage extends BaseController { database.beginTransaction(); - state = database.executeFast("REPLACE INTO messages_v2 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, 0, ?)"); - state2 = database.executeFast("REPLACE INTO media_v4 VALUES(?, ?, ?, ?, ?)"); if (message.dialog_id == 0) { MessageObject.getDialogId(message); } fixUnsupportedMedia(message); - state.requery(); NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); - state.bindInteger(1, message.id); - state.bindLong(2, message.dialog_id); - state.bindInteger(3, readState); - state.bindInteger(4, message.send_state); - state.bindInteger(5, message.date); - state.bindByteBuffer(6, data); - state.bindInteger(7, (MessageObject.isOut(message) || message.from_scheduled ? 1 : 0)); - state.bindInteger(8, message.ttl); - if ((message.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { - state.bindInteger(9, message.views); - } else { - state.bindInteger(9, getMessageMediaType(message)); + + for (int i = 0; i < 2; i++) { + boolean isTopic = i == 1; + int topicId = MessageObject.getTopicId(message); + if (isTopic && topicId == 0) { + continue; + } + if (isTopic) { + state = database.executeFast("REPLACE INTO messages_topics VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, 0, ?)"); + } else { + state = database.executeFast("REPLACE INTO messages_v2 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, 0, ?, ?)"); + } + state.requery(); + + int pointer = 1; + state.bindInteger(pointer++, message.id); + state.bindLong(pointer++, message.dialog_id); + if (isTopic) { + state.bindInteger(pointer++, topicId); + } + state.bindInteger(pointer++, readState); + state.bindInteger(pointer++, message.send_state); + state.bindInteger(pointer++, message.date); + state.bindByteBuffer(pointer++, data); + state.bindInteger(pointer++, (MessageObject.isOut(message) || message.from_scheduled ? 1 : 0)); + state.bindInteger(pointer++, message.ttl); + if ((message.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { + state.bindInteger(pointer++, message.views); + } else { + state.bindInteger(pointer++, getMessageMediaType(message)); + } + int flags = 0; + if (message.stickerVerified == 0) { + flags |= 1; + } else if (message.stickerVerified == 2) { + flags |= 2; + } + state.bindInteger(pointer++, flags); + state.bindInteger(pointer++, message.mentioned ? 1 : 0); + state.bindInteger(pointer++, message.forwards); + NativeByteBuffer repliesData = null; + if (message.replies != null) { + repliesData = new NativeByteBuffer(message.replies.getObjectSize()); + message.replies.serializeToStream(repliesData); + state.bindByteBuffer(pointer++, repliesData); + } else { + state.bindNull(pointer++); + } + if (message.reply_to != null) { + state.bindInteger(pointer++, message.reply_to.reply_to_top_id != 0 ? message.reply_to.reply_to_top_id : message.reply_to.reply_to_msg_id); + } else { + state.bindInteger(pointer++, 0); + } + state.bindLong(pointer++, MessageObject.getChannelId(message)); + if (customParams != null) { + state.bindByteBuffer(pointer++, customParams); + } else { + state.bindNull(pointer++); + } + if (!isTopic) { + if ((message.flags & 131072) != 0) { + state.bindLong(pointer++, message.grouped_id); + } else { + state.bindNull(pointer++); + } + } + state.step(); + state.dispose(); + state = null; + if (repliesData != null) { + repliesData.reuse(); + } } - int flags = 0; - if (message.stickerVerified == 0) { - flags |= 1; - } else if (message.stickerVerified == 2) { - flags |= 2; - } - state.bindInteger(10, flags); - state.bindInteger(11, message.mentioned ? 1 : 0); - state.bindInteger(12, message.forwards); - NativeByteBuffer repliesData = null; - if (message.replies != null) { - repliesData = new NativeByteBuffer(message.replies.getObjectSize()); - message.replies.serializeToStream(repliesData); - state.bindByteBuffer(13, repliesData); - } else { - state.bindNull(13); - } - if (message.reply_to != null) { - state.bindInteger(14, message.reply_to.reply_to_top_id != 0 ? message.reply_to.reply_to_top_id : message.reply_to.reply_to_msg_id); - } else { - state.bindInteger(14, 0); - } - state.bindLong(15, MessageObject.getChannelId(message)); - if (customParams != null) { - state.bindByteBuffer(16, customParams); - } else { - state.bindNull(16); - } - state.step(); if (MediaDataController.canAddMessageToMedia(message)) { - state2.requery(); - state2.bindInteger(1, message.id); - state2.bindLong(2, message.dialog_id); - state2.bindInteger(3, message.date); - state2.bindInteger(4, MediaDataController.getMediaType(message)); - state2.bindByteBuffer(5, data); - state2.step(); - } - if (repliesData != null) { - repliesData.reuse(); + for (int i = 0; i < 2; i++) { + boolean isTopic = i == 1; + int topicId = MessageObject.getTopicId(message); + if (isTopic && topicId == 0) { + continue; + } + if (i == 0) { + state2 = database.executeFast("REPLACE INTO media_v4 VALUES(?, ?, ?, ?, ?)"); + } else { + state2 = database.executeFast("REPLACE INTO media_topics VALUES(?, ?, ?, ?, ?, ?)"); + } + int pointer = 1; + state2.requery(); + state2.bindInteger(pointer++, message.id); + state2.bindLong(pointer++, message.dialog_id); + if (i != 0) { + state2.bindLong(pointer++, topicId); + } + state2.bindInteger(pointer++, message.date); + state2.bindInteger(pointer++, MediaDataController.getMediaType(message)); + state2.bindByteBuffer(pointer++, data); + state2.step(); + state2.dispose(); + state2 = null; + } } + if (customParams != null) { customParams.reuse(); } data.reuse(); - state.dispose(); - state = null; - state2.dispose(); - state2 = null; - database.commitTransaction(); if (broadcast) { HashMap userHashMap = new HashMap<>(); @@ -12158,10 +14030,13 @@ public class MessagesStorage extends BaseController { }); } - public void putMessages(TLRPC.messages_Messages messages, long dialogId, int load_type, int max_id, boolean createDialog, boolean scheduled) { + // put messages in data base while load history + public void putMessages(TLRPC.messages_Messages messages, long dialogId, int load_type, int max_id, boolean createDialog, boolean scheduled, int threadMessageId) { storageQueue.postRunnable(() -> { SQLitePreparedStatement state_messages = null; + SQLitePreparedStatement state_messages_topics = null; SQLitePreparedStatement state_media = null; + SQLitePreparedStatement state_media_topics = null; SQLitePreparedStatement state_polls = null; SQLitePreparedStatement state_webpage = null; SQLitePreparedStatement state_tasks = null; @@ -12202,10 +14077,12 @@ public class MessagesStorage extends BaseController { broadcastScheduledMessagesChange(dialogId); } else { int mentionCountUpdate = Integer.MAX_VALUE; + boolean isTopic = threadMessageId != 0; + String holesTableName = isTopic ? "messages_holes_topics" : "messages_holes"; if (messages.messages.isEmpty()) { if (load_type == 0) { - doneHolesInTable("messages_holes", dialogId, max_id); - doneHolesInMedia(dialogId, max_id, -1); + doneHolesInTable(holesTableName, dialogId, max_id, threadMessageId); + doneHolesInMedia(dialogId, max_id, -1, threadMessageId); } return; } @@ -12213,17 +14090,17 @@ public class MessagesStorage extends BaseController { if (load_type == 0) { int minId = messages.messages.get(messages.messages.size() - 1).id; - closeHolesInTable("messages_holes", dialogId, minId, max_id); - closeHolesInMedia(dialogId, minId, max_id, -1); + closeHolesInTable(holesTableName, dialogId, minId, max_id, threadMessageId); + closeHolesInMedia(dialogId, minId, max_id, -1, threadMessageId); } else if (load_type == 1) { int maxId = messages.messages.get(0).id; - closeHolesInTable("messages_holes", dialogId, max_id, maxId); - closeHolesInMedia(dialogId, max_id, maxId, -1); + closeHolesInTable(holesTableName, dialogId, max_id, maxId, threadMessageId); + closeHolesInMedia(dialogId, max_id, maxId, -1, threadMessageId); } else if (load_type == 3 || load_type == 2 || load_type == 4) { int maxId = max_id == 0 && load_type != 4 ? Integer.MAX_VALUE : messages.messages.get(0).id; int minId = messages.messages.get(messages.messages.size() - 1).id; - closeHolesInTable("messages_holes", dialogId, minId, maxId); - closeHolesInMedia(dialogId, minId, maxId, -1); + closeHolesInTable(holesTableName, dialogId, minId, maxId, threadMessageId); + closeHolesInMedia(dialogId, minId, maxId, -1, threadMessageId); } int count = messages.messages.size(); @@ -12235,9 +14112,13 @@ public class MessagesStorage extends BaseController { ArrayList filesToDelete = new ArrayList<>(); ArrayList namesToDelete = new ArrayList<>(); ArrayList> idsToDelete = new ArrayList<>(); + Integer lastMessageId = null; + Long lastMessageGroupId = null; - state_messages = database.executeFast("REPLACE INTO messages_v2 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, 0, ?)"); + state_messages_topics = database.executeFast("REPLACE INTO messages_topics VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, 0, ?)"); + state_messages = database.executeFast("REPLACE INTO messages_v2 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, 0, ?, ?)"); state_media = database.executeFast("REPLACE INTO media_v4 VALUES(?, ?, ?, ?, ?)"); + state_media_topics = database.executeFast("REPLACE INTO media_topics VALUES(?, ?, ?, ?, ?, ?)"); state_polls = null; state_webpage = null; state_tasks = null; @@ -12246,6 +14127,10 @@ public class MessagesStorage extends BaseController { long channelId = 0; for (int a = 0; a < count; a++) { TLRPC.Message message = messages.messages.get(a); + if (lastMessageId == null && message != null || lastMessageId != null && lastMessageId < message.id) { + lastMessageId = message.id; + lastMessageGroupId = (message.flags & 131072) != 0 ? message.grouped_id : null; + } if (channelId == 0) { channelId = message.peer_id.channel_id; @@ -12325,16 +14210,21 @@ public class MessagesStorage extends BaseController { cursor = null; if (exist) { - state3 = database.executeFast("UPDATE dialogs SET date = ?, last_mid = ?, inbox_max = ?, last_mid_i = ?, pts = ?, date_i = ? WHERE did = ?"); + state3 = database.executeFast("UPDATE dialogs SET date = ?, last_mid = ?, last_mid_group = ?, inbox_max = ?, last_mid_i = ?, pts = ?, date_i = ? WHERE did = ?"); state3.bindInteger(1, message.date); state3.bindInteger(2, message.id); - state3.bindInteger(3, message.id); + if (message != null && (message.flags & 131072) != 0) { + state3.bindLong(3, message.grouped_id); + } else { + state3.bindNull(3); + } state3.bindInteger(4, message.id); - state3.bindInteger(5, messages.pts); - state3.bindInteger(6, message.date); - state3.bindLong(7, dialogId); + state3.bindInteger(5, message.id); + state3.bindInteger(6, messages.pts); + state3.bindInteger(7, message.date); + state3.bindLong(8, dialogId); } else { - state3 = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + state3 = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); state3.bindLong(1, dialogId); state3.bindInteger(2, message.date); state3.bindInteger(3, 0); @@ -12350,6 +14240,11 @@ public class MessagesStorage extends BaseController { state3.bindInteger(13, -1); state3.bindNull(14); state3.bindInteger(15, 0); + if (message != null && (message.flags & 131072) != 0) { + state3.bindLong(16, message.grouped_id); + } else { + state3.bindNull(16); + } unknownDialogsIds.put(dialogId, true); } state3.step(); @@ -12358,74 +14253,115 @@ public class MessagesStorage extends BaseController { } fixUnsupportedMedia(message); - state_messages.requery(); NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); - state_messages.bindInteger(1, message.id); - state_messages.bindLong(2, dialogId); - state_messages.bindInteger(3, MessageObject.getUnreadFlags(message)); - state_messages.bindInteger(4, message.send_state); - state_messages.bindInteger(5, message.date); - state_messages.bindByteBuffer(6, data); - state_messages.bindInteger(7, (MessageObject.isOut(message) || message.from_scheduled ? 1 : 0)); - state_messages.bindInteger(8, message.ttl); - if ((message.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { - state_messages.bindInteger(9, message.views); - } else { - state_messages.bindInteger(9, getMessageMediaType(message)); - } - int flags = 0; - if (message.stickerVerified == 0) { - flags |= 1; - } else if (message.stickerVerified == 2) { - flags |= 2; - } - state_messages.bindInteger(10, flags); - state_messages.bindInteger(11, message.mentioned ? 1 : 0); - state_messages.bindInteger(12, message.forwards); - NativeByteBuffer repliesData = null; - if (message.replies != null) { - repliesData = new NativeByteBuffer(message.replies.getObjectSize()); - message.replies.serializeToStream(repliesData); - state_messages.bindByteBuffer(13, repliesData); - } else { - state_messages.bindNull(13); - } - if (message.reply_to != null) { - state_messages.bindInteger(14, message.reply_to.reply_to_top_id != 0 ? message.reply_to.reply_to_top_id : message.reply_to.reply_to_msg_id); - } else { - state_messages.bindInteger(14, 0); - } - state_messages.bindLong(15, MessageObject.getChannelId(message)); - NativeByteBuffer customParams = MessageCustomParamsHelper.writeLocalParams(message); - if (customParams == null) { - state_messages.bindNull(16); - } else { - state_messages.bindByteBuffer(16, customParams); - } - state_messages.step(); - if (MediaDataController.canAddMessageToMedia(message)) { - state_media.requery(); - state_media.bindInteger(1, message.id); - state_media.bindLong(2, dialogId); - state_media.bindInteger(3, message.date); - state_media.bindInteger(4, MediaDataController.getMediaType(message)); - state_media.bindByteBuffer(5, data); - state_media.step(); - } else if (message instanceof TLRPC.TL_messageService && message.action instanceof TLRPC.TL_messageActionHistoryClear) { - try { - database.executeFast(String.format(Locale.US, "DELETE FROM media_v4 WHERE mid = %d AND uid = %d", message.id, dialogId)).stepThis().dispose(); - database.executeFast("DELETE FROM media_counts_v2 WHERE uid = " + dialogId).stepThis().dispose(); - } catch (Exception e2) { - FileLog.e(e2); + for (int i = 0; i < 2; i++) { + boolean isTopicMessage = i == 1; + int topicId = threadMessageId; + if (isTopicMessage && topicId == 0) { + topicId = MessageObject.getTopicId(message); + } + if (isTopicMessage && topicId == 0) { + continue; + } + + SQLitePreparedStatement currentState = isTopicMessage ? state_messages_topics : state_messages; + currentState.requery(); + + int pointer = 1; + currentState.bindInteger(pointer++, message.id); + currentState.bindLong(pointer++, dialogId); + if (isTopicMessage) { + currentState.bindInteger(pointer++, topicId); + } + currentState.bindInteger(pointer++, MessageObject.getUnreadFlags(message)); + currentState.bindInteger(pointer++, message.send_state); + currentState.bindInteger(pointer++, message.date); + currentState.bindByteBuffer(pointer++, data); + currentState.bindInteger(pointer++, (MessageObject.isOut(message) || message.from_scheduled ? 1 : 0)); + currentState.bindInteger(pointer++, message.ttl); + if ((message.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { + currentState.bindInteger(pointer++, message.views); + } else { + currentState.bindInteger(pointer++, getMessageMediaType(message)); + } + int flags = 0; + if (message.stickerVerified == 0) { + flags |= 1; + } else if (message.stickerVerified == 2) { + flags |= 2; + } + currentState.bindInteger(pointer++, flags); + currentState.bindInteger(pointer++, message.mentioned ? 1 : 0); + currentState.bindInteger(pointer++, message.forwards); + NativeByteBuffer repliesData = null; + if (message.replies != null) { + repliesData = new NativeByteBuffer(message.replies.getObjectSize()); + message.replies.serializeToStream(repliesData); + currentState.bindByteBuffer(pointer++, repliesData); + } else { + currentState.bindNull(pointer++); + } + if (message.reply_to != null) { + currentState.bindInteger(pointer++, message.reply_to.reply_to_top_id != 0 ? message.reply_to.reply_to_top_id : message.reply_to.reply_to_msg_id); + } else { + currentState.bindInteger(pointer++, 0); + } + currentState.bindLong(pointer++, MessageObject.getChannelId(message)); + NativeByteBuffer customParams = MessageCustomParamsHelper.writeLocalParams(message); + if (customParams == null) { + currentState.bindNull(pointer++); + } else { + currentState.bindByteBuffer(pointer++, customParams); + } + if (!isTopicMessage) { + if ((message.flags & 131072) != 0) { + currentState.bindLong(pointer++, message.grouped_id); + } else { + currentState.bindNull(pointer++); + } + } + currentState.step(); + + if (repliesData != null) { + repliesData.reuse(); + } + if (customParams != null) { + customParams.reuse(); } } - if (repliesData != null) { - repliesData.reuse(); + + if (threadMessageId == 0 || load_type == -2) { + if (MediaDataController.canAddMessageToMedia(message)) { + state_media.requery(); + state_media.bindInteger(1, message.id); + state_media.bindLong(2, dialogId); + state_media.bindInteger(3, message.date); + state_media.bindInteger(4, MediaDataController.getMediaType(message)); + state_media.bindByteBuffer(5, data); + state_media.step(); + } else if (message instanceof TLRPC.TL_messageService && message.action instanceof TLRPC.TL_messageActionHistoryClear) { + try { + database.executeFast(String.format(Locale.US, "DELETE FROM media_v4 WHERE mid = %d AND uid = %d", message.id, dialogId)).stepThis().dispose(); + database.executeFast("DELETE FROM media_counts_v2 WHERE uid = " + dialogId).stepThis().dispose(); + } catch (Exception e2) { + FileLog.e(e2); + } + } } - if (customParams != null) { - customParams.reuse(); + int topicId = MessageObject.getTopicId(message); + if (threadMessageId != 0 || (load_type == -2 && topicId != 0)) { + if (MediaDataController.canAddMessageToMedia(message)) { + state_media_topics.requery(); + state_media_topics.bindInteger(1, message.id); + state_media_topics.bindLong(2, dialogId); + state_media_topics.bindLong(3, threadMessageId != 0 ? threadMessageId : topicId); + state_media_topics.bindInteger(4, message.date); + state_media_topics.bindInteger(5, MediaDataController.getMediaType(message)); + state_media_topics.bindByteBuffer(6, data); + state_media_topics.step(); + } } data.reuse(); @@ -12471,6 +14407,8 @@ public class MessagesStorage extends BaseController { } state_messages.dispose(); state_messages = null; + state_messages_topics.dispose(); + state_messages_topics = null; state_media.dispose(); state_media = null; if (state_webpage != null) { @@ -12502,9 +14440,15 @@ public class MessagesStorage extends BaseController { getMessagesController().processDialogsUpdateRead(null, sparseArray); } + boolean updateDialogs = false; + if (lastMessageId != null) { + database.executeFast(String.format(Locale.US, "UPDATE dialogs SET last_mid_group = %s WHERE did = %d AND last_mid <= %d", lastMessageGroupId == null ? "NULL" : lastMessageGroupId + "", dialogId, lastMessageId)).stepThis().dispose(); + updateDialogs = true; + } + database.commitTransaction(); - if (createDialog) { + if (createDialog || updateDialogs) { updateDialogsWithDeletedMessages(dialogId, channelId, new ArrayList<>(), null, false); } } @@ -12514,6 +14458,9 @@ public class MessagesStorage extends BaseController { if (database != null) { database.commitTransaction(); } + if (state_messages_topics != null) { + state_messages_topics.dispose(); + } if (state_messages != null) { state_messages.dispose(); } @@ -12730,7 +14677,8 @@ public class MessagesStorage extends BaseController { cnt = 100; } - cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, s.flags, m.date, d.pts, d.inbox_max, d.outbox_max, m.replydata, d.pinned, d.unread_count_i, d.flags, d.folder_id, d.data, d.unread_reactions FROM dialogs as d LEFT JOIN messages_v2 as m ON d.last_mid = m.mid AND d.did = m.uid LEFT JOIN dialog_settings as s ON d.did = s.did WHERE d.folder_id = %d ORDER BY d.pinned DESC, d.date DESC LIMIT %d,%d", fid, off, cnt)); + ArrayList> dialogsToLoadGroupMessages = new ArrayList<>(); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, s.flags, m.date, d.pts, d.inbox_max, d.outbox_max, m.replydata, d.pinned, d.unread_count_i, d.flags, d.folder_id, d.data, d.unread_reactions, d.last_mid_group FROM dialogs as d LEFT JOIN messages_v2 as m ON d.last_mid = m.mid AND d.did = m.uid AND d.last_mid_group IS NULL LEFT JOIN dialog_settings as s ON d.did = s.did WHERE d.folder_id = %d ORDER BY d.pinned DESC, d.date DESC LIMIT %d,%d", fid, off, cnt)); while (cursor.next()) { long dialogId = cursor.longValue(0); TLRPC.Dialog dialog; @@ -12777,6 +14725,9 @@ public class MessagesStorage extends BaseController { } dialog.folder_id = cursor.intValue(17); dialog.unread_reactions_count = cursor.intValue(19); + if (!cursor.isNull(20)) { + dialogsToLoadGroupMessages.add(new Pair<>(dialogId, cursor.longValue(20))); + } dialogs.dialogs.add(dialog); if (draftsDialogIds != null) { @@ -12852,6 +14803,80 @@ public class MessagesStorage extends BaseController { } cursor.dispose(); cursor = null; + + if (!dialogsToLoadGroupMessages.isEmpty()) { + StringBuilder whereClause = new StringBuilder(); + for (int i = 0; i < dialogsToLoadGroupMessages.size(); ++i) { + Pair pair = dialogsToLoadGroupMessages.get(i); + whereClause.append("uid = ").append(pair.first).append(" AND group_id = ").append(pair.second); + if (i + 1 < dialogsToLoadGroupMessages.size()) { + whereClause.append(" OR "); + } + } + cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, data, read_state, mid, send_state, date, replydata, group_id FROM messages_v2 WHERE %s ORDER BY date DESC", whereClause)); + int COUNT = 0; + while (cursor.next()) { + COUNT++; + long did = cursor.longValue(0); + NativeByteBuffer data = cursor.byteBufferValue(1); + TLRPC.Dialog dialog = null; + for (int i = 0; i < dialogs.dialogs.size(); ++i) { + TLRPC.Dialog d = dialogs.dialogs.get(i); + if (d != null && d.id == did) { + dialog = d; + break; + } + } + if (dialog == null) { + continue; + } + if (data != null) { + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + if (message != null) { + message.readAttachPath(data, getUserConfig().clientUserId); + data.reuse(); + MessageObject.setUnreadFlags(message, cursor.intValue(2)); + message.id = cursor.intValue(3); + int date = cursor.intValue(5); + if (date != 0) { + dialog.last_message_date = date; + } + message.send_state = cursor.intValue(4); + message.dialog_id = did; + dialogs.messages.add(message); + + addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad, null); + + try { + if (message.reply_to != null && message.reply_to.reply_to_msg_id != 0 && ( + message.action instanceof TLRPC.TL_messageActionPinMessage || + message.action instanceof TLRPC.TL_messageActionPaymentSent || + message.action instanceof TLRPC.TL_messageActionGameScore)) { + if (!cursor.isNull(7)) { + NativeByteBuffer data2 = cursor.byteBufferValue(7); + if (data2 != null) { + message.replyMessage = TLRPC.Message.TLdeserialize(data2, data2.readInt32(false), false); + message.replyMessage.readAttachPath(data2, getUserConfig().clientUserId); + data2.reuse(); + if (message.replyMessage != null) { + addUsersAndChatsFromMessage(message.replyMessage, usersToLoad, chatsToLoad, null); + } + } + } + if (message.replyMessage == null) { + addReplyMessages(message, replyMessageOwners, dialogReplyMessagesIds); + } + } + } catch (Exception e) { + FileLog.e(e); + } + } else { + data.reuse(); + } + } + } + cursor.dispose(); + } } loadReplyMessages(replyMessageOwners, dialogReplyMessagesIds, usersToLoad, chatsToLoad, false); @@ -12960,12 +14985,15 @@ public class MessagesStorage extends BaseController { LongSparseArray new_dialogMessage = new LongSparseArray<>(dialogs.messages.size()); for (int a = 0; a < dialogs.messages.size(); a++) { TLRPC.Message message = dialogs.messages.get(a); - new_dialogMessage.put(MessageObject.getDialogId(message), message); + long did = MessageObject.getDialogId(message); + if (!new_dialogMessage.containsKey(did) || new_dialogMessage.get(did) != null && new_dialogMessage.get(did).date < message.date) { + new_dialogMessage.put(did, message); + } } if (!dialogs.dialogs.isEmpty()) { - state_messages = database.executeFast("REPLACE INTO messages_v2 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, 0, NULL)"); - state_dialogs = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + state_messages = database.executeFast("REPLACE INTO messages_v2 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, 0, NULL, ?)"); + state_dialogs = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); state_media = database.executeFast("REPLACE INTO media_v4 VALUES(?, ?, ?, ?, ?)"); state_settings = database.executeFast("REPLACE INTO dialog_settings VALUES(?, ?)"); state_holes = database.executeFast("REPLACE INTO messages_holes VALUES(?, ?, ?)"); @@ -13057,6 +15085,11 @@ public class MessagesStorage extends BaseController { state_messages.bindInteger(14, 0); } state_messages.bindLong(15, MessageObject.getChannelId(message)); + if ((message.flags & 131072) != 0) { + state_messages.bindLong(16, message.grouped_id); + } else { + state_messages.bindNull(16); + } state_messages.step(); if (MediaDataController.canAddMessageToMedia(message)) { @@ -13099,8 +15132,8 @@ public class MessagesStorage extends BaseController { } if (exists) { - closeHolesInTable("messages_holes", dialog.id, message.id, message.id); - closeHolesInMedia(dialog.id, message.id, message.id, -1); + closeHolesInTable("messages_holes", dialog.id, message.id, message.id, 0); + closeHolesInMedia(dialog.id, message.id, message.id, -1, 0); } else { createFirstHoles(dialog.id, state_holes, state_media_holes, message.id); } @@ -13135,6 +15168,11 @@ public class MessagesStorage extends BaseController { state_dialogs.bindNull(14); } state_dialogs.bindInteger(15, dialog.unread_reactions_count); + if (message != null && (message.flags & 131072) != 0) { + state_dialogs.bindLong(16, message.grouped_id); + } else { + state_dialogs.bindNull(16); + } state_dialogs.step(); if (data != null) { data.reuse(); @@ -13413,7 +15451,7 @@ public class MessagesStorage extends BaseController { }); } - private void resetAllUnreadCounters(boolean muted) { + public void resetAllUnreadCounters(boolean muted) { for (int a = 0, N = dialogFilters.size(); a < N; a++) { MessagesController.DialogFilter filter = dialogFilters.get(a); if (muted) { @@ -14016,83 +16054,143 @@ public class MessagesStorage extends BaseController { return messageIds; } - public void updateUnreadReactionsCount(long dialogId, int count) { + public void updateUnreadReactionsCount(long dialogId, int topicId, int count) { + updateUnreadReactionsCount(dialogId, topicId, count, false); + } + + public void updateUnreadReactionsCount(long dialogId, int topicId, int count, boolean increment) { storageQueue.postRunnable(() -> { SQLitePreparedStatement state = null; - try { - state = database.executeFast("UPDATE dialogs SET unread_reactions = ? WHERE did = ?"); - state.bindInteger(1, Math.max(count, 0)); - state.bindLong(2, dialogId); - state.step(); - state.dispose(); - state = null; - - if (count == 0) { - state = database.executeFast("UPDATE reaction_mentions SET state = 0 WHERE dialog_id = ?"); - state.bindLong(1, dialogId); + if (topicId != 0) { + try { + int currentReactions = 0; + if (increment) { + SQLiteCursor cursor = database.queryFinalized(String.format("SELECT unread_reactions FROM topics WHERE did = %d AND topic_id = %d", dialogId, topicId)); + if (cursor.next()) { + currentReactions = cursor.intValue(0); + } + cursor.dispose(); + } + state = database.executeFast("UPDATE topics SET unread_reactions = ? WHERE did = ? AND topic_id = ?"); + state.bindInteger(1, Math.max(currentReactions + count, 0)); + state.bindLong(2, dialogId); + state.bindInteger(3, topicId); state.step(); state.dispose(); state = null; + + if (count == 0) { + state = database.executeFast("UPDATE reaction_mentions_topics SET state = 0 WHERE dialog_id = ? AND topic_id = ? "); + state.bindLong(1, dialogId); + state.bindInteger(2, topicId); + state.step(); + state.dispose(); + state = null; + } + } catch (SQLiteException e) { + e.printStackTrace(); + } finally { + if (state != null) { + state.dispose(); + } } - } catch (SQLiteException e) { - e.printStackTrace(); - } finally { - if (state != null) { + } else { + try { + state = database.executeFast("UPDATE dialogs SET unread_reactions = ? WHERE did = ?"); + state.bindInteger(1, Math.max(count, 0)); + state.bindLong(2, dialogId); + state.step(); state.dispose(); + state = null; + + if (count == 0) { + state = database.executeFast("UPDATE reaction_mentions SET state = 0 WHERE dialog_id = ?"); + state.bindLong(1, dialogId); + state.step(); + state.dispose(); + state = null; + } + } catch (SQLiteException e) { + e.printStackTrace(); + } finally { + if (state != null) { + state.dispose(); + } } } }); } - public void markMessageReactionsAsRead(long dialogId, int messageId, boolean usequeue) { + public void markMessageReactionsAsRead(long dialogId, int topicId, int messageId, boolean usequeue) { if (usequeue) { getStorageQueue().postRunnable(() -> { - markMessageReactionsAsReadInternal(dialogId, messageId); + markMessageReactionsAsReadInternal(dialogId, topicId, messageId); }); } else { - markMessageReactionsAsReadInternal(dialogId, messageId); + markMessageReactionsAsReadInternal(dialogId, topicId, messageId); } } - public void markMessageReactionsAsReadInternal(long dialogId, int messageId) { + public void markMessageReactionsAsReadInternal(long dialogId, int topicId, int messageId) { SQLitePreparedStatement state = null; SQLiteCursor cursor = null; try { - state = getMessagesStorage().getDatabase().executeFast("UPDATE reaction_mentions SET state = 0 WHERE message_id = ? AND dialog_id = ?"); - state.bindInteger(1, messageId); - state.bindLong(2, dialogId); - state.step(); - state.dispose(); - state = null; - - cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM messages_v2 WHERE uid = %d AND mid = %d", dialogId, messageId)); - TLRPC.Message message = null; - if (cursor.next()) { - NativeByteBuffer data = cursor.byteBufferValue(0); - if (data != null) { - message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, getUserConfig().clientUserId); - data.reuse(); - if (message.reactions != null && message.reactions.recent_reactions != null) { - for (int i = 0; i < message.reactions.recent_reactions.size(); i++) { - message.reactions.recent_reactions.get(i).unread = false; + for (int k = 0; k < 2; k++) { + boolean isTopic = k == 1; + if (isTopic && topicId == 0) { + continue; + } + if (!isTopic) { + state = getMessagesStorage().getDatabase().executeFast("UPDATE reaction_mentions SET state = 0 WHERE message_id = ? AND dialog_id = ?"); + state.bindInteger(1, messageId); + state.bindLong(2, dialogId); + state.step(); + state.dispose(); + state = null; + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM messages_v2 WHERE uid = %d AND mid = %d", dialogId, messageId)); + } else { + state = getMessagesStorage().getDatabase().executeFast("UPDATE reaction_mentions_topics SET state = 0 WHERE message_id = ? AND dialog_id = ? AND topic_id = ? "); + state.bindInteger(1, messageId); + state.bindLong(2, dialogId); + state.bindInteger(3, topicId); + state.step(); + state.dispose(); + state = null; + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM messages_topics WHERE uid = %d AND mid = %d", dialogId, messageId)); + } + TLRPC.Message message = null; + if (cursor.next()) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { + message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + message.readAttachPath(data, getUserConfig().clientUserId); + data.reuse(); + if (message.reactions != null && message.reactions.recent_reactions != null) { + for (int i = 0; i < message.reactions.recent_reactions.size(); i++) { + message.reactions.recent_reactions.get(i).unread = false; + } } } } - } - cursor.dispose(); - cursor = null; - if (message != null) { - state = getMessagesStorage().getDatabase().executeFast(String.format(Locale.US, "UPDATE messages_v2 SET data = ? WHERE uid = %d AND mid = %d", dialogId, messageId)); - try { - NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); - message.serializeToStream(data); - state.bindByteBuffer(1, data); - state.step(); - state.dispose(); - data.reuse(); - } catch (Exception e) { - FileLog.e(e); + cursor.dispose(); + cursor = null; + + if (message != null) { + if (!isTopic) { + state = getMessagesStorage().getDatabase().executeFast(String.format(Locale.US, "UPDATE messages_v2 SET data = ? WHERE uid = %d AND mid = %d", dialogId, messageId)); + } else { + state = getMessagesStorage().getDatabase().executeFast(String.format(Locale.US, "UPDATE messages_topics SET data = ? WHERE uid = %d AND mid = %d", dialogId, messageId)); + } + try { + NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); + message.serializeToStream(data); + state.bindByteBuffer(1, data); + state.step(); + state.dispose(); + data.reuse(); + } catch (Exception e) { + FileLog.e(e); + } } } } catch (SQLiteException e) { @@ -14108,7 +16206,7 @@ public class MessagesStorage extends BaseController { } - public void updateDialogUnreadReactions(long dialogId, int newUnreadCount, boolean increment) { + public void updateDialogUnreadReactions(long dialogId, int topicId, int newUnreadCount, boolean increment) { storageQueue.postRunnable(() -> { SQLiteCursor cursor = null; SQLitePreparedStatement state = null; @@ -14129,6 +16227,27 @@ public class MessagesStorage extends BaseController { state.step(); state.dispose(); state = null; + + if (topicId != 0) { + oldUnreadRactions = 0; + if (increment) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT unread_reactions FROM topics WHERE did = %d AND topic_id = %d", dialogId, topicId)); + if (cursor.next()) { + oldUnreadRactions = Math.max(0, cursor.intValue(0)); + } + cursor.dispose(); + cursor = null; + } + + oldUnreadRactions += newUnreadCount; + state = getMessagesStorage().getDatabase().executeFast("UPDATE topics SET unread_reactions = ? WHERE did = ? AND topic_id = ?"); + state.bindInteger(1, oldUnreadRactions); + state.bindLong(2, dialogId); + state.bindInteger(3, topicId); + state.step(); + state.dispose(); + state = null; + } } catch (SQLiteException e) { e.printStackTrace(); } finally { @@ -14142,6 +16261,16 @@ public class MessagesStorage extends BaseController { }); } + private boolean isForum(long dialogId) { + int v = dialogIsForum.get(dialogId, -1); + if (v == -1) { + TLRPC.Chat chat = getChat(-dialogId); + v = chat != null && chat.forum ? 1 : 0; + dialogIsForum.put(dialogId, v); + } + return v == 1; + } + public interface IntCallback { void run(int param); } @@ -14157,4 +16286,28 @@ public class MessagesStorage extends BaseController { public interface BooleanCallback { void run(boolean param); } + + public static class TopicKey { + public long dialogId; + public int topicId; + + public static TopicKey of(long dialogId, int topicId) { + TopicKey topicKey = new TopicKey(); + topicKey.dialogId = dialogId; + topicKey.topicId = topicId; + return topicKey; + } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TopicKey topicKey = (TopicKey) o; + return dialogId == topicKey.dialogId && topicId == topicKey.topicId; + } + + @Override + public int hashCode() { + return Objects.hash(dialogId, topicId); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java index dcb41e27d..18f4910e8 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java @@ -9,7 +9,6 @@ package org.telegram.messenger; import android.os.SystemClock; -import android.util.Log; import android.util.SparseArray; import androidx.annotation.UiThread; @@ -262,6 +261,8 @@ public class NotificationCenter { public static final int userEmojiStatusUpdated = totalEvents++; public static final int requestPermissions = totalEvents++; public static final int permissionsGranted = totalEvents++; + public static int topicsDidLoaded = totalEvents++; + public static int chatSwithcedToForum = totalEvents++; private SparseArray> observers = new SparseArray<>(); private SparseArray> removeAfterBroadcast = new SparseArray<>(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java index d286c3c2a..dfdd72a49 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java @@ -69,6 +69,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; public class NotificationsController extends BaseController { @@ -91,6 +92,7 @@ public class NotificationsController extends BaseController { public ArrayList popupReplyMessages = new ArrayList<>(); private HashSet openedInBubbleDialogs = new HashSet<>(); private long openedDialogId = 0; + private int openedTopicId = 0; private int lastButtonId = 5000; private int total_unread_count = 0; private int personalCount = 0; @@ -132,6 +134,8 @@ public class NotificationsController extends BaseController { public static final int SETTING_SOUND_ON = 0; public static final int SETTING_SOUND_OFF = 1; + NotificationsSettingsFacade dialogsNotificationsFacade; + static { if (Build.VERSION.SDK_INT >= 26 && ApplicationLoader.applicationContext != null) { notificationManager = NotificationManagerCompat.from(ApplicationLoader.applicationContext); @@ -210,6 +214,8 @@ public class NotificationsController extends BaseController { FileLog.e(e); } }; + + dialogsNotificationsFacade = new NotificationsSettingsFacade(currentAccount); } public static void checkOtherNotificationsChannel() { @@ -247,37 +253,49 @@ public class NotificationsController extends BaseController { } } - public void muteUntil(long did, int selectedTimeInSeconds) { + public static String getSharedPrefKey(long dialog_id, int topicId) { + String str = Long.toString(dialog_id); + if (topicId != 0) { + str += "_" + topicId; + } + return str; + } + + public void muteUntil(long did, int topicId, int selectedTimeInSeconds) { if (did != 0) { SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); SharedPreferences.Editor editor = preferences.edit(); long flags; + boolean override = topicId != 0; boolean defaultEnabled = NotificationsController.getInstance(currentAccount).isGlobalNotificationsEnabled(did); + String sharedPrefKey = NotificationsController.getSharedPrefKey(did, topicId); if (selectedTimeInSeconds == Integer.MAX_VALUE) { - if (!defaultEnabled) { - editor.remove("notify2_" + did); + if (!defaultEnabled && !override) { + editor.remove("notify2_" + sharedPrefKey); flags = 0; } else { - editor.putInt("notify2_" + did, 2); + editor.putInt("notify2_" + sharedPrefKey, 2); flags = 1; } } else { - editor.putInt("notify2_" + did, 3); - editor.putInt("notifyuntil_" + did, getConnectionsManager().getCurrentTime() + selectedTimeInSeconds); + editor.putInt("notify2_" + sharedPrefKey, 3); + editor.putInt("notifyuntil_" + sharedPrefKey, getConnectionsManager().getCurrentTime() + selectedTimeInSeconds); flags = ((long) selectedTimeInSeconds << 32) | 1; } - NotificationsController.getInstance(currentAccount).removeNotificationsForDialog(did); - MessagesStorage.getInstance(currentAccount).setDialogFlags(did, flags); - editor.commit(); - TLRPC.Dialog dialog = MessagesController.getInstance(currentAccount).dialogs_dict.get(did); - if (dialog != null) { - dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); - if (selectedTimeInSeconds != Integer.MAX_VALUE || defaultEnabled) { - dialog.notify_settings.mute_until = selectedTimeInSeconds; + editor.apply(); + if (topicId == 0) { + NotificationsController.getInstance(currentAccount).removeNotificationsForDialog(did); + MessagesStorage.getInstance(currentAccount).setDialogFlags(did, flags); + TLRPC.Dialog dialog = MessagesController.getInstance(currentAccount).dialogs_dict.get(did); + if (dialog != null) { + dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + if (selectedTimeInSeconds != Integer.MAX_VALUE || defaultEnabled) { + dialog.notify_settings.mute_until = selectedTimeInSeconds; + } } } - NotificationsController.getInstance(currentAccount).updateServerNotificationsSettings(did); + NotificationsController.getInstance(currentAccount).updateServerNotificationsSettings(did, topicId); } } @@ -287,6 +305,7 @@ public class NotificationsController extends BaseController { channelGroupsCreated = false; notificationsQueue.postRunnable(() -> { openedDialogId = 0; + openedTopicId = 0; total_unread_count = 0; personalCount = 0; pushMessages.clear(); @@ -348,8 +367,11 @@ public class NotificationsController extends BaseController { inChatSoundEnabled = value; } - public void setOpenedDialogId(long dialog_id) { - notificationsQueue.postRunnable(() -> openedDialogId = dialog_id); + public void setOpenedDialogId(long dialog_id, int topicId) { + notificationsQueue.postRunnable(() -> { + openedDialogId = dialog_id; + openedTopicId = topicId; + }); } public void setOpenedInBubble(long dialogId, boolean opened) { @@ -765,7 +787,8 @@ public class NotificationsController extends BaseController { MessageObject messageObject = messageObjects.get(a); if (messageObject.messageOwner != null && (messageObject.isImportedForward() || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionSetMessagesTTL || - messageObject.messageOwner.silent && (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionContactSignUp || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionUserJoined))) { + messageObject.messageOwner.silent && (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionContactSignUp || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionUserJoined)) || + MessageObject.isTopicActionMessage(messageObject)) { continue; } int mid = messageObject.getId(); @@ -820,6 +843,7 @@ public class NotificationsController extends BaseController { } long originalDialogId = dialogId; + int topicId = MessageObject.getTopicId(messageObject.messageOwner); if (dialogId == openedDialogId && ApplicationLoader.isScreenOn) { if (!isFcm) { playInChatSound(); @@ -840,10 +864,10 @@ public class NotificationsController extends BaseController { boolean isChat = DialogObject.isChatDialog(dialogId); int index = settingsCache.indexOfKey(dialogId); boolean value; - if (index >= 0) { + if (index >= 0 && topicId == 0) { value = settingsCache.valueAt(index); } else { - int notifyOverride = getNotifyOverride(preferences, dialogId); + int notifyOverride = getNotifyOverride(preferences, dialogId, topicId); if (notifyOverride == -1) { value = isGlobalNotificationsEnabled(dialogId, isChannel); /*if (BuildVars.DEBUG_PRIVATE_VERSION && BuildVars.LOGS_ENABLED) { @@ -882,7 +906,7 @@ public class NotificationsController extends BaseController { if (messageObject.isReactionPush) { SparseBooleanArray sparseBooleanArray = new SparseBooleanArray(); sparseBooleanArray.put(mid, true); - getMessagesController().checkUnreadReactions(dialogId, sparseBooleanArray); + getMessagesController().checkUnreadReactions(dialogId, MessageObject.getTopicId(messageObject.messageOwner), sparseBooleanArray); } } @@ -914,6 +938,7 @@ public class NotificationsController extends BaseController { } else if (added) { MessageObject messageObject = messageObjects.get(0); long dialog_id = messageObject.getDialogId(); + int topicId = MessageObject.getTopicId(messageObject.messageOwner); Boolean isChannel; if (messageObject.isFcmMessage()) { isChannel = messageObject.localChannel; @@ -922,7 +947,7 @@ public class NotificationsController extends BaseController { } int old_unread_count = total_unread_count; - int notifyOverride = getNotifyOverride(preferences, dialog_id); + int notifyOverride = getNotifyOverride(preferences, dialog_id, topicId); boolean canAddValue; if (notifyOverride == -1) { canAddValue = isGlobalNotificationsEnabled(dialog_id, isChannel); @@ -982,20 +1007,28 @@ public class NotificationsController extends BaseController { long dialogId = dialogsToUpdate.keyAt(b); Integer currentCount = pushDialogs.get(dialogId); int newCount = dialogsToUpdate.get(dialogId); - + boolean forum = false; if (DialogObject.isChatDialog(dialogId)) { TLRPC.Chat chat = getMessagesController().getChat(-dialogId); if (chat == null || chat.min || ChatObject.isNotInChat(chat)) { newCount = 0; } + if (chat != null) { + forum = chat.forum; + } } - int notifyOverride = getNotifyOverride(preferences, dialogId); + boolean canAddValue; - if (notifyOverride == -1) { - canAddValue = isGlobalNotificationsEnabled(dialogId); + if (!forum) { + int notifyOverride = getNotifyOverride(preferences, dialogId, 0); + if (notifyOverride == -1) { + canAddValue = isGlobalNotificationsEnabled(dialogId); + } else { + canAddValue = notifyOverride != 2; + } } else { - canAddValue = notifyOverride != 2; + canAddValue = true; } if (notifyCheck && !canAddValue) { @@ -1123,15 +1156,16 @@ public class NotificationsController extends BaseController { } long dialog_id = messageObject.getDialogId(); long original_dialog_id = dialog_id; + int topicId = MessageObject.getTopicId(messageObject.messageOwner); if (messageObject.messageOwner.mentioned) { dialog_id = messageObject.getFromChatId(); } int index = settingsCache.indexOfKey(dialog_id); boolean value; - if (index >= 0) { + if (index >= 0 && topicId == 0) { value = settingsCache.valueAt(index); } else { - int notifyOverride = getNotifyOverride(preferences, dialog_id); + int notifyOverride = getNotifyOverride(preferences, dialog_id, topicId); if (notifyOverride == -1) { value = isGlobalNotificationsEnabled(dialog_id); } else { @@ -1161,7 +1195,7 @@ public class NotificationsController extends BaseController { if (index >= 0) { value = settingsCache.valueAt(index); } else { - int notifyOverride = getNotifyOverride(preferences, dialog_id); + int notifyOverride = getNotifyOverride(preferences, dialog_id, 0); if (notifyOverride == -1) { value = isGlobalNotificationsEnabled(dialog_id); } else { @@ -1190,16 +1224,17 @@ public class NotificationsController extends BaseController { } long dialogId = messageObject.getDialogId(); long originalDialogId = dialogId; + int topicId = MessageObject.getTopicId(messageObject.messageOwner); long randomId = messageObject.messageOwner.random_id; if (messageObject.messageOwner.mentioned) { dialogId = messageObject.getFromChatId(); } int index = settingsCache.indexOfKey(dialogId); boolean value; - if (index >= 0) { + if (index >= 0 && topicId == 0) { value = settingsCache.valueAt(index); } else { - int notifyOverride = getNotifyOverride(preferences, dialogId); + int notifyOverride = getNotifyOverride(preferences, dialogId, topicId); if (notifyOverride == -1) { value = isGlobalNotificationsEnabled(dialogId); } else { @@ -2591,10 +2626,10 @@ public class NotificationsController extends BaseController { && (messageObject.messageOwner.action == null || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty); } - private int getNotifyOverride(SharedPreferences preferences, long dialog_id) { - int notifyOverride = preferences.getInt("notify2_" + dialog_id, -1); + private int getNotifyOverride(SharedPreferences preferences, long dialog_id, int topicId) { + int notifyOverride = dialogsNotificationsFacade.getProperty(NotificationsSettingsFacade.PROPERTY_NOTIFY, dialog_id, topicId, -1); if (notifyOverride == 3) { - int muteUntil = preferences.getInt("notifyuntil_" + dialog_id, 0); + int muteUntil = dialogsNotificationsFacade.getProperty(NotificationsSettingsFacade.PROPERTY_NOTIFY_UNTIL, dialog_id, topicId, 0); if (muteUntil >= getConnectionsManager().getCurrentTime()) { notifyOverride = 2; } @@ -2654,7 +2689,7 @@ public class NotificationsController extends BaseController { try { SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); - int notifyOverride = getNotifyOverride(preferences, openedDialogId); + int notifyOverride = getNotifyOverride(preferences, openedDialogId, openedTopicId); if (notifyOverride == 2) { return; } @@ -2733,11 +2768,11 @@ public class NotificationsController extends BaseController { return true; } - public void deleteNotificationChannel(long dialogId) { - deleteNotificationChannel(dialogId, -1); + public void deleteNotificationChannel(long dialogId, int topicId) { + deleteNotificationChannel(dialogId, topicId, -1); } - private void deleteNotificationChannelInternal(long dialogId, int what) { + private void deleteNotificationChannelInternal(long dialogId, int topicId, int what) { if (Build.VERSION.SDK_INT < 26) { return; } @@ -2746,6 +2781,9 @@ public class NotificationsController extends BaseController { SharedPreferences.Editor editor = preferences.edit(); if (what == 0 || what == -1) { String key = "org.telegram.key" + dialogId; + if (topicId != 0) { + key += ".topic" + topicId; + } String channelId = preferences.getString(key, null); if (channelId != null) { editor.remove(key).remove(key + "_s"); @@ -2780,11 +2818,11 @@ public class NotificationsController extends BaseController { } } - public void deleteNotificationChannel(long dialogId, int what) { + public void deleteNotificationChannel(long dialogId, int topicId, int what) { if (Build.VERSION.SDK_INT < 26) { return; } - notificationsQueue.postRunnable(() -> deleteNotificationChannelInternal(dialogId, what)); + notificationsQueue.postRunnable(() -> deleteNotificationChannelInternal(dialogId, topicId, what)); } public void deleteNotificationChannelGlobal(int type) { @@ -3079,7 +3117,7 @@ public class NotificationsController extends BaseController { } @TargetApi(26) - private String validateChannelId(long dialogId, String name, long[] vibrationPattern, int ledColor, Uri sound, int importance, boolean isDefault, boolean isInApp, boolean isSilent, int type) { + private String validateChannelId(long dialogId, int topicId, String name, long[] vibrationPattern, int ledColor, Uri sound, int importance, boolean isDefault, boolean isInApp, boolean isSilent, int type) { ensureGroupsCreated(); SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); @@ -3126,7 +3164,8 @@ public class NotificationsController extends BaseController { if (isInApp) { name = LocaleController.formatString("NotificationsChatInApp", R.string.NotificationsChatInApp, name); } - key = (isInApp ? "org.telegram.keyia" : "org.telegram.key") + dialogId; + //TODO notifications + key = (isInApp ? "org.telegram.keyia" : "org.telegram.key") + dialogId + "_" + topicId; } key += "_" + soundHash; String channelId = preferences.getString(key, null); @@ -3178,8 +3217,8 @@ public class NotificationsController extends BaseController { updateServerNotificationsSettings(type); } } else { - editor.putInt("notify2_" + dialogId, 2); - updateServerNotificationsSettings(dialogId, true); + editor.putInt("notify2_" + NotificationsController.getSharedPrefKey(dialogId, 0), 2); + updateServerNotificationsSettings(dialogId, 0,true); } edited = true; } else if (channelImportance != importance) { @@ -3352,6 +3391,8 @@ public class NotificationsController extends BaseController { } long dialog_id = lastMessageObject.getDialogId(); + int topicId = MessageObject.getTopicId(lastMessageObject.messageOwner); + boolean isChannel = false; long override_dialog_id = dialog_id; if (lastMessageObject.messageOwner.mentioned) { @@ -3383,7 +3424,7 @@ public class NotificationsController extends BaseController { int ledColor = 0xff0000ff; int importance = 0; - int notifyOverride = getNotifyOverride(preferences, override_dialog_id); + int notifyOverride = getNotifyOverride(preferences, override_dialog_id, topicId); boolean value; if (notifyOverride == -1) { value = isGlobalNotificationsEnabled(dialog_id, isChannel); @@ -3531,7 +3572,7 @@ public class NotificationsController extends BaseController { } } - if (!notifyDisabled && !preferences.getBoolean("sound_enabled_" + dialog_id, true)) { + if (!notifyDisabled && !preferences.getBoolean("sound_enabled_" + getSharedPrefKey(dialog_id, topicId), true)) { notifyDisabled = true; } @@ -3546,18 +3587,21 @@ public class NotificationsController extends BaseController { int customVibrate; int customImportance; Integer customLedColor; - if (preferences.getBoolean("custom_" + dialog_id, false)) { - customVibrate = preferences.getInt("vibrate_" + dialog_id, 0); - customImportance = preferences.getInt("priority_" + dialog_id, 3); - long soundDocumentId = preferences.getLong("sound_document_id_" + dialog_id, 0); + String key = getSharedPrefKey(dialog_id, topicId); + if (dialogsNotificationsFacade.getProperty("custom_", dialog_id, topicId, false)) { + customVibrate = dialogsNotificationsFacade.getProperty("vibrate_", dialog_id, topicId, 0); + customImportance = dialogsNotificationsFacade.getProperty("priority_", dialog_id, topicId, 3); + long soundDocumentId = dialogsNotificationsFacade.getProperty("sound_document_id_" , dialog_id, topicId, 0L); if (soundDocumentId != 0) { customIsInternalSound = true; customSoundPath = getMediaDataController().ringtoneDataStore.getSoundPath(soundDocumentId); } else { - customSoundPath = preferences.getString("sound_path_" + dialog_id, null); + customSoundPath = dialogsNotificationsFacade.getPropertyString("sound_path_" , dialog_id, topicId, null); } - if (preferences.contains("color_" + dialog_id)) { - customLedColor = preferences.getInt("color_" + dialog_id, 0); + + int color = dialogsNotificationsFacade.getProperty("color_", dialog_id, topicId, 0); + if (color != 0) { + customLedColor = color; } else { customLedColor = null; } @@ -3858,7 +3902,7 @@ public class NotificationsController extends BaseController { mBuilder.addAction(R.drawable.ic_ab_reply, LocaleController.getString("Reply", R.string.Reply), PendingIntent.getBroadcast(ApplicationLoader.applicationContext, 2, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT)); } } - showExtraNotifications(mBuilder, detailText, dialog_id, chatName, vibrationPattern, ledColor, sound, configImportance, isDefault, isInApp, notifyDisabled, chatType); + showExtraNotifications(mBuilder, detailText, dialog_id, topicId, chatName, vibrationPattern, ledColor, sound, configImportance, isDefault, isInApp, notifyDisabled, chatType); scheduleNotificationRepeat(); } catch (Exception e) { FileLog.e(e); @@ -3878,7 +3922,7 @@ public class NotificationsController extends BaseController { } } - private void resetNotificationSound(NotificationCompat.Builder notificationBuilder, long dialogId, String chatName, long[] vibrationPattern, int ledColor, Uri sound, int importance, boolean isDefault, boolean isInApp, boolean isSilent, int chatType) { + private void resetNotificationSound(NotificationCompat.Builder notificationBuilder, long dialogId, int topicId, String chatName, long[] vibrationPattern, int ledColor, Uri sound, int importance, boolean isDefault, boolean isInApp, boolean isSilent, int chatType) { Uri defaultSound = Settings.System.DEFAULT_RINGTONE_URI; if (defaultSound != null && sound != null && !TextUtils.equals(defaultSound.toString(), sound.toString())) { SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); @@ -3903,21 +3947,21 @@ public class NotificationsController extends BaseController { } getNotificationsController().deleteNotificationChannelGlobalInternal(chatType, -1); } else { - editor.putString("sound_" + dialogId, ringtoneName); - editor.putString("sound_path_" + dialogId, newSound); - deleteNotificationChannelInternal(dialogId, -1); + editor.putString("sound_" + NotificationsController.getSharedPrefKey(dialogId, topicId), ringtoneName); + editor.putString("sound_path_" + NotificationsController.getSharedPrefKey(dialogId, topicId), newSound); + deleteNotificationChannelInternal(dialogId, topicId, -1); } editor.commit(); sound = Settings.System.DEFAULT_RINGTONE_URI; - notificationBuilder.setChannelId(validateChannelId(dialogId, chatName, vibrationPattern, ledColor, sound, importance, isDefault, isInApp, isSilent, chatType)); + notificationBuilder.setChannelId(validateChannelId(dialogId, topicId, chatName, vibrationPattern, ledColor, sound, importance, isDefault, isInApp, isSilent, chatType)); notificationManager.notify(notificationId, notificationBuilder.build()); } } @SuppressLint("InlinedApi") - private void showExtraNotifications(NotificationCompat.Builder notificationBuilder, String summary, long lastDialogId, String chatName, long[] vibrationPattern, int ledColor, Uri sound, int importance, boolean isDefault, boolean isInApp, boolean isSilent, int chatType) { + private void showExtraNotifications(NotificationCompat.Builder notificationBuilder, String summary, long lastDialogId, int lastTopicId, String chatName, long[] vibrationPattern, int ledColor, Uri sound, int importance, boolean isDefault, boolean isInApp, boolean isSilent, int chatType) { if (Build.VERSION.SDK_INT >= 26) { - notificationBuilder.setChannelId(validateChannelId(lastDialogId, chatName, vibrationPattern, ledColor, sound, importance, isDefault, isInApp, isSilent, chatType)); + notificationBuilder.setChannelId(validateChannelId(lastDialogId, lastTopicId, chatName, vibrationPattern, ledColor, sound, importance, isDefault, isInApp, isSilent, chatType)); } Notification mainNotification = notificationBuilder.build(); if (Build.VERSION.SDK_INT < 18) { @@ -3930,11 +3974,12 @@ public class NotificationsController extends BaseController { SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); - ArrayList sortedDialogs = new ArrayList<>(); + ArrayList sortedDialogs = new ArrayList<>(); LongSparseArray> messagesByDialogs = new LongSparseArray<>(); for (int a = 0; a < pushMessages.size(); a++) { MessageObject messageObject = pushMessages.get(a); long dialog_id = messageObject.getDialogId(); + int topicId = MessageObject.getTopicId(messageObject.messageOwner); int dismissDate = preferences.getInt("dismissDate" + dialog_id, 0); if (messageObject.messageOwner.date <= dismissDate) { continue; @@ -3944,7 +3989,7 @@ public class NotificationsController extends BaseController { if (arrayList == null) { arrayList = new ArrayList<>(); messagesByDialogs.put(dialog_id, arrayList); - sortedDialogs.add(dialog_id); + sortedDialogs.add(new DialogKey(dialog_id, topicId)); } arrayList.add(messageObject); } @@ -3958,18 +4003,20 @@ public class NotificationsController extends BaseController { class NotificationHolder { int id; long dialogId; + int topicId; String name; TLRPC.User user; TLRPC.Chat chat; NotificationCompat.Builder notification; - NotificationHolder(int i, long li, String n, TLRPC.User u, TLRPC.Chat c, NotificationCompat.Builder builder) { + NotificationHolder(int i, long li, int topicId, String n, TLRPC.User u, TLRPC.Chat c, NotificationCompat.Builder builder) { id = i; name = n; user = u; chat = c; notification = builder; dialogId = li; + this.topicId = topicId; } void call() { @@ -3980,7 +4027,7 @@ public class NotificationsController extends BaseController { notificationManager.notify(id, notification.build()); } catch (SecurityException e) { FileLog.e(e); - resetNotificationSound(notification, dialogId, chatName, vibrationPattern, ledColor, sound, importance, isDefault, isInApp, isSilent, chatType); + resetNotificationSound(notification, dialogId, lastTopicId, chatName, vibrationPattern, ledColor, sound, importance, isDefault, isInApp, isSilent, chatType); } } } @@ -4001,7 +4048,9 @@ public class NotificationsController extends BaseController { if (holders.size() >= maxCount) { break; } - long dialogId = sortedDialogs.get(b); + DialogKey dialogKey = sortedDialogs.get(b); + long dialogId = dialogKey.dialogId; + int topicId = dialogKey.topicId; ArrayList messageObjects = messagesByDialogs.get(dialogId); int maxId = messageObjects.get(0).getId(); @@ -4017,7 +4066,6 @@ public class NotificationsController extends BaseController { for (int i = 0; i < messageObjects.size(); i++) { if (maxDate < messageObjects.get(i).messageOwner.date) { maxDate = messageObjects.get(i).messageOwner.date; - } } TLRPC.Chat chat = null; @@ -4074,6 +4122,14 @@ public class NotificationsController extends BaseController { if (chat.photo != null && chat.photo.photo_small != null && chat.photo.photo_small.volume_id != 0 && chat.photo.photo_small.local_id != 0) { photoPath = chat.photo.photo_small; } + + if (topicId != 0) { + TLRPC.TL_forumTopic topic = getMessagesController().getTopicsController().findTopic(chat.id, topicId); + if (topic != null) { + name = topic.title + " in " + name; + } + } + } } } else { @@ -4144,6 +4200,7 @@ public class NotificationsController extends BaseController { Intent replyIntent = new Intent(ApplicationLoader.applicationContext, WearReplyReceiver.class); replyIntent.putExtra("dialog_id", dialogId); replyIntent.putExtra("max_id", maxId); + replyIntent.putExtra("topic_id", topicId); replyIntent.putExtra("currentAccount", currentAccount); PendingIntent replyPendingIntent = PendingIntent.getBroadcast(ApplicationLoader.applicationContext, internalId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT); RemoteInput remoteInputWear = new RemoteInput.Builder(EXTRA_VOICE_REPLY).setLabel(LocaleController.getString("Reply", R.string.Reply)).build(); @@ -4211,6 +4268,10 @@ public class NotificationsController extends BaseController { int rowsMid = 0; for (int a = messageObjects.size() - 1; a >= 0; a--) { MessageObject messageObject = messageObjects.get(a); + int messageTopicId = MessageObject.getTopicId(messageObject.messageOwner); + if (topicId != messageTopicId) { + continue; + } String message = getShortStringForMessage(messageObject, senderName, preview); if (dialogId == selfUserId) { senderName[0] = name; @@ -4247,8 +4308,8 @@ public class NotificationsController extends BaseController { } else { uid = dialogId; } - Person person = personCache.get(uid); - String personName = ""; + Person person = personCache.get(uid + ((long) topicId << 16)); + CharSequence personName = ""; if (senderName[0] == null) { if (waitingForPasscode) { if (DialogObject.isChatDialog(dialogId)) { @@ -4266,6 +4327,7 @@ public class NotificationsController extends BaseController { } else { personName = senderName[0]; } + if (person == null || !TextUtils.equals(person.getName(), personName)) { Person.Builder personBuilder = new Person.Builder().setName(personName); if (preview[0] && !DialogObject.isEncryptedDialog(dialogId) && Build.VERSION.SDK_INT >= 28) { @@ -4291,10 +4353,11 @@ public class NotificationsController extends BaseController { personCache.put(uid, person); } + if (!DialogObject.isEncryptedDialog(dialogId)) { boolean setPhoto = false; if (preview[0] && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !((ActivityManager) ApplicationLoader.applicationContext.getSystemService(Context.ACTIVITY_SERVICE)).isLowRamDevice()) { - if (!waitingForPasscode && !messageObject.isSecretMedia() && (messageObject.type == 1 || messageObject.isSticker())) { + if (!waitingForPasscode && !messageObject.isSecretMedia() && (messageObject.type == MessageObject.TYPE_PHOTO || messageObject.isSticker())) { File attach = getFileLoader().getPathToMessage(messageObject.messageOwner); NotificationCompat.MessagingStyle.Message msg = new NotificationCompat.MessagingStyle.Message(message, ((long) messageObject.messageOwner.date) * 1000L, person); String mimeType = messageObject.isSticker() ? "image/webp" : "image/jpeg"; @@ -4376,6 +4439,9 @@ public class NotificationsController extends BaseController { } else { intent.putExtra("chatId", -dialogId); } + if (topicId != 0) { + intent.putExtra("topicId", topicId); + } intent.putExtra("currentAccount", currentAccount); PendingIntent contentIntent = PendingIntent.getActivity(ApplicationLoader.applicationContext, 0, intent, PendingIntent.FLAG_ONE_SHOT); @@ -4489,7 +4555,7 @@ public class NotificationsController extends BaseController { if (Build.VERSION.SDK_INT >= 26) { setNotificationChannel(mainNotification, builder, useSummaryNotification); } - holders.add(new NotificationHolder(internalId, dialogId, name, user, chat, builder)); + holders.add(new NotificationHolder(internalId, dialogId, topicId, name, user, chat, builder)); wearNotificationsIds.put(dialogId, internalId); } @@ -4501,7 +4567,7 @@ public class NotificationsController extends BaseController { notificationManager.notify(notificationId, mainNotification); } catch (SecurityException e) { FileLog.e(e); - resetNotificationSound(notificationBuilder, lastDialogId, chatName, vibrationPattern, ledColor, sound, importance, isDefault, isInApp, isSilent, chatType); + resetNotificationSound(notificationBuilder, lastDialogId, lastTopicId, chatName, vibrationPattern, ledColor, sound, importance, isDefault, isInApp, isSilent, chatType); } } else { if (openedInBubbleDialogs.isEmpty()) { @@ -4616,30 +4682,32 @@ public class NotificationsController extends BaseController { public static final int SETTING_MUTE_UNMUTE = 4; public static final int SETTING_MUTE_CUSTOM = 5; - public void clearDialogNotificationsSettings(long did) { + public void clearDialogNotificationsSettings(long did, int topicId) { SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); SharedPreferences.Editor editor = preferences.edit(); - editor.remove("notify2_" + did).remove("custom_" + did); + String prefKey = NotificationsController.getSharedPrefKey(did, topicId); + editor.remove("notify2_" + prefKey).remove("custom_" + prefKey); getMessagesStorage().setDialogFlags(did, 0); TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(did); if (dialog != null) { dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); } editor.commit(); - getNotificationsController().updateServerNotificationsSettings(did, true); + getNotificationsController().updateServerNotificationsSettings(did, topicId,true); } - public void setDialogNotificationsSettings(long dialog_id, int setting) { + public void setDialogNotificationsSettings(long dialog_id, int topicId, int setting) { SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); SharedPreferences.Editor editor = preferences.edit(); TLRPC.Dialog dialog = MessagesController.getInstance(UserConfig.selectedAccount).dialogs_dict.get(dialog_id); if (setting == SETTING_MUTE_UNMUTE) { boolean defaultEnabled = isGlobalNotificationsEnabled(dialog_id); if (defaultEnabled) { - editor.remove("notify2_" + dialog_id); + editor.remove("notify2_" + NotificationsController.getSharedPrefKey(dialog_id, topicId)); } else { - editor.putInt("notify2_" + dialog_id, 0); + editor.putInt("notify2_" + NotificationsController.getSharedPrefKey(dialog_id, topicId), 0); } + //TODO topic getMessagesStorage().setDialogFlags(dialog_id, 0); if (dialog != null) { dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); @@ -4657,11 +4725,11 @@ public class NotificationsController extends BaseController { } long flags; if (setting == SETTING_MUTE_FOREVER) { - editor.putInt("notify2_" + dialog_id, 2); + editor.putInt("notify2_" + NotificationsController.getSharedPrefKey(dialog_id, topicId), 2); flags = 1; } else { - editor.putInt("notify2_" + dialog_id, 3); - editor.putInt("notifyuntil_" + dialog_id, untilTime); + editor.putInt("notify2_" + NotificationsController.getSharedPrefKey(dialog_id, topicId), 3); + editor.putInt("notifyuntil_" + NotificationsController.getSharedPrefKey(dialog_id, topicId), untilTime); flags = ((long) untilTime << 32) | 1; } NotificationsController.getInstance(UserConfig.selectedAccount).removeNotificationsForDialog(dialog_id); @@ -4672,14 +4740,14 @@ public class NotificationsController extends BaseController { } } editor.commit(); - updateServerNotificationsSettings(dialog_id); + updateServerNotificationsSettings(dialog_id, topicId); } - public void updateServerNotificationsSettings(long dialog_id) { - updateServerNotificationsSettings(dialog_id, true); + public void updateServerNotificationsSettings(long dialog_id, int topicId) { + updateServerNotificationsSettings(dialog_id, topicId, true); } - public void updateServerNotificationsSettings(long dialogId, boolean post) { + public void updateServerNotificationsSettings(long dialogId, int topicId, boolean post) { if (post) { getNotificationCenter().postNotificationName(NotificationCenter.notificationsSettingsUpdated); } @@ -4691,23 +4759,23 @@ public class NotificationsController extends BaseController { req.settings = new TLRPC.TL_inputPeerNotifySettings(); req.settings.flags |= 1; - req.settings.show_previews = preferences.getBoolean("content_preview_" + dialogId, true); + req.settings.show_previews = preferences.getBoolean("content_preview_" + NotificationsController.getSharedPrefKey(dialogId, topicId), true); req.settings.flags |= 2; - req.settings.silent = preferences.getBoolean("silent_" + dialogId, false); + req.settings.silent = preferences.getBoolean("silent_" + NotificationsController.getSharedPrefKey(dialogId, topicId), false); - int mute_type = preferences.getInt("notify2_" + dialogId, -1); + int mute_type = preferences.getInt("notify2_" + NotificationsController.getSharedPrefKey(dialogId, topicId), -1); if (mute_type != -1) { req.settings.flags |= 4; if (mute_type == 3) { - req.settings.mute_until = preferences.getInt("notifyuntil_" + dialogId, 0); + req.settings.mute_until = preferences.getInt("notifyuntil_" + NotificationsController.getSharedPrefKey(dialogId, topicId), 0); } else { req.settings.mute_until = mute_type != 2 ? 0 : Integer.MAX_VALUE; } } - long soundDocumentId = preferences.getLong("sound_document_id_" + dialogId, 0); - String soundPath = preferences.getString("sound_path_" + dialogId, null); + long soundDocumentId = preferences.getLong("sound_document_id_" + NotificationsController.getSharedPrefKey(dialogId, topicId), 0); + String soundPath = preferences.getString("sound_path_" + NotificationsController.getSharedPrefKey(dialogId, topicId), null); req.settings.flags |= 8; if (soundDocumentId != 0) { TLRPC.TL_notificationSoundRingtone ringtoneSound = new TLRPC.TL_notificationSoundRingtone(); @@ -4718,19 +4786,25 @@ public class NotificationsController extends BaseController { req.settings.sound = new TLRPC.TL_notificationSoundNone(); } else { TLRPC.TL_notificationSoundLocal localSound = new TLRPC.TL_notificationSoundLocal(); - localSound.title = preferences.getString("sound_" + dialogId, null); + localSound.title = preferences.getString("sound_" + NotificationsController.getSharedPrefKey(dialogId, topicId), null); localSound.data = soundPath; req.settings.sound = localSound; } } else { req.settings.sound = new TLRPC.TL_notificationSoundDefault(); } + if (topicId != 0) { + TLRPC.TL_inputNotifyForumTopic topicPeer = new TLRPC.TL_inputNotifyForumTopic(); + topicPeer.peer = getMessagesController().getInputPeer(dialogId); + topicPeer.top_msg_id = topicId; + req.peer = topicPeer; + } else { + req.peer = new TLRPC.TL_inputNotifyPeer(); + ((TLRPC.TL_inputNotifyPeer) req.peer).peer = getMessagesController().getInputPeer(dialogId); + } - req.peer = new TLRPC.TL_inputNotifyPeer(); - - ((TLRPC.TL_inputNotifyPeer) req.peer).peer = getMessagesController().getInputPeer(dialogId); getConnectionsManager().sendRequest(req, (response, error) -> { - + // FileLog.d("updateServerNotificationsSettings " + dialogId + " " + topicId + " error = " + error); }); } @@ -4842,25 +4916,69 @@ public class NotificationsController extends BaseController { } } - public void muteDialog(long dialog_id, boolean mute) { + public void muteDialog(long dialog_id, int topicId, boolean mute) { if (mute) { - NotificationsController.getInstance(currentAccount).muteUntil(dialog_id, Integer.MAX_VALUE); + NotificationsController.getInstance(currentAccount).muteUntil(dialog_id, topicId, Integer.MAX_VALUE); } else { boolean defaultEnabled = NotificationsController.getInstance(currentAccount).isGlobalNotificationsEnabled(dialog_id); + boolean override = topicId != 0; SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); SharedPreferences.Editor editor = preferences.edit(); - if (defaultEnabled) { - editor.remove("notify2_" + dialog_id); + if (defaultEnabled && !override) { + editor.remove("notify2_" + getSharedPrefKey(dialog_id, topicId)); } else { - editor.putInt("notify2_" + dialog_id, 0); + editor.putInt("notify2_" + getSharedPrefKey(dialog_id, topicId), 0); + } + if (topicId == 0) { + getMessagesStorage().setDialogFlags(dialog_id, 0); + TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(dialog_id); + if (dialog != null) { + dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + } } - getMessagesStorage().setDialogFlags(dialog_id, 0); editor.apply(); - TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(dialog_id); - if (dialog != null) { - dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + updateServerNotificationsSettings(dialog_id, topicId); + } + } + + public NotificationsSettingsFacade getNotificationsSettingsFacade() { + return dialogsNotificationsFacade; + } + + public void loadTopicsNotificationsExceptions(long dialogId, Consumer> consumer) { + getMessagesStorage().getStorageQueue().postRunnable(() -> { + HashSet topics = new HashSet<>(); + SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + Map values = preferences.getAll(); + for (Map.Entry entry : values.entrySet()) { + String key = entry.getKey(); + if (key.startsWith("notify2_" + dialogId)) { + key = key.replace("notify2_" + dialogId, ""); + + int topicId = Utilities.parseInt(key); + if (topicId != 0) { + if (getMessagesController().isDialogMuted(dialogId, topicId) != getMessagesController().isDialogMuted(dialogId, 0)) { + topics.add(topicId); + } + } + } } - updateServerNotificationsSettings(dialog_id); + AndroidUtilities.runOnUIThread(() -> { + if (consumer != null) { + consumer.accept(topics); + } + }); + }); + + } + + private static class DialogKey { + final long dialogId; + final int topicId; + + private DialogKey(long dialogId, int topicId) { + this.dialogId = dialogId; + this.topicId = topicId; } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsDisabledReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsDisabledReceiver.java index 2a61efa4c..b6cba21c7 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsDisabledReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsDisabledReceiver.java @@ -8,6 +8,9 @@ package org.telegram.messenger; +import static android.app.NotificationManager.EXTRA_BLOCKED_STATE; +import static android.app.NotificationManager.EXTRA_NOTIFICATION_CHANNEL_ID; + import android.annotation.TargetApi; import android.content.BroadcastReceiver; import android.content.Context; @@ -16,9 +19,6 @@ import android.content.SharedPreferences; import android.os.SystemClock; import android.text.TextUtils; -import static android.app.NotificationManager.EXTRA_BLOCKED_STATE; -import static android.app.NotificationManager.EXTRA_NOTIFICATION_CHANNEL_ID; - @TargetApi(28) public class NotificationsDisabledReceiver extends BroadcastReceiver { @@ -86,6 +86,8 @@ public class NotificationsDisabledReceiver extends BroadcastReceiver { if (dialogId == 0) { return; } + int topicId = 0; + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); String currentChannel = preferences.getString("org.telegram.key" + dialogId, null); if (!channelId.equals(currentChannel)) { return; @@ -94,12 +96,12 @@ public class NotificationsDisabledReceiver extends BroadcastReceiver { FileLog.d("apply channel " + channelId + " state"); } SharedPreferences.Editor editor = preferences.edit(); - editor.putInt("notify2_" + dialogId, state ? 2 : 0); + editor.putInt("notify2_" + key, state ? 2 : 0); if (!state) { - editor.remove("notifyuntil_" + dialogId); + editor.remove("notifyuntil_" + key); } editor.commit(); - AccountInstance.getInstance(account).getNotificationsController().updateServerNotificationsSettings(dialogId, true); + AccountInstance.getInstance(account).getNotificationsController().updateServerNotificationsSettings(dialogId, 0, true); } AccountInstance.getInstance(account).getConnectionsManager().resumeNetworkMaybe(); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsSettingsFacade.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsSettingsFacade.java new file mode 100644 index 000000000..a828a455c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsSettingsFacade.java @@ -0,0 +1,258 @@ +package org.telegram.messenger; + +import static org.telegram.messenger.NotificationsController.TYPE_PRIVATE; + +import android.content.SharedPreferences; + +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLRPC; + +public class NotificationsSettingsFacade { + + public final static String PROPERTY_NOTIFY = "notify2_"; + public final static String PROPERTY_CUSTOM = "custom_"; + public final static String PROPERTY_NOTIFY_UNTIL = "notifyuntil_"; + public final static String PROPERTY_CONTENT_PREVIEW = "content_preview_"; + public final static String PROPERTY_SILENT = "silent_"; + + private final int currentAccount; + + public NotificationsSettingsFacade(int currentAccount) { + this.currentAccount = currentAccount; + } + + + public boolean isDefault(long dialogId, int topicId) { + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); + return false; + } + + public void clearPreference(long dialogId, int topicId) { + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); + getPreferences().edit() + .remove(PROPERTY_NOTIFY + key) + .remove(PROPERTY_CUSTOM + key) + .remove(PROPERTY_NOTIFY_UNTIL + key) + .remove(PROPERTY_CONTENT_PREVIEW + key) + .remove(PROPERTY_SILENT + key) + .apply(); + + } + + + public int getProperty(String property, long dialogId, int topicId, int defaultValue) { + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); + if (getPreferences().contains(property + key)) { + return getPreferences().getInt(property + key, defaultValue); + } + key = NotificationsController.getSharedPrefKey(dialogId, 0); + return getPreferences().getInt(property + key, defaultValue); + } + + public long getProperty(String property, long dialogId, int topicId, long defaultValue) { + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); + if (getPreferences().contains(property + key)) { + return getPreferences().getLong(property + key, defaultValue); + } + key = NotificationsController.getSharedPrefKey(dialogId, 0); + return getPreferences().getLong(property + key, defaultValue); + } + + public boolean getProperty(String property, long dialogId, int topicId, boolean defaultValue) { + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); + if (getPreferences().contains(property + key)) { + return getPreferences().getBoolean(property + key, defaultValue); + } + key = NotificationsController.getSharedPrefKey(dialogId, 0); + return getPreferences().getBoolean(property + key, defaultValue); + } + + public String getPropertyString(String property, long dialogId, int topicId, String defaultValue) { + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); + if (getPreferences().contains(property + key)) { + return getPreferences().getString(property + key, defaultValue); + } + key = NotificationsController.getSharedPrefKey(dialogId, 0); + return getPreferences().getString(property + key, defaultValue); + } + + + public void removeProperty(String property, long dialogId, int topicId) { + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); + getPreferences().edit().remove(property + key).apply(); + } + + private SharedPreferences getPreferences() { + return MessagesController.getNotificationsSettings(currentAccount); + } + + public void applyDialogNotificationsSettings(long dialogId, int topicId, TLRPC.PeerNotifySettings notify_settings) { + if (notify_settings == null) { + return; + } + Utilities.globalQueue.postRunnable(() -> { + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); + MessagesController messagesController = MessagesController.getInstance(currentAccount); + ConnectionsManager connectionsManager = ConnectionsManager.getInstance(currentAccount); + MessagesStorage messagesStorage = MessagesStorage.getInstance(currentAccount); + NotificationsController notificationsController = NotificationsController.getInstance(currentAccount); + + int currentValue = getPreferences().getInt(PROPERTY_NOTIFY + key, -1); + int currentValue2 = getPreferences().getInt(PROPERTY_NOTIFY_UNTIL + key, 0); + SharedPreferences.Editor editor = getPreferences().edit(); + boolean updated = false; + if ((notify_settings.flags & 2) != 0) { + editor.putBoolean(PROPERTY_SILENT + key, notify_settings.silent); + } else { + editor.remove(PROPERTY_SILENT + key); + } + + TLRPC.Dialog dialog = null; + if (topicId == 0) { + dialog = messagesController.dialogs_dict.get(dialogId); + } + if (dialog != null) { + dialog.notify_settings = notify_settings; + } + + if ((notify_settings.flags & 4) != 0) { + if (notify_settings.mute_until > connectionsManager.getCurrentTime()) { + int until = 0; + if (notify_settings.mute_until > connectionsManager.getCurrentTime() + 60 * 60 * 24 * 365) { + if (currentValue != 2) { + updated = true; + editor.putInt(PROPERTY_NOTIFY + key, 2); + if (dialog != null) { + dialog.notify_settings.mute_until = Integer.MAX_VALUE; + } + } + } else { + if (currentValue != 3 || currentValue2 != notify_settings.mute_until) { + updated = true; + editor.putInt(PROPERTY_NOTIFY + key, 3); + editor.putInt(PROPERTY_NOTIFY_UNTIL + key, notify_settings.mute_until); + if (dialog != null) { + dialog.notify_settings.mute_until = until; + } + } + until = notify_settings.mute_until; + } + if (topicId == 0) { + messagesStorage.setDialogFlags(dialogId, ((long) until << 32) | 1); + notificationsController.removeNotificationsForDialog(dialogId); + } + } else { + if (currentValue != 0 && currentValue != 1) { + updated = true; + if (dialog != null) { + dialog.notify_settings.mute_until = 0; + } + editor.putInt(PROPERTY_NOTIFY + key, 0); + } + if (topicId == 0) { + messagesStorage.setDialogFlags(dialogId, 0); + } + } + } else { + if (currentValue != -1) { + updated = true; + if (dialog != null) { + dialog.notify_settings.mute_until = 0; + } + editor.remove(PROPERTY_NOTIFY + key); + } + if (topicId == 0) { + messagesStorage.setDialogFlags(dialogId, 0); + } + } + applySoundSettings(notify_settings.android_sound, editor, dialogId, topicId, 0, false); + editor.apply(); + if (updated) { + AndroidUtilities.runOnUIThread(() -> { + NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.notificationsSettingsUpdated); + }); + } + }); + } + + public void applySoundSettings(TLRPC.NotificationSound settings, SharedPreferences.Editor editor, long dialogId, int topicId, int globalType, boolean serverUpdate) { + if (settings == null) { + return; + } + String soundPref; + String soundPathPref; + String soundDocPref; + if (dialogId != 0) { + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); + soundPref = "sound_" + key; + soundPathPref = "sound_path_" + key; + soundDocPref = "sound_document_id_" + key; + } else { + if (globalType == NotificationsController.TYPE_GROUP) { + soundPref = "GroupSound"; + soundDocPref = "GroupSoundDocId"; + soundPathPref = "GroupSoundPath"; + } else if (globalType == TYPE_PRIVATE) { + soundPref = "GlobalSound"; + soundDocPref = "GlobalSoundDocId"; + soundPathPref = "GlobalSoundPath"; + } else { + soundPref = "ChannelSound"; + soundDocPref = "ChannelSoundDocId"; + soundPathPref = "ChannelSoundPath"; + } + } + + if (settings instanceof TLRPC.TL_notificationSoundDefault) { + editor.putString(soundPref, "Default"); + editor.putString(soundPathPref, "Default"); + editor.remove(soundDocPref); + } else if (settings instanceof TLRPC.TL_notificationSoundNone) { + editor.putString(soundPref, "NoSound"); + editor.putString(soundPathPref, "NoSound"); + editor.remove(soundDocPref); + } else if (settings instanceof TLRPC.TL_notificationSoundLocal) { + TLRPC.TL_notificationSoundLocal localSound = (TLRPC.TL_notificationSoundLocal) settings; + editor.putString(soundPref, localSound.title); + editor.putString(soundPathPref, localSound.data); + editor.remove(soundDocPref); + } else if (settings instanceof TLRPC.TL_notificationSoundRingtone) { + TLRPC.TL_notificationSoundRingtone soundRingtone = (TLRPC.TL_notificationSoundRingtone) settings; + editor.putLong(soundDocPref, soundRingtone.id); + MediaDataController.getInstance(currentAccount).checkRingtones(); + if (serverUpdate && dialogId != 0) { + editor.putBoolean("custom_" + dialogId, true); + } + MediaDataController.getInstance(currentAccount).ringtoneDataStore.getDocument(soundRingtone.id); + } + } + + public void setSettingsForDialog(TLRPC.Dialog dialog, TLRPC.PeerNotifySettings notify_settings) { + SharedPreferences.Editor editor = getPreferences().edit(); + long dialogId = MessageObject.getPeerId(dialog.peer); + + if ((dialog.notify_settings.flags & 2) != 0) { + editor.putBoolean(PROPERTY_SILENT + dialogId, dialog.notify_settings.silent); + } else { + editor.remove(PROPERTY_SILENT + dialogId); + } + ConnectionsManager connectionsManager = ConnectionsManager.getInstance(currentAccount); + if ((dialog.notify_settings.flags & 4) != 0) { + if (dialog.notify_settings.mute_until > connectionsManager.getCurrentTime()) { + if (dialog.notify_settings.mute_until > connectionsManager.getCurrentTime() + 60 * 60 * 24 * 365) { + editor.putInt(PROPERTY_NOTIFY + dialogId, 2); + dialog.notify_settings.mute_until = Integer.MAX_VALUE; + } else { + editor.putInt(PROPERTY_NOTIFY + dialogId, 3); + editor.putInt(PROPERTY_NOTIFY_UNTIL + dialogId, dialog.notify_settings.mute_until); + } + } else { + editor.putInt(PROPERTY_NOTIFY + dialogId, 0); + } + } else { + editor.remove(PROPERTY_NOTIFY + dialogId); + } + + editor.apply(); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/PushListenerController.java b/TMessagesProj/src/main/java/org/telegram/messenger/PushListenerController.java index 7d671c815..486cfcb6e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/PushListenerController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/PushListenerController.java @@ -264,6 +264,8 @@ public class PushListenerController { long chat_id; long user_id; long dialogId = 0; + + int topicId = 0; boolean scheduled; if (custom.has("channel_id")) { channel_id = custom.getLong("channel_id"); @@ -283,6 +285,9 @@ public class PushListenerController { } else { chat_id = 0; } + if (custom.has("topic_id")) { + topicId =custom.getInt("topic_id"); + } if (custom.has("encryption_id")) { dialogId = DialogObject.makeEncryptedDialogId(custom.getInt("encryption_id")); } @@ -350,7 +355,7 @@ public class PushListenerController { deletedMessages.put(-channel_id, ids); NotificationsController.getInstance(currentAccount).removeDeletedMessagesFromNotifications(deletedMessages, true); - MessagesController.getInstance(currentAccount).checkUnreadReactions(dialogId, sparseBooleanArray); + MessagesController.getInstance(currentAccount).checkUnreadReactions(dialogId, topicId, sparseBooleanArray); if (BuildVars.LOGS_ENABLED) { FileLog.d(tag + " received " + loc_key + " for dialogId = " + dialogId + " mids = " + TextUtils.join(",", ids)); } @@ -1147,6 +1152,11 @@ public class PushListenerController { messageOwner.from_scheduled = scheduled; MessageObject messageObject = new MessageObject(currentAccount, messageOwner, messageText, name, userName, localMessage, channel, supergroup, edited); + if (topicId != 0) { + messageObject.messageOwner.reply_to = new TLRPC.TL_messageReplyHeader(); + messageObject.messageOwner.reply_to.forum_topic = true; + messageObject.messageOwner.reply_to.reply_to_top_id = topicId; + } messageObject.isReactionPush = loc_key.startsWith("REACT_") || loc_key.startsWith("CHAT_REACT_"); ArrayList arrayList = new ArrayList<>(); arrayList.add(messageObject); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java index 8957efd9e..ee550d4cc 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java @@ -154,7 +154,7 @@ public class SecretChatHelper extends BaseController { ArrayList arr = new ArrayList<>(); arr.add(newMsg); - getMessagesStorage().putMessages(arr, false, true, true, 0, false); + getMessagesStorage().putMessages(arr, false, true, true, 0, false, 0); return newMsg; } @@ -534,7 +534,7 @@ public class SecretChatHelper extends BaseController { ImageLoader.getInstance().replaceImageInCache(fileName, fileName2, ImageLocation.getForPhoto(size, newMsg.media.photo), true); ArrayList arr = new ArrayList<>(); arr.add(newMsg); - getMessagesStorage().putMessages(arr, false, true, false, 0, false); + getMessagesStorage().putMessages(arr, false, true, false, 0, false, 0); //getMessagesStorage().putSentFile(originalPath, newMsg.media.photo, 3); } else if (newMsg.media instanceof TLRPC.TL_messageMediaDocument && newMsg.media.document != null) { @@ -568,7 +568,7 @@ public class SecretChatHelper extends BaseController { ArrayList arr = new ArrayList<>(); arr.add(newMsg); - getMessagesStorage().putMessages(arr, false, true, false, 0, false); + getMessagesStorage().putMessages(arr, false, true, false, 0, false, 0); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java index fa5340f49..28ff12fe6 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java @@ -83,6 +83,7 @@ import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; @@ -100,6 +101,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe private LongSparseArray uploadingMessagesIdDialogs = new LongSparseArray<>(); private HashMap waitingForLocation = new HashMap<>(); private HashMap waitingForCallback = new HashMap<>(); + private HashMap> waitingForCallbackMap = new HashMap<>(); private HashMap waitingForVote = new HashMap<>(); private LongSparseArray voteSendTime = new LongSparseArray(); private HashMap importingHistoryFiles = new HashMap<>(); @@ -1057,7 +1059,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe ArrayList messages = new ArrayList<>(); messages.add(obj.messageOwner); - getMessagesStorage().putMessages(messages, false, true, false, 0, obj.scheduled); + getMessagesStorage().putMessages(messages, false, true, false, 0, obj.scheduled, 0); break; } } @@ -1068,7 +1070,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe ArrayList messages = new ArrayList<>(); messages.add(message.obj.messageOwner); - getMessagesStorage().putMessages(messages, false, true, false, 0, message.obj.scheduled); + getMessagesStorage().putMessages(messages, false, true, false, 0, message.obj.scheduled, 0); break; } } @@ -1140,7 +1142,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe messageObject.messageOwner.attachPath = cacheFile.toString(); ArrayList messages = new ArrayList<>(); messages.add(messageObject.messageOwner); - getMessagesStorage().putMessages(messages, false, true, false, 0, messageObject.scheduled); + getMessagesStorage().putMessages(messages, false, true, false, 0, messageObject.scheduled, 0); getNotificationCenter().postNotificationName(NotificationCenter.updateMessageMedia, messageObject.messageOwner); message.photoSize = photo.sizes.get(photo.sizes.size() - 1); message.locationParent = photo; @@ -1189,7 +1191,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } ArrayList messages = new ArrayList<>(); messages.add(messageObject.messageOwner); - getMessagesStorage().putMessages(messages, false, true, false, 0, messageObject.scheduled); + getMessagesStorage().putMessages(messages, false, true, false, 0, messageObject.scheduled, 0); message.performMediaUpload = true; performSendDelayedMessage(message); getNotificationCenter().postNotificationName(NotificationCenter.updateMessageMedia, message.obj.messageOwner); @@ -1243,7 +1245,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe object.type = -1; object.setType(); object.caption = null; - if (object.type != 0) { + if (object.type != MessageObject.TYPE_TEXT) { object.generateCaption(); } else { object.resetLayout(); @@ -1252,7 +1254,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe ArrayList arr = new ArrayList<>(); arr.add(object.messageOwner); - getMessagesStorage().putMessages(arr, false, true, false, 0, object.scheduled); + getMessagesStorage().putMessages(arr, false, true, false, 0, object.scheduled, 0); ArrayList arrayList = new ArrayList<>(); arrayList.add(object); @@ -1330,7 +1332,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe TLRPC.TL_messages_messages messagesRes = new TLRPC.TL_messages_messages(); messagesRes.messages.add(prevMessage.messageOwner); - getMessagesStorage().putMessages(messagesRes, message.peer, -2, 0, false, scheduled); + getMessagesStorage().putMessages(messagesRes, message.peer, -2, 0, false, scheduled, 0); } if (!checkReadyToSendGroups.contains(message)) { checkReadyToSendGroups.add(message); @@ -1544,7 +1546,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); ArrayList arr = new ArrayList<>(); arr.add(message); - getMessagesStorage().putMessages(arr, false, true, false, 0, false); + getMessagesStorage().putMessages(arr, false, true, false, 0, false, 0); performSendMessageRequest(req, newMsgObj, null, null, null, null, false); } @@ -1655,6 +1657,10 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } public int sendMessage(ArrayList messages, final long peer, boolean forwardFromMyName, boolean hideCaption, boolean notify, int scheduleDate) { + return sendMessage(messages, peer, forwardFromMyName, hideCaption, notify, scheduleDate, null); + } + + public int sendMessage(ArrayList messages, final long peer, boolean forwardFromMyName, boolean hideCaption, boolean notify, int scheduleDate, MessageObject replyToTopMsg) { if (messages == null || messages.isEmpty()) { return 0; } @@ -1718,9 +1724,9 @@ public class SendMessagesHelper extends BaseController implements NotificationCe for (int a = 0; a < messages.size(); a++) { MessageObject msgObj = messages.get(a); if (msgObj.getId() <= 0 || msgObj.needDrawBluredPreview()) { - if (msgObj.type == 0 && !TextUtils.isEmpty(msgObj.messageText)) { + if (msgObj.type == MessageObject.TYPE_TEXT && !TextUtils.isEmpty(msgObj.messageText)) { TLRPC.WebPage webPage = msgObj.messageOwner.media != null ? msgObj.messageOwner.media.webpage : null; - sendMessage(msgObj.messageText.toString(), peer, null, null, webPage, webPage != null, msgObj.messageOwner.entities, null, null, notify, scheduleDate, null, false); + sendMessage(msgObj.messageText.toString(), peer, null, replyToTopMsg, webPage, webPage != null, msgObj.messageOwner.entities, null, null, notify, scheduleDate, null, false); } continue; } @@ -1990,8 +1996,17 @@ public class SendMessagesHelper extends BaseController implements NotificationCe FileLog.d("forward message user_id = " + inputPeer.user_id + " chat_id = " + inputPeer.chat_id + " channel_id = " + inputPeer.channel_id + " access_hash = " + inputPeer.access_hash); } + if (replyToTopMsg != null) { + newMsg.reply_to = new TLRPC.TL_messageReplyHeader(); + newMsg.reply_to.reply_to_msg_id = replyToTopMsg.getId(); + if (replyToTopMsg.isTopicMainMessage) { + newMsg.reply_to.forum_topic = true; + newMsg.reply_to.flags |= 8; + } + } + if (arr.size() == 100 || a == messages.size() - 1 || a != messages.size() - 1 && messages.get(a + 1).getDialogId() != msgObj.getDialogId()) { - getMessagesStorage().putMessages(new ArrayList<>(arr), false, true, false, 0, scheduleDate != 0); + getMessagesStorage().putMessages(new ArrayList<>(arr), false, true, false, 0, scheduleDate != 0, 0); getMessagesController().updateInterfaceWithMessages(peer, objArr, scheduleDate != 0); getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); getUserConfig().saveConfig(false); @@ -1999,6 +2014,10 @@ public class SendMessagesHelper extends BaseController implements NotificationCe final TLRPC.TL_messages_forwardMessages req = new TLRPC.TL_messages_forwardMessages(); req.to_peer = inputPeer; req.silent = !notify || MessagesController.getNotificationsSettings(currentAccount).getBoolean("silent_" + peer, false); + if (replyToTopMsg != null) { + req.top_msg_id = replyToTopMsg.getId(); + req.flags |= 512; + } if (scheduleDate != 0) { req.schedule_date = scheduleDate; req.flags |= 1024; @@ -2108,7 +2127,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe messageIds.add(oldId); getMessagesController().deleteMessages(messageIds, null, null, newMsgObj1.dialog_id, false, true); getMessagesStorage().getStorageQueue().postRunnable(() -> { - getMessagesStorage().putMessages(sentMessages, true, false, false, 0, false); + getMessagesStorage().putMessages(sentMessages, true, false, false, 0, false, 0); AndroidUtilities.runOnUIThread(() -> { ArrayList messageObjects = new ArrayList<>(); messageObjects.add(new MessageObject(msgObj.currentAccount, msgObj.messageOwner, true, true)); @@ -2122,7 +2141,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } else { getMessagesStorage().getStorageQueue().postRunnable(() -> { getMessagesStorage().updateMessageStateAndId(newMsgObj1.random_id, MessageObject.getPeerId(peer_id), oldId, newMsgObj1.id, 0, false, scheduleDate != 0 ? 1 : 0); - getMessagesStorage().putMessages(sentMessages, true, false, false, 0, scheduleDate != 0); + getMessagesStorage().putMessages(sentMessages, true, false, false, 0, scheduleDate != 0, 0); AndroidUtilities.runOnUIThread(() -> { newMsgObj1.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; getMediaDataController().increasePeerRaiting(peer); @@ -2316,7 +2335,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe newMsg.attachPath = ""; } newMsg.local_id = 0; - if ((messageObject.type == 3 || videoEditedInfo != null || messageObject.type == MessageObject.TYPE_VOICE) && !TextUtils.isEmpty(newMsg.attachPath)) { + if ((messageObject.type == MessageObject.TYPE_VIDEO || videoEditedInfo != null || messageObject.type == MessageObject.TYPE_VOICE) && !TextUtils.isEmpty(newMsg.attachPath)) { messageObject.attachPathExists = true; } if (messageObject.videoEditedInfo != null && videoEditedInfo == null) { @@ -2355,7 +2374,8 @@ public class SendMessagesHelper extends BaseController implements NotificationCe ArrayList arr = new ArrayList<>(); arr.add(newMsg); - getMessagesStorage().putMessages(arr, false, true, false, 0, messageObject.scheduled); + getMessagesStorage().putMessages(arr, false, true, false, 0, messageObject.scheduled, MessageObject.getTopicId(newMsg)); + getMessagesController().getTopicsController().processEditedMessage(newMsg); messageObject.type = -1; messageObject.setType(); @@ -2653,6 +2673,12 @@ public class SendMessagesHelper extends BaseController implements NotificationCe final String key = dialogId + "_" + msgId + "_" + Utilities.bytesToHex(data) + "_" + 0; waitingForCallback.put(key, true); + List keys = waitingForCallbackMap.get(dialogId + "_" + msgId); + if (keys == null) { + waitingForCallbackMap.put(dialogId + "_" + msgId, keys = new ArrayList<>()); + } + keys.add(key); + if (DialogObject.isUserDialog(dialogId)) { TLRPC.User user = getMessagesController().getUser(dialogId); if (user == null) { @@ -2679,11 +2705,26 @@ public class SendMessagesHelper extends BaseController implements NotificationCe req.flags |= 1; req.data = data; } - getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> waitingForCallback.remove(key)), ConnectionsManager.RequestFlagFailOnServerErrors); + List finalKeys = keys; + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + waitingForCallback.remove(key); + finalKeys.remove(key); + }), ConnectionsManager.RequestFlagFailOnServerErrors); getMessagesController().markDialogAsRead(dialogId, msgId, msgId, 0, false, 0, 0, true, 0); }); } + public void onMessageEdited(TLRPC.Message message) { + if (message != null && message.reply_markup != null) { + List keys = waitingForCallbackMap.remove(message.dialog_id + "_" + message.id); + if (keys != null) { + for (String key : keys) { + waitingForCallback.remove(key); + } + } + } + } + public byte[] isSendingVote(MessageObject messageObject) { if (messageObject == null) { return null; @@ -2828,9 +2869,17 @@ public class SendMessagesHelper extends BaseController implements NotificationCe final String key = messageObject.getDialogId() + "_" + messageObject.getId() + "_" + Utilities.bytesToHex(button.data) + "_" + type; waitingForCallback.put(key, true); + List keys = waitingForCallbackMap.get(messageObject.getDialogId() + "_" + messageObject.getId()); + if (keys == null) { + waitingForCallbackMap.put(messageObject.getDialogId() + "_" + messageObject.getId(), keys = new ArrayList<>()); + } + keys.add(key); + TLObject[] request = new TLObject[1]; + List finalKeys = keys; RequestDelegate requestDelegate = (response, error) -> AndroidUtilities.runOnUIThread(() -> { waitingForCallback.remove(key); + finalKeys.remove(key); if (cacheFinal && response == null) { sendCallback(false, messageObject, button, parentFragment); } else if (response != null) { @@ -3245,29 +3294,33 @@ public class SendMessagesHelper extends BaseController implements NotificationCe type = 11; message = retryMessageObject.getDiceEmoji(); caption = ""; - } else if (retryMessageObject.type == 0 || retryMessageObject.isAnimatedEmoji()) { + } else if (retryMessageObject.type == MessageObject.TYPE_TEXT || retryMessageObject.isAnimatedEmoji()) { if (retryMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { //game = retryMessageObject.messageOwner.media.game; } else { message = newMsg.message; } type = 0; - } else if (retryMessageObject.type == 4) { + } else if (retryMessageObject.type == MessageObject.TYPE_GEO) { location = newMsg.media; type = 1; - } else if (retryMessageObject.type == 1) { + } else if (retryMessageObject.type == MessageObject.TYPE_PHOTO) { photo = (TLRPC.TL_photo) newMsg.media.photo; if (retryMessageObject.messageOwner.message != null) { caption = retryMessageObject.messageOwner.message; } type = 2; - } else if (retryMessageObject.type == 3 || retryMessageObject.type == 5 || retryMessageObject.videoEditedInfo != null) { + } else if ( + retryMessageObject.type == MessageObject.TYPE_VIDEO || + retryMessageObject.type == MessageObject.TYPE_ROUND_VIDEO || + retryMessageObject.videoEditedInfo != null + ) { type = 3; document = (TLRPC.TL_document) newMsg.media.document; if (retryMessageObject.messageOwner.message != null) { caption = retryMessageObject.messageOwner.message; } - } else if (retryMessageObject.type == 12) { + } else if (retryMessageObject.type == MessageObject.TYPE_CONTACT) { user = new TLRPC.TL_userRequest_old2(); user.phone = newMsg.media.phone_number; user.first_name = newMsg.media.first_name; @@ -3279,7 +3332,13 @@ public class SendMessagesHelper extends BaseController implements NotificationCe user.restriction_reason.add(reason); user.id = newMsg.media.user_id; type = 6; - } else if (retryMessageObject.type == 8 || retryMessageObject.type == 9 || retryMessageObject.type == MessageObject.TYPE_STICKER || retryMessageObject.type == 14 || retryMessageObject.type == MessageObject.TYPE_ANIMATED_STICKER) { + } else if ( + retryMessageObject.type == MessageObject.TYPE_GIF || + retryMessageObject.type == MessageObject.TYPE_FILE || + retryMessageObject.type == MessageObject.TYPE_STICKER || + retryMessageObject.type == MessageObject.TYPE_MUSIC || + retryMessageObject.type == MessageObject.TYPE_ANIMATED_STICKER + ) { document = (TLRPC.TL_document) newMsg.media.document; type = 7; if (retryMessageObject.messageOwner.message != null) { @@ -3595,6 +3654,15 @@ public class SendMessagesHelper extends BaseController implements NotificationCe if (replyToTopMsg != null && replyToTopMsg != replyToMsg) { newMsg.reply_to.reply_to_top_id = replyToTopMsg.getId(); newMsg.reply_to.flags |= 2; + if (replyToTopMsg.isTopicMainMessage) { + newMsg.reply_to.forum_topic = true; + newMsg.reply_to.flags |= 8; + } + } else { + if (replyToMsg.isTopicMainMessage) { + newMsg.reply_to.forum_topic = true; + newMsg.reply_to.flags |= 8; + } } } if (linkedToGroup != 0) { @@ -3690,7 +3758,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe newMsgObj.sendAnimationData = sendAnimationData; newMsgObj.wasJustSent = true; newMsgObj.scheduled = scheduleDate != 0; - if (!newMsgObj.isForwarded() && (newMsgObj.type == 3 || videoEditedInfo != null || newMsgObj.type == MessageObject.TYPE_VOICE) && !TextUtils.isEmpty(newMsg.attachPath)) { + if (!newMsgObj.isForwarded() && (newMsgObj.type == MessageObject.TYPE_VIDEO || videoEditedInfo != null || newMsgObj.type == MessageObject.TYPE_VOICE) && !TextUtils.isEmpty(newMsg.attachPath)) { newMsgObj.attachPathExists = true; } if (newMsgObj.videoEditedInfo != null && videoEditedInfo == null) { @@ -3702,7 +3770,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe objArr.add(newMsgObj); ArrayList arr = new ArrayList<>(); arr.add(newMsg); - MessagesStorage.getInstance(currentAccount).putMessages(arr, false, true, false, 0, scheduleDate != 0); + MessagesStorage.getInstance(currentAccount).putMessages(arr, false, true, false, 0, scheduleDate != 0, 0); MessagesController.getInstance(currentAccount).updateInterfaceWithMessages(peer, objArr, scheduleDate != 0); if (scheduleDate == 0) { NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); @@ -3744,6 +3812,10 @@ public class SendMessagesHelper extends BaseController implements NotificationCe reqSend.silent = newMsg.silent; reqSend.peer = sendToPeer; reqSend.random_id = newMsg.random_id; + if (replyToTopMsg != null) { + reqSend.top_msg_id = replyToTopMsg.getId(); + reqSend.flags |= 512; + } if (updateStickersOreder) { reqSend.update_stickersets_order = true; } @@ -4072,6 +4144,10 @@ public class SendMessagesHelper extends BaseController implements NotificationCe request = new TLRPC.TL_messages_sendMultiMedia(); request.peer = sendToPeer; request.silent = newMsg.silent; + if (replyToTopMsg != null) { + request.top_msg_id = replyToTopMsg.getId(); + request.flags |= 512; + } if (newMsg.reply_to != null && newMsg.reply_to.reply_to_msg_id != 0) { request.flags |= 1; request.reply_to_msg_id = newMsg.reply_to.reply_to_msg_id; @@ -4104,6 +4180,10 @@ public class SendMessagesHelper extends BaseController implements NotificationCe TLRPC.TL_messages_sendMedia request = new TLRPC.TL_messages_sendMedia(); request.peer = sendToPeer; request.silent = newMsg.silent; + if (replyToTopMsg != null) { + request.top_msg_id = replyToTopMsg.getId(); + request.flags |= 512; + } if (newMsg.reply_to != null && newMsg.reply_to.reply_to_msg_id != 0) { request.flags |= 1; request.reply_to_msg_id = newMsg.reply_to.reply_to_msg_id; @@ -4463,6 +4543,10 @@ public class SendMessagesHelper extends BaseController implements NotificationCe TLRPC.TL_messages_forwardMessages reqSend = new TLRPC.TL_messages_forwardMessages(); reqSend.to_peer = sendToPeer; reqSend.with_my_score = retryMessageObject.messageOwner.with_my_score; + if (replyToTopMsg != null) { + reqSend.top_msg_id = replyToTopMsg.getId(); + reqSend.flags |= 512; + } if (params != null && params.containsKey("fwd_id")) { int fwdId = Utilities.parseInt(params.get("fwd_id")); reqSend.drop_author = true; @@ -4503,6 +4587,10 @@ public class SendMessagesHelper extends BaseController implements NotificationCe TLRPC.TL_messages_sendInlineBotResult reqSend = new TLRPC.TL_messages_sendInlineBotResult(); reqSend.peer = sendToPeer; reqSend.random_id = newMsg.random_id; + if (replyToTopMsg != null) { + reqSend.top_msg_id = replyToTopMsg.getId(); + reqSend.flags |= 512; + } if (newMsg.from_id != null) { reqSend.send_as = getMessagesController().getInputPeer(newMsg.from_id); } @@ -4943,7 +5031,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe return; } else if (add) { delayedMessages.remove(key); - getMessagesStorage().putMessages(message.messages, false, true, false, 0, message.scheduled); + getMessagesStorage().putMessages(message.messages, false, true, false, 0, message.scheduled, 0); getMessagesController().updateInterfaceWithMessages(message.peer, message.messageObjects, message.scheduled); if (!message.scheduled) { getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); @@ -5279,7 +5367,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe getNotificationCenter().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newMsgObj.id, newMsgObj, newMsgObj.dialog_id, grouped_id, existFlags, scheduled); getMessagesStorage().getStorageQueue().postRunnable(() -> { getMessagesStorage().updateMessageStateAndId(newMsgObj.random_id, MessageObject.getPeerId(newMsgObj.peer_id), oldId, newMsgObj.id, 0, false, scheduled ? 1 : 0); - getMessagesStorage().putMessages(sentMessages, true, false, false, 0, scheduled); + getMessagesStorage().putMessages(sentMessages, true, false, false, 0, scheduled, 0); AndroidUtilities.runOnUIThread(() -> { getMediaDataController().increasePeerRaiting(newMsgObj.dialog_id); getNotificationCenter().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newMsgObj.id, newMsgObj, newMsgObj.dialog_id, grouped_id, existFlags, scheduled); @@ -5573,7 +5661,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe messageIds.add(oldId); getMessagesController().deleteMessages(messageIds, null, null, newMsgObj.dialog_id, false, true); getMessagesStorage().getStorageQueue().postRunnable(() -> { - getMessagesStorage().putMessages(sentMessages, true, false, false, 0, false); + getMessagesStorage().putMessages(sentMessages, true, false, false, 0, false, 0); AndroidUtilities.runOnUIThread(() -> { ArrayList messageObjects = new ArrayList<>(); messageObjects.add(new MessageObject(msgObj.currentAccount, msgObj.messageOwner, true, true)); @@ -5590,7 +5678,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe getNotificationCenter().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newMsgObj.id, newMsgObj, newMsgObj.dialog_id, 0L, existFlags, scheduled); getMessagesStorage().getStorageQueue().postRunnable(() -> { getMessagesStorage().updateMessageStateAndId(newMsgObj.random_id, MessageObject.getPeerId(newMsgObj.peer_id), oldId, newMsgObj.id, 0, false, scheduled ? 1 : 0); - getMessagesStorage().putMessages(sentMessages, true, false, false, 0, scheduled); + getMessagesStorage().putMessages(sentMessages, true, false, false, 0, scheduled, 0); AndroidUtilities.runOnUIThread(() -> { getMediaDataController().increasePeerRaiting(newMsgObj.dialog_id); getNotificationCenter().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newMsgObj.id, newMsgObj, newMsgObj.dialog_id, 0L, existFlags, scheduled); @@ -6525,7 +6613,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe TLRPC.TL_messages_messages messagesRes = new TLRPC.TL_messages_messages(); messagesRes.messages.add(prevMessage.messageOwner); - accountInstance.getMessagesStorage().putMessages(messagesRes, message.peer, -2, 0, false, scheduleDate != 0); + accountInstance.getMessagesStorage().putMessages(messagesRes, message.peer, -2, 0, false, scheduleDate != 0, 0); instance.sendReadyToSendGroup(message, true, true); } }); @@ -7482,11 +7570,13 @@ public class SendMessagesHelper extends BaseController implements NotificationCe if (thumb != null) { int side = isEncrypted || info.ttl != 0 ? 90 : Math.max(thumb.getWidth(), thumb.getHeight()); size = ImageLoader.scaleAndSaveImage(thumb, side, side, side > 90 ? 80 : 55, isEncrypted); - thumbKey = getKeyForPhotoSize(accountInstance, size, null, true, false); + if (size != null && size.location != null) { + thumbKey = getKeyForPhotoSize(accountInstance, size, null, true, false); - String fileName = size.location.volume_id + "_" + size.location.local_id + ".jpg"; - File fileDir = FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE); - localPath = new File(fileDir, fileName).getAbsolutePath(); + String fileName = size.location.volume_id + "_" + size.location.local_id + ".jpg"; + File fileDir = FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE); + localPath = new File(fileDir, fileName).getAbsolutePath(); + } } document = new TLRPC.TL_document(); document.file_reference = new byte[0]; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java b/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java index b2781924d..292cb4861 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java @@ -97,6 +97,7 @@ public class SharedConfig { public static boolean stickersReorderingHintUsed; public static boolean disableVoiceAudioEffects; public static boolean forceDisableTabletMode; + public static boolean useLNavigation; private static int lastLocalId = -210000; public static String storageCacheDir; @@ -168,6 +169,8 @@ public class SharedConfig { public static int fastScrollHintCount = 3; public static boolean dontAskManageStorage; + public static boolean isFloatingDebugActive; + static { loadConfig(); } @@ -243,7 +246,6 @@ public class SharedConfig { editor.putBoolean("forwardingOptionsHintShown", forwardingOptionsHintShown); editor.putInt("lockRecordAudioVideoHint", lockRecordAudioVideoHint); editor.putString("storageCacheDir", !TextUtils.isEmpty(storageCacheDir) ? storageCacheDir : ""); - editor.putBoolean("hasEmailLogin", hasEmailLogin); if (pendingAppUpdate != null) { try { @@ -262,6 +264,12 @@ public class SharedConfig { editor.putLong("appUpdateCheckTime", lastUpdateCheckTime); editor.apply(); + + editor = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Context.MODE_PRIVATE).edit(); + editor.putBoolean("hasEmailLogin", hasEmailLogin); + editor.putBoolean("useLNavigation", useLNavigation); + editor.putBoolean("floatingDebugActive", isFloatingDebugActive); + editor.apply(); } catch (Exception e) { FileLog.e(e); } @@ -426,6 +434,8 @@ public class SharedConfig { fastScrollHintCount = preferences.getInt("fastScrollHintCount", 3); dontAskManageStorage = preferences.getBoolean("dontAskManageStorage", false); hasEmailLogin = preferences.getBoolean("hasEmailLogin", false); + useLNavigation = preferences.getBoolean("useLNavigation", BuildVars.DEBUG_VERSION); + isFloatingDebugActive = preferences.getBoolean("floatingDebugActive", false); preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); showNotificationsForAllAccounts = preferences.getBoolean("AllAccounts", true); @@ -444,7 +454,7 @@ public class SharedConfig { public static void updateTabletConfig() { if (fontSizeIsDefault) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("userconfing", Context.MODE_PRIVATE); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); fontSize = preferences.getInt("fons_size", AndroidUtilities.isTablet() ? 18 : 16); ivFontSize = preferences.getInt("iv_font_size", fontSize); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SvgHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/SvgHelper.java index 4db9475f9..6a03738a2 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SvgHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SvgHelper.java @@ -39,6 +39,7 @@ import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.os.Build; +import android.util.SparseArray; import org.telegram.ui.ActionBar.Theme; import org.xml.sax.Attributes; @@ -122,6 +123,7 @@ public class SvgHelper { private Theme.ResourcesProvider currentResourcesProvider; private float colorAlpha; private float crossfadeAlpha = 1.0f; + SparseArray overridePaintByPosition = new SparseArray<>(); private boolean aspectFill = true; @@ -225,11 +227,15 @@ public class SvgHelper { Object object = commands.get(a); if (object instanceof Matrix) { canvas.save(); - // canvas.concat((Matrix) object); + canvas.concat((Matrix) object); } else if (object == null) { canvas.restore(); } else { Paint paint; + Paint overridePaint = overridePaintByPosition.get(a); + if (overridePaint == null) { + overridePaint = this.overridePaint; + } if (drawInBackground) { paint = backgroundPaint; } else if (overridePaint != null) { @@ -352,6 +358,14 @@ public class SvgHelper { public void setPaint(Paint paint) { overridePaint = paint; } + + public void setPaint(Paint paint, int position) { + overridePaintByPosition.put(position, paint); + } + + public void copyCommandFromPosition(int position) { + commands.add(commands.get(position)); + } } public static Bitmap getBitmap(int res, int width, int height, int color) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/TopicsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/TopicsController.java new file mode 100644 index 000000000..3457822f8 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/TopicsController.java @@ -0,0 +1,886 @@ +package org.telegram.messenger; + +import android.content.SharedPreferences; +import android.text.TextPaint; +import android.text.TextUtils; +import android.util.SparseArray; + +import androidx.collection.LongSparseArray; + +import org.telegram.SQLite.SQLiteCursor; +import org.telegram.messenger.support.LongSparseIntArray; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.NativeByteBuffer; +import org.telegram.tgnet.RequestDelegate; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.Components.Forum.ForumUtilities; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; + +public class TopicsController extends BaseController { + + public static final int TOPIC_FLAG_TITLE = 1; + public static final int TOPIC_FLAG_ICON = 2; + public static final int TOPIC_FLAG_PIN = 4; + public static final int TOPIC_FLAG_CLOSE = 8; + + + private static final int MAX_PRELOAD_COUNT = 20; + + public static final int LOAD_TYPE_PRELOAD = 0; + public static final int LOAD_TYPE_LOAD_NEXT = 1; + public static final int LOAD_TYPE_LOAD_UNKNOWN = 2; + + LongSparseArray> topicsByChatId = new LongSparseArray<>(); + LongSparseArray> topicsMapByChatId = new LongSparseArray<>(); + LongSparseIntArray topicsIsLoading = new LongSparseIntArray(); + LongSparseIntArray endIsReached = new LongSparseIntArray(); + LongSparseArray topicsByTopMsgId = new LongSparseArray<>(); + + LongSparseIntArray currentOpenTopicsCounter = new LongSparseIntArray(); + LongSparseIntArray openedTopicsBuChatId = new LongSparseIntArray(); + + public TopicsController(int num) { + super(num); + } + + public void preloadTopics(long chatId) { + loadTopics(chatId, true, LOAD_TYPE_PRELOAD); + } + + + public void loadTopics(long chatId) { + loadTopics(chatId, false, LOAD_TYPE_LOAD_NEXT); + } + + private void loadTopics(long chatId, boolean fromCache, int loadType) { + if (topicsIsLoading.get(chatId, 0) != 0) { + return; + } + + if (BuildVars.DEBUG_PRIVATE_VERSION) { + FileLog.d("load topics " + chatId + " fromCache=" + fromCache + " loadType=" + loadType); + } + if (fromCache) { + getMessagesStorage().loadTopics(-chatId, topics -> { + AndroidUtilities.runOnUIThread(() -> { + if (BuildVars.DEBUG_PRIVATE_VERSION) { + FileLog.d("loaded from cache " + chatId + " topics_count=" + (topics == null ? 0 : topics.size())); + } + + topicsIsLoading.put(chatId, 0); + processTopics(chatId, topics, null, fromCache, loadType, -1); + }); + }); + return; + } + + topicsIsLoading.put(chatId, 1); + TLRPC.TL_channels_getForumTopics getForumTopics = new TLRPC.TL_channels_getForumTopics(); + getForumTopics.channel = getMessagesController().getInputChannel(chatId); + if (loadType == LOAD_TYPE_PRELOAD) { + getForumTopics.limit = MAX_PRELOAD_COUNT; + } else if (loadType == LOAD_TYPE_LOAD_NEXT) { + getForumTopics.limit = 100; + TopicsLoadOffset loadOffsets = getLoadOffset(chatId); + getForumTopics.offset_date = loadOffsets.lastMessageDate; + getForumTopics.offset_id = loadOffsets.lastMessageId; + getForumTopics.offset_topic = loadOffsets.lastTopicId; + + if (BuildVars.DEBUG_PRIVATE_VERSION) { + FileLog.d("offset_date=" + loadOffsets.lastMessageDate + " offset_id=" + loadOffsets.lastMessageId + " offset_topic=" + loadOffsets.lastTopicId); + } + } + + getConnectionsManager().sendRequest(getForumTopics, (response, error) -> { + if (response != null) { + SparseArray messagesMap = new SparseArray<>(); + TLRPC.TL_messages_forumTopics topics = (TLRPC.TL_messages_forumTopics) response; + for (int i = 0; i < topics.messages.size(); i++) { + messagesMap.put(topics.messages.get(i).id, topics.messages.get(i)); + } + AndroidUtilities.runOnUIThread(() -> { + getMessagesController().putUsers(((TLRPC.TL_messages_forumTopics) response).users, false); + getMessagesController().putChats(((TLRPC.TL_messages_forumTopics) response).chats, false); + + topicsIsLoading.put(chatId, 0); + processTopics(chatId, topics.topics, messagesMap, false, loadType, ((TLRPC.TL_messages_forumTopics) response).count); + getMessagesStorage().putMessages(topics.messages, false, true, false, 0, false, 0); + + getMessagesStorage().saveTopics(-chatId, topicsByChatId.get(chatId), true, true); + + if (!topics.topics.isEmpty() && loadType == LOAD_TYPE_LOAD_NEXT) { + TLRPC.TL_forumTopic lastTopic = topics.topics.get(topics.topics.size() - 1); + TLRPC.Message lastTopicMessage = messagesMap.get(lastTopic.top_message); + saveLoadOffset(chatId, lastTopic.top_message, lastTopicMessage.date, lastTopic.id); + } else if (getTopics(chatId) == null || getTopics(chatId).size() < topics.count) { + clearLoadingOffset(chatId); + loadTopics(chatId); + } + }); + } else { + AndroidUtilities.runOnUIThread(() -> { + topicsIsLoading.put(chatId, 0); + getNotificationCenter().postNotificationName(NotificationCenter.topicsDidLoaded, chatId, false); + }); + } + + }); + } + + public void processTopics(long chatId, ArrayList newTopics, SparseArray messagesMap, boolean fromCache, int loadType, int totalCount) { + ArrayList topics = topicsByChatId.get(chatId); + ArrayList topicsToReload = null; + LongSparseArray topicsMap = topicsMapByChatId.get(chatId); + + if (topics == null) { + topics = new ArrayList<>(); + topicsByChatId.put(chatId, topics); + + } + if (topicsMap == null) { + topicsMap = new LongSparseArray<>(); + topicsMapByChatId.put(chatId, topicsMap); + } + boolean changed = false; + if (newTopics != null) { + for (int i = 0; i < newTopics.size(); i++) { + TLRPC.TL_forumTopic newTopic = newTopics.get(i); + if (!topicsMap.containsKey(newTopic.id)) { + if (messagesMap != null) { + newTopic.topMessage = messagesMap.get(newTopic.top_message); + newTopic.topicStartMessage = messagesMap.get(newTopic.id); + } + if (newTopic.topMessage == null) { + if (topicsToReload == null) { + topicsToReload = new ArrayList<>(); + } + topicsToReload.add(newTopic); + } else { + if (newTopic.topicStartMessage == null) { + newTopic.topicStartMessage = new TLRPC.TL_message(); + newTopic.topicStartMessage.message = ""; + newTopic.topicStartMessage.id = newTopic.id; + newTopic.topicStartMessage.peer_id = getMessagesController().getPeer(-chatId); + newTopic.topicStartMessage.action = new TLRPC.TL_messageActionTopicCreate(); + newTopic.topicStartMessage.action.title = newTopic.title; + } + topics.add(newTopic); + topicsMap.put(newTopic.id, newTopic); + topicsByTopMsgId.put(messageHash(newTopic.top_message, chatId), newTopic); + changed = true; + } + } + } + } + + if (changed) { + sortTopics(chatId); + } + + if (topicsToReload != null) { + reloadTopics(chatId, topicsToReload); + } else if (loadType == LOAD_TYPE_LOAD_NEXT && topics.size() >= totalCount && totalCount >= 0) { + endIsReached.put(chatId, 1); + } + + getNotificationCenter().postNotificationName(NotificationCenter.topicsDidLoaded, chatId, true); + + if ((loadType == LOAD_TYPE_PRELOAD || (loadType == LOAD_TYPE_PRELOAD && !fromCache)) && fromCache && topicsByChatId.get(chatId).isEmpty()) { + loadTopics(chatId, false, LOAD_TYPE_PRELOAD); + } + } + + private void updateDialogUnreadCount(long chatId) { + int[] counters = getForumUnreadCount(chatId); + int unread = counters[0]; + int unread_mentions = counters[1]; +// getMessagesStorage().getStorageQueue().postRunnable(() -> { +// SQLiteDatabase database = getMessagesStorage().getDatabase(); +// try { +// SQLiteCursor cursor = database.queryFinalized("SELECT unread_count, unread_count_i FROM dialogs WHERE did = " + (-chatId)); +// boolean needUpdate = false; +// if (cursor.next()) { +// int currentUnreadCount = cursor.intValue(0); +// int currentUnreadMentions = cursor.intValue(1); +// if ((unread > 0) != (currentUnreadCount > 0) || (unread_mentions > 0) != (currentUnreadMentions > 0)) { +// needUpdate = true; +// } +// Log.d("kek", "update dialog read by topics " + chatId + " " + unread + " " + unread_mentions + " current " + currentUnreadCount + " " + currentUnreadMentions + " update " + needUpdate); +// } +// cursor.dispose(); +// if (needUpdate) { +// database.executeFast(String.format(Locale.US, "UPDATE dialogs SET unread_count = %d, unread_count_i - %d WHERE did = %d", unread, unread_mentions, -chatId)).stepThis().dispose(); +// SQLiteCursor cursor2 = database.queryFinalized("SELECT unread_count, unread_count_i FROM dialogs WHERE did = " + (-chatId)); +// if (cursor2.next()) { +// int currentUnreadCount = cursor2.intValue(0); +// Log.d("kek", "currentUnreadCount " + currentUnreadCount); +// } +// cursor2.dispose(); +// getMessagesStorage().resetAllUnreadCounters(false); +// } +// } catch (Throwable e) { +// e.printStackTrace(); +// } +// }); + } + + private long messageHash(int messageId, long chatId) { + return chatId + ((long) messageId << 12); + } + + + public ArrayList getTopics(long chatId) { + return topicsByChatId.get(chatId); + } + + private void sortTopics(long chatId) { + sortTopics(chatId, true); + } + + private void sortTopics(long chatId, boolean notify) { + ArrayList topics = topicsByChatId.get(chatId); + if (topics != null) { + if (openedTopicsBuChatId.get(chatId, 0) > 0) { + Collections.sort(topics, Comparator.comparingInt(o -> o.pinned ? -1 : -o.topMessage.date)); + } + if (notify) { + getNotificationCenter().postNotificationName(NotificationCenter.topicsDidLoaded, chatId, true); + } + } + } + + public void updateTopicsWithDeletedMessages(long dialogId, ArrayList messages) { + if (dialogId > 0) { + return; + } + long chatId = -dialogId; + AndroidUtilities.runOnUIThread(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { + ArrayList topicsToUpdate = null; + try { + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT topic_id, top_message FROM topics WHERE did = %d AND top_message IN (%s)", dialogId, TextUtils.join(",", messages))); + while (cursor.next()) { + if (topicsToUpdate == null) { + topicsToUpdate = new ArrayList<>(); + } + + TLRPC.TL_forumTopic topic = new TLRPC.TL_forumTopic(); + topic.id = cursor.intValue(0); + topic.top_message = cursor.intValue(1); + topic.from_id = getMessagesController().getPeer(getUserConfig().clientUserId); + topic.notify_settings = new TLRPC.TL_peerNotifySettings(); + topicsToUpdate.add(topic); + } + cursor.dispose(); + + if (topicsToUpdate != null) { + for (int i = 0; i < topicsToUpdate.size(); i++) { + TLRPC.TL_forumTopic topic = topicsToUpdate.get(i); + cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT mid, data FROM messages_topics WHERE uid = %d AND topic_id = %d ORDER BY mid DESC LIMIT 1", dialogId, topic.id)); + + if (cursor.next()) { + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + message.readAttachPath(data, getUserConfig().clientUserId); + data.reuse(); + topicsByTopMsgId.remove(messageHash(topic.top_message, chatId)); + topic.top_message = message.id; + topic.topMessage = message; + topic.groupedMessages = null; + topicsByTopMsgId.put(messageHash(topic.top_message, chatId), topic); + } + } + cursor.dispose(); + } + + for (int i = 0; i < topicsToUpdate.size(); i++) { + getMessagesStorage().getDatabase().executeFast(String.format(Locale.US, "UPDATE topics SET top_message = %d WHERE did = %d AND topic_id = %d", topicsToUpdate.get(i).top_message, dialogId, topicsToUpdate.get(i).id)).stepThis().dispose(); + } + } + + + } catch (Exception e) { + e.printStackTrace(); + } + ArrayList finalTopicsToUpdate = topicsToUpdate; + getMessagesStorage().loadGroupedMessagesForTopics(dialogId, finalTopicsToUpdate); + if (finalTopicsToUpdate != null) { + AndroidUtilities.runOnUIThread(() -> { + boolean changed = false; + ArrayList topicsToReload = null; + + for (int i = 0; i < finalTopicsToUpdate.size(); i++) { + TLRPC.TL_forumTopic fromUpdate = finalTopicsToUpdate.get(i); + LongSparseArray map = topicsMapByChatId.get(chatId); + TLRPC.TL_forumTopic toUpdate; + if (map != null) { + toUpdate = map.get(fromUpdate.id); + if (toUpdate != null && fromUpdate.top_message != -1 && fromUpdate.topMessage != null) { + topicsByTopMsgId.remove(messageHash(toUpdate.top_message, chatId)); + toUpdate.top_message = fromUpdate.topMessage.id; + toUpdate.topMessage = fromUpdate.topMessage; + toUpdate.groupedMessages = fromUpdate.groupedMessages; + + topicsByTopMsgId.put(messageHash(toUpdate.top_message, chatId), toUpdate); + changed = true; + } else if (fromUpdate.top_message == -1 || fromUpdate.topMessage == null) { + if (topicsToReload == null) { + topicsToReload = new ArrayList<>(); + } + topicsToReload.add(fromUpdate); + } + } + } + if (changed) { + sortTopics(chatId); + } + if (topicsToReload != null) { + reloadTopics(chatId, topicsToReload); + } + }); + } + }); + }); + } + + private void reloadTopics(long chatId, ArrayList topicsToReload) { + TLRPC.TL_channels_getForumTopicsByID req = new TLRPC.TL_channels_getForumTopicsByID(); + for (int i = 0; i < topicsToReload.size(); i++) { + req.topics.add(topicsToReload.get(i).id); + } + req.channel = getMessagesController().getInputChannel(chatId); + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + if (response != null) { + SparseArray messagesMap = new SparseArray<>(); + TLRPC.TL_messages_forumTopics topics = (TLRPC.TL_messages_forumTopics) response; + for (int i = 0; i < topics.messages.size(); i++) { + messagesMap.put(topics.messages.get(i).id, topics.messages.get(i)); + } + AndroidUtilities.runOnUIThread(() -> { + getMessagesController().putUsers(((TLRPC.TL_messages_forumTopics) response).users, false); + getMessagesController().putChats(((TLRPC.TL_messages_forumTopics) response).chats, false); + + processTopics(chatId, topics.topics, messagesMap, false, LOAD_TYPE_LOAD_UNKNOWN, -1); + getMessagesStorage().putMessages(topics.messages, false, true, false, 0, false, 0); + getMessagesStorage().saveTopics(-chatId, topicsByChatId.get(chatId), true, true); + }); + } + })); + } + + public void updateMaxReadId(long chatId, int topicId, int readMaxId, int unreadCount) { + TLRPC.TL_forumTopic topic = findTopic(chatId, topicId); + if (topic != null) { + topic.read_inbox_max_id = readMaxId; + topic.unread_count = unreadCount; + sortTopics(chatId); + } + } + + public TLRPC.TL_forumTopic findTopic(long chatId, int topicId) { + LongSparseArray topicsMap = topicsMapByChatId.get(chatId); + if (topicsMap != null) { + return topicsMap.get(topicId); + } + return null; + } + + public String getTopicName(TLRPC.Chat chat, MessageObject message) { + if (message.messageOwner.reply_to == null) { + return null; + } + int topicId = message.messageOwner.reply_to.reply_to_top_id; + if (topicId == 0) { + topicId = message.messageOwner.reply_to.reply_to_msg_id; + } + if (topicId != 0) { + TLRPC.TL_forumTopic topic = findTopic(chat.id, topicId); + if (topic != null) { + return topic.title; + } + } + return ""; + } + + public CharSequence getTopicIconName(TLRPC.Chat chat, MessageObject message, TextPaint paint) { + if (message.messageOwner.reply_to == null) { + return null; + } + int topicId = message.messageOwner.reply_to.reply_to_top_id; + if (topicId == 0) { + topicId = message.messageOwner.reply_to.reply_to_msg_id; + } + if (topicId != 0) { + TLRPC.TL_forumTopic topic = findTopic(chat.id, topicId); + if (topic != null) { + return ForumUtilities.getTopicSpannedName(topic, paint); + } + } + return null; + } + + private final static int[] countsTmp = new int[4]; + + public int[] getForumUnreadCount(long chatId) { + ArrayList topics = topicsByChatId.get(chatId); + Arrays.fill(countsTmp, 0); + if (topics != null) { + for (int i = 0; i < topics.size(); i++) { + TLRPC.TL_forumTopic topic = topics.get(i); + countsTmp[0] += topic.unread_count > 0 ? 1 : 0; + countsTmp[1] += topic.unread_mentions_count > 0 ? 1 : 0; + countsTmp[2] += topic.unread_reactions_count > 0 ? 1 : 0; + if (!getMessagesController().isDialogMuted(-chatId, topic.id)) { + countsTmp[3] += topic.unread_count; + } + + } + } + return countsTmp; + } + + public void onTopicCreated(long dialogId, TLRPC.TL_forumTopic forumTopic, boolean saveInDatabase) { + LongSparseArray map = topicsMapByChatId.get(-dialogId); + if (findTopic(-dialogId, forumTopic.id) != null) { + return; + } + if (map == null) { + map = new LongSparseArray<>(); + topicsMapByChatId.put(-dialogId, map); + } + ArrayList list = topicsByChatId.get(-dialogId); + if (list == null) { + list = new ArrayList<>(); + topicsByChatId.put(-dialogId, list); + } + map.put(forumTopic.id, forumTopic); + list.add(forumTopic); + if (saveInDatabase) { + getMessagesStorage().saveTopics(dialogId, Collections.singletonList(forumTopic), false, true); + } + sortTopics(-dialogId, true); + } + + public void onTopicEdited(long dialogId, TLRPC.TL_forumTopic forumTopic) { + getMessagesStorage().updateTopicData(dialogId, forumTopic, TOPIC_FLAG_ICON + TOPIC_FLAG_TITLE); + sortTopics(-dialogId); + } + + public void deleteTopics(long chatId, ArrayList topicIds) { + ArrayList topicsArray = topicsByChatId.get(chatId); + LongSparseArray topicsMap = topicsMapByChatId.get(chatId); + if (topicsMap != null && topicsArray != null) { + for (int i = 0; i < topicIds.size(); i++) { + int topicId = topicIds.get(i); + TLRPC.TL_forumTopic topic = topicsMap.get(topicId); + topicsMap.remove(topicId); + if (topic != null) { + topicsByTopMsgId.remove(messageHash(topic.top_message, chatId)); + topicsArray.remove(topic); + } + } + sortTopics(chatId); + } + for (int i = 0; i < topicIds.size(); i++) { + deleteTopic(chatId, topicIds.get(i), 0); + } + } + + private void deleteTopic(long chatId, int topicId, int offset) { + TLRPC.TL_channels_deleteTopicHistory deleteTopicHistory = new TLRPC.TL_channels_deleteTopicHistory(); + deleteTopicHistory.channel = getMessagesController().getInputChannel(chatId); + deleteTopicHistory.top_msg_id = topicId; + if (offset == 0) { + getMessagesStorage().removeTopic(-chatId, topicId); + } + ConnectionsManager.getInstance(currentAccount).sendRequest(deleteTopicHistory, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error == null) { + TLRPC.TL_messages_affectedHistory res = (TLRPC.TL_messages_affectedHistory) response; + getMessagesController().processNewChannelDifferenceParams(res.pts, res.pts_count, chatId); + if (res.offset > 0) { + deleteTopic(chatId, topicId, res.offset); + } + } + + } + }); + } + + public void toggleCloseTopic(long chatId, int topicId, boolean close) { + TLRPC.TL_channels_editForumTopic req = new TLRPC.TL_channels_editForumTopic(); + req.channel = getMessagesController().getInputChannel(chatId); + req.topic_id = topicId; + req.flags |= 4; + req.closed = close; + + LongSparseArray topicsMap = topicsMapByChatId.get(chatId); + if (topicsMap != null) { + TLRPC.TL_forumTopic topic = topicsMap.get(topicId); + if (topic != null) { + topic.closed = close; + getMessagesStorage().updateTopicData(-chatId, topic, TOPIC_FLAG_CLOSE); + } + } + + ConnectionsManager.getInstance(currentAccount).sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error == null) { + + } + } + }); + } + + public void pinTopic(long chatId, int topicId, boolean pin) { + TLRPC.TL_channels_updatePinnedForumTopic req = new TLRPC.TL_channels_updatePinnedForumTopic(); + req.channel = getMessagesController().getInputChannel(chatId); + req.topic_id = topicId; + req.pinned = pin; + + ArrayList topics = topicsByChatId.get(chatId); + if (topics != null) { + for (int i = 0; i < topics.size(); ++i) { + TLRPC.TL_forumTopic topic = topics.get(i); + if (topic != null && (topicId == topic.id && pin) != topic.pinned) { + topic.pinned = topicId == topic.id && pin; +// topic.flags = topic.pinned ? (topic.flags | 8) : (topic.flags &~ 8); + getMessagesStorage().updateTopicData(-chatId, topic, TOPIC_FLAG_PIN); + } + } + } + + sortTopics(chatId); + + ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + if (response instanceof TLRPC.Updates) { +// getMessagesController().processUpdates((TLRPC.Updates) response, false); + } + }); + } + + public void updateMentionsUnread(long dialogId, int topicId, int topicMentionsCount) { + AndroidUtilities.runOnUIThread(() -> { + TLRPC.TL_forumTopic topic = findTopic(-dialogId, topicId); + if (topic != null) { + topic.unread_mentions_count = topicMentionsCount; + sortTopics(-dialogId, true); + } + }); + } + + public int updateReactionsUnread(long dialogId, int topicId, int count, boolean increment) { + TLRPC.TL_forumTopic topic = findTopic(-dialogId, topicId); + int totalCount = -1; + if (topic != null) { + if (increment) { + topic.unread_reactions_count += count; + if (topic.unread_reactions_count < 0) { + topic.unread_reactions_count = 0; + } + } else { + topic.unread_reactions_count = count; + } + totalCount = topic.unread_reactions_count; + sortTopics(-dialogId, true); + } + return totalCount; + } + + public void markAllReactionsAsRead(long chatId, int topicId) { + TLRPC.TL_forumTopic topic = findTopic(chatId, topicId); + if (topic != null && topic.unread_reactions_count > 0) { + topic.unread_reactions_count = 0; + sortTopics(chatId); + } + } + + public TLRPC.Message getLastMessage(long id) { + ArrayList topics = topicsByChatId.get(id); + if (!topics.isEmpty()) { + return topics.get(0).topMessage; + } + return null; + } + + LongSparseArray offsets = new LongSparseArray<>(); + + public TopicsLoadOffset getLoadOffset(long chatId) { + TopicsLoadOffset offset = offsets.get(chatId); + if (offset != null) { + return offset; + } + return new TopicsLoadOffset(); +// SharedPreferences sharedPreferences = getUserConfig().getPreferences(); +// TopicsLoadOffset topicsLoadOffset = new TopicsLoadOffset(); +// topicsLoadOffset.lastMessageId = sharedPreferences.getInt("topics_load_offset_message_id_" + chatId, 0); +// topicsLoadOffset.lastMessageDate = sharedPreferences.getInt("topics_load_offset_date_" + chatId, 0); +// topicsLoadOffset.lastMessageId = sharedPreferences.getInt("topics_load_offset_topic_id_" + chatId, 0); +// return topicsLoadOffset; + } + + public void saveLoadOffset(long chatId, int lastMessageId, int lastMessageDate, int lastTopicId) { + TopicsLoadOffset offset = new TopicsLoadOffset(); + offset.lastMessageId = lastMessageId; + offset.lastMessageDate = lastMessageDate; + offset.lastTopicId = lastTopicId; + offsets.put(chatId, offset); +// SharedPreferences.Editor editor = getUserConfig().getPreferences().edit(); +// editor.putInt("topics_load_offset_message_id_" + chatId, lastMessageId); +// editor.putInt("topics_load_offset_date_" + chatId, lastMessageDate); +// editor.putInt("topics_load_offset_topic_id_" + chatId, lastTopicId); +// editor.apply(); + } + + public void clearLoadingOffset(long chatId) { + offsets.remove(chatId); +// SharedPreferences.Editor editor = getUserConfig().getPreferences().edit(); +// editor.remove("topics_load_offset_message_id_" + chatId); +// editor.remove("topics_load_offset_date_" + chatId); +// editor.remove("topics_load_offset_topic_id_" + chatId); +// editor.apply(); + } + + public boolean endIsReached(long chatId) { + return endIsReached.get(chatId, 0) == 1; + } + + public void processUpdate(List topicUpdates) { + AndroidUtilities.runOnUIThread(() -> { + HashSet changedDialogs = new HashSet<>(); + LongSparseArray> topicsToReload = null; + for (int i = 0; i < topicUpdates.size(); i++) { + TopicUpdate update = topicUpdates.get(i); + if (update.reloadTopic) { + if (topicsToReload == null) { + topicsToReload = new LongSparseArray<>(); + } + ArrayList arrayList = topicsToReload.get(update.dialogId); + if (arrayList == null) { + arrayList = new ArrayList<>(); + topicsToReload.put(update.dialogId, arrayList); + } + TLRPC.TL_forumTopic forumTopic = new TLRPC.TL_forumTopic(); + forumTopic.id = update.topicId; + arrayList.add(forumTopic); + } else { + TLRPC.TL_forumTopic topic = findTopic(-update.dialogId, update.topicId); + if (topic != null) { + if (update.onlyCounters) { + if (update.unreadCount >= 0) { + topic.unread_count = update.unreadCount; + } + if (update.unreadMentions >= 0) { + topic.unread_mentions_count = update.unreadMentions; + } + } else { + topicsByTopMsgId.remove(messageHash(topic.top_message, -update.dialogId)); + topic.topMessage = update.topMessage; + topic.groupedMessages = update.groupedMessages; + topic.top_message = update.topMessageId; + topic.unread_count = update.unreadCount; + topic.unread_mentions_count = update.unreadMentions; + topicsByTopMsgId.put(messageHash(topic.top_message, -update.dialogId), topic); + } + changedDialogs.add(-update.dialogId); + } + } + } + for (Long changedDialog : changedDialogs) { + sortTopics(changedDialog, true); + } + + if (topicsToReload != null) { + for (int i = 0; i < topicsToReload.size(); i++) { + long dialogId = topicsToReload.keyAt(i); + ArrayList topics = topicsToReload.valueAt(i); + reloadTopics(-dialogId, topics); + } + } + + }); + } + + public boolean isLoading(long chatId) { + return topicsIsLoading.get(chatId, 0) == 1 && (topicsByChatId.get(chatId) == null || topicsByChatId.get(chatId).isEmpty()); + } + + public void onTopicsDeletedServerSide(ArrayList topicsToDelete) { + AndroidUtilities.runOnUIThread(() -> { + HashSet changedChatId = new HashSet<>(); + for (int i = 0; i < topicsToDelete.size(); i++) { + MessagesStorage.TopicKey topicKey = topicsToDelete.get(i); + long chatId = -topicKey.dialogId; + LongSparseArray topicsMap = topicsMapByChatId.get(chatId); + if (topicsMap != null) { + topicsMap.remove(topicKey.topicId); + } + ArrayList topics = topicsByChatId.get(chatId); + if (topics != null) { + for (int k = 0; k < topics.size(); k++) { + if (topics.get(k).id == topicKey.topicId) { + topics.remove(k); + getNotificationCenter().postNotificationName(NotificationCenter.dialogDeleted, -chatId, topicKey.topicId); + changedChatId.add(chatId); + break; + } + } + } + + } + for (Long chatId : changedChatId) { + sortTopics(chatId, true); + } + }); + } + + public void reloadTopics(long chatId) { + AndroidUtilities.runOnUIThread(() -> { + topicsByChatId.remove(chatId); + topicsMapByChatId.remove(chatId); + endIsReached.delete(chatId); + clearLoadingOffset(chatId); + + TLRPC.Chat chat = getMessagesController().getChat(chatId); + if (chat != null && chat.forum) { + preloadTopics(chatId); + } + }); + } + + public void databaseCleared() { + AndroidUtilities.runOnUIThread(() -> { + topicsByChatId.clear(); + topicsMapByChatId.clear(); + endIsReached.clear(); + + SharedPreferences.Editor editor = getUserConfig().getPreferences().edit(); + for (String key : getUserConfig().getPreferences().getAll().keySet()) { + if (key.startsWith("topics_load_offset_message_id_")) { + editor.remove(key); + } + if (key.startsWith("topics_load_offset_date_")) { + editor.remove(key); + } + if (key.startsWith("topics_load_offset_topic_id_")) { + editor.remove(key); + } + } + editor.apply(); + }); + } + + public void updateReadOutbox(HashMap topicsReadOutbox) { + + AndroidUtilities.runOnUIThread(() -> { + HashSet updatedChats = new HashSet<>(); + for (MessagesStorage.TopicKey topicKey : topicsReadOutbox.keySet()) { + int value = topicsReadOutbox.get(topicKey); + TLRPC.TL_forumTopic topic = findTopic(-topicKey.dialogId, topicKey.topicId); + if (topic != null) { + topic.read_outbox_max_id = Math.max(topic.read_outbox_max_id, value); + updatedChats.add(-topicKey.dialogId); + if ( topic.read_outbox_max_id >= topic.topMessage.id) { + topic.topMessage.unread = false; + } + } + } + //TODO topics + // optimize move to mask update + for (Long chatId : updatedChats) { + NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.topicsDidLoaded, chatId, true); + } + }); + } + + public void updateTopicInUi(long dialogId, TLRPC.TL_forumTopic forumTopic, int flags) { + TLRPC.TL_forumTopic topic = findTopic(-dialogId, forumTopic.id); + if (topic != null) { + if ((flags & TOPIC_FLAG_TITLE) != 0) { + topic.title = forumTopic.title; + } + if ((flags & TOPIC_FLAG_ICON) != 0) { + topic.icon_emoji_id = forumTopic.icon_emoji_id; + } + if ((flags & TOPIC_FLAG_CLOSE) != 0) { + topic.closed = forumTopic.closed; + } + if ((flags & TOPIC_FLAG_PIN) != 0) { + topic.pinned = forumTopic.pinned; + } + sortTopics(-dialogId); + } + } + + public void processEditedMessages(LongSparseArray> editingMessagesFinal) { + HashSet changedChatId = new HashSet<>(); + for (int i = 0; i < editingMessagesFinal.size(); i++) { + ArrayList messageObjects = editingMessagesFinal.valueAt(i); + for (int j = 0; j < messageObjects.size(); j++) { + TLRPC.TL_forumTopic topic = topicsByTopMsgId.get(messageHash(messageObjects.get(j).getId(), -messageObjects.get(j).getDialogId())); + if (topic != null) { + topic.topMessage = messageObjects.get(j).messageOwner; + changedChatId.add(-messageObjects.get(j).getDialogId()); + } + } + } + for (Long chatId : changedChatId) { + sortTopics(chatId, true); + } + } + + public void processEditedMessage(TLRPC.Message newMsg) { + TLRPC.TL_forumTopic topic = topicsByTopMsgId.get(messageHash(newMsg.id, -newMsg.dialog_id)); + if (topic != null) { + topic.topMessage = newMsg; + sortTopics(-newMsg.dialog_id, true); + } + } + + private class TopicsLoadOffset { + int lastMessageId; + int lastMessageDate; + int lastTopicId; + } + + public static class TopicUpdate { + long dialogId; + int topicId; + int unreadMentions; + int unreadCount; + int topMessageId; + TLRPC.Message topMessage; + ArrayList groupedMessages; + boolean reloadTopic; + boolean onlyCounters; + } + + + public void onTopicFragmentResume(long chatId) { + int v = openedTopicsBuChatId.get(chatId, 0); + openedTopicsBuChatId.put(chatId, v + 1); + sortTopics(chatId); + } + + public void onTopicFragmentPause(long chatId) { + int v = openedTopicsBuChatId.get(chatId, 0); + v--; + if (v < 0) { + v = 0; + } + openedTopicsBuChatId.put(chatId, v); + + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java b/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java index 3b5ed7d7e..41e33b371 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java @@ -70,8 +70,10 @@ public class UserConfig extends BaseController { public String premiumGiftsStickerPack; public String genericAnimationsStickerPack; + public String defaultTopicIcons; public long lastUpdatedPremiumGiftsStickerPack; public long lastUpdatedGenericAnimations; + public long lastUpdatedDefaultTopicIcons; public volatile byte[] savedPasswordHash; public volatile byte[] savedSaltedPassword; @@ -405,7 +407,7 @@ public class UserConfig extends BaseController { } } - private SharedPreferences getPreferences() { + public SharedPreferences getPreferences() { if (currentAccount == 0) { return ApplicationLoader.applicationContext.getSharedPreferences("userconfing", Context.MODE_PRIVATE); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java index e8acef487..a95f6f4c1 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java @@ -22,6 +22,7 @@ import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.SecureRandom; +import java.util.HashMap; import java.util.Random; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -112,32 +113,36 @@ public class Utilities { if (value == null) { return 0; } - int val = 0; - try { - int start = -1, end; - for (end = 0; end < value.length(); ++end) { - char character = value.charAt(end); - boolean allowedChar = character == '-' || character >= '0' && character <= '9'; - if (allowedChar && start < 0) { - start = end; - } else if (!allowedChar && start >= 0) { - end++; - break; + if (BuildConfig.BUILD_HOST_IS_WINDOWS) { + Matcher matcher = pattern.matcher(value); + if (matcher.find()) { + return Integer.valueOf(matcher.group()); + } + } else { + int val = 0; + try { + int start = -1, end; + for (end = 0; end < value.length(); ++end) { + char character = value.charAt(end); + boolean allowedChar = character == '-' || character >= '0' && character <= '9'; + if (allowedChar && start < 0) { + start = end; + } else if (!allowedChar && start >= 0) { + end++; + break; + } } - } - if (start >= 0) { - String str = value.subSequence(start, end).toString(); + if (start >= 0) { + String str = value.subSequence(start, end).toString(); // val = parseInt(str); - val = Integer.parseInt(str); - } -// Matcher matcher = pattern.matcher(value); -// if (matcher.find()) { -// String num = matcher.group(0); -// val = Integer.parseInt(num); -// } - } catch (Exception ignore) {} - return val; + val = Integer.parseInt(str); + } + } catch (Exception ignore) {} + return val; + } + return 0; } + private static int parseInt(final String s) { int num = 0; boolean negative = true; @@ -464,4 +469,12 @@ public class Utilities { public static interface Callback { public void run(T arg); } + + public static Value getOrDefault(HashMap map, Key key, Value defaultValue) { + Value v = map.get(key); + if (v == null) { + return defaultValue; + } + return v; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/WearReplyReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/WearReplyReceiver.java index 27f472a4b..8e1b6b78f 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/WearReplyReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/WearReplyReceiver.java @@ -33,6 +33,7 @@ public class WearReplyReceiver extends BroadcastReceiver { } long dialogId = intent.getLongExtra("dialog_id", 0); int maxId = intent.getIntExtra("max_id", 0); + int topicId = intent.getIntExtra("topic_id", 0); int currentAccount = intent.getIntExtra("currentAccount", 0); if (dialogId == 0 || maxId == 0 || !UserConfig.isValidAccount(currentAccount)) { return; @@ -45,7 +46,7 @@ public class WearReplyReceiver extends BroadcastReceiver { TLRPC.User user1 = accountInstance.getMessagesStorage().getUserSync(dialogId); AndroidUtilities.runOnUIThread(() -> { accountInstance.getMessagesController().putUser(user1, true); - sendMessage(accountInstance, text, dialogId, maxId); + sendMessage(accountInstance, text, dialogId, topicId, maxId); }); }); return; @@ -57,17 +58,31 @@ public class WearReplyReceiver extends BroadcastReceiver { TLRPC.Chat chat1 = accountInstance.getMessagesStorage().getChatSync(-dialogId); AndroidUtilities.runOnUIThread(() -> { accountInstance.getMessagesController().putChat(chat1, true); - sendMessage(accountInstance, text, dialogId, maxId); + sendMessage(accountInstance, text, dialogId, topicId, maxId); }); }); return; } } - sendMessage(accountInstance, text, dialogId, maxId); + sendMessage(accountInstance, text, dialogId, topicId, maxId); } - private void sendMessage(AccountInstance accountInstance, CharSequence text, long dialog_id, int max_id) { - accountInstance.getSendMessagesHelper().sendMessage(text.toString(), dialog_id, null, null, null, true, null, null, null, true, 0, null, false); - accountInstance.getMessagesController().markDialogAsRead(dialog_id, max_id, max_id, 0, false, 0, 0, true, 0); + private void sendMessage(AccountInstance accountInstance, CharSequence text, long dialog_id, int topicId, int max_id) { + MessageObject replyToMsgId = null; + if (topicId != 0) { + TLRPC.TL_message topicStartMessage = new TLRPC.TL_message(); + topicStartMessage.message = ""; + topicStartMessage.id = topicId; + topicStartMessage.peer_id = accountInstance.getMessagesController().getPeer(dialog_id); + topicStartMessage.action = new TLRPC.TL_messageActionTopicCreate(); + topicStartMessage.action.title = ""; + replyToMsgId = new MessageObject(accountInstance.getCurrentAccount(), topicStartMessage, false, false); + } + + accountInstance.getSendMessagesHelper().sendMessage(text.toString(), dialog_id, replyToMsgId, null, null, true, null, null, null, true, 0, null, false); + //TODO handle topics + if (topicId == 0) { + accountInstance.getMessagesController().markDialogAsRead(dialog_id, max_id, max_id, 0, false, topicId, 0, true, 0); + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java b/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java index 219c225d8..029212870 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java @@ -44,6 +44,7 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.LaunchActivity; +import org.w3c.dom.Text; import java.lang.ref.WeakReference; import java.net.URLEncoder; @@ -181,11 +182,35 @@ public class Browser { return url.matches("^(https" + (forceHttps ? "" : "?") + "://)?(te\\.?legra\\.ph|graph\\.org).*"); // telegra.ph, te.legra.ph, graph.org } + public static String extractUsername(String link) { + if (link == null || TextUtils.isEmpty(link)) { + return null; + } + if (link.startsWith("@")) { + return link.substring(1); + } + if (link.startsWith("t.me/")) { + return link.substring(5); + } + if (link.startsWith("http://t.me/")) { + return link.substring(12); + } + if (link.startsWith("https://t.me/")) { + return link.substring(13); + } + Matcher prefixMatcher = LaunchActivity.PREFIX_T_ME_PATTERN.matcher(link); + if (prefixMatcher.find()) { + return prefixMatcher.group(1); + } + return null; + } + public static boolean urlMustNotHaveConfirmation(String url) { return ( isTelegraphUrl(url, false, true) || url.matches("^(https://)?t\\.me/iv\\??.*") || // t.me/iv? - url.matches("^(https://)?telegram\\.org/(blog|tour)/?.*") // telegram.org/blog, telegram.org/tour + url.matches("^(https://)?telegram\\.org/(blog|tour)/?.*") || // telegram.org/blog, telegram.org/tour + url.matches("^(https://)?fragment\\.com/?.*") // fragment.com ); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraView.java b/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraView.java index a80b830c0..40c13c13e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraView.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraView.java @@ -546,7 +546,7 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur return x; } - public void focusToPoint(int x, int y) { + public void focusToPoint(int x, int y, boolean visible) { Rect focusRect = calculateTapArea(x, y, 1f); Rect meteringRect = calculateTapArea(x, y, 1.5f); @@ -554,13 +554,19 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur cameraSession.focusToRect(focusRect, meteringRect); } - focusProgress = 0.0f; - innerAlpha = 1.0f; - outerAlpha = 1.0f; - cx = x; - cy = y; - lastDrawTime = System.currentTimeMillis(); - invalidate(); + if (visible) { + focusProgress = 0.0f; + innerAlpha = 1.0f; + outerAlpha = 1.0f; + cx = x; + cy = y; + lastDrawTime = System.currentTimeMillis(); + invalidate(); + } + } + + public void focusToPoint(int x, int y) { + focusToPoint(x, y, true); } public void setZoom(float value) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/utils/BitmapsCache.java b/TMessagesProj/src/main/java/org/telegram/messenger/utils/BitmapsCache.java index df7c2ad5c..7673d4a02 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/utils/BitmapsCache.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/utils/BitmapsCache.java @@ -74,22 +74,40 @@ public class BitmapsCache { public void createCache() { try { long time = System.currentTimeMillis(); - RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); if (file.exists()) { + RandomAccessFile randomAccessFile = null; try { + randomAccessFile = new RandomAccessFile(file, "r"); cacheCreated = randomAccessFile.readBoolean(); + int framesCount = randomAccessFile.readInt(); + if (framesCount == 0) { + cacheCreated = false; + } if (cacheCreated) { randomAccessFile.close(); return; } else { file.delete(); } - } catch (Exception e) { + } catch (Throwable e) { + try { + file.delete(); + } catch (Throwable e2) { + } + } finally { + if (randomAccessFile != null) { + try { + randomAccessFile.close(); + } catch (Throwable e2) { + + } + } } } - randomAccessFile.close(); - randomAccessFile = new RandomAccessFile(file, "rw"); + + RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); + Bitmap[] bitmap = new Bitmap[N]; ByteArrayOutputStream[] byteArrayOutputStream = new ByteArrayOutputStream[N]; @@ -267,10 +285,15 @@ public class BitmapsCache { return cacheCreated; } RandomAccessFile randomAccessFile = null; + int framesCount; try { synchronized (mutex) { randomAccessFile = new RandomAccessFile(file, "r"); cacheCreated = randomAccessFile.readBoolean(); + framesCount = randomAccessFile.readInt(); + if (framesCount <= 0) { + cacheCreated = false; + } } } catch (Exception e) { @@ -283,7 +306,7 @@ public class BitmapsCache { } } } - checkCache = false; + checkCache = true; return cacheCreated; } @@ -346,7 +369,7 @@ public class BitmapsCache { } catch (FileNotFoundException e) { } catch (Throwable e) { - FileLog.e(e); + FileLog.e(e, false); } if (randomAccessFile != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java b/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java index 20f762466..fc443af53 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java @@ -77,6 +77,7 @@ public class ConnectionsManager extends BaseController { public final static int RequestFlagForceDownload = 32; public final static int RequestFlagInvokeAfter = 64; public final static int RequestFlagNeedQuickAck = 128; + public final static int RequestFlagDoNotWaitFloodWait = 1024; public final static int ConnectionStateConnecting = 1; public final static int ConnectionStateWaitingForNetwork = 2; @@ -1296,7 +1297,7 @@ public class ConnectionsManager extends BaseController { buffer.writeBytes(bytes); return buffer; } catch (Throwable e) { - FileLog.e(e); + FileLog.e(e, false); } finally { try { if (httpConnectionStream != null) { @@ -1392,7 +1393,7 @@ public class ConnectionsManager extends BaseController { task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null); currentTask = task; }); - FileLog.e(e); + FileLog.e(e, false); } return null; } diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java b/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java index 4e7382799..e5152723a 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java @@ -587,6 +587,14 @@ public class NativeByteBuffer extends AbstractSerializedData { return buffer.remaining(); } + @Override + protected void finalize() throws Throwable { + if (!reused) { + reuse(); + } + super.finalize(); + } + public static native long native_getFreeBuffer(int length); public static native ByteBuffer native_getJavaByteBuffer(long address); public static native int native_limit(long address); diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java index 5a2db99ba..da32dfc87 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java @@ -17,6 +17,7 @@ import androidx.annotation.Nullable; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.ImageLoader; +import org.telegram.messenger.MessageObject; import org.telegram.messenger.SharedConfig; import org.telegram.messenger.Utilities; @@ -67,7 +68,7 @@ public class TLRPC { public static final int MESSAGE_FLAG_HAS_BOT_ID = 0x00000800; public static final int MESSAGE_FLAG_EDITED = 0x00008000; - public static final int LAYER = 147; + public static final int LAYER = 148; public static class TL_stats_megagroupStats extends TLObject { public static int constructor = 0xef7ff916; @@ -424,6 +425,7 @@ public class TLRPC { public boolean change_info; public boolean invite_users; public boolean pin_messages; + public boolean manage_topics; public int until_date; public static TL_chatBannedRights TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { @@ -453,6 +455,7 @@ public class TLRPC { change_info = (flags & 1024) != 0; invite_users = (flags & 32768) != 0; pin_messages = (flags & 131072) != 0; + manage_topics = (flags & 262144) != 0; until_date = stream.readInt32(exception); } @@ -470,6 +473,7 @@ public class TLRPC { flags = change_info ? (flags | 1024) : (flags &~ 1024); flags = invite_users ? (flags | 32768) : (flags &~ 32768); flags = pin_messages ? (flags | 131072) : (flags &~ 131072); + flags = manage_topics ? (flags | 262144) : (flags &~ 262144); stream.writeInt32(flags); stream.writeInt32(until_date); } @@ -973,6 +977,9 @@ public class TLRPC { public static NotifyPeer TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { NotifyPeer result = null; switch (constructor) { + case 0x226e6308: + result = new TL_notifyForumTopic(); + break; case 0xd612e8ef: result = new TL_notifyBroadcasts(); break; @@ -996,6 +1003,24 @@ public class TLRPC { } } + public static class TL_notifyForumTopic extends NotifyPeer { + public static int constructor = 0x226e6308; + + public Peer peer; + public int top_msg_id; + + public void readParams(AbstractSerializedData stream, boolean exception) { + peer = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + top_msg_id = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + stream.writeInt32(top_msg_id); + } + } + public static class TL_notifyBroadcasts extends NotifyPeer { public static int constructor = 0xd612e8ef; @@ -4201,6 +4226,7 @@ public class TLRPC { public boolean anonymous; public boolean manage_call; public boolean other; + public boolean manage_topics; public static TL_chatAdminRights TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { if (TL_chatAdminRights.constructor != constructor) { @@ -4228,6 +4254,7 @@ public class TLRPC { anonymous = (flags & 1024) != 0; manage_call = (flags & 2048) != 0; other = (flags & 4096) != 0; + manage_topics = (flags & 8192) != 0; } public void serializeToStream(AbstractSerializedData stream) { @@ -4243,6 +4270,7 @@ public class TLRPC { flags = anonymous ? (flags | 1024) : (flags &~ 1024); flags = manage_call ? (flags | 2048) : (flags &~ 2048); flags = other ? (flags | 4096) : (flags &~ 4096); + flags = manage_topics ? (flags | 8192) : (flags &~ 8192); stream.writeInt32(flags); } } @@ -10852,25 +10880,47 @@ public class TLRPC { } } - public static class TL_messages_sponsoredMessages extends TLObject { - public static int constructor = 0x65a4c7d5; - + public static abstract class messages_SponsoredMessages extends TLObject { + public int flags; + public int posts_between; public ArrayList messages = new ArrayList<>(); public ArrayList chats = new ArrayList<>(); public ArrayList users = new ArrayList<>(); - public static TL_messages_sponsoredMessages TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - if (TL_messages_sponsoredMessages.constructor != constructor) { - if (exception) { - throw new RuntimeException(String.format("can't parse magic %x in TL_messages_sponsoredMessages", constructor)); - } else { - return null; - } + public static messages_SponsoredMessages TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + messages_SponsoredMessages result = null; + switch (constructor) { + case 0x1839490f: + result = new TL_messages_sponsoredMessagesEmpty(); + break; + case 0xc9ee1d87: + result = new TL_messages_sponsoredMessages(); + break; + case 0x65a4c7d5: + result = new TL_messages_sponsoredMessagesLayer147(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in messages_SponsoredMessages", constructor)); + } + if (result != null) { + result.readParams(stream, exception); } - TL_messages_sponsoredMessages result = new TL_messages_sponsoredMessages(); - result.readParams(stream, exception); return result; } + } + + public static class TL_messages_sponsoredMessagesEmpty extends messages_SponsoredMessages { + public static int constructor = 0x1839490f; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_messages_sponsoredMessagesLayer147 extends messages_SponsoredMessages { + public static int constructor = 0xc9ee1d87; public void readParams(AbstractSerializedData stream, boolean exception) { int magic = stream.readInt32(exception); @@ -10943,6 +10993,88 @@ public class TLRPC { } } + public static class TL_messages_sponsoredMessages extends messages_SponsoredMessages { + public static int constructor = 0xc9ee1d87; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + if ((flags & 1) != 0) { + posts_between = stream.readInt32(exception); + } + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_sponsoredMessage object = TL_sponsoredMessage.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + messages.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Chat object = Chat.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + chats.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + User object = User.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + users.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + if ((flags & 1) != 0) { + stream.writeInt32(posts_between); + } + stream.writeInt32(0x1cb5c415); + int count = messages.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + messages.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = chats.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + chats.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = users.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + users.get(a).serializeToStream(stream); + } + } + } + public static class TL_messageViews extends TLObject { public static int constructor = 0x455b853d; @@ -21034,6 +21166,9 @@ public class TLRPC { case 0xb1db7c7e: result = new TL_inputNotifyBroadcasts(); break; + case 0x5c467992: + result = new TL_inputNotifyForumTopic(); + break; } if (result == null && exception) { throw new RuntimeException(String.format("can't parse magic %x in InputNotifyPeer", constructor)); @@ -21087,6 +21222,24 @@ public class TLRPC { } } + public static class TL_inputNotifyForumTopic extends InputNotifyPeer { + public static int constructor = 0x5c467992; + + public InputPeer peer; + public int top_msg_id; + + public void readParams(AbstractSerializedData stream, boolean exception) { + peer = InputPeer.TLdeserialize(stream, stream.readInt32(exception), exception); + top_msg_id = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + stream.writeInt32(top_msg_id); + } + } + public static abstract class InputFileLocation extends TLObject { public long id; @@ -21910,6 +22063,7 @@ public class TLRPC { public UserProfilePhoto photo; public UserStatus status; public int flags; + public int flags2; public boolean self; public boolean contact; public boolean mutual_contact; @@ -21936,6 +22090,7 @@ public class TLRPC { public boolean bot_menu_webview; public boolean attach_menu_enabled; public EmojiStatus emoji_status; + public ArrayList usernames = new ArrayList<>(); public static User TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { User result = null; @@ -21949,9 +22104,12 @@ public class TLRPC { case 0xd3bc4b7a: result = new TL_userEmpty(); break; - case 0x5d99adee: + case 0x8f97c628: result = new TL_user(); break; + case 0x5d99adee: + result = new TL_user_layer147(); + break; case 0x3ff6ecb0: result = new TL_user_layer144(); break; @@ -22077,6 +22235,175 @@ public class TLRPC { } public static class TL_user extends User { + public static int constructor = 0x8f97c628; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + self = (flags & 1024) != 0; + contact = (flags & 2048) != 0; + mutual_contact = (flags & 4096) != 0; + deleted = (flags & 8192) != 0; + bot = (flags & 16384) != 0; + bot_chat_history = (flags & 32768) != 0; + bot_nochats = (flags & 65536) != 0; + verified = (flags & 131072) != 0; + restricted = (flags & 262144) != 0; + min = (flags & 1048576) != 0; + bot_inline_geo = (flags & 2097152) != 0; + support = (flags & 8388608) != 0; + scam = (flags & 16777216) != 0; + apply_min_photo = (flags & 33554432) != 0; + fake = (flags & 67108864) != 0; + bot_attach_menu = (flags & 134217728) != 0; + premium = (flags & 268435456) != 0; + attach_menu_enabled = (flags & 536870912) != 0; + flags2 = stream.readInt32(exception); + id = stream.readInt64(exception); + if ((flags & 1) != 0) { + access_hash = stream.readInt64(exception); + } + if ((flags & 2) != 0) { + first_name = stream.readString(exception); + } + if ((flags & 4) != 0) { + last_name = stream.readString(exception); + } + if ((flags & 8) != 0) { + username = stream.readString(exception); + } + if ((flags & 16) != 0) { + phone = stream.readString(exception); + } + if ((flags & 32) != 0) { + photo = UserProfilePhoto.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 64) != 0) { + status = UserStatus.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 16384) != 0) { + bot_info_version = stream.readInt32(exception); + } + if ((flags & 262144) != 0) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_restrictionReason object = TL_restrictionReason.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + restriction_reason.add(object); + } + } + if ((flags & 524288) != 0) { + bot_inline_placeholder = stream.readString(exception); + } + if ((flags & 4194304) != 0) { + lang_code = stream.readString(exception); + } + if ((flags & 1073741824) != 0) { + emoji_status = EmojiStatus.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags2 & 1) != 0) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_username object = TL_username.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + usernames.add(object); + } + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = self ? (flags | 1024) : (flags &~ 1024); + flags = contact ? (flags | 2048) : (flags &~ 2048); + flags = mutual_contact ? (flags | 4096) : (flags &~ 4096); + flags = deleted ? (flags | 8192) : (flags &~ 8192); + flags = bot ? (flags | 16384) : (flags &~ 16384); + flags = bot_chat_history ? (flags | 32768) : (flags &~ 32768); + flags = bot_nochats ? (flags | 65536) : (flags &~ 65536); + flags = verified ? (flags | 131072) : (flags &~ 131072); + flags = restricted ? (flags | 262144) : (flags &~ 262144); + flags = min ? (flags | 1048576) : (flags &~ 1048576); + flags = bot_inline_geo ? (flags | 2097152) : (flags &~ 2097152); + flags = support ? (flags | 8388608) : (flags &~ 8388608); + flags = scam ? (flags | 16777216) : (flags &~ 16777216); + flags = apply_min_photo ? (flags | 33554432) : (flags &~ 33554432); + flags = fake ? (flags | 67108864) : (flags &~ 67108864); + flags = bot_attach_menu ? (flags | 134217728) : (flags &~ 134217728); + flags = premium ? (flags | 268435456) : (flags &~ 268435456); + flags = attach_menu_enabled ? (flags | 536870912) : (flags &~ 536870912); + stream.writeInt32(flags); + stream.writeInt32(flags2); + stream.writeInt64(id); + if ((flags & 1) != 0) { + stream.writeInt64(access_hash); + } + if ((flags & 2) != 0) { + stream.writeString(first_name); + } + if ((flags & 4) != 0) { + stream.writeString(last_name); + } + if ((flags & 8) != 0) { + stream.writeString(username); + } + if ((flags & 16) != 0) { + stream.writeString(phone); + } + if ((flags & 32) != 0) { + photo.serializeToStream(stream); + } + if ((flags & 64) != 0) { + status.serializeToStream(stream); + } + if ((flags & 16384) != 0) { + stream.writeInt32(bot_info_version); + } + if ((flags & 262144) != 0) { + stream.writeInt32(0x1cb5c415); + int count = restriction_reason.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + restriction_reason.get(a).serializeToStream(stream); + } + } + if ((flags & 524288) != 0) { + stream.writeString(bot_inline_placeholder); + } + if ((flags & 4194304) != 0) { + stream.writeString(lang_code); + } + if ((flags & 1073741824) != 0) { + emoji_status.serializeToStream(stream); + } + if ((flags2 & 1) != 0) { + stream.writeInt32(0x1cb5c415); + int count = usernames.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + usernames.get(a).serializeToStream(stream); + } + } + } + } + + public static class TL_user_layer147 extends User { public static int constructor = 0x5d99adee; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -23644,6 +23971,9 @@ public class TLRPC { case 0x55555550: result = new TL_messageActionUserJoined(); break; + case 0xb18a431c: + result = new TL_messageActionTopicEdit(); + break; case 0x55555551: result = new TL_messageActionUserUpdatedPhoto(); break; @@ -23689,6 +24019,9 @@ public class TLRPC { case 0x80e11a7f: result = new TL_messageActionPhoneCall(); break; + case 0xd999256: + result = new TL_messageActionTopicCreate(); + break; case 0xb5a1ce5a: result = new TL_messageActionChatEditTitle(); break; @@ -24059,6 +24392,40 @@ public class TLRPC { } } + public static class TL_messageActionTopicEdit extends MessageAction { + public static int constructor = 0xb18a431c; + + public long icon_emoji_id; + public boolean closed; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + if ((flags & 1) != 0) { + title = stream.readString(exception); + } + if ((flags & 2) != 0) { + icon_emoji_id = stream.readInt64(exception); + } + if ((flags & 4) != 0) { + closed = stream.readBool(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + if ((flags & 1) != 0) { + stream.writeString(title); + } + if ((flags & 2) != 0) { + stream.writeInt64(icon_emoji_id); + } + if ((flags & 4) != 0) { + stream.writeBool(closed); + } + } + } + public static class TL_messageActionChatJoinedByRequest extends MessageAction { public static int constructor = 0xebbca3cb; @@ -24434,6 +24801,32 @@ public class TLRPC { } } + public static class TL_messageActionTopicCreate extends MessageAction { + public static int constructor = 0xd999256; + + public int icon_color; + public long icon_emoji_id; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + title = stream.readString(exception); + icon_color = stream.readInt32(exception); + if ((flags & 1) != 0) { + icon_emoji_id = stream.readInt64(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeString(title); + stream.writeInt32(icon_color); + if ((flags & 1) != 0) { + stream.writeInt64(icon_emoji_id); + } + } + } + public static class TL_messageActionChatEditTitle extends MessageAction { public static int constructor = 0xb5a1ce5a; @@ -26077,10 +26470,12 @@ public class TLRPC { public static int constructor = 0xa6d57763; public int flags; + public boolean reply_to_scheduled; + public boolean forum_topic; public int reply_to_msg_id; public Peer reply_to_peer_id; public int reply_to_top_id; - public long reply_to_random_id; + public long reply_to_random_id; //custom public static TL_messageReplyHeader TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { if (TL_messageReplyHeader.constructor != constructor) { @@ -26097,6 +26492,8 @@ public class TLRPC { public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); + reply_to_scheduled = (flags & 4) != 0; + forum_topic = (flags & 8) != 0; reply_to_msg_id = stream.readInt32(exception); if ((flags & 1) != 0) { reply_to_peer_id = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); @@ -26108,6 +26505,8 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = reply_to_scheduled ? (flags | 4) : (flags &~ 4); + flags = forum_topic ? (flags | 8) : (flags &~ 8); stream.writeInt32(flags); stream.writeInt32(reply_to_msg_id); if ((flags & 1) != 0) { @@ -29555,6 +29954,9 @@ public class TLRPC { case 0x4c4d4ce: result = new TL_inputStickerSetEmojiGenericAnimations(); break; + case 0x44c1f8e9: + result = new TL_inputStickerSetEmojiDefaultTopicIcons(); + break; } if (result == null && exception) { throw new RuntimeException(String.format("can't parse magic %x in InputStickerSet", constructor)); @@ -29656,6 +30058,15 @@ public class TLRPC { } } + public static class TL_inputStickerSetEmojiDefaultTopicIcons extends InputStickerSet { + public static int constructor = 0x44c1f8e9; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + public static class TL_channelAdminLogEventsFilter extends TLObject { public static int constructor = 0xea107ae4; @@ -30388,7 +30799,7 @@ public class TLRPC { case 0xd7ca61a2: result = new TL_updateChatParticipantAdmin(); break; - case 0x44bdd535: + case 0xea29055d: result = new TL_updateChannelReadMessagesContents(); break; case 0xee3b272a: @@ -30400,13 +30811,13 @@ public class TLRPC { case 0xe16459c3: result = new TL_updateDialogUnreadMark(); break; - case 0xee2bb969: + case 0x1b49ec6d: result = new TL_updateDraftMessage(); break; - case 0xc3f202e0: + case 0xa7848924: result = new TL_updateUserName(); break; - case 0x154798c3: + case 0x5e1b3cb8: result = new TL_updateMessageReactions(); break; case 0xab0f6b1e: @@ -30520,6 +30931,9 @@ public class TLRPC { case 0x14b85813: result = new TL_updateBotMenuButton(); break; + case 0xf694b0ae: + result = new TL_updateChannelPinnedTopic(); + break; } if (result == null && exception) { throw new RuntimeException(String.format("can't parse magic %x in Update", constructor)); @@ -31712,13 +32126,19 @@ public class TLRPC { } public static class TL_updateChannelReadMessagesContents extends Update { - public static int constructor = 0x44bdd535; + public static int constructor = 0xea29055d; + public int flags; public long channel_id; + public int top_msg_id; public ArrayList messages = new ArrayList<>(); public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); channel_id = stream.readInt64(exception); + if ((flags & 1) != 0) { + top_msg_id = stream.readInt32(exception); + } int magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { if (exception) { @@ -31734,7 +32154,11 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(flags); stream.writeInt64(channel_id); + if ((flags & 1) != 0) { + stream.writeInt32(top_msg_id); + } stream.writeInt32(0x1cb5c415); int count = messages.size(); stream.writeInt32(count); @@ -31812,36 +32236,60 @@ public class TLRPC { } public static class TL_updateDraftMessage extends Update { - public static int constructor = 0xee2bb969; + public static int constructor = 0x1b49ec6d; + public int flags; public Peer peer; + public int top_msg_id; public DraftMessage draft; public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); peer = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 1) != 0) { + top_msg_id = stream.readInt32(exception); + } draft = DraftMessage.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(flags); peer.serializeToStream(stream); + if ((flags & 1) != 0) { + stream.writeInt32(top_msg_id); + } draft.serializeToStream(stream); } } public static class TL_updateUserName extends Update { - public static int constructor = 0xc3f202e0; + public static int constructor = 0xa7848924; public long user_id; public String first_name; public String last_name; - public String username; + public ArrayList usernames = new ArrayList<>(); public void readParams(AbstractSerializedData stream, boolean exception) { user_id = stream.readInt64(exception); first_name = stream.readString(exception); last_name = stream.readString(exception); - username = stream.readString(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_username object = TL_username.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + usernames.add(object); + } } public void serializeToStream(AbstractSerializedData stream) { @@ -31849,28 +32297,43 @@ public class TLRPC { stream.writeInt64(user_id); stream.writeString(first_name); stream.writeString(last_name); - stream.writeString(username); + stream.writeInt32(0x1cb5c415); + int count = usernames.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + usernames.get(a).serializeToStream(stream); + } } } public static class TL_updateMessageReactions extends Update { - public static int constructor = 0x154798c3; + public static int constructor = 0x5e1b3cb8; + public int flags; public Peer peer; public int msg_id; + public int top_msg_id; public TL_messageReactions reactions; public boolean updateUnreadState = true; //custom public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); peer = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); msg_id = stream.readInt32(exception); - reactions = TL_messageReactions.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 1) != 0) { + top_msg_id = stream.readInt32(exception); + } + reactions = MessageReactions.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(flags); peer.serializeToStream(stream); stream.writeInt32(msg_id); + if ((flags & 1) != 0) { + stream.writeInt32(top_msg_id); + } reactions.serializeToStream(stream); } } @@ -32681,6 +33144,31 @@ public class TLRPC { } } + public static class TL_updateChannelPinnedTopic extends Update { + public static int constructor = 0xf694b0ae; + + public int flags; + public long channel_id; + public int topic_id; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + channel_id = stream.readInt64(exception); + if ((flags & 1) != 0) { + topic_id = stream.readInt32(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeInt64(channel_id); + if ((flags & 1) != 0) { + stream.writeInt32(topic_id); + } + } + } + public static class TL_receivedNotifyMessage extends TLObject { public static int constructor = 0xa384b779; @@ -32837,10 +33325,10 @@ public class TLRPC { public static class TL_messages_allStickers extends messages_AllStickers { public static int constructor = 0xcdbbcebb; - public long hash; + public long hash2; public void readParams(AbstractSerializedData stream, boolean exception) { - hash = stream.readInt64(exception); + hash2 = stream.readInt64(exception); int magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { if (exception) { @@ -32860,7 +33348,7 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt64(hash); + stream.writeInt64(hash2); stream.writeInt32(0x1cb5c415); int count = sets.size(); stream.writeInt32(count); @@ -37019,6 +37507,8 @@ public class TLRPC { public String start_param; public String message; public ArrayList entities = new ArrayList<>(); + public boolean recommended; + public boolean show_peer_photo; public static TL_sponsoredMessage TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { if (TL_sponsoredMessage.constructor != constructor) { @@ -37035,6 +37525,8 @@ public class TLRPC { public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); + recommended = (flags & 32) != 0; + show_peer_photo = (flags & 64) != 0; random_id = stream.readByteArray(exception); if ((flags & 8) != 0) { from_id = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); @@ -37073,6 +37565,8 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = recommended ? (flags | 32) : (flags &~ 32); + flags = show_peer_photo ? (flags | 64) : (flags &~ 64); stream.writeInt32(flags); stream.writeByteArray(random_id); if ((flags & 8) != 0) { @@ -38585,6 +39079,7 @@ public class TLRPC { public int count; public ArrayList participants = new ArrayList<>(); public ArrayList users = new ArrayList<>(); + public ArrayList chats = new ArrayList<>(); public static channels_ChannelParticipants TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { channels_ChannelParticipants result = null; @@ -38609,11 +39104,6 @@ public class TLRPC { public static class TL_channels_channelParticipants extends channels_ChannelParticipants { public static int constructor = 0x9ab0feaf; - public int count; - public ArrayList participants = new ArrayList<>(); - public ArrayList chats = new ArrayList<>(); - public ArrayList users = new ArrayList<>(); - public void readParams(AbstractSerializedData stream, boolean exception) { count = stream.readInt32(exception); int magic = stream.readInt32(exception); @@ -40287,6 +40777,15 @@ public class TLRPC { case 0x3e7f6847: result = new TL_channelAdminLogEventActionParticipantVolume(); break; + case 0x2cc6383: + result = new TL_channelAdminLogEventActionToggleForum(); + break; + case 0xf06fe208: + result = new TL_channelAdminLogEventActionEditTopic(); + break; + case 0x5d8d353b: + result = new TL_channelAdminLogEventActionPinTopic(); + break; case 0xe6d83d7e: result = new TL_channelAdminLogEventActionParticipantToggleBan(); break; @@ -40338,6 +40837,15 @@ public class TLRPC { case 0xcb2ac766: result = new TL_channelAdminLogEventActionToggleNoForwards(); break; + case 0xf04fb3a9: + result = new TL_channelAdminLogEventActionChangeUsernames(); + break; + case 0x58707d28: + result = new TL_channelAdminLogEventActionCreateTopic(); + break; + case 0xae168909: + result = new TL_channelAdminLogEventActionDeleteTopic(); + break; } if (result == null && exception) { throw new RuntimeException(String.format("can't parse magic %x in ChannelAdminLogEventAction", constructor)); @@ -40658,6 +41166,39 @@ public class TLRPC { } } + public static class TL_channelAdminLogEventActionToggleForum extends ChannelAdminLogEventAction { + public static int constructor = 0x2cc6383; + + public boolean new_value; + + public void readParams(AbstractSerializedData stream, boolean exception) { + new_value = stream.readBool(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeBool(new_value); + } + } + + public static class TL_channelAdminLogEventActionEditTopic extends ChannelAdminLogEventAction { + public static int constructor = 0xf06fe208; + + public ForumTopic prev_topic; + public ForumTopic new_topic; + + public void readParams(AbstractSerializedData stream, boolean exception) { + prev_topic = ForumTopic.TLdeserialize(stream, stream.readInt32(exception), exception); + new_topic = ForumTopic.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + prev_topic.serializeToStream(stream); + new_topic.serializeToStream(stream); + } + } + public static class TL_channelAdminLogEventActionParticipantToggleBan extends ChannelAdminLogEventAction { public static int constructor = 0xe6d83d7e; @@ -40910,6 +41451,35 @@ public class TLRPC { } } + public static class TL_channelAdminLogEventActionPinTopic extends ChannelAdminLogEventAction { + public static int constructor = 0x5d8d353b; + + public int flags; + public ForumTopic prev_topic; + public ForumTopic new_topic; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + if ((flags & 1) != 0) { + prev_topic = ForumTopic.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 2) != 0) { + new_topic = ForumTopic.TLdeserialize(stream, stream.readInt32(exception), exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + if ((flags & 1) != 0) { + prev_topic.serializeToStream(stream); + } + if ((flags & 2) != 0) { + new_topic.serializeToStream(stream); + } + } + } + public static class TL_channelAdminLogEventActionChangeHistoryTTL extends ChannelAdminLogEventAction { public static int constructor = 0x6e941a38; @@ -40943,6 +41513,83 @@ public class TLRPC { } } + public static class TL_channelAdminLogEventActionCreateTopic extends ChannelAdminLogEventAction { + public static int constructor = 0x58707d28; + + public ForumTopic topic; + + public void readParams(AbstractSerializedData stream, boolean exception) { + topic = ForumTopic.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + topic.serializeToStream(stream); + } + } + + public static class TL_channelAdminLogEventActionDeleteTopic extends ChannelAdminLogEventAction { + public static int constructor = 0xae168909; + + public ForumTopic topic; + + public void readParams(AbstractSerializedData stream, boolean exception) { + topic = ForumTopic.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + topic.serializeToStream(stream); + } + } + + public static class TL_channelAdminLogEventActionChangeUsernames extends ChannelAdminLogEventAction { + public static int constructor = 0xf04fb3a9; + + public ArrayList prev_value = new ArrayList<>(); + public ArrayList new_value = new ArrayList<>(); + + public void readParams(AbstractSerializedData stream, boolean exception) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + prev_value.add(stream.readString(exception)); + } + + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + new_value.add(stream.readString(exception)); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(0x1cb5c415); + stream.writeInt32(prev_value.size()); + for (int i = 0; i < prev_value.size(); ++i) { + stream.writeString(prev_value.get(i)); + } + stream.writeInt32(0x1cb5c415); + stream.writeInt32(new_value.size()); + for (int i = 0; i < new_value.size(); ++i) { + stream.writeString(new_value.get(i)); + } + } + } + public static abstract class InputWebFileLocation extends TLObject { public static InputWebFileLocation TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { @@ -41332,6 +41979,7 @@ public class TLRPC { public String title; public int date; public int flags; + public int flags2; public boolean creator; public boolean kicked; public boolean deactivated; @@ -41359,6 +42007,7 @@ public class TLRPC { public boolean fake; public boolean gigagroup; public boolean noforwards; + public boolean forum; public ArrayList restriction_reason = new ArrayList<>(); public TL_channelAdminRights_layer92 admin_rights_layer92; public TL_channelBannedRights_layer92 banned_rights_layer92; @@ -41369,6 +42018,8 @@ public class TLRPC { public boolean join_to_send; public boolean join_request; + public ArrayList usernames = new ArrayList<>(); + public static Chat TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { Chat result = null; switch (constructor) { @@ -41429,9 +42080,12 @@ public class TLRPC { case 0xa14dca52: result = new TL_channel_layer67(); break; - case 0x8261ac61: + case 0x83259464: result = new TL_channel(); break; + case 0x8261ac61: + result = new TL_channel_layer147(); + break; case 0xfb0ccc41: result = new TL_chatForbidden_old(); break; @@ -41812,6 +42466,155 @@ public class TLRPC { } public static class TL_channel extends Chat { + public static int constructor = 0x83259464; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + creator = (flags & 1) != 0; + left = (flags & 4) != 0; + broadcast = (flags & 32) != 0; + verified = (flags & 128) != 0; + megagroup = (flags & 256) != 0; + restricted = (flags & 512) != 0; + signatures = (flags & 2048) != 0; + min = (flags & 4096) != 0; + scam = (flags & 524288) != 0; + has_link = (flags & 1048576) != 0; + has_geo = (flags & 2097152) != 0; + slowmode_enabled = (flags & 4194304) != 0; + call_active = (flags & 8388608) != 0; + call_not_empty = (flags & 16777216) != 0; + fake = (flags & 33554432) != 0; + gigagroup = (flags & 67108864) != 0; + noforwards = (flags & 134217728) != 0; + join_to_send = (flags & 268435456) != 0; + join_request = (flags & 536870912) != 0; + forum = (flags & 1073741824) != 0; + flags2 = stream.readInt32(exception); + id = stream.readInt64(exception); + if ((flags & 8192) != 0) { + access_hash = stream.readInt64(exception); + } + title = stream.readString(exception); + if ((flags & 64) != 0) { + username = stream.readString(exception); + } + photo = ChatPhoto.TLdeserialize(stream, stream.readInt32(exception), exception); + date = stream.readInt32(exception); + if ((flags & 512) != 0) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_restrictionReason object = TL_restrictionReason.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + restriction_reason.add(object); + } + } + if ((flags & 16384) != 0) { + admin_rights = TL_chatAdminRights.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 32768) != 0) { + banned_rights = TL_chatBannedRights.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 262144) != 0) { + default_banned_rights = TL_chatBannedRights.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 131072) != 0) { + participants_count = stream.readInt32(exception); + } + if ((flags2 & 1) != 0) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_username object = TL_username.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + usernames.add(object); + } + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = creator ? (flags | 1) : (flags &~ 1); + flags = left ? (flags | 4) : (flags &~ 4); + flags = broadcast ? (flags | 32) : (flags &~ 32); + flags = verified ? (flags | 128) : (flags &~ 128); + flags = megagroup ? (flags | 256) : (flags &~ 256); + flags = restricted ? (flags | 512) : (flags &~ 512); + flags = signatures ? (flags | 2048) : (flags &~ 2048); + flags = min ? (flags | 4096) : (flags &~ 4096); + flags = scam ? (flags | 524288) : (flags &~ 524288); + flags = has_link ? (flags | 1048576) : (flags &~ 1048576); + flags = has_geo ? (flags | 2097152) : (flags &~ 2097152); + flags = slowmode_enabled ? (flags | 4194304) : (flags &~ 4194304); + flags = call_active ? (flags | 8388608) : (flags &~ 8388608); + flags = call_not_empty ? (flags | 16777216) : (flags &~ 16777216); + flags = fake ? (flags | 33554432) : (flags &~ 33554432); + flags = gigagroup ? (flags | 67108864) : (flags &~ 67108864); + flags = noforwards ? (flags | 134217728) : (flags &~ 134217728); + flags = join_to_send ? (flags | 268435456) : (flags &~ 268435456); + flags = join_request ? (flags | 536870912) : (flags &~ 536870912); + flags = forum ? (flags | 1073741824) : (flags &~ 1073741824); + stream.writeInt32(flags); + stream.writeInt32(flags2); + stream.writeInt64(id); + if ((flags & 8192) != 0) { + stream.writeInt64(access_hash); + } + stream.writeString(title); + if ((flags & 64) != 0) { + stream.writeString(username); + } + photo.serializeToStream(stream); + stream.writeInt32(date); + if ((flags & 512) != 0) { + stream.writeInt32(0x1cb5c415); + int count = restriction_reason.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + restriction_reason.get(a).serializeToStream(stream); + } + } + if ((flags & 16384) != 0) { + admin_rights.serializeToStream(stream); + } + if ((flags & 32768) != 0) { + banned_rights.serializeToStream(stream); + } + if ((flags & 262144) != 0) { + default_banned_rights.serializeToStream(stream); + } + if ((flags & 131072) != 0) { + stream.writeInt32(participants_count); + } + if ((flags2 & 1) != 0) { + stream.writeInt32(0x1cb5c415); + int count = usernames.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + usernames.get(a).serializeToStream(stream); + } + } + } + } + + public static class TL_channel_layer147 extends TL_channel { public static int constructor = 0x8261ac61; @@ -41836,6 +42639,7 @@ public class TLRPC { noforwards = (flags & 134217728) != 0; join_to_send = (flags & 268435456) != 0; join_request = (flags & 536870912) != 0; + forum = (flags & 1073741824) != 0; id = stream.readInt64(exception); if ((flags & 8192) != 0) { access_hash = stream.readInt64(exception); @@ -41898,6 +42702,7 @@ public class TLRPC { flags = noforwards ? (flags | 134217728) : (flags &~ 134217728); flags = join_to_send ? (flags | 268435456) : (flags &~ 268435456); flags = join_request ? (flags | 536870912) : (flags &~ 536870912); + flags = forum ? (flags | 1073741824) : (flags &~ 1073741824); stream.writeInt32(flags); stream.writeInt64(id); if ((flags & 8192) != 0) { @@ -50014,7 +50819,7 @@ public class TLRPC { } public static class TL_messages_sendMessage extends TLObject { - public static int constructor = 0xd9d75a4; + public static int constructor = 0x1cc20387; public int flags; public boolean no_webpage; @@ -50025,6 +50830,7 @@ public class TLRPC { public boolean update_stickersets_order; public InputPeer peer; public int reply_to_msg_id; + public int top_msg_id; public String message; public long random_id; public ReplyMarkup reply_markup; @@ -50049,6 +50855,9 @@ public class TLRPC { if ((flags & 1) != 0) { stream.writeInt32(reply_to_msg_id); } + if ((flags & 512) != 0) { + stream.writeInt32(top_msg_id); + } stream.writeString(message); stream.writeInt64(random_id); if ((flags & 4) != 0) { @@ -50072,7 +50881,7 @@ public class TLRPC { } public static class TL_messages_sendMedia extends TLObject { - public static int constructor = 0xe25ff8e0; + public static int constructor = 0x7547c966; public int flags; public boolean silent; @@ -50082,6 +50891,7 @@ public class TLRPC { public boolean update_stickersets_order; public InputPeer peer; public int reply_to_msg_id; + public int top_msg_id; public InputMedia media; public String message; public long random_id; @@ -50106,6 +50916,9 @@ public class TLRPC { if ((flags & 1) != 0) { stream.writeInt32(reply_to_msg_id); } + if ((flags & 512) != 0) { + stream.writeInt32(top_msg_id); + } media.serializeToStream(stream); stream.writeString(message); stream.writeInt64(random_id); @@ -50130,7 +50943,7 @@ public class TLRPC { } public static class TL_messages_forwardMessages extends TLObject { - public static int constructor = 0xcc30290b; + public static int constructor = 0xc661bbc4; public int flags; public boolean silent; @@ -50138,10 +50951,12 @@ public class TLRPC { public boolean with_my_score; public boolean drop_author; public boolean drop_media_captions; + public boolean noforwards; public InputPeer from_peer; public ArrayList id = new ArrayList<>(); public ArrayList random_id = new ArrayList<>(); public InputPeer to_peer; + public int top_msg_id; public int schedule_date; public InputPeer send_as; @@ -50156,6 +50971,7 @@ public class TLRPC { flags = with_my_score ? (flags | 256) : (flags &~ 256); flags = drop_author ? (flags | 2048) : (flags &~ 2048); flags = drop_media_captions ? (flags | 4096) : (flags &~ 4096); + flags = noforwards ? (flags | 16384) : (flags &~ 16384); stream.writeInt32(flags); from_peer.serializeToStream(stream); stream.writeInt32(0x1cb5c415); @@ -50171,6 +50987,9 @@ public class TLRPC { stream.writeInt64(random_id.get(a)); } to_peer.serializeToStream(stream); + if ((flags & 512) != 0) { + stream.writeInt32(top_msg_id); + } if ((flags & 1024) != 0) { stream.writeInt32(schedule_date); } @@ -52295,7 +53114,7 @@ public class TLRPC { } public static class TL_messages_sendInlineBotResult extends TLObject { - public static int constructor = 0x7aa11297; + public static int constructor = 0xd3fbdccb; public int flags; public boolean silent; @@ -52304,6 +53123,7 @@ public class TLRPC { public boolean hide_via; public InputPeer peer; public int reply_to_msg_id; + public int top_msg_id; public long random_id; public long query_id; public String id; @@ -52325,6 +53145,9 @@ public class TLRPC { if ((flags & 1) != 0) { stream.writeInt32(reply_to_msg_id); } + if ((flags & 512) != 0) { + stream.writeInt32(top_msg_id); + } stream.writeInt64(random_id); stream.writeInt64(query_id); stream.writeString(id); @@ -52479,11 +53302,12 @@ public class TLRPC { } public static class TL_messages_saveDraft extends TLObject { - public static int constructor = 0xbc39e14b; + public static int constructor = 0xb4331e3f; public int flags; public boolean no_webpage; public int reply_to_msg_id; + public int top_msg_id; public InputPeer peer; public String message; public ArrayList entities = new ArrayList<>(); @@ -52499,6 +53323,9 @@ public class TLRPC { if ((flags & 1) != 0) { stream.writeInt32(reply_to_msg_id); } + if ((flags & 4) != 0) { + stream.writeInt32(top_msg_id); + } peer.serializeToStream(stream); stream.writeString(message); if ((flags & 8) != 0) { @@ -52952,9 +53779,11 @@ public class TLRPC { } public static class TL_messages_getUnreadMentions extends TLObject { - public static int constructor = 0x46578472; + public static int constructor = 0xf107e790; + public int flags; public InputPeer peer; + public int top_msg_id; public int offset_id; public int add_offset; public int limit; @@ -52967,7 +53796,11 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(flags); peer.serializeToStream(stream); + if ((flags & 1) != 0) { + stream.writeInt32(top_msg_id); + } stream.writeInt32(offset_id); stream.writeInt32(add_offset); stream.writeInt32(limit); @@ -52977,9 +53810,11 @@ public class TLRPC { } public static class TL_messages_readMentions extends TLObject { - public static int constructor = 0xf0189d3; + public static int constructor = 0x36e5bf4d; + public int flags; public InputPeer peer; + public int top_msg_id; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { return TL_messages_affectedHistory.TLdeserialize(stream, constructor, exception); @@ -52987,7 +53822,11 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(flags); peer.serializeToStream(stream); + if ((flags & 1) != 0) { + stream.writeInt32(top_msg_id); + } } } @@ -53011,14 +53850,17 @@ public class TLRPC { } public static class TL_messages_sendMultiMedia extends TLObject { - public static int constructor = 0xf803138f; + public static int constructor = 0xb6f11a1c; public int flags; public boolean silent; public boolean background; public boolean clear_draft; + public boolean noforwards; + public boolean update_stickersets_order; public InputPeer peer; public int reply_to_msg_id; + public int top_msg_id; public ArrayList multi_media = new ArrayList<>(); public int schedule_date; public InputPeer send_as; @@ -53032,11 +53874,16 @@ public class TLRPC { flags = silent ? (flags | 32) : (flags &~ 32); flags = background ? (flags | 64) : (flags &~ 64); flags = clear_draft ? (flags | 128) : (flags &~ 128); + flags = noforwards ? (flags | 16384) : (flags &~ 16384); + flags = update_stickersets_order ? (flags | 32768) : (flags &~ 32768); stream.writeInt32(flags); peer.serializeToStream(stream); if ((flags & 1) != 0) { stream.writeInt32(reply_to_msg_id); } + if ((flags & 512) != 0) { + stream.writeInt32(top_msg_id); + } stream.writeInt32(0x1cb5c415); int count = multi_media.size(); stream.writeInt32(count); @@ -53278,9 +54125,11 @@ public class TLRPC { } public static class TL_messages_getSearchCounters extends TLObject { - public static int constructor = 0x732eef00; + public static int constructor = 0xae7cc1; + public int flags; public InputPeer peer; + public int top_msg_id; public ArrayList filters = new ArrayList<>(); public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { @@ -53298,7 +54147,11 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(flags); peer.serializeToStream(stream); + if ((flags & 1) != 0) { + stream.writeInt32(top_msg_id); + } stream.writeInt32(0x1cb5c415); int count = filters.size(); stream.writeInt32(count); @@ -53579,9 +54432,11 @@ public class TLRPC { } public static class TL_messages_getUnreadReactions extends TLObject { - public static int constructor = 0xe85bae1a; + public static int constructor = 0x3223495b; + public int flags; public InputPeer peer; + public int top_msg_id; public int offset_id; public int add_offset; public int limit; @@ -53594,7 +54449,11 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(flags); peer.serializeToStream(stream); + if ((flags & 1) != 0) { + stream.writeInt32(top_msg_id); + } stream.writeInt32(offset_id); stream.writeInt32(add_offset); stream.writeInt32(limit); @@ -53604,9 +54463,11 @@ public class TLRPC { } public static class TL_messages_readReactions extends TLObject { - public static int constructor = 0x82e251d7; + public static int constructor = 0x54aa7f8e; + public int flags; public InputPeer peer; + public int top_msg_id; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { return TL_messages_affectedHistory.TLdeserialize(stream, constructor, exception); @@ -53614,7 +54475,11 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(flags); peer.serializeToStream(stream); + if ((flags & 1) != 0) { + stream.writeInt32(top_msg_id); + } } } @@ -55376,7 +56241,7 @@ public class TLRPC { public InputChannel channel; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return TL_messages_sponsoredMessages.TLdeserialize(stream, constructor, exception); + return messages_SponsoredMessages.TLdeserialize(stream, constructor, exception); } public void serializeToStream(AbstractSerializedData stream) { @@ -57530,7 +58395,7 @@ public class TLRPC { //EncryptedChat end //Message start - public static abstract class Message extends TLObject { + public static class Message extends TLObject { public int id; public Peer from_id; public Peer peer_id; @@ -57566,6 +58431,7 @@ public class TLRPC { public ArrayList restriction_reason = new ArrayList<>(); public int ttl_period; public boolean noforwards; + public boolean topic_start; public int send_state = 0; //custom public int fwd_msg_id = 0; //custom public String attachPath = ""; //custom @@ -57588,6 +58454,7 @@ public class TLRPC { public boolean voiceTranscriptionOpen; //custom public boolean voiceTranscriptionRated; //custom public boolean voiceTranscriptionFinal; //custom + public boolean voiceTranscriptionForce; //custom public long voiceTranscriptionId; //custom public boolean premiumEffectWasPlayed; //custom @@ -58384,6 +59251,7 @@ public class TLRPC { edit_hide = (flags & 2097152) != 0; pinned = (flags & 16777216) != 0; noforwards = (flags & 67108864) != 0; + topic_start = (flags & 134217728) != 0; id = stream.readInt32(exception); if ((flags & 256) != 0) { from_id = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); @@ -58484,6 +59352,7 @@ public class TLRPC { flags = edit_hide ? (flags | 2097152) : (flags &~ 2097152); flags = pinned ? (flags | 16777216) : (flags &~ 16777216); flags = noforwards ? (flags | 67108864) : (flags &~ 67108864); + flags = topic_start ? (flags | 134217728) : (flags &~ 134217728); stream.writeInt32(flags); stream.writeInt32(id); if ((flags & 256) != 0) { @@ -63137,6 +64006,614 @@ public class TLRPC { stream.writeInt64(hash); } } + + public static abstract class ForumTopic extends TLObject { + + public static ForumTopic TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + ForumTopic result = null; + switch (constructor) { + case 0x23f109b: + result = new TL_forumTopicDeleted(); + break; + case 0x5920d6dc: + result = new TL_forumTopic_layer147(); + break; + case 0x71701da9: + result = new TL_forumTopic(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in ForumTopic", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_forumTopicDeleted extends ForumTopic { + public static int constructor = 0x23f109b; + + public int id; + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(id); + } + } + + public static class TL_forumTopic_layer147 extends TL_forumTopic { + public static int constructor = 0x5920d6dc; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + my = (flags & 2) != 0; + closed = (flags & 4) != 0; + pinned = (flags & 8) != 0; + id = stream.readInt32(exception); + date = stream.readInt32(exception); + title = stream.readString(exception); + icon_color = stream.readInt32(exception); + if ((flags & 1) != 0) { + icon_emoji_id = stream.readInt64(exception); + } + top_message = stream.readInt32(exception); + read_inbox_max_id = stream.readInt32(exception); + read_outbox_max_id = stream.readInt32(exception); + unread_count = stream.readInt32(exception); + unread_mentions_count = stream.readInt32(exception); + unread_reactions_count = stream.readInt32(exception); + from_id = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = my ? (flags | 2) : (flags &~ 2); + flags = closed ? (flags | 4) : (flags &~ 4); + flags = pinned ? (flags | 8) : (flags &~ 8); + stream.writeInt32(flags); + stream.writeInt32(id); + stream.writeInt32(date); + stream.writeString(title); + stream.writeInt32(icon_color); + if ((flags & 1) != 0) { + stream.writeInt64(icon_emoji_id); + } + stream.writeInt32(top_message); + stream.writeInt32(read_inbox_max_id); + stream.writeInt32(read_outbox_max_id); + stream.writeInt32(unread_count); + stream.writeInt32(unread_mentions_count); + stream.writeInt32(unread_reactions_count); + from_id.serializeToStream(stream); + notify_settings.serializeToStream(stream); + } + } + + public static class TL_forumTopic extends ForumTopic { + public static int constructor = 0x71701da9; + + public int flags; + public boolean my; + public boolean closed; + public boolean pinned; + public int id; + public int date; + public String title; + public int icon_color; + public long icon_emoji_id; + public int top_message; + public int read_inbox_max_id; + public int read_outbox_max_id; + public int unread_count; + public int unread_mentions_count; + public int unread_reactions_count; + public Peer from_id; + public PeerNotifySettings notify_settings; + public DraftMessage draft; + public Message topicStartMessage; // custom + public ArrayList groupedMessages; // custom + public Message topMessage; // custom + public String searchQuery; //custom + + public static TL_forumTopic TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + TL_forumTopic result = null; + switch (constructor) { + case 0x5920d6dc: + result = new TL_forumTopic_layer147(); + break; + case 0x71701da9: + result = new TL_forumTopic(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_forumTopic", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + my = (flags & 2) != 0; + closed = (flags & 4) != 0; + pinned = (flags & 8) != 0; + id = stream.readInt32(exception); + date = stream.readInt32(exception); + title = stream.readString(exception); + icon_color = stream.readInt32(exception); + if ((flags & 1) != 0) { + icon_emoji_id = stream.readInt64(exception); + } + top_message = stream.readInt32(exception); + read_inbox_max_id = stream.readInt32(exception); + read_outbox_max_id = stream.readInt32(exception); + unread_count = stream.readInt32(exception); + unread_mentions_count = stream.readInt32(exception); + unread_reactions_count = stream.readInt32(exception); + from_id = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 16) != 0) { + draft = DraftMessage.TLdeserialize(stream, stream.readInt32(exception), exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = my ? (flags | 2) : (flags &~ 2); + flags = closed ? (flags | 4) : (flags &~ 4); + flags = pinned ? (flags | 8) : (flags &~ 8); + stream.writeInt32(flags); + stream.writeInt32(id); + stream.writeInt32(date); + stream.writeString(title); + stream.writeInt32(icon_color); + if ((flags & 1) != 0) { + stream.writeInt64(icon_emoji_id); + } + stream.writeInt32(top_message); + stream.writeInt32(read_inbox_max_id); + stream.writeInt32(read_outbox_max_id); + stream.writeInt32(unread_count); + stream.writeInt32(unread_mentions_count); + stream.writeInt32(unread_reactions_count); + from_id.serializeToStream(stream); + notify_settings.serializeToStream(stream); + if ((flags & 16) != 0) { + draft.serializeToStream(stream); + } + } + } + + public static class TL_messages_forumTopics extends TLObject { + public static int constructor = 0x367617d3; + + public int flags; + public boolean order_by_create_date; + public int count; + public ArrayList topics = new ArrayList<>(); + public ArrayList messages = new ArrayList<>(); + public ArrayList chats = new ArrayList<>(); + public ArrayList users = new ArrayList<>(); + public int pts; + + public static TL_messages_forumTopics TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_messages_forumTopics.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_messages_forumTopics", constructor)); + } else { + return null; + } + } + TL_messages_forumTopics result = new TL_messages_forumTopics(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + order_by_create_date = (flags & 1) != 0; + count = stream.readInt32(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_forumTopic object = TL_forumTopic.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + topics.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Message object = Message.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + messages.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Chat object = Chat.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + chats.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + User object = User.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + users.add(object); + } + pts = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = order_by_create_date ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + stream.writeInt32(count); + stream.writeInt32(0x1cb5c415); + int count = topics.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + topics.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = messages.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + messages.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = chats.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + chats.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = users.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + users.get(a).serializeToStream(stream); + } + stream.writeInt32(pts); + } + } + + public static class TL_channels_toggleForum extends TLObject { + public static int constructor = 0xa4298b29; + + public InputChannel channel; + public boolean enabled; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stream.writeBool(enabled); + } + } + + public static class TL_channels_createForumTopic extends TLObject { + public static int constructor = 0xf40c0224; + + public int flags; + public InputChannel channel; + public String title; + public int icon_color; + public long icon_emoji_id; + public long random_id; + public InputPeer send_as; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + channel.serializeToStream(stream); + stream.writeString(title); + if ((flags & 1) != 0) { + stream.writeInt32(icon_color); + } + if ((flags & 8) != 0) { + stream.writeInt64(icon_emoji_id); + } + stream.writeInt64(random_id); + if ((flags & 4) != 0) { + send_as.serializeToStream(stream); + } + } + } + + public static class TL_channels_editForumTopic extends TLObject { + public static int constructor = 0x6c883e2d; + + public int flags; + public InputChannel channel; + public int topic_id; + public String title; + public long icon_emoji_id; + public boolean closed; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + channel.serializeToStream(stream); + stream.writeInt32(topic_id); + if ((flags & 1) != 0) { + stream.writeString(title); + } + if ((flags & 2) != 0) { + stream.writeInt64(icon_emoji_id); + } + if ((flags & 4) != 0) { + stream.writeBool(closed); + } + } + } + + public static class TL_channels_updatePinnedForumTopic extends TLObject { + public static int constructor = 0x6c2d9026; + + public InputChannel channel; + public int topic_id; + public boolean pinned; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stream.writeInt32(topic_id); + stream.writeBool(pinned); + } + } + + public static class TL_channels_getForumTopics extends TLObject { + public static int constructor = 0xde560d1; + + public int flags; + public InputChannel channel; + public String q; + public int offset_date; + public int offset_id; + public int offset_topic; + public int limit; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_messages_forumTopics.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + channel.serializeToStream(stream); + if ((flags & 1) != 0) { + stream.writeString(q); + } + stream.writeInt32(offset_date); + stream.writeInt32(offset_id); + stream.writeInt32(offset_topic); + stream.writeInt32(limit); + } + } + + public static class TL_channels_getForumTopicsByID extends TLObject { + public static int constructor = 0xb0831eb9; + + public InputChannel channel; + public ArrayList topics = new ArrayList<>(); + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_messages_forumTopics.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = topics.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + stream.writeInt32(topics.get(a)); + } + } + } + + public static class TL_username extends TLObject { + public static int constructor = 0xb4073647; + + public int flags; + public boolean editable; + public boolean active; + public String username; + + public static TL_username TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_username.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_username", constructor)); + } else { + return null; + } + } + TL_username result = new TL_username(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + editable = (flags & 1) != 0; + active = (flags & 2) != 0; + username = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = editable ? (flags | 1) : (flags &~ 1); + flags = active ? (flags | 2) : (flags &~ 2); + stream.writeInt32(flags); + stream.writeString(username); + } + } + public static class TL_channels_deleteTopicHistory extends TLObject { + public static int constructor = 0x34435f2d; + + public InputChannel channel; + public int top_msg_id; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_messages_affectedHistory.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stream.writeInt32(top_msg_id); + } + } + + public static class TL_account_reorderUsernames extends TLObject { + public static int constructor = 0xef500eab; + + public ArrayList order = new ArrayList<>(); + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(0x1cb5c415); + int count = order.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + stream.writeString(order.get(a)); + } + } + } + + public static class TL_account_toggleUsername extends TLObject { + public static int constructor = 0x58d6b376; + + public String username; + public boolean active; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(username); + stream.writeBool(active); + } + } + + public static class TL_channels_reorderUsernames extends TLObject { + public static int constructor = 0xb45ced1d; + + public InputChannel channel; + public ArrayList order = new ArrayList<>(); + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = order.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + stream.writeString(order.get(a)); + } + } + } + + public static class TL_channels_toggleUsername extends TLObject { + public static int constructor = 0x50f24105; + + public InputChannel channel; + public String username; + public boolean active; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stream.writeString(username); + stream.writeBool(active); + } + } + + public static class TL_channels_deactivateAllUsernames extends TLObject { + public static int constructor = 0xa245dd3; + + public InputChannel channel; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + } + } //functions public static class Vector extends TLObject { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java index 7f333c703..afc9c2fcf 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java @@ -25,7 +25,6 @@ import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; -import android.util.Log; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; @@ -53,28 +52,24 @@ import org.telegram.messenger.SharedConfig; import org.telegram.ui.Components.BackButtonMenu; import org.telegram.ui.Components.Bulletin; import org.telegram.ui.Components.CubicBezierInterpolator; +import org.telegram.ui.Components.FloatingDebug.FloatingDebugController; +import org.telegram.ui.Components.FloatingDebug.FloatingDebugProvider; import org.telegram.ui.Components.GroupCallPip; import org.telegram.ui.Components.LayoutHelper; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collections; +import java.util.List; -public class ActionBarLayout extends FrameLayout { - - public interface ActionBarLayoutDelegate { - boolean onPreIme(); - - boolean needPresentFragment(BaseFragment fragment, boolean removeLast, boolean forceWithoutAnimation, ActionBarLayout layout); - - boolean needAddFragmentToStack(BaseFragment fragment, ActionBarLayout layout); - - boolean needCloseLastFragment(ActionBarLayout layout); - - void onRebuildAllFragments(ActionBarLayout layout, boolean last); - } +public class ActionBarLayout extends FrameLayout implements INavigationLayout, FloatingDebugProvider { public boolean highlightActionButtons = false; + @Override + public void setHighlightActionButtons(boolean highlightActionButtons) { + this.highlightActionButtons = highlightActionButtons; + } + public class LayoutContainer extends FrameLayout { private Rect rect = new Rect(); @@ -300,33 +295,6 @@ public class ActionBarLayout extends FrameLayout { } } - public static class ThemeAnimationSettings { - - public final Theme.ThemeInfo theme; - public final int accentId; - public final boolean nightTheme; - public final boolean instant; - public boolean onlyTopFragment; - public boolean applyTheme = true; - public Runnable afterStartDescriptionsAddedRunnable; - public Runnable beforeAnimationRunnable; - public Runnable afterAnimationRunnable; - public onAnimationProgress animationProgress; - public long duration = 200; - public Theme.ResourcesProvider resourcesProvider; - - public ThemeAnimationSettings(Theme.ThemeInfo theme, int accentId, boolean nightTheme, boolean instant) { - this.theme = theme; - this.accentId = accentId; - this.nightTheme = nightTheme; - this.instant = instant; - } - - public interface onAnimationProgress { - void setProgress(float p); - } - } - private static Drawable headerShadowDrawable; private static Drawable layerShadowDrawable; private static Paint scrimPaint; @@ -405,11 +373,11 @@ public class ActionBarLayout extends FrameLayout { private int titleOverlayTextId; private Runnable overlayAction; - private ActionBarLayoutDelegate delegate; + private INavigationLayoutDelegate delegate; protected Activity parentActivity; - public ArrayList fragmentsStack; - public ArrayList pulledDialogs; + private List fragmentsStack; + private List pulledDialogs; private Rect rect = new Rect(); private boolean delayedAnimationResumed; private Runnable onFragmentStackChangedListener; @@ -427,7 +395,8 @@ public class ActionBarLayout extends FrameLayout { } } - public void init(ArrayList stack) { + @Override + public void setFragmentStack(List stack) { this.fragmentsStack = stack; this.containerViewBack = new LayoutContainer(parentActivity); addView(containerViewBack); @@ -464,18 +433,30 @@ public class ActionBarLayout extends FrameLayout { } } - public void drawHeaderShadow(Canvas canvas, int y) { - drawHeaderShadow(canvas, 255, y); + private int[] measureSpec = new int[2]; + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (delegate != null) { + measureSpec[0] = widthMeasureSpec; + measureSpec[1] = heightMeasureSpec; + delegate.onMeasureOverride(measureSpec); + widthMeasureSpec = measureSpec[0]; + heightMeasureSpec = measureSpec[1]; + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + @Override public void setInBubbleMode(boolean value) { inBubbleMode = value; } + @Override public boolean isInBubbleMode() { return inBubbleMode; } + @Override public void drawHeaderShadow(Canvas canvas, int alpha, int y) { if (headerShadowDrawable != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { @@ -522,13 +503,7 @@ public class ActionBarLayout extends FrameLayout { return innerTranslationX; } - public void dismissDialogs() { - if (!fragmentsStack.isEmpty()) { - BaseFragment lastFragment = fragmentsStack.get(fragmentsStack.size() - 1); - lastFragment.dismissCurrentDialog(); - } - } - + @Override public void onResume() { if (transitionAnimationInProgress) { if (currentAnimation != null) { @@ -555,6 +530,7 @@ public class ActionBarLayout extends FrameLayout { } } + @Override public void onUserLeaveHint() { if (!fragmentsStack.isEmpty()) { BaseFragment lastFragment = fragmentsStack.get(fragmentsStack.size() - 1); @@ -562,6 +538,7 @@ public class ActionBarLayout extends FrameLayout { } } + @Override public void onPause() { if (!fragmentsStack.isEmpty()) { BaseFragment lastFragment = fragmentsStack.get(fragmentsStack.size() - 1); @@ -646,6 +623,7 @@ public class ActionBarLayout extends FrameLayout { invalidate(); } + @Override public float getCurrentPreviewFragmentAlpha() { if (inPreviewMode || transitionAnimationPreviewMode || previewOpenAnimationInProgress) { return (oldFragment != null && oldFragment.inPreviewMode ? containerViewBack : containerView).getAlpha(); @@ -654,6 +632,7 @@ public class ActionBarLayout extends FrameLayout { } } + @Override public void drawCurrentPreviewFragment(Canvas canvas, Drawable foregroundDrawable) { if (inPreviewMode || transitionAnimationPreviewMode || previewOpenAnimationInProgress) { final ViewGroup v = oldFragment != null && oldFragment.inPreviewMode ? containerViewBack : containerView; @@ -697,8 +676,9 @@ public class ActionBarLayout extends FrameLayout { } } - public void setDelegate(ActionBarLayoutDelegate actionBarLayoutDelegate) { - delegate = actionBarLayoutDelegate; + @Override + public void setDelegate(INavigationLayoutDelegate INavigationLayoutDelegate) { + delegate = INavigationLayoutDelegate; } private void onSlideAnimationEnd(final boolean backAnimation) { @@ -807,6 +787,7 @@ public class ActionBarLayout extends FrameLayout { lastFragment.prepareFragmentToSlide(false, true); } + @Override public boolean onTouchEvent(MotionEvent ev) { if (!checkTransitionAnimation() && !inActionMode && !animationInProgress) { if (fragmentsStack.size() > 1) { @@ -944,6 +925,7 @@ public class ActionBarLayout extends FrameLayout { return false; } + @Override public void onBackPressed() { if (transitionAnimationPreviewMode || startedTracking || checkTransitionAnimation() || fragmentsStack.isEmpty()) { return; @@ -963,6 +945,7 @@ public class ActionBarLayout extends FrameLayout { } } + @Override public void onLowMemory() { for (BaseFragment fragment : fragmentsStack) { fragment.onLowMemory(); @@ -1002,6 +985,7 @@ public class ActionBarLayout extends FrameLayout { return fragmentsStack.get(fragmentsStack.size() - 1); } + @Override public boolean checkTransitionAnimation() { if (transitionAnimationPreviewMode) { return false; @@ -1012,10 +996,17 @@ public class ActionBarLayout extends FrameLayout { return transitionAnimationInProgress; } + @Override public boolean isPreviewOpenAnimationInProgress() { return previewOpenAnimationInProgress; } + @Override + public boolean isSwipeInProgress() { + return startedTracking; + } + + @Override public boolean isTransitionAnimationInProgress() { return transitionAnimationInProgress || animationInProgress; } @@ -1058,22 +1049,6 @@ public class ActionBarLayout extends FrameLayout { containerViewBack.setVisibility(View.INVISIBLE); } - public boolean presentFragmentAsPreview(BaseFragment fragment) { - return presentFragment(fragment, false, false, true, true, null); - } - - public boolean presentFragmentAsPreviewWithMenu(BaseFragment fragment, ActionBarPopupWindow.ActionBarPopupWindowLayout menu) { - return presentFragment(fragment, false, false, true, true, menu); - } - - public boolean presentFragment(BaseFragment fragment) { - return presentFragment(fragment, false, false, true, false, null); - } - - public boolean presentFragment(BaseFragment fragment, boolean removeLast) { - return presentFragment(fragment, removeLast, false, true, false, null); - } - private void startLayoutAnimation(final boolean open, final boolean first, final boolean preview) { if (first) { animationProgress = 0.0f; @@ -1166,6 +1141,7 @@ public class ActionBarLayout extends FrameLayout { }); } + @Override public void resumeDelayedFragmentAnimation() { delayedAnimationResumed = true; if (delayedOpenAnimationRunnable == null || waitingForKeyboardCloseRunnable != null) { @@ -1180,6 +1156,7 @@ public class ActionBarLayout extends FrameLayout { return inPreviewMode || transitionAnimationPreviewMode; } + @Override public boolean isInPassivePreviewMode() { return (inPreviewMode && previewMenu == null) || transitionAnimationPreviewMode; } @@ -1188,13 +1165,16 @@ public class ActionBarLayout extends FrameLayout { return isInPreviewMode() && previewMenu != null; } + @Override + public boolean presentFragment(NavigationParams params) { + BaseFragment fragment = params.fragment; + boolean removeLast = params.removeLast; + boolean forceWithoutAnimation = params.noAnimation; + boolean check = params.checkPresentFromDelegate; + boolean preview = params.preview; + ActionBarPopupWindow.ActionBarPopupWindowLayout menu = params.menuView; - public boolean presentFragment(final BaseFragment fragment, final boolean removeLast, boolean forceWithoutAnimation, boolean check, final boolean preview) { - return presentFragment(fragment, removeLast, forceWithoutAnimation, check, preview, null); - } - - public boolean presentFragment(final BaseFragment fragment, final boolean removeLast, boolean forceWithoutAnimation, boolean check, final boolean preview, ActionBarPopupWindow.ActionBarPopupWindowLayout menu) { - if (fragment == null || checkTransitionAnimation() || delegate != null && check && !delegate.needPresentFragment(fragment, removeLast, forceWithoutAnimation, this) || !fragment.onFragmentCreate()) { + if (fragment == null || checkTransitionAnimation() || delegate != null && check && !delegate.needPresentFragment(this, params) || !fragment.onFragmentCreate()) { return false; } if (inPreviewMode && transitionAnimationPreviewMode) { @@ -1489,6 +1469,12 @@ public class ActionBarLayout extends FrameLayout { return true; } + @Override + public List getFragmentStack() { + return fragmentsStack; + } + + @Override public void setFragmentStackChangedListener(Runnable onFragmentStackChanged) { this.onFragmentStackChangedListener = onFragmentStackChanged; } @@ -1500,10 +1486,7 @@ public class ActionBarLayout extends FrameLayout { ImageLoader.getInstance().onFragmentStackChanged(); } - public boolean addFragmentToStack(BaseFragment fragment) { - return addFragmentToStack(fragment, -1); - } - + @Override public boolean addFragmentToStack(BaseFragment fragment, int position) { if (delegate != null && !delegate.needAddFragmentToStack(fragment, this) || !fragment.onFragmentCreate()) { return false; @@ -1548,6 +1531,7 @@ public class ActionBarLayout extends FrameLayout { onFragmentStackChanged(); } + @Override public void movePreviewFragment(float dy) { if (!inPreviewMode || previewMenu != null || transitionAnimationPreviewMode) { return; @@ -1566,6 +1550,7 @@ public class ActionBarLayout extends FrameLayout { } } + @Override public void expandPreviewFragment() { previewOpenAnimationInProgress = true; inPreviewMode = false; @@ -1604,6 +1589,7 @@ public class ActionBarLayout extends FrameLayout { fragment.setInMenuMode(false); } + @Override public void finishPreviewFragment() { if (!inPreviewMode && !transitionAnimationPreviewMode) { return; @@ -1803,7 +1789,8 @@ public class ActionBarLayout extends FrameLayout { } } - public void showFragment(int i) { + @Override + public void bringToFront(int i) { if (fragmentsStack.isEmpty()) { return; } @@ -1859,7 +1846,7 @@ public class ActionBarLayout extends FrameLayout { if (fragmentsStack.isEmpty()) { return; } - showFragment(fragmentsStack.size() - 1); + bringToFront(fragmentsStack.size() - 1); } private void removeFragmentFromStackInternal(BaseFragment fragment) { @@ -1870,13 +1857,7 @@ public class ActionBarLayout extends FrameLayout { onFragmentStackChanged(); } - public void removeFragmentFromStack(int num) { - if (num >= fragmentsStack.size()) { - return; - } - removeFragmentFromStackInternal(fragmentsStack.get(num)); - } - + @Override public void removeFragmentFromStack(BaseFragment fragment) { if (useAlphaAnimations && fragmentsStack.size() == 1 && AndroidUtilities.isTablet()) { closeLastFragment(true); @@ -1941,9 +1922,13 @@ public class ActionBarLayout extends FrameLayout { if (animationProgressListener != null) { animationProgressListener.setProgress(value); } + if (delegate != null) { + delegate.onThemeProgress(value); + } } @Keep + @Override public float getThemeAnimationValue() { return themeAnimationValue; } @@ -1976,14 +1961,7 @@ public class ActionBarLayout extends FrameLayout { } } - public void animateThemedValues(Theme.ThemeInfo theme, int accentId, boolean nightTheme, boolean instant) { - animateThemedValues(new ThemeAnimationSettings(theme, accentId, nightTheme, instant), null); - } - - public void animateThemedValues(Theme.ThemeInfo theme, int accentId, boolean nightTheme, boolean instant, Runnable onDone) { - animateThemedValues(new ThemeAnimationSettings(theme, accentId, nightTheme, instant), onDone); - } - + @Override public void animateThemedValues(ThemeAnimationSettings settings, Runnable onDone) { if (transitionAnimationInProgress || startedTracking) { animateThemeAfterAnimation = true; @@ -2135,6 +2113,7 @@ public class ActionBarLayout extends FrameLayout { } } + @Override public void rebuildLogout() { containerView.removeAllViews(); containerViewBack.removeAllViews(); @@ -2143,6 +2122,7 @@ public class ActionBarLayout extends FrameLayout { oldFragment = null; } + @Override public void rebuildAllFragmentViews(boolean last, boolean showLastAfter) { if (transitionAnimationInProgress || startedTracking) { rebuildAfterAnimation = true; @@ -2169,6 +2149,7 @@ public class ActionBarLayout extends FrameLayout { } } + @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_MENU && !checkTransitionAnimation() && !startedTracking && currentActionBar != null) { currentActionBar.onMenuButtonPressed(); @@ -2176,6 +2157,7 @@ public class ActionBarLayout extends FrameLayout { return super.onKeyUp(keyCode, event); } + @Override public void onActionModeStarted(Object mode) { if (currentActionBar != null) { currentActionBar.setVisibility(GONE); @@ -2183,6 +2165,7 @@ public class ActionBarLayout extends FrameLayout { inActionMode = true; } + @Override public void onActionModeFinished(Object mode) { if (currentActionBar != null) { currentActionBar.setVisibility(VISIBLE); @@ -2232,6 +2215,7 @@ public class ActionBarLayout extends FrameLayout { } } + @Override public void startActivityForResult(final Intent intent, final int requestCode) { if (parentActivity == null) { return; @@ -2253,14 +2237,37 @@ public class ActionBarLayout extends FrameLayout { } } + @Override + public Theme.MessageDrawable getMessageDrawableOutStart() { + return messageDrawableOutStart; + } + + @Override + public Theme.MessageDrawable getMessageDrawableOutMediaStart() { + return messageDrawableOutMediaStart; + } + + @Override + public List getPulledDialogs() { + return pulledDialogs; + } + + @Override + public void setPulledDialogs(List pulledDialogs) { + this.pulledDialogs = pulledDialogs; + } + + @Override public void setUseAlphaAnimations(boolean value) { useAlphaAnimations = value; } + @Override public void setBackgroundView(View view) { backgroundView = view; } + @Override public void setDrawerLayoutContainer(DrawerLayoutContainer layout) { drawerLayoutContainer = layout; } @@ -2273,6 +2280,7 @@ public class ActionBarLayout extends FrameLayout { removeActionBarExtraHeight = value; } + @Override public void setTitleOverlayText(String title, int titleId, Runnable action) { titleOverlayText = title; titleOverlayTextId = titleId; @@ -2285,6 +2293,7 @@ public class ActionBarLayout extends FrameLayout { } } + @Override public boolean extendActionMode(Menu menu) { return !fragmentsStack.isEmpty() && fragmentsStack.get(fragmentsStack.size() - 1).extendActionMode(menu); } @@ -2294,12 +2303,44 @@ public class ActionBarLayout extends FrameLayout { return false; } + @Override public void setFragmentPanTranslationOffset(int offset) { if (containerView != null) { containerView.setFragmentPanTranslationOffset(offset); } } + @Override + public ViewGroup getOverlayContainerView() { + return this; + } + + @Override + public List onGetDebugItems() { + BaseFragment fragment = getLastFragment(); + if (fragment != null) { + List items = new ArrayList<>(); + if (fragment instanceof FloatingDebugProvider) { + items.addAll(((FloatingDebugProvider) fragment).onGetDebugItems()); + } + observeDebugItemsFromView(items, fragment.getFragmentView()); + return items; + } + return Collections.emptyList(); + } + + private void observeDebugItemsFromView(List items, View v) { + if (v instanceof FloatingDebugProvider) { + items.addAll(((FloatingDebugProvider) v).onGetDebugItems()); + } + if (v instanceof ViewGroup) { + ViewGroup vg = (ViewGroup) v; + for (int i = 0; i < vg.getChildCount(); i++) { + observeDebugItemsFromView(items, vg.getChildAt(i)); + } + } + } + private View findScrollingChild(ViewGroup parent, float x, float y) { int n = parent.getChildCount(); for (int i = 0; i < n; i++) { @@ -2322,34 +2363,4 @@ public class ActionBarLayout extends FrameLayout { return null; } - private class StartColorsProvider implements Theme.ResourcesProvider { - - HashMap colors = new HashMap<>(); - - String[] keysToSave = new String[]{ - Theme.key_chat_outBubble, - Theme.key_chat_outBubbleGradient1, - Theme.key_chat_outBubbleGradient2, - Theme.key_chat_outBubbleGradient3, - Theme.key_chat_outBubbleGradientAnimated, - Theme.key_chat_outBubbleShadow - }; - - @Override - public Integer getColor(String key) { - return colors.get(key); - } - - @Override - public Integer getCurrentColor(String key) { - return colors.get(key); - } - - public void saveColors(Theme.ResourcesProvider fragmentResourceProvider) { - colors.clear(); - for (String key : keysToSave) { - colors.put(key, fragmentResourceProvider.getCurrentColor(key)); - } - } - } } \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java index 2b0667cc2..5598b85fe 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java @@ -29,7 +29,6 @@ import android.transition.TransitionManager; import android.transition.TransitionSet; import android.transition.TransitionValues; import android.transition.Visibility; -import android.util.Log; import android.util.TypedValue; import android.view.ActionMode; import android.view.Gravity; @@ -60,7 +59,6 @@ import org.telegram.messenger.LocaleController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; -import org.telegram.messenger.XiaomiUtilities; import org.telegram.tgnet.TLRPC; import org.telegram.ui.Adapters.FiltersView; import org.telegram.ui.Components.BackupImageView; @@ -78,6 +76,15 @@ public class ActionBarMenuItem extends FrameLayout { private FrameLayout wrappedSearchFrameLayout; + public void setSearchPaddingStart(int padding) { + searchItemPaddingStart = padding; + if (searchContainer != null) { + ((MarginLayoutParams)searchContainer.getLayoutParams()).leftMargin = AndroidUtilities.dp(padding); + searchContainer.setClipChildren(searchItemPaddingStart != 0); + } + + } + public static class ActionBarMenuItemSearchListener { public void onSearchExpand() { } @@ -171,6 +178,7 @@ public class ActionBarMenuItem extends FrameLayout { private float transitionOffset; private View showSubMenuFrom; private final Theme.ResourcesProvider resourcesProvider; + public int searchItemPaddingStart; private OnClickListener onClickListener; @@ -711,9 +719,9 @@ public class ActionBarMenuItem extends FrameLayout { } }); - // if (measurePopup) { - container.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.x - AndroidUtilities.dp(40), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.y, MeasureSpec.AT_MOST)); - measurePopup = false; + // if (measurePopup) { + container.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.x - AndroidUtilities.dp(40), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.y, MeasureSpec.AT_MOST)); + measurePopup = false; //} processedPopupClick = false; popupWindow.setFocusable(true); @@ -889,35 +897,35 @@ public class ActionBarMenuItem extends FrameLayout { ChangeBounds changeBounds = new ChangeBounds(); changeBounds.setDuration(150); transition.addTransition(new Visibility() { - @Override - public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { - if (view instanceof SearchFilterView) { - AnimatorSet set = new AnimatorSet(); - set.playTogether( - ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1f), - ObjectAnimator.ofFloat(view, View.SCALE_X, 0.5f, 1f), - ObjectAnimator.ofFloat(view, View.SCALE_Y, 0.5f, 1f) - ); - set.setInterpolator(CubicBezierInterpolator.DEFAULT); - return set; - } - return ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1f); - } - @Override - public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { - if (view instanceof SearchFilterView) { - AnimatorSet set = new AnimatorSet(); - set.playTogether( - ObjectAnimator.ofFloat(view, View.ALPHA, view.getAlpha(), 0f), - ObjectAnimator.ofFloat(view, View.SCALE_X, view.getScaleX(), 0.5f), - ObjectAnimator.ofFloat(view, View.SCALE_Y, view.getScaleX(), 0.5f) - ); - set.setInterpolator(CubicBezierInterpolator.DEFAULT); - return set; - } - return ObjectAnimator.ofFloat(view, View.ALPHA, 1f, 0); - } - }.setDuration(150)).addTransition(changeBounds); + @Override + public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { + if (view instanceof SearchFilterView) { + AnimatorSet set = new AnimatorSet(); + set.playTogether( + ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1f), + ObjectAnimator.ofFloat(view, View.SCALE_X, 0.5f, 1f), + ObjectAnimator.ofFloat(view, View.SCALE_Y, 0.5f, 1f) + ); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + return set; + } + return ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1f); + } + @Override + public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { + if (view instanceof SearchFilterView) { + AnimatorSet set = new AnimatorSet(); + set.playTogether( + ObjectAnimator.ofFloat(view, View.ALPHA, view.getAlpha(), 0f), + ObjectAnimator.ofFloat(view, View.SCALE_X, view.getScaleX(), 0.5f), + ObjectAnimator.ofFloat(view, View.SCALE_Y, view.getScaleX(), 0.5f) + ); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + return set; + } + return ObjectAnimator.ofFloat(view, View.ALPHA, 1f, 0); + } + }.setDuration(150)).addTransition(changeBounds); transition.setOrdering(TransitionSet.ORDERING_TOGETHER); transition.setInterpolator(CubicBezierInterpolator.EASE_OUT); int selectedAccount = UserConfig.selectedAccount; @@ -1194,7 +1202,7 @@ public class ActionBarMenuItem extends FrameLayout { searchField.layout(x, searchField.getTop(), x + searchField.getMeasuredWidth(), searchField.getBottom()); } }; - searchContainer.setClipChildren(false); + searchContainer.setClipChildren(searchItemPaddingStart != 0); wrappedSearchFrameLayout = null; if (wrapInScrollView) { wrappedSearchFrameLayout = new FrameLayout(getContext()); @@ -1232,11 +1240,11 @@ public class ActionBarMenuItem extends FrameLayout { }; horizontalScrollView.addView(searchContainer, LayoutHelper.createScroll(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, 0)); horizontalScrollView.setHorizontalScrollBarEnabled(false); - horizontalScrollView.setClipChildren(false); + horizontalScrollView.setClipChildren(searchItemPaddingStart != 0); wrappedSearchFrameLayout.addView(horizontalScrollView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0, 0, 0, 48, 0)); - parentMenu.addView(wrappedSearchFrameLayout, 0, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, 1.0f, 0, 0, 0, 0)); + parentMenu.addView(wrappedSearchFrameLayout, 0, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, 1.0f, searchItemPaddingStart, 0, 0, 0)); } else { - parentMenu.addView(searchContainer, 0, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, 1.0f, 6, 0, 0, 0)); + parentMenu.addView(searchContainer, 0, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, 1.0f, searchItemPaddingStart + 6, 0, 0, 0)); } searchContainer.setVisibility(GONE); @@ -1370,11 +1378,11 @@ public class ActionBarMenuItem extends FrameLayout { searchFilterLayout.setVisibility(View.VISIBLE); if (!LocaleController.isRTL) { searchContainer.addView(searchFieldCaption, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 36, Gravity.CENTER_VERTICAL | Gravity.LEFT, 0, 5.5f, 0, 0)); - searchContainer.addView(searchField, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 36, Gravity.CENTER_VERTICAL, 6, 0, 48, 0)); + searchContainer.addView(searchField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.CENTER_VERTICAL, 6, 0, wrapInScrollView ? 0 : 48, 0)); searchContainer.addView(searchFilterLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 32, Gravity.CENTER_VERTICAL, 0, 0, 48, 0)); } else { searchContainer.addView(searchFilterLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 32, Gravity.CENTER_VERTICAL, 0, 0, 48, 0)); - searchContainer.addView(searchField, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 36, Gravity.CENTER_VERTICAL, 0, 0, wrapInScrollView ? 0 : 48, 0)); + searchContainer.addView(searchField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.CENTER_VERTICAL, 0, 0, wrapInScrollView ? 0 : 48, 0)); searchContainer.addView(searchFieldCaption, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 36, Gravity.CENTER_VERTICAL | Gravity.RIGHT, 0, 5.5f, 48, 0)); } searchFilterLayout.setClipChildren(false); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java index c48c0d828..41f79690a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java @@ -15,11 +15,13 @@ import android.animation.ObjectAnimator; import android.app.Dialog; import android.content.Context; import android.graphics.Canvas; +import android.graphics.ColorFilter; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; import android.os.Build; import android.os.Bundle; import android.text.TextPaint; @@ -38,17 +40,22 @@ import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.LineProgressView; +import org.telegram.ui.Components.RLottieDrawable; import org.telegram.ui.Components.RLottieImageView; import org.telegram.ui.Components.RadialProgressView; import org.telegram.ui.Components.spoilers.SpoilersTextView; import java.util.ArrayList; +import java.util.Map; public class AlertDialog extends Dialog implements Drawable.Callback { @@ -88,8 +95,10 @@ public class AlertDialog extends Dialog implements Drawable.Callback { private CharSequence message; private int topResId; private View topView; + private boolean topAnimationIsNew; private int topAnimationId; private int topAnimationSize; + private Map topAnimationLayerColors; private int topHeight = 132; private Drawable topDrawable; private int topBackgroundColor; @@ -139,7 +148,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback { private ArrayList itemViews = new ArrayList<>(); private float aspectRatio; private boolean dimEnabled = true; - private float dimAlpha = 0.6f; + private float dimAlpha = 0.5f; private boolean dimCustom = false; private final Theme.ResourcesProvider resourcesProvider; private boolean topAnimationAutoRepeat = true; @@ -213,7 +222,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback { backgroundPaddings = new Rect(); if (progressStyle != 3) { - shadowDrawable = context.getResources().getDrawable(R.drawable.popup_fixed_alert).mutate(); + shadowDrawable = context.getResources().getDrawable(R.drawable.popup_fixed_alert3).mutate(); shadowDrawable.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_dialogBackground), PorterDuff.Mode.MULTIPLY)); shadowDrawable.getPadding(backgroundPaddings); } @@ -354,7 +363,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback { } } - setMeasuredDimension(width, maxContentHeight - availableHeight + getPaddingTop() + getPaddingBottom()); + setMeasuredDimension(width, maxContentHeight - availableHeight + getPaddingTop() + getPaddingBottom() - (topAnimationIsNew ? AndroidUtilities.dp(8) : 0)); inLayout = false; if (lastScreenWidth != AndroidUtilities.displaySize.x) { @@ -468,11 +477,53 @@ public class AlertDialog extends Dialog implements Drawable.Callback { } else { topImageView.setAutoRepeat(topAnimationAutoRepeat); topImageView.setAnimation(topAnimationId, topAnimationSize, topAnimationSize); + if (topAnimationLayerColors != null) { + RLottieDrawable drawable = topImageView.getAnimatedDrawable(); + for (Map.Entry en : topAnimationLayerColors.entrySet()) { + drawable.setLayerColor(en.getKey(), en.getValue()); + } + } topImageView.playAnimation(); } topImageView.setScaleType(ImageView.ScaleType.CENTER); - topImageView.setBackgroundDrawable(getContext().getResources().getDrawable(R.drawable.popup_fixed_top)); - topImageView.getBackground().setColorFilter(new PorterDuffColorFilter(topBackgroundColor, PorterDuff.Mode.MULTIPLY)); + if (topAnimationIsNew) { + GradientDrawable d = new GradientDrawable(); + d.setColor(topBackgroundColor); + d.setCornerRadius(AndroidUtilities.dp(128)); + topImageView.setBackground(new Drawable() { + int size = topAnimationSize + AndroidUtilities.dp(52); + + @Override + public void draw(@NonNull Canvas canvas) { + d.setBounds((int) ((topImageView.getWidth() - size) / 2f), (int) ((topImageView.getHeight() - size) / 2f), (int) ((topImageView.getWidth() + size) / 2f), (int) ((topImageView.getHeight() + size) / 2f)); + d.draw(canvas); + } + + @Override + public void setAlpha(int alpha) { + d.setAlpha(alpha); + } + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) { + d.setColorFilter(colorFilter); + } + + @Override + public int getOpacity() { + return d.getOpacity(); + } + }); + topHeight = 92; + } else { + topImageView.setBackgroundDrawable(getContext().getResources().getDrawable(R.drawable.popup_fixed_top)); + topImageView.getBackground().setColorFilter(new PorterDuffColorFilter(topBackgroundColor, PorterDuff.Mode.MULTIPLY)); + } + if (topAnimationIsNew) { + topImageView.setTranslationY(AndroidUtilities.dp(16)); + } else { + topImageView.setTranslationY(0); + } topImageView.setPadding(0, 0, 0, 0); containerView.addView(topImageView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, topHeight, Gravity.LEFT | Gravity.TOP, -8, -8, 0, 0)); } else if (topView != null) { @@ -482,15 +533,15 @@ public class AlertDialog extends Dialog implements Drawable.Callback { if (title != null) { titleContainer = new FrameLayout(getContext()); - containerView.addView(titleContainer, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, 24, 0, 24, 0)); + containerView.addView(titleContainer, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, topAnimationIsNew ? Gravity.CENTER_HORIZONTAL : 0, 24, 0, 24, 0)); titleTextView = new SpoilersTextView(getContext(), false); titleTextView.setText(title); titleTextView.setTextColor(getThemedColor(Theme.key_dialogTextBlack)); titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - titleTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); - titleContainer.addView(titleTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 0, 19, 0, (subtitle != null ? 2 : (items != null ? 14 : 10)))); + titleTextView.setGravity((topAnimationIsNew ? Gravity.CENTER_HORIZONTAL : LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + titleContainer.addView(titleTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (topAnimationIsNew ? Gravity.CENTER_HORIZONTAL : LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 0, 19, 0, topAnimationIsNew ? 4 : (subtitle != null ? 2 : (items != null ? 14 : 10)))); } if (secondTitle != null && title != null) { @@ -544,7 +595,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback { } messageTextView = new SpoilersTextView(getContext(), false); - messageTextView.setTextColor(getThemedColor(Theme.key_dialogTextBlack)); + messageTextView.setTextColor(getThemedColor(topAnimationIsNew ? Theme.key_windowBackgroundWhiteGrayText : Theme.key_dialogTextBlack)); messageTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); messageTextView.setMovementMethod(new AndroidUtilities.LinkMovementMethodMy()); messageTextView.setLinkTextColor(getThemedColor(Theme.key_dialogTextLink)); @@ -552,7 +603,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback { messageTextView.setClickable(false); messageTextView.setEnabled(false); } - messageTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + messageTextView.setGravity((topAnimationIsNew ? Gravity.CENTER_HORIZONTAL : LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); if (progressViewStyle == 1) { progressViewContainer = new FrameLayout(getContext()); containerView.addView(progressViewContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 44, Gravity.LEFT | Gravity.TOP, 23, title == null ? 24 : 0, 23, 24)); @@ -592,7 +643,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback { progressView.setProgressColor(getThemedColor(Theme.key_dialog_inlineProgress)); progressViewContainer.addView(progressView, LayoutHelper.createLinear(86, 86)); } else { - scrollContainer.addView(messageTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 0, 24, customView != null || items != null ? customViewOffset : 0)); + scrollContainer.addView(messageTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (topAnimationIsNew ? Gravity.CENTER_HORIZONTAL : LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 0, 24, customView != null || items != null ? customViewOffset : 0)); } if (!TextUtils.isEmpty(message)) { messageTextView.setText(message); @@ -734,6 +785,9 @@ public class AlertDialog extends Dialog implements Drawable.Callback { } buttonsLayout.setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8)); containerView.addView(buttonsLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 52)); + if (topAnimationIsNew) { + buttonsLayout.setTranslationY(-AndroidUtilities.dp(8)); + } if (positiveButtonText != null) { TextView textView = new TextView(getContext()) { @@ -746,20 +800,20 @@ public class AlertDialog extends Dialog implements Drawable.Callback { @Override public void setTextColor(int color) { super.setTextColor(color); - setBackgroundDrawable(Theme.getRoundRectSelectorDrawable(color)); + setBackgroundDrawable(Theme.getRoundRectSelectorDrawable(AndroidUtilities.dp(6), color)); } }; textView.setMinWidth(AndroidUtilities.dp(64)); textView.setTag(Dialog.BUTTON_POSITIVE); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setTextColor(getThemedColor(dialogButtonColorKey)); textView.setGravity(Gravity.CENTER); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); // textView.setLines(1); // textView.setSingleLine(true); //TODO - textView.setText(positiveButtonText.toString().toUpperCase()); - textView.setBackgroundDrawable(Theme.getRoundRectSelectorDrawable(getThemedColor(dialogButtonColorKey))); - textView.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), 0); + textView.setText(positiveButtonText.toString()); + textView.setBackgroundDrawable(Theme.getRoundRectSelectorDrawable(AndroidUtilities.dp(6), getThemedColor(dialogButtonColorKey))); + textView.setPadding(AndroidUtilities.dp(12), 0, AndroidUtilities.dp(12), 0); if (verticalButtons) { buttonsLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, 36, LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT)); } else { @@ -786,20 +840,20 @@ public class AlertDialog extends Dialog implements Drawable.Callback { @Override public void setTextColor(int color) { super.setTextColor(color); - setBackgroundDrawable(Theme.getRoundRectSelectorDrawable(color)); + setBackgroundDrawable(Theme.getRoundRectSelectorDrawable(AndroidUtilities.dp(6), color)); } }; textView.setMinWidth(AndroidUtilities.dp(64)); textView.setTag(Dialog.BUTTON_NEGATIVE); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setTextColor(getThemedColor(dialogButtonColorKey)); textView.setGravity(Gravity.CENTER); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); textView.setEllipsize(TextUtils.TruncateAt.END); textView.setSingleLine(true); - textView.setText(negativeButtonText.toString().toUpperCase()); - textView.setBackgroundDrawable(Theme.getRoundRectSelectorDrawable(getThemedColor(dialogButtonColorKey))); - textView.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), 0); + textView.setText(negativeButtonText.toString()); + textView.setBackgroundDrawable(Theme.getRoundRectSelectorDrawable(AndroidUtilities.dp(6), getThemedColor(dialogButtonColorKey))); + textView.setPadding(AndroidUtilities.dp(12), 0, AndroidUtilities.dp(12), 0); if (verticalButtons) { buttonsLayout.addView(textView, 0, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, 36, LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT)); } else { @@ -826,20 +880,20 @@ public class AlertDialog extends Dialog implements Drawable.Callback { @Override public void setTextColor(int color) { super.setTextColor(color); - setBackgroundDrawable(Theme.getRoundRectSelectorDrawable(color)); + setBackgroundDrawable(Theme.getRoundRectSelectorDrawable(AndroidUtilities.dp(6), color)); } }; textView.setMinWidth(AndroidUtilities.dp(64)); textView.setTag(Dialog.BUTTON_NEUTRAL); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setTextColor(getThemedColor(dialogButtonColorKey)); textView.setGravity(Gravity.CENTER); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); textView.setEllipsize(TextUtils.TruncateAt.END); textView.setSingleLine(true); - textView.setText(neutralButtonText.toString().toUpperCase()); - textView.setBackground(Theme.getRoundRectSelectorDrawable(getThemedColor(dialogButtonColorKey))); - textView.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), 0); + textView.setText(neutralButtonText.toString()); + textView.setBackground(Theme.getRoundRectSelectorDrawable(AndroidUtilities.dp(6), getThemedColor(dialogButtonColorKey))); + textView.setPadding(AndroidUtilities.dp(12), 0, AndroidUtilities.dp(12), 0); if (verticalButtons) { buttonsLayout.addView(textView, 1, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, 36, LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT)); } else { @@ -1296,10 +1350,20 @@ public class AlertDialog extends Dialog implements Drawable.Callback { } public Builder setTopAnimation(int resId, int size, boolean autoRepeat, int backgroundColor) { + return setTopAnimation(resId, size, autoRepeat, backgroundColor, null); + } + + public Builder setTopAnimation(int resId, int size, boolean autoRepeat, int backgroundColor, Map layerColors) { alertDialog.topAnimationId = resId; alertDialog.topAnimationSize = size; alertDialog.topAnimationAutoRepeat = autoRepeat; alertDialog.topBackgroundColor = backgroundColor; + alertDialog.topAnimationLayerColors = layerColors; + return this; + } + + public Builder setTopAnimationIsNew(boolean isNew) { + alertDialog.topAnimationIsNew = isNew; return this; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java index 3305d7a24..b1200182e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java @@ -15,6 +15,7 @@ import android.app.Dialog; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.graphics.Canvas; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.Build; @@ -62,7 +63,7 @@ public abstract class BaseFragment { protected int currentAccount = UserConfig.selectedAccount; protected View fragmentView; - protected ActionBarLayout parentLayout; + protected INavigationLayout parentLayout; protected ActionBar actionBar; protected boolean inPreviewMode; protected boolean inMenuMode; @@ -92,6 +93,18 @@ public abstract class BaseFragment { currentAccount = account; } + public boolean hasOwnBackground() { + return hasOwnBackground; + } + + public void setHasOwnBackground(boolean hasOwnBackground) { + this.hasOwnBackground = hasOwnBackground; + } + + public boolean getFragmentBeginToShow() { + return fragmentBeginToShow; + } + public ActionBar getActionBar() { return actionBar; } @@ -100,6 +113,10 @@ public abstract class BaseFragment { return fragmentView; } + public void setFragmentView(View fragmentView) { + this.fragmentView = fragmentView; + } + public View createView(Context context) { return null; } @@ -136,7 +153,7 @@ public abstract class BaseFragment { return parentLayout != null && parentLayout.isInPassivePreviewMode(); } - protected void setInPreviewMode(boolean value) { + public void setInPreviewMode(boolean value) { inPreviewMode = value; if (actionBar != null) { if (inPreviewMode) { @@ -147,18 +164,18 @@ public abstract class BaseFragment { } } - protected void setInMenuMode(boolean value) { + public void setInMenuMode(boolean value) { inMenuMode = value; } - protected void onPreviewOpenAnimationEnd() { + public void onPreviewOpenAnimationEnd() { } protected boolean hideKeyboardOnShow() { return true; } - protected void clearViews() { + public void clearViews() { if (fragmentView != null) { ViewGroup parent = (ViewGroup) fragmentView.getParent(); if (parent != null) { @@ -185,16 +202,16 @@ public abstract class BaseFragment { parentLayout = null; } - protected void onRemoveFromParent() { + public void onRemoveFromParent() { } public void setParentFragment(BaseFragment fragment) { setParentLayout(fragment.parentLayout); - fragmentView = createView(parentLayout.getContext()); + fragmentView = createView(parentLayout.getView().getContext()); } - protected void setParentLayout(ActionBarLayout layout) { + public void setParentLayout(INavigationLayout layout) { if (parentLayout != layout) { parentLayout = layout; inBubbleMode = parentLayout != null && parentLayout.isInBubbleMode(); @@ -208,12 +225,12 @@ public abstract class BaseFragment { FileLog.e(e); } } - if (parentLayout != null && parentLayout.getContext() != fragmentView.getContext()) { + if (parentLayout != null && parentLayout.getView().getContext() != fragmentView.getContext()) { fragmentView = null; } } if (actionBar != null) { - boolean differentParent = parentLayout != null && parentLayout.getContext() != actionBar.getContext(); + boolean differentParent = parentLayout != null && parentLayout.getView().getContext() != actionBar.getContext(); if (actionBar.shouldAddToContainer() || differentParent) { ViewGroup parent = (ViewGroup) actionBar.getParent(); if (parent != null) { @@ -229,7 +246,7 @@ public abstract class BaseFragment { } } if (parentLayout != null && actionBar == null) { - actionBar = createActionBar(parentLayout.getContext()); + actionBar = createActionBar(parentLayout.getView().getContext()); if (actionBar != null) { actionBar.parentFragment = this; } @@ -340,11 +357,23 @@ public abstract class BaseFragment { } } + public void setPaused(boolean paused) { + if (isPaused == paused) { + return; + } + + if (paused) { + onPause(); + } else { + onResume(); + } + } + public BaseFragment getFragmentForAlert(int offset) { - if (parentLayout == null || parentLayout.fragmentsStack.size() <= 1 + offset) { + if (parentLayout == null || parentLayout.getFragmentStack().size() <= 1 + offset) { return this; } - return parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2 - offset); + return parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 2 - offset); } public void onConfigurationChanged(android.content.res.Configuration newConfig) { @@ -372,10 +401,10 @@ public abstract class BaseFragment { } public boolean isLastFragment() { - return parentLayout != null && !parentLayout.fragmentsStack.isEmpty() && parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 1) == this; + return parentLayout != null && parentLayout.getLastFragment() == this; } - public ActionBarLayout getParentLayout() { + public INavigationLayout getParentLayout() { return parentLayout; } @@ -409,9 +438,13 @@ public abstract class BaseFragment { return allowPresentFragment() && parentLayout != null && parentLayout.presentFragment(fragment, removeLast, forceWithoutAnimation, true, false, null); } + public boolean presentFragment(INavigationLayout.NavigationParams params) { + return allowPresentFragment() && parentLayout != null && parentLayout.presentFragment(params); + } + public Activity getParentActivity() { if (parentLayout != null) { - return parentLayout.parentActivity; + return parentLayout.getParentActivity(); } return null; } @@ -467,26 +500,26 @@ public abstract class BaseFragment { } } - protected void onSlideProgress(boolean isOpen, float progress) { + public void onSlideProgress(boolean isOpen, float progress) { } - protected void onTransitionAnimationProgress(boolean isOpen, float progress) { + public void onTransitionAnimationProgress(boolean isOpen, float progress) { } - protected void onTransitionAnimationStart(boolean isOpen, boolean backward) { + public void onTransitionAnimationStart(boolean isOpen, boolean backward) { inTransitionAnimation = true; if (isOpen) { fragmentBeginToShow = true; } } - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { inTransitionAnimation = false; } - protected void onBecomeFullyVisible() { + public void onBecomeFullyVisible() { AccessibilityManager mgr = (AccessibilityManager) ApplicationLoader.applicationContext.getSystemService(Context.ACCESSIBILITY_SERVICE); if (mgr.isEnabled()) { ActionBar actionBar = getActionBar(); @@ -499,15 +532,15 @@ public abstract class BaseFragment { } } - protected int getPreviewHeight() { + public int getPreviewHeight() { return LayoutHelper.MATCH_PARENT; } - protected void onBecomeFullyHidden() { + public void onBecomeFullyHidden() { } - protected AnimatorSet onCustomTransitionAnimation(boolean isOpen, final Runnable callback) { + public AnimatorSet onCustomTransitionAnimation(boolean isOpen, Runnable callback) { return null; } @@ -524,7 +557,7 @@ public abstract class BaseFragment { } public Dialog showDialog(Dialog dialog, boolean allowInTransition, final Dialog.OnDismissListener onDismissListener) { - if (dialog == null || parentLayout == null || parentLayout.animationInProgress || parentLayout.startedTracking || !allowInTransition && parentLayout.checkTransitionAnimation()) { + if (dialog == null || parentLayout == null || parentLayout.isTransitionAnimationInProgress() || parentLayout.isSwipeInProgress() || !allowInTransition && parentLayout.checkTransitionAnimation()) { return null; } try { @@ -669,7 +702,7 @@ public abstract class BaseFragment { return false; } - protected void prepareFragmentToSlide(boolean topFragment, boolean beginSlide) { + public void prepareFragmentToSlide(boolean topFragment, boolean beginSlide) { } @@ -677,18 +710,18 @@ public abstract class BaseFragment { } - public ActionBarLayout[] showAsSheet(BaseFragment fragment) { + public INavigationLayout[] showAsSheet(BaseFragment fragment) { if (getParentActivity() == null) { return null; } - ActionBarLayout[] actionBarLayout = new ActionBarLayout[]{new ActionBarLayout(getParentActivity())}; + INavigationLayout[] actionBarLayout = new INavigationLayout[]{INavigationLayout.newLayout(getParentActivity())}; BottomSheet bottomSheet = new BottomSheet(getParentActivity(), true) { { - actionBarLayout[0].init(new ArrayList<>()); + actionBarLayout[0].setFragmentStack(new ArrayList<>()); actionBarLayout[0].addFragmentToStack(fragment); actionBarLayout[0].showLastFragment(); - actionBarLayout[0].setPadding(backgroundPaddingLeft, 0, backgroundPaddingLeft, 0); - containerView = actionBarLayout[0]; + actionBarLayout[0].getView().setPadding(backgroundPaddingLeft, 0, backgroundPaddingLeft, 0); + containerView = actionBarLayout[0].getView(); setApplyBottomPadding(false); setApplyBottomPadding(false); setOnDismissListener(dialog -> fragment.onFragmentDestroy()); @@ -701,7 +734,7 @@ public abstract class BaseFragment { @Override public void onBackPressed() { - if (actionBarLayout[0] == null || actionBarLayout[0].fragmentsStack.size() <= 1) { + if (actionBarLayout[0] == null || actionBarLayout[0].getFragmentStack().size() <= 1) { super.onBackPressed(); } else { actionBarLayout[0].onBackPressed(); @@ -791,4 +824,8 @@ public abstract class BaseFragment { } return ColorUtils.calculateLuminance(color) > 0.7f; } + + public void drawOverlay(Canvas canvas, View parent) { + + } } \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java index abc0aff9c..bca7d0f60 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java @@ -1599,7 +1599,8 @@ public class BottomSheet extends Dialog { try { super.dismiss(); } catch (Exception e) { - FileLog.e(e); + //ignore: not attached to window manager + FileLog.e(e, false); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java index 205667889..e011b5da9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java @@ -51,7 +51,7 @@ public class DrawerLayoutContainer extends FrameLayout { private static final int MIN_DRAWER_MARGIN = 64; private ViewGroup drawerLayout; - private ActionBarLayout parentActionBarLayout; + private INavigationLayout parentActionBarLayout; private boolean maybeStartTracking; private boolean startedTracking; @@ -204,8 +204,8 @@ public class DrawerLayoutContainer extends FrameLayout { if (drawerLayout.getVisibility() != newVisibility) { drawerLayout.setVisibility(newVisibility); } - if (!parentActionBarLayout.fragmentsStack.isEmpty()) { - BaseFragment currentFragment = parentActionBarLayout.fragmentsStack.get(0); + if (!parentActionBarLayout.getFragmentStack().isEmpty()) { + BaseFragment currentFragment = parentActionBarLayout.getFragmentStack().get(0); if (drawerPosition == drawerLayout.getMeasuredWidth()) { currentFragment.setProgressToDrawerOpened(1f); } else if (drawerPosition == 0) { @@ -233,8 +233,8 @@ public class DrawerLayoutContainer extends FrameLayout { if (!allowOpenDrawer || drawerLayout == null) { return; } - if (AndroidUtilities.isTablet() && parentActionBarLayout != null && parentActionBarLayout.parentActivity != null) { - AndroidUtilities.hideKeyboard(parentActionBarLayout.parentActivity.getCurrentFocus()); + if (AndroidUtilities.isTablet() && parentActionBarLayout != null && parentActionBarLayout.getParentActivity() != null) { + AndroidUtilities.hideKeyboard(parentActionBarLayout.getParentActivity().getCurrentFocus()); } cancelCurrentAnimation(); AnimatorSet animatorSet = new AnimatorSet(); @@ -307,7 +307,7 @@ public class DrawerLayoutContainer extends FrameLayout { return drawerLayout; } - public void setParentActionBarLayout(ActionBarLayout layout) { + public void setParentActionBarLayout(INavigationLayout layout) { parentActionBarLayout = layout; } @@ -432,13 +432,13 @@ public class DrawerLayoutContainer extends FrameLayout { return true; } - if ((allowOpenDrawerBySwipe || drawerOpened) && allowOpenDrawer && parentActionBarLayout.fragmentsStack.size() == 1) { + if ((allowOpenDrawerBySwipe || drawerOpened) && allowOpenDrawer && parentActionBarLayout.getFragmentStack().size() == 1) { if (ev != null && (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_MOVE) && !startedTracking && !maybeStartTracking) { View scrollingChild = findScrollingChild(this, ev.getX(),ev.getY()); if (scrollingChild != null) { return false; } - parentActionBarLayout.getHitRect(rect); + parentActionBarLayout.getView().getHitRect(rect); startedTrackingX = (int) ev.getX(); startedTrackingY = (int) ev.getY(); if (rect.contains(startedTrackingX, startedTrackingY)) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/INavigationLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/INavigationLayout.java new file mode 100644 index 000000000..96c71ee9d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/INavigationLayout.java @@ -0,0 +1,379 @@ +package org.telegram.ui.ActionBar; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; + +import org.telegram.messenger.SharedConfig; +import org.telegram.ui.Components.BackButtonMenu; +import org.telegram.ui.LNavigation.LNavigation; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public interface INavigationLayout { + int REBUILD_FLAG_REBUILD_LAST = 1, REBUILD_FLAG_REBUILD_ONLY_LAST = 2; + + boolean presentFragment(NavigationParams params); + boolean checkTransitionAnimation(); + boolean addFragmentToStack(BaseFragment fragment, int position); + void removeFragmentFromStack(BaseFragment fragment); + List getFragmentStack(); + void setDelegate(INavigationLayoutDelegate INavigationLayoutDelegate); + void closeLastFragment(boolean animated, boolean forceNoAnimation); + DrawerLayoutContainer getDrawerLayoutContainer(); + void setDrawerLayoutContainer(DrawerLayoutContainer drawerLayoutContainer); + void setRemoveActionBarExtraHeight(boolean removeExtraHeight); + void setTitleOverlayText(String title, int titleId, Runnable action); + void animateThemedValues(ThemeAnimationSettings settings, Runnable onDone); + float getThemeAnimationValue(); + void setFragmentStackChangedListener(Runnable onFragmentStackChanged); + boolean isTransitionAnimationInProgress(); + void resumeDelayedFragmentAnimation(); + + boolean isInPassivePreviewMode(); + void setInBubbleMode(boolean bubbleMode); + boolean isInBubbleMode(); + + boolean isInPreviewMode(); + boolean isPreviewOpenAnimationInProgress(); + void movePreviewFragment(float dy); + void expandPreviewFragment(); + void finishPreviewFragment(); + void setFragmentPanTranslationOffset(int offset); + ViewGroup getOverlayContainerView(); + void setHighlightActionButtons(boolean highlight); + float getCurrentPreviewFragmentAlpha(); + void drawCurrentPreviewFragment(Canvas canvas, Drawable foregroundDrawable); + + void drawHeaderShadow(Canvas canvas, int alpha, int y); + + boolean isSwipeInProgress(); + + void onPause(); + void onResume(); + void onBackPressed(); + void onUserLeaveHint(); + void onLowMemory(); + boolean extendActionMode(Menu menu); + void onActionModeStarted(Object mode); + void onActionModeFinished(Object mode); + void startActivityForResult(Intent intent, int requestCode); + + // TODO: Migrate them to be out of navigation layout + Theme.MessageDrawable getMessageDrawableOutStart(); + Theme.MessageDrawable getMessageDrawableOutMediaStart(); + + // TODO: Make something like FieldsContainer and put them there? + List getPulledDialogs(); + void setPulledDialogs(List pulledDialogs); + + static INavigationLayout newLayout(Context context) { + return SharedConfig.useLNavigation ? new LNavigation(context) : new ActionBarLayout(context); + } + + default boolean hasIntegratedBlurInPreview() { + return false; + } + + default void rebuildFragments(int flags) { + if ((flags & REBUILD_FLAG_REBUILD_ONLY_LAST) != 0) { + showLastFragment(); + return; + } + boolean last = (flags & REBUILD_FLAG_REBUILD_LAST) != 0; + rebuildAllFragmentViews(last, last); + } + + default void setBackgroundView(View backgroundView) { + // Not always required + } + + default void setUseAlphaAnimations(boolean useAlphaAnimations) { + // Not always required + } + + /** + * @deprecated Should be replaced with {@link INavigationLayout#rebuildFragments(int)} + */ + @Deprecated + default void rebuildLogout() { + // No-op usually, can contain hackfixes + } + + /** + * @deprecated Should be replaced with {@link INavigationLayout#rebuildFragments(int)} + */ + @Deprecated + default void showLastFragment() {} + + /** + * @deprecated Should be replaced with {@link INavigationLayout#rebuildFragments(int)} + */ + @Deprecated + default void rebuildAllFragmentViews(boolean last, boolean showLastAfter) {} + + default void drawHeaderShadow(Canvas canvas, int y) { + drawHeaderShadow(canvas, 0xFF, y); + } + + default BaseFragment getBackgroundFragment() { + return getFragmentStack().size() <= 1 ? null : getFragmentStack().get(getFragmentStack().size() - 2); + } + + default BaseFragment getLastFragment() { + return getFragmentStack().isEmpty() ? null : getFragmentStack().get(getFragmentStack().size() - 1); + } + + default void animateThemedValues(Theme.ThemeInfo theme, int accentId, boolean nightTheme, boolean instant) { + animateThemedValues(new ThemeAnimationSettings(theme, accentId, nightTheme, instant), null); + } + + default void animateThemedValues(Theme.ThemeInfo theme, int accentId, boolean nightTheme, boolean instant, Runnable onDone) { + animateThemedValues(new ThemeAnimationSettings(theme, accentId, nightTheme, instant), onDone); + } + + /** + * @deprecated Deprecated in favor of {@link INavigationLayout#bringToFront(int)} + */ + @Deprecated + default void showFragment(int i) { + bringToFront(i); + } + + default void bringToFront(int i) { + BaseFragment fragment = getFragmentStack().get(i); + removeFragmentFromStack(fragment); + addFragmentToStack(fragment); + rebuildFragments(REBUILD_FLAG_REBUILD_ONLY_LAST); + } + + default void removeAllFragments() { + for (BaseFragment fragment : new ArrayList<>(getFragmentStack())) { + removeFragmentFromStack(fragment); + } + } + + default Activity getParentActivity() { + Context ctx = getView().getContext(); + if (ctx instanceof Activity) { + return (Activity) ctx; + } + throw new IllegalArgumentException("NavigationLayout added in non-activity context!"); + } + + default ViewGroup getView() { + if (this instanceof ViewGroup) { + return (ViewGroup) this; + } + throw new IllegalArgumentException("You should override getView() if you're not inheriting from it."); + } + + default void closeLastFragment() { + closeLastFragment(true); + } + + default void closeLastFragment(boolean animated) { + closeLastFragment(animated, false); + } + + default void setFragmentStack(List stack) { + init(stack); + } + + /** + * @deprecated This method was replaced with {@link INavigationLayout#setFragmentStack(List)} + */ + @Deprecated + default void init(List stack) { + throw new RuntimeException("Neither setFragmentStack(...) or init(...) were overriden!"); + } + + default void removeFragmentFromStack(int i) { + if (i < 0 || i >= getFragmentStack().size()) { + return; + } + removeFragmentFromStack(getFragmentStack().get(i)); + } + + default boolean addFragmentToStack(BaseFragment fragment) { + return addFragmentToStack(fragment, -1); + } + + default boolean presentFragment(BaseFragment fragment) { + return presentFragment(new NavigationParams(fragment)); + } + + default boolean presentFragment(BaseFragment fragment, boolean removeLast) { + return presentFragment(new NavigationParams(fragment).setRemoveLast(removeLast)); + } + + default boolean presentFragmentAsPreview(BaseFragment fragment) { + return presentFragment(new NavigationParams(fragment).setPreview(true)); + } + + default boolean presentFragmentAsPreviewWithMenu(BaseFragment fragment, ActionBarPopupWindow.ActionBarPopupWindowLayout menuView) { + return presentFragment(new NavigationParams(fragment).setPreview(true).setMenuView(menuView)); + } + + /** + * @deprecated You should use {@link INavigationLayout.NavigationParams} for advanced params + */ + @Deprecated + default boolean presentFragment(BaseFragment fragment, boolean removeLast, boolean forceWithoutAnimation, boolean check, boolean preview) { + return presentFragment(new NavigationParams(fragment).setRemoveLast(removeLast).setNoAnimation(forceWithoutAnimation).setCheckPresentFromDelegate(check).setPreview(preview)); + } + + /** + * @deprecated You should use {@link INavigationLayout.NavigationParams} for advanced params + */ + @Deprecated + default boolean presentFragment(BaseFragment fragment, boolean removeLast, boolean forceWithoutAnimation, boolean check, boolean preview, ActionBarPopupWindow.ActionBarPopupWindowLayout menuView) { + return presentFragment(new NavigationParams(fragment).setRemoveLast(removeLast).setNoAnimation(forceWithoutAnimation).setCheckPresentFromDelegate(check).setPreview(preview).setMenuView(menuView)); + } + + default void dismissDialogs() { + List fragmentsStack = getFragmentStack(); + if (!fragmentsStack.isEmpty()) { + BaseFragment lastFragment = fragmentsStack.get(fragmentsStack.size() - 1); + lastFragment.dismissCurrentDialog(); + } + } + + interface INavigationLayoutDelegate { + default boolean needPresentFragment(INavigationLayout layout, NavigationParams params) { + return needPresentFragment(params.fragment, params.removeLast, params.noAnimation, layout); + } + + /** + * @deprecated You should override {@link INavigationLayoutDelegate#needPresentFragment(INavigationLayout, NavigationParams)} for more fields + */ + default boolean needPresentFragment(BaseFragment fragment, boolean removeLast, boolean forceWithoutAnimation, INavigationLayout layout) { + return true; + } + + default boolean needAddFragmentToStack(BaseFragment fragment, INavigationLayout layout) { + return true; + } + + default boolean onPreIme() { + return false; + } + + default boolean needCloseLastFragment(INavigationLayout layout) { + return true; + } + + default void onMeasureOverride(int[] measureSpec) {} + default void onRebuildAllFragments(INavigationLayout layout, boolean last) {} + default void onThemeProgress(float progress) {} + } + + class NavigationParams { + public BaseFragment fragment; + public boolean removeLast; + public boolean noAnimation; + public boolean checkPresentFromDelegate = true; + public boolean preview; + public ActionBarPopupWindow.ActionBarPopupWindowLayout menuView; + public boolean needDelayWithoutAnimation; + + public boolean isFromDelay; + public boolean delayDone; + + public NavigationParams(BaseFragment fragment) { + this.fragment = fragment; + } + + public NavigationParams setRemoveLast(boolean removeLast) { + this.removeLast = removeLast; + return this; + } + + public NavigationParams setNoAnimation(boolean noAnimation) { + this.noAnimation = noAnimation; + return this; + } + + public NavigationParams setCheckPresentFromDelegate(boolean checkPresentFromDelegate) { + this.checkPresentFromDelegate = checkPresentFromDelegate; + return this; + } + + public NavigationParams setPreview(boolean preview) { + this.preview = preview; + return this; + } + + public NavigationParams setMenuView(ActionBarPopupWindow.ActionBarPopupWindowLayout menuView) { + this.menuView = menuView; + return this; + } + + public NavigationParams setNeedDelayWithoutAnimation(boolean needDelayWithoutAnimation) { + this.needDelayWithoutAnimation = needDelayWithoutAnimation; + return this; + } + } + + class ThemeAnimationSettings { + public final Theme.ThemeInfo theme; + public final int accentId; + public final boolean nightTheme; + public final boolean instant; + public boolean onlyTopFragment; + public boolean applyTheme = true; + public Runnable afterStartDescriptionsAddedRunnable; + public Runnable beforeAnimationRunnable; + public Runnable afterAnimationRunnable; + public onAnimationProgress animationProgress; + public long duration = 200; + public Theme.ResourcesProvider resourcesProvider; + + public ThemeAnimationSettings(Theme.ThemeInfo theme, int accentId, boolean nightTheme, boolean instant) { + this.theme = theme; + this.accentId = accentId; + this.nightTheme = nightTheme; + this.instant = instant; + } + + public interface onAnimationProgress { + void setProgress(float p); + } + } + + class StartColorsProvider implements Theme.ResourcesProvider { + HashMap colors = new HashMap<>(); + String[] keysToSave = new String[] { + Theme.key_chat_outBubble, + Theme.key_chat_outBubbleGradient1, + Theme.key_chat_outBubbleGradient2, + Theme.key_chat_outBubbleGradient3, + Theme.key_chat_outBubbleGradientAnimated, + Theme.key_chat_outBubbleShadow + }; + + @Override + public Integer getColor(String key) { + return colors.get(key); + } + + @Override + public Integer getCurrentColor(String key) { + return colors.get(key); + } + + public void saveColors(Theme.ResourcesProvider fragmentResourceProvider) { + colors.clear(); + for (String key : keysToSave) { + colors.put(key, fragmentResourceProvider.getCurrentColor(key)); + } + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java index e234065f7..f8cb565d5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java @@ -27,14 +27,11 @@ import android.text.SpannableStringBuilder; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; -import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; -import androidx.core.math.MathUtils; - import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.Emoji; import org.telegram.ui.Cells.DialogCell; @@ -71,6 +68,7 @@ public class SimpleTextView extends View implements Drawable.Callback { private int rightDrawableTopPadding; private boolean buildFullLayout; private float fullAlpha; + private boolean widthWrapContent; private Drawable wrapBackgroundDrawable; @@ -82,6 +80,7 @@ public class SimpleTextView extends View implements Drawable.Callback { private Paint fadePaint; private Paint fadePaintBack; private Paint fadeEllpsizePaint; + private int fadeEllpsizePaintWidth; private int lastWidth; private int offsetX; @@ -95,6 +94,7 @@ public class SimpleTextView extends View implements Drawable.Callback { private boolean rightDrawableOutside; private boolean ellipsizeByGradient; + private int ellipsizeByGradientWidthDp = 16; private int paddingRight; private int minWidth; @@ -124,6 +124,8 @@ public class SimpleTextView extends View implements Drawable.Callback { private AnimatedEmojiSpan.EmojiGroupedSpans emojiStack; private boolean attachedToWindow; + private Layout.Alignment mAlignment = Layout.Alignment.ALIGN_NORMAL; + public SimpleTextView(Context context) { super(context); textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); @@ -196,6 +198,16 @@ public class SimpleTextView extends View implements Drawable.Callback { updateFadePaints(); } + public void setEllipsizeByGradient(int value) { + setEllipsizeByGradient(true); + ellipsizeByGradientWidthDp = value; + updateFadePaints(); + } + + public void setWidthWrapContent(boolean value) { + widthWrapContent = value; + } + private void updateFadePaints() { if ((fadePaint == null || fadePaintBack == null) && scrollNonFitText) { fadePaint = new Paint(); @@ -206,9 +218,9 @@ public class SimpleTextView extends View implements Drawable.Callback { fadePaintBack.setShader(new LinearGradient(0, 0, AndroidUtilities.dp(6), 0, new int[]{0, 0xffffffff}, new float[]{0f, 1f}, Shader.TileMode.CLAMP)); fadePaintBack.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); } - if (fadeEllpsizePaint == null && ellipsizeByGradient) { + if ((fadeEllpsizePaint == null || fadeEllpsizePaintWidth != AndroidUtilities.dp(ellipsizeByGradientWidthDp)) && ellipsizeByGradient) { fadeEllpsizePaint = new Paint(); - fadeEllpsizePaint.setShader(new LinearGradient(0, 0, AndroidUtilities.dp(16), 0, new int[]{0, 0xffffffff}, new float[]{0f, 1f}, Shader.TileMode.CLAMP)); + fadeEllpsizePaint.setShader(new LinearGradient(0, 0, fadeEllpsizePaintWidth = AndroidUtilities.dp(ellipsizeByGradientWidthDp), 0, new int[]{0, 0xffffffff}, new float[]{0f, 1f}, Shader.TileMode.CLAMP)); fadeEllpsizePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); } } @@ -325,7 +337,7 @@ public class SimpleTextView extends View implements Drawable.Callback { string = TextUtils.ellipsize(string, textPaint, width, TextUtils.TruncateAt.END); } if (!ellipsizeByGradient && !string.equals(text)) { - fullLayout = StaticLayoutEx.createStaticLayout(text, 0, text.length(), textPaint, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.END, width, fullTextMaxLines, false); + fullLayout = StaticLayoutEx.createStaticLayout(text, 0, text.length(), textPaint, width, getAlignment(), 1.0f, 0.0f, false, TextUtils.TruncateAt.END, width, fullTextMaxLines, false); if (fullLayout != null) { int end = fullLayout.getLineEnd(0); int start = fullLayout.getLineStart(1); @@ -338,22 +350,22 @@ public class SimpleTextView extends View implements Drawable.Callback { } else { part = "…"; } - firstLineLayout = new StaticLayout(string, 0, string.length(), textPaint, scrollNonFitText ? AndroidUtilities.dp(2000) : width + AndroidUtilities.dp(8), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - layout = new StaticLayout(substr, 0, substr.length(), textPaint, scrollNonFitText ? AndroidUtilities.dp(2000) : width + AndroidUtilities.dp(8), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + firstLineLayout = new StaticLayout(string, 0, string.length(), textPaint, scrollNonFitText ? AndroidUtilities.dp(2000) : width + AndroidUtilities.dp(8), getAlignment(), 1.0f, 0.0f, false); + layout = new StaticLayout(substr, 0, substr.length(), textPaint, scrollNonFitText ? AndroidUtilities.dp(2000) : width + AndroidUtilities.dp(8), getAlignment(), 1.0f, 0.0f, false); if (layout.getLineLeft(0) != 0) { part = "\u200F" + part; } - partLayout = new StaticLayout(part, 0, part.length(), textPaint, scrollNonFitText ? AndroidUtilities.dp(2000) : width + AndroidUtilities.dp(8), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - fullLayout = StaticLayoutEx.createStaticLayout(full, 0, full.length(), textPaint, width + AndroidUtilities.dp(8) + fullLayoutAdditionalWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.END, width + fullLayoutAdditionalWidth, fullTextMaxLines, false); + partLayout = new StaticLayout(part, 0, part.length(), textPaint, scrollNonFitText ? AndroidUtilities.dp(2000) : width + AndroidUtilities.dp(8), getAlignment(), 1.0f, 0.0f, false); + fullLayout = StaticLayoutEx.createStaticLayout(full, 0, full.length(), textPaint, width + AndroidUtilities.dp(8) + fullLayoutAdditionalWidth, getAlignment(), 1.0f, 0.0f, false, TextUtils.TruncateAt.END, width + fullLayoutAdditionalWidth, fullTextMaxLines, false); } } else { - layout = new StaticLayout(string, 0, string.length(), textPaint, scrollNonFitText || ellipsizeByGradient ? AndroidUtilities.dp(2000) : width + AndroidUtilities.dp(8), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + layout = new StaticLayout(string, 0, string.length(), textPaint, scrollNonFitText || ellipsizeByGradient ? AndroidUtilities.dp(2000) : width + AndroidUtilities.dp(8), getAlignment(), 1.0f, 0.0f, false); fullLayout = null; partLayout = null; firstLineLayout = null; } } else if (maxLines > 1) { - layout = StaticLayoutEx.createStaticLayout(text, 0, text.length(), textPaint, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.END, width, maxLines, false); + layout = StaticLayoutEx.createStaticLayout(text, 0, text.length(), textPaint, width, getAlignment(), 1.0f, 0.0f, false, TextUtils.TruncateAt.END, width, maxLines, false); } else { CharSequence string; if (scrollNonFitText || ellipsizeByGradient) { @@ -365,7 +377,7 @@ public class SimpleTextView extends View implements Drawable.Callback { calcOffset(width); return false; }*/ - layout = new StaticLayout(string, 0, string.length(), textPaint, scrollNonFitText || ellipsizeByGradient ? AndroidUtilities.dp(2000) : width + AndroidUtilities.dp(8), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + layout = new StaticLayout(string, 0, string.length(), textPaint, scrollNonFitText || ellipsizeByGradient ? AndroidUtilities.dp(2000) : width + AndroidUtilities.dp(8), getAlignment(), 1.0f, 0.0f, false); } spoilersPool.addAll(spoilers); @@ -390,6 +402,15 @@ public class SimpleTextView extends View implements Drawable.Callback { return true; } + public void setAlignment(Layout.Alignment alignment) { + mAlignment = alignment; + requestLayout(); + } + + private Layout.Alignment getAlignment() { + return mAlignment; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); @@ -407,6 +428,10 @@ public class SimpleTextView extends View implements Drawable.Callback { } else { finalHeight = getPaddingTop() + textHeight + getPaddingBottom(); } + if (widthWrapContent) { +// textWidth = (int) Math.ceil(layout.getLineWidth(0)); + width = Math.min(width, getPaddingLeft() + textWidth + getPaddingRight() + minusWidth + (rightDrawableOutside && rightDrawable != null ? rightDrawable.getIntrinsicWidth() + drawablePadding : 0)); + } setMeasuredDimension(width, finalHeight); if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.CENTER_VERTICAL) { @@ -632,7 +657,10 @@ public class SimpleTextView extends View implements Drawable.Callback { } public void setRightPadding(int padding) { - paddingRight = padding; + if (paddingRight != padding) { + paddingRight = padding; + invalidate(); + } } @Override @@ -803,10 +831,10 @@ public class SimpleTextView extends View implements Drawable.Callback { canvas.translate(getMaxTextWidth() - paddingRight - AndroidUtilities.dp(6), 0); canvas.drawRect(0, 0, AndroidUtilities.dp(6), getMeasuredHeight(), fadePaintBack); canvas.restore(); - } else if (ellipsizeByGradient && fadeEllpsizePaint != null) { + } else if (ellipsizeByGradient && (!widthWrapContent || textDoesNotFit) && fadeEllpsizePaint != null) { canvas.save(); - canvas.translate(getMaxTextWidth() - paddingRight - AndroidUtilities.dp(rightDrawable != null && !(rightDrawable instanceof AnimatedEmojiDrawable.SwapAnimatedEmojiDrawable) && rightDrawableOutside ? 18 : 16), 0); - canvas.drawRect(0, 0, AndroidUtilities.dp(16), getMeasuredHeight(), fadeEllpsizePaint); + canvas.translate(getMaxTextWidth() - paddingRight - fadeEllpsizePaintWidth - AndroidUtilities.dp(rightDrawable != null && !(rightDrawable instanceof AnimatedEmojiDrawable.SwapAnimatedEmojiDrawable) && rightDrawableOutside ? +2 : 0), 0); + canvas.drawRect(0, 0, fadeEllpsizePaintWidth, getMeasuredHeight(), fadeEllpsizePaint); canvas.restore(); } updateScrollAnimation(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java index 3be424963..fe31bd40e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java @@ -170,6 +170,10 @@ public class Theme { public boolean isSelected; private Path path; + public Path getPath() { + return path; + } + private Rect backupRect = new Rect(); private final ResourcesProvider resourcesProvider; @@ -183,6 +187,7 @@ public class Theme { public static MotionBackgroundDrawable[] motionBackground = new MotionBackgroundDrawable[3]; private int[] currentShadowDrawableRadius = new int[]{-1, -1, -1, -1}; + private Bitmap[] shadowDrawableBitmap = new Bitmap[4]; private Drawable[] shadowDrawable = new Drawable[4]; private int[] shadowDrawableColor = new int[]{0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}; @@ -212,6 +217,8 @@ public class Theme { PathDrawParams pathDrawCacheParams; private int overrideRoundRadius; + private float overrideRounding; + public boolean forceInvalidatePath; public MessageDrawable(int type, boolean out, boolean selected) { this(type, out, selected, null); @@ -430,7 +437,14 @@ public class Theme { } public Drawable getBackgroundDrawable() { - int newRad = AndroidUtilities.dp(SharedConfig.bubbleRadius); + int newRad; + if (overrideRoundRadius != 0) { + newRad = overrideRoundRadius; + } else if (overrideRounding > 0) { + newRad = 0; + } else { + newRad = AndroidUtilities.dp(SharedConfig.bubbleRadius); + } int idx; if (isTopNear && isBottomNear) { idx = 3; @@ -557,6 +571,9 @@ public class Theme { boolean forceSetColor = false; if (currentShadowDrawableRadius[idx] != newRad) { currentShadowDrawableRadius[idx] = newRad; + if (shadowDrawableBitmap[idx] != null) { + shadowDrawableBitmap[idx].recycle(); + } try { Bitmap bitmap = Bitmap.createBitmap(dp(50), dp(40), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); @@ -582,6 +599,7 @@ public class Theme { draw(canvas, shadowPaint); } + shadowDrawableBitmap[idx] = bitmap; shadowDrawable[idx] = new NinePatchDrawable(bitmap, getByteBuffer(bitmap.getWidth() / 2 - 1, bitmap.getWidth() / 2 + 1, bitmap.getHeight() / 2 - 1, bitmap.getHeight() / 2 + 1).array(), new Rect(), null); forceSetColor = true; } catch (Throwable ignore) { @@ -596,6 +614,20 @@ public class Theme { return shadowDrawable[idx]; } + @Override + protected void finalize() throws Throwable { + super.finalize(); + + for (Bitmap bitmap : shadowDrawableBitmap) { + if (bitmap != null) { + bitmap.recycle(); + } + } + Arrays.fill(shadowDrawableBitmap, null); + Arrays.fill(shadowDrawable, null); + Arrays.fill(currentShadowDrawableRadius, -1); + } + private static ByteBuffer getByteBuffer(int x1, int x2, int y1, int y2) { ByteBuffer buffer = ByteBuffer.allocate(4 + 4 * 7 + 4 * 2 + 4 * 2 + 4 * 9).order(ByteOrder.nativeOrder()); buffer.put((byte) 0x01); @@ -662,7 +694,7 @@ public class Theme { public void draw(Canvas canvas, Paint paintToUse) { Rect bounds = getBounds(); - if (paintToUse == null && gradientShader == null) { + if (paintToUse == null && gradientShader == null && overrideRoundRadius == 0 && overrideRounding <= 0) { Drawable background = getBackgroundDrawable(); if (background != null) { background.setBounds(bounds); @@ -676,12 +708,15 @@ public class Theme { if (overrideRoundRadius != 0) { rad = overrideRoundRadius; nearRad = overrideRoundRadius; + } else if (overrideRounding > 0) { + rad = AndroidUtilities.lerp(dp(SharedConfig.bubbleRadius), Math.min(bounds.width(), bounds.height()) / 2, overrideRounding); + nearRad = AndroidUtilities.lerp(dp(Math.min(6, SharedConfig.bubbleRadius)), Math.min(bounds.width(), bounds.height()) / 2, overrideRounding); } else if (currentType == TYPE_PREVIEW) { rad = dp(6); nearRad = dp(6); } else { rad = dp(SharedConfig.bubbleRadius); - nearRad = dp(Math.min(5, SharedConfig.bubbleRadius)); + nearRad = dp(Math.min(6, SharedConfig.bubbleRadius)); } int smallRad = dp(6); @@ -712,8 +747,8 @@ public class Theme { path = this.path; invalidatePath = true; } - if (invalidatePath) { - path.reset(); + if (invalidatePath || overrideRoundRadius != 0) { + path.rewind(); if (isOut) { if (drawFullBubble || currentType == TYPE_PREVIEW || paintToUse != null || drawFullBottom) { if (currentType == TYPE_MEDIA) { @@ -888,6 +923,10 @@ public class Theme { this.overrideRoundRadius = radius; } + public void setRoundingRadius(float rounding) { + this.overrideRounding = rounding; + } + public static class PathDrawParams { Path path = new Path(); Rect lastRect = new Rect(); @@ -901,6 +940,10 @@ public class Theme { lastRect.set(bounds); return invalidate; } + + public Path getPath() { + return path; + } } } @@ -2366,6 +2409,21 @@ public class Theme { themeAccent.backgroundRotation = patternRotations[a]; themeAccent.patternSlug = patternSlugs[a]; } + + //override default themes + if (isHome(themeAccent) && name.equals("Dark Blue") || name.equals("Night")) { + themeAccent.myMessagesAccentColor = 0xff6573f8; + themeAccent.myMessagesGradientAccentColor1 = 0xff7644cb; + themeAccent.myMessagesGradientAccentColor2 = 0xff8849b4; + themeAccent.myMessagesGradientAccentColor3 = 0xffa751a8; + if (name.equals("Night")) { + themeAccent.patternIntensity = -0.57f; + themeAccent.backgroundOverrideColor = 0xff6c7fa6; + themeAccent.backgroundGradientOverrideColor1 = 0xff2e344b; + themeAccent.backgroundGradientOverrideColor2 = 0xff7874a7; + themeAccent.backgroundGradientOverrideColor3 = 0xff333258; + } + } themeAccentsMap.put(themeAccent.id, themeAccent); themeAccents.add(themeAccent); } @@ -2863,7 +2921,9 @@ public class Theme { public static Drawable dialogs_errorDrawable; public static Drawable dialogs_reorderDrawable; public static Drawable dialogs_lockDrawable; + public static Drawable dialogs_lock2Drawable; public static Drawable dialogs_muteDrawable; + public static Drawable dialogs_unmuteDrawable; public static Drawable dialogs_verifiedDrawable; public static ScamDrawable dialogs_scamDrawable; public static ScamDrawable dialogs_fakeDrawable; @@ -2954,6 +3014,7 @@ public class Theme { public static TextPaint chat_forwardNamePaint; public static TextPaint chat_replyNamePaint; public static TextPaint chat_replyTextPaint; + public static TextPaint chat_commentTextPaint; public static TextPaint chat_contextResult_titleTextPaint; public static TextPaint chat_contextResult_descriptionTextPaint; @@ -3895,6 +3956,7 @@ public class Theme { public static final String key_drawable_goIcon = "drawableGoIcon"; public static final String key_drawable_msgError = "drawableMsgError"; public static final String key_drawable_msgIn = "drawableMsgIn"; + public static final String key_drawable_msgInInstant = "drawableMsgInInstant"; public static final String key_drawable_msgInClock = "drawableMsgInClock"; public static final String key_drawable_msgInClockSelected = "drawableMsgInClockSelected"; public static final String key_drawable_msgInSelected = "drawableMsgInSelected"; @@ -6538,13 +6600,33 @@ public class Theme { for (int a = 0; a < count; a++) { Drawable layer = drawable.getDrawable(a); if (layer instanceof RippleRadMaskDrawable) { - drawable.setDrawableByLayerId(android.R.id.mask, new RippleRadMaskDrawable(top, bottom)); + ((RippleRadMaskDrawable) layer).setRadius(top, bottom); +// drawable.setDrawableByLayerId(android.R.id.mask, new RippleRadMaskDrawable(top, bottom)); break; } } } } + public static void setMaskDrawableRad(Drawable rippleDrawable, int topLeftRad, int topRightRad, int bottomRightRad, int bottomLeftRad) { + if (Build.VERSION.SDK_INT < 21) { + return; + } + if (rippleDrawable instanceof RippleDrawable) { + RippleDrawable drawable = (RippleDrawable) rippleDrawable; + int count = drawable.getNumberOfLayers(); + for (int a = 0; a < count; a++) { + Drawable layer = drawable.getDrawable(a); + if (layer instanceof RippleRadMaskDrawable) { + ((RippleRadMaskDrawable) layer).setRadius(topLeftRad, topRightRad, bottomRightRad, bottomLeftRad); +// drawable.setDrawableByLayerId(android.R.id.mask, new RippleRadMaskDrawable(top, bottom)); + break; + } + } + } + } + + public static Drawable createRadSelectorDrawable(int color, int topRad, int bottomRad) { if (Build.VERSION.SDK_INT >= 21) { maskPaint.setColor(0xffffffff); @@ -7660,7 +7742,7 @@ public class Theme { switchingNightTheme = false; } } else { - if (currentTheme != currentDayTheme && (currentTheme == null || currentNightTheme != null && currentTheme.isDark() != currentNightTheme.isDark())) { + if (currentTheme != currentDayTheme && (currentTheme == null || currentDayTheme != null && currentTheme.isLight() != currentDayTheme.isLight())) { isInNigthMode = false; lastThemeSwitchTime = SystemClock.elapsedRealtime(); switchingNightTheme = true; @@ -8796,6 +8878,7 @@ public class Theme { dialogs_actionMessagePaint = new Paint(Paint.ANTI_ALIAS_FLAG); dialogs_lockDrawable = resources.getDrawable(R.drawable.list_secret); + dialogs_lock2Drawable = resources.getDrawable(R.drawable.msg_mini_lock2); dialogs_checkDrawable = resources.getDrawable(R.drawable.list_check).mutate(); dialogs_playDrawable = resources.getDrawable(R.drawable.minithumb_play).mutate(); dialogs_checkReadDrawable = resources.getDrawable(R.drawable.list_check).mutate(); @@ -8804,6 +8887,7 @@ public class Theme { dialogs_errorDrawable = resources.getDrawable(R.drawable.list_warning_sign); dialogs_reorderDrawable = resources.getDrawable(R.drawable.list_reorder).mutate(); dialogs_muteDrawable = resources.getDrawable(R.drawable.list_mute).mutate(); + dialogs_unmuteDrawable = resources.getDrawable(R.drawable.list_unmute).mutate(); dialogs_verifiedDrawable = resources.getDrawable(R.drawable.verified_area).mutate(); dialogs_scamDrawable = new ScamDrawable(11, 0); dialogs_fakeDrawable = new ScamDrawable(11, 1); @@ -8873,6 +8957,7 @@ public class Theme { dialogs_offlinePaint.setColor(getColor(key_windowBackgroundWhiteGrayText3)); setDrawableColorByKey(dialogs_lockDrawable, key_chats_secretIcon); + setDrawableColorByKey(dialogs_lock2Drawable, key_chats_pinnedIcon); setDrawableColorByKey(dialogs_checkDrawable, key_chats_sentCheck); setDrawableColorByKey(dialogs_checkReadDrawable, key_chats_sentReadCheck); setDrawableColorByKey(dialogs_halfCheckDrawable, key_chats_sentReadCheck); @@ -8881,6 +8966,7 @@ public class Theme { setDrawableColorByKey(dialogs_pinnedDrawable, key_chats_pinnedIcon); setDrawableColorByKey(dialogs_reorderDrawable, key_chats_pinnedIcon); setDrawableColorByKey(dialogs_muteDrawable, key_chats_muteIcon); + setDrawableColorByKey(dialogs_unmuteDrawable, key_chats_muteIcon); setDrawableColorByKey(dialogs_mentionDrawable, key_chats_mentionIcon); setDrawableColorByKey(dialogs_reactionsMentionDrawable, key_chats_mentionIcon); setDrawableColorByKey(dialogs_verifiedDrawable, key_chats_verifiedBackground); @@ -8922,6 +9008,14 @@ public class Theme { chat_msgTextPaintThreeEmoji = new TextPaint(Paint.ANTI_ALIAS_FLAG); chat_msgBotButtonPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); chat_msgBotButtonPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + chat_namePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + chat_namePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + chat_replyNamePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + chat_replyNamePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + chat_replyTextPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + chat_forwardNamePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + chat_adminPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + chat_timePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); } final float[] emojiSizePercents = new float[] {.7f, .52f, .37f, .28f, .25f, .19f}; @@ -8935,6 +9029,14 @@ public class Theme { chat_msgTextPaint.setTextSize(AndroidUtilities.dp(SharedConfig.fontSize)); chat_msgGameTextPaint.setTextSize(AndroidUtilities.dp(14)); chat_msgBotButtonPaint.setTextSize(AndroidUtilities.dp(15)); + float smallerDp = (2 * SharedConfig.fontSize + 10) / 3f; // 6f + SharedConfig.fontSize / 2f; + chat_namePaint.setTextSize(AndroidUtilities.dp(smallerDp)); + chat_replyNamePaint.setTextSize(AndroidUtilities.dp(smallerDp)); + chat_replyTextPaint.setTextSize(AndroidUtilities.dp(smallerDp)); + chat_forwardNamePaint.setTextSize(AndroidUtilities.dp(smallerDp)); + chat_adminPaint.setTextSize(AndroidUtilities.dp(smallerDp - 1)); +// float timeDp = 2 * (SharedConfig.fontSize - 16) / 3f + 12; +// chat_timePaint.setTextSize(AndroidUtilities.dp(timeDp)); } } @@ -8991,6 +9093,7 @@ public class Theme { chat_replyNamePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); chat_replyNamePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); chat_replyTextPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + chat_commentTextPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); chat_instantViewPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); chat_instantViewPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); chat_instantViewRectPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -9272,6 +9375,7 @@ public class Theme { addChatDrawable(key_drawable_msgInSelected, chat_msgInSelectedDrawable, null); addChatDrawable(key_drawable_msgInMedia, chat_msgInMediaDrawable, null); addChatDrawable(key_drawable_msgInMediaSelected, chat_msgInMediaSelectedDrawable, null); + addChatDrawable(key_drawable_msgInInstant, chat_msgInInstantDrawable, key_chat_inInstant); addChatDrawable(key_drawable_msgOut, chat_msgOutDrawable, null); addChatDrawable(key_drawable_msgOutSelected, chat_msgOutSelectedDrawable, null); addChatDrawable(key_drawable_msgOutMedia, chat_msgOutMediaDrawable, null); @@ -9325,12 +9429,14 @@ public class Theme { chat_contactNamePaint.setTextSize(AndroidUtilities.dp(15)); chat_contactPhonePaint.setTextSize(AndroidUtilities.dp(13)); chat_durationPaint.setTextSize(AndroidUtilities.dp(12)); + float smallerDp = (2 * SharedConfig.fontSize + 10) / 3f; // 6f + SharedConfig.fontSize / 2f; + chat_namePaint.setTextSize(AndroidUtilities.dp(smallerDp)); + chat_replyNamePaint.setTextSize(AndroidUtilities.dp(smallerDp)); + chat_replyTextPaint.setTextSize(AndroidUtilities.dp(smallerDp)); + chat_forwardNamePaint.setTextSize(AndroidUtilities.dp(smallerDp)); + chat_adminPaint.setTextSize(AndroidUtilities.dp(smallerDp - 1)); + float timeDp = 2 * (SharedConfig.fontSize - 16) / 3f + 12; chat_timePaint.setTextSize(AndroidUtilities.dp(12)); - chat_adminPaint.setTextSize(AndroidUtilities.dp(13)); - chat_namePaint.setTextSize(AndroidUtilities.dp(14)); - chat_forwardNamePaint.setTextSize(AndroidUtilities.dp(14)); - chat_replyNamePaint.setTextSize(AndroidUtilities.dp(14)); - chat_replyTextPaint.setTextSize(AndroidUtilities.dp(14)); chat_gamePaint.setTextSize(AndroidUtilities.dp(13)); chat_shipmentPaint.setTextSize(AndroidUtilities.dp(13)); chat_instantViewPaint.setTextSize(AndroidUtilities.dp(13)); @@ -9342,6 +9448,8 @@ public class Theme { chat_contextResult_descriptionTextPaint.setTextSize(AndroidUtilities.dp(13)); chat_radialProgressPaint.setStrokeWidth(AndroidUtilities.dp(3)); chat_radialProgress2Paint.setStrokeWidth(AndroidUtilities.dp(2)); + chat_commentTextPaint.setTextSize(AndroidUtilities.dp(14)); + chat_commentTextPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionIntroActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionIntroActivity.java index 7c82d64ae..e9c5883af 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionIntroActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionIntroActivity.java @@ -195,14 +195,14 @@ public class ActionIntroActivity extends BaseFragment implements LocationControl if (width > height) { titleTextView.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.6f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); descriptionText.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.6f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); - buttonTextView.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.6f), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(42), MeasureSpec.EXACTLY)); + buttonTextView.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.6f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(42), MeasureSpec.EXACTLY)); } else { titleTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); descriptionText.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); if (currentType == ACTION_TYPE_SET_PASSCODE) { buttonTextView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(24 * 2), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50), MeasureSpec.EXACTLY)); } else { - buttonTextView.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.6f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(42), MeasureSpec.EXACTLY)); + buttonTextView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(72), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50), MeasureSpec.EXACTLY)); } } break; @@ -384,7 +384,7 @@ public class ActionIntroActivity extends BaseFragment implements LocationControl y = (int) (height * 0.493f); descriptionText.layout(0, y, descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight()); x = (width - buttonTextView.getMeasuredWidth()) / 2; - y = (int) (height * 0.71f); + y = (int) (height * 0.853f); buttonTextView.layout(x, y, x + buttonTextView.getMeasuredWidth(), y + buttonTextView.getMeasuredHeight()); } break; @@ -703,6 +703,7 @@ public class ActionIntroActivity extends BaseFragment implements LocationControl titleTextView.setText(LocaleController.getString("PeopleNearby", R.string.PeopleNearby)); descriptionText.setText(LocaleController.getString("PeopleNearbyAccessInfo", R.string.PeopleNearbyAccessInfo)); buttonTextView.setText(LocaleController.getString("PeopleNearbyAllowAccess", R.string.PeopleNearbyAllowAccess)); + flickerButton = true; break; } case ACTION_TYPE_NEARBY_LOCATION_ENABLED: { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java index ac5ec4776..4d15e58f9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java @@ -607,7 +607,7 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter { if (chat.participants_count != 0) { subtitle = LocaleController.formatPluralStringComma("Subscribers", chat.participants_count); } else { - if (TextUtils.isEmpty(chat.username)) { + if (!ChatObject.isPublic(chat)) { subtitle = LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate).toLowerCase(); } else { subtitle = LocaleController.getString("ChannelPublic", R.string.ChannelPublic).toLowerCase(); @@ -619,7 +619,7 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter { } else { if (chat.has_geo) { subtitle = LocaleController.getString("MegaLocation", R.string.MegaLocation); - } else if (TextUtils.isEmpty(chat.username)) { + } else if (!ChatObject.isPublic(chat)) { subtitle = LocaleController.getString("MegaPrivate", R.string.MegaPrivate).toLowerCase(); } else { subtitle = LocaleController.getString("MegaPublic", R.string.MegaPublic).toLowerCase(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java index ac743c1ce..0bb544e48 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java @@ -13,11 +13,15 @@ import android.os.SystemClock; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; -import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import androidx.collection.LongSparseArray; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.SQLite.SQLiteCursor; import org.telegram.SQLite.SQLitePreparedStatement; @@ -25,21 +29,20 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; import org.telegram.messenger.DialogObject; -import org.telegram.messenger.MediaDataController; +import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; import org.telegram.messenger.MessagesStorage; +import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.messenger.UserObject; import org.telegram.messenger.Utilities; -import org.telegram.messenger.FileLog; -import org.telegram.messenger.R; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; -import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.DialogCell; import org.telegram.ui.Cells.GraySectionCell; import org.telegram.ui.Cells.HashtagSearchCell; @@ -56,11 +59,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; -import androidx.collection.LongSparseArray; -import androidx.recyclerview.widget.DefaultItemAnimator; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { private final int VIEW_TYPE_PROFILE_CELL = 0; @@ -1124,7 +1122,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { if (chat == null) { chat = (TLRPC.Chat) obj; } - un = chat.username; + un = ChatObject.getPublicUsername(chat); } else if (obj instanceof TLRPC.EncryptedChat) { encryptedChat = MessagesController.getInstance(currentAccount).getEncryptedChat(((TLRPC.EncryptedChat) obj).id); user = MessagesController.getInstance(currentAccount).getUser(encryptedChat.user_id); @@ -1219,9 +1217,9 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { if (chat != null && chat.participants_count != 0) { String membersString; if (ChatObject.isChannel(chat) && !chat.megagroup) { - membersString = LocaleController.formatPluralString("Subscribers", chat.participants_count); + membersString = LocaleController.formatPluralStringComma("Subscribers", chat.participants_count, ' '); } else { - membersString = LocaleController.formatPluralString("Members", chat.participants_count); + membersString = LocaleController.formatPluralStringComma("Members", chat.participants_count, ' '); } if (username instanceof SpannableStringBuilder) { ((SpannableStringBuilder) username).append(", ").append(membersString); @@ -1381,7 +1379,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { cell.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); cell.useSeparator = (position != getItemCount() - 1); MessageObject messageObject = (MessageObject) getItem(position); - cell.setDialog(messageObject.getDialogId(), messageObject, messageObject.messageOwner.date, false); + cell.setDialog(messageObject.getDialogId(), messageObject, messageObject.messageOwner.date, false, false); break; } case VIEW_TYPE_HASHTAG_CELL: { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java index 6f2dbb571..bf615a236 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java @@ -1113,7 +1113,7 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter implement } firstName = chat.title; lastName = null; - username = chat.username; + username = ChatObject.getPublicUsername(chat); object = chat; id = -chat.id; } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MessagesSearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MessagesSearchAdapter.java index 2e9018add..a54538aaa 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MessagesSearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MessagesSearchAdapter.java @@ -12,6 +12,8 @@ import android.content.Context; import android.view.View; import android.view.ViewGroup; +import androidx.recyclerview.widget.RecyclerView; + import org.telegram.messenger.MediaDataController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.UserConfig; @@ -22,8 +24,6 @@ import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; -import androidx.recyclerview.widget.RecyclerView; - public class MessagesSearchAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; @@ -86,7 +86,7 @@ public class MessagesSearchAdapter extends RecyclerListView.SelectionAdapter { DialogCell cell = (DialogCell) holder.itemView; cell.useSeparator = true; MessageObject messageObject = (MessageObject) getItem(position); - cell.setDialog(messageObject.getDialogId(), messageObject, messageObject.messageOwner.date, true); + cell.setDialog(messageObject.getDialogId(), messageObject, messageObject.messageOwner.date, true, false); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java index 8fda5b0ab..89fcbae33 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java @@ -17,6 +17,7 @@ import android.view.ViewGroup; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ChatObject; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; import org.telegram.messenger.UserObject; @@ -320,7 +321,7 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { id = ((TLRPC.User) object).id; self = ((TLRPC.User) object).self; } else if (object instanceof TLRPC.Chat) { - un = ((TLRPC.Chat) object).username; + un = ChatObject.getPublicUsername((TLRPC.Chat) object); id = ((TLRPC.Chat) object).id; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ArchivedStickersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ArchivedStickersActivity.java index 1b36fda9f..5367e05f1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ArchivedStickersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ArchivedStickersActivity.java @@ -236,12 +236,12 @@ public class ArchivedStickersActivity extends BaseFragment implements Notificati } @Override - protected void onTransitionAnimationStart(boolean isOpen, boolean backward) { + public void onTransitionAnimationStart(boolean isOpen, boolean backward) { isInTransition = true; } @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { isInTransition = false; if (doOnTransitionEnd != null) { doOnTransitionEnd.run(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java index 2772b168d..36ddeeae5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java @@ -104,6 +104,7 @@ import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import org.json.JSONObject; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.ChatObject; import org.telegram.messenger.DownloadController; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; @@ -3201,7 +3202,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg } if (pageBlock instanceof TLRPC.TL_pageBlockChannel) { TLRPC.TL_pageBlockChannel pageBlockChannel = (TLRPC.TL_pageBlockChannel) pageBlock; - MessagesController.getInstance(currentAccount).openByUserName(pageBlockChannel.channel.username, parentFragment, 2); + MessagesController.getInstance(currentAccount).openByUserName(ChatObject.getPublicUsername(pageBlockChannel.channel), parentFragment, 2); close(false, true); } else if (pageBlock instanceof TL_pageBlockRelatedArticlesChild) { TL_pageBlockRelatedArticlesChild pageBlockRelatedArticlesChild = (TL_pageBlockRelatedArticlesChild) pageBlock; @@ -4161,7 +4162,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg messageObject.messageOwner.media.webpage = webPage; TLRPC.TL_messages_messages messagesRes = new TLRPC.TL_messages_messages(); messagesRes.messages.add(messageObject.messageOwner); - MessagesStorage.getInstance(currentAccount).putMessages(messagesRes, messageObject.getDialogId(), -2, 0, false, messageObject.scheduled); + MessagesStorage.getInstance(currentAccount).putMessages(messagesRes, messageObject.getDialogId(), -2, 0, false, messageObject.scheduled, 0); } pagesStack.set(0, webPage); if (pagesStack.size() == 1) { @@ -4194,7 +4195,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg if (messageObject != null) { TLRPC.TL_messages_messages messagesRes = new TLRPC.TL_messages_messages(); messagesRes.messages.add(messageObject.messageOwner); - MessagesStorage.getInstance(currentAccount).putMessages(messagesRes, messageObject.getDialogId(), -2, 0, false, messageObject.scheduled); + MessagesStorage.getInstance(currentAccount).putMessages(messagesRes, messageObject.getDialogId(), -2, 0, false, messageObject.scheduled, 0); } } } @@ -4652,7 +4653,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg } private void loadChannel(final BlockChannelCell cell, WebpageAdapter adapter, TLRPC.Chat channel) { - if (loadingChannel || TextUtils.isEmpty(channel.username)) { + if (loadingChannel || !ChatObject.isPublic(channel)) { return; } loadingChannel = true; @@ -6025,7 +6026,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg float y = event.getY(); if (channelCell.getVisibility() == VISIBLE && y > channelCell.getTranslationY() && y < channelCell.getTranslationY() + AndroidUtilities.dp(39)) { if (parentAdapter.channelBlock != null && event.getAction() == MotionEvent.ACTION_UP) { - MessagesController.getInstance(currentAccount).openByUserName(parentAdapter.channelBlock.channel.username, parentFragment, 2); + MessagesController.getInstance(currentAccount).openByUserName(ChatObject.getPublicUsername(parentAdapter.channelBlock.channel), parentFragment, 2); close(false, true); } return true; @@ -9909,7 +9910,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg float y = event.getY(); if (channelCell.getVisibility() == VISIBLE && y > channelCell.getTranslationY() && y < channelCell.getTranslationY() + AndroidUtilities.dp(39)) { if (parentAdapter.channelBlock != null && event.getAction() == MotionEvent.ACTION_UP) { - MessagesController.getInstance(currentAccount).openByUserName(parentAdapter.channelBlock.channel.username, parentFragment, 2); + MessagesController.getInstance(currentAccount).openByUserName(ChatObject.getPublicUsername(parentAdapter.channelBlock.channel), parentFragment, 2); close(false, true); } return true; @@ -11212,6 +11213,9 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg } public boolean openPhoto(TLRPC.PageBlock block, WebpageAdapter adapter) { + if (parentFragment == null || parentFragment.getParentActivity() == null) { + return false; + } final int index; final List pageBlocks; if (!(block instanceof TLRPC.TL_pageBlockVideo) || WebPageUtils.isVideo(adapter.currentPage, block)) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/BubbleActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/BubbleActivity.java index cefb23b38..e40e16255 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/BubbleActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/BubbleActivity.java @@ -8,11 +8,7 @@ package org.telegram.ui; -import android.Manifest; -import android.app.Activity; import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; import android.os.Bundle; import android.os.SystemClock; import android.view.View; @@ -25,19 +21,14 @@ import org.telegram.messenger.AccountInstance; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.BuildVars; -import org.telegram.messenger.ContactsController; import org.telegram.messenger.FileLog; -import org.telegram.messenger.ImageLoader; -import org.telegram.messenger.LocaleController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.SharedConfig; import org.telegram.messenger.UserConfig; -import org.telegram.messenger.camera.CameraController; -import org.telegram.ui.ActionBar.ActionBarLayout; -import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.DrawerLayoutContainer; +import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.PasscodeView; @@ -45,13 +36,13 @@ import org.telegram.ui.Components.ThemeEditorView; import java.util.ArrayList; -public class BubbleActivity extends BasePermissionsActivity implements ActionBarLayout.ActionBarLayoutDelegate { +public class BubbleActivity extends BasePermissionsActivity implements INavigationLayout.INavigationLayoutDelegate { private boolean finished; private ArrayList mainFragmentsStack = new ArrayList<>(); private PasscodeView passcodeView; - private ActionBarLayout actionBarLayout; + private INavigationLayout actionBarLayout; protected DrawerLayoutContainer drawerLayoutContainer; private Intent passcodeSaveIntent; @@ -89,7 +80,7 @@ public class BubbleActivity extends BasePermissionsActivity implements ActionBar Theme.createDialogsResources(this); Theme.createChatResources(this, false); - actionBarLayout = new ActionBarLayout(this); + actionBarLayout = INavigationLayout.newLayout(this); actionBarLayout.setInBubbleMode(true); actionBarLayout.setRemoveActionBarExtraHeight(true); @@ -99,11 +90,11 @@ public class BubbleActivity extends BasePermissionsActivity implements ActionBar RelativeLayout launchLayout = new RelativeLayout(this); drawerLayoutContainer.addView(launchLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - launchLayout.addView(actionBarLayout, LayoutHelper.createRelative(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + launchLayout.addView(actionBarLayout.getView(), LayoutHelper.createRelative(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); drawerLayoutContainer.setParentActionBarLayout(actionBarLayout); actionBarLayout.setDrawerLayoutContainer(drawerLayoutContainer); - actionBarLayout.init(mainFragmentsStack); + actionBarLayout.setFragmentStack(mainFragmentsStack); actionBarLayout.setDelegate(this); passcodeView = new PasscodeView(this); @@ -188,11 +179,6 @@ public class BubbleActivity extends BasePermissionsActivity implements ActionBar return true; } - @Override - public boolean onPreIme() { - return false; - } - @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); @@ -246,8 +232,8 @@ public class BubbleActivity extends BasePermissionsActivity implements ActionBar if (editorView != null) { editorView.onActivityResult(requestCode, resultCode, data); } - if (actionBarLayout.fragmentsStack.size() != 0) { - BaseFragment fragment = actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1); + if (actionBarLayout.getFragmentStack().size() != 0) { + BaseFragment fragment = actionBarLayout.getFragmentStack().get(actionBarLayout.getFragmentStack().size() - 1); fragment.onActivityResultFragment(requestCode, resultCode, data); } } @@ -257,8 +243,8 @@ public class BubbleActivity extends BasePermissionsActivity implements ActionBar super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (!checkPermissionsResult(requestCode, permissions, grantResults)) return; - if (actionBarLayout.fragmentsStack.size() != 0) { - BaseFragment fragment = actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1); + if (actionBarLayout.getFragmentStack().size() != 0) { + BaseFragment fragment = actionBarLayout.getFragmentStack().get(actionBarLayout.getFragmentStack().size() - 1); fragment.onRequestPermissionsResultFragment(requestCode, permissions, grantResults); } @@ -361,27 +347,12 @@ public class BubbleActivity extends BasePermissionsActivity implements ActionBar } @Override - public boolean needPresentFragment(BaseFragment fragment, boolean removeLast, boolean forceWithoutAnimation, ActionBarLayout layout) { - return true; - } - - @Override - public boolean needAddFragmentToStack(BaseFragment fragment, ActionBarLayout layout) { - return true; - } - - @Override - public boolean needCloseLastFragment(ActionBarLayout layout) { - if (layout.fragmentsStack.size() <= 1) { + public boolean needCloseLastFragment(INavigationLayout layout) { + if (layout.getFragmentStack().size() <= 1) { onFinish(); finish(); return false; } return true; } - - @Override - public void onRebuildAllFragments(ActionBarLayout layout, boolean last) { - - } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java index 0dcf0a640..43cc611e2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java @@ -110,6 +110,8 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe long fragmentCreateTime; + private boolean updateDatabaseSize; + @Override public boolean onFragmentCreate() { super.onFragmentCreate(); @@ -537,7 +539,7 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe CheckBoxCell cell = (CheckBoxCell) v; int num = (Integer) cell.getTag(); if (enabledCount == 1 && clearViewData[num].clear) { - AndroidUtilities.shakeView(((CheckBoxCell) v).getCheckBoxView(), 2, 0); + AndroidUtilities.shakeView(((CheckBoxCell) v).getCheckBoxView()); return; } @@ -630,6 +632,7 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe progressDialog = null; if (listAdapter != null) { databaseSize = MessagesStorage.getInstance(currentAccount).getDatabaseSize(); + updateDatabaseSize = true; listAdapter.notifyDataSetChanged(); } } @@ -709,7 +712,8 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe case 0: TextSettingsCell textCell = (TextSettingsCell) holder.itemView; if (position == databaseRow) { - textCell.setTextAndValue(LocaleController.getString("ClearLocalDatabase", R.string.ClearLocalDatabase), AndroidUtilities.formatFileSize(databaseSize), false); + textCell.setTextAndValue(LocaleController.getString("ClearLocalDatabase", R.string.ClearLocalDatabase), AndroidUtilities.formatFileSize(databaseSize), updateDatabaseSize, false); + updateDatabaseSize = false; } else if (position == migrateOldFolderRow) { textCell.setTextAndValue(LocaleController.getString("MigrateOldFolder", R.string.MigrateOldFolder), null, false); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CalendarActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CalendarActivity.java index 8ff67b7c7..668c8d888 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CalendarActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CalendarActivity.java @@ -87,6 +87,7 @@ public class CalendarActivity extends BaseFragment { Paint blackoutPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private long dialogId; + private int topicId; private boolean loading; private boolean checkEnterItems; private boolean inSelectionMode; @@ -140,6 +141,7 @@ public class CalendarActivity extends BaseFragment { @Override public boolean onFragmentCreate() { dialogId = getArguments().getLong("dialog_id"); + topicId = getArguments().getInt("topic_id"); calendarType = getArguments().getInt("type"); if (dialogId >= 0) { @@ -313,8 +315,8 @@ public class CalendarActivity extends BaseFragment { public void run(boolean forAll) { finishFragment(); - if (parentLayout.fragmentsStack.size() >= 2) { - BaseFragment fragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2); + if (parentLayout.getFragmentStack().size() >= 2) { + BaseFragment fragment = parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 2); if (fragment instanceof ChatActivity) { ((ChatActivity) fragment).deleteHistory(dateSelectedStart, dateSelectedEnd + 86400, forAll); } @@ -600,6 +602,9 @@ public class CalendarActivity extends BaseFragment { @SuppressLint("NotifyDataSetChanged") @Override public boolean onSingleTapUp(MotionEvent e) { + if (parentLayout == null) { + return false; + } if (calendarType == TYPE_MEDIA_CALENDAR && messagesByDays != null) { PeriodDay day = getDayAtCoord(e.getX(), e.getY()); if (day != null && day.messageObject != null && callback != null) { @@ -640,8 +645,8 @@ public class CalendarActivity extends BaseFragment { } } else { PeriodDay day = getDayAtCoord(e.getX(), e.getY()); - if (day != null && parentLayout.fragmentsStack.size() >= 2) { - BaseFragment fragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2); + if (day != null && parentLayout.getFragmentStack().size() >= 2) { + BaseFragment fragment = parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 2); if (fragment instanceof ChatActivity) { finishFragment(); ((ChatActivity) fragment).jumpToDate(day.date); @@ -710,8 +715,8 @@ public class CalendarActivity extends BaseFragment { cellJump.setTextAndIcon(LocaleController.getString("JumpToDate", R.string.JumpToDate), R.drawable.msg_message); cellJump.setMinimumWidth(160); cellJump.setOnClickListener(view -> { - if (parentLayout.fragmentsStack.size() >= 3) { - BaseFragment fragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 3); + if (parentLayout.getFragmentStack().size() >= 3) { + BaseFragment fragment = parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 3); if (fragment instanceof ChatActivity) { AndroidUtilities.runOnUIThread(() -> { finishFragment(); @@ -740,8 +745,8 @@ public class CalendarActivity extends BaseFragment { cellDelete.setTextAndIcon(LocaleController.getString("ClearHistory", R.string.ClearHistory), R.drawable.msg_delete); cellDelete.setMinimumWidth(160); cellDelete.setOnClickListener(view -> { - if (parentLayout.fragmentsStack.size() >= 3) { - BaseFragment fragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 3); + if (parentLayout.getFragmentStack().size() >= 3) { + BaseFragment fragment = parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 3); if (fragment instanceof ChatActivity) { AlertsCreator.createClearDaysDialogAlert(CalendarActivity.this, 1, getMessagesController().getUser(dialogId), null, false, new MessagesStorage.BooleanCallback() { @Override @@ -773,7 +778,7 @@ public class CalendarActivity extends BaseFragment { }); blurredView.setVisibility(View.GONE); blurredView.setFitsSystemWindows(true); - parentLayout.containerView.addView(blurredView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + parentLayout.getOverlayContainerView().addView(blurredView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); prepareBlurBitmap(); presentFragmentAsPreviewWithMenu(chatActivity, previewMenu); @@ -1261,13 +1266,13 @@ public class CalendarActivity extends BaseFragment { } @Override - protected void onTransitionAnimationStart(boolean isOpen, boolean backward) { + public void onTransitionAnimationStart(boolean isOpen, boolean backward) { super.onTransitionAnimationStart(isOpen, backward); isOpened = true; } @Override - protected void onTransitionAnimationProgress(boolean isOpen, float progress) { + public void onTransitionAnimationProgress(boolean isOpen, float progress) { super.onTransitionAnimationProgress(isOpen, progress); if (blurredView != null && blurredView.getVisibility() == View.VISIBLE) { if (isOpen) { @@ -1279,7 +1284,7 @@ public class CalendarActivity extends BaseFragment { } @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen && blurredView != null && blurredView.getVisibility() == View.VISIBLE) { blurredView.setVisibility(View.GONE); blurredView.setBackground(null); @@ -1393,12 +1398,12 @@ public class CalendarActivity extends BaseFragment { if (blurredView == null) { return; } - int w = (int) (parentLayout.getMeasuredWidth() / 6.0f); - int h = (int) (parentLayout.getMeasuredHeight() / 6.0f); + int w = (int) (parentLayout.getView().getMeasuredWidth() / 6.0f); + int h = (int) (parentLayout.getView().getMeasuredHeight() / 6.0f); Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); canvas.scale(1.0f / 6.0f, 1.0f / 6.0f); - parentLayout.draw(canvas); + parentLayout.getView().draw(canvas); Utilities.stackBlurBitmap(bitmap, Math.max(7, Math.max(w, h) / 180)); blurredView.setBackground(new BitmapDrawable(bitmap)); blurredView.setAlpha(0.0f); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CallLogActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CallLogActivity.java index 934e72503..c332f1515 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CallLogActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CallLogActivity.java @@ -1069,7 +1069,7 @@ public class CallLogActivity extends BaseFragment implements NotificationCenter. cell.button.setTag(chat.id); String text; if (ChatObject.isChannel(chat) && !chat.megagroup) { - if (TextUtils.isEmpty(chat.username)) { + if (!ChatObject.isPublic(chat)) { text = LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate).toLowerCase(); } else { text = LocaleController.getString("ChannelPublic", R.string.ChannelPublic).toLowerCase(); @@ -1077,7 +1077,7 @@ public class CallLogActivity extends BaseFragment implements NotificationCenter. } else { if (chat.has_geo) { text = LocaleController.getString("MegaLocation", R.string.MegaLocation); - } else if (TextUtils.isEmpty(chat.username)) { + } else if (!ChatObject.isPublic(chat)) { text = LocaleController.getString("MegaPrivate", R.string.MegaPrivate).toLowerCase(); } else { text = LocaleController.getString("MegaPublic", R.string.MegaPublic).toLowerCase(); @@ -1115,7 +1115,7 @@ public class CallLogActivity extends BaseFragment implements NotificationCenter. } @Override - protected void onTransitionAnimationStart(boolean isOpen, boolean backward) { + public void onTransitionAnimationStart(boolean isOpen, boolean backward) { super.onTransitionAnimationStart(isOpen, backward); if (isOpen) { openTransitionStarted = true; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java index 7237c6d39..0b1566f39 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java @@ -15,6 +15,8 @@ import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; import android.graphics.ImageFormat; import android.graphics.Paint; import android.graphics.Path; @@ -33,6 +35,7 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.text.style.ClickableSpan; +import android.util.Log; import android.util.SparseArray; import android.util.TypedValue; import android.view.Gravity; @@ -75,9 +78,9 @@ import org.telegram.messenger.camera.CameraSession; import org.telegram.messenger.camera.CameraView; import org.telegram.messenger.camera.Size; import org.telegram.ui.ActionBar.ActionBar; -import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.AnimationProperties; @@ -154,15 +157,15 @@ public class CameraScanActivity extends BaseFragment { } } - public static ActionBarLayout[] showAsSheet(BaseFragment parentFragment, boolean gallery, int type, CameraScanActivityDelegate cameraDelegate) { + public static INavigationLayout[] showAsSheet(BaseFragment parentFragment, boolean gallery, int type, CameraScanActivityDelegate cameraDelegate) { if (parentFragment == null || parentFragment.getParentActivity() == null) { return null; } - ActionBarLayout[] actionBarLayout = new ActionBarLayout[]{new ActionBarLayout(parentFragment.getParentActivity())}; + INavigationLayout[] actionBarLayout = new INavigationLayout[]{INavigationLayout.newLayout(parentFragment.getParentActivity())}; BottomSheet bottomSheet = new BottomSheet(parentFragment.getParentActivity(), false) { CameraScanActivity fragment; { - actionBarLayout[0].init(new ArrayList<>()); + actionBarLayout[0].setFragmentStack(new ArrayList<>()); fragment = new CameraScanActivity(type) { @Override public void finishFragment() { @@ -178,9 +181,9 @@ public class CameraScanActivity extends BaseFragment { fragment.needGalleryButton = gallery; actionBarLayout[0].addFragmentToStack(fragment); actionBarLayout[0].showLastFragment(); - actionBarLayout[0].setPadding(backgroundPaddingLeft, 0, backgroundPaddingLeft, 0); + actionBarLayout[0].getView().setPadding(backgroundPaddingLeft, 0, backgroundPaddingLeft, 0); fragment.setDelegate(cameraDelegate); - containerView = actionBarLayout[0]; + containerView = actionBarLayout[0].getView(); setApplyBottomPadding(false); setApplyBottomPadding(false); setOnDismissListener(dialog -> fragment.onFragmentDestroy()); @@ -193,7 +196,7 @@ public class CameraScanActivity extends BaseFragment { @Override public void onBackPressed() { - if (actionBarLayout[0] == null || actionBarLayout[0].fragmentsStack.size() <= 1) { + if (actionBarLayout[0] == null || actionBarLayout[0].getFragmentStack().size() <= 1) { super.onBackPressed(); } else { actionBarLayout[0].onBackPressed(); @@ -888,6 +891,11 @@ public class CameraScanActivity extends BaseFragment { public void run() { if (cameraView != null && !recognized && cameraView.getCameraSession() != null) { handler.post(() -> { + try { + cameraView.focusToPoint(cameraView.getWidth() / 2, cameraView.getHeight() / 2, false); + } catch (Exception ignore) { + + } if (cameraView != null) { processShot(cameraView.getTextureView().getBitmap()); } @@ -1011,6 +1019,52 @@ public class CameraScanActivity extends BaseFragment { } } + private Bitmap invert(Bitmap bitmap) { + int height = bitmap.getHeight(); + int width = bitmap.getWidth(); + + Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(newBitmap); + Paint paint = new Paint(); + + ColorMatrix matrixGrayscale = new ColorMatrix(); + matrixGrayscale.setSaturation(0); + ColorMatrix matrixInvert = new ColorMatrix(); + matrixInvert.set(new float[] { + -1.0f, 0.0f, 0.0f, 0.0f, 255.0f, + 0.0f, -1.0f, 0.0f, 0.0f, 255.0f, + 0.0f, 0.0f, -1.0f, 0.0f, 255.0f, + 0.0f, 0.0f, 0.0f, 1.0f, 0.0f + }); + matrixInvert.preConcat(matrixGrayscale); + paint.setColorFilter(new ColorMatrixColorFilter(matrixInvert)); + canvas.drawBitmap(bitmap, 0, 0, paint); + return newBitmap; + } + + private Bitmap monochrome(Bitmap bitmap, int threshold) { + int height = bitmap.getHeight(); + int width = bitmap.getWidth(); + + Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(newBitmap); + Paint paint = new Paint(); + + paint.setColorFilter(new ColorMatrixColorFilter(createThresholdMatrix(threshold))); + canvas.drawBitmap(bitmap, 0, 0, paint); + + return newBitmap; + } + public static ColorMatrix createThresholdMatrix(int threshold) { + ColorMatrix matrix = new ColorMatrix(new float[] { + 85.f, 85.f, 85.f, 0.f, -255.f * threshold, + 85.f, 85.f, 85.f, 0.f, -255.f * threshold, + 85.f, 85.f, 85.f, 0.f, -255.f * threshold, + 0f, 0f, 0f, 1f, 0f + }); + return matrix; + } + private class QrResult { String text; RectF bounds; @@ -1052,6 +1106,60 @@ public class CameraScanActivity extends BaseFragment { } bounds.set(minX, minY, maxX, maxY); } + } else if (bitmap != null) { + Bitmap inverted = invert(bitmap); + bitmap.recycle(); + frame = new Frame.Builder().setBitmap(inverted).build(); + width = inverted.getWidth(); + height = inverted.getHeight(); + codes = visionQrReader.detect(frame); + if (codes != null && codes.size() > 0) { + Barcode code = codes.valueAt(0); + text = code.rawValue; + if (code.cornerPoints == null || code.cornerPoints.length == 0) { + bounds = null; + } else { + float minX = Float.MAX_VALUE, + maxX = Float.MIN_VALUE, + minY = Float.MAX_VALUE, + maxY = Float.MIN_VALUE; + for (Point point : code.cornerPoints) { + minX = Math.min(minX, point.x); + maxX = Math.max(maxX, point.x); + minY = Math.min(minY, point.y); + maxY = Math.max(maxY, point.y); + } + bounds.set(minX, minY, maxX, maxY); + } + } else { + Bitmap monochrome = monochrome(inverted, 90); + inverted.recycle(); + frame = new Frame.Builder().setBitmap(monochrome).build(); + width = inverted.getWidth(); + height = inverted.getHeight(); + codes = visionQrReader.detect(frame); + if (codes != null && codes.size() > 0) { + Barcode code = codes.valueAt(0); + text = code.rawValue; + if (code.cornerPoints == null || code.cornerPoints.length == 0) { + bounds = null; + } else { + float minX = Float.MAX_VALUE, + maxX = Float.MIN_VALUE, + minY = Float.MAX_VALUE, + maxY = Float.MIN_VALUE; + for (Point point : code.cornerPoints) { + minX = Math.min(minX, point.x); + maxX = Math.max(maxX, point.x); + minY = Math.min(minY, point.y); + maxY = Math.max(maxY, point.y); + } + bounds.set(minX, minY, maxX, maxY); + } + } else { + text = null; + } + } } else { text = null; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java index 8650d3452..c504048ce 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java @@ -40,6 +40,7 @@ import android.widget.FrameLayout; import android.widget.TextView; import androidx.annotation.Nullable; +import androidx.core.view.GestureDetectorCompat; import androidx.recyclerview.widget.RecyclerView; import org.telegram.messenger.AndroidUtilities; @@ -93,6 +94,8 @@ public class AboutLinkCell extends FrameLayout { private boolean needSpace = false; private boolean moreButtonDisabled; + private GestureDetectorCompat gestureDetector; + public AboutLinkCell(Context context, BaseFragment fragment) { this(context, fragment, null); } @@ -103,59 +106,9 @@ public class AboutLinkCell extends FrameLayout { this.resourcesProvider = resourcesProvider; parentFragment = fragment; - container = new FrameLayout(context) { - @Override - public boolean onTouchEvent(MotionEvent event) { - int x = (int) event.getX(); - int y = (int) event.getY(); - - boolean result = false; - if (textLayout != null || nextLinesLayouts != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || pressedLink != null && event.getAction() == MotionEvent.ACTION_UP) { - if (x >= showMoreTextView.getLeft() && x <= showMoreTextView.getRight() && - y >= showMoreTextView.getTop() && y <= showMoreTextView.getBottom()) { - return super.onTouchEvent(event); - } - if (getMeasuredWidth() > 0 && x > getMeasuredWidth() - AndroidUtilities.dp(23)) { - return super.onTouchEvent(event); - } - if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (firstThreeLinesLayout != null && expandT < 1 && shouldExpand) { - if (checkTouchTextLayout(firstThreeLinesLayout, textX, textY, x, y)) { - result = true; - } else if (nextLinesLayouts != null) { - for (int i = 0; i < nextLinesLayouts.length; ++i) { - if (checkTouchTextLayout(nextLinesLayouts[i], nextLinesLayoutsPositions[i].x, nextLinesLayoutsPositions[i].y, x, y)) { - result = true; - break; - } - } - } - } else if (checkTouchTextLayout(textLayout, textX, textY, x, y)) { - result = true; - } - if (!result) { - resetPressedLink(); - } - } else if (pressedLink != null) { - try { - onLinkClick((ClickableSpan) pressedLink.getSpan()); - } catch (Exception e) { - FileLog.e(e); - } - resetPressedLink(); - result = true; - } - } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { - resetPressedLink(); - } - } - return result || super.onTouchEvent(event); - } - }; + container = new FrameLayout(context); container.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); links = new LinkSpanDrawable.LinkCollector(container); - container.setClickable(true); rippleBackground = Theme.createRadSelectorDrawable(Theme.getColor(Theme.key_listSelector, resourcesProvider), 0, 0); valueTextView = new TextView(context); @@ -191,7 +144,7 @@ public class AboutLinkCell extends FrameLayout { if (wasPressed != pressed) { invalidate(); } - return super.onTouchEvent(event); + return pressed || super.onTouchEvent(event); } @Override @@ -230,6 +183,46 @@ public class AboutLinkCell extends FrameLayout { setWillNotDraw(false); } + @Override + public boolean onTouchEvent(MotionEvent event) { + int x = (int) event.getX(); + int y = (int) event.getY(); + + if (showMoreTextView.getVisibility() == View.VISIBLE && + x >= showMoreTextBackgroundView.getLeft() && x <= showMoreTextBackgroundView.getRight() && + y >= showMoreTextBackgroundView.getTop() && y <= showMoreTextBackgroundView.getBottom() + ) { +// event.offsetLocation(showMoreTextBackgroundView.getLeft(), showMoreTextBackgroundView.getTop()); + return false; + } + + boolean result = false; + if (textLayout != null || nextLinesLayouts != null) { + if (event.getAction() == MotionEvent.ACTION_DOWN || pressedLink != null && event.getAction() == MotionEvent.ACTION_UP) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + resetPressedLink(); + LinkSpanDrawable link = hitLink(x, y); + if (link != null) { + result = true; + links.addLink(pressedLink = link); + AndroidUtilities.runOnUIThread(longPressedRunnable, ViewConfiguration.getLongPressTimeout()); + } + } else if (pressedLink != null) { + try { + onLinkClick((ClickableSpan) pressedLink.getSpan()); + } catch (Exception e) { + FileLog.e(e); + } + resetPressedLink(); + result = true; + } + } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { + resetPressedLink(); + } + } + return result || super.onTouchEvent(event); + } + private void setShowMoreMarginBottom(int marginBottom) { FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) showMoreTextBackgroundView.getLayoutParams(); if (lp.bottomMargin != marginBottom) { @@ -325,11 +318,6 @@ public class AboutLinkCell extends FrameLayout { canvas.restore(); } - @Override - public void setOnClickListener(@Nullable OnClickListener l) { - container.setOnClickListener(l); - } - protected void didPressUrl(String url) { } @@ -426,7 +414,34 @@ public class AboutLinkCell extends FrameLayout { } }; - private boolean checkTouchTextLayout(StaticLayout textLayout, int textX, int textY, int ex, int ey) { + private LinkSpanDrawable hitLink(int x, int y) { + if (x >= showMoreTextView.getLeft() && x <= showMoreTextView.getRight() && + y >= showMoreTextView.getTop() && y <= showMoreTextView.getBottom()) { + return null; + } + if (getMeasuredWidth() > 0 && x > getMeasuredWidth() - AndroidUtilities.dp(23)) { + return null; + } + LinkSpanDrawable link; + if (firstThreeLinesLayout != null && expandT < 1 && shouldExpand) { + if ((link = checkTouchTextLayout(firstThreeLinesLayout, textX, textY, x, y)) != null) { + return link; + } + if (nextLinesLayouts != null) { + for (int i = 0; i < nextLinesLayouts.length; ++i) { + if ((link = checkTouchTextLayout(nextLinesLayouts[i], nextLinesLayoutsPositions[i].x, nextLinesLayoutsPositions[i].y, x, y)) != null) { + return link; + } + } + } + } + if ((link = checkTouchTextLayout(textLayout, textX, textY, x, y)) != null) { + return link; + } + return null; + } + + private LinkSpanDrawable checkTouchTextLayout(StaticLayout textLayout, int textX, int textY, int ex, int ey) { try { int x = (int) (ex - textX); int y = (int) (ey - textY); @@ -438,26 +453,19 @@ public class AboutLinkCell extends FrameLayout { Spannable buffer = (Spannable) textLayout.getText(); ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); if (link.length != 0 && !AndroidUtilities.isAccessibilityScreenReaderEnabled()) { - resetPressedLink(); - pressedLink = new LinkSpanDrawable(link[0], parentFragment.getResourceProvider(), ex, ey); - links.addLink(pressedLink); - int start = buffer.getSpanStart(pressedLink.getSpan()); - int end = buffer.getSpanEnd(pressedLink.getSpan()); - LinkPath path = pressedLink.obtainNewPath(); + LinkSpanDrawable linkDrawable = new LinkSpanDrawable(link[0], parentFragment.getResourceProvider(), ex, ey); + int start = buffer.getSpanStart(link[0]); + int end = buffer.getSpanEnd(link[0]); + LinkPath path = linkDrawable.obtainNewPath(); path.setCurrentLayout(textLayout, start, textY); textLayout.getSelectionPath(start, end, path); - AndroidUtilities.runOnUIThread(longPressedRunnable, ViewConfiguration.getLongPressTimeout()); - return true; - } else { - return false; + return linkDrawable; } - } else { - return false; } } catch (Exception e) { FileLog.e(e); - return false; } + return null; } private void onLinkClick(ClickableSpan pressedLink) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/AdminedChannelCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AdminedChannelCell.java index 20e0f540c..b5d5319b8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/AdminedChannelCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AdminedChannelCell.java @@ -18,6 +18,7 @@ import android.widget.FrameLayout; import android.widget.ImageView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ChatObject; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; @@ -89,7 +90,7 @@ public class AdminedChannelCell extends FrameLayout { currentChannel = channel; avatarDrawable.setInfo(channel); nameTextView.setText(channel.title); - SpannableStringBuilder stringBuilder = new SpannableStringBuilder(url + channel.username); + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(url + ChatObject.getPublicUsername(channel)); stringBuilder.setSpan(new URLSpanNoUnderline(""), url.length(), stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); statusTextView.setText(stringBuilder); avatarImageView.setForUserOrChat(channel, avatarDrawable); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/BaseCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/BaseCell.java index e599a2278..deff68f91 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/BaseCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/BaseCell.java @@ -63,6 +63,12 @@ public abstract class BaseCell extends ViewGroup { setDrawableBounds(drawable, (int) x, (int) y, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); } + public static float setDrawableBounds(Drawable drawable, float x, float y, float h) { + float w = drawable.getIntrinsicWidth() * h / drawable.getIntrinsicHeight(); + setDrawableBounds(drawable, (int) x, (int) y, (int) w, (int) h); + return w; + } + public static void setDrawableBounds(Drawable drawable, int x, int y, int w, int h) { if (drawable != null) { drawable.setBounds(x, y, x + w, y + h); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java index 4c299fdae..4c0e2c5d4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java @@ -47,6 +47,7 @@ import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaDataController; import org.telegram.messenger.MessageObject; +import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.SharedConfig; @@ -56,10 +57,12 @@ import org.telegram.messenger.browser.Browser; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AnimatedEmojiDrawable; import org.telegram.ui.Components.AnimatedEmojiSpan; import org.telegram.ui.Components.AvatarDrawable; +import org.telegram.ui.Components.Forum.ForumUtilities; import org.telegram.ui.Components.Premium.StarParticlesView; import org.telegram.ui.Components.RLottieDrawable; import org.telegram.ui.Components.TypefaceSpan; @@ -143,6 +146,22 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD default void needShowEffectOverlay(ChatActionCell cell, TLRPC.Document document, TLRPC.VideoSize videoSize) { } + + default BaseFragment getBaseFragment() { + return null; + } + + default long getDialogId() { + return 0; + } + + default int getTopicId() { + return 0; + } + + default boolean canDrawOutboundsContent() { + return true; + } } public interface ThemeDelegate extends Theme.ResourcesProvider { @@ -453,7 +472,7 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD MediaDataController.getInstance(currentAccount).loadStickersByEmojiOrName(packName, false, set == null); } } - } else if (messageObject.type == 11) { + } else if (messageObject.type == MessageObject.TYPE_ACTION_PHOTO) { imageReceiver.setAllowStartLottieAnimation(true); imageReceiver.setDelegate(null); imageReceiver.setRoundRadius(AndroidUtilities.roundMessageSize / 2); @@ -499,6 +518,7 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD imageReceiver.setImageBitmap((Bitmap) null); } rippleView.setVisibility(messageObject.type == MessageObject.TYPE_GIFT_PREMIUM ? VISIBLE : GONE); + ForumUtilities.applyTopicToMessage(messageObject); requestLayout(); } @@ -548,7 +568,7 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD imageReceiver.onAttachedToWindow(); setStarsPaused(false); - animatedEmojiStack = AnimatedEmojiSpan.update(AnimatedEmojiDrawable.CACHE_TYPE_MESSAGES, this, animatedEmojiStack, textLayout); + animatedEmojiStack = AnimatedEmojiSpan.update(AnimatedEmojiDrawable.CACHE_TYPE_MESSAGES, this, canDrawInParent && (delegate != null && !delegate.canDrawOutboundsContent()), animatedEmojiStack, textLayout); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.didUpdatePremiumGiftStickers); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.diceStickersDidLoad); } @@ -581,7 +601,7 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD boolean result = false; if (event.getAction() == MotionEvent.ACTION_DOWN) { if (delegate != null) { - if ((messageObject.type == 11 || messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) && imageReceiver.isInsideImage(x, y)) { + if ((messageObject.type == MessageObject.TYPE_ACTION_PHOTO || messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) && imageReceiver.isInsideImage(x, y)) { imagePressed = true; result = true; } @@ -694,7 +714,14 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD private void openLink(CharacterStyle link) { if (delegate != null && link instanceof URLSpan) { String url = ((URLSpan) link).getURL(); - if (url.startsWith("invite") && pressedLink instanceof URLSpanNoUnderline) { + if (url.startsWith("topic") && pressedLink instanceof URLSpanNoUnderline) { + URLSpanNoUnderline spanNoUnderline = (URLSpanNoUnderline) pressedLink; + TLObject object = spanNoUnderline.getObject(); + if (object instanceof TLRPC.TL_forumTopic) { + TLRPC.TL_forumTopic forumTopic = (TLRPC.TL_forumTopic) object; + ForumUtilities.openTopic(delegate.getBaseFragment(), -delegate.getDialogId(), forumTopic, 0); + } + } else if (url.startsWith("invite") && pressedLink instanceof URLSpanNoUnderline) { URLSpanNoUnderline spanNoUnderline = (URLSpanNoUnderline) pressedLink; TLObject object = spanNoUnderline.getObject(); if (object instanceof TLRPC.TL_chatInviteExported) { @@ -738,9 +765,10 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD spoilersPool.addAll(spoilers); spoilers.clear(); - if (text instanceof Spannable) + if (text instanceof Spannable) { SpoilerEffect.addSpoilers(this, textLayout, (Spannable) text, spoilersPool, spoilers); - animatedEmojiStack = AnimatedEmojiSpan.update(AnimatedEmojiDrawable.CACHE_TYPE_MESSAGES, this, animatedEmojiStack, textLayout); + } + animatedEmojiStack = AnimatedEmojiSpan.update(AnimatedEmojiDrawable.CACHE_TYPE_MESSAGES, this, canDrawInParent && (delegate != null && !delegate.canDrawOutboundsContent()), animatedEmojiStack, textLayout); textHeight = 0; textWidth = 0; @@ -788,7 +816,7 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD } int additionalHeight = 0; if (messageObject != null) { - if (messageObject.type == 11) { + if (messageObject.type == MessageObject.TYPE_ACTION_PHOTO) { additionalHeight = AndroidUtilities.roundMessageSize + AndroidUtilities.dp(10); } else if (messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) { additionalHeight = giftRectSize + AndroidUtilities.dp(12); @@ -813,26 +841,32 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD } private void buildLayout() { - CharSequence text; + CharSequence text = null; MessageObject messageObject = currentMessageObject; if (messageObject != null) { - if (messageObject.messageOwner != null && messageObject.messageOwner.media != null && messageObject.messageOwner.media.ttl_seconds != 0) { - if (messageObject.messageOwner.media.photo instanceof TLRPC.TL_photoEmpty) { - text = LocaleController.getString("AttachPhotoExpired", R.string.AttachPhotoExpired); - } else if (messageObject.messageOwner.media.document instanceof TLRPC.TL_documentEmpty) { - text = LocaleController.getString("AttachVideoExpired", R.string.AttachVideoExpired); + if (delegate.getTopicId() == 0 && MessageObject.isTopicActionMessage(messageObject)) { + TLRPC.TL_forumTopic topic = MessagesController.getInstance(currentAccount).getTopicsController().findTopic(-messageObject.getDialogId(), MessageObject.getTopicId(messageObject.messageOwner)); + text = ForumUtilities.createActionTextWithTopic(topic, messageObject); + } + if (text == null) { + if (messageObject.messageOwner != null && messageObject.messageOwner.media != null && messageObject.messageOwner.media.ttl_seconds != 0) { + if (messageObject.messageOwner.media.photo instanceof TLRPC.TL_photoEmpty) { + text = LocaleController.getString("AttachPhotoExpired", R.string.AttachPhotoExpired); + } else if (messageObject.messageOwner.media.document instanceof TLRPC.TL_documentEmpty) { + text = LocaleController.getString("AttachVideoExpired", R.string.AttachVideoExpired); + } else { + text = AnimatedEmojiSpan.cloneSpans(messageObject.messageText); + } } else { text = AnimatedEmojiSpan.cloneSpans(messageObject.messageText); } - } else { - text = AnimatedEmojiSpan.cloneSpans(messageObject.messageText); } } else { text = customText; } createLayout(text, previousWidth); if (messageObject != null) { - if (messageObject.type == 11) { + if (messageObject.type == MessageObject.TYPE_ACTION_PHOTO) { imageReceiver.setImageCoords((previousWidth - AndroidUtilities.roundMessageSize) / 2f, textHeight + AndroidUtilities.dp(19), AndroidUtilities.roundMessageSize, AndroidUtilities.roundMessageSize); } else if (messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) { createGiftPremiumLayouts(LocaleController.getString(R.string.ActionGiftPremiumTitle), LocaleController.formatString(R.string.ActionGiftPremiumSubtitle, LocaleController.formatPluralString("Months", messageObject.messageOwner.action.months)), LocaleController.getString(R.string.ActionGiftPremiumView), giftRectSize); @@ -882,7 +916,7 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD } } } - if (messageObject != null && (messageObject.type == 11 || messageObject.type == MessageObject.TYPE_GIFT_PREMIUM)) { + if (messageObject != null && (messageObject.type == MessageObject.TYPE_ACTION_PHOTO || messageObject.type == MessageObject.TYPE_GIFT_PREMIUM)) { imageReceiver.draw(canvas); } @@ -900,8 +934,10 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD } canvas.save(); SpoilerEffect.clipOutCanvas(canvas, spoilers); - AnimatedEmojiSpan.drawAnimatedEmojis(canvas, textLayout, animatedEmojiStack, 0, spoilers, 0, 0, 0, 1f); textLayout.draw(canvas); + if (delegate == null || delegate.canDrawOutboundsContent()) { + AnimatedEmojiSpan.drawAnimatedEmojis(canvas, textLayout, animatedEmojiStack, 0, spoilers, 0, 0, 0, 1f); + } canvas.restore(); for (SpoilerEffect eff : spoilers) { @@ -1157,7 +1193,7 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD @Override public void onSuccessDownload(String fileName) { MessageObject messageObject = currentMessageObject; - if (messageObject != null && messageObject.type == 11) { + if (messageObject != null && messageObject.type == MessageObject.TYPE_ACTION_PHOTO) { TLRPC.PhotoSize strippedPhotoSize = null; for (int a = 0, N = messageObject.photoThumbs.size(); a < N; a++) { TLRPC.PhotoSize photoSize = messageObject.photoThumbs.get(a); @@ -1241,4 +1277,11 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD Paint paint = themeDelegate != null ? themeDelegate.getPaint(paintKey) : null; return paint != null ? paint : Theme.getThemePaint(paintKey); } + + public void drawOutboundsContent(Canvas canvas) { + canvas.save(); + canvas.translate(textXLeft, textY); + AnimatedEmojiSpan.drawAnimatedEmojis(canvas, textLayout, animatedEmojiStack, 0, spoilers, 0, 0, 0, 1f); + canvas.restore(); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java index cdb4cb092..d18293122 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java @@ -53,6 +53,7 @@ import android.text.TextUtils; import android.text.style.CharacterStyle; import android.text.style.ClickableSpan; import android.text.style.URLSpan; +import android.util.Log; import android.util.Property; import android.util.SparseArray; import android.util.StateSet; @@ -113,6 +114,7 @@ import org.telegram.ui.ChatActivity; import org.telegram.ui.Components.AnimatedEmojiDrawable; import org.telegram.ui.Components.AnimatedEmojiSpan; import org.telegram.ui.Components.AnimatedFileDrawable; +import org.telegram.ui.Components.AnimatedFloat; import org.telegram.ui.Components.AnimatedNumberLayout; import org.telegram.ui.Components.AnimationProperties; import org.telegram.ui.Components.AudioVisualizerDrawable; @@ -122,6 +124,7 @@ import org.telegram.ui.Components.CheckBoxBase; import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.EmptyStubSpan; import org.telegram.ui.Components.FloatSeekBarAccessibilityDelegate; +import org.telegram.ui.Components.Forum.ForumUtilities; import org.telegram.ui.Components.InfiniteProgress; import org.telegram.ui.Components.LinkPath; import org.telegram.ui.Components.LinkSpanDrawable; @@ -130,7 +133,6 @@ import org.telegram.ui.Components.MessageBackgroundDrawable; import org.telegram.ui.Components.MotionBackgroundDrawable; import org.telegram.ui.Components.MsgClockDrawable; import org.telegram.ui.Components.Point; -import org.telegram.ui.Components.Premium.PremiumGradient; import org.telegram.ui.Components.RLottieDrawable; import org.telegram.ui.Components.RadialProgress2; import org.telegram.ui.Components.Reactions.ReactionsLayoutInBubble; @@ -440,11 +442,18 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate default void needShowPremiumFeatures(String source) { } + default void needShowPremiumBulletin(int type) { + } + default String getAdminRank(long uid) { return null; } - default boolean needPlayMessage(MessageObject messageObject) { + default boolean needPlayMessage(MessageObject messageObject, boolean muted) { + return false; + } + + default boolean drawingVideoPlayerContainer() { return false; } @@ -680,6 +689,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private int[] pressedState = new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}; private float animatingLoadingProgressProgress; CharSequence accessibilityText; + private boolean wasTranscriptionOpen; private RoundVideoPlayingDrawable roundVideoPlayingDrawable; @@ -936,12 +946,15 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate public ImageReceiver replyImageReceiver; public int replyStartX; public int replyStartY; + public float replyHeight; private int replyNameWidth; private int replyNameOffset; private int replyTextWidth; private int replyTextOffset; public boolean needReplyImage; private boolean replyPressed; + private boolean replySelectorPressed, replySelectorCanBePressed; + private float replyTouchX, replyTouchY; private TLRPC.PhotoSize currentReplyPhoto; private int drawSideButton; @@ -964,6 +977,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private boolean drawForwardedName; private float forwardNameX; private int forwardNameY; + private int forwardHeight; private float[] forwardNameOffsetX = new float[2]; private float drawTimeX; @@ -975,6 +989,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private String currentTimeString; private boolean drawTime = true; private boolean forceNotDrawTime; + private Paint drillHolePaint; + private Path drillHolePath; private float unlockAlpha = 1f; private float unlockX; @@ -1003,6 +1019,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private TLRPC.User currentForwardUser; private TLRPC.User currentViaBotUser; + private long currentReplyUserId; private TLRPC.Chat currentForwardChannel; private String currentForwardName; private String currentForwardNameString; @@ -1012,6 +1029,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate public AnimatedEmojiSpan.EmojiGroupedSpans animatedEmojiStack; public AnimatedEmojiSpan.EmojiGroupedSpans animatedEmojiReplyStack; public AnimatedEmojiSpan.EmojiGroupedSpans animatedEmojiDescriptionStack; + public Drawable replySelector; + public Rect replySelectorRect = new Rect(); + public int replySelectorColor, replySelectorRadLeft, replySelectorRadRight; private ChatMessageCellDelegate delegate; @@ -1040,6 +1060,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private boolean imageDrawn; private boolean photoImageOutOfBounds; + private boolean wouldBeInPip; + private AnimatedFloat roundVideoPlayPipFloat = new AnimatedFloat(this, 200, CubicBezierInterpolator.EASE_OUT); + private Paint roundVideoPipPaint; + private Runnable unregisterFlagSecure; private Runnable diceFinishCallback = new Runnable() { @@ -1177,6 +1201,15 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate seekBar.setProgress(progress); } } else if (currentMessageObject.isRoundVideo()) { + if (useSeekBarWaveform) { + if (seekBarWaveform != null) { + seekBarWaveform.setProgress(progress); + } + } else { + if (seekBar != null) { + seekBar.setProgress(progress); + } + } currentMessageObject.audioProgress = progress; } else { return; @@ -1221,7 +1254,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate commentAvatarImages[a] = new ImageReceiver(this); commentAvatarImages[a].setRoundRadius(AndroidUtilities.dp(12)); commentAvatarDrawables[a] = new AvatarDrawable(); - commentAvatarDrawables[a].setTextSize(AndroidUtilities.dp(8)); + commentAvatarDrawables[a].setTextSize(AndroidUtilities.dp(18)); } } @@ -1282,7 +1315,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } private boolean checkTextBlockMotionEvent(MotionEvent event) { - if (!(currentMessageObject.type == 0 || currentMessageObject.type == MessageObject.TYPE_EMOJIS) || currentMessageObject.textLayoutBlocks == null || currentMessageObject.textLayoutBlocks.isEmpty() || !(currentMessageObject.messageText instanceof Spannable)) { + if (!(currentMessageObject.type == MessageObject.TYPE_TEXT || currentMessageObject.type == MessageObject.TYPE_EMOJIS) || currentMessageObject.textLayoutBlocks == null || currentMessageObject.textLayoutBlocks.isEmpty() || !(currentMessageObject.messageText instanceof Spannable)) { return false; } if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_UP && pressedLinkType == 1) { @@ -1403,7 +1436,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } resetPressedLink(1); pressedEmoji = null; - } else if (link[0] == pressedLink.getSpan()) { + } else if (pressedLink != null && link[0] == pressedLink.getSpan()) { delegate.didPressUrl(this, pressedLink.getSpan(), false); resetPressedLink(1); return true; @@ -1591,11 +1624,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } private boolean checkTranscribeButtonMotionEvent(MotionEvent event) { - return useTranscribeButton && transcribeButton != null && transcribeButton.onTouch(event.getAction(), event.getX() - transcribeX, event.getY() - transcribeY); + return useTranscribeButton && (!isPlayingRound || getVideoTranscriptionProgress() > 0 || wasTranscriptionOpen) && transcribeButton != null && transcribeButton.onTouch(event.getAction(), event.getX(), event.getY()); } private boolean checkLinkPreviewMotionEvent(MotionEvent event) { - if (currentMessageObject.type != 0 || !hasLinkPreview) { + if (currentMessageObject.type != MessageObject.TYPE_TEXT || !hasLinkPreview) { return false; } int x = (int) event.getX(); @@ -1731,7 +1764,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { if (documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND) { if (!MediaController.getInstance().isPlayingMessage(currentMessageObject) || MediaController.getInstance().isMessagePaused()) { - delegate.needPlayMessage(currentMessageObject); + delegate.needPlayMessage(currentMessageObject, false); } else { MediaController.getInstance().pauseMessage(currentMessageObject); } @@ -1780,7 +1813,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } private boolean checkPollButtonMotionEvent(MotionEvent event) { - if (currentMessageObject.eventId != 0 || pollVoteInProgress || pollUnvoteInProgress || pollButtons.isEmpty() || currentMessageObject.type != 17 || !currentMessageObject.isSent()) { + if (currentMessageObject.eventId != 0 || pollVoteInProgress || pollUnvoteInProgress || pollButtons.isEmpty() || currentMessageObject.type != MessageObject.TYPE_POLL || !currentMessageObject.isSent()) { return false; } int x = (int) event.getX(); @@ -1879,7 +1912,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } private boolean checkInstantButtonMotionEvent(MotionEvent event) { - if (!currentMessageObject.isSponsored() && (!drawInstantView || currentMessageObject.type == 0)) { + if (!currentMessageObject.isSponsored() && (!drawInstantView || currentMessageObject.type == MessageObject.TYPE_TEXT)) { return false; } int x = (int) event.getX(); @@ -2010,9 +2043,17 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if ((documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC || documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT) && currentPosition != null && (currentPosition.flags & MessageObject.POSITION_FLAG_TOP) == 0) { return false; } - boolean allow = currentMessageObject.type == 16; + boolean allow = currentMessageObject.type == MessageObject.TYPE_PHONE_CALL; if (!allow) { - allow = !(documentAttachType != DOCUMENT_ATTACH_TYPE_DOCUMENT && currentMessageObject.type != 12 && documentAttachType != DOCUMENT_ATTACH_TYPE_MUSIC && documentAttachType != DOCUMENT_ATTACH_TYPE_VIDEO && documentAttachType != DOCUMENT_ATTACH_TYPE_GIF && currentMessageObject.type != 8 || hasGamePreview || hasInvoicePreview); + allow = !( + documentAttachType != DOCUMENT_ATTACH_TYPE_DOCUMENT && + currentMessageObject.type != MessageObject.TYPE_CONTACT && + documentAttachType != DOCUMENT_ATTACH_TYPE_MUSIC && + documentAttachType != DOCUMENT_ATTACH_TYPE_VIDEO && + documentAttachType != DOCUMENT_ATTACH_TYPE_GIF && + currentMessageObject.type != MessageObject.TYPE_GIF + || hasGamePreview || hasInvoicePreview + ); } if (!allow) { return false; @@ -2023,7 +2064,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate boolean result = false; if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (currentMessageObject.type == 16) { + if (currentMessageObject.type == MessageObject.TYPE_PHONE_CALL) { int idx = currentMessageObject.isVideoCall() ? 1 : 0; if (x >= otherX && x <= otherX + AndroidUtilities.dp(30 + (idx == 0 ? 202 : 200)) && y >= otherY - AndroidUtilities.dp(14) && y <= otherY + AndroidUtilities.dp(50)) { otherPressed = true; @@ -2048,7 +2089,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { if (event.getAction() == MotionEvent.ACTION_UP) { if (otherPressed) { - if (currentMessageObject.type == 16 && Build.VERSION.SDK_INT >= 21 && selectorDrawable[0] != null) { + if (currentMessageObject.type == MessageObject.TYPE_PHONE_CALL && Build.VERSION.SDK_INT >= 21 && selectorDrawable[0] != null) { selectorDrawable[0].setState(StateSet.NOTHING); } otherPressed = false; @@ -2058,7 +2099,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate result = true; } } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (currentMessageObject.type == 16 && otherPressed && Build.VERSION.SDK_INT >= 21 && selectorDrawable[0] != null) { + if (currentMessageObject.type == MessageObject.TYPE_PHONE_CALL && otherPressed && Build.VERSION.SDK_INT >= 21 && selectorDrawable[0] != null) { selectorDrawable[0].setHotspot(x, y); } } @@ -2210,7 +2251,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate result = true; } } - if (currentMessageObject.type == 12) { + if (currentMessageObject.type == MessageObject.TYPE_CONTACT) { long uid = MessageObject.getMedia(currentMessageObject.messageOwner).user_id; TLRPC.User user = null; if (uid != 0) { @@ -2227,7 +2268,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (currentMessageObject.isSendError()) { imagePressed = false; result = false; - } else if (currentMessageObject.type == 8 && buttonState == -1 && SharedConfig.autoplayGifs && photoImage.getAnimation() == null) { + } else if (currentMessageObject.type == MessageObject.TYPE_GIF && buttonState == -1 && SharedConfig.autoplayGifs && photoImage.getAnimation() == null) { imagePressed = false; result = false; } @@ -2255,7 +2296,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate invalidate(); } else if (imagePressed) { imagePressed = false; - if (buttonState == -1 || buttonState == 2 || buttonState == 3 || drawVideoImageButton) { + if (buttonState == -1 || buttonState == 1 && isRoundVideo || buttonState == 2 || buttonState == 3 || drawVideoImageButton) { playSoundEffect(SoundEffectConstants.CLICK); didClickedImage(); } else if (buttonState == 0) { @@ -2270,7 +2311,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } private boolean checkAudioMotionEvent(MotionEvent event) { - if (documentAttachType != DOCUMENT_ATTACH_TYPE_AUDIO && documentAttachType != DOCUMENT_ATTACH_TYPE_MUSIC) { + if (documentAttachType != DOCUMENT_ATTACH_TYPE_AUDIO && documentAttachType != DOCUMENT_ATTACH_TYPE_MUSIC && (documentAttachType != DOCUMENT_ATTACH_TYPE_ROUND || documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND && currentMessageObject != null && !currentMessageObject.isVoiceTranscriptionOpen())) { return false; } if (AndroidUtilities.isAccessibilityScreenReaderEnabled()) { @@ -2283,7 +2324,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (useSeekBarWaveform) { result = seekBarWaveform.onTouch(event.getAction(), event.getX() - seekBarX - AndroidUtilities.dp(13), event.getY() - seekBarY); } else { - if (MediaController.getInstance().isPlayingMessage(currentMessageObject)) { + if (MediaController.getInstance().isPlayingMessage(currentMessageObject) || currentMessageObject != null && documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND && currentMessageObject.isVoiceTranscriptionOpen()) { result = seekBar.onTouch(event.getAction(), event.getX() - seekBarX, event.getY() - seekBarY); } else { result = false; @@ -2357,7 +2398,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate return result; } - public boolean checkSpoilersMotionEvent(MotionEvent event) { + public boolean checkSpoilersMotionEvent(MotionEvent event, int n) { + if (n > 15 || getParent() == null) { + return false; + } if (currentMessageObject.hasValidGroupId() && currentMessagesGroup != null && !currentMessagesGroup.isDocuments) { ViewGroup parent = (ViewGroup) getParent(); for (int i = 0; i < parent.getChildCount(); i++) { @@ -2371,7 +2415,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate (position.flags & MessageObject.POSITION_FLAG_LEFT) != 0) { if (cell != this) { event.offsetLocation(this.getLeft() - cell.getLeft(), this.getTop() - cell.getTop()); - boolean result = cell.checkSpoilersMotionEvent(event); + boolean result = cell.checkSpoilersMotionEvent(event, n + 1); event.offsetLocation(-(this.getLeft() - cell.getLeft()), -(this.getTop() - cell.getTop())); return result; } @@ -2583,7 +2627,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate lastTouchY = event.getY(); backgroundDrawable.setTouchCoords(lastTouchX, lastTouchY); - boolean result = checkSpoilersMotionEvent(event); + boolean result = checkSpoilersMotionEvent(event, 0); if (!result) { result = checkTextBlockMotionEvent(event); @@ -2694,7 +2738,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } result = true; invalidate(); - } else if (drawForwardedName && forwardedNameLayout[0] != null && x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + AndroidUtilities.dp(32)) { + } else if (drawForwardedName && forwardedNameLayout[0] != null && x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + forwardHeight) { if (viaWidth != 0 && x >= forwardNameX + viaNameWidth + AndroidUtilities.dp(4)) { forwardBotPressed = true; } else { @@ -2717,8 +2761,23 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { replyEnd = replyStartX + backgroundDrawableRight; } - if (x >= replyStartX && x <= replyEnd && y >= replyStartY && y <= replyStartY + AndroidUtilities.dp(35)) { + if (x >= replyStartX && x <= replyEnd && y >= replyStartY && y <= replyStartY + replyHeight) { replyPressed = true; + replyTouchX = x; + replyTouchY = y + getY(); + if (replySelector != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + replySelector.setHotspot(x, y); + } + replySelectorPressed = false; + replySelectorCanBePressed = true; + postDelayed(() -> { + if (replyPressed && !replySelectorPressed && replySelectorCanBePressed) { + replySelectorPressed = true; + replySelector.setState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled}); + } + }, ViewConfiguration.getTapTimeout()); + } result = true; } } @@ -2792,7 +2851,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { forwardNamePressed = false; } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (!(x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + AndroidUtilities.dp(32))) { + if (!(x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + forwardHeight)) { forwardNamePressed = false; } } @@ -2811,7 +2870,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate forwardBotPressed = false; } else if (event.getAction() == MotionEvent.ACTION_MOVE) { if (drawForwardedName && forwardedNameLayout[0] != null) { - if (!(x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + AndroidUtilities.dp(32))) { + if (!(x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + forwardHeight)) { forwardBotPressed = false; } } else { @@ -2823,6 +2882,19 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else if (replyPressed) { if (event.getAction() == MotionEvent.ACTION_UP) { replyPressed = false; + if (replySelector != null) { + if (!replySelectorPressed) { + replySelector.setState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled}); + post(() -> { + replySelector.setState(new int[]{}); + invalidate(); + }); + } else { + replySelector.setState(new int[]{}); + } + replySelectorPressed = false; + replySelectorCanBePressed = false; + } playSoundEffect(SoundEffectConstants.CLICK); if (replyPanelIsForward) { if (delegate != null) { @@ -2841,6 +2913,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { replyPressed = false; + if (replySelector != null) { + replySelector.setState(new int[]{}); + } } else if (event.getAction() == MotionEvent.ACTION_MOVE) { int replyEnd; if (currentMessageObject.shouldDrawWithoutBackground()) { @@ -2848,8 +2923,15 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { replyEnd = replyStartX + backgroundDrawableRight; } - if (!(x >= replyStartX && x <= replyEnd && y >= replyStartY && y <= replyStartY + AndroidUtilities.dp(35))) { + if (!(x >= replyStartX && x <= replyEnd && y >= replyStartY && y <= replyStartY + replyHeight)) { replyPressed = false; + replySelectorPressed = false; + replySelectorCanBePressed = false; + if (replySelector != null) { + replySelector.setState(new int[]{}); + } + } else if (replySelector != null && replySelectorCanBePressed && Math.sqrt(Math.pow(x - replyTouchX, 2) + Math.pow((y + getY()) - replyTouchY, 2)) > AndroidUtilities.dp(2)) { + replySelectorCanBePressed = false; } } } else if (sideButtonPressed) { @@ -3049,6 +3131,17 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate lastTime = duration; } } else if (isRoundVideo) { + if (useSeekBarWaveform) { + if (!seekBarWaveform.isDragging()) { + seekBarWaveform.setProgress(currentMessageObject.audioProgress, true); + } + } else { + if (!seekBar.isDragging()) { + seekBar.setProgress(currentMessageObject.audioProgress); + seekBar.setBufferedProgress(currentMessageObject.bufferedProgress); + } + } + int duration = 0; TLRPC.Document document = currentMessageObject.getDocument(); for (int a = 0; a < document.attributes.size(); a++) { @@ -3254,7 +3347,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else if (buttonState == 0) { didPressButton(true, false); } - } else if (currentMessageObject.type == 12) { + } else if (currentMessageObject.type == MessageObject.TYPE_CONTACT) { long uid = MessageObject.getMedia(currentMessageObject.messageOwner).user_id; TLRPC.User user = null; if (uid != 0) { @@ -3266,12 +3359,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate didPressButton(true, false); } else { if (!MediaController.getInstance().isPlayingMessage(currentMessageObject) || MediaController.getInstance().isMessagePaused()) { - delegate.needPlayMessage(currentMessageObject); + delegate.needPlayMessage(currentMessageObject, false); } else { MediaController.getInstance().pauseMessage(currentMessageObject); } } - } else if (currentMessageObject.type == 8) { + } else if (currentMessageObject.type == MessageObject.TYPE_GIF) { if (buttonState == -1 || buttonState == 1 && canStreamVideo && autoPlayingMedia) { //if (SharedConfig.autoplayGifs) { delegate.didPressImage(this, lastTouchX, lastTouchY); @@ -3318,7 +3411,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // open message options then if (delegate != null) { - if (currentMessageObject.type == 16) { + if (currentMessageObject.type == MessageObject.TYPE_PHONE_CALL) { delegate.didLongPress(this, 0, 0); } else { delegate.didPressOther(this, otherX, otherY); @@ -3342,7 +3435,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } private boolean isPhotoDataChanged(MessageObject object) { - if (object.type == 0 || object.type == 14) { + if (object.type == MessageObject.TYPE_TEXT || object.type == MessageObject.TYPE_MUSIC) { return false; } if (object.type == MessageObject.TYPE_GEO) { @@ -3386,7 +3479,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } return !url.equals(currentUrl); } else if (currentPhotoObject == null || currentPhotoObject.location instanceof TLRPC.TL_fileLocationUnavailable) { - return object.type == MessageObject.TYPE_PHOTO || object.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW || object.type == MessageObject.TYPE_ROUND_VIDEO || object.type == MessageObject.TYPE_VIDEO || object.type == 8 || object.isAnyKindOfSticker(); + return ( + object.type == MessageObject.TYPE_PHOTO || + object.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW || + object.type == MessageObject.TYPE_ROUND_VIDEO || + object.type == MessageObject.TYPE_VIDEO || + object.type == MessageObject.TYPE_GIF || + object.isAnyKindOfSticker() + ); } else if (currentMessageObject != null && photoNotSet) { File cacheFile = FileLoader.getInstance(currentAccount).getPathToMessage(currentMessageObject.messageOwner); return cacheFile.exists(); @@ -3850,7 +3950,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate currentMessagesGroup = groupedMessages; lastTime = -2; lastPostAuthor = messageObject.messageOwner.post_author; - isHighlightedAnimated = false; + if (messageIdChanged || groupChanged) { + isHighlightedAnimated = false; + wasTranscriptionOpen = false; + } widthBeforeNewTimeLine = -1; if (currentMessagesGroup != null && (currentMessagesGroup.posArray.size() > 1)) { currentPosition = currentMessagesGroup.positions.get(currentMessageObject); @@ -3869,6 +3972,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate drawPinnedBottom = pinnedBottom && (currentPosition == null || (currentPosition.flags & MessageObject.POSITION_FLAG_BOTTOM) != 0); } + boolean wasPlayingRound = isPlayingRound; isPlayingRound = isRoundVideo && MediaController.getInstance().isPlayingMessage(currentMessageObject) && delegate != null && !delegate.keyboardIsOpened() && !delegate.isLandscape(); photoImage.setCrossfadeWithOldImage(false); photoImage.setCrossfadeDuration(ImageReceiver.DEFAULT_CROSSFADE_DURATION); @@ -3885,7 +3989,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate sideButtonPressed = false; hasNewLineForTime = false; flipImage = false; - isThreadPost = isThreadChat && messageObject.messageOwner.fwd_from != null && messageObject.messageOwner.fwd_from.channel_post != 0; + isThreadPost = isThreadChat && messageObject.messageOwner.fwd_from != null && messageObject.messageOwner.fwd_from.channel_post != 0 && messageObject.messageOwner.reply_to == null; isAvatarVisible = !isThreadPost && isChat && !messageObject.isOutOwner() && messageObject.needDrawAvatar() && (currentPosition == null || currentPosition.edge); boolean drawAvatar = isChat && !isThreadPost && !messageObject.isOutOwner() && messageObject.needDrawAvatar(); if (messageObject.customAvatarDrawable != null) { @@ -3922,6 +4026,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate currentReplyPhoto = null; currentUser = null; currentChat = null; + currentReplyUserId = 0; currentViaBotUser = null; instantViewLayout = null; drawNameLayout = false; @@ -4037,7 +4142,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate boolean canChangeRadius = true; - if (messageIdChanged || messageObject.reactionsChanged) { + if (messageIdChanged || messageObject.reactionsChanged || wasPlayingRound != isPlayingRound) { messageObject.reactionsChanged = false; if (currentPosition == null || ((currentPosition.flags & MessageObject.POSITION_FLAG_BOTTOM) != 0)) { boolean isSmall = !messageObject.shouldDrawReactionsInLayout(); @@ -4145,12 +4250,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } } - commentWidth = totalCommentWidth = (int) Math.ceil(Theme.chat_replyNamePaint.measureText(comment)); - commentLayout = new StaticLayout(comment, Theme.chat_replyNamePaint, commentWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + commentWidth = totalCommentWidth = (int) Math.ceil(Theme.chat_commentTextPaint.measureText(comment)); + commentLayout = new StaticLayout(comment, Theme.chat_commentTextPaint, commentWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (commentCount != 0 && !LocaleController.isRTL) { drawCommentNumber = true; if (commentNumberLayout == null) { - commentNumberLayout = new AnimatedNumberLayout(this, Theme.chat_replyNamePaint); + commentNumberLayout = new AnimatedNumberLayout(this, Theme.chat_commentTextPaint); commentNumberLayout.setNumber(commentCount, false); } else { commentNumberLayout.setNumber(commentCount, messageObject.animateComments); @@ -4181,7 +4286,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate drawCommentNumber = false; } - if (messageObject.type == 0) { + if (messageObject.type == MessageObject.TYPE_TEXT) { drawForwardedName = !isRepliesChat; int maxWidth; @@ -4198,7 +4303,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { maxWidth = Math.min(getParentWidth(), AndroidUtilities.displaySize.y) - AndroidUtilities.dp(80); } - drawName = isPinnedChat || messageObject.messageOwner.peer_id.channel_id != 0 && (!messageObject.isOutOwner() || messageObject.isSupergroup()) || messageObject.isImportedForward() && messageObject.messageOwner.fwd_from.from_id == null; + drawName = isPinnedChat || (messageObject.messageOwner.peer_id != null && messageObject.messageOwner.peer_id.channel_id != 0 && (!messageObject.isOutOwner() || messageObject.isSupergroup())) || messageObject.isImportedForward() && messageObject.messageOwner.fwd_from.from_id == null; } availableTimeWidth = maxWidth; @@ -5192,7 +5297,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate photoImage.setImageBitmap((Drawable) null); calcBackgroundWidth(maxWidth, timeMore, maxChildWidth); } - } else if (messageObject.type == 16) { + } else if (messageObject.type == MessageObject.TYPE_PHONE_CALL) { createSelectorDrawable(0); drawName = false; drawForwardedName = false; @@ -5261,7 +5366,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (drawPinnedTop) { namesOffset -= AndroidUtilities.dp(1); } - } else if (messageObject.type == 12) { + } else if (messageObject.type == MessageObject.TYPE_CONTACT) { drawName = messageObject.isFromGroup() && messageObject.isSupergroup() || messageObject.isImportedForward() && messageObject.messageOwner.fwd_from.from_id == null; drawForwardedName = !isRepliesChat; drawPhotoImage = true; @@ -5299,8 +5404,6 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate CharSequence phone; if (!TextUtils.isEmpty(messageObject.vCardData)) { phone = messageObject.vCardData; - drawInstantView = true; - drawInstantViewType = 5; } else { if (user != null && !TextUtils.isEmpty(user.phone)) { phone = PhoneFormat.getInstance().format("+" + user.phone); @@ -5313,6 +5416,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } } + if (user != null) { + drawInstantView = true; + drawInstantViewType = 5; + } CharSequence currentNameString = ContactsController.formatName(MessageObject.getMedia(messageObject.messageOwner).first_name, MessageObject.getMedia(messageObject.messageOwner).last_name).replace('\n', ' '); if (currentNameString.length() == 0) { @@ -5399,7 +5506,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate totalHeight += reactionsLayoutInBubble.totalHeight; } } - } else if (messageObject.type == 14) { + } else if (messageObject.type == MessageObject.TYPE_MUSIC) { drawName = (messageObject.isFromGroup() && messageObject.isSupergroup() || messageObject.isImportedForward() && messageObject.messageOwner.fwd_from.from_id == null) && (currentPosition == null || (currentPosition.flags & MessageObject.POSITION_FLAG_TOP) != 0); int maxWidth; if (AndroidUtilities.isTablet()) { @@ -5728,7 +5835,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (!messageObject.isAnyKindOfSticker() && messageObject.type != MessageObject.TYPE_ROUND_VIDEO) { drawName = (messageObject.isFromGroup() && messageObject.isSupergroup() || messageObject.isImportedForward() && messageObject.messageOwner.fwd_from.from_id == null) && (currentPosition == null || (currentPosition.flags & MessageObject.POSITION_FLAG_TOP) != 0); } - mediaBackground = isMedia = messageObject.type != 9; + mediaBackground = isMedia = messageObject.type != MessageObject.TYPE_FILE; drawImageButton = true; drawPhotoImage = true; @@ -5736,7 +5843,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate int photoHeight = 0; int additionHeight = 0; - if (messageObject.gifState != 2 && !SharedConfig.autoplayGifs && (messageObject.type == 8 || messageObject.type == MessageObject.TYPE_ROUND_VIDEO)) { + if (messageObject.gifState != 2 && !SharedConfig.autoplayGifs && (messageObject.type == MessageObject.TYPE_GIF || messageObject.type == MessageObject.TYPE_ROUND_VIDEO)) { messageObject.gifState = 1; } @@ -5751,7 +5858,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } photoImage.setForcePreview(messageObject.needDrawBluredPreview()); - if (messageObject.type == 9) { + if (messageObject.type == MessageObject.TYPE_FILE) { if (AndroidUtilities.isTablet()) { backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(300)); } else { @@ -6042,7 +6149,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate maxWidth = Math.max(backgroundWidth, maxWidth); if (!reactionsLayoutInBubble.isSmall) { reactionsLayoutInBubble.measure(maxWidth, currentMessageObject.isOutOwner() ? Gravity.RIGHT : Gravity.LEFT); - reactionsLayoutInBubble.drawServiceShaderBackground = true; + reactionsLayoutInBubble.drawServiceShaderBackground = 1f; reactionsLayoutInBubble.totalHeight = reactionsLayoutInBubble.height;// + AndroidUtilities.dp(8); additionHeight += reactionsLayoutInBubble.totalHeight + AndroidUtilities.dp(8); reactionsLayoutInBubble.positionOffsetY += AndroidUtilities.dp(8); @@ -6192,7 +6299,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } if (!reactionsLayoutInBubble.isSmall) { reactionsLayoutInBubble.measure(maxWidth, currentMessageObject.isOutOwner() && (currentMessageObject.isAnimatedEmoji() || currentMessageObject.isAnyKindOfSticker()) ? Gravity.RIGHT : Gravity.LEFT); - reactionsLayoutInBubble.drawServiceShaderBackground = true; + reactionsLayoutInBubble.drawServiceShaderBackground = 1f; reactionsLayoutInBubble.totalHeight = reactionsLayoutInBubble.height + AndroidUtilities.dp(8); additionHeight += reactionsLayoutInBubble.totalHeight; if (!currentMessageObject.isAnimatedEmoji()) { @@ -6210,7 +6317,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (AndroidUtilities.isTablet()) { photoWidth = (int) (AndroidUtilities.getMinTabletSide() * 0.7f); } else { - if (currentPhotoObject != null && (messageObject.type == MessageObject.TYPE_PHOTO || messageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW || messageObject.type == MessageObject.TYPE_VIDEO || messageObject.type == 8) && currentPhotoObject.w >= currentPhotoObject.h) { + if ( + currentPhotoObject != null && ( + messageObject.type == MessageObject.TYPE_PHOTO || + messageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW || + messageObject.type == MessageObject.TYPE_VIDEO || + messageObject.type == MessageObject.TYPE_GIF + ) && currentPhotoObject.w >= currentPhotoObject.h + ) { photoWidth = Math.min(getParentWidth(), AndroidUtilities.displaySize.y) - AndroidUtilities.dp(64 + (checkNeedDrawShareButton(messageObject) ? 10 : 0)); useFullWidth = true; } else { @@ -6220,7 +6334,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } photoHeight = photoWidth + AndroidUtilities.dp(100); if (!useFullWidth) { - if (messageObject.type != 5 && checkNeedDrawShareButton(messageObject)) { + if (messageObject.type != MessageObject.TYPE_ROUND_VIDEO && checkNeedDrawShareButton(messageObject)) { photoWidth -= AndroidUtilities.dp(20); } if (photoWidth > AndroidUtilities.getPhotoSize()) { @@ -6235,10 +6349,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate boolean needQualityPreview = false; - if (messageObject.type == MessageObject.TYPE_PHOTO || messageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW) { //photo + if (messageObject.type == MessageObject.TYPE_PHOTO || messageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW) { // photo updateSecretTimeText(messageObject); currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 40); - } else if (messageObject.type == MessageObject.TYPE_VIDEO || messageObject.type == 8) { //video, gif + } else if (messageObject.type == MessageObject.TYPE_VIDEO || messageObject.type == MessageObject.TYPE_GIF) { createDocumentLayout(0, messageObject); currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 40); updateSecretTimeText(messageObject); @@ -6254,7 +6368,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate int w; int h; if (messageObject.type == MessageObject.TYPE_ROUND_VIDEO) { - if (isPlayingRound) { + if (isPlayingRound && !messageObject.isVoiceTranscriptionOpen()) { w = h = AndroidUtilities.roundPlayingMessageSize; } else { w = h = AndroidUtilities.roundMessageSize; @@ -6312,7 +6426,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate mediaBackground = false; } - if ((w == 0 || h == 0) && messageObject.type == 8) { + if ((w == 0 || h == 0) && messageObject.type == MessageObject.TYPE_GIF) { TLRPC.Document document = messageObject.getDocument(); if (document != null) { for (int a = 0; a < document.attributes.size(); a++) { @@ -6378,7 +6492,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (messageObject.isRoundVideo()) { w = h = Math.min(w, h); - drawBackground = false; + drawBackground = messageObject.isVoiceTranscriptionOpen(); + mediaBackground = !drawBackground; + if (drawBackground) { + h = 0; + } photoImage.setRoundRadius(w / 2); canChangeRadius = false; } else if (messageObject.needDrawBluredPreview() && !messageObject.hasExtendedMediaPreview()) { @@ -6390,6 +6508,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } int widthForCaption = 0; + int widthCaptionMin = -1; boolean fixPhotoWidth = false; if (currentMessagesGroup != null) { float maxHeight = Math.max(getParentWidth(), AndroidUtilities.displaySize.y) * 0.5f; @@ -6551,6 +6670,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { captionWidth = widthForCaption; } + if (widthCaptionMin > 0 && captionWidth > widthCaptionMin) { + photoWidth += captionWidth - widthCaptionMin; + backgroundWidth += captionWidth - widthCaptionMin; + } captionHeight = captionLayout.getHeight(); addedCaptionHeight = captionHeight + AndroidUtilities.dp(9); if (currentPosition == null || (currentPosition.flags & MessageObject.POSITION_FLAG_BOTTOM) != 0) { @@ -6576,8 +6699,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate int maxWidth = Math.max(backgroundWidth - AndroidUtilities.dp(36), widthForCaption); reactionsLayoutInBubble.measure(maxWidth, Gravity.LEFT); if (!reactionsLayoutInBubble.isEmpty) { - if (shouldDrawTimeOnMedia()) { - reactionsLayoutInBubble.drawServiceShaderBackground = true; + if (isRoundVideo) { + reactionsLayoutInBubble.drawServiceShaderBackground = 1f - getVideoTranscriptionProgress(); + } else if (shouldDrawTimeOnMedia()) { + reactionsLayoutInBubble.drawServiceShaderBackground = 1f; } int heightLocal = reactionsLayoutInBubble.height; if (captionLayout == null) { @@ -6590,6 +6715,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate reactionsLayoutInBubble.totalHeight = heightLocal; additionHeight += reactionsLayoutInBubble.totalHeight; + if (isRoundVideo && currentMessageObject != null && currentMessageObject.isVoiceTranscriptionOpen()) { + reactionsLayoutInBubble.positionOffsetY += AndroidUtilities.dp(8); + } + if (!shouldDrawTimeOnMedia()) { int widthToCheck = Math.min(maxWidth, reactionsLayoutInBubble.width + timeWidthTotal + getExtraTimeX() + AndroidUtilities.dp(2)); @@ -6632,7 +6761,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } if (messageChanged || messageIdChanged || dataChanged) { currentPhotoFilter = currentPhotoFilterThumb = String.format(Locale.US, "%d_%d", (int) (w / AndroidUtilities.density), (int) (h / AndroidUtilities.density)); - if (messageObject.photoThumbs != null && messageObject.photoThumbs.size() > 1 || messageObject.type == MessageObject.TYPE_VIDEO || messageObject.type == 8 || messageObject.type == MessageObject.TYPE_ROUND_VIDEO) { + if ( + messageObject.photoThumbs != null && messageObject.photoThumbs.size() > 1 || + messageObject.type == MessageObject.TYPE_VIDEO || + messageObject.type == MessageObject.TYPE_GIF || + messageObject.type == MessageObject.TYPE_ROUND_VIDEO + ) { if (messageObject.needDrawBluredPreview()) { currentPhotoFilter += "_b2"; currentPhotoFilterThumb += "_b2"; @@ -6656,7 +6790,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } boolean noSize = false; - if (messageObject.type == MessageObject.TYPE_VIDEO || messageObject.type == 8 || messageObject.type == MessageObject.TYPE_ROUND_VIDEO) { + if (messageObject.type == MessageObject.TYPE_VIDEO || messageObject.type == MessageObject.TYPE_GIF || messageObject.type == MessageObject.TYPE_ROUND_VIDEO) { noSize = true; } if (currentPhotoObject != null && !noSize && currentPhotoObject.size == 0) { @@ -6718,7 +6852,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate photoImage.setImageBitmap((Drawable) null); } } - } else if (messageObject.type == 8 || messageObject.type == MessageObject.TYPE_ROUND_VIDEO) { + } else if (messageObject.type == MessageObject.TYPE_GIF || messageObject.type == MessageObject.TYPE_ROUND_VIDEO) { String fileName = FileLoader.getAttachFileName(messageObject.getDocument()); int localFile = 0; if (messageObject.attachPathExists) { @@ -6786,13 +6920,16 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate setMessageObjectInternal(messageObject); if (drawForwardedName && messageObject.needDrawForwarded() && (currentPosition == null || currentPosition.minY == 0)) { - if (messageObject.type != 5) { + if (messageObject.type != MessageObject.TYPE_ROUND_VIDEO) { namesOffset += AndroidUtilities.dp(5); } } else if (drawNameLayout && (messageObject.getReplyMsgId() == 0 || isThreadChat && messageObject.getReplyTopMsgId() == 0)) { namesOffset += AndroidUtilities.dp(7); } totalHeight = photoHeight + AndroidUtilities.dp(14) + namesOffset + additionHeight; + if (messageObject.isVoiceTranscriptionOpen()) { + totalHeight += AndroidUtilities.dp(70 - 14); + } if (currentPosition != null && (currentPosition.flags & MessageObject.POSITION_FLAG_BOTTOM) == 0 && !currentMessageObject.isDocument() && currentMessageObject.type != MessageObject.TYPE_EMOJIS) { totalHeight -= AndroidUtilities.dp(3); } @@ -6836,11 +6973,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate totalHeight -= AndroidUtilities.dp(drawPhotoImage ? 3 : 6); } } - photoImage.setImageCoords(0, y + namesOffset + additionalTop, photoWidth, photoHeight); + if (messageObject.isRoundVideo() && messageObject.isVoiceTranscriptionOpen()) { + photoImage.setImageCoords(0, AndroidUtilities.dp(13), AndroidUtilities.dp(44), AndroidUtilities.dp(44)); + } else { + photoImage.setImageCoords(0, y + namesOffset + additionalTop, photoWidth, photoHeight); + } invalidate(); } - // if ((currentPosition == null || currentMessageObject.isMusic() || currentMessageObject.isDocument()) && !messageObject.isAnyKindOfSticker() && addedCaptionHeight == 0) { if (!messageObject.isRestrictedMessage && captionLayout == null && (messageObject.caption != null || messageObject.isVoiceTranscriptionOpen())) { currentCaption = messageObject.isVoiceTranscriptionOpen() ? messageObject.getVoiceTranscription() : messageObject.caption; @@ -6853,14 +6993,17 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } try { int width = backgroundWidth; - if (messageObject.isVoiceTranscriptionOpen()) { + if ((currentMessageObject.type == MessageObject.TYPE_VOICE || isRoundVideo) && messageObject.isVoiceTranscriptionOpen()) { if (AndroidUtilities.isTablet()) { width = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(drawAvatar ? 102 : 50); } else { width = getParentWidth() - AndroidUtilities.dp(drawAvatar ? 102 : 50); } } - int widthForCaption = width - AndroidUtilities.dp(31) - AndroidUtilities.dp(10) - getExtraTextX() * 2; + if (drawSideButton != 0 && isRoundVideo) { + width -= AndroidUtilities.dp(24); + } + int widthForCaption = width - AndroidUtilities.dp(31 + (currentMessageObject.type != MessageObject.TYPE_ROUND_VIDEO ? 10 : 0)) - getExtraTextX() * 2; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { captionLayout = StaticLayout.Builder.obtain(currentCaption, 0, currentCaption.length(), Theme.chat_msgTextPaint, widthForCaption) .setBreakStrategy(StaticLayout.BREAK_STRATEGY_HIGH_QUALITY) @@ -6870,13 +7013,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { captionLayout = new StaticLayout(currentCaption, Theme.chat_msgTextPaint, widthForCaption, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } - updateSeekBarWaveformWidth(); + updateSeekBarWaveformWidth(null); updateCaptionSpoilers(); } catch (Exception e) { FileLog.e(e); } } - if (captionLayout != null || currentMessageObject.type == MessageObject.TYPE_VOICE) { + if (captionLayout != null || currentMessageObject.type == MessageObject.TYPE_VOICE || currentMessageObject.type == MessageObject.TYPE_ROUND_VIDEO) { try { if (messageObject.isVoiceTranscriptionOpen() && captionLayout != null) { float startMaxWidth = backgroundWidth - AndroidUtilities.dp(31) - AndroidUtilities.dp(10) - getExtraTextX() * 2, maxWidth = startMaxWidth; @@ -6970,7 +7113,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate FileLog.e(e); } - if (messageObject.type == MessageObject.TYPE_PHOTO || messageObject.type == MessageObject.TYPE_VIDEO || messageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW) { + if ( + messageObject.type == MessageObject.TYPE_PHOTO || + messageObject.type == MessageObject.TYPE_VIDEO || + messageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW + ) { totalHeight += AndroidUtilities.dp(6); } @@ -7115,6 +7262,17 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else if (drawPinnedTop && pinnedBottom && currentPosition != null && currentPosition.siblingHeights == null) { totalHeight -= AndroidUtilities.dp(1); } + if (!mediaBackground) { + if (messageObject.type == MessageObject.TYPE_TEXT) { + totalHeight -= AndroidUtilities.dp(2); + } + if (drawPinnedBottom) { + totalHeight -= AndroidUtilities.dp(1); + } + if (drawPinnedTop) { + totalHeight -= AndroidUtilities.dp(1); + } + } if (messageObject.type != MessageObject.TYPE_EMOJIS) { if (messageObject.isAnyKindOfSticker() && totalHeight < AndroidUtilities.dp(70)) { additionalTimeOffsetY = AndroidUtilities.dp(70) - totalHeight; @@ -7233,7 +7391,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } if (transcribeButton != null) { - transcribeButton.setOpen(currentMessageObject.messageOwner != null && currentMessageObject.messageOwner.voiceTranscriptionOpen && currentMessageObject.messageOwner.voiceTranscriptionFinal, !messageIdChanged); + transcribeButton.setOpen(currentMessageObject.messageOwner != null && currentMessageObject.messageOwner.voiceTranscriptionOpen && currentMessageObject.messageOwner.voiceTranscriptionFinal && TranscribeButton.isVideoTranscriptionOpen(currentMessageObject), !messageIdChanged); transcribeButton.setLoading(TranscribeButton.isTranscribing(currentMessageObject), !messageIdChanged); } updateWaveform(); @@ -7356,6 +7514,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate return false; } } + if (replySelector != null) { + replySelector.setState(new int[]{}); + } if (pressedEmoji != null) { // hadLongPress = true; // if (delegate.didPressAnimatedEmoji(pressedEmoji)) { @@ -7593,6 +7754,16 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate videoRadialProgress.setPressed(forcePressed || videoButtonPressed != 0, false); } + @Override + public void onSeekBarPressed() { + requestDisallowInterceptTouchEvent(true); + } + + @Override + public void onSeekBarReleased() { + requestDisallowInterceptTouchEvent(false); + } + @Override public void onSeekBarDrag(float progress) { if (currentMessageObject == null) { @@ -7618,40 +7789,80 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } private void updateWaveform() { - if (currentMessageObject == null || documentAttachType != DOCUMENT_ATTACH_TYPE_AUDIO) { + if (currentMessageObject == null || documentAttachType != DOCUMENT_ATTACH_TYPE_AUDIO && documentAttachType != DOCUMENT_ATTACH_TYPE_ROUND) { return; } - for (int a = 0; a < documentAttach.attributes.size(); a++) { - TLRPC.DocumentAttribute attribute = documentAttach.attributes.get(a); - if (attribute instanceof TLRPC.TL_documentAttributeAudio) { - if (attribute.waveform == null || attribute.waveform.length == 0) { - MediaController.getInstance().generateWaveform(currentMessageObject); - } - useSeekBarWaveform = attribute.waveform != null; - seekBarWaveform.setWaveform(attribute.waveform); - break; - } + byte[] waveform = currentMessageObject.getWaveform(); + useSeekBarWaveform = waveform != null; + if (seekBarWaveform != null) { + seekBarWaveform.setWaveform(waveform); } - useTranscribeButton = currentMessageObject.isVoice() && useSeekBarWaveform && currentMessageObject.messageOwner != null && !(MessageObject.getMedia(currentMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaWebPage) && UserConfig.getInstance(currentAccount).isPremium(); - updateSeekBarWaveformWidth(); + useTranscribeButton = currentMessageObject != null && (!currentMessageObject.isOutOwner() || currentMessageObject.isSent()) && (UserConfig.getInstance(currentAccount).isPremium() || !MessagesController.getInstance(currentAccount).didPressTranscribeButtonEnough() && (currentMessageObject.messageOwner != null && currentMessageObject.messageOwner.voiceTranscriptionForce || currentMessageObject.getDuration() >= 60) && !MessagesController.getInstance(currentAccount).premiumLocked) && (currentMessageObject.isVoice() && useSeekBarWaveform || currentMessageObject.isRoundVideo()) && currentMessageObject.messageOwner != null && !(MessageObject.getMedia(currentMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaWebPage); + updateSeekBarWaveformWidth(null); } - private void updateSeekBarWaveformWidth() { - if (seekBarWaveform != null) { - int offset = -AndroidUtilities.dp(92 + (hasLinkPreview ? 10 : 0)) - AndroidUtilities.dp(useTranscribeButton ? 34 : 0); - if (transitionParams.animateBackgroundBoundsInner && documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO) { - int fromBackgroundWidth = this.backgroundWidth; - int toBackgroundWidth = (int) (this.backgroundWidth - transitionParams.toDeltaLeft + transitionParams.toDeltaRight); - int backgroundWidth = (int) (this.backgroundWidth - transitionParams.deltaLeft + transitionParams.deltaRight); - seekBarWaveform.setSize(backgroundWidth + offset, AndroidUtilities.dp(30), fromBackgroundWidth + offset, toBackgroundWidth + offset); - } else { - seekBarWaveform.setSize(backgroundWidth + offset, AndroidUtilities.dp(30)); + private int seekBarWaveformTranslateX; + private int seekBarTranslateX; + + private void updateSeekBarWaveformWidth(Canvas canvas) { + seekBarWaveformTranslateX = 0; + seekBarTranslateX = 0; + int offset = -AndroidUtilities.dp(92 + (hasLinkPreview ? 10 : 0)); + if (transitionParams.animateBackgroundBoundsInner && (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND)) { + int fromBackgroundWidth = this.backgroundWidth; + int toBackgroundWidth = (int) (this.backgroundWidth - transitionParams.toDeltaLeft + transitionParams.toDeltaRight); + int backgroundWidth = (int) (this.backgroundWidth - transitionParams.deltaLeft + transitionParams.deltaRight); + if (isRoundVideo && !drawBackground) { + backgroundWidth += getVideoTranscriptionProgress() * AndroidUtilities.dp(8); + toBackgroundWidth += AndroidUtilities.dp(8); + } + if (!(transitionParams.toDeltaLeft != 0 || transitionParams.toDeltaRight != 0)) { + toBackgroundWidth = backgroundWidth; + } + if (seekBarWaveform != null) { + if (transitionParams.animateUseTranscribeButton) { + seekBarWaveform.setSize( + backgroundWidth + offset - (int) (AndroidUtilities.dp(34) * getUseTranscribeButtonProgress()), + AndroidUtilities.dp(30), + fromBackgroundWidth + offset + (!useTranscribeButton ? -AndroidUtilities.dp(34) : 0), + toBackgroundWidth + offset + (useTranscribeButton ? -AndroidUtilities.dp(34) : 0) + ); + } else { + seekBarWaveform.setSize( + backgroundWidth + offset - (int) (AndroidUtilities.dp(34) * getUseTranscribeButtonProgress()), + AndroidUtilities.dp(30), + fromBackgroundWidth + offset - (int) (AndroidUtilities.dp(34) * getUseTranscribeButtonProgress()), + toBackgroundWidth + offset - (int) (AndroidUtilities.dp(34) * getUseTranscribeButtonProgress()) + ); + } + } + if (seekBar != null) { + seekBar.setSize(backgroundWidth - (int) (getUseTranscribeButtonProgress() * AndroidUtilities.dp(34)) - AndroidUtilities.dp((documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC ? 65 : 72) + (hasLinkPreview ? 10 : 0)), AndroidUtilities.dp(30)); + } + } else { + if (seekBarWaveform != null) { + if (transitionParams.animateUseTranscribeButton) { + seekBarWaveform.setSize( + backgroundWidth + offset - (int) (AndroidUtilities.dp(34) * getUseTranscribeButtonProgress()), + AndroidUtilities.dp(30), + backgroundWidth + offset + (!useTranscribeButton ? -AndroidUtilities.dp(34) : 0), + backgroundWidth + offset + (useTranscribeButton ? -AndroidUtilities.dp(34) : 0) + ); + } else { + seekBarWaveform.setSize( + backgroundWidth + offset - (int) (AndroidUtilities.dp(34) * getUseTranscribeButtonProgress()), + AndroidUtilities.dp(30) + ); + } + } + if (seekBar != null) { + seekBar.setSize(backgroundWidth - (int) (getUseTranscribeButtonProgress() * AndroidUtilities.dp(34)) - AndroidUtilities.dp((documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC ? 65 : 72) + (hasLinkPreview ? 10 : 0)), AndroidUtilities.dp(30)); } } } private int createDocumentLayout(int maxWidth, MessageObject messageObject) { - if (messageObject.type == 0) { + if (messageObject.type == MessageObject.TYPE_TEXT) { documentAttach = MessageObject.getMedia(messageObject.messageOwner).webpage.document; } else { documentAttach = messageObject.getDocument(); @@ -8026,7 +8237,23 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } radii[a * 2] = radii[a * 2 + 1] = 0; } - path.addRoundRect(rect, radii, Path.Direction.CW); + if (!out && !drawPinnedBottom && currentPosition == null) { + path.moveTo(rect.left + AndroidUtilities.dp(6), rect.top); + path.lineTo(rect.left + AndroidUtilities.dp(6), rect.bottom - AndroidUtilities.dp(6) - AndroidUtilities.dp(2 + 3)); + AndroidUtilities.rectTmp.set( + rect.left + AndroidUtilities.dp(7 - 12 - 2), + rect.bottom - AndroidUtilities.dp(12 + 2 + 9), + rect.left + AndroidUtilities.dp(8 - 2), + rect.bottom// - AndroidUtilities.dp(2 + 1) + ); + path.arcTo(AndroidUtilities.rectTmp, 0, 83, false); + AndroidUtilities.rectTmp.set(rect.right - radii[4] * 2, rect.bottom - radii[5] * 2, rect.right, rect.bottom); + path.arcTo(AndroidUtilities.rectTmp, 90, -90, false); + path.lineTo(rect.right, rect.top); + path.close(); + } else { + path.addRoundRect(rect, radii, Path.Direction.CW); + } path.close(); canvas.drawPath(path, maskPaint); } else { @@ -8116,7 +8343,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate instantWidth = backgroundWidth - AndroidUtilities.dp(34); } totalHeight += AndroidUtilities.dp(46); - if (currentMessageObject.type == 12) { + if (currentMessageObject.type == MessageObject.TYPE_CONTACT) { totalHeight += AndroidUtilities.dp(14); } if (currentMessageObject.isSponsored() && hasNewLineForTime) { @@ -8246,6 +8473,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } timeX -= getExtraTimeX(); +// if (transitionParams != null && !transitionParams.shouldAnimateTimeX) { +// transitionParams.lastTimeX = timeX; +// } if ((currentMessageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { viewsLayout = new StaticLayout(currentViewsString, Theme.chat_timePaint, viewsTextWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); @@ -8282,13 +8512,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } lastSize = currentSize; - if (currentMessageObject.type == 0) { + if (currentMessageObject.type == MessageObject.TYPE_TEXT) { textY = AndroidUtilities.dp(10) + namesOffset; } if (isRoundVideo) { updatePlayingMessageProgress(); } - if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO) { + if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND) { if (currentMessageObject.isOutOwner()) { seekBarX = layoutWidth - backgroundWidth + AndroidUtilities.dp(57); buttonX = layoutWidth - backgroundWidth + AndroidUtilities.dp(14); @@ -8309,13 +8539,66 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate buttonX += AndroidUtilities.dp(10); timeAudioX += AndroidUtilities.dp(10); } - updateSeekBarWaveformWidth(); - seekBar.setSize(backgroundWidth - AndroidUtilities.dp(72 + (hasLinkPreview ? 10 : 0)), AndroidUtilities.dp(30)); + updateSeekBarWaveformWidth(null); seekBarY = AndroidUtilities.dp(13) + namesOffset + mediaOffsetY; buttonY = AndroidUtilities.dp(13) + namesOffset + mediaOffsetY; radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(44), buttonY + AndroidUtilities.dp(44)); updatePlayingMessageProgress(); + + if (documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND) { + int x; + if (currentMessageObject.type == MessageObject.TYPE_TEXT && (hasLinkPreview || hasGamePreview || hasInvoicePreview)) { + int linkX; + if (hasGamePreview) { + linkX = unmovedTextX - AndroidUtilities.dp(10); + } else if (hasInvoicePreview) { + linkX = unmovedTextX + AndroidUtilities.dp(1); + } else { + linkX = unmovedTextX + AndroidUtilities.dp(1); + } + if (isSmallImage) { + x = linkX + backgroundWidth - AndroidUtilities.dp(81); + } else { + x = linkX + (hasInvoicePreview ? -AndroidUtilities.dp(6.3f) : AndroidUtilities.dp(10)); + } + } else { + if (currentMessageObject.isOutOwner()) { + if (mediaBackground) { + x = layoutWidth - backgroundWidth - AndroidUtilities.dp(3); + } else { + x = layoutWidth - backgroundWidth + AndroidUtilities.dp(6); + } + } else { + if (isChat && isAvatarVisible && (!isPlayingRound || currentMessageObject.isVoiceTranscriptionOpen())) { + x = AndroidUtilities.dp(63); + } else { + x = AndroidUtilities.dp(15); + } + if (currentPosition != null && !currentPosition.edge) { + x -= AndroidUtilities.dp(10); + } + } + } + if (currentPosition != null) { + if ((currentPosition.flags & MessageObject.POSITION_FLAG_LEFT) == 0) { + x -= AndroidUtilities.dp(2); + } + if (currentPosition.leftSpanOffset != 0) { + x += (int) Math.ceil(currentPosition.leftSpanOffset / 1000.0f * getGroupPhotosWidth()); + } + } + if (currentMessageObject.type != MessageObject.TYPE_TEXT) { + x -= AndroidUtilities.dp(2); + } + if (currentMessageObject.isVoiceTranscriptionOpen()) { + x += AndroidUtilities.dp(10); + } + if (!transitionParams.imageChangeBoundsTransition || transitionParams.updatePhotoImageX) { + transitionParams.updatePhotoImageX = false; + photoImage.setImageCoords((float) x, photoImage.getImageY(), photoImage.getImageWidth(), photoImage.getImageHeight()); + } + } } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { if (currentMessageObject.isOutOwner()) { seekBarX = layoutWidth - backgroundWidth + AndroidUtilities.dp(56); @@ -8337,7 +8620,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate buttonX += AndroidUtilities.dp(10); timeAudioX += AndroidUtilities.dp(10); } - seekBar.setSize(backgroundWidth - AndroidUtilities.dp(65 + (hasLinkPreview ? 10 : 0)), AndroidUtilities.dp(30)); + updateSeekBarWaveformWidth(null); seekBarY = AndroidUtilities.dp(29) + namesOffset + mediaOffsetY; buttonY = AndroidUtilities.dp(13) + namesOffset + mediaOffsetY; radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(44), buttonY + AndroidUtilities.dp(44)); @@ -8359,22 +8642,19 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate buttonY = AndroidUtilities.dp(13) + namesOffset + mediaOffsetY; radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(44), buttonY + AndroidUtilities.dp(44)); photoImage.setImageCoords(buttonX - AndroidUtilities.dp(10), buttonY - AndroidUtilities.dp(10), photoImage.getImageWidth(), photoImage.getImageHeight()); - } else if (currentMessageObject.type == 12) { + } else if (currentMessageObject.type == MessageObject.TYPE_CONTACT) { int x; - if (currentMessageObject.isOutOwner()) { x = layoutWidth - backgroundWidth + AndroidUtilities.dp(14); + } else if (isChat && !isThreadPost && currentMessageObject.needDrawAvatar()) { + x = AndroidUtilities.dp(72); } else { - if (isChat && !isThreadPost && currentMessageObject.needDrawAvatar()) { - x = AndroidUtilities.dp(72); - } else { - x = AndroidUtilities.dp(23); - } + x = AndroidUtilities.dp(23); } photoImage.setImageCoords(x, AndroidUtilities.dp(13) + namesOffset, AndroidUtilities.dp(44), AndroidUtilities.dp(44)); } else { int x; - if (currentMessageObject.type == 0 && (hasLinkPreview || hasGamePreview || hasInvoicePreview)) { + if (currentMessageObject.type == MessageObject.TYPE_TEXT && (hasLinkPreview || hasGamePreview || hasInvoicePreview)) { int linkX; if (hasGamePreview) { linkX = unmovedTextX - AndroidUtilities.dp(10); @@ -8414,7 +8694,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate x += (int) Math.ceil(currentPosition.leftSpanOffset / 1000.0f * getGroupPhotosWidth()); } } - if (currentMessageObject.type != 0) { + if (currentMessageObject.type != MessageObject.TYPE_TEXT) { x -= AndroidUtilities.dp(2); } if (!transitionParams.imageChangeBoundsTransition || transitionParams.updatePhotoImageX) { @@ -8434,7 +8714,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } public boolean needDelayRoundProgressDraw() { - return (documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) && currentMessageObject.type != 5 && MediaController.getInstance().isPlayingMessage(currentMessageObject); + return (documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) && currentMessageObject.type != MessageObject.TYPE_ROUND_VIDEO && MediaController.getInstance().isPlayingMessage(currentMessageObject); } public void drawRoundProgress(Canvas canvas) { @@ -8488,9 +8768,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate rect.set(photoImage.getImageX() + AndroidUtilities.dpf2(1.5f) + inset, photoImage.getImageY() + AndroidUtilities.dpf2(1.5f) + inset, photoImage.getImageX2() - AndroidUtilities.dpf2(1.5f) - inset, photoImage.getImageY2() - AndroidUtilities.dpf2(1.5f) - inset); int oldAplha = -1; - if (roundProgressAlpha != 1f) { + if (roundProgressAlpha != 1f || !hasLinkPreview && getVideoTranscriptionProgress() > 0) { oldAplha = Theme.chat_radialProgressPaint.getAlpha(); - Theme.chat_radialProgressPaint.setAlpha((int) (roundProgressAlpha * oldAplha)); + Theme.chat_radialProgressPaint.setAlpha((int) (roundProgressAlpha * (!hasLinkPreview ? 1f - getVideoTranscriptionProgress() : 1f) * oldAplha)); } if (videoForwardDrawable != null && videoForwardDrawable.isAnimating()) { @@ -8511,7 +8791,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate seekbarRoundX = (float) (rect.centerX() + Math.sin(Math.toRadians(-360 * audioProgress + 180)) * radius); seekbarRoundY = (float) (rect.centerY() + Math.cos(Math.toRadians(-360 * audioProgress + 180)) * radius); Theme.chat_radialProgressPausedSeekbarPaint.setColor(Color.WHITE); - Theme.chat_radialProgressPausedSeekbarPaint.setAlpha((int) (255 * Math.min(1f, pauseProgress))); + Theme.chat_radialProgressPausedSeekbarPaint.setAlpha((int) (255 * Math.min(1f, pauseProgress) * (!hasLinkPreview ? 1f - getVideoTranscriptionProgress() : 1))); canvas.drawCircle(seekbarRoundX, seekbarRoundY, AndroidUtilities.dp(3) + AndroidUtilities.dp(5) * pauseProgress + AndroidUtilities.dp(3) * roundToPauseProgress2, Theme.chat_radialProgressPausedSeekbarPaint); } if (roundSeekbarOutAlpha != 0f) { @@ -8608,7 +8888,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } private void drawContent(Canvas canvas) { - boolean newPart = needNewVisiblePart && currentMessageObject.type == 0, hasSpoilers = hasSpoilers(); + boolean newPart = needNewVisiblePart && currentMessageObject.type == MessageObject.TYPE_TEXT, hasSpoilers = hasSpoilers(); if (newPart || hasSpoilers) { getLocalVisibleRect(scrollRect); if (hasSpoilers) { @@ -8627,7 +8907,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate buttonY = (transitionParams.animateFromButtonY * (1f - transitionParams.animateChangeProgress) + this.buttonY * (transitionParams.animateChangeProgress)); radialProgress.setProgressRect((int) buttonX, (int) buttonY, (int) buttonX + AndroidUtilities.dp(44), (int) buttonY + AndroidUtilities.dp(44)); } - updateSeekBarWaveformWidth(); + updateSeekBarWaveformWidth(canvas); forceNotDrawTime = currentMessagesGroup != null; photoImage.setPressed((isHighlightedAnimated || isHighlighted) && currentPosition != null ? 2 : 0); photoImage.setVisible(!PhotoViewer.isShowingImage(currentMessageObject) && !SecretMediaViewer.getInstance().isShowingImage(currentMessageObject), false); @@ -8660,7 +8940,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate imageDrawn = false; radialProgress.setCircleCrossfadeColor(null, 0.0f, 1.0f); - if (currentMessageObject.type == 0 || currentMessageObject.type == MessageObject.TYPE_EMOJIS) { + if (currentMessageObject.type == MessageObject.TYPE_TEXT || currentMessageObject.type == MessageObject.TYPE_EMOJIS) { if (currentMessageObject.isOutOwner()) { textX = getCurrentBackgroundLeft() + AndroidUtilities.dp(11) + getExtraTextX(); } else { @@ -8730,16 +9010,21 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } drawTime = true; } else if (drawPhotoImage) { - if (isRoundVideo && MediaController.getInstance().isPlayingMessage(currentMessageObject) && MediaController.getInstance().isVideoDrawingReady() && canvas.isHardwareAccelerated()) { + float pipFloat = roundVideoPlayPipFloat.get(); + if (isRoundVideo && ( + MediaController.getInstance().isPlayingMessage(currentMessageObject) && MediaController.getInstance().isVideoDrawingReady() && canvas.isHardwareAccelerated() && (currentMessageObject == null || !currentMessageObject.isVoiceTranscriptionOpen() || pipFloat >= 1) + )) { imageDrawn = true; drawTime = true; } else { if (currentMessageObject.type == MessageObject.TYPE_ROUND_VIDEO && Theme.chat_roundVideoShadow != null) { float x = photoImage.getImageX() - AndroidUtilities.dp(3); float y = photoImage.getImageY() - AndroidUtilities.dp(2); - Theme.chat_roundVideoShadow.setAlpha(255/*(int) (photoImage.getCurrentAlpha() * 255)*/); + int wasAlpha = 255; // Theme.chat_roundVideoShadow.getAlpha(); + Theme.chat_roundVideoShadow.setAlpha((int) (255 * (1f - getVideoTranscriptionProgress()))); Theme.chat_roundVideoShadow.setBounds((int) x, (int) y, (int) (x + photoImage.getImageWidth() + AndroidUtilities.dp(6)), (int) (y + photoImage.getImageHeight() + AndroidUtilities.dp(6))); Theme.chat_roundVideoShadow.draw(canvas); + Theme.chat_roundVideoShadow.setAlpha(wasAlpha); if (!photoImage.hasBitmapImage() || photoImage.getCurrentAlpha() != 1) { Theme.chat_docBackPaint.setColor(getThemedColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outBubble : Theme.key_chat_inBubble)); @@ -8804,6 +9089,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate photoImage.setSkipUpdateFrame(false); } } + if (isRoundVideo && currentMessageObject.isVoiceTranscriptionOpen()) { + if (roundVideoPipPaint == null) { + roundVideoPipPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + } + roundVideoPipPaint.setColor(getThemedColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outBubble : Theme.key_chat_inBubble)); + canvas.drawCircle(photoImage.getCenterX(), photoImage.getCenterY(), photoImage.getImageWidth() / 2f * pipFloat, roundVideoPipPaint); + } boolean drawTimeOld = drawTime; boolean groupPhotoVisible = photoImage.getVisible(); drawTime = groupPhotoVisible || currentMessageObject.shouldDrawReactionsInLayout() && currentMessageObject.hasReactions(); @@ -8862,23 +9154,6 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate Theme.chat_msgMediaMenuDrawable.draw(canvas); Theme.chat_msgMediaMenuDrawable.setAlpha(oldAlpha); } - } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND) { - if (durationLayout != null) { - boolean playing = MediaController.getInstance().isPlayingMessage(currentMessageObject); - if (playing || roundProgressAlpha != 0) { - if (playing) { - roundProgressAlpha = 1f; - } else { - roundProgressAlpha -= 16 / 150f; - if (roundProgressAlpha < 0) { - roundProgressAlpha = 0; - } else { - invalidate(); - } - } - drawRoundProgress(canvas); - } - } } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { if (currentMessageObject.isOutOwner()) { Theme.chat_audioTitlePaint.setColor(getThemedColor(Theme.key_chat_outAudioTitleText)); @@ -8953,7 +9228,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { menuDrawable = isDrawSelectionBackground() ? Theme.chat_msgInMenuSelectedDrawable : Theme.chat_msgInMenuDrawable; } - setDrawableBounds(menuDrawable, otherX = (int) buttonX + backgroundWidth - AndroidUtilities.dp(currentMessageObject.type == 0 ? 58 : 48), otherY = (int) buttonY - AndroidUtilities.dp(2)); + setDrawableBounds(menuDrawable, otherX = (int) buttonX + backgroundWidth - AndroidUtilities.dp(currentMessageObject.type == MessageObject.TYPE_TEXT ? 58 : 48), otherY = (int) buttonY - AndroidUtilities.dp(2)); if (transitionParams.animateChangeProgress != 1f && transitionParams.animateShouldDrawMenuDrawable) { menuDrawable.setAlpha((int) (255 * transitionParams.animateChangeProgress)); } @@ -8962,7 +9237,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate menuDrawable.setAlpha(255); } } - } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO) { + } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND) { if (currentMessageObject.isOutOwner()) { Theme.chat_audioTimePaint.setColor(getThemedColor(isDrawSelectionBackground() ? Theme.key_chat_outAudioDurationSelectedText : Theme.key_chat_outAudioDurationText)); radialProgress.setProgressColor(getThemedColor(isDrawSelectionBackground() || buttonPressed != 0 ? Theme.key_chat_outAudioSelectedProgress : Theme.key_chat_outAudioProgress)); @@ -8977,59 +9252,177 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate audioVisualizerDrawable = Theme.getAnimatedOutAudioVisualizerDrawable(currentMessageObject); } - if (audioVisualizerDrawable != null) { + if (audioVisualizerDrawable != null && !(documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND && hasLinkPreview)) { audioVisualizerDrawable.setParentView(this); - audioVisualizerDrawable.draw(canvas, buttonX + AndroidUtilities.dp(22), buttonY + AndroidUtilities.dp(22), currentMessageObject.isOutOwner(), resourcesProvider); + audioVisualizerDrawable.draw(canvas, buttonX + AndroidUtilities.dp(22), buttonY + AndroidUtilities.dp(22), currentMessageObject.isOutOwner(), documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND ? getVideoTranscriptionProgress() : 1, resourcesProvider); } - if (!enterTransitionInProgress) { + if (!enterTransitionInProgress && documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO) { radialProgress.setBackgroundDrawable(isDrawSelectionBackground() ? currentBackgroundSelectedDrawable : currentBackgroundDrawable); radialProgress.draw(canvas); } int seekBarX = this.seekBarX; int timeAudioX = this.timeAudioX; + int yoffset = 0; if (transitionParams.animateButton) { int offset = this.buttonX - (int) (transitionParams.animateFromButtonX * (1f - transitionParams.animateChangeProgress) + this.buttonX * (transitionParams.animateChangeProgress)); seekBarX -= offset; timeAudioX -= offset; } + if (isRoundVideo && currentMessageObject.isOutOwner()) { + seekBarX = getCurrentBackgroundLeft() + AndroidUtilities.dp(57); + timeAudioX = getCurrentBackgroundLeft() + AndroidUtilities.dp(67); + } + if (isRoundVideo) { + yoffset += (1f - getVideoTranscriptionProgress()) * AndroidUtilities.roundMessageSize; + } canvas.save(); - if (useSeekBarWaveform) { - canvas.translate(seekBarX + AndroidUtilities.dp(13), seekBarY); - seekBarWaveform.draw(canvas, this); - } else { - canvas.translate(seekBarX, seekBarY); - seekBar.draw(canvas); + if (!(documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND && hasLinkPreview)) { + if (useSeekBarWaveform) { + canvas.translate(seekBarX + AndroidUtilities.dp(13) - seekBarWaveformTranslateX, seekBarY + yoffset); + seekBarWaveform.setAlpha(isRoundVideo ? CubicBezierInterpolator.EASE_IN.getInterpolation(getVideoTranscriptionProgress()) : 1f); + seekBarWaveform.draw(canvas, this); + } else { + canvas.translate(seekBarX, seekBarY + yoffset); + seekBar.setAlpha(isRoundVideo ? CubicBezierInterpolator.EASE_IN.getInterpolation(getVideoTranscriptionProgress()) : 1f); + seekBar.draw(canvas); + } } canvas.restore(); - if (useTranscribeButton) { - canvas.save(); - int backgroundWidth = this.backgroundWidth; - if (transitionParams.animateBackgroundBoundsInner && documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO) { - backgroundWidth = (int) (this.backgroundWidth - transitionParams.deltaLeft + transitionParams.deltaRight); + float transcribeAlpha = getUseTranscribeButtonProgress(); + float playingRoundAlpha = 0; + if (transitionParams.animatePlayingRound) { + if (isPlayingRound) { + playingRoundAlpha = transitionParams.animateChangeProgress; + } else { + playingRoundAlpha = 1f - transitionParams.animateChangeProgress; } + } else if (isPlayingRound) { + playingRoundAlpha = 1f; + } + if (!wasTranscriptionOpen) { + transcribeAlpha = AndroidUtilities.lerp(transcribeAlpha * (1f - playingRoundAlpha), transcribeAlpha, getVideoTranscriptionProgress()); + } + if (transcribeAlpha > 0 && !(documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND && hasLinkPreview)) { + canvas.save(); + int backgroundWidth = getCurrentBackgroundRight() - getCurrentBackgroundLeft() + AndroidUtilities.dp(pinnedBottom ? 6 : 0); int seekBarWidth = backgroundWidth - AndroidUtilities.dp(92 + (hasLinkPreview ? 10 : 0) + 36); - canvas.translate(transcribeX = seekBarX + AndroidUtilities.dp(13 + 8) + seekBarWidth, transcribeY = seekBarY); if (transcribeButton == null) { - transcribeButton = new TranscribeButton(this, seekBarWaveform); - transcribeButton.setOpen(currentMessageObject.messageOwner != null && currentMessageObject.messageOwner.voiceTranscriptionOpen && currentMessageObject.messageOwner.voiceTranscriptionFinal, false); + transcribeButton = new TranscribeButton(this, seekBarWaveform) { + @Override + public void drawGradientBackground(Canvas canvas, Rect bounds, float alpha) { + Paint paint; + if (currentMessageObject.shouldDrawWithoutBackground()) { + paint = getThemedPaint(Theme.key_paint_chatActionBackground); + } else { + paint = getThemedPaint(Theme.key_paint_chatTimeBackground); + } + int oldAlpha = paint.getAlpha(); + paint.setAlpha((int) (oldAlpha * alpha)); + + applyServiceShaderMatrix(); + canvas.drawRect(bounds, paint); + if (paint == getThemedPaint(Theme.key_paint_chatActionBackground) && hasGradientService()) { + int oldAlpha2 = Theme.chat_actionBackgroundGradientDarkenPaint.getAlpha(); + Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha((int) (oldAlpha2 * alpha)); + canvas.drawRect(bounds, Theme.chat_actionBackgroundGradientDarkenPaint); + Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha(oldAlpha2); + } + paint.setAlpha(oldAlpha); + } + + @Override + protected void onOpen() { + wasTranscriptionOpen = true; + } + }; + transcribeButton.setOpen(currentMessageObject.messageOwner != null && currentMessageObject.messageOwner.voiceTranscriptionOpen && currentMessageObject.messageOwner.voiceTranscriptionFinal && TranscribeButton.isVideoTranscriptionOpen(currentMessageObject), false); transcribeButton.setLoading(TranscribeButton.isTranscribing(currentMessageObject), false); } - transcribeButton.setColor(currentMessageObject.isOut(), getThemedColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outReactionButtonBackground : Theme.key_chat_inReactionButtonBackground), getThemedColor(Theme.key_windowBackgroundWhiteGrayText)); - transcribeButton.draw(canvas); + if (drawSideButton != 0) { + transcribeX = AndroidUtilities.lerp( + seekBarX + AndroidUtilities.dp(13 + 8) + seekBarWidth, + sideStartX, + 1f - getVideoTranscriptionProgress() + ); + transcribeY = AndroidUtilities.lerp( + seekBarY + AndroidUtilities.dp(3), + sideStartY - AndroidUtilities.dp(32 + 8), + 1f - getVideoTranscriptionProgress() + ); + } else { + boolean drawAvatar = isChat && !isThreadPost && !currentMessageObject.isOutOwner() && currentMessageObject.needDrawAvatar() || currentMessageObject.customAvatarDrawable != null; + transcribeX = AndroidUtilities.lerp( + seekBarX + AndroidUtilities.dp(13 + 8) + seekBarWidth, + currentMessageObject != null && currentMessageObject.isOutOwner() ? + getCurrentBackgroundLeft() - AndroidUtilities.dp(32 + 8) + AndroidUtilities.dp(28) * playingRoundAlpha : + getCurrentBackgroundRight() + AndroidUtilities.dp(8) - AndroidUtilities.dp(40) * playingRoundAlpha, + 1f - getVideoTranscriptionProgress() + ); + float y = layoutHeight + transitionParams.deltaBottom - AndroidUtilities.dp(28 - (drawPinnedBottom ? 2 : 0)); + if (!reactionsLayoutInBubble.isEmpty) { + y -= reactionsLayoutInBubble.getCurrentTotalHeight(transitionParams.animateChangeProgress); + } + y = AndroidUtilities.lerp(y, AndroidUtilities.dp(44) + namesOffset + mediaOffsetY - AndroidUtilities.dp(1.7f), getVideoTranscriptionProgress()); + y += AndroidUtilities.dp(1.7f); + transcribeY = AndroidUtilities.lerp( + seekBarY + AndroidUtilities.dp(3), + y - AndroidUtilities.dp(12) - (currentMessageObject.isOutOwner() ? 0 : AndroidUtilities.dp(28) * playingRoundAlpha), + 1f - getVideoTranscriptionProgress() + ); + } + transcribeButton.setBounds( + (int) transcribeX, + (int) transcribeY, + AndroidUtilities.lerp(AndroidUtilities.dp(30), AndroidUtilities.dp(32), 1f - getVideoTranscriptionProgress()), + AndroidUtilities.lerp(AndroidUtilities.dp(24), AndroidUtilities.dp(32), 1f - getVideoTranscriptionProgress()), + AndroidUtilities.lerp(AndroidUtilities.dp(8), AndroidUtilities.dp(16), 1f - getVideoTranscriptionProgress()) + ); + transcribeButton.setColor( + ColorUtils.blendARGB( + getThemedColor(Theme.key_chat_serviceText), + getThemedColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outReactionButtonBackground : Theme.key_chat_inReactionButtonBackground), + documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO ? 1 : getVideoTranscriptionProgress() + ), + getThemedColor(Theme.key_windowBackgroundWhiteGrayText), + currentMessageObject.isOut(), + documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO ? 0 : 1f - getVideoTranscriptionProgress() + ); + transcribeButton.draw(canvas, transcribeAlpha); canvas.restore(); } - canvas.save(); - canvas.translate(timeAudioX, AndroidUtilities.dp(44) + namesOffset + mediaOffsetY); - durationLayout.draw(canvas); - canvas.restore(); + if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO) { + canvas.save(); + canvas.translate(timeAudioX, AndroidUtilities.dp(44) + namesOffset + mediaOffsetY + yoffset); + durationLayout.draw(canvas); + canvas.restore(); - if (currentMessageObject.type != 0 && currentMessageObject.isContentUnread()) { - Theme.chat_docBackPaint.setColor(getThemedColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outVoiceSeekbarFill : Theme.key_chat_inVoiceSeekbarFill)); - canvas.drawCircle(timeAudioX + timeWidthAudio + AndroidUtilities.dp(6), AndroidUtilities.dp(51) + namesOffset + mediaOffsetY, AndroidUtilities.dp(3), Theme.chat_docBackPaint); + if (currentMessageObject.type != MessageObject.TYPE_TEXT && currentMessageObject.isContentUnread()) { + Theme.chat_docBackPaint.setColor(getThemedColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outVoiceSeekbarFill : Theme.key_chat_inVoiceSeekbarFill)); + canvas.drawCircle(timeAudioX + timeWidthAudio + AndroidUtilities.dp(6), AndroidUtilities.dp(51) + namesOffset + mediaOffsetY, AndroidUtilities.dp(3), Theme.chat_docBackPaint); + } + } + + if (documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND) { + if (durationLayout != null) { + boolean playing = MediaController.getInstance().isPlayingMessage(currentMessageObject); + if (playing || roundProgressAlpha != 0) { + if (playing) { + roundProgressAlpha = 1f; + } else { + roundProgressAlpha -= 16 / 150f; + if (roundProgressAlpha < 0) { + roundProgressAlpha = 0; + } else { + invalidate(); + } + } + drawRoundProgress(canvas); + } + } } } @@ -9043,7 +9436,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (hasOldCaptionPreview) { int linkX; - if (currentMessageObject.type == MessageObject.TYPE_PHOTO || currentMessageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || currentMessageObject.type == 8) { + if ( + currentMessageObject.type == MessageObject.TYPE_PHOTO || + currentMessageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW || + documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || + currentMessageObject.type == MessageObject.TYPE_GIF + ) { linkX = (int) (photoImage.getImageX() + AndroidUtilities.dp(5)); } else { linkX = backgroundDrawableLeft + AndroidUtilities.dp(currentMessageObject.isOutOwner() ? 11 : 17); @@ -9107,7 +9505,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate int titleY; int subtitleY; if (drawPhotoImage) { - if (currentMessageObject.type == 0) { + if (currentMessageObject.type == MessageObject.TYPE_TEXT) { setDrawableBounds(menuDrawable, otherX = (int) (photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(56)), otherY = (int) (photoImage.getImageY() + AndroidUtilities.dp(4))); } else { setDrawableBounds(menuDrawable, otherX = (int) (photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(40)), otherY = (int) (photoImage.getImageY() + AndroidUtilities.dp(4))); @@ -9151,7 +9549,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } } else { - setDrawableBounds(menuDrawable, otherX = (int) buttonX + backgroundWidth - AndroidUtilities.dp(currentMessageObject.type == 0 ? 58 : 48), otherY = (int) buttonY - AndroidUtilities.dp(2)); + setDrawableBounds(menuDrawable, otherX = (int) buttonX + backgroundWidth - AndroidUtilities.dp(currentMessageObject.type == MessageObject.TYPE_TEXT ? 58 : 48), otherY = (int) buttonY - AndroidUtilities.dp(2)); x = buttonX + AndroidUtilities.dp(53); titleY = (int) buttonY + AndroidUtilities.dp(4); subtitleY = (int) buttonY + AndroidUtilities.dp(27); @@ -9215,6 +9613,18 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate transitionParams.recordDrawingState(); } + private float getUseTranscribeButtonProgress() { + if (transitionParams.animateUseTranscribeButton) { + if (useTranscribeButton) { + return transitionParams.animateChangeProgress; + } else { + return 1f - transitionParams.animateChangeProgress; + } + } else { + return useTranscribeButton ? 1 : 0; + } + } + private void updateReactionLayoutPosition() { if (!reactionsLayoutInBubble.isEmpty && (currentPosition == null || ((currentPosition.flags & MessageObject.POSITION_FLAG_BOTTOM) != 0 && (currentPosition.flags & MessageObject.POSITION_FLAG_LEFT) != 0)) && !reactionsLayoutInBubble.isSmall) { if (currentMessageObject.type == MessageObject.TYPE_EMOJIS || currentMessageObject.isAnimatedEmoji() || currentMessageObject.isAnyKindOfSticker()) { @@ -9227,9 +9637,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (currentMessageObject.isOutOwner()) { reactionsLayoutInBubble.x = getCurrentBackgroundLeft() + AndroidUtilities.dp(11); } else { - reactionsLayoutInBubble.x = getCurrentBackgroundLeft() + AndroidUtilities.dp(!mediaBackground && drawPinnedBottom ? 11 : 17); - if (mediaBackground) { - reactionsLayoutInBubble.x -= AndroidUtilities.dp(9); + if (isRoundVideo) { + reactionsLayoutInBubble.x = getCurrentBackgroundLeft() + AndroidUtilities.dp(11) + (int) AndroidUtilities.dp(AndroidUtilities.lerp(0, !drawPinnedBottom ? 6 : 0, getVideoTranscriptionProgress())); + reactionsLayoutInBubble.x -= (int) ((1f - getVideoTranscriptionProgress()) * AndroidUtilities.dp(9)); + } else { + reactionsLayoutInBubble.x = getCurrentBackgroundLeft() + AndroidUtilities.dp(!mediaBackground && drawPinnedBottom ? 11 : 17); + if (mediaBackground) { + reactionsLayoutInBubble.x -= AndroidUtilities.dp(9); + } } } } @@ -9238,7 +9653,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (hasNewLineForTime) { reactionsLayoutInBubble.y -= AndroidUtilities.dp(16); } - if (captionLayout != null && ((currentMessageObject.type != 2 && !(currentMessageObject.isOut() && drawForwardedName && !drawPhotoImage) && !(currentMessageObject.type == 9 && drawPhotoImage)) || (currentPosition != null && currentMessagesGroup != null))) { + if (captionLayout != null && ((currentMessageObject.type != MessageObject.TYPE_VOICE && !(currentMessageObject.isOut() && drawForwardedName && !drawPhotoImage) && !(currentMessageObject.type == MessageObject.TYPE_FILE && drawPhotoImage)) || (currentPosition != null && currentMessagesGroup != null))) { reactionsLayoutInBubble.y -= AndroidUtilities.dp(14); } reactionsLayoutInBubble.y = reactionsLayoutInBubble.y + reactionsLayoutInBubble.positionOffsetY; @@ -9554,7 +9969,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate Theme.chat_instantViewPaint.setColor(getThemedColor(Theme.key_chat_outPreviewInstantText)); backPaint.setColor(getThemedColor(Theme.key_chat_outPreviewInstantText)); } else { - instantDrawable = Theme.chat_msgInInstantDrawable; + instantDrawable = getThemedDrawable(Theme.key_drawable_msgInInstant); Theme.chat_instantViewPaint.setColor(getThemedColor(Theme.key_chat_inPreviewInstantText)); backPaint.setColor(getThemedColor(Theme.key_chat_inPreviewInstantText)); } @@ -9658,6 +10073,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate rect.set(x, y + AndroidUtilities.dp(4), x + AndroidUtilities.dp(8), y + AndroidUtilities.dp(8 + 4)); canvas.drawArc(rect, button.angle, 220, false, Theme.chat_botProgressPaint); invalidate(); + if (getParent() != null) { + ((View) getParent()).invalidate(); + } long newTime = System.currentTimeMillis(); if (Math.abs(button.lastUpdateTime - System.currentTimeMillis()) < 1000) { long delta = (newTime - button.lastUpdateTime); @@ -9734,8 +10152,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate bottom = (int) (parentHeight - getY()); } rect.set( - getCurrentBackgroundLeft(), top, - currentBackgroundDrawable.getBounds().right, bottom + getCurrentBackgroundLeft(), top, + currentBackgroundDrawable.getBounds().right, bottom ); } else { rect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); @@ -9806,7 +10224,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } public void updateCaptionLayout() { - if (currentMessageObject.type == MessageObject.TYPE_PHOTO || currentMessageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || currentMessageObject.type == 8) { + if ( + currentMessageObject.type == MessageObject.TYPE_PHOTO || + currentMessageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW || + documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || + currentMessageObject.type == MessageObject.TYPE_GIF + ) { float x, y, h; if (transitionParams.imageChangeBoundsTransition) { x = transitionParams.animateToImageX; @@ -9826,7 +10249,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate captionY -= AndroidUtilities.dp(shouldDrawTimeOnMedia() ? 41.3f : 43); } } else { - captionX = backgroundDrawableLeft + AndroidUtilities.dp(currentMessageObject.isOutOwner() || mediaBackground || drawPinnedBottom ? 11 : 17) + captionOffsetX; + if (isRoundVideo) { + captionX = getBackgroundDrawableLeft() + AndroidUtilities.dp(11 + (currentMessageObject.isOutOwner() ? 0 : 6)); + } else { + captionX = backgroundDrawableLeft + AndroidUtilities.dp(currentMessageObject.isOutOwner() || mediaBackground || drawPinnedBottom ? 11 : 17) + captionOffsetX; + } captionY = totalHeight - captionHeight - AndroidUtilities.dp(drawPinnedTop ? 9 : 10); if (drawCommentButton && drawSideButton != 3) { captionY -= AndroidUtilities.dp(shouldDrawTimeOnMedia() ? 41.3f : 43); @@ -9865,10 +10292,20 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } private int getIconForCurrentState() { - if (currentMessageObject != null && currentMessageObject.hasExtendedMedia()) { + if (currentMessageObject == null || currentMessageObject != null && currentMessageObject.hasExtendedMedia()) { return MediaActionDrawable.ICON_NONE; } - if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { + if (documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND && currentMessageObject.isVoiceTranscriptionOpen()) { + if (currentMessageObject.isOutOwner()) { + radialProgress.setColors(Theme.key_chat_outLoader, Theme.key_chat_outLoaderSelected, Theme.key_chat_outMediaIcon, Theme.key_chat_outMediaIconSelected); + } else { + radialProgress.setColors(Theme.key_chat_inLoader, Theme.key_chat_inLoaderSelected, Theme.key_chat_inMediaIcon, Theme.key_chat_inMediaIconSelected); + } + if (buttonState == 1 || buttonState == 4) { + return MediaActionDrawable.ICON_PAUSE; + } + return MediaActionDrawable.ICON_PLAY; + } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { if (currentMessageObject.isOutOwner()) { radialProgress.setColors(Theme.key_chat_outLoader, Theme.key_chat_outLoaderSelected, Theme.key_chat_outMediaIcon, Theme.key_chat_outMediaIconSelected); } else { @@ -9947,7 +10384,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate maxWidth = Math.min(getParentWidth(), AndroidUtilities.displaySize.y); } } - if (isPlayingRound) { + if (isPlayingRound && (currentMessageObject == null || !currentMessageObject.isVoiceTranscriptionOpen())) { int backgroundWidthLocal = backgroundWidth - (AndroidUtilities.roundPlayingMessageSize - AndroidUtilities.roundMessageSize); return maxWidth - backgroundWidthLocal - AndroidUtilities.dp(57); } @@ -9987,14 +10424,22 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate drawRadialCheckBackground = false; String fileName = null; boolean fileExists = false; - if (currentMessageObject.type == MessageObject.TYPE_PHOTO || currentMessageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW) { + if (currentMessageObject.type == MessageObject.TYPE_PHOTO) { if (currentPhotoObject == null) { radialProgress.setIcon(MediaActionDrawable.ICON_NONE, ifSame, animated); return; } fileName = FileLoader.getAttachFileName(currentPhotoObject); fileExists = currentMessageObject.mediaExists; - } else if (currentMessageObject.type == 8 || documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || documentAttachType == DOCUMENT_ATTACH_TYPE_WALLPAPER || currentMessageObject.type == 9 || documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { + } else if ( + currentMessageObject.type == MessageObject.TYPE_GIF || + documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND || + documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || + documentAttachType == DOCUMENT_ATTACH_TYPE_WALLPAPER || + currentMessageObject.type == MessageObject.TYPE_FILE || + documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || + documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC + ) { if (currentMessageObject.useCustomPhoto) { buttonState = 1; radialProgress.setIcon(getIconForCurrentState(), ifSame, animated); @@ -10037,7 +10482,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } boolean fromBot = currentMessageObject.messageOwner.params != null && currentMessageObject.messageOwner.params.containsKey("query_id"); - if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { + if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC || documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND && currentMessageObject != null && currentMessageObject.isVoiceTranscriptionOpen()) { if (currentMessageObject.isOut() && (currentMessageObject.isSending() && !currentMessageObject.isForwarded() || currentMessageObject.isEditing() && currentMessageObject.isEditingMedia()) || currentMessageObject.isSendError() && fromBot) { if (!TextUtils.isEmpty(currentMessageObject.messageOwner.attachPath)) { DownloadController.getInstance(currentAccount).addLoadingFileObserver(currentMessageObject.messageOwner.attachPath, currentMessageObject, this); @@ -10130,13 +10575,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } updatePlayingMessageProgress(); - } else if (currentMessageObject.type == 0 && - documentAttachType != DOCUMENT_ATTACH_TYPE_DOCUMENT && - documentAttachType != DOCUMENT_ATTACH_TYPE_GIF && - documentAttachType != DOCUMENT_ATTACH_TYPE_ROUND && - documentAttachType != DOCUMENT_ATTACH_TYPE_VIDEO && - documentAttachType != DOCUMENT_ATTACH_TYPE_WALLPAPER && - documentAttachType != DOCUMENT_ATTACH_TYPE_THEME) { + } else if ( + currentMessageObject.type == MessageObject.TYPE_TEXT && + documentAttachType != DOCUMENT_ATTACH_TYPE_DOCUMENT && + documentAttachType != DOCUMENT_ATTACH_TYPE_GIF && + documentAttachType != DOCUMENT_ATTACH_TYPE_VIDEO && + documentAttachType != DOCUMENT_ATTACH_TYPE_WALLPAPER && + documentAttachType != DOCUMENT_ATTACH_TYPE_THEME + ) { if (currentPhotoObject == null || !drawImageButton) { return; } @@ -10321,7 +10767,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { buttonState = 0; } - boolean hasDocLayout = currentMessageObject.type == MessageObject.TYPE_VIDEO || currentMessageObject.type == 8 || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO; + boolean hasDocLayout = currentMessageObject.type == MessageObject.TYPE_VIDEO || currentMessageObject.type == MessageObject.TYPE_GIF || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO; boolean fullWidth = true; if (currentPosition != null) { int mask = MessageObject.POSITION_FLAG_LEFT | MessageObject.POSITION_FLAG_RIGHT; @@ -10348,7 +10794,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { createLoadingProgressLayout(documentAttach); } - boolean hasDocLayout = currentMessageObject.type == MessageObject.TYPE_VIDEO || currentMessageObject.type == 8 || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO; + boolean hasDocLayout = currentMessageObject.type == MessageObject.TYPE_VIDEO || currentMessageObject.type == MessageObject.TYPE_GIF || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO; boolean fullWidth = true; if (currentPosition != null) { int mask = MessageObject.POSITION_FLAG_LEFT | MessageObject.POSITION_FLAG_RIGHT; @@ -10389,7 +10835,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { FileLoader.getInstance(currentAccount).loadFile(documentAttach, currentMessageObject, FileLoader.PRIORITY_NORMAL_UP, 0); currentMessageObject.loadingCancelled = false; - } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) { + } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND) { createLoadingProgressLayout(documentAttach); FileLoader.getInstance(currentAccount).loadFile(documentAttach, currentMessageObject, FileLoader.PRIORITY_NORMAL_UP, currentMessageObject.shouldEncryptPhotoOrVideo() ? 2 : 0); currentMessageObject.loadingCancelled = false; @@ -10397,7 +10843,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate radialProgress.setMiniIcon(getMiniIconForCurrentState(), false, true); invalidate(); } else if (miniButtonState == 1) { - if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { + if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC || documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND) { if (MediaController.getInstance().isPlayingMessage(currentMessageObject)) { MediaController.getInstance().cleanupPlayer(true, true); } @@ -10415,12 +10861,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate currentMessageObject.putInDownloadsStore = true; } if (buttonState == 0 && (!drawVideoImageButton || video)) { - if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { + if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC || documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND && currentMessageObject != null && currentMessageObject.isVoiceTranscriptionOpen()) { if (miniButtonState == 0) { FileLoader.getInstance(currentAccount).loadFile(documentAttach, currentMessageObject, FileLoader.PRIORITY_NORMAL_UP, 0); currentMessageObject.loadingCancelled = false; } - if (delegate.needPlayMessage(currentMessageObject)) { + if (delegate.needPlayMessage(currentMessageObject, false)) { if (hasMiniProgress == 2 && miniButtonState != 1) { miniButtonState = 1; radialProgress.setProgress(0, false); @@ -10449,7 +10895,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (currentMessageObject.type == MessageObject.TYPE_PHOTO || currentMessageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW) { photoImage.setForceLoading(true); photoImage.setImage(ImageLocation.getForObject(currentPhotoObject, photoParentObject), currentPhotoFilter, ImageLocation.getForObject(currentPhotoObjectThumb, photoParentObject), currentPhotoFilterThumb, currentPhotoObjectThumbStripped, currentPhotoObject.size, null, currentMessageObject, currentMessageObject.shouldEncryptPhotoOrVideo() ? 2 : 0); - } else if (currentMessageObject.type == 8) { + } else if (currentMessageObject.type == MessageObject.TYPE_GIF) { FileLoader.getInstance(currentAccount).loadFile(documentAttach, currentMessageObject, FileLoader.PRIORITY_NORMAL_UP, 0); if (currentMessageObject.loadedFileSize > 0) { createLoadingProgressLayout(documentAttach); @@ -10463,7 +10909,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate photoImage.setForceLoading(true); photoImage.setImage(ImageLocation.getForDocument(document), null, ImageLocation.getForObject(thumb, document), thumbFilter, document.size, null, currentMessageObject, 0); } - } else if (currentMessageObject.type == 9) { + wouldBeInPip = true; + ChatMessageCell.this.invalidate(); + } else if (currentMessageObject.type == MessageObject.TYPE_FILE) { FileLoader.getInstance(currentAccount).loadFile(documentAttach, currentMessageObject, FileLoader.PRIORITY_NORMAL_UP, 0); if (currentMessageObject.loadedFileSize > 0) { createLoadingProgressLayout(documentAttach); @@ -10473,7 +10921,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (currentMessageObject.loadedFileSize > 0) { createLoadingProgressLayout(currentMessageObject.getDocument()); } - } else if (currentMessageObject.type == 0 && documentAttachType != DOCUMENT_ATTACH_TYPE_NONE) { + } else if (currentMessageObject.type == MessageObject.TYPE_TEXT && documentAttachType != DOCUMENT_ATTACH_TYPE_NONE) { if (documentAttachType == DOCUMENT_ATTACH_TYPE_GIF) { photoImage.setForceLoading(true); photoImage.setImage(ImageLocation.getForDocument(documentAttach), null, ImageLocation.getForDocument(currentPhotoObject, documentAttach), currentPhotoFilterThumb, documentAttach.size, null, currentMessageObject, 0); @@ -10501,7 +10949,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } else if (buttonState == 1 && (!drawVideoImageButton || video)) { photoImage.setForceLoading(false); - if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { + if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC || documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND && currentMessageObject != null && currentMessageObject.isVoiceTranscriptionOpen()) { boolean result = MediaController.getInstance().pauseMessage(currentMessageObject); if (result) { buttonState = 0; @@ -10515,12 +10963,23 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } else { currentMessageObject.loadingCancelled = true; - if (documentAttachType == DOCUMENT_ATTACH_TYPE_GIF || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT || documentAttachType == DOCUMENT_ATTACH_TYPE_WALLPAPER) { + if ( + documentAttachType == DOCUMENT_ATTACH_TYPE_GIF || + documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || + documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT || + documentAttachType == DOCUMENT_ATTACH_TYPE_WALLPAPER + ) { FileLoader.getInstance(currentAccount).cancelLoadFile(documentAttach); - } else if (currentMessageObject.type == 0 || currentMessageObject.type == MessageObject.TYPE_PHOTO || currentMessageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW || currentMessageObject.type == 8 || currentMessageObject.type == MessageObject.TYPE_ROUND_VIDEO) { + } else if ( + currentMessageObject.type == MessageObject.TYPE_TEXT || + currentMessageObject.type == MessageObject.TYPE_PHOTO || + currentMessageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW || + currentMessageObject.type == MessageObject.TYPE_GIF || + currentMessageObject.type == MessageObject.TYPE_ROUND_VIDEO + ) { ImageLoader.getInstance().cancelForceLoadingForImageReceiver(photoImage); photoImage.cancelLoadImage(); - } else if (currentMessageObject.type == 9) { + } else if (currentMessageObject.type == MessageObject.TYPE_FILE) { FileLoader.getInstance(currentAccount).cancelLoadFile(currentMessageObject.getDocument()); } buttonState = 0; @@ -10533,7 +10992,27 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } } else if (buttonState == 2) { - if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { + if (documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND && currentMessageObject != null && currentMessageObject.isVoiceTranscriptionOpen()) { + if (miniButtonState == 0) { + FileLoader.getInstance(currentAccount).loadFile(documentAttach, currentMessageObject, FileLoader.PRIORITY_NORMAL_UP, 0); + currentMessageObject.loadingCancelled = false; + } + if (delegate.needPlayMessage(currentMessageObject, false)) { + if (hasMiniProgress == 2 && miniButtonState != 1) { + miniButtonState = 1; + radialProgress.setProgress(0, false); + radialProgress.setMiniIcon(getMiniIconForCurrentState(), false, true); + } + updatePlayingMessageProgress(); + buttonState = 1; + radialProgress.setIcon(getIconForCurrentState(), false, true); + invalidate(); + } + if (isRoundVideo) { + wouldBeInPip = true; + ChatMessageCell.this.invalidate(); + } + } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { radialProgress.setProgress(0, false); FileLoader.getInstance(currentAccount).loadFile(documentAttach, currentMessageObject, FileLoader.PRIORITY_NORMAL_UP, 0); currentMessageObject.loadingCancelled = false; @@ -10563,7 +11042,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } delegate.didPressImage(this, 0, 0); } else if (buttonState == 4) { - if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { + if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC || documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND && currentMessageObject != null && currentMessageObject.isVoiceTranscriptionOpen()) { if (currentMessageObject.isOut() && (currentMessageObject.isSending() || currentMessageObject.isEditing()) || currentMessageObject.isSendError()) { if (delegate != null && radialProgress.getIcon() != MediaActionDrawable.ICON_CHECK) { delegate.didPressCancelSendButton(this); @@ -10626,7 +11105,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate autoPlayingMedia = true; } } - if (currentMessageObject.type == 0) { + if (currentMessageObject.type == MessageObject.TYPE_TEXT) { if (!autoPlayingMedia && documentAttachType == DOCUMENT_ATTACH_TYPE_GIF && currentMessageObject.gifState != 1) { buttonState = 2; didPressButton(true, false); @@ -10652,7 +11131,17 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (setCurrentDiceValue(!memCache && !currentMessageObject.wasUnread)) { return; } - if (thumb && currentMessageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW && !currentMessageObject.mediaExists || !thumb && !currentMessageObject.mediaExists && !currentMessageObject.attachPathExists && (currentMessageObject.type == 0 && (documentAttachType == DOCUMENT_ATTACH_TYPE_WALLPAPER || documentAttachType == DOCUMENT_ATTACH_TYPE_NONE || documentAttachType == DOCUMENT_ATTACH_TYPE_STICKER) || currentMessageObject.type == MessageObject.TYPE_PHOTO)) { + if ( + thumb && currentMessageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW && !currentMessageObject.mediaExists || + !thumb && !currentMessageObject.mediaExists && !currentMessageObject.attachPathExists && ( + currentMessageObject.type == MessageObject.TYPE_TEXT && ( + documentAttachType == DOCUMENT_ATTACH_TYPE_WALLPAPER || + documentAttachType == DOCUMENT_ATTACH_TYPE_NONE || + documentAttachType == DOCUMENT_ATTACH_TYPE_STICKER + ) || + currentMessageObject.type == MessageObject.TYPE_PHOTO + ) + ) { currentMessageObject.mediaExists = true; updateButtonState(false, true, false); } @@ -10933,9 +11422,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } } - edited = edited && !currentMessageObject.isVoiceTranscriptionOpen(); if (currentMessageObject.isSponsored()) { - timeString = LocaleController.getString("SponsoredMessage", R.string.SponsoredMessage); + if (currentMessageObject.sponsoredRecommended) { + timeString = LocaleController.getString("SponsoredMessageRecommended", R.string.SponsoredMessageRecommended); + } else { + timeString = LocaleController.getString("SponsoredMessage", R.string.SponsoredMessage); + } } else if (currentMessageObject.scheduled && currentMessageObject.messageOwner.date == 0x7FFFFFFE) { timeString = ""; } else if (edited) { @@ -10959,17 +11451,19 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if ((messageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { currentViewsString = String.format("%s", LocaleController.formatShortNumber(Math.max(1, messageObject.messageOwner.views), null)); viewsTextWidth = (int) Math.ceil(Theme.chat_timePaint.measureText(currentViewsString)); - timeWidth += viewsTextWidth + Theme.chat_msgInViewsDrawable.getIntrinsicWidth() + AndroidUtilities.dp(10); + float drawableWidth = Theme.chat_msgInViewsDrawable.getIntrinsicWidth() * (Theme.chat_timePaint.getTextSize() - AndroidUtilities.dp(2)) / Theme.chat_msgInViewsDrawable.getIntrinsicHeight(); + timeWidth += viewsTextWidth + drawableWidth + AndroidUtilities.dp(10); } if (messageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW) { - String str = LocaleController.formatString(R.string.PaymentCheckoutPay, LocaleController.getInstance().formatCurrencyString(messageObject.messageOwner.media.total_amount, messageObject.messageOwner.media.currency)); + String str = LocaleController.formatString(R.string.PaymentCheckoutPay, LocaleController.getInstance().formatCurrencyString(messageObject.messageOwner.media.total_amount, messageObject.messageOwner.media.currency).toUpperCase(Locale.ROOT)); currentUnlockString = str.length() >= 2 ? str.substring(0, 1).toUpperCase(Locale.ROOT) + str.substring(1).toLowerCase(Locale.ROOT) : str; unlockTextWidth = (int) Math.ceil(Theme.chat_unlockExtendedMediaTextPaint.measureText(currentUnlockString)); } if (isChat && isMegagroup && !isThreadChat && hasReplies) { currentRepliesString = String.format("%s", LocaleController.formatShortNumber(getRepliesCount(), null)); repliesTextWidth = (int) Math.ceil(Theme.chat_timePaint.measureText(currentRepliesString)); - timeWidth += repliesTextWidth + Theme.chat_msgInRepliesDrawable.getIntrinsicWidth() + AndroidUtilities.dp(10); + float drawableWidth = Theme.chat_msgInRepliesDrawable.getIntrinsicWidth() * (Theme.chat_timePaint.getTextSize() - AndroidUtilities.dp(2)) / Theme.chat_msgInRepliesDrawable.getIntrinsicHeight(); + timeWidth += repliesTextWidth + drawableWidth + AndroidUtilities.dp(10); } else { currentRepliesString = null; } @@ -11060,6 +11554,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } private void updateCurrentUserAndChat() { + if (currentMessageObject == null) { + return; + } MessagesController messagesController = MessagesController.getInstance(currentAccount); TLRPC.MessageFwdHeader fwd_from = currentMessageObject.messageOwner.fwd_from; long currentUserId = UserConfig.getInstance(currentAccount).getClientUserId(); @@ -11100,6 +11597,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate currentChat = messagesController.getChat(currentMessageObject.messageOwner.peer_id.channel_id); } } + if (currentMessageObject != null && currentMessageObject.getChatId() != 0 && currentMessageObject.messageOwner != null && currentMessageObject.replyMessageObject != null && currentMessageObject.replyMessageObject.isFromUser()) { + currentReplyUserId = currentMessageObject.replyMessageObject.messageOwner.from_id.user_id; + } else { + currentReplyUserId = 0; + } } private void setMessageObjectInternal(MessageObject messageObject) { @@ -11134,7 +11636,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } boolean needAuthorName = isNeedAuthorName(); - boolean viaBot = (messageObject.messageOwner.fwd_from == null || messageObject.type == 14) && viaUsername != null; + boolean viaBot = (messageObject.messageOwner.fwd_from == null || messageObject.type == MessageObject.TYPE_MUSIC) && viaUsername != null; if (!hasPsaHint && (needAuthorName || viaBot)) { drawNameLayout = true; nameWidth = getMaxNameWidth(); @@ -11151,8 +11653,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else if (isMegagroup && currentChat != null && currentMessageObject.isForwardedChannelPost()) { adminString = LocaleController.getString("DiscussChannel", R.string.DiscussChannel); adminWidth = (int) Math.ceil(Theme.chat_adminPaint.measureText(adminString)); - nameWidth -= adminWidth; //TODO - } else if (currentUser != null && !currentMessageObject.isOutOwner() && !currentMessageObject.isAnyKindOfSticker() && currentMessageObject.type != 5 && delegate != null && (adminLabel = delegate.getAdminRank(currentUser.id)) != null) { + nameWidth -= adminWidth; + } else if ((currentUser != null || currentChat != null) && !currentMessageObject.isOutOwner() && !currentMessageObject.isAnyKindOfSticker() && currentMessageObject.type != MessageObject.TYPE_ROUND_VIDEO && delegate != null && (adminLabel = delegate.getAdminRank(currentUser != null ? currentUser.id : currentChat.id)) != null) { if (adminLabel.length() == 0) { adminLabel = LocaleController.getString("ChatAdmin", R.string.ChatAdmin); } @@ -11208,7 +11710,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (nameLayout.getLineCount() > 0) { nameWidth = nameLayoutWidth = (int) Math.ceil(nameLayout.getLineWidth(0)); if (!messageObject.isAnyKindOfSticker()) { - namesOffset += AndroidUtilities.dp(19); + namesOffset += AndroidUtilities.dp(5) + Theme.chat_namePaint.getTextSize(); } nameOffsetX = nameLayout.getLineLeft(0); } else { @@ -11227,7 +11729,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate FileLog.e(e); } if (currentNameStatusDrawable == null) { - currentNameStatusDrawable = new AnimatedEmojiDrawable.SwapAnimatedEmojiDrawable(getParent() instanceof View ? (View) getParent() : null, AndroidUtilities.dp(20)); + currentNameStatusDrawable = new AnimatedEmojiDrawable.SwapAnimatedEmojiDrawable(this, true, AndroidUtilities.dp(20)); } if (currentNameStatus == null) { currentNameStatusDrawable.set((Drawable) null, false); @@ -11328,8 +11830,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } forwardNameOffsetX[0] = forwardedNameLayout[0].getLineLeft(0); forwardNameOffsetX[1] = forwardedNameLayout[1].getLineLeft(0); - if (messageObject.type != 5 && !messageObject.isAnyKindOfSticker()) { - namesOffset += AndroidUtilities.dp(36); + if (messageObject.type != MessageObject.TYPE_ROUND_VIDEO && !messageObject.isAnyKindOfSticker()) { + namesOffset += AndroidUtilities.dp(8) + Theme.chat_forwardNamePaint.getTextSize() * 2; } } catch (Exception e) { FileLog.e(e); @@ -11339,12 +11841,15 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if ((!isThreadChat || messageObject.getReplyTopMsgId() != 0) && messageObject.hasValidReplyMessageObject() || messageObject.messageOwner.fwd_from != null && messageObject.isDice()) { if (currentPosition == null || currentPosition.minY == 0) { - if (!messageObject.isAnyKindOfSticker() && messageObject.type != 5) { - namesOffset += AndroidUtilities.dp(42); - if (messageObject.type != 0) { + if (!messageObject.isAnyKindOfSticker() && messageObject.type != MessageObject.TYPE_ROUND_VIDEO) { + namesOffset += AndroidUtilities.dp(14) + (Theme.chat_replyTextPaint.getTextSize() + Theme.chat_replyNamePaint.getTextSize()); + if (messageObject.type != MessageObject.TYPE_TEXT) { namesOffset += AndroidUtilities.dp(5); } } + if (drawForwardedName && forwardedNameLayout[0] != null) { + namesOffset += AndroidUtilities.dp(2); + } int maxWidth = getMaxNameWidth(); if (!messageObject.shouldDrawWithoutBackground()) { @@ -11385,14 +11890,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate needReplyImage = false; } else { if (messageObject.replyMessageObject.isRoundVideo()) { - replyImageReceiver.setRoundRadius(AndroidUtilities.dp(22)); + replyImageReceiver.setRoundRadius(AndroidUtilities.dp(32)); } else { replyImageReceiver.setRoundRadius(AndroidUtilities.dp(2)); } currentReplyPhoto = photoSize; replyImageReceiver.setImage(ImageLocation.getForObject(photoSize, photoObject), "50_50", ImageLocation.getForObject(thumbPhotoSize, photoObject), "50_50_b", size, null, messageObject.replyMessageObject, cacheType); needReplyImage = true; - maxWidth -= AndroidUtilities.dp(44); + maxWidth -= AndroidUtilities.dp(16) + (Theme.chat_replyTextPaint.getTextSize() + Theme.chat_replyNamePaint.getTextSize()); } if (messageObject.hideSendersName) { @@ -11437,11 +11942,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (name == null) { name = LocaleController.getString("Loading", R.string.Loading); } - if (messageObject.replyMessageObject.getMedia(messageObject.messageOwner) instanceof TLRPC.TL_messageMediaGame) { - stringFinalText = Emoji.replaceEmoji(messageObject.replyMessageObject.getMedia(messageObject.messageOwner).game.title, Theme.chat_replyTextPaint.getFontMetricsInt(), AndroidUtilities.dp(14), false); + if (messageObject.replyMessageObject.messageTextForReply != null) { + stringFinalText = messageObject.replyMessageObject.messageTextForReply; + } else if (MessageObject.getMedia(messageObject.replyMessageObject) instanceof TLRPC.TL_messageMediaGame) { + stringFinalText = Emoji.replaceEmoji(MessageObject.getMedia(messageObject.replyMessageObject).game.title, Theme.chat_replyTextPaint.getFontMetricsInt(), AndroidUtilities.dp(14), false); stringFinalText = TextUtils.ellipsize(stringFinalText, Theme.chat_replyTextPaint, maxWidth, TextUtils.TruncateAt.END); - } else if (messageObject.replyMessageObject.getMedia(messageObject.messageOwner) instanceof TLRPC.TL_messageMediaInvoice) { - stringFinalText = Emoji.replaceEmoji(messageObject.replyMessageObject.getMedia(messageObject.messageOwner).title, Theme.chat_replyTextPaint.getFontMetricsInt(), AndroidUtilities.dp(14), false); + } else if (MessageObject.getMedia(messageObject.replyMessageObject) instanceof TLRPC.TL_messageMediaInvoice) { + stringFinalText = Emoji.replaceEmoji(MessageObject.getMedia(messageObject.replyMessageObject).title, Theme.chat_replyTextPaint.getFontMetricsInt(), AndroidUtilities.dp(14), false); stringFinalText = TextUtils.ellipsize(stringFinalText, Theme.chat_replyTextPaint, maxWidth, TextUtils.TruncateAt.END); } else if (!TextUtils.isEmpty(messageObject.replyMessageObject.caption)) { String mess = messageObject.replyMessageObject.caption.toString(); @@ -11457,7 +11964,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (stringFinalText instanceof Spannable && messageObject.replyMessageObject.messageOwner != null) { MediaDataController.addTextStyleRuns(messageObject.replyMessageObject.messageOwner.entities, messageObject.replyMessageObject.caption, (Spannable) stringFinalText); } - } else if (messageObject.replyMessageObject.messageText != null && messageObject.replyMessageObject.messageText.length() > 0) { + } else if (messageObject.replyMessageObject != null && messageObject.replyMessageObject.messageOwner != null && messageObject.replyMessageObject.messageOwner.action instanceof TLRPC.TL_messageActionTopicCreate) { + TLRPC.TL_messageActionTopicCreate topicCreate = (TLRPC.TL_messageActionTopicCreate) messageObject.replyMessageObject.messageOwner.action; + TLRPC.TL_forumTopic topic = MessagesController.getInstance(currentAccount).getTopicsController().findTopic(-messageObject.getDialogId(), messageObject.replyMessageObject.messageOwner.id); + stringFinalText = ForumUtilities.getTopicSpannedName(topic, Theme.chat_replyTextPaint); + } else if (messageObject.replyMessageObject != null && messageObject.replyMessageObject.messageText != null && messageObject.replyMessageObject.messageText.length() > 0) { String mess = messageObject.replyMessageObject.messageText.toString(); if (mess.length() > 150) { mess = mess.substring(0, 150); @@ -11520,7 +12031,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } catch (Exception ignore) { } try { - replyNameWidth = AndroidUtilities.dp(4 + (needReplyImage ? 44 : 0)); + replyNameWidth = AndroidUtilities.dp(4) + (needReplyImage ? AndroidUtilities.dp(16) + (int) (Theme.chat_replyTextPaint.getTextSize() + Theme.chat_replyNamePaint.getTextSize()) : 0); if (stringFinalName != null) { replyNameLayout = new StaticLayout(stringFinalName, Theme.chat_replyNamePaint, maxWidth + AndroidUtilities.dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (replyNameLayout.getLineCount() > 0) { @@ -11532,7 +12043,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate FileLog.e(e); } try { - replyTextWidth = AndroidUtilities.dp(4 + (needReplyImage ? 44 : 0)); + replyTextWidth = AndroidUtilities.dp(4) + (needReplyImage ? AndroidUtilities.dp(16) + (int) (Theme.chat_replyTextPaint.getTextSize() + Theme.chat_replyNamePaint.getTextSize()) : 0); if (stringFinalText != null) { SpannableStringBuilder sb = new SpannableStringBuilder(stringFinalText); boolean changed = false; @@ -11542,11 +12053,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate sb.removeSpan(span); } } - if (changed) { - stringFinalText = TextUtils.ellipsize(sb, Theme.chat_replyTextPaint, maxWidth, TextUtils.TruncateAt.END); - } else { - stringFinalText = sb; - } + stringFinalText = TextUtils.ellipsize(sb, Theme.chat_replyTextPaint, maxWidth, TextUtils.TruncateAt.END); replyTextLayout = new StaticLayout(stringFinalText, Theme.chat_replyTextPaint, maxWidth + AndroidUtilities.dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (replyTextLayout.getLineCount() > 0) { replyTextWidth += (int) Math.ceil(replyTextLayout.getLineWidth(0)) + AndroidUtilities.dp(8); @@ -11563,9 +12070,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } else if (!isThreadChat && messageObject.getReplyMsgId() != 0) { if (!(messageObject.replyMessageObject != null && messageObject.replyMessageObject.messageOwner instanceof TLRPC.TL_messageEmpty)) { - if (!messageObject.isAnyKindOfSticker() && messageObject.type != 5) { - namesOffset += AndroidUtilities.dp(42); - if (messageObject.type != 0) { + if (!messageObject.isAnyKindOfSticker() && messageObject.type != MessageObject.TYPE_ROUND_VIDEO) { + namesOffset += AndroidUtilities.dp(14) + (Theme.chat_replyTextPaint.getTextSize() + Theme.chat_replyNamePaint.getTextSize()); + if (messageObject.type != MessageObject.TYPE_TEXT) { namesOffset += AndroidUtilities.dp(5); } } @@ -11590,7 +12097,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } private boolean isNeedAuthorName() { - return isPinnedChat && currentMessageObject.type == 0 || (!pinnedTop || ChatObject.isChannel(currentChat) && !currentChat.megagroup) && drawName && isChat && (!currentMessageObject.isOutOwner() || currentMessageObject.isSupergroup() && currentMessageObject.isFromGroup()) || currentMessageObject.isImportedForward() && currentMessageObject.messageOwner.fwd_from.from_id == null; + return ( + isPinnedChat && currentMessageObject.type == MessageObject.TYPE_TEXT || + !pinnedTop && drawName && isChat && (!currentMessageObject.isOutOwner() || currentMessageObject.isSupergroup() && currentMessageObject.isFromGroup()) || + currentMessageObject.isImportedForward() && currentMessageObject.messageOwner.fwd_from.from_id == null + ); } private String getAuthorName() { @@ -11764,7 +12275,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } if (documentAttach != null) { - if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO) { + if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND) { if (currentMessageObject.isOutOwner()) { seekBarWaveform.setColors(getThemedColor(Theme.key_chat_outVoiceSeekbar), getThemedColor(Theme.key_chat_outVoiceSeekbarFill), getThemedColor(Theme.key_chat_outVoiceSeekbarSelected)); seekBar.setColors(getThemedColor(Theme.key_chat_outAudioSeekbar), getThemedColor(Theme.key_chat_outAudioCacheSeekbar), getThemedColor(Theme.key_chat_outAudioSeekbarFill), getThemedColor(Theme.key_chat_outAudioSeekbarFill), getThemedColor(Theme.key_chat_outAudioSeekbarSelected)); @@ -11781,7 +12292,16 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } if (currentMessageObject.type == MessageObject.TYPE_ROUND_VIDEO) { - Theme.chat_timePaint.setColor(getThemedColor(Theme.key_chat_serviceText)); + Theme.chat_timePaint.setColor( + ColorUtils.blendARGB( + getThemedColor(Theme.key_chat_serviceText), + getThemedColor(isDrawSelectionBackground() ? + (currentMessageObject.isOutOwner() ? Theme.key_chat_outTimeSelectedText : Theme.key_chat_inTimeSelectedText) : + (currentMessageObject.isOutOwner() ? Theme.key_chat_outTimeText : Theme.key_chat_inTimeText) + ), + getVideoTranscriptionProgress() + ) + ); } else { if (mediaBackground) { if (currentMessageObject.shouldDrawWithoutBackground()) { @@ -11867,6 +12387,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } if (replyNameLayout != null) { + replyHeight = AndroidUtilities.dp(7) + Theme.chat_replyNamePaint.getTextSize() + Theme.chat_replyTextPaint.getTextSize(); if (currentMessageObject.shouldDrawWithoutBackground()) { if (currentMessageObject.isOutOwner()) { replyStartX = AndroidUtilities.dp(23); @@ -11879,7 +12400,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate replyStartX = backgroundDrawableLeft + backgroundDrawableRight + AndroidUtilities.dp(17); } if (drawForwardedName) { - replyStartY = forwardNameY + AndroidUtilities.dp(38); + forwardHeight = AndroidUtilities.dp(4) + (int) Theme.chat_forwardNamePaint.getTextSize() * 2; + replyStartY = forwardNameY + forwardHeight + AndroidUtilities.dp(6); } else { replyStartY = AndroidUtilities.dp(12); } @@ -11893,7 +12415,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate replyStartX = backgroundDrawableLeft + AndroidUtilities.dp(drawPinnedBottom ? 12 : 18) + getExtraTextX(); } } - replyStartY = AndroidUtilities.dp(12 + (drawForwardedName && forwardedNameLayout[0] != null ? 36 : 0) + (drawNameLayout && nameLayout != null ? 20 : 0)); + forwardHeight = AndroidUtilities.dp(4) + (int) Theme.chat_forwardNamePaint.getTextSize() * 2; + replyStartY = AndroidUtilities.dp(12) + (drawNameLayout && nameLayout != null ? AndroidUtilities.dp(6) + (int) Theme.chat_namePaint.getTextSize() : 0) + (drawForwardedName && forwardedNameLayout[0] != null ? AndroidUtilities.dp(4) + forwardHeight : 0); } } if (currentPosition == null && !transitionParams.animateBackgroundBoundsInner && !(enterTransitionInProgress && !currentMessageObject.isVoice())) { @@ -11907,7 +12430,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate drawTime(canvas, 1f, false); } - if ((controlsAlpha != 1.0f || timeAlpha != 1.0f) && currentMessageObject.type != 5) { + if ((controlsAlpha != 1.0f || timeAlpha != 1.0f) && currentMessageObject.type != MessageObject.TYPE_ROUND_VIDEO) { long newTime = System.currentTimeMillis(); long dt = Math.abs(lastControlsAlphaChangeTime - newTime); if (dt > 17) { @@ -11931,7 +12454,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } - if (drawBackground && shouldDrawSelectionOverlay() && currentMessagesGroup == null) { + if ((drawBackground || transitionParams.animateDrawBackground) && shouldDrawSelectionOverlay() && currentMessagesGroup == null) { if (selectionOverlayPaint == null) { selectionOverlayPaint = new Paint(Paint.ANTI_ALIAS_FLAG); } @@ -12028,6 +12551,18 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate backgroundDrawableTop = additionalTop + (drawPinnedTop ? 0 : AndroidUtilities.dp(1)); int backgroundHeight = layoutHeight - offsetBottom + additionalBottom; backgroundDrawableBottom = backgroundDrawableTop + backgroundHeight; + + if (!mediaBackground) { + if (drawPinnedTop) { + backgroundDrawableTop -= AndroidUtilities.dp(1); + backgroundHeight += AndroidUtilities.dp(1); + } + if (drawPinnedBottom) { + backgroundDrawableBottom += AndroidUtilities.dp(1); + backgroundHeight += AndroidUtilities.dp(1); + } + } + if (forceMediaByGroup) { setDrawableBoundsInner(currentBackgroundDrawable, backgroundLeft, backgroundDrawableTop - additionalTop, backgroundDrawableRight, backgroundHeight - additionalBottom + 10); setDrawableBoundsInner(currentBackgroundSelectedDrawable, backgroundDrawableLeft, backgroundDrawableTop, backgroundDrawableRight - AndroidUtilities.dp(6), backgroundHeight); @@ -12071,7 +12606,6 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (!mediaBackground) { backgroundDrawableLeft += AndroidUtilities.dp(6); } - } if (currentPosition != null) { if ((currentPosition.flags & MessageObject.POSITION_FLAG_RIGHT) == 0) { @@ -12100,6 +12634,18 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate backgroundDrawableTop = additionalTop + (drawPinnedTop ? 0 : AndroidUtilities.dp(1)); int backgroundHeight = layoutHeight - offsetBottom + additionalBottom; backgroundDrawableBottom = backgroundDrawableTop + backgroundHeight; + + if (!mediaBackground) { + if (drawPinnedTop) { + backgroundDrawableTop -= AndroidUtilities.dp(1); + backgroundHeight += AndroidUtilities.dp(1); + } + if (drawPinnedBottom) { + backgroundDrawableBottom += AndroidUtilities.dp(1); + backgroundHeight += AndroidUtilities.dp(1); + } + } + setDrawableBoundsInner(currentBackgroundDrawable, backgroundDrawableLeft, backgroundDrawableTop, backgroundDrawableRight, backgroundHeight); if (forceMediaByGroup) { setDrawableBoundsInner(currentBackgroundSelectedDrawable, backgroundDrawableLeft + AndroidUtilities.dp(6), backgroundDrawableTop, backgroundDrawableRight - AndroidUtilities.dp(6), backgroundHeight); @@ -12181,14 +12727,39 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate return; } - boolean needRestore = false; + int restoreCount = canvas.getSaveCount(); if (transitionYOffsetForDrawables != 0) { - needRestore = true; canvas.save(); canvas.translate(0, transitionYOffsetForDrawables); } - if (drawBackground && currentBackgroundDrawable != null && (currentPosition == null || isDrawSelectionBackground() && (currentMessageObject.isMusic() || currentMessageObject.isDocument())) && !(enterTransitionInProgress && !currentMessageObject.isVoice())) { + float pinnedBottomOffset = 0; + if (currentMessageObject != null && currentMessageObject.isRoundVideo()) { + float progress = getVideoTranscriptionProgress(); + if (transitionParams.animateDrawBackground) { + Rect bounds = currentBackgroundDrawable.getBounds(); + bounds.bottom = AndroidUtilities.lerp(bounds.top + bounds.width(), bounds.bottom, progress); + } + currentBackgroundDrawable.setRoundingRadius(1f - progress); + pinnedBottomOffset = AndroidUtilities.lerp(backgroundWidth / 2, 0, progress); + canvas.saveLayerAlpha(0, 0, getWidth(), Math.max(currentBackgroundDrawable.getBounds().bottom, getHeight()), (int) (255 * progress), Canvas.ALL_SAVE_FLAG); + + roundVideoPlayPipFloat.set((MediaController.getInstance().isPiPShown() && MediaController.getInstance().isPlayingMessageAndReadyToDraw(currentMessageObject) || wouldBeInPip) && canvas.isHardwareAccelerated() ? 1f : 0f); + if (MediaController.getInstance().isPiPShown()) { + wouldBeInPip = false; + } + if (!hasLinkPreview && MediaController.getInstance().isPlayingMessageAndReadyToDraw(currentMessageObject) && canvas.isHardwareAccelerated() && !MediaController.getInstance().isPiPShown()) { + if (drillHolePath == null) { + drillHolePath = new Path(); + } else { + drillHolePath.rewind(); + } + drillHolePath.addCircle(photoImage.getCenterX(), photoImage.getCenterY(), photoImage.getImageWidth() / 2, Path.Direction.CW); + canvas.clipPath(drillHolePath, Region.Op.DIFFERENCE); + } + } + + if ((drawBackground || transitionParams.animateDrawBackground) && currentBackgroundDrawable != null && (currentPosition == null || isDrawSelectionBackground() && (currentMessageObject.isMusic() || currentMessageObject.isDocument())) && !(enterTransitionInProgress && !currentMessageObject.isVoice())) { float alphaInternal = this.alphaInternal; if (fromParent) { alphaInternal *= getAlpha(); @@ -12229,7 +12800,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (isDrawSelectionBackground() && (currentPosition == null || currentMessageObject.isMusic() || currentMessageObject.isDocument() || getBackground() != null)) { if (currentPosition != null) { canvas.save(); - canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight()); +// canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight()); } currentSelectedBackgroundAlpha = 1f; currentBackgroundSelectedDrawable.setAlpha((int) (255 * alphaInternal)); @@ -12255,7 +12826,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate Rect rect = currentBackgroundDrawable.getBounds(); drawable.setBounds(rect.left, rect.top, rect.right + AndroidUtilities.dp(6), rect.bottom); canvas.save(); - canvas.clipRect(rect.right - AndroidUtilities.dp(12), rect.bottom - AndroidUtilities.dp(16), rect.right + AndroidUtilities.dp(12), rect.bottom); + canvas.translate(-pinnedBottomOffset, 0); + canvas.clipRect(rect.right - AndroidUtilities.dp(16), rect.bottom - AndroidUtilities.dp(16), rect.right + AndroidUtilities.dp(16), rect.bottom); int w = parentWidth; int h = parentHeight; if (h == 0) { @@ -12285,7 +12857,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate Rect rect = currentBackgroundDrawable.getBounds(); drawable.setBounds(rect.left - AndroidUtilities.dp(6), rect.top, rect.right, rect.bottom); canvas.save(); - canvas.clipRect(rect.left - AndroidUtilities.dp(6), rect.bottom - AndroidUtilities.dp(16), rect.left + AndroidUtilities.dp(6), rect.bottom); + canvas.translate(pinnedBottomOffset, 0); + canvas.clipRect(rect.left - AndroidUtilities.dp(6), rect.bottom - AndroidUtilities.dp(16), rect.left + AndroidUtilities.dp(6 + 12), rect.bottom); drawable.draw(canvas); drawable.setAlpha(255); canvas.restore(); @@ -12293,9 +12866,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } } - if (needRestore) { - canvas.restore(); + if (currentMessageObject != null && currentMessageObject.isRoundVideo()) { + currentBackgroundDrawable.setRoundingRadius(0); } + canvas.restoreToCount(restoreCount); } public boolean drawBackgroundInParent() { @@ -12390,11 +12964,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate (!transitionParams.transitionBotButtons.isEmpty() && transitionParams.animateBotButtonsChanged) || !botButtons.isEmpty() || drawSideButton != 0 || - drawNameLayout && nameLayout != null && currentNameStatus != null || + drawNameLayout && nameLayout != null && currentNameStatusDrawable != null && currentNameStatusDrawable.getDrawable() != null || animatedEmojiStack != null && !animatedEmojiStack.holders.isEmpty() || currentMessagesGroup == null && - (transitionParams.animateReplaceCaptionLayout && transitionParams.animateChangeProgress != 1f || transitionParams.animateChangeProgress != 1.0f && transitionParams.animateMessageText) && - transitionParams.animateOutAnimateEmoji != null && !transitionParams.animateOutAnimateEmoji.holders.isEmpty() + (transitionParams.animateReplaceCaptionLayout && transitionParams.animateChangeProgress != 1f || transitionParams.animateChangeProgress != 1.0f && transitionParams.animateMessageText) && + transitionParams.animateOutAnimateEmoji != null && !transitionParams.animateOutAnimateEmoji.holders.isEmpty() ); } @@ -12403,7 +12977,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate drawAnimatedEmojis(canvas, 1f); } - if (currentNameStatus != null && currentNameStatusDrawable != null && drawNameLayout && nameLayout != null) { + if (currentNameStatusDrawable != null && drawNameLayout && nameLayout != null) { int color; if (currentMessageObject.shouldDrawWithoutBackground()) { color = getThemedColor(Theme.key_chat_stickerNameText); @@ -12610,23 +13184,30 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate sideStartX += currentMessagesGroup.transitionParams.offsetRight - animationOffsetX; } } - sideStartY = layoutHeight - AndroidUtilities.dp(41) + transitionParams.deltaBottom; + sideStartY = layoutHeight + transitionParams.deltaBottom - AndroidUtilities.dp(41); if (currentMessagesGroup != null) { sideStartY += currentMessagesGroup.transitionParams.offsetBottom; if (currentMessagesGroup.transitionParams.backgroundChangeBounds) { sideStartY -= getTranslationY(); } } - if (!reactionsLayoutInBubble.isSmall && reactionsLayoutInBubble.drawServiceShaderBackground) { - sideStartY -= reactionsLayoutInBubble.getCurrentTotalHeight(transitionParams.animateChangeProgress); + if (!reactionsLayoutInBubble.isSmall) { + if (isRoundVideo) { + sideStartY -= reactionsLayoutInBubble.getCurrentTotalHeight(transitionParams.animateChangeProgress) * (1f - getVideoTranscriptionProgress()); + } else if (reactionsLayoutInBubble.drawServiceShaderBackground > 0) { + sideStartY -= reactionsLayoutInBubble.getCurrentTotalHeight(transitionParams.animateChangeProgress); + } } - if (!currentMessageObject.isOutOwner() && isRoundVideo && isAvatarVisible) { - float offsetSize = (AndroidUtilities.roundPlayingMessageSize - AndroidUtilities.roundMessageSize) * 0.7f; - float offsetX = isPlayingRound ? offsetSize : 0; + if (!currentMessageObject.isOutOwner() && isRoundVideo && !hasLinkPreview) { + float offsetSize = isAvatarVisible ? ((AndroidUtilities.roundPlayingMessageSize - AndroidUtilities.roundMessageSize) * 0.7f) : AndroidUtilities.dp(50); + float offsetX = isPlayingRound ? offsetSize * (1f - getVideoTranscriptionProgress()) : 0; + float offsetY = isPlayingRound ? AndroidUtilities.dp(28) * (1f - getVideoTranscriptionProgress()) : 0; if (transitionParams.animatePlayingRound) { - offsetX = (isPlayingRound ? transitionParams.animateChangeProgress : (1f - transitionParams.animateChangeProgress)) * offsetSize; + offsetX = (isPlayingRound ? transitionParams.animateChangeProgress : (1f - transitionParams.animateChangeProgress)) * (1f - getVideoTranscriptionProgress()) * offsetSize; + offsetY = (isPlayingRound ? transitionParams.animateChangeProgress : (1f - transitionParams.animateChangeProgress)) * (1f - getVideoTranscriptionProgress()) * AndroidUtilities.dp(28); } sideStartX -= offsetX; + sideStartY -= offsetY; } if (drawSideButton == 3) { if (!(enterTransitionInProgress && !currentMessageObject.isVoice())) { @@ -12673,15 +13254,28 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate public int getBackgroundDrawableLeft() { if (currentMessageObject.isOutOwner()) { + if (isRoundVideo) { + return layoutWidth - backgroundWidth - (int) ((1f - getVideoTranscriptionProgress()) * AndroidUtilities.dp(9)); + } return layoutWidth - backgroundWidth - (!mediaBackground ? 0 : AndroidUtilities.dp(9)); } else { - int r = AndroidUtilities.dp((isChat && isAvatarVisible ? 48 : 0) + (!mediaBackground ? 3 : 9)); + int r; + if (isRoundVideo) { + r = AndroidUtilities.dp((isChat && isAvatarVisible ? 48 : 0) + 3); + r += (int) (AndroidUtilities.dp(6) * (1f - getVideoTranscriptionProgress())); + } else { + r = AndroidUtilities.dp((isChat && isAvatarVisible ? 48 : 0) + (!mediaBackground ? 3 : 9)); + } if (currentMessagesGroup != null && !currentMessagesGroup.isDocuments) { if (currentPosition.leftSpanOffset != 0) { r += (int) Math.ceil(currentPosition.leftSpanOffset / 1000.0f * getGroupPhotosWidth()); } } - if ((!mediaBackground && drawPinnedBottom)) { + if (isRoundVideo) { + if (drawPinnedBottom) { + r += (int) (AndroidUtilities.dp(6) * (1f - getVideoTranscriptionProgress())); + } + } else if ((!mediaBackground && drawPinnedBottom)) { r += AndroidUtilities.dp(6); } return r; @@ -12689,11 +13283,19 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } public int getBackgroundDrawableRight() { - int right = backgroundWidth - (mediaBackground ? 0 : AndroidUtilities.dp(3)); - if (!mediaBackground && drawPinnedBottom && currentMessageObject.isOutOwner()) { - right -= AndroidUtilities.dp(6); + int right = backgroundWidth; + if (isRoundVideo) { + right -= (int) (getVideoTranscriptionProgress() * AndroidUtilities.dp(3)); + if (drawPinnedBottom && currentMessageObject.isOutOwner()) { + right -= AndroidUtilities.dp(6) * (1f - getVideoTranscriptionProgress()); + } + if (drawPinnedBottom && !currentMessageObject.isOutOwner()) { + right -= AndroidUtilities.dp(6) * (1f - getVideoTranscriptionProgress()); + } + return getBackgroundDrawableLeft() + right; } - if (!mediaBackground && drawPinnedBottom && !currentMessageObject.isOutOwner()) { + right -= (mediaBackground ? 0 : AndroidUtilities.dp(3)); + if (!mediaBackground && drawPinnedBottom) { right -= AndroidUtilities.dp(6); } return getBackgroundDrawableLeft() + right; @@ -12706,7 +13308,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate additionalTop -= AndroidUtilities.dp(3); } } - return additionalTop + (drawPinnedTop ? 0 : AndroidUtilities.dp(1)); + int top = additionalTop + (drawPinnedTop ? 0 : AndroidUtilities.dp(1)); + if (!mediaBackground) { + if (drawPinnedTop) { + top -= AndroidUtilities.dp(1); + } + } + return top; } public int getBackgroundDrawableBottom() { @@ -12728,7 +13336,17 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { offsetBottom = AndroidUtilities.dp(2); } - return getBackgroundDrawableTop() + layoutHeight - offsetBottom + additionalBottom; + int top = getBackgroundDrawableTop(); + int bottom = top + layoutHeight - offsetBottom + additionalBottom; + if (!mediaBackground) { + if (drawPinnedTop) { + bottom += AndroidUtilities.dp(1); + } + if (drawPinnedBottom) { + bottom += AndroidUtilities.dp(1); + } + } + return bottom; } public void drawBackground(Canvas canvas, int left, int top, int right, int bottom, boolean pinnedTop, boolean pinnedBottom, boolean selected, int keyboardHeight) { @@ -12806,11 +13424,19 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate restore = canvas.saveLayerAlpha(rect, (int) (255 * alpha), Canvas.ALL_SAVE_FLAG); } - float replyForwardAlpha = Math.max(0, Math.min(1, - currentMessageObject.type == MessageObject.TYPE_ROUND_VIDEO ? - 1f - (photoImage.getImageWidth() - AndroidUtilities.roundMessageSize) / (AndroidUtilities.roundPlayingMessageSize - AndroidUtilities.roundMessageSize) : - 1 - )); + float replyForwardAlpha = 1f; + if (isRoundVideo && !hasLinkPreview) { + replyForwardAlpha *= 1f - getVideoTranscriptionProgress(); + if (transitionParams.animatePlayingRound) { + if (isPlayingRound) { + replyForwardAlpha *= (1f - transitionParams.animateChangeProgress); + } else { + replyForwardAlpha *= transitionParams.animateChangeProgress; + } + } else if (isPlayingRound) { + replyForwardAlpha = 0; + } + } if (drawNameLayout && nameLayout != null) { canvas.save(); @@ -12990,18 +13616,19 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { forwardNameXLocal = transitionParams.animateForwardNameX; } - if (currentMessageObject.isOutOwner() && currentMessageObject.type == MessageObject.TYPE_ROUND_VIDEO && transitionParams.animatePlayingRound || isPlayingRound) { + if (!currentMessageObject.isVoiceTranscriptionOpen() && (currentMessageObject.isOutOwner() && currentMessageObject.type == MessageObject.TYPE_ROUND_VIDEO && transitionParams.animatePlayingRound || isPlayingRound)) { forwardNameXLocal -= AndroidUtilities.dp(78) * (isPlayingRound ? transitionParams.animateChangeProgress : (1f - transitionParams.animateChangeProgress)); } forwardNameY = AndroidUtilities.dp(12); + forwardHeight = AndroidUtilities.dp(4) + (int) Theme.chat_forwardNamePaint.getTextSize() * 2; int backWidth = forwardedNameWidthLocal + AndroidUtilities.dp(14); if (hasReply) { needDrawReplyBackground = false; int replyBackWidth = Math.max(replyNameWidth, replyTextWidth) + AndroidUtilities.dp(14); - rect.set((int) forwardNameXLocal - AndroidUtilities.dp(7), forwardNameY - AndroidUtilities.dp(6), (int) forwardNameXLocal - AndroidUtilities.dp(7) + Math.max(backWidth, replyBackWidth), forwardNameY + AndroidUtilities.dp(38) + AndroidUtilities.dp(41)); + rect.set((int) forwardNameXLocal - AndroidUtilities.dp(7), forwardNameY - AndroidUtilities.dp(6), (int) forwardNameXLocal - AndroidUtilities.dp(7) + Math.max(backWidth, replyBackWidth), forwardNameY + forwardHeight + AndroidUtilities.dp(6) + AndroidUtilities.dp(41)); } else { - rect.set((int) forwardNameXLocal - AndroidUtilities.dp(7), forwardNameY - AndroidUtilities.dp(6), (int) forwardNameXLocal - AndroidUtilities.dp(7) + backWidth, forwardNameY + AndroidUtilities.dp(38)); + rect.set((int) forwardNameXLocal - AndroidUtilities.dp(7), forwardNameY - AndroidUtilities.dp(6), (int) forwardNameXLocal - AndroidUtilities.dp(7) + backWidth, forwardNameY + forwardHeight + AndroidUtilities.dp(6)); } applyServiceShaderMatrix(); int oldAlpha1 = -1, oldAlpha2 = -1; @@ -13024,7 +13651,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha(oldAlpha2); } } else { - forwardNameY = AndroidUtilities.dp(10 + (drawNameLayout ? 19 : 0)); + forwardNameY = AndroidUtilities.dp(10) + (drawNameLayout ? AndroidUtilities.dp(5) + (int) Theme.chat_namePaint.getTextSize() : 0); + forwardHeight = AndroidUtilities.dp(4) + (int) Theme.chat_forwardNamePaint.getTextSize() * 2; if (currentMessageObject.isOutOwner()) { if (hasPsaHint) { Theme.chat_forwardNamePaint.setColor(getThemedColor(Theme.key_chat_outPsaNameText)); @@ -13076,7 +13704,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate for (int a = 0; a < 2; a++) { canvas.save(); - canvas.translate(forwardNameXLocal - forwardNameOffsetX[a], forwardNameY + AndroidUtilities.dp(16) * a); + canvas.translate(forwardNameXLocal - forwardNameOffsetX[a], forwardNameY + (forwardHeight / 2f + 2) * a); if (animatingAlpha != 1f || replyForwardAlpha != 1f) { int oldAlpha = forwardedNameLayoutLocal[a].getPaint().getAlpha(); forwardedNameLayoutLocal[a].getPaint().setAlpha((int) (oldAlpha * animatingAlpha * replyForwardAlpha)); @@ -13135,12 +13763,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } if (transitionParams.animateBackgroundBoundsInner) { if (isRoundVideo) { - replyStartX += transitionParams.deltaLeft + transitionParams.deltaRight; + replyStartX += (currentMessageObject.isOutOwner() ? 0 : transitionParams.deltaLeft) + transitionParams.deltaRight; } else { replyStartX += transitionParams.deltaLeft; } replyStartY = this.replyStartY * transitionParams.animateChangeProgress + transitionParams.animateFromReplyY * (1f - transitionParams.animateChangeProgress); } + int rippleColor = getThemedColor(Theme.key_listSelector); if (currentMessageObject.shouldDrawWithoutBackground()) { Theme.chat_replyLinePaint.setColor(getThemedColor(Theme.key_chat_stickerReplyLine)); int oldAlpha = Theme.chat_replyLinePaint.getAlpha(); @@ -13153,7 +13782,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate Theme.chat_replyTextPaint.setAlpha((int) (oldAlpha * timeAlpha * replyForwardAlpha)); if (needDrawReplyBackground) { int backWidth = Math.max(replyNameWidth, replyTextWidth) + AndroidUtilities.dp(14); - rect.set((int) replyStartX - AndroidUtilities.dp(7), replyStartY - AndroidUtilities.dp(6), (int) replyStartX - AndroidUtilities.dp(7) + backWidth, replyStartY + AndroidUtilities.dp(41)); + rect.set((int) replyStartX - AndroidUtilities.dp(7), replyStartY - AndroidUtilities.dp(6), (int) replyStartX - AndroidUtilities.dp(7) + backWidth, replyStartY + AndroidUtilities.dp(6) + replyHeight); applyServiceShaderMatrix(); oldAlpha = getThemedPaint(Theme.key_paint_chatActionBackground).getAlpha(); getThemedPaint(Theme.key_paint_chatActionBackground).setAlpha((int) (oldAlpha * timeAlpha * replyForwardAlpha)); @@ -13170,45 +13799,110 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (currentMessageObject.isOutOwner()) { Theme.chat_replyLinePaint.setColor(getThemedColor(Theme.key_chat_outReplyLine)); Theme.chat_replyNamePaint.setColor(getThemedColor(Theme.key_chat_outReplyNameText)); - if (currentMessageObject.hasValidReplyMessageObject() && (currentMessageObject.replyMessageObject.type == 0 || !TextUtils.isEmpty(currentMessageObject.replyMessageObject.caption)) && !(MessageObject.getMedia(currentMessageObject.replyMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaGame || MessageObject.getMedia(currentMessageObject.replyMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaInvoice)) { + rippleColor = ColorUtils.setAlphaComponent(getThemedColor(Theme.key_chat_outReplyLine), 0x1e); + if (currentMessageObject.hasValidReplyMessageObject() && (currentMessageObject.replyMessageObject.type == MessageObject.TYPE_TEXT || !TextUtils.isEmpty(currentMessageObject.replyMessageObject.caption)) && !(MessageObject.getMedia(currentMessageObject.replyMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaGame || MessageObject.getMedia(currentMessageObject.replyMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaInvoice)) { Theme.chat_replyTextPaint.setColor(getThemedColor(Theme.key_chat_outReplyMessageText)); } else { Theme.chat_replyTextPaint.setColor(getThemedColor(isDrawSelectionBackground() ? Theme.key_chat_outReplyMediaMessageSelectedText : Theme.key_chat_outReplyMediaMessageText)); } } else { - Theme.chat_replyLinePaint.setColor(getThemedColor(Theme.key_chat_inReplyLine)); - Theme.chat_replyNamePaint.setColor(getThemedColor(Theme.key_chat_inReplyNameText)); - if (currentMessageObject.hasValidReplyMessageObject() && (currentMessageObject.replyMessageObject.type == 0 || !TextUtils.isEmpty(currentMessageObject.replyMessageObject.caption)) && !(MessageObject.getMedia(currentMessageObject.replyMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaGame || MessageObject.getMedia(currentMessageObject.replyMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaInvoice)) { + if (currentReplyUserId == 0) { + Theme.chat_replyLinePaint.setColor(getThemedColor(Theme.key_chat_inReplyLine)); + Theme.chat_replyNamePaint.setColor(getThemedColor(Theme.key_chat_inReplyNameText)); + rippleColor = ColorUtils.setAlphaComponent(getThemedColor(Theme.key_chat_inReplyLine), 0x1e); + } else { + Theme.chat_replyLinePaint.setColor(getThemedColor(AvatarDrawable.getNameColorNameForId(currentReplyUserId))); + Theme.chat_replyLinePaint.setAlpha(230); + Theme.chat_replyNamePaint.setColor(getThemedColor(AvatarDrawable.getNameColorNameForId(currentReplyUserId))); + Theme.chat_replyNamePaint.setAlpha(230); + rippleColor = ColorUtils.setAlphaComponent(getThemedColor(AvatarDrawable.getNameColorNameForId(currentReplyUserId)), 0x1e); + } + if (currentMessageObject.hasValidReplyMessageObject() && (currentMessageObject.replyMessageObject.type == MessageObject.TYPE_TEXT || !TextUtils.isEmpty(currentMessageObject.replyMessageObject.caption)) && !(MessageObject.getMedia(currentMessageObject.replyMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaGame || MessageObject.getMedia(currentMessageObject.replyMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaInvoice)) { Theme.chat_replyTextPaint.setColor(getThemedColor(Theme.key_chat_inReplyMessageText)); } else { Theme.chat_replyTextPaint.setColor(getThemedColor(isDrawSelectionBackground() ? Theme.key_chat_inReplyMediaMessageSelectedText : Theme.key_chat_inReplyMediaMessageText)); } } } - forwardNameX = replyStartX - replyTextOffset + AndroidUtilities.dp(10 + (needReplyImage ? 44 : 0)); + int offset = (int) Math.min(AndroidUtilities.dp(10), (replyHeight - AndroidUtilities.dp(35)) / 1.5f + AndroidUtilities.dp(10)); + forwardNameX = replyStartX - replyTextOffset + offset + (needReplyImage ? offset - AndroidUtilities.dp(1) + replyHeight : 0); if ((currentPosition == null || currentPosition.minY == 0 && currentPosition.minX == 0) && !(enterTransitionInProgress && !currentMessageObject.isVoice())) { - AndroidUtilities.rectTmp.set(replyStartX, replyStartY, replyStartX + AndroidUtilities.dp(2), replyStartY + AndroidUtilities.dp(35)); + if (getAlpha() * replyForwardAlpha != 1f) { + AndroidUtilities.rectTmp.set(0, 0, getWidth(), getHeight()); + canvas.saveLayerAlpha(AndroidUtilities.rectTmp, (int) (0xFF * getAlpha() * replyForwardAlpha), Canvas.ALL_SAVE_FLAG); + } + + int leftRad, rightRad, bottomRad = AndroidUtilities.dp(Math.min(2, SharedConfig.bubbleRadius)); + if (currentMessageObject.shouldDrawWithoutBackground()) { + leftRad = rightRad = bottomRad = AndroidUtilities.dp(3); + replySelectorRect.set( + (int) (replyStartX - AndroidUtilities.dp(7)), + (int) (replyStartY - AndroidUtilities.dp(7)), + (int) (replyStartX + Math.max(replyNameWidth, replyTextWidth) + AndroidUtilities.dp(7)), + (int) (replyStartY + replyHeight + AndroidUtilities.dp(7)) + ); + } else { + if (drawNameLayout || (drawForwardedName && forwardedNameLayout[0] != null)) { + leftRad = bottomRad; + } else if (currentMessageObject.isOutOwner() || !drawPinnedTop) { + leftRad = AndroidUtilities.dp(SharedConfig.bubbleRadius / 3f); + } else { + leftRad = AndroidUtilities.dp(Math.min(6, SharedConfig.bubbleRadius) / 3f); + } + if (drawNameLayout || (drawForwardedName && forwardedNameLayout[0] != null)) { + rightRad = bottomRad; + } else if (!currentMessageObject.isOutOwner() || !drawPinnedTop) { + rightRad = AndroidUtilities.dp(SharedConfig.bubbleRadius / 3f); + } else { + rightRad = AndroidUtilities.dp(Math.min(6, SharedConfig.bubbleRadius) / 3f); + } + replySelectorRect.set( + (int) (getCurrentBackgroundLeft() + AndroidUtilities.dp(6 + (!currentMessageObject.isOutOwner() && !mediaBackground && !drawPinnedBottom ? 6 : 0)) + getExtraTextX()), + (int) (replyStartY - AndroidUtilities.dp(drawNameLayout ? 3 : (3 + (!mediaBackground && drawPinnedTop ? 2 : 0)))), + (int) ((currentMessagesGroup != null ? replyStartX + Math.max(replyNameWidth, replyTextWidth) + AndroidUtilities.dp(10) : getBackgroundDrawableRight()) - AndroidUtilities.dp(6 + (currentMessageObject.isOutOwner() && !mediaBackground && !drawPinnedBottom ? 6 : 0)) - getExtraTextX()), + (int) (replyStartY + replyHeight + AndroidUtilities.dp(2)) + ); + } + if (replySelector == null) { + replySelector = Theme.createRadSelectorDrawable(replySelectorColor = rippleColor, 0, 0); + replySelector.setCallback(this); + } + replySelector.setBounds(replySelectorRect); + if (leftRad != replySelectorRadLeft || rightRad != replySelectorRadRight) { + Theme.setMaskDrawableRad(replySelector, replySelectorRadLeft = leftRad, replySelectorRadRight = rightRad, bottomRad, bottomRad); + } + if (rippleColor != replySelectorColor) { + Theme.setSelectorDrawableColor(replySelector, replySelectorColor = rippleColor, true); + } + replySelector.draw(canvas); + + AndroidUtilities.rectTmp.set(replyStartX, replyStartY, replyStartX + AndroidUtilities.dp(2), replyStartY + replyHeight); + canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(1), AndroidUtilities.dp(1), Theme.chat_replyLinePaint); if (needReplyImage) { replyImageReceiver.setAlpha(replyForwardAlpha); - replyImageReceiver.setImageCoords(replyStartX + AndroidUtilities.dp(10), replyStartY, AndroidUtilities.dp(35), AndroidUtilities.dp(35)); + replyImageReceiver.setImageCoords(replyStartX + offset, replyStartY, replyHeight, replyHeight); replyImageReceiver.draw(canvas); } if (replyNameLayout != null) { canvas.save(); - canvas.translate(replyStartX - replyNameOffset + AndroidUtilities.dp(10 + (needReplyImage ? 44 : 0)), replyStartY); + canvas.translate(replyStartX - replyNameOffset + offset + (needReplyImage ? offset - AndroidUtilities.dp(1) + replyHeight : 0), replyStartY); replyNameLayout.draw(canvas); canvas.restore(); } if (replyTextLayout != null) { canvas.save(); - canvas.translate(forwardNameX, replyStartY + AndroidUtilities.dp(19)); + canvas.translate(forwardNameX, replyStartY + Theme.chat_replyNamePaint.getTextSize() + AndroidUtilities.dp(5)); int spoilersColor = currentMessageObject.isOut() && !ChatObject.isChannelAndNotMegaGroup(currentMessageObject.getChatId(), currentAccount) ? getThemedColor(Theme.key_chat_outTimeText) : replyTextLayout.getPaint().getColor(); SpoilerEffect.renderWithRipple(this, invalidateSpoilersParent, spoilersColor, -AndroidUtilities.dp(2), spoilersPatchedReplyTextLayout, replyTextLayout, replySpoilers, canvas, false); AnimatedEmojiSpan.drawAnimatedEmojis(canvas, replyTextLayout, animatedEmojiReplyStack, 0, replySpoilers, 0, 0, 0, alpha); canvas.restore(); } + + if (getAlpha() * replyForwardAlpha != 1f) { + canvas.restore(); + } } } if (restore != Integer.MIN_VALUE) { @@ -13342,11 +14036,19 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate invalidate(); } + if (isRoundVideo) { + reactionsLayoutInBubble.drawServiceShaderBackground = 1f - getVideoTranscriptionProgress(); + } + if (!selectionOnly && (currentPosition == null || ((currentPosition.flags & MessageObject.POSITION_FLAG_BOTTOM) != 0 && (currentPosition.flags & MessageObject.POSITION_FLAG_LEFT) != 0)) && !reactionsLayoutInBubble.isSmall) { - if (reactionsLayoutInBubble.drawServiceShaderBackground) { + if (reactionsLayoutInBubble.drawServiceShaderBackground > 0) { applyServiceShaderMatrix(); } - if (reactionsLayoutInBubble.drawServiceShaderBackground || !transitionParams.animateBackgroundBoundsInner || currentPosition != null) { + if (getAlpha() != 1f) { + AndroidUtilities.rectTmp.set(0, 0, getWidth(), getHeight()); + canvas.saveLayerAlpha(AndroidUtilities.rectTmp, (int) (0xFF * getAlpha()), Canvas.ALL_SAVE_FLAG); + } + if (reactionsLayoutInBubble.drawServiceShaderBackground > 0 || !transitionParams.animateBackgroundBoundsInner || currentPosition != null || isRoundVideo) { reactionsLayoutInBubble.draw(canvas, transitionParams.animateChange ? transitionParams.animateChangeProgress : 1f, null); } else { canvas.save(); @@ -13354,6 +14056,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate reactionsLayoutInBubble.draw(canvas, transitionParams.animateChange ? transitionParams.animateChangeProgress : 1f, null); canvas.restore(); } + if (getAlpha() != 1f) { + canvas.restore(); + } } } @@ -13412,12 +14117,18 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate endX += AndroidUtilities.dp(14); buttonX -= AndroidUtilities.dp(10); } - commentButtonRect.set(buttonX, (int) buttonY, endX - AndroidUtilities.dp(14), layoutHeight - AndroidUtilities.dp(h)); + commentButtonRect.set(buttonX - AndroidUtilities.dp((currentMessageObject == null || !currentMessageObject.isOut()) && !drawPinnedBottom && currentPosition == null ? 6 : 0), (int) buttonY, endX - AndroidUtilities.dp(14), layoutHeight - AndroidUtilities.dp(h)); if (selectorDrawable[1] != null && selectorDrawableMaskType[1] == 2) { + int count = canvas.getSaveCount(); selectorDrawable[1].setBounds(commentButtonRect); selectorDrawable[1].draw(canvas); + canvas.restoreToCount(count); } if (currentPosition == null || (currentPosition.flags & MessageObject.POSITION_FLAG_LEFT) != 0 && currentPosition.minX == 0 && currentPosition.maxX == 0) { + if (getAlpha() != 1f) { + AndroidUtilities.rectTmp.set(0, 0, getWidth(), getHeight()); + canvas.saveLayerAlpha(AndroidUtilities.rectTmp, (int) (0xFF * getAlpha()), Canvas.ALL_SAVE_FLAG); + } Theme.chat_instantViewPaint.setColor(getThemedColor(Theme.key_chat_inPreviewInstantText)); boolean drawnAvatars = false; int avatarsOffset = 2; @@ -13440,6 +14151,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } } + if (getAlpha() != 1f) { + canvas.restore(); + } if (!mediaBackground || captionLayout != null || (!reactionsLayoutInBubble.isEmpty && !reactionsLayoutInBubble.isSmall)) { if (isDrawSelectionBackground()) { Theme.chat_replyLinePaint.setColor(getThemedColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outVoiceSeekbarSelected : Theme.key_chat_inVoiceSeekbarSelected)); @@ -13457,19 +14171,20 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate int backgroundWidth = (int) (this.backgroundWidth - transitionParams.deltaLeft + transitionParams.deltaRight); endX = x + backgroundWidth - AndroidUtilities.dp(12); } + Theme.chat_replyLinePaint.setAlpha((int) (Theme.chat_replyLinePaint.getAlpha() * ChatMessageCell.this.getAlpha())); canvas.drawLine(x, ly, endX - AndroidUtilities.dp(14), ly, Theme.chat_replyLinePaint); } if (commentLayout != null && drawSideButton != 3) { - Theme.chat_replyNamePaint.setColor(getThemedColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outPreviewInstantText : Theme.key_chat_inPreviewInstantText)); + Theme.chat_commentTextPaint.setColor(getThemedColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outPreviewInstantText : Theme.key_chat_inPreviewInstantText)); commentX = x + AndroidUtilities.dp(33 + avatarsOffset); if (drawCommentNumber) { commentX += commentNumberWidth + AndroidUtilities.dp(4); } - int prevAlpha = Theme.chat_replyNamePaint.getAlpha(); + int prevAlpha = Theme.chat_commentTextPaint.getAlpha(); if (transitionParams.animateComments) { if (transitionParams.animateCommentsLayout != null) { canvas.save(); - Theme.chat_replyNamePaint.setAlpha((int) (prevAlpha * (1.0 - transitionParams.animateChangeProgress))); + Theme.chat_commentTextPaint.setAlpha((int) (prevAlpha * (1.0 - transitionParams.animateChangeProgress))); float cx = transitionParams.animateCommentX + (commentX - transitionParams.animateCommentX) * transitionParams.animateChangeProgress; canvas.translate(cx, y - AndroidUtilities.dp(0.1f) + (pinnedBottom ? AndroidUtilities.dp(2) : 0)); transitionParams.animateCommentsLayout.draw(canvas); @@ -13479,7 +14194,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate canvas.save(); canvas.translate(x + AndroidUtilities.dp(33 + avatarsOffset), y - AndroidUtilities.dp(0.1f) + (pinnedBottom ? AndroidUtilities.dp(2) : 0)); if (!currentMessageObject.isSent()) { - Theme.chat_replyNamePaint.setAlpha(127); + Theme.chat_commentTextPaint.setAlpha(127); Theme.chat_commentArrowDrawable.setAlpha(127); Theme.chat_commentDrawable.setAlpha(127); } else { @@ -13489,21 +14204,23 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (drawCommentNumber || transitionParams.animateComments && transitionParams.animateDrawCommentNumber) { if (drawCommentNumber && transitionParams.animateComments) { if (transitionParams.animateDrawCommentNumber) { - Theme.chat_replyNamePaint.setAlpha(prevAlpha); + Theme.chat_commentTextPaint.setAlpha(prevAlpha); } else { - Theme.chat_replyNamePaint.setAlpha((int) (prevAlpha * transitionParams.animateChangeProgress)); + Theme.chat_commentTextPaint.setAlpha((int) (prevAlpha * transitionParams.animateChangeProgress)); } } + Theme.chat_commentTextPaint.setAlpha((int) (Theme.chat_commentTextPaint.getAlpha() * ChatMessageCell.this.getAlpha())); commentNumberLayout.draw(canvas); if (drawCommentNumber) { canvas.translate(commentNumberWidth + AndroidUtilities.dp(4), 0); } } if (transitionParams.animateComments && transitionParams.animateCommentsLayout != null) { - Theme.chat_replyNamePaint.setAlpha((int) (prevAlpha * transitionParams.animateChangeProgress)); + Theme.chat_commentTextPaint.setAlpha((int) (prevAlpha * transitionParams.animateChangeProgress)); } else { - Theme.chat_replyNamePaint.setAlpha((int) (prevAlpha * alpha)); + Theme.chat_commentTextPaint.setAlpha((int) (prevAlpha * alpha)); } + Theme.chat_commentTextPaint.setAlpha((int) (Theme.chat_commentTextPaint.getAlpha() * ChatMessageCell.this.getAlpha())); commentLayout.draw(canvas); canvas.restore(); commentUnreadX = x + commentWidth + AndroidUtilities.dp(33 + avatarsOffset) + AndroidUtilities.dp(9); @@ -13625,17 +14342,30 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else if (transitionParams.moveCaption) { captionX = this.captionX * transitionParams.animateChangeProgress + transitionParams.captionFromX * (1f - transitionParams.animateChangeProgress); captionY = this.captionY * transitionParams.animateChangeProgress + transitionParams.captionFromY * (1f - transitionParams.animateChangeProgress); - } else if (!currentMessageObject.isVoice() || !TextUtils.isEmpty(currentMessageObject.caption)) { + } else if ((!currentMessageObject.isVoice() && !currentMessageObject.isRoundVideo()) || !TextUtils.isEmpty(currentMessageObject.caption)) { captionX += transitionParams.deltaLeft; } } + + if (isRoundVideo && transitionParams.animateDrawBackground) { + captionY += (1f - getVideoTranscriptionProgress()) * AndroidUtilities.roundMessageSize; + } int restore = Integer.MIN_VALUE; if (renderingAlpha != 1.0f) { rect.set(captionX, captionY, captionX + captionLayout.getWidth(), captionY + captionLayout.getHeight()); restore = canvas.saveLayerAlpha(rect, (int) (255 * renderingAlpha), Canvas.ALL_SAVE_FLAG); } - if (transitionParams.animateBackgroundBoundsInner && currentBackgroundDrawable != null && currentMessagesGroup == null) { + if (isRoundVideo && transitionParams.animateDrawBackground) { + if (backgroundCacheParams != null) { + Path path = backgroundCacheParams.getPath(); + if (path != null && !path.isEmpty()) { + canvas.translate(0, transitionYOffsetForDrawables); + canvas.clipPath(path); + canvas.translate(0, -transitionYOffsetForDrawables); + } + } + } else if (transitionParams.animateBackgroundBoundsInner && currentBackgroundDrawable != null && currentMessagesGroup == null) { int bottomoffset = (drawCommentButton && commentButtonRect != null ? commentButtonRect.height() : 0) + (reactionsLayoutInBubble != null && !reactionsLayoutInBubble.isSmall ? reactionsLayoutInBubble.height : 0); if (currentMessageObject.isOutOwner() && !mediaBackground && !pinnedBottom) { canvas.clipRect( @@ -13659,6 +14389,18 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate canvas.drawPath(urlPathSelection.get(b), Theme.chat_textSearchSelectionPaint); } } + if (currentMessageObject.type == MessageObject.TYPE_ROUND_VIDEO) { + Theme.chat_timePaint.setColor( + ColorUtils.blendARGB( + getThemedColor(Theme.key_chat_serviceText), + getThemedColor(isDrawSelectionBackground() ? + (currentMessageObject.isOutOwner() ? Theme.key_chat_outTimeSelectedText : Theme.key_chat_inTimeSelectedText) : + (currentMessageObject.isOutOwner() ? Theme.key_chat_outTimeText : Theme.key_chat_inTimeText) + ), + getVideoTranscriptionProgress() + ) + ); + } if (!selectionOnly) { try { if (getDelegate() != null && getDelegate().getTextSelectionHelper() != null && getDelegate().getTextSelectionHelper().isSelected(currentMessageObject)) { @@ -13744,11 +14486,20 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } private void drawTimeInternal(Canvas canvas, float alpha, boolean fromParent, float timeX, StaticLayout timeLayout, float timeWidth, boolean drawSelectionBackground) { - if ((!drawTime || groupPhotoInvisible) && shouldDrawTimeOnMedia() || timeLayout == null || (currentMessageObject.deleted && currentPosition != null) || currentMessageObject.type == 16) { + if ((!drawTime || groupPhotoInvisible) && shouldDrawTimeOnMedia() || timeLayout == null || (currentMessageObject.deleted && currentPosition != null) || currentMessageObject.type == MessageObject.TYPE_PHONE_CALL) { return; } if (currentMessageObject.type == MessageObject.TYPE_ROUND_VIDEO) { - Theme.chat_timePaint.setColor(getThemedColor(Theme.key_chat_serviceText)); + Theme.chat_timePaint.setColor( + ColorUtils.blendARGB( + getThemedColor(Theme.key_chat_serviceText), + getThemedColor(isDrawSelectionBackground() ? + (currentMessageObject.isOutOwner() ? Theme.key_chat_outTimeSelectedText : Theme.key_chat_inTimeSelectedText) : + (currentMessageObject.isOutOwner() ? Theme.key_chat_outTimeText : Theme.key_chat_inTimeText) + ), + getVideoTranscriptionProgress() + ) + ); } else { if (shouldDrawTimeOnMedia()) { if (currentMessageObject.shouldDrawWithoutBackground()) { @@ -13775,7 +14526,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate canvas.translate(0, AndroidUtilities.dp(2)); } boolean bigRadius = false; - float layoutHeight = this.layoutHeight + transitionParams.deltaBottom; + float layoutHeight = this.layoutHeight; + if (transitionParams.animateBackgroundBoundsInner) { + layoutHeight += transitionParams.deltaBottom; + } float timeTitleTimeX = timeX; if (transitionParams.shouldAnimateTimeX) { timeTitleTimeX = transitionParams.animateFromTimeX * (1f - transitionParams.animateChangeProgress) + this.timeX * transitionParams.animateChangeProgress; @@ -13825,9 +14579,15 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate r = AndroidUtilities.dp(4); } float x1 = timeX - AndroidUtilities.dp(bigRadius ? 6 : 4); - float timeY = photoImage.getImageY2() + additionalTimeOffsetY; + float timeY; + if (documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND) { + timeY = layoutHeight - (AndroidUtilities.dp(drawPinnedBottom ? 4 : 5) + reactionsLayoutInBubble.getCurrentTotalHeight(transitionParams.animateChangeProgress)) * (1f - getVideoTranscriptionProgress()); + } else { + timeY = photoImage.getImageY2() + additionalTimeOffsetY; + } float y1 = timeY - AndroidUtilities.dp(23); - rect.set(x1, y1, x1 + timeWidth + AndroidUtilities.dp((bigRadius ? 12 : 8) + (currentMessageObject.isOutOwner() ? 20 + (currentMessageObject.type == MessageObject.TYPE_EMOJIS ? 4 : 0) : 0)), y1 + AndroidUtilities.dp(17)); + float timeHeight = Math.max(AndroidUtilities.dp(17), Theme.chat_timePaint.getTextSize() + AndroidUtilities.dp(5)); + rect.set(x1, y1, x1 + timeWidth + AndroidUtilities.dp((bigRadius ? 12 : 8) + (currentMessageObject.isOutOwner() ? 20 + (currentMessageObject.type == MessageObject.TYPE_EMOJIS ? 4 : 0) : 0)), y1 + timeHeight); applyServiceShaderMatrix(); canvas.drawRoundRect(rect, r, r, paint); @@ -14058,7 +14818,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate unlockSpoilerPath.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(32), AndroidUtilities.dp(32), Path.Direction.CW); canvas.clipPath(unlockSpoilerPath, Region.Op.DIFFERENCE); - int sColor = Theme.getColor(Theme.key_windowBackgroundWhite); + int sColor = Color.WHITE; unlockSpoilerEffect.setColor(ColorUtils.setAlphaComponent(sColor, (int) (Color.alpha(sColor) * 0.325f))); unlockSpoilerEffect.setBounds((int) photoImage.getImageX(), (int) photoImage.getImageY(), (int) photoImage.getImageX2(), (int) photoImage.getImageY2()); unlockSpoilerEffect.draw(canvas); @@ -14160,7 +14920,15 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate clockColor = getThemedColor(drawSelectionBackground ? Theme.key_chat_outSentClockSelected : Theme.key_chat_mediaSentClock); } clockDrawable.setColor(clockColor); - float timeY = shouldDrawTimeOnMedia() ? photoImage.getImageY2() + additionalTimeOffsetY - AndroidUtilities.dp(9.0f) : layoutHeight - AndroidUtilities.dp(pinnedBottom || pinnedTop ? 9.5f : 8.5f) + timeYOffset; + float timeY; + if (shouldDrawTimeOnMedia()) { + timeY = photoImage.getImageY2() + additionalTimeOffsetY - AndroidUtilities.dp(9.0f); + } else { + timeY = layoutHeight - AndroidUtilities.dp(pinnedBottom || pinnedTop ? 9.5f : 8.5f) + timeYOffset; + if (isRoundVideo) { + timeY -= (AndroidUtilities.dp(drawPinnedBottom ? 4 : 5) + reactionsLayoutInBubble.getCurrentTotalHeight(transitionParams.animateChangeProgress)) * (1f - getVideoTranscriptionProgress()); + } + } setDrawableBounds(clockDrawable, timeX + (currentMessageObject.scheduled ? 0 : AndroidUtilities.dp(11)), timeY - clockDrawable.getIntrinsicHeight()); clockDrawable.setAlpha((int) (255 * alpha)); if (useScale) { @@ -14177,7 +14945,15 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else if (drawError) { if (!currentMessageObject.isOutOwner()) { float x = timeX + (currentMessageObject.scheduled ? 0 : AndroidUtilities.dp(11)); - float y = shouldDrawTimeOnMedia() ? photoImage.getImageY2() + additionalTimeOffsetY - AndroidUtilities.dp(21.5f) : layoutHeight - AndroidUtilities.dp(pinnedBottom || pinnedTop ? 21.5f : 20.5f) + timeYOffset; + float y; + if (shouldDrawTimeOnMedia()) { + y = photoImage.getImageY2() + additionalTimeOffsetY - AndroidUtilities.dp(21.5f); + } else { + y = layoutHeight - AndroidUtilities.dp(pinnedBottom || pinnedTop ? 21.5f : 20.5f) + timeYOffset; + if (isRoundVideo) { + y -= reactionsLayoutInBubble.getCurrentTotalHeight(transitionParams.animateChangeProgress) * (1f - getVideoTranscriptionProgress()); + } + } rect.set(x, y, x + AndroidUtilities.dp(14), y + AndroidUtilities.dp(14)); int oldAlpha = Theme.chat_msgErrorPaint.getAlpha(); Theme.chat_msgErrorPaint.setAlpha((int) (255 * alpha)); @@ -14206,20 +14982,30 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate float offsetX = reactionsLayoutInBubble.isSmall ? reactionsLayoutInBubble.getCurrentWidth(1f) : 0; int timeAlpha = Theme.chat_timePaint.getAlpha(); - float timeY = shouldDrawTimeOnMedia() ? photoImage.getImageY2() + additionalTimeOffsetY - AndroidUtilities.dp(7.3f) - timeLayout.getHeight() : layoutHeight - AndroidUtilities.dp(pinnedBottom || pinnedTop ? 7.5f : 6.5f) - timeLayout.getHeight() + timeYOffset; + float timeY; + if (shouldDrawTimeOnMedia() && documentAttachType != DOCUMENT_ATTACH_TYPE_ROUND) { + timeY = photoImage.getImageY2() + additionalTimeOffsetY - AndroidUtilities.dp(7.3f) - timeLayout.getHeight(); + } else { + timeY = layoutHeight - AndroidUtilities.dp(pinnedBottom || pinnedTop ? 7.5f : 6.5f) - timeLayout.getHeight() + timeYOffset; + if (isRoundVideo) { + timeY -= (AndroidUtilities.dp(drawPinnedBottom ? 4 : 5) + reactionsLayoutInBubble.getCurrentTotalHeight(transitionParams.animateChangeProgress)) * (1f - getVideoTranscriptionProgress()); + } + } if (repliesLayout != null || transitionParams.animateReplies) { float repliesX = (transitionParams.shouldAnimateTimeX ? this.timeX : timeX) + offsetX; boolean inAnimation = transitionParams.animateReplies && transitionParams.animateRepliesLayout == null && repliesLayout != null; boolean outAnimation = transitionParams.animateReplies && transitionParams.animateRepliesLayout != null && repliesLayout == null; boolean replaceAnimation = transitionParams.animateReplies && transitionParams.animateRepliesLayout != null && repliesLayout != null; - if (transitionParams.shouldAnimateTimeX && !inAnimation) { - if (outAnimation) { - repliesX = transitionParams.animateFromTimeXReplies; + if (!(isRoundVideo && transitionParams.animateDrawBackground)) { + if (transitionParams.shouldAnimateTimeX && !inAnimation) { + if (outAnimation) { + repliesX = transitionParams.animateFromTimeXReplies; + } else { + repliesX = transitionParams.animateFromTimeXReplies * (1f - transitionParams.animateChangeProgress) + repliesX * transitionParams.animateChangeProgress; + } } else { - repliesX = transitionParams.animateFromTimeXReplies * (1f - transitionParams.animateChangeProgress) + repliesX * transitionParams.animateChangeProgress; + repliesX += transitionParams.deltaRight; } - } else { - repliesX += transitionParams.deltaRight; } if (currentMessagesGroup != null && currentMessagesGroup.transitionParams.backgroundChangeBounds) { repliesX += currentMessagesGroup.transitionParams.offsetRight; @@ -14228,6 +15014,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate repliesX += animationOffsetX; } Drawable repliesDrawable; + float w; if (shouldDrawTimeOnMedia()) { if (currentMessageObject.shouldDrawWithoutBackground()) { repliesDrawable = getThemedDrawable(Theme.key_drawable_msgStickerReplies); @@ -14241,7 +15028,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate repliesDrawable = getThemedDrawable(drawSelectionBackground ? Theme.key_drawable_msgOutRepliesSelected : Theme.key_drawable_msgOutReplies); } } - setDrawableBounds(repliesDrawable, repliesX, timeY); + w = setDrawableBounds(repliesDrawable, repliesX, timeY, Theme.chat_timePaint.getTextSize()); float repliesAlpha = alpha; if (inAnimation) { repliesAlpha *= transitionParams.animateChangeProgress; @@ -14251,7 +15038,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate repliesDrawable.setAlpha((int) (255 * repliesAlpha)); if (useScale) { canvas.save(); - float cx = repliesX + (repliesDrawable.getIntrinsicWidth() + AndroidUtilities.dp(3) + repliesTextWidth) / 2f; + float cx = repliesX + (w + AndroidUtilities.dp(3) + repliesTextWidth) / 2f; canvas.scale(scale, scale, cx, repliesDrawable.getBounds().centerY()); } repliesDrawable.draw(canvas); @@ -14261,14 +15048,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (replaceAnimation) { canvas.save(); Theme.chat_timePaint.setAlpha((int) (timeAlpha * (1.0 - transitionParams.animateChangeProgress))); - canvas.translate(repliesX + repliesDrawable.getIntrinsicWidth() + AndroidUtilities.dp(3), timeY); + canvas.translate(repliesX + w + AndroidUtilities.dp(3), timeY); transitionParams.animateRepliesLayout.draw(canvas); canvas.restore(); } Theme.chat_timePaint.setAlpha((int) (timeAlpha * repliesAlpha)); } canvas.save(); - canvas.translate(repliesX + repliesDrawable.getIntrinsicWidth() + AndroidUtilities.dp(3), timeY); + canvas.translate(repliesX + w + AndroidUtilities.dp(3), timeY); if (repliesLayout != null) { repliesLayout.draw(canvas); } else if (transitionParams.animateRepliesLayout != null) { @@ -14276,7 +15063,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } canvas.restore(); if (repliesLayout != null) { - offsetX += repliesDrawable.getIntrinsicWidth() + repliesTextWidth + AndroidUtilities.dp(10); + offsetX += w + repliesTextWidth + AndroidUtilities.dp(10); } if (useScale) { @@ -14290,10 +15077,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } if (viewsLayout != null) { float viewsX = (transitionParams.shouldAnimateTimeX ? this.timeX : timeX) + offsetX; - if (transitionParams.shouldAnimateTimeX) { - viewsX = transitionParams.animateFromTimeXViews * (1f - transitionParams.animateChangeProgress) + viewsX * transitionParams.animateChangeProgress; - } else { - viewsX += transitionParams.deltaRight; + if (!(isRoundVideo && transitionParams.animateDrawBackground)) { + if (transitionParams.shouldAnimateTimeX) { + viewsX = transitionParams.animateFromTimeXViews * (1f - transitionParams.animateChangeProgress) + viewsX * transitionParams.animateChangeProgress; + } else { + viewsX += transitionParams.deltaRight; + } } if (currentMessagesGroup != null && currentMessagesGroup.transitionParams.backgroundChangeBounds) { viewsX += currentMessagesGroup.transitionParams.offsetRight; @@ -14315,8 +15104,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate viewsDrawable = getThemedDrawable(drawSelectionBackground ? Theme.key_drawable_msgOutViewsSelected : Theme.key_drawable_msgOutViews); } } - float y = shouldDrawTimeOnMedia() ? photoImage.getImageY2() + additionalTimeOffsetY - AndroidUtilities.dp(5.5f) - timeLayout.getHeight() : layoutHeight - AndroidUtilities.dp(pinnedBottom || pinnedTop ? 5.5f : 4.5f) - timeLayout.getHeight() + timeYOffset; - setDrawableBounds(viewsDrawable, viewsX, y); + float y = timeY; + float w = setDrawableBounds(viewsDrawable, viewsX, y + AndroidUtilities.dp(1.5f), Theme.chat_timePaint.getTextSize() - AndroidUtilities.dp(2)); if (useScale) { canvas.save(); float cx = viewsX + (viewsDrawable.getIntrinsicWidth() + AndroidUtilities.dp(3) + viewsTextWidth) / 2f; @@ -14329,21 +15118,21 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (transitionParams.animateViewsLayout != null) { canvas.save(); Theme.chat_timePaint.setAlpha((int) (timeAlpha * (1.0 - transitionParams.animateChangeProgress))); - canvas.translate(viewsX + viewsDrawable.getIntrinsicWidth() + AndroidUtilities.dp(3), timeY); + canvas.translate(viewsX + w + AndroidUtilities.dp(3), y); transitionParams.animateViewsLayout.draw(canvas); canvas.restore(); Theme.chat_timePaint.setAlpha((int) (timeAlpha * transitionParams.animateChangeProgress)); } canvas.save(); - canvas.translate(viewsX + viewsDrawable.getIntrinsicWidth() + AndroidUtilities.dp(3), timeY); + canvas.translate(viewsX + w + AndroidUtilities.dp(3), y); viewsLayout.draw(canvas); canvas.restore(); if (useScale) { canvas.restore(); } - offsetX += viewsTextWidth + Theme.chat_msgInViewsDrawable.getIntrinsicWidth() + AndroidUtilities.dp(10); + offsetX += viewsTextWidth + w + AndroidUtilities.dp(10); if (transitionParams.animateViewsLayout != null) { Theme.chat_timePaint.setAlpha(timeAlpha); @@ -14354,14 +15143,16 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate float pinnedX = (transitionParams.shouldAnimateTimeX ? this.timeX : timeX) + offsetX; boolean inAnimation = transitionParams.animatePinned && isPinned; boolean outAnimation = transitionParams.animatePinned && !isPinned; - if (transitionParams.shouldAnimateTimeX && !inAnimation) { - if (outAnimation) { - pinnedX = transitionParams.animateFromTimeXPinned; + if (!isRoundVideo) { + if (transitionParams.shouldAnimateTimeX && !inAnimation) { + if (outAnimation) { + pinnedX = transitionParams.animateFromTimeXPinned; + } else { + pinnedX = transitionParams.animateFromTimeXPinned * (1f - transitionParams.animateChangeProgress) + pinnedX * transitionParams.animateChangeProgress; + } } else { - pinnedX = transitionParams.animateFromTimeXPinned * (1f - transitionParams.animateChangeProgress) + pinnedX * transitionParams.animateChangeProgress; + pinnedX += transitionParams.deltaRight; } - } else { - pinnedX += transitionParams.deltaRight; } if (currentMessagesGroup != null && currentMessagesGroup.transitionParams.backgroundChangeBounds) { pinnedX += currentMessagesGroup.transitionParams.offsetRight; @@ -14384,21 +15175,22 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate pinnedDrawable = getThemedDrawable(drawSelectionBackground ? Theme.key_drawable_msgOutPinnedSelected : Theme.key_drawable_msgOutPinned); } } + float w; if (transitionParams.animatePinned) { if (isPinned) { pinnedDrawable.setAlpha((int) (255 * alpha * transitionParams.animateChangeProgress)); - setDrawableBounds(pinnedDrawable, pinnedX, timeY); + w = setDrawableBounds(pinnedDrawable, pinnedX, timeY, Theme.chat_timePaint.getTextSize() + AndroidUtilities.dp(1)); } else { pinnedDrawable.setAlpha((int) (255 * alpha * (1.0f - transitionParams.animateChangeProgress))); - setDrawableBounds(pinnedDrawable, pinnedX, timeY); + w = setDrawableBounds(pinnedDrawable, pinnedX, timeY, Theme.chat_timePaint.getTextSize() + AndroidUtilities.dp(1)); } } else { pinnedDrawable.setAlpha((int) (255 * alpha)); - setDrawableBounds(pinnedDrawable, pinnedX, timeY); + w = setDrawableBounds(pinnedDrawable, pinnedX, timeY, Theme.chat_timePaint.getTextSize() + AndroidUtilities.dp(1)); } if (useScale) { canvas.save(); - float cx = pinnedX + pinnedDrawable.getIntrinsicWidth() / 2f; + float cx = pinnedX + w / 2f; canvas.scale(scale, scale, cx, pinnedDrawable.getBounds().centerY()); } pinnedDrawable.draw(canvas); @@ -14417,7 +15209,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (useScale) { alpha = alpha * progress; } - float timeY = photoImage.getImageY2() + additionalTimeOffsetY - AndroidUtilities.dp(8.5f); + float timeY; + if (documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND) { + timeY = layoutHeight - (AndroidUtilities.dp(drawPinnedBottom ? 4 : 5) + reactionsLayoutInBubble.getCurrentTotalHeight(transitionParams.animateChangeProgress)) * (1f - getVideoTranscriptionProgress()); + } else { + timeY = photoImage.getImageY2() + additionalTimeOffsetY; + } + timeY -= AndroidUtilities.dp(8.5f); if (drawClock) { MsgClockDrawable drawable = Theme.chat_msgClockDrawable; int color; @@ -14645,7 +15443,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { drawLoadingProgress = (buttonState == 1 || miniButtonState == 1 || animatingLoadingProgressProgress != 0) && !currentMessageObject.isSecretMedia() && (documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || documentAttachType == DOCUMENT_ATTACH_TYPE_GIF || documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT); - if (currentMessageObject.type == MessageObject.TYPE_VIDEO || currentMessageObject.type == 8 || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) { + if (currentMessageObject.type == MessageObject.TYPE_VIDEO || currentMessageObject.type == MessageObject.TYPE_GIF || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) { alpha = currentMessageObject.needDrawBluredPreview() && docTitleLayout == null ? 0 : animatingDrawVideoImageButtonProgress; } drawDocTitleLayout = alpha > 0 && docTitleLayout != null; @@ -14882,7 +15680,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate canvas.restore(); } } - } else if (currentMessageObject.type == 16) { + } else if (currentMessageObject.type == MessageObject.TYPE_PHONE_CALL) { if (currentMessageObject.isOutOwner()) { Theme.chat_audioTitlePaint.setColor(getThemedColor(Theme.key_chat_messageTextOut)); Theme.chat_contactPhonePaint.setColor(getThemedColor(isDrawSelectionBackground() ? Theme.key_chat_outTimeSelectedText : Theme.key_chat_outTimeText)); @@ -15257,7 +16055,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } updatePollAnimations(dt); canvas.restore(); - } else if (currentMessageObject.type == 12) { + } else if (currentMessageObject.type == MessageObject.TYPE_CONTACT) { if (currentMessageObject.isOutOwner()) { Theme.chat_contactNamePaint.setColor(getThemedColor(Theme.key_chat_outContactNameText)); Theme.chat_contactPhonePaint.setColor(getThemedColor(isDrawSelectionBackground() ? Theme.key_chat_outContactPhoneSelectedText : Theme.key_chat_outContactPhoneText)); @@ -15330,8 +16128,37 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { radialProgress.setBackgroundDrawable(isDrawSelectionBackground() ? currentBackgroundSelectedDrawable : currentBackgroundDrawable); } - if (!currentMessageObject.needDrawBluredPreview() || !MediaController.getInstance().isPlayingMessage(currentMessageObject)) { + boolean restore = false; + if (currentMessageObject.isOutOwner() && !currentMessageObject.isSent()) { + radialProgress.setProgressRect( + photoImage.getImageX() + (photoImage.getImageWidth() - radialProgress.getRadius()) / 2f, + photoImage.getImageY() + (photoImage.getImageHeight() - radialProgress.getRadius()) / 2f, + photoImage.getImageX() + (photoImage.getImageWidth() + radialProgress.getRadius()) / 2f, + photoImage.getImageY() + (photoImage.getImageHeight() + radialProgress.getRadius()) / 2f + ); + } else if (currentMessageObject != null && currentMessageObject.isRoundVideo()) { + radialProgress.setProgressRect( + photoImage.getImageX(), + photoImage.getImageY(), + photoImage.getImageX() + photoImage.getImageWidth(), + photoImage.getImageY() + photoImage.getImageHeight() + ); + canvas.saveLayerAlpha(radialProgress.getProgressRect(), (int) (255 * getVideoTranscriptionProgress()), Canvas.ALL_SAVE_FLAG); + float scale = photoImage.getImageHeight() / (radialProgress.getRadius() * 2); + canvas.scale(scale, scale, radialProgress.getProgressRect().centerX(), radialProgress.getProgressRect().centerY()); + restore = true; + } + if ((!isRoundVideo || !hasLinkPreview) && (!currentMessageObject.needDrawBluredPreview() || !MediaController.getInstance().isPlayingMessage(currentMessageObject))) { + if (isRoundVideo) { + radialProgress.overrideCircleAlpha = .25f + .75f * (1f - getVideoTranscriptionProgress()); + } radialProgress.draw(canvas); + if (isRoundVideo) { + radialProgress.overrideCircleAlpha = 1f; + } + } + if (restore) { + canvas.restore(); } } if (buttonState == -1 && currentMessageObject.needDrawBluredPreview() && !MediaController.getInstance().isPlayingMessage(currentMessageObject) && photoImage.getVisible() && currentMessageObject.messageOwner.destroyTime != 0) { @@ -15383,7 +16210,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } - x1 = backgroundDrawableLeft + transitionParams.deltaLeft + AndroidUtilities.dp(8) + roundPlayingDrawableProgress + offsetX; + x1 = backgroundDrawableLeft + transitionParams.deltaLeft + (!currentMessageObject.isOutOwner() && !drawPinnedBottom && drawBackground ? AndroidUtilities.dp(6) : 0) + AndroidUtilities.dp(8) + roundPlayingDrawableProgress + offsetX; y1 = layoutHeight - AndroidUtilities.dp(28 - (drawPinnedBottom ? 2 : 0)); if (!reactionsLayoutInBubble.isEmpty) { y1 -= reactionsLayoutInBubble.totalHeight; @@ -15392,15 +16219,25 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (transitionParams.animateRoundVideoDotY) { y1 = transitionParams.animateFromRoundVideoDotY * (1f - transitionParams.animateChangeProgress) + y1 * transitionParams.animateChangeProgress; } + + int timeAudioX = this.timeAudioX; + if (!hasLinkPreview) { + if (currentMessageObject.isOutOwner()) { + timeAudioX = (getWidth() - ((transitionParams.animateBackgroundBoundsInner ? (int) (backgroundWidth - transitionParams.deltaLeft + transitionParams.deltaRight) : backgroundWidth)) - (!drawPinnedBottom && mediaBackground ? AndroidUtilities.dp(8) : 0)) + AndroidUtilities.dp(67); + } + x1 = AndroidUtilities.lerp(x1, timeAudioX - AndroidUtilities.dp(4), getVideoTranscriptionProgress()); + y1 = AndroidUtilities.lerp(y1, AndroidUtilities.dp(44) + namesOffset + mediaOffsetY - AndroidUtilities.dp(1.7f), getVideoTranscriptionProgress()); + } + rect.set(x1, y1, x1 + timeWidthAudio + AndroidUtilities.dp(8 + 12 + 2), y1 + AndroidUtilities.dp(17)); int oldAlpha = getThemedPaint(Theme.key_paint_chatActionBackground).getAlpha(); - getThemedPaint(Theme.key_paint_chatActionBackground).setAlpha((int) (oldAlpha * timeAlpha)); + getThemedPaint(Theme.key_paint_chatActionBackground).setAlpha((int) (oldAlpha * timeAlpha * (1f - getVideoTranscriptionProgress()))); applyServiceShaderMatrix(); canvas.drawRoundRect(rect, AndroidUtilities.dp(6), AndroidUtilities.dp(6), getThemedPaint(Theme.key_paint_chatActionBackground)); if (hasGradientService()) { int oldAlpha2 = Theme.chat_actionBackgroundGradientDarkenPaint.getAlpha(); - Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha((int) (oldAlpha2 * timeAlpha)); + Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha((int) (oldAlpha2 * timeAlpha * (1f - getVideoTranscriptionProgress()))); canvas.drawRoundRect(rect, AndroidUtilities.dp(6), AndroidUtilities.dp(6), Theme.chat_actionBackgroundGradientDarkenPaint); Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha(oldAlpha2); } @@ -15435,7 +16272,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate float cy = y1 + AndroidUtilities.dp(8.3f); canvas.save(); canvas.scale((1f - roundPlayingDrawableProgress), (1f - roundPlayingDrawableProgress), cx, cy); - Theme.chat_docBackPaint.setColor(getThemedColor(Theme.key_chat_serviceText)); + Theme.chat_docBackPaint.setColor(Theme.chat_timePaint.getColor()); Theme.chat_docBackPaint.setAlpha((int) (255 * timeAlpha * (1f - roundPlayingDrawableProgress))); canvas.drawCircle(cx, cy, AndroidUtilities.dp(3), Theme.chat_docBackPaint); canvas.restore(); @@ -15444,7 +16281,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate setDrawableBounds(roundVideoPlayingDrawable, x1 + timeWidthAudio + AndroidUtilities.dp(6), y1 + AndroidUtilities.dp(2.3f)); canvas.save(); canvas.scale(roundPlayingDrawableProgress, roundPlayingDrawableProgress, roundVideoPlayingDrawable.getBounds().centerX(), roundVideoPlayingDrawable.getBounds().centerY()); - roundVideoPlayingDrawable.setAlpha((int) (255 * roundPlayingDrawableProgress)); + if (!hasLinkPreview) { + roundVideoPlayingDrawable.timeColor = Theme.chat_timePaint.getColor(); + roundVideoPlayingDrawable.colorProgress = getVideoTranscriptionProgress(); + } else { + roundVideoPlayingDrawable.colorProgress = 0; + } + roundVideoPlayingDrawable.setAlpha((int) (255 * roundPlayingDrawableProgress * (1f - getVideoTranscriptionProgress()))); roundVideoPlayingDrawable.draw(canvas); canvas.restore(); } @@ -15489,6 +16332,20 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate return pinnedBottom; } + public float getVideoTranscriptionProgress() { + if (transitionParams == null || currentMessageObject == null || !currentMessageObject.isRoundVideo()) { + return 1; + } + if (transitionParams.animateDrawBackground) { + if (drawBackground) { + return transitionParams.animateChangeProgress; + } else { + return 1f - transitionParams.animateChangeProgress; + } + } + return drawBackground ? 1 : 0; + } + public boolean drawPinnedTop() { if (currentMessagesGroup != null && currentMessagesGroup.isDocuments) { if (currentPosition != null && (currentPosition.flags & MessageObject.POSITION_FLAG_TOP) != 0) { @@ -15528,7 +16385,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate int icon = getIconForCurrentState(); if (icon != MediaActionDrawable.ICON_NONE && icon != MediaActionDrawable.ICON_FILE) { didPressButton(true, false); - } else if (currentMessageObject.type == 16) { + } else if (currentMessageObject.type == MessageObject.TYPE_PHONE_CALL) { delegate.didPressOther(this, otherX, otherY); } else { didClickedImage(); @@ -15538,7 +16395,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate didPressMiniButton(true); } else if (action == R.id.acc_action_msg_options) { if (delegate != null) { - if (currentMessageObject.type == 16) { + if (currentMessageObject.type == MessageObject.TYPE_PHONE_CALL) { delegate.didLongPress(this, 0, 0); } else { delegate.didPressOther(this, otherX, otherY); @@ -15686,11 +16543,6 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } public int computeHeight(MessageObject object, MessageObject.GroupedMessages groupedMessages) { - /*if (object.type == 2 || object.type == 12 || object.type == 9 || - object.type == 4 || object.type == 14 || object.type == 10 || object.type == 11 || - object.type == MessageObject.TYPE_ROUND_VIDEO) { - return object.getApproximateHeight(); - }*/ photoImage.setIgnoreImageSet(true); avatarImage.setIgnoreImageSet(true); replyImageReceiver.setIgnoreImageSet(true); @@ -16083,7 +16935,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate actionLabel = LocaleController.getString("AccActionCancelDownload", R.string.AccActionCancelDownload); break; default: - if (currentMessageObject.type == 16) { + if (currentMessageObject.type == MessageObject.TYPE_PHONE_CALL) { actionLabel = LocaleController.getString("CallAgain", R.string.CallAgain); } } @@ -16374,7 +17226,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate info.setContentDescription(sb.toString()); info.addAction(AccessibilityNodeInfo.ACTION_CLICK); - rect.set(replyStartX, replyStartY, replyStartX + Math.max(replyNameWidth, replyTextWidth), replyStartY + AndroidUtilities.dp(35)); + rect.set(replyStartX, replyStartY, replyStartX + Math.max(replyNameWidth, replyTextWidth), replyStartY + (int) replyHeight); info.setBoundsInParent(rect); if (accessibilityVirtualViewBounds.get(virtualViewId) == null || !accessibilityVirtualViewBounds.get(virtualViewId).equals(rect)) { accessibilityVirtualViewBounds.put(virtualViewId, new Rect(rect)); @@ -16395,7 +17247,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate info.addAction(AccessibilityNodeInfo.ACTION_CLICK); int x = (int) Math.min(forwardNameX - forwardNameOffsetX[0], forwardNameX - forwardNameOffsetX[1]); - rect.set(x, forwardNameY, x + forwardedNameWidth, forwardNameY + AndroidUtilities.dp(32)); + rect.set(x, forwardNameY, x + forwardedNameWidth, forwardNameY + forwardHeight); info.setBoundsInParent(rect); if (accessibilityVirtualViewBounds.get(virtualViewId) == null || !accessibilityVirtualViewBounds.get(virtualViewId).equals(rect)) { accessibilityVirtualViewBounds.put(virtualViewId, new Rect(rect)); @@ -16434,7 +17286,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate info.setEnabled(true); info.setText(currentMessageObject.isVoiceTranscriptionOpen() ? LocaleController.getString("AccActionCloseTranscription", R.string.AccActionCloseTranscription) : LocaleController.getString("AccActionOpenTranscription", R.string.AccActionOpenTranscription)); info.addAction(AccessibilityNodeInfo.ACTION_CLICK); - rect.set((int) transcribeX, (int) transcribeY, (int) (transcribeX + AndroidUtilities.dp(30)), (int) (transcribeY + AndroidUtilities.dp(30))); + if (transcribeButton != null) { + rect.set((int) transcribeX, (int) transcribeY, (int) (transcribeX + transcribeButton.width()), (int) (transcribeY + transcribeButton.height())); + } info.setBoundsInParent(rect); rect.offset(pos[0], pos[1]); info.setBoundsInScreen(rect); @@ -16604,22 +17458,48 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { super.setAlpha(alpha); } + if ((currentPosition == null || currentPosition.minY == 0 && currentPosition.minX == 0) && !(enterTransitionInProgress && !currentMessageObject.isVoice()) && replyNameLayout != null && replyTextLayout != null || // Reply layout + (currentPosition == null || ((currentPosition.flags & MessageObject.POSITION_FLAG_BOTTOM) != 0 && (currentPosition.flags & MessageObject.POSITION_FLAG_LEFT) != 0)) && !reactionsLayoutInBubble.isSmall) { // Reactions + invalidate(); + } } public int getCurrentBackgroundLeft() { + if (currentBackgroundDrawable == null) { + return 0; + } int left = currentBackgroundDrawable.getBounds().left; - if (!currentMessageObject.isOutOwner() && transitionParams.changePinnedBottomProgress != 1 && (!mediaBackground && !drawPinnedBottom)) { - left -= AndroidUtilities.dp(6); + if (!currentMessageObject.isOutOwner() && transitionParams.changePinnedBottomProgress != 1 && (isRoundVideo || !mediaBackground) && !drawPinnedBottom) { + if (isRoundVideo) { + left -= AndroidUtilities.dp(6) * (getVideoTranscriptionProgress()); + } else { + left -= AndroidUtilities.dp(6); + } } return left; } + public int getCurrentBackgroundRight() { + if (currentBackgroundDrawable == null) { + return getWidth(); + } + int right = currentBackgroundDrawable.getBounds().right; + if (currentMessageObject.isOutOwner() && transitionParams.changePinnedBottomProgress != 1 && (isRoundVideo || !mediaBackground) && !drawPinnedBottom) { + if (isRoundVideo) { + right += AndroidUtilities.dp(6) * (getVideoTranscriptionProgress()); + } else { + right += AndroidUtilities.dp(6); + } + } + return right; + } + public TransitionParams getTransitionParams() { return transitionParams; } public int getTopMediaOffset() { - if (currentMessageObject != null && currentMessageObject.type == 14) { + if (currentMessageObject != null && currentMessageObject.type == MessageObject.TYPE_MUSIC) { return mediaOffsetY + namesOffset; } return 0; @@ -16751,6 +17631,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate public int animateFromTimeX; public boolean shouldAnimateTimeX; public int lastBackgroundLeft, lastBackgroundRight; + public boolean lastDrawBackground; + public boolean animateDrawBackground; + public boolean lastUseTranscribeButton; + public boolean animateUseTranscribeButton; public boolean animateDrawingTimeAlpha; @@ -16840,6 +17724,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate lastSignMessage = lastPostAuthor; + lastDrawBackground = drawBackground; + lastUseTranscribeButton = useTranscribeButton; + lastButtonX = buttonX; lastButtonY = buttonY; @@ -16953,6 +17840,18 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate changed = true; } + animateDrawBackground = false; + if (drawBackground != lastDrawBackground) { + animateDrawBackground = true; + changed = true; + } + + animateUseTranscribeButton = false; + if (useTranscribeButton != lastUseTranscribeButton) { + animateUseTranscribeButton = true; + changed = true; + } + if (captionLayout != lastDrawingCaptionLayout) { String oldCaption = lastDrawingCaptionLayout == null ? null : lastDrawingCaptionLayout.getText().toString(); String currentCaption = captionLayout == null ? null : captionLayout.getText().toString(); @@ -17063,7 +17962,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate animateTimeWidth = lastTimeWidth; changed = true; } else if (timeDrawablesIsChanged || Math.abs(timeX - lastTimeX) > 1) { - shouldAnimateTimeX = true; + shouldAnimateTimeX = true ; animateTimeWidth = lastTimeWidth; animateFromTimeX = lastTimeX; animateFromTimeXViews = lastTimeXViews; @@ -17180,6 +18079,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate animateShouldDrawTimeOnMedia = false; animateShouldDrawMenuDrawable = false; shouldAnimateTimeX = false; + animateDrawBackground = false; animateSign = false; animateDrawingTimeAlpha = false; animateLocationIsExpired = false; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ContextLinkCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ContextLinkCell.java index a1023a521..f04af8990 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ContextLinkCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ContextLinkCell.java @@ -143,7 +143,7 @@ public class ContextLinkCell extends FrameLayout implements DownloadController.F linkImageView = new ImageReceiver(this); linkImageView.setLayerNum(1); linkImageView.setUseSharedAnimationQueue(true); - letterDrawable = new LetterDrawable(resourcesProvider); + letterDrawable = new LetterDrawable(resourcesProvider, LetterDrawable.STYLE_DEFAULT); radialProgress = new RadialProgress2(this); TAG = DownloadController.getInstance(currentAccount).generateObserverTag(); setFocusable(true); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java index 9db8a0d07..e3f445f2e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java @@ -42,12 +42,14 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; import android.view.animation.OvershootInterpolator; +import androidx.core.content.ContextCompat; + import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ChatThemeController; import org.telegram.messenger.ContactsController; import org.telegram.messenger.DialogObject; -import org.telegram.messenger.DownloadController; import org.telegram.messenger.Emoji; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; @@ -70,9 +72,11 @@ import org.telegram.ui.Components.AnimatedEmojiDrawable; import org.telegram.ui.Components.AnimatedEmojiSpan; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.CheckBox2; +import org.telegram.ui.Components.ColoredImageSpan; import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.EmptyStubSpan; import org.telegram.ui.Components.ForegroundColorSpanThemable; +import org.telegram.ui.Components.Forum.ForumUtilities; import org.telegram.ui.Components.Premium.PremiumGradient; import org.telegram.ui.Components.PullForegroundDrawable; import org.telegram.ui.Components.RLottieDrawable; @@ -88,6 +92,7 @@ import org.telegram.ui.Components.spoilers.SpoilerEffect; import org.telegram.ui.DialogsActivity; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Stack; @@ -98,6 +103,19 @@ public class DialogCell extends BaseCell { private RLottieDrawable lastDrawTranslationDrawable; private int lastDrawSwipeMessageStringId; public boolean swipeCanceled; + public static final int SENT_STATE_NOTHING = -1; + public static final int SENT_STATE_PROGRESS = 0; + public static final int SENT_STATE_SENT = 1; + public static final int SENT_STATE_READ = 2; + public boolean drawAvatar = true; + public int messagePaddingStart = 72; + public int heightDefault = 72; + public int heightThreeLines = 78; + public TLRPC.TL_forumTopic forumTopic; + private boolean isTopic; + private boolean twoLinesForName; + private boolean nameIsEllipsized; + public float chekBoxPaddingTop = 42; public void setMoving(boolean moving) { this.moving = moving; @@ -107,6 +125,38 @@ public class DialogCell extends BaseCell { return moving; } + public void setForumTopic(TLRPC.TL_forumTopic topic, long dialog_id, MessageObject messageObject, boolean animated) { + forumTopic = topic; + isTopic = forumTopic != null; + if (currentDialogId != dialog_id) { + lastStatusDrawableParams = -1; + } + if (messageObject.topicIconDrawable[0] != null) { + messageObject.topicIconDrawable[0].setColor(topic.icon_color); + } + currentDialogId = dialog_id; + lastDialogChangedTime = System.currentTimeMillis(); + message = messageObject; + isDialogCell = false; + if (messageObject != null) { + lastMessageDate = messageObject.messageOwner.date; + currentEditDate = messageObject != null ? messageObject.messageOwner.edit_date : 0; + markUnread = false; + messageId = messageObject != null ? messageObject.getId() : 0; + lastUnreadState = messageObject != null && messageObject.isUnread(); + } + if (message != null) { + lastSendState = message.messageOwner.send_state; + } + if (!animated) { + lastStatusDrawableParams = -1; + } + if (topic != null) { + groupMessages = topic.groupedMessages; + } + update(0, animated); + } + public static class FixedWidthSpan extends ReplacementSpan { private int width; @@ -145,7 +195,7 @@ public class DialogCell extends BaseCell { public int date; public boolean verified; public boolean isMedia; - public boolean sent; + public int sent = -1; } private int paintIndex; @@ -165,8 +215,12 @@ public class DialogCell extends BaseCell { private boolean lastUnreadState; private int lastSendState; private boolean dialogMuted; + private boolean topicMuted; + private boolean drawUnmute; private float dialogMutedProgress; + private boolean hasUnmutedTopics = false; private MessageObject message; + private ArrayList groupMessages; private boolean clearingDialog; private CharSequence lastMessageString; private int index; @@ -194,11 +248,12 @@ public class DialogCell extends BaseCell { private float currentRevealBounceProgress; private float archiveBackgroundProgress; - private boolean hasMessageThumb; - private ImageReceiver thumbImage = new ImageReceiver(this); - private boolean drawPlay; + private int thumbsCount; + private boolean hasVideoThumb; + private ImageReceiver[] thumbImage = new ImageReceiver[3]; + private boolean[] drawPlay = new boolean[3]; - private ImageReceiver avatarImage = new ImageReceiver(this); + public ImageReceiver avatarImage = new ImageReceiver(this); private AvatarDrawable avatarDrawable = new AvatarDrawable(); private boolean animatingArchiveAvatar; private float animatingArchiveAvatarProgress; @@ -241,6 +296,8 @@ public class DialogCell extends BaseCell { private int timeTop; private StaticLayout timeLayout; + private int lock2Left; + private boolean promoDialog; private boolean drawCheck1; @@ -258,7 +315,7 @@ public class DialogCell extends BaseCell { private Stack spoilersPool = new Stack<>(); private List spoilers = new ArrayList<>(); - private AnimatedEmojiSpan.EmojiGroupedSpans animatedEmojiStack; + private AnimatedEmojiSpan.EmojiGroupedSpans animatedEmojiStack, animatedEmojiStack2; private int messageNameTop; private int messageNameLeft; @@ -274,8 +331,10 @@ public class DialogCell extends BaseCell { private boolean drawReorder; private boolean drawPinBackground; private boolean drawPin; + private boolean drawPinForced; private int pinTop; private int pinLeft; + protected int translateY; private boolean drawCount; private boolean drawCount2 = true; @@ -326,6 +385,7 @@ public class DialogCell extends BaseCell { private StaticLayout swipeMessageTextLayout; private int swipeMessageTextId; private int swipeMessageWidth; + private int readOutboxMaxId = -1; public static class BounceInterpolator implements Interpolator { @@ -356,17 +416,13 @@ public class DialogCell extends BaseCell { parentFragment = fragment; Theme.createDialogsResources(context); avatarImage.setRoundRadius(AndroidUtilities.dp(28)); - thumbImage.setRoundRadius(AndroidUtilities.dp(2)); + for (int i = 0; i < thumbImage.length; ++i) { + thumbImage[i] = new ImageReceiver(this); + thumbImage[i].setRoundRadius(AndroidUtilities.dp(2)); + } useForceThreeLines = forceThreeLines; currentAccount = account; - if (needCheck) { - checkBox = new CheckBox2(context, 21, resourcesProvider); - checkBox.setColor(null, Theme.key_windowBackgroundWhite, Theme.key_checkboxCheck); - checkBox.setDrawUnchecked(false); - checkBox.setDrawBackgroundAsArc(3); - addView(checkBox); - } emojiStatus = new AnimatedEmojiDrawable.SwapAnimatedEmojiDrawable(this, AndroidUtilities.dp(22)); emojiStatus.center = false; @@ -402,6 +458,10 @@ public class DialogCell extends BaseCell { checkChatTheme(); } + protected boolean drawLock2() { + return false; + } + public void setDialogIndex(int i) { index = i; } @@ -453,7 +513,7 @@ public class DialogCell extends BaseCell { } } - public void setDialog(long dialog_id, MessageObject messageObject, int date, boolean useMe) { + public void setDialog(long dialog_id, MessageObject messageObject, int date, boolean useMe, boolean animated) { if (currentDialogId != dialog_id) { lastStatusDrawableParams = -1; } @@ -473,7 +533,7 @@ public class DialogCell extends BaseCell { if (message != null) { lastSendState = message.messageOwner.send_state; } - update(0); + update(0, animated); } public long getDialogId() { @@ -499,9 +559,11 @@ public class DialogCell extends BaseCell { drawRevealBackground = false; currentRevealProgress = 0.0f; attachedToWindow = false; - reorderIconProgress = drawPin && drawReorder ? 1.0f : 0.0f; + reorderIconProgress = getIsPinned() && drawReorder ? 1.0f : 0.0f; avatarImage.onDetachedFromWindow(); - thumbImage.onDetachedFromWindow(); + for (int i = 0; i < thumbImage.length; ++i) { + thumbImage[i].onDetachedFromWindow(); + } if (translationDrawable != null) { translationDrawable.stop(); translationDrawable.setProgress(0.0f); @@ -516,15 +578,19 @@ public class DialogCell extends BaseCell { emojiStatus.detach(); } AnimatedEmojiSpan.release(this, animatedEmojiStack); + AnimatedEmojiSpan.release(this, animatedEmojiStack2); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); avatarImage.onAttachedToWindow(); - thumbImage.onAttachedToWindow(); + for (int i = 0; i < thumbImage.length; ++i) { + thumbImage[i].onAttachedToWindow(); + } resetPinnedArchiveState(); animatedEmojiStack = AnimatedEmojiSpan.update(AnimatedEmojiDrawable.CACHE_TYPE_MESSAGES, this, animatedEmojiStack, messageLayout); + animatedEmojiStack2 = AnimatedEmojiSpan.update(AnimatedEmojiDrawable.CACHE_TYPE_MESSAGES, this, animatedEmojiStack2, messageNameLayout); } public void resetPinnedArchiveState() { @@ -533,7 +599,7 @@ public class DialogCell extends BaseCell { avatarDrawable.setArchivedAvatarHiddenProgress(archiveBackgroundProgress); clipProgress = 0.0f; isSliding = false; - reorderIconProgress = drawPin && drawReorder ? 1.0f : 0.0f; + reorderIconProgress = getIsPinned() && drawReorder ? 1.0f : 0.0f; attachedToWindow = true; cornerProgress = 0.0f; setTranslationX(0); @@ -548,11 +614,26 @@ public class DialogCell extends BaseCell { if (checkBox != null) { checkBox.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(24), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(24), MeasureSpec.EXACTLY)); } - setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), AndroidUtilities.dp(useForceThreeLines || SharedConfig.useThreeLinesLayout ? 78 : 72) + (useSeparator ? 1 : 0)); + if (isTopic) { + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), AndroidUtilities.dp(useForceThreeLines || SharedConfig.useThreeLinesLayout ? heightThreeLines : heightDefault) + (useSeparator ? 1 : 0)); + checkTwoLinesForName(); + } + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), AndroidUtilities.dp(useForceThreeLines || SharedConfig.useThreeLinesLayout ? heightThreeLines : heightDefault) + (useSeparator ? 1 : 0) + (twoLinesForName ? AndroidUtilities.dp(20) : 0)); topClip = 0; bottomClip = getMeasuredHeight(); } + private void checkTwoLinesForName() { + twoLinesForName = false; + if (isTopic) { + buildLayout(); + if (nameIsEllipsized) { + twoLinesForName = true; + buildLayout(); + } + } + } + int lastSize; @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { @@ -560,8 +641,9 @@ public class DialogCell extends BaseCell { return; } if (checkBox != null) { - int x = LocaleController.isRTL ? (right - left) - AndroidUtilities.dp(useForceThreeLines || SharedConfig.useThreeLinesLayout ? 43 : 45) : AndroidUtilities.dp(useForceThreeLines || SharedConfig.useThreeLinesLayout ? 43 : 45); - int y = AndroidUtilities.dp(useForceThreeLines || SharedConfig.useThreeLinesLayout ? 48 : 42); + int paddingStart = AndroidUtilities.dp(messagePaddingStart - (useForceThreeLines || SharedConfig.useThreeLinesLayout ? 29 : 27)); + int x = LocaleController.isRTL ? (right - left) - paddingStart : paddingStart; + int y = AndroidUtilities.dp(chekBoxPaddingTop + (useForceThreeLines || SharedConfig.useThreeLinesLayout ? 6 : 0)); checkBox.layout(x, y, x + checkBox.getMeasuredWidth(), y + checkBox.getMeasuredHeight()); } int size = getMeasuredHeight() + getMeasuredWidth() << 16; @@ -588,7 +670,15 @@ public class DialogCell extends BaseCell { } public boolean getIsPinned() { - return drawPin; + return drawPin || drawPinForced; + } + + public void setPinForced(boolean value) { + drawPinForced = value; + if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) { + buildLayout(); + } + invalidate(); } private CharSequence formatArchivedDialogNames() { @@ -670,8 +760,8 @@ public class DialogCell extends BaseCell { CharSequence messageString = ""; CharSequence messageNameString = null; CharSequence printingString = null; - if (isDialogCell) { - printingString = MessagesController.getInstance(currentAccount).getPrintingString(currentDialogId, 0, true); + if (isDialogCell || isTopic) { + printingString = MessagesController.getInstance(currentAccount).getPrintingString(currentDialogId, getTopicId(), true); } TextPaint currentMessagePaint = Theme.dialogs_messagePaint[paintIndex]; boolean checkMessage = true; @@ -681,7 +771,8 @@ public class DialogCell extends BaseCell { drawPremium = false; drawScam = 0; drawPinBackground = false; - hasMessageThumb = false; + thumbsCount = 0; + hasVideoThumb = false; nameLayoutEllipsizeByGradient = false; int offsetName = 0; boolean showChecks = !UserObject.isUserSelf(user) && !useMeForMyMessages; @@ -726,19 +817,19 @@ public class DialogCell extends BaseCell { if (useForceThreeLines || SharedConfig.useThreeLinesLayout) { nameLockTop = AndroidUtilities.dp(12.5f); if (!LocaleController.isRTL) { - nameLockLeft = AndroidUtilities.dp(72 + 6); - nameLeft = AndroidUtilities.dp(72 + 10) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); + nameLockLeft = AndroidUtilities.dp(messagePaddingStart + 6); + nameLeft = AndroidUtilities.dp(messagePaddingStart + 10) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); } else { - nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(72 + 6) - Theme.dialogs_lockDrawable.getIntrinsicWidth(); + nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(messagePaddingStart + 6) - Theme.dialogs_lockDrawable.getIntrinsicWidth(); nameLeft = AndroidUtilities.dp(22); } } else { nameLockTop = AndroidUtilities.dp(16.5f); if (!LocaleController.isRTL) { - nameLockLeft = AndroidUtilities.dp(72 + 4); - nameLeft = AndroidUtilities.dp(72 + 8) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); + nameLockLeft = AndroidUtilities.dp(messagePaddingStart + 4); + nameLeft = AndroidUtilities.dp(messagePaddingStart + 8) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); } else { - nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(72 + 4) - Theme.dialogs_lockDrawable.getIntrinsicWidth(); + nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(messagePaddingStart + 4) - Theme.dialogs_lockDrawable.getIntrinsicWidth(); nameLeft = AndroidUtilities.dp(18); } } @@ -746,13 +837,13 @@ public class DialogCell extends BaseCell { drawVerified = customDialog.verified; if (useForceThreeLines || SharedConfig.useThreeLinesLayout) { if (!LocaleController.isRTL) { - nameLeft = AndroidUtilities.dp(72 + 6); + nameLeft = AndroidUtilities.dp(messagePaddingStart + 6); } else { nameLeft = AndroidUtilities.dp(22); } } else { if (!LocaleController.isRTL) { - nameLeft = AndroidUtilities.dp(72 + 4); + nameLeft = AndroidUtilities.dp(messagePaddingStart + 4); } else { nameLeft = AndroidUtilities.dp(18); } @@ -795,26 +886,36 @@ public class DialogCell extends BaseCell { drawCount = false; } - if (customDialog.sent) { + if (customDialog.sent == SENT_STATE_PROGRESS) { + drawClock = true; + drawCheck1 = false; + drawCheck2 = false; + } else if (customDialog.sent == SENT_STATE_READ) { drawCheck1 = true; drawCheck2 = true; + drawClock = false; + } else if (customDialog.sent == SENT_STATE_SENT) { + drawCheck1 = false; + drawCheck2 = true; + drawClock = false; } else { + drawClock = false; drawCheck1 = false; drawCheck2 = false; } - drawClock = false; + drawError = false; nameString = customDialog.name; } else { if (useForceThreeLines || SharedConfig.useThreeLinesLayout) { if (!LocaleController.isRTL) { - nameLeft = AndroidUtilities.dp(72 + 6); + nameLeft = AndroidUtilities.dp(messagePaddingStart + 6); } else { nameLeft = AndroidUtilities.dp(22); } } else { if (!LocaleController.isRTL) { - nameLeft = AndroidUtilities.dp(72 + 4); + nameLeft = AndroidUtilities.dp(messagePaddingStart + 4); } else { nameLeft = AndroidUtilities.dp(18); } @@ -826,19 +927,19 @@ public class DialogCell extends BaseCell { if (useForceThreeLines || SharedConfig.useThreeLinesLayout) { nameLockTop = AndroidUtilities.dp(12.5f); if (!LocaleController.isRTL) { - nameLockLeft = AndroidUtilities.dp(72 + 6); - nameLeft = AndroidUtilities.dp(72 + 10) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); + nameLockLeft = AndroidUtilities.dp(messagePaddingStart + 6); + nameLeft = AndroidUtilities.dp(messagePaddingStart + 10) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); } else { - nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(72 + 6) - Theme.dialogs_lockDrawable.getIntrinsicWidth(); + nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(messagePaddingStart + 6) - Theme.dialogs_lockDrawable.getIntrinsicWidth(); nameLeft = AndroidUtilities.dp(22); } } else { nameLockTop = AndroidUtilities.dp(16.5f); if (!LocaleController.isRTL) { - nameLockLeft = AndroidUtilities.dp(72 + 4); - nameLeft = AndroidUtilities.dp(72 + 8) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); + nameLockLeft = AndroidUtilities.dp(messagePaddingStart + 4); + nameLeft = AndroidUtilities.dp(messagePaddingStart + 8) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); } else { - nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(72 + 4) - Theme.dialogs_lockDrawable.getIntrinsicWidth(); + nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(messagePaddingStart + 4) - Theme.dialogs_lockDrawable.getIntrinsicWidth(); nameLeft = AndroidUtilities.dp(18); } } @@ -887,20 +988,27 @@ public class DialogCell extends BaseCell { lastDate = message.messageOwner.date; } - if (isDialogCell) { - draftMessage = MediaDataController.getInstance(currentAccount).getDraft(currentDialogId, 0); - if (draftMessage != null && (TextUtils.isEmpty(draftMessage.message) && draftMessage.reply_to_msg_id == 0 || lastDate > draftMessage.date && unreadCount != 0) || - ChatObject.isChannel(chat) && !chat.megagroup && !chat.creator && (chat.admin_rights == null || !chat.admin_rights.post_messages) || - chat != null && (chat.left || chat.kicked)) { + if (isTopic) { + draftMessage = MediaDataController.getInstance(currentAccount).getDraft(currentDialogId, getTopicId()); + if (draftMessage != null && TextUtils.isEmpty(draftMessage.message)) { draftMessage = null; } + } else if (isDialogCell) { + draftMessage = MediaDataController.getInstance(currentAccount).getDraft(currentDialogId, 0); } else { draftMessage = null; } + if (draftMessage != null && (TextUtils.isEmpty(draftMessage.message) && draftMessage.reply_to_msg_id == 0 || lastDate > draftMessage.date && unreadCount != 0) || + ChatObject.isChannel(chat) && !chat.megagroup && !chat.creator && (chat.admin_rights == null || !chat.admin_rights.post_messages) || + chat != null && (chat.left || chat.kicked)) { + draftMessage = null; + } + + if (printingString != null) { lastPrintString = printingString; - printingStringType = MessagesController.getInstance(currentAccount).getPrintingStringType(currentDialogId, 0); + printingStringType = MessagesController.getInstance(currentAccount).getPrintingStringType(currentDialogId, getTopicId()); StatusDrawable statusDrawable = Theme.getChatStatusDrawable(printingStringType); int startPadding = 0; if (statusDrawable != null) { @@ -992,9 +1100,9 @@ public class DialogCell extends BaseCell { } drawCount2 = true; boolean lastMessageIsReaction = false; - if (currentDialogId > 0 && message.isOutOwner() && message.messageOwner.reactions != null && message.messageOwner.reactions.recent_reactions != null && !message.messageOwner.reactions.recent_reactions.isEmpty()) { + if (dialogsType == 0 && currentDialogId > 0 && message.isOutOwner() && message.messageOwner.reactions != null && message.messageOwner.reactions.recent_reactions != null && !message.messageOwner.reactions.recent_reactions.isEmpty() && reactionMentionCount > 0) { TLRPC.MessagePeerReaction lastReaction = message.messageOwner.reactions.recent_reactions.get(0); - if (lastReaction.peer_id.user_id != 0 &&lastReaction.peer_id.user_id != UserConfig.getInstance(currentAccount).clientUserId) { + if (lastReaction.unread && lastReaction.peer_id.user_id != 0 &&lastReaction.peer_id.user_id != UserConfig.getInstance(currentAccount).clientUserId) { lastMessageIsReaction = true; ReactionsLayoutInBubble.VisibleReaction visibleReaction = ReactionsLayoutInBubble.VisibleReaction.fromTLReaction(lastReaction.reaction); currentMessagePaint = Theme.dialogs_messagePrintingPaint[paintIndex]; @@ -1020,7 +1128,7 @@ public class DialogCell extends BaseCell { if (chat.participants_count != 0) { messageString = LocaleController.formatPluralStringComma("Subscribers", chat.participants_count); } else { - if (TextUtils.isEmpty(chat.username)) { + if (!ChatObject.isPublic(chat)) { messageString = LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate).toLowerCase(); } else { messageString = LocaleController.getString("ChannelPublic", R.string.ChannelPublic).toLowerCase(); @@ -1032,7 +1140,7 @@ public class DialogCell extends BaseCell { } else { if (chat.has_geo) { messageString = LocaleController.getString("MegaLocation", R.string.MegaLocation); - } else if (TextUtils.isEmpty(chat.username)) { + } else if (!ChatObject.isPublic(chat)) { messageString = LocaleController.getString("MegaPrivate", R.string.MegaPrivate).toLowerCase(); } else { messageString = LocaleController.getString("MegaPublic", R.string.MegaPublic).toLowerCase(); @@ -1052,7 +1160,7 @@ public class DialogCell extends BaseCell { } else if (!useForceThreeLines && !SharedConfig.useThreeLinesLayout && currentDialogFolderId != 0) { checkMessage = false; messageString = formatArchivedDialogNames(); - } else if (message.messageOwner instanceof TLRPC.TL_messageService) { + } else if (message.messageOwner instanceof TLRPC.TL_messageService && (!MessageObject.isTopicActionMessage(message) || message.messageOwner.action instanceof TLRPC.TL_messageActionTopicCreate)) { if (ChatObject.isChannelAndNotMegaGroup(chat) && (message.messageOwner.action instanceof TLRPC.TL_messageActionChannelMigrateFrom)) { messageString = ""; showChecks = false; @@ -1062,34 +1170,60 @@ public class DialogCell extends BaseCell { currentMessagePaint = Theme.dialogs_messagePrintingPaint[paintIndex]; } else { boolean needEmoji = true; - if (TextUtils.isEmpty(restrictionReason) && currentDialogFolderId == 0 && encryptedChat == null && !message.needDrawBluredPreview() && (message.isPhoto() || message.isNewGif() || message.isVideo())) { - String type = message.isWebpage() ? message.messageOwner.media.webpage.type : null; - if (!("app".equals(type) || "profile".equals(type) || "article".equals(type) || type != null && type.startsWith("telegram_"))) { - TLRPC.PhotoSize smallThumb = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, 40); - TLRPC.PhotoSize bigThumb = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, AndroidUtilities.getPhotoSize()); - if (smallThumb == bigThumb) { - bigThumb = null; - } - if (smallThumb != null) { - hasMessageThumb = true; - drawPlay = message.isVideo(); - String fileName = FileLoader.getAttachFileName(bigThumb); - if (message.mediaExists || DownloadController.getInstance(currentAccount).canDownloadMedia(message) || FileLoader.getInstance(currentAccount).isLoadingFile(fileName)) { - int size; - if (message.type == MessageObject.TYPE_PHOTO) { - size = bigThumb != null ? bigThumb.size : 0; - } else { - size = 0; + if (groupMessages != null && groupMessages.size() > 1 && TextUtils.isEmpty(restrictionReason) && currentDialogFolderId == 0 && encryptedChat == null) { + thumbsCount = 0; + hasVideoThumb = false; + Collections.sort(groupMessages, (a, b) -> a.getId() - b.getId()); + for (int i = 0; i < groupMessages.size(); ++i) { + MessageObject message = groupMessages.get(i); + if (message != null && !message.needDrawBluredPreview() && (message.isPhoto() || message.isNewGif() || message.isVideo() || message.isRoundVideo())) { + String type = message.isWebpage() ? message.messageOwner.media.webpage.type : null; + if (!("app".equals(type) || "profile".equals(type) || "article".equals(type) || type != null && type.startsWith("telegram_"))) { + TLRPC.PhotoSize smallThumb = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, 40); + TLRPC.PhotoSize bigThumb = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, AndroidUtilities.getPhotoSize()); + if (smallThumb == bigThumb) { + bigThumb = null; + } + if (smallThumb != null) { + hasVideoThumb = hasVideoThumb || (message.isVideo() || message.isRoundVideo()); + if (thumbsCount < 2) { + thumbsCount++; + drawPlay[i] = message.isVideo() || message.isRoundVideo(); + int size = message.type == MessageObject.TYPE_PHOTO && bigThumb != null ? bigThumb.size : 0; + thumbImage[i].setImage(ImageLocation.getForObject(bigThumb, message.photoThumbsObject), "20_20", ImageLocation.getForObject(smallThumb, message.photoThumbsObject), "20_20", size, null, message, 0); + thumbImage[i].setRoundRadius(message.isRoundVideo() ? AndroidUtilities.dp(18) : AndroidUtilities.dp(2)); + needEmoji = false; + } + } + } + } + } + } else if (message != null && currentDialogFolderId == 0) { + thumbsCount = 0; + hasVideoThumb = false; + if (!message.needDrawBluredPreview() && (message.isPhoto() || message.isNewGif() || message.isVideo() || message.isRoundVideo())) { + String type = message.isWebpage() ? message.messageOwner.media.webpage.type : null; + if (!("app".equals(type) || "profile".equals(type) || "article".equals(type) || type != null && type.startsWith("telegram_"))) { + TLRPC.PhotoSize smallThumb = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, 40); + TLRPC.PhotoSize bigThumb = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, AndroidUtilities.getPhotoSize()); + if (smallThumb == bigThumb) { + bigThumb = null; + } + if (smallThumb != null) { + hasVideoThumb = hasVideoThumb || (message.isVideo() || message.isRoundVideo()); + if (thumbsCount < 3) { + thumbsCount++; + drawPlay[0] = message.isVideo() || message.isRoundVideo(); + int size = message.type == MessageObject.TYPE_PHOTO && bigThumb != null ? bigThumb.size : 0; + thumbImage[0].setImage(ImageLocation.getForObject(bigThumb, message.photoThumbsObject), "20_20", ImageLocation.getForObject(smallThumb, message.photoThumbsObject), "20_20", size, null, message, 0); + thumbImage[0].setRoundRadius(message.isRoundVideo() ? AndroidUtilities.dp(18) : AndroidUtilities.dp(2)); + needEmoji = false; } - thumbImage.setImage(ImageLocation.getForObject(bigThumb, message.photoThumbsObject), "20_20", ImageLocation.getForObject(smallThumb, message.photoThumbsObject), "20_20", size, null, message, 0); - } else { - thumbImage.setImage(null, null, ImageLocation.getForObject(smallThumb, message.photoThumbsObject), "20_20", (Drawable) null, message, 0); } - needEmoji = false; } } } - if (chat != null && chat.id > 0 && fromChat == null && (!ChatObject.isChannel(chat) || ChatObject.isMegagroup(chat))) { + if (chat != null && chat.id > 0 && fromChat == null && (!ChatObject.isChannel(chat) || ChatObject.isMegagroup(chat)) && !ForumUtilities.isTopicCreateMessage(message)) { if (message.isOutOwner()) { messageNameString = LocaleController.getString("FromYou", R.string.FromYou); } else if (message != null && message.messageOwner.fwd_from != null && message.messageOwner.fwd_from.from_name != null) { @@ -1107,11 +1241,39 @@ public class DialogCell extends BaseCell { } else { messageNameString = "DELETED"; } + if (chat.forum && !isTopic) { + CharSequence topicName = MessagesController.getInstance(currentAccount).getTopicsController().getTopicIconName(chat, message, currentMessagePaint); + if (!TextUtils.isEmpty(topicName)) { + SpannableStringBuilder arrowSpan = new SpannableStringBuilder("-"); + ColoredImageSpan coloredImageSpan = new ColoredImageSpan(ContextCompat.getDrawable(ApplicationLoader.applicationContext, R.drawable.msg_mini_forumarrow).mutate()); + coloredImageSpan.setColorKey(useForceThreeLines || SharedConfig.useThreeLinesLayout ? null : Theme.key_chats_nameMessage); + arrowSpan.setSpan(coloredImageSpan, 0, 1, 0); + SpannableStringBuilder nameSpannableString = new SpannableStringBuilder(); + nameSpannableString.append(messageNameString).append(arrowSpan).append(topicName); + messageNameString = nameSpannableString; + } + } checkMessage = false; SpannableStringBuilder stringBuilder; + MessageObject captionMessage = getCaptionMessage(); if (!TextUtils.isEmpty(restrictionReason)) { stringBuilder = SpannableStringBuilder.valueOf(String.format(messageFormat, restrictionReason, messageNameString)); - } else if (message.caption != null) { + } else if (MessageObject.isTopicActionMessage(message)) { + CharSequence mess; + if (message.messageTextShort != null && (!(message.messageOwner.action instanceof TLRPC.TL_messageActionTopicCreate) || !isTopic)) { + mess = message.messageTextShort; + } else { + mess = message.messageText; + } + stringBuilder = AndroidUtilities.formatSpannable(messageFormat, mess, messageNameString); + if (message.topicIconDrawable[0] != null) { + TLRPC.TL_forumTopic topic = MessagesController.getInstance(currentAccount).getTopicsController().findTopic(-message.getDialogId(), MessageObject.getTopicId(message.messageOwner)); + if (topic != null) { + message.topicIconDrawable[0].setColor(topic.icon_color); + } + } + } else if (captionMessage != null && captionMessage.caption != null) { + MessageObject message = captionMessage; CharSequence mess = message.caption.toString(); String emoji; if (!needEmoji) { @@ -1132,7 +1294,7 @@ public class DialogCell extends BaseCell { if (message.messageTrimmedToHighlight != null) { str = message.messageTrimmedToHighlight; } - int w = getMeasuredWidth() - AndroidUtilities.dp(72 + 23 + 24); + int w = getMeasuredWidth() - AndroidUtilities.dp(messagePaddingStart + 23 + 24); if (hasNameInMessage) { if (!TextUtils.isEmpty(messageNameString)) { w -= currentMessagePaint.measureText(messageNameString.toString()); @@ -1157,6 +1319,7 @@ public class DialogCell extends BaseCell { } else if (message.messageOwner.media != null && !message.isMediaEmpty()) { currentMessagePaint = Theme.dialogs_messagePrintingPaint[paintIndex]; String innerMessage; + String colorKey = Theme.key_chats_attachMessage; if (message.messageOwner.media instanceof TLRPC.TL_messageMediaPoll) { TLRPC.TL_messageMediaPoll mediaPoll = (TLRPC.TL_messageMediaPoll) message.messageOwner.media; if (Build.VERSION.SDK_INT >= 18) { @@ -1172,19 +1335,27 @@ public class DialogCell extends BaseCell { } } else if (message.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) { innerMessage = message.messageOwner.media.title; - } else if (message.type == 14) { + } else if (message.type == MessageObject.TYPE_MUSIC) { if (Build.VERSION.SDK_INT >= 18) { innerMessage = String.format("\uD83C\uDFA7 \u2068%s - %s\u2069", message.getMusicAuthor(), message.getMusicTitle()); } else { innerMessage = String.format("\uD83C\uDFA7 %s - %s", message.getMusicAuthor(), message.getMusicTitle()); } + } else if (thumbsCount > 1) { + if (hasVideoThumb) { + innerMessage = LocaleController.formatPluralString("Media", groupMessages == null ? 0 : groupMessages.size()); + } else { + innerMessage = LocaleController.formatPluralString("Photos", groupMessages == null ? 0 : groupMessages.size()); + } + colorKey = Theme.key_chats_actionMessage; } else { innerMessage = msgText.toString(); + colorKey = Theme.key_chats_actionMessage; } innerMessage = innerMessage.replace('\n', ' '); stringBuilder = AndroidUtilities.formatSpannable(messageFormat, innerMessage, messageNameString); try { - stringBuilder.setSpan(new ForegroundColorSpanThemable(Theme.key_chats_attachMessage, resourcesProvider), hasNameInMessage ? messageNameString.length() + 2 : 0, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.setSpan(new ForegroundColorSpanThemable(colorKey, resourcesProvider), hasNameInMessage ? messageNameString.length() + 2 : 0, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } catch (Exception e) { FileLog.e(e); } @@ -1194,7 +1365,7 @@ public class DialogCell extends BaseCell { if (message.messageTrimmedToHighlight != null) { mess = message.messageTrimmedToHighlight; } - int w = getMeasuredWidth() - AndroidUtilities.dp(72 + 23 + 10); + int w = getMeasuredWidth() - AndroidUtilities.dp(messagePaddingStart + 23 + 10); if (hasNameInMessage) { if (!TextUtils.isEmpty(messageNameString)) { w -= currentMessagePaint.measureText(messageNameString.toString()); @@ -1235,7 +1406,7 @@ public class DialogCell extends BaseCell { messageString = messageH; } } - if (hasMessageThumb) { + if (thumbsCount > 0) { if (!(messageString instanceof SpannableStringBuilder)) { messageString = new SpannableStringBuilder(messageString); } @@ -1243,20 +1414,33 @@ public class DialogCell extends BaseCell { SpannableStringBuilder builder = (SpannableStringBuilder) messageString; if (thumbInsertIndex >= builder.length()) { builder.append(" "); - builder.setSpan(new FixedWidthSpan(AndroidUtilities.dp(thumbSize + 6)), builder.length() - 1, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + builder.setSpan(new FixedWidthSpan(AndroidUtilities.dp(thumbsCount * (thumbSize + 2) - 2 + 5)), builder.length() - 1, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { builder.insert(thumbInsertIndex, " "); - builder.setSpan(new FixedWidthSpan(AndroidUtilities.dp(thumbSize + 6)), thumbInsertIndex, thumbInsertIndex + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + builder.setSpan(new FixedWidthSpan(AndroidUtilities.dp(thumbsCount * (thumbSize + 2) - 2 + 5)), thumbInsertIndex, thumbInsertIndex + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } } else { if (!TextUtils.isEmpty(restrictionReason)) { messageString = restrictionReason; + } else if (MessageObject.isTopicActionMessage(message)) { + if (message.messageTextShort != null && (!(message.messageOwner.action instanceof TLRPC.TL_messageActionTopicCreate) || !isTopic)) { + messageString = message.messageTextShort; + } else { + messageString = message.messageText; + } + if (message.topicIconDrawable[0] != null) { + TLRPC.TL_forumTopic topic = MessagesController.getInstance(currentAccount).getTopicsController().findTopic(-message.getDialogId(), MessageObject.getTopicId(message.messageOwner)); + if (topic != null) { + message.topicIconDrawable[0].setColor(topic.icon_color); + } + } } else if (message.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto && message.messageOwner.media.photo instanceof TLRPC.TL_photoEmpty && message.messageOwner.media.ttl_seconds != 0) { messageString = LocaleController.getString("AttachPhotoExpired", R.string.AttachPhotoExpired); } else if (message.messageOwner.media instanceof TLRPC.TL_messageMediaDocument && message.messageOwner.media.document instanceof TLRPC.TL_documentEmpty && message.messageOwner.media.ttl_seconds != 0) { messageString = LocaleController.getString("AttachVideoExpired", R.string.AttachVideoExpired); - } else if (message.caption != null) { + } else if (getCaptionMessage() != null) { + MessageObject message = getCaptionMessage(); String emoji; if (!needEmoji) { emoji = ""; @@ -1276,7 +1460,7 @@ public class DialogCell extends BaseCell { if (message.messageTrimmedToHighlight != null) { str = message.messageTrimmedToHighlight; } - int w = getMeasuredWidth() - AndroidUtilities.dp(72 + 23 + 24); + int w = getMeasuredWidth() - AndroidUtilities.dp(messagePaddingStart + 23 + 24); if (hasNameInMessage) { if (!TextUtils.isEmpty(messageNameString)) { w -= currentMessagePaint.measureText(messageNameString.toString()); @@ -1295,6 +1479,13 @@ public class DialogCell extends BaseCell { } messageString = new SpannableStringBuilder(emoji).append(msgBuilder); } + } else if (thumbsCount > 1) { + if (hasVideoThumb) { + messageString = LocaleController.formatPluralString("Media", groupMessages == null ? 0 : groupMessages.size()); + } else { + messageString = LocaleController.formatPluralString("Photos", groupMessages == null ? 0 : groupMessages.size()); + } + currentMessagePaint = Theme.dialogs_messagePrintingPaint[paintIndex]; } else { if (message.messageOwner.media instanceof TLRPC.TL_messageMediaPoll) { TLRPC.TL_messageMediaPoll mediaPoll = (TLRPC.TL_messageMediaPoll) message.messageOwner.media; @@ -1303,7 +1494,7 @@ public class DialogCell extends BaseCell { messageString = "\uD83C\uDFAE " + message.messageOwner.media.game.title; } else if (message.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) { messageString = message.messageOwner.media.title; - } else if (message.type == 14) { + } else if (message.type == MessageObject.TYPE_MUSIC) { messageString = String.format("\uD83C\uDFA7 %s - %s", message.getMusicAuthor(), message.getMusicTitle()); } else { if (message.hasHighlightedWords() && !TextUtils.isEmpty(message.messageOwner.message)){ @@ -1311,7 +1502,7 @@ public class DialogCell extends BaseCell { if (message.messageTrimmedToHighlight != null) { messageString = message.messageTrimmedToHighlight; } - int w = getMeasuredWidth() - AndroidUtilities.dp(72 + 23 ); + int w = getMeasuredWidth() - AndroidUtilities.dp(messagePaddingStart + 23 ); messageString = AndroidUtilities.ellipsizeCenterEnd(messageString, message.highlightedWords.get(0), w, currentMessagePaint, 130).toString(); } else { SpannableStringBuilder stringBuilder = new SpannableStringBuilder(msgText); @@ -1327,13 +1518,13 @@ public class DialogCell extends BaseCell { currentMessagePaint = Theme.dialogs_messagePrintingPaint[paintIndex]; } } - if (hasMessageThumb) { + if (thumbsCount > 0) { if (message.hasHighlightedWords() && !TextUtils.isEmpty(message.messageOwner.message)) { messageString = message.messageTrimmedToHighlight; if (message.messageTrimmedToHighlight != null) { messageString = message.messageTrimmedToHighlight; } - int w = getMeasuredWidth() - AndroidUtilities.dp(72 + 23 + thumbSize + 6); + int w = getMeasuredWidth() - AndroidUtilities.dp(messagePaddingStart + 23 + (thumbSize + 2) * thumbsCount - 2 + 5); messageString = AndroidUtilities.ellipsizeCenterEnd(messageString, message.highlightedWords.get(0), w, currentMessagePaint, 130).toString(); } else { if (messageString.length() > 150) { @@ -1347,7 +1538,7 @@ public class DialogCell extends BaseCell { checkMessage = false; SpannableStringBuilder builder = (SpannableStringBuilder) messageString; builder.insert(0, " "); - builder.setSpan(new FixedWidthSpan(AndroidUtilities.dp(thumbSize + 6)), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + builder.setSpan(new FixedWidthSpan(AndroidUtilities.dp((thumbSize + 2) * thumbsCount - 2 + 5)), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Emoji.replaceEmoji(builder, Theme.dialogs_messagePaint[paintIndex].getFontMetricsInt(), AndroidUtilities.dp(17), false); if (message.hasHighlightedWords()) { CharSequence s = AndroidUtilities.highlightText(builder, message.highlightedWords, resourcesProvider); @@ -1438,7 +1629,13 @@ public class DialogCell extends BaseCell { drawCount = false; drawMention = false; } else if (message.isSent()) { - drawCheck1 = !message.isUnread() || ChatObject.isChannel(chat) && !chat.megagroup; + if (forumTopic != null) { + drawCheck1 = forumTopic.read_outbox_max_id >= message.getId(); + } else if (isDialogCell) { + drawCheck1 = (readOutboxMaxId > 0 && readOutboxMaxId >= message.getId()) || !message.isUnread() || ChatObject.isChannel(chat) && !chat.megagroup; + } else { + drawCheck1 = !message.isUnread() || ChatObject.isChannel(chat) && !chat.megagroup; + } drawCheck2 = true; drawClock = false; drawError = false; @@ -1465,7 +1662,7 @@ public class DialogCell extends BaseCell { } if (!TextUtils.isEmpty(messagesController.promoPsaMessage)) { messageString = messagesController.promoPsaMessage; - hasMessageThumb = false; + thumbsCount = 0; } } } @@ -1474,7 +1671,11 @@ public class DialogCell extends BaseCell { nameString = LocaleController.getString("ArchivedChats", R.string.ArchivedChats); } else { if (chat != null) { - nameString = chat.title; + if (isTopic) { + nameString = forumTopic.title; + } else { + nameString = chat.title; + } } else if (user != null) { if (UserObject.isReplyUser(user)) { nameString = LocaleController.getString("RepliesTitle", R.string.RepliesTitle); @@ -1491,7 +1692,7 @@ public class DialogCell extends BaseCell { nameString = UserObject.getUserName(user); } } - if (nameString.length() == 0) { + if (nameString != null && nameString.length() == 0) { nameString = LocaleController.getString("HiddenName", R.string.HiddenName); } } @@ -1512,10 +1713,21 @@ public class DialogCell extends BaseCell { timeLeft = 0; } + int timeLeftOffset = 0; + if (drawLock2()) { + if (LocaleController.isRTL) { + lock2Left = timeLeft + timeWidth + AndroidUtilities.dp(4); + } else { + lock2Left = timeLeft - Theme.dialogs_lock2Drawable.getIntrinsicWidth() - AndroidUtilities.dp(4); + } + timeLeftOffset += Theme.dialogs_lock2Drawable.getIntrinsicWidth() + AndroidUtilities.dp(4); + timeWidth += timeLeftOffset; + } + if (!LocaleController.isRTL) { - nameWidth = getMeasuredWidth() - nameLeft - AndroidUtilities.dp(14) - timeWidth; + nameWidth = getMeasuredWidth() - nameLeft - AndroidUtilities.dp(14 + 8) - timeWidth; } else { - nameWidth = getMeasuredWidth() - nameLeft - AndroidUtilities.dp(77) - timeWidth; + nameWidth = getMeasuredWidth() - nameLeft - AndroidUtilities.dp(messagePaddingStart + 5 + 8) - timeWidth; nameLeft += timeWidth; } if (drawNameLock) { @@ -1525,7 +1737,7 @@ public class DialogCell extends BaseCell { int w = Theme.dialogs_clockDrawable.getIntrinsicWidth() + AndroidUtilities.dp(5); nameWidth -= w; if (!LocaleController.isRTL) { - clockDrawLeft = timeLeft - w; + clockDrawLeft = timeLeft - timeLeftOffset - w; } else { clockDrawLeft = timeLeft + timeWidth + AndroidUtilities.dp(5); nameLeft += w; @@ -1536,7 +1748,7 @@ public class DialogCell extends BaseCell { if (drawCheck1) { nameWidth -= Theme.dialogs_halfCheckDrawable.getIntrinsicWidth() - AndroidUtilities.dp(8); if (!LocaleController.isRTL) { - halfCheckDrawLeft = timeLeft - w; + halfCheckDrawLeft = timeLeft - timeLeftOffset - w; checkDrawLeft = halfCheckDrawLeft - AndroidUtilities.dp(5.5f); } else { checkDrawLeft = timeLeft + timeWidth + AndroidUtilities.dp(5); @@ -1545,7 +1757,7 @@ public class DialogCell extends BaseCell { } } else { if (!LocaleController.isRTL) { - checkDrawLeft1 = timeLeft - w; + checkDrawLeft1 = timeLeft - timeLeftOffset - w; } else { checkDrawLeft1 = timeLeft + timeWidth + AndroidUtilities.dp(5); nameLeft += w; @@ -1559,7 +1771,7 @@ public class DialogCell extends BaseCell { if (LocaleController.isRTL) { nameLeft += w; } - } else if (dialogMuted && !drawVerified && drawScam == 0) { + } else if ((dialogMuted || drawUnmute) && !drawVerified && drawScam == 0) { int w = AndroidUtilities.dp(6) + Theme.dialogs_muteDrawable.getIntrinsicWidth(); nameWidth -= w; if (LocaleController.isRTL) { @@ -1594,7 +1806,10 @@ public class DialogCell extends BaseCell { nameLayoutFits = nameStringFinal.length() == TextUtils.ellipsize(nameStringFinal, Theme.dialogs_namePaint[paintIndex], ellipsizeWidth, TextUtils.TruncateAt.END).length(); ellipsizeWidth += AndroidUtilities.dp(48); } - nameStringFinal = TextUtils.ellipsize(nameStringFinal, Theme.dialogs_namePaint[paintIndex], ellipsizeWidth, TextUtils.TruncateAt.END); + nameIsEllipsized = Theme.dialogs_namePaint[paintIndex].measureText(nameStringFinal.toString()) > ellipsizeWidth; + if (!twoLinesForName) { + nameStringFinal = TextUtils.ellipsize(nameStringFinal, Theme.dialogs_namePaint[paintIndex], ellipsizeWidth, TextUtils.TruncateAt.END); + } nameStringFinal = Emoji.replaceEmoji(nameStringFinal, Theme.dialogs_namePaint[paintIndex].getFontMetricsInt(), AndroidUtilities.dp(20), false); if (message != null && message.hasHighlightedWords()) { CharSequence s = AndroidUtilities.highlightText(nameStringFinal, message.highlightedWords, resourcesProvider); @@ -1602,7 +1817,11 @@ public class DialogCell extends BaseCell { nameStringFinal = s; } } - nameLayout = new StaticLayout(nameStringFinal, Theme.dialogs_namePaint[paintIndex], Math.max(ellipsizeWidth, nameWidth), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (twoLinesForName) { + nameLayout = StaticLayoutEx.createStaticLayout(nameStringFinal, Theme.dialogs_namePaint[paintIndex], ellipsizeWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.END, ellipsizeWidth, 2); + } else { + nameLayout = new StaticLayout(nameStringFinal, Theme.dialogs_namePaint[paintIndex], Math.max(ellipsizeWidth, nameWidth), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } nameLayoutTranslateX = nameLayoutEllipsizeByGradient && nameLayout.isRtlCharAt(0) ? -AndroidUtilities.dp(36) : 0; nameLayoutEllipsizeLeft = nameLayout.isRtlCharAt(0); } catch (Exception e) { @@ -1621,42 +1840,49 @@ public class DialogCell extends BaseCell { pinTop = AndroidUtilities.dp(43); countTop = AndroidUtilities.dp(43); checkDrawTop = AndroidUtilities.dp(13); - messageWidth = getMeasuredWidth() - AndroidUtilities.dp(72 + 21); + messageWidth = getMeasuredWidth() - AndroidUtilities.dp(messagePaddingStart + 21); if (LocaleController.isRTL) { messageLeft = messageNameLeft = AndroidUtilities.dp(16); avatarLeft = getMeasuredWidth() - AndroidUtilities.dp(66); thumbLeft = avatarLeft - AndroidUtilities.dp(13 + 18); } else { - messageLeft = messageNameLeft = AndroidUtilities.dp(72 + 6); + messageLeft = messageNameLeft = AndroidUtilities.dp(messagePaddingStart + 6); avatarLeft = AndroidUtilities.dp(10); thumbLeft = avatarLeft + AndroidUtilities.dp(56 + 13); } avatarImage.setImageCoords(avatarLeft, avatarTop, AndroidUtilities.dp(56), AndroidUtilities.dp(56)); - thumbImage.setImageCoords(thumbLeft, avatarTop + AndroidUtilities.dp(31), AndroidUtilities.dp(18), AndroidUtilities.dp(18)); + for (int i = 0; i < thumbImage.length; ++i) { + thumbImage[i].setImageCoords(thumbLeft + (thumbSize + 2) * i, avatarTop + AndroidUtilities.dp(31) + (twoLinesForName ? AndroidUtilities.dp(20) : 0), AndroidUtilities.dp(18), AndroidUtilities.dp(18)); + } } else { avatarTop = AndroidUtilities.dp(9); messageNameTop = AndroidUtilities.dp(31); timeTop = AndroidUtilities.dp(16); errorTop = AndroidUtilities.dp(39); pinTop = AndroidUtilities.dp(39); - countTop = AndroidUtilities.dp(39); + countTop = isTopic ? AndroidUtilities.dp(36) : AndroidUtilities.dp(39); checkDrawTop = AndroidUtilities.dp(17); - messageWidth = getMeasuredWidth() - AndroidUtilities.dp(72 + 23); + messageWidth = getMeasuredWidth() - AndroidUtilities.dp(messagePaddingStart + 23 - (LocaleController.isRTL ? 0 : 12)); if (LocaleController.isRTL) { messageLeft = messageNameLeft = AndroidUtilities.dp(22); avatarLeft = getMeasuredWidth() - AndroidUtilities.dp(64); - thumbLeft = avatarLeft - AndroidUtilities.dp(11 + thumbSize); + thumbLeft = avatarLeft - AndroidUtilities.dp(11 + (thumbsCount * (thumbSize + 2) - 2)); } else { - messageLeft = messageNameLeft = AndroidUtilities.dp(72 + 4); + messageLeft = messageNameLeft = AndroidUtilities.dp(messagePaddingStart + 4); avatarLeft = AndroidUtilities.dp(10); thumbLeft = avatarLeft + AndroidUtilities.dp(56 + 11); } avatarImage.setImageCoords(avatarLeft, avatarTop, AndroidUtilities.dp(54), AndroidUtilities.dp(54)); - thumbImage.setImageCoords(thumbLeft, avatarTop + AndroidUtilities.dp(30), AndroidUtilities.dp(thumbSize), AndroidUtilities.dp(thumbSize)); + for (int i = 0; i < thumbImage.length; ++i) { + thumbImage[i].setImageCoords(thumbLeft + (thumbSize + 2) * i, avatarTop + AndroidUtilities.dp(30) + (twoLinesForName ? AndroidUtilities.dp(20) : 0), AndroidUtilities.dp(thumbSize), AndroidUtilities.dp(thumbSize)); + } } - if (drawPin) { + if (twoLinesForName) { + messageNameTop += AndroidUtilities.dp(20); + } + if (getIsPinned()) { if (!LocaleController.isRTL) { pinLeft = getMeasuredWidth() - Theme.dialogs_pinnedDrawable.getIntrinsicWidth() - AndroidUtilities.dp(14); } else { @@ -1734,7 +1960,7 @@ public class DialogCell extends BaseCell { } } } else { - if (drawPin) { + if (getIsPinned()) { int w = Theme.dialogs_pinnedDrawable.getIntrinsicWidth() + AndroidUtilities.dp(8); messageWidth -= w; if (LocaleController.isRTL) { @@ -1781,16 +2007,26 @@ public class DialogCell extends BaseCell { FileLog.e(e); } messageTop = AndroidUtilities.dp(32 + 19); - thumbImage.setImageY(avatarTop + AndroidUtilities.dp(40)); + int yoff = nameIsEllipsized && isTopic ? AndroidUtilities.dp(20) : 0; + for (int i = 0; i < thumbImage.length; ++i) { + thumbImage[i].setImageY(avatarTop + yoff + AndroidUtilities.dp(40)); + } } else { messageNameLayout = null; if (useForceThreeLines || SharedConfig.useThreeLinesLayout) { messageTop = AndroidUtilities.dp(32); - thumbImage.setImageY(avatarTop + AndroidUtilities.dp(21)); + int yoff = nameIsEllipsized && isTopic ? AndroidUtilities.dp(20) : 0; + for (int i = 0; i < thumbImage.length; ++i) { + thumbImage[i].setImageY(avatarTop + yoff + AndroidUtilities.dp(21)); + } } else { messageTop = AndroidUtilities.dp(39); } } + if (twoLinesForName) { + messageTop += AndroidUtilities.dp(20); + } + animatedEmojiStack2 = AnimatedEmojiSpan.update(AnimatedEmojiDrawable.CACHE_TYPE_MESSAGES, this, animatedEmojiStack2, messageNameLayout); try { CharSequence messageStringFinal; @@ -1800,6 +2036,9 @@ public class DialogCell extends BaseCell { currentMessagePaint = Theme.dialogs_messagePaint[paintIndex]; } else if (!useForceThreeLines && !SharedConfig.useThreeLinesLayout || messageNameString != null) { messageStringFinal = TextUtils.ellipsize(messageString, currentMessagePaint, messageWidth - AndroidUtilities.dp(12), TextUtils.TruncateAt.END); + if (messageStringFinal instanceof Spanned && ((Spanned) messageStringFinal).getSpans(0, messageStringFinal.length(), FixedWidthSpan.class).length <= 0) { + messageStringFinal = TextUtils.ellipsize(messageString, currentMessagePaint, messageWidth - AndroidUtilities.dp(12 + (thumbsCount * (thumbSize + 2) - 2) + 5), TextUtils.TruncateAt.END); + } } else { messageStringFinal = messageString; } @@ -1814,15 +2053,15 @@ public class DialogCell extends BaseCell { } if (useForceThreeLines || SharedConfig.useThreeLinesLayout) { - if (hasMessageThumb && messageNameString != null) { - messageWidth += AndroidUtilities.dp(6); + if (thumbsCount > 0 && messageNameString != null) { + messageWidth += AndroidUtilities.dp(5); } messageLayout = StaticLayoutEx.createStaticLayout(messageStringFinal, currentMessagePaint, messageWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, AndroidUtilities.dp(1), false, TextUtils.TruncateAt.END, messageWidth, messageNameString != null ? 1 : 2); } else { - if (hasMessageThumb) { - messageWidth += thumbSize + AndroidUtilities.dp(6); + if (thumbsCount > 0) { + messageWidth += AndroidUtilities.dp((thumbsCount * (thumbSize + 2) - 2) + 5); if (LocaleController.isRTL) { - messageLeft -= thumbSize + AndroidUtilities.dp(6); + messageLeft -= AndroidUtilities.dp((thumbsCount * (thumbSize + 2) - 2) + 5); } } messageLayout = new StaticLayout(messageStringFinal, currentMessagePaint, messageWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); @@ -1842,10 +2081,11 @@ public class DialogCell extends BaseCell { if (nameLayout != null && nameLayout.getLineCount() > 0) { left = nameLayout.getLineLeft(0); widthpx = Math.ceil(nameLayout.getLineWidth(0)); + nameWidth += AndroidUtilities.dp(12); if (nameLayoutEllipsizeByGradient) { widthpx = Math.min(nameWidth, widthpx); } - if (dialogMuted && !drawVerified && drawScam == 0) { + if ((dialogMuted || drawUnmute) && !drawVerified && drawScam == 0) { nameMuteLeft = (int) (nameLeft + (nameWidth - widthpx) - AndroidUtilities.dp(6) - Theme.dialogs_muteDrawable.getIntrinsicWidth()); } else if (drawVerified) { nameMuteLeft = (int) (nameLeft + (nameWidth - widthpx) - AndroidUtilities.dp(6) - Theme.dialogs_verifiedDrawable.getIntrinsicWidth()); @@ -1905,7 +2145,7 @@ public class DialogCell extends BaseCell { nameLeft -= (nameWidth - widthpx); } } - if (dialogMuted || drawVerified || drawPremium || drawScam != 0) { + if (dialogMuted || drawUnmute || drawVerified || drawPremium || drawScam != 0) { nameMuteLeft = (int) (nameLeft + left + AndroidUtilities.dp(6)); } } @@ -1923,7 +2163,7 @@ public class DialogCell extends BaseCell { messageNameLeft -= messageNameLayout.getLineLeft(0); } } - if (messageLayout != null && hasMessageThumb) { + if (messageLayout != null && thumbsCount > 0) { try { int textLen = messageLayout.getText().length(); if (offsetName >= textLen) { @@ -1935,12 +2175,14 @@ public class DialogCell extends BaseCell { if (offset != 0) { offset += AndroidUtilities.dp(3); } - thumbImage.setImageX(messageLeft + offset); + for (int i = 0; i < thumbsCount; ++i) { + thumbImage[i].setImageX(messageLeft + offset + AndroidUtilities.dp((thumbSize + 2) * i)); + } } catch (Exception e) { FileLog.e(e); } } - if (messageLayout != null && printingStringType >= 0) { + if (messageLayout != null && printingStringType >= 0 && messageLayout.getText().length() > 0) { float x1, x2; if (printingStringReplaceIndex >= 0 && printingStringReplaceIndex + 1 < messageLayout.getText().length() ){ x1 = messageLayout.getPrimaryHorizontal(printingStringReplaceIndex); @@ -2052,8 +2294,10 @@ public class DialogCell extends BaseCell { MessageObject newMessageObject; if (currentDialogFolderId != 0) { newMessageObject = findFolderTopMessage(); + groupMessages = null; } else { - newMessageObject = MessagesController.getInstance(currentAccount).dialogMessage.get(dialog.id); + groupMessages = MessagesController.getInstance(currentAccount).dialogMessage.get(dialog.id); + newMessageObject = groupMessages != null && groupMessages.size() > 0 ? groupMessages.get(0) : null; } if (currentDialogId != dialog.id || message != null && message.getId() != dialog.top_message || @@ -2113,7 +2357,11 @@ public class DialogCell extends BaseCell { public void setChecked(boolean checked, boolean animated) { if (checkBox == null) { - return; + checkBox = new CheckBox2(getContext(), 21, resourcesProvider); + checkBox.setColor(null, Theme.key_windowBackgroundWhite, Theme.key_checkboxCheck); + checkBox.setDrawUnchecked(false); + checkBox.setDrawBackgroundAsArc(3); + addView(checkBox); } checkBox.setChecked(checked, animated); } @@ -2127,7 +2375,8 @@ public class DialogCell extends BaseCell { if (!dialogs.isEmpty()) { for (int a = 0, N = dialogs.size(); a < N; a++) { TLRPC.Dialog dialog = dialogs.get(a); - MessageObject object = MessagesController.getInstance(currentAccount).dialogMessage.get(dialog.id); + ArrayList groupMessages = MessagesController.getInstance(currentAccount).dialogMessage.get(dialog.id); + MessageObject object = groupMessages != null && groupMessages.size() > 0 ? groupMessages.get(0) : null; if (object != null && (maxMessage == null || object.messageOwner.date > maxMessage.messageOwner.date)) { maxMessage = object; } @@ -2154,21 +2403,37 @@ public class DialogCell extends BaseCell { unreadCount = customDialog.unread_count; drawPin = customDialog.pinned; dialogMuted = customDialog.muted; + hasUnmutedTopics = false; avatarDrawable.setInfo(customDialog.id, customDialog.name, null); avatarImage.setImage(null, "50_50", avatarDrawable, null, 0); - thumbImage.setImageBitmap((BitmapDrawable) null); + for (int i = 0; i < thumbImage.length; ++i) { + thumbImage[i].setImageBitmap((BitmapDrawable) null); + } + avatarImage.setRoundRadius(AndroidUtilities.dp(28)); + drawUnmute = false; } else { int oldUnreadCount = unreadCount; boolean oldHasReactionsMentions = reactionMentionCount != 0; boolean oldMarkUnread = markUnread; + hasUnmutedTopics = false; + readOutboxMaxId = -1; if (isDialogCell) { TLRPC.Dialog dialog = MessagesController.getInstance(currentAccount).dialogs_dict.get(currentDialogId); if (dialog != null) { + readOutboxMaxId = dialog.read_outbox_max_id; if (mask == 0) { clearingDialog = MessagesController.getInstance(currentAccount).isClearingDialog(dialog.id); - message = MessagesController.getInstance(currentAccount).dialogMessage.get(dialog.id); + groupMessages = MessagesController.getInstance(currentAccount).dialogMessage.get(dialog.id); + message = groupMessages != null && groupMessages.size() > 0 ? groupMessages.get(0) : null; lastUnreadState = message != null && message.isUnread(); - if (dialog instanceof TLRPC.TL_dialogFolder) { + TLRPC.Chat localChat = MessagesController.getInstance(currentAccount).getChat(-dialog.id); + if (localChat != null && localChat.forum) { + int[] counts = MessagesController.getInstance(currentAccount).getTopicsController().getForumUnreadCount(localChat.id); + unreadCount = counts[0]; + mentionCount = counts[1]; + reactionMentionCount = counts[2]; + hasUnmutedTopics = counts[3] != 0; + } else if (dialog instanceof TLRPC.TL_dialogFolder) { unreadCount = MessagesStorage.getInstance(currentAccount).getArchiveUnreadCount(); mentionCount = 0; reactionMentionCount = 0; @@ -2201,6 +2466,11 @@ public class DialogCell extends BaseCell { } else { drawPin = false; } + if (forumTopic != null) { + unreadCount = forumTopic.unread_count; + mentionCount = forumTopic.unread_mentions_count; + reactionMentionCount = forumTopic.unread_reactions_count; + } if (dialogsType == 2) { drawPin = false; } @@ -2225,9 +2495,9 @@ public class DialogCell extends BaseCell { } invalidate(); } - if (isDialogCell) { + if (isDialogCell || isTopic) { if ((mask & MessagesController.UPDATE_MASK_USER_PRINT) != 0) { - CharSequence printString = MessagesController.getInstance(currentAccount).getPrintingString(currentDialogId, 0, true); + CharSequence printString = MessagesController.getInstance(currentAccount).getPrintingString(currentDialogId, getTopicId(), true); if (lastPrintString != null && printString == null || lastPrintString == null && printString != null || lastPrintString != null && !lastPrintString.equals(printString)) { continueUpdate = true; } @@ -2274,7 +2544,15 @@ public class DialogCell extends BaseCell { int newCount; int newMentionCount; int newReactionCout = 0; - if (dialog instanceof TLRPC.TL_dialogFolder) { + + TLRPC.Chat localChat = dialog == null ? null : MessagesController.getInstance(currentAccount).getChat(-dialog.id); + if (localChat != null && localChat.forum) { + int[] counts = MessagesController.getInstance(currentAccount).getTopicsController().getForumUnreadCount(localChat.id); + newCount = counts[0]; + newMentionCount = counts[1]; + newReactionCout = counts[2]; + hasUnmutedTopics = counts[3] != 0; + } else if (dialog instanceof TLRPC.TL_dialogFolder) { newCount = MessagesStorage.getInstance(currentAccount).getArchiveUnreadCount(); newMentionCount = 0; } else if (dialog != null) { @@ -2314,6 +2592,7 @@ public class DialogCell extends BaseCell { long dialogId; if (currentDialogFolderId != 0) { dialogMuted = false; + drawUnmute = false; message = findFolderTopMessage(); if (message != null) { dialogId = message.getDialogId(); @@ -2321,7 +2600,22 @@ public class DialogCell extends BaseCell { dialogId = 0; } } else { - dialogMuted = isDialogCell && MessagesController.getInstance(currentAccount).isDialogMuted(currentDialogId); + drawUnmute = false; + if (forumTopic != null) { + boolean allDialogMuted = MessagesController.getInstance(currentAccount).isDialogMuted(currentDialogId, 0); + topicMuted = MessagesController.getInstance(currentAccount).isDialogMuted(currentDialogId, forumTopic.id); + if (allDialogMuted == topicMuted) { + dialogMuted = false; + drawUnmute = false; + } else { + dialogMuted = topicMuted; + drawUnmute = !topicMuted; + } + } else { + dialogMuted = isDialogCell && MessagesController.getInstance(currentAccount).isDialogMuted(currentDialogId, getTopicId()); + } + + dialogId = currentDialogId; } @@ -2369,7 +2663,7 @@ public class DialogCell extends BaseCell { } } - if (animated && (oldUnreadCount != unreadCount || oldMarkUnread != markUnread) && (System.currentTimeMillis() - lastDialogChangedTime) > 100) { + if (animated && (oldUnreadCount != unreadCount || oldMarkUnread != markUnread) && (!isDialogCell || (System.currentTimeMillis() - lastDialogChangedTime) > 100)) { if (countAnimator != null) { countAnimator.cancel(); } @@ -2399,8 +2693,8 @@ public class DialogCell extends BaseCell { countAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT); } if (drawCount && drawCount2 && countLayout != null) { - String oldStr = String.valueOf(oldUnreadCount); - String newStr = String.valueOf(unreadCount); + String oldStr = String.format("%d", oldUnreadCount); + String newStr = String.format("%d", unreadCount); if (oldStr.length() == newStr.length()) { SpannableStringBuilder oldSpannableStr = new SpannableStringBuilder(oldStr); @@ -2456,15 +2750,17 @@ public class DialogCell extends BaseCell { } reactionsMentionsAnimator.start(); } + + avatarImage.setRoundRadius(isDialogCell && chat != null && chat.forum && currentDialogFolderId == 0 ? AndroidUtilities.dp(16) : AndroidUtilities.dp(28)); } - if (getMeasuredWidth() != 0 || getMeasuredHeight() != 0) { + if (!isTopic && (getMeasuredWidth() != 0 || getMeasuredHeight() != 0)) { buildLayout(); } else { requestLayout(); } if (!animated) { - dialogMutedProgress = dialogMuted ? 1f : 0f; + dialogMutedProgress = (dialogMuted || drawUnmute) ? 1f : 0f; if (countAnimator != null) { countAnimator.cancel(); } @@ -2473,6 +2769,10 @@ public class DialogCell extends BaseCell { invalidate(); } + private int getTopicId() { + return forumTopic == null ? 0 : forumTopic.id; + } + @Override public float getTranslationX() { return translationX; @@ -2480,7 +2780,10 @@ public class DialogCell extends BaseCell { @Override public void setTranslationX(float value) { - translationX = (int) value; + if (value == translationX) { + return; + } + translationX = value; if (translationDrawable != null && translationX == 0) { translationDrawable.setProgress(0.0f); translationAnimationStarted = false; @@ -2588,7 +2891,7 @@ public class DialogCell extends BaseCell { translationDrawable = Theme.dialogs_swipeUnreadDrawable; } } else if (SharedConfig.getChatSwipeAction(currentAccount) == SwipeGestureSettingsView.SWIPE_GESTURE_PIN) { - if (drawPin) { + if (getIsPinned()) { swipeMessage = LocaleController.getString("SwipeUnpin", swipeMessageStringId = R.string.SwipeUnpin); translationDrawable = Theme.dialogs_swipeUnpinDrawable; } else { @@ -2721,10 +3024,10 @@ public class DialogCell extends BaseCell { } if (currentDialogFolderId != 0 && (!SharedConfig.archiveHidden || archiveBackgroundProgress != 0)) { Theme.dialogs_pinnedPaint.setColor(AndroidUtilities.getOffsetColor(0, Theme.getColor(Theme.key_chats_pinnedOverlay, resourcesProvider), archiveBackgroundProgress, 1.0f)); - canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), Theme.dialogs_pinnedPaint); - } else if (drawPin || drawPinBackground) { + canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight() - translateY, Theme.dialogs_pinnedPaint); + } else if (getIsPinned() || drawPinBackground) { Theme.dialogs_pinnedPaint.setColor(Theme.getColor(Theme.key_chats_pinnedOverlay, resourcesProvider)); - canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), Theme.dialogs_pinnedPaint); + canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight() - translateY, Theme.dialogs_pinnedPaint); } if (translationX != 0 || cornerProgress != 0.0f) { @@ -2741,7 +3044,7 @@ public class DialogCell extends BaseCell { if (currentDialogFolderId != 0 && (!SharedConfig.archiveHidden || archiveBackgroundProgress != 0)) { Theme.dialogs_pinnedPaint.setColor(AndroidUtilities.getOffsetColor(0, Theme.getColor(Theme.key_chats_pinnedOverlay, resourcesProvider), archiveBackgroundProgress, 1.0f)); canvas.drawRoundRect(rect, cornersRadius, cornersRadius, Theme.dialogs_pinnedPaint); - } else if (drawPin || drawPinBackground) { + } else if (getIsPinned() || drawPinBackground) { Theme.dialogs_pinnedPaint.setColor(Theme.getColor(Theme.key_chats_pinnedOverlay, resourcesProvider)); canvas.drawRoundRect(rect, cornersRadius, cornersRadius, Theme.dialogs_pinnedPaint); } @@ -2815,6 +3118,16 @@ public class DialogCell extends BaseCell { canvas.restore(); } + if (drawLock2()) { + Theme.dialogs_lock2Drawable.setBounds( + lock2Left, + timeTop + (timeLayout.getHeight() - Theme.dialogs_lock2Drawable.getIntrinsicHeight()) / 2, + lock2Left + Theme.dialogs_lock2Drawable.getIntrinsicWidth(), + timeTop + (timeLayout.getHeight() - Theme.dialogs_lock2Drawable.getIntrinsicHeight()) / 2 + Theme.dialogs_lock2Drawable.getIntrinsicHeight() + ); + Theme.dialogs_lock2Drawable.draw(canvas); + } + if (messageNameLayout != null) { if (currentDialogFolderId != 0) { Theme.dialogs_messageNamePaint.setColor(Theme.dialogs_messageNamePaint.linkColor = Theme.getColor(Theme.key_chats_nameMessageArchived_threeLines, resourcesProvider)); @@ -2827,6 +3140,7 @@ public class DialogCell extends BaseCell { canvas.translate(messageNameLeft, messageNameTop); try { messageNameLayout.draw(canvas); + AnimatedEmojiSpan.drawAnimatedEmojis(canvas, messageNameLayout, animatedEmojiStack2, -.075f, null, 0, 0, 0, 1f); } catch (Exception e) { FileLog.e(e); } @@ -2913,15 +3227,16 @@ public class DialogCell extends BaseCell { lastStatusDrawableParams = (this.drawClock ? 1 : 0) + (this.drawCheck1 ? 2 : 0) + (this.drawCheck2 ? 4 : 0); } - if (dialogsType != 2 && (dialogMuted || dialogMutedProgress > 0) && !drawVerified && drawScam == 0 && !drawPremium) { - if (dialogMuted && dialogMutedProgress != 1f) { + boolean drawMuted = drawUnmute || dialogMuted; + if (dialogsType != 2 && (drawMuted || dialogMutedProgress > 0) && !drawVerified && drawScam == 0 && !drawPremium) { + if (drawMuted && dialogMutedProgress != 1f) { dialogMutedProgress += 16 / 150f; if (dialogMutedProgress > 1f) { dialogMutedProgress = 1f; } else { invalidate(); } - } else if (!dialogMuted && dialogMutedProgress != 0f) { + } else if (!drawMuted && dialogMutedProgress != 0f) { dialogMutedProgress -= 16 / 150f; if (dialogMutedProgress < 0f) { dialogMutedProgress = 0f; @@ -2929,16 +3244,29 @@ public class DialogCell extends BaseCell { invalidate(); } } - setDrawableBounds(Theme.dialogs_muteDrawable, nameMuteLeft - AndroidUtilities.dp(useForceThreeLines || SharedConfig.useThreeLinesLayout ? 0 : 1), AndroidUtilities.dp(SharedConfig.useThreeLinesLayout ? 13.5f : 17.5f)); + float muteX = nameMuteLeft - AndroidUtilities.dp(useForceThreeLines || SharedConfig.useThreeLinesLayout ? 0 : 1); + float muteY = AndroidUtilities.dp(SharedConfig.useThreeLinesLayout ? 13.5f : 17.5f); + setDrawableBounds(Theme.dialogs_muteDrawable, muteX, muteY); + setDrawableBounds(Theme.dialogs_unmuteDrawable, muteX, muteY); if (dialogMutedProgress != 1f) { canvas.save(); canvas.scale(dialogMutedProgress, dialogMutedProgress, Theme.dialogs_muteDrawable.getBounds().centerX(), Theme.dialogs_muteDrawable.getBounds().centerY()); - Theme.dialogs_muteDrawable.setAlpha((int) (255 * dialogMutedProgress)); - Theme.dialogs_muteDrawable.draw(canvas); - Theme.dialogs_muteDrawable.setAlpha(255); + if (drawUnmute) { + Theme.dialogs_unmuteDrawable.setAlpha((int) (255 * dialogMutedProgress)); + Theme.dialogs_unmuteDrawable.draw(canvas); + Theme.dialogs_unmuteDrawable.setAlpha(255); + } else { + Theme.dialogs_muteDrawable.setAlpha((int) (255 * dialogMutedProgress)); + Theme.dialogs_muteDrawable.draw(canvas); + Theme.dialogs_muteDrawable.setAlpha(255); + } canvas.restore(); } else { - Theme.dialogs_muteDrawable.draw(canvas); + if (drawUnmute) { + Theme.dialogs_unmuteDrawable.draw(canvas); + } else { + Theme.dialogs_muteDrawable.draw(canvas); + } } } else if (drawVerified) { @@ -2978,11 +3306,17 @@ public class DialogCell extends BaseCell { setDrawableBounds(Theme.dialogs_errorDrawable, errorLeft + AndroidUtilities.dp(5.5f), errorTop + AndroidUtilities.dp(5)); Theme.dialogs_errorDrawable.draw(canvas); } else if ((drawCount || drawMention) && drawCount2 || countChangeProgress != 1f || drawReactionMention || reactionsMentionsChangeProgress != 1f) { + boolean drawCounterMuted; + if (isTopic) { + drawCounterMuted = topicMuted; + } else { + drawCounterMuted = chat != null && chat.forum && forumTopic == null ? !hasUnmutedTopics : dialogMuted; + } if (drawCount && drawCount2 || countChangeProgress != 1f) { final float progressFinal = (unreadCount == 0 && !markUnread) ? 1f - countChangeProgress : countChangeProgress; if (countOldLayout == null || unreadCount == 0) { StaticLayout drawLayout = unreadCount == 0 ? countOldLayout : countLayout; - Paint paint = dialogMuted || currentDialogFolderId != 0 ? Theme.dialogs_countGrayPaint : Theme.dialogs_countPaint; + Paint paint = drawCounterMuted || currentDialogFolderId != 0 ? Theme.dialogs_countGrayPaint : Theme.dialogs_countPaint; paint.setAlpha((int) ((1.0f - reorderIconProgress) * 255)); Theme.dialogs_countTextPaint.setAlpha((int) ((1.0f - reorderIconProgress) * 255)); @@ -2990,7 +3324,7 @@ public class DialogCell extends BaseCell { rect.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23)); if (progressFinal != 1f) { - if (drawPin) { + if (getIsPinned()) { Theme.dialogs_pinnedDrawable.setAlpha((int) ((1.0f - reorderIconProgress) * 255)); setDrawableBounds(Theme.dialogs_pinnedDrawable, pinLeft, pinTop); canvas.save(); @@ -3014,7 +3348,7 @@ public class DialogCell extends BaseCell { canvas.restore(); } } else { - Paint paint = dialogMuted || currentDialogFolderId != 0 ? Theme.dialogs_countGrayPaint : Theme.dialogs_countPaint; + Paint paint = drawCounterMuted || currentDialogFolderId != 0 ? Theme.dialogs_countGrayPaint : Theme.dialogs_countPaint; paint.setAlpha((int) ((1.0f - reorderIconProgress) * 255)); Theme.dialogs_countTextPaint.setAlpha((int) ((1.0f - reorderIconProgress) * 255)); @@ -3077,7 +3411,7 @@ public class DialogCell extends BaseCell { int x = mentionLeft - AndroidUtilities.dp(5.5f); rect.set(x, countTop, x + mentionWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23)); - Paint paint = dialogMuted && folderId != 0 ? Theme.dialogs_countGrayPaint : Theme.dialogs_countPaint; + Paint paint = drawCounterMuted && folderId != 0 ? Theme.dialogs_countGrayPaint : Theme.dialogs_countPaint; canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, paint); if (mentionLayout != null) { Theme.dialogs_countTextPaint.setAlpha((int) ((1.0f - reorderIconProgress) * 255)); @@ -3113,7 +3447,7 @@ public class DialogCell extends BaseCell { Theme.dialogs_reactionsMentionDrawable.draw(canvas); canvas.restore(); } - } else if (drawPin) { + } else if (getIsPinned()) { Theme.dialogs_pinnedDrawable.setAlpha((int) ((1.0f - reorderIconProgress) * 255)); setDrawableBounds(Theme.dialogs_pinnedDrawable, pinLeft, pinTop); Theme.dialogs_pinnedDrawable.draw(canvas); @@ -3125,17 +3459,19 @@ public class DialogCell extends BaseCell { canvas.scale(scale, scale, avatarImage.getCenterX(), avatarImage.getCenterY()); } - if (currentDialogFolderId == 0 || archivedChatsDrawable == null || !archivedChatsDrawable.isDraw()) { + if (drawAvatar && (currentDialogFolderId == 0 || archivedChatsDrawable == null || !archivedChatsDrawable.isDraw())) { avatarImage.draw(canvas); } - if (hasMessageThumb) { - thumbImage.draw(canvas); - if (drawPlay) { - int x = (int) (thumbImage.getCenterX() - Theme.dialogs_playDrawable.getIntrinsicWidth() / 2); - int y = (int) (thumbImage.getCenterY() - Theme.dialogs_playDrawable.getIntrinsicHeight() / 2); - setDrawableBounds(Theme.dialogs_playDrawable, x, y); - Theme.dialogs_playDrawable.draw(canvas); + if (thumbsCount > 0) { + for (int i = 0; i < thumbsCount; ++i) { + thumbImage[i].draw(canvas); + if (drawPlay[i]) { + int x = (int) (thumbImage[i].getCenterX() - Theme.dialogs_playDrawable.getIntrinsicWidth() / 2); + int y = (int) (thumbImage[i].getCenterY() - Theme.dialogs_playDrawable.getIntrinsicHeight() / 2); + setDrawableBounds(Theme.dialogs_playDrawable, x, y); + Theme.dialogs_playDrawable.draw(canvas); + } } } @@ -3283,7 +3619,7 @@ public class DialogCell extends BaseCell { if (fullSeparator || currentDialogFolderId != 0 && archiveHidden && !fullSeparator2 || fullSeparator2 && !archiveHidden) { left = 0; } else { - left = AndroidUtilities.dp(72); + left = AndroidUtilities.dp(messagePaddingStart); } if (LocaleController.isRTL) { canvas.drawLine(0, getMeasuredHeight() - 1, getMeasuredWidth() - left, getMeasuredHeight() - 1, Theme.dividerPaint); @@ -3427,8 +3763,8 @@ public class DialogCell extends BaseCell { } public void onReorderStateChanged(boolean reordering, boolean animated) { - if (!drawPin && reordering || drawReorder == reordering) { - if (!drawPin) { + if (!getIsPinned() && reordering || drawReorder == reordering) { + if (!getIsPinned()) { drawReorder = false; } return; @@ -3568,9 +3904,12 @@ public class DialogCell extends BaseCell { StringBuilder messageString = new StringBuilder(); messageString.append(message.messageText); if (!message.isMediaEmpty()) { - if (!TextUtils.isEmpty(message.caption)) { - messageString.append(". "); - messageString.append(message.caption); + MessageObject captionMessage = getCaptionMessage(); + if (captionMessage != null && !TextUtils.isEmpty(captionMessage.caption)) { + if (messageString.length() > 0) { + messageString.append(". "); + } + messageString.append(captionMessage); } } int len = messageLayout == null ? -1 : messageLayout.getText().length(); @@ -3590,6 +3929,31 @@ public class DialogCell extends BaseCell { event.setContentDescription(sb.toString()); } + private MessageObject getCaptionMessage() { + if (groupMessages == null) { + if (message != null && message.caption != null) { + return message; + } + return null; + } + + MessageObject captionMessage = null; + int hasCaption = 0; + for (int i = 0; i < groupMessages.size(); ++i) { + MessageObject msg = groupMessages.get(i); + if (msg != null && msg.caption != null) { + captionMessage = msg; + if (!TextUtils.isEmpty(msg.caption)) { + hasCaption++; + } + } + } + if (hasCaption > 1) { + return null; + } + return captionMessage; + } + public void setClipProgress(float value) { clipProgress = value; invalidate(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java index b40d580c2..c955d8f3e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java @@ -15,7 +15,6 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; -import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; @@ -29,15 +28,12 @@ import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; -import android.view.animation.LinearInterpolator; -import android.view.animation.OvershootInterpolator; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AndroidUtilities; @@ -49,6 +45,7 @@ import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaDataController; import org.telegram.messenger.MessageObject; +import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; @@ -59,7 +56,6 @@ import org.telegram.ui.ActionBar.DrawerLayoutContainer; import org.telegram.ui.ActionBar.SimpleTextView; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AnimatedEmojiDrawable; -import org.telegram.ui.Components.AnimatedFloat; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.CubicBezierInterpolator; @@ -83,7 +79,7 @@ public class DrawerProfileCell extends FrameLayout implements NotificationCenter private ImageView shadowView; private ImageView arrowView; private RLottieImageView darkThemeView; - private RLottieDrawable sunDrawable; + private static RLottieDrawable sunDrawable; private boolean updateRightDrawable = true; private AnimatedEmojiDrawable.SwapAnimatedEmojiDrawable status; private AnimatedStatusView animatedStatus; @@ -139,7 +135,9 @@ public class DrawerProfileCell extends FrameLayout implements NotificationCenter nameTextView.setTextSize(15); nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); nameTextView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); - addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM, 16, 0, 76, 28)); + nameTextView.setEllipsizeByGradient(true); + nameTextView.setRightDrawableOutside(true); + addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM, 16, 0, 52, 28)); phoneTextView = new TextView(context); phoneTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); @@ -147,7 +145,7 @@ public class DrawerProfileCell extends FrameLayout implements NotificationCenter phoneTextView.setMaxLines(1); phoneTextView.setSingleLine(true); phoneTextView.setGravity(Gravity.LEFT); - addView(phoneTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM, 16, 0, 76, 9)); + addView(phoneTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM, 16, 0, 52, 9)); arrowView = new ImageView(context); arrowView.setScaleType(ImageView.ScaleType.CENTER); @@ -155,14 +153,18 @@ public class DrawerProfileCell extends FrameLayout implements NotificationCenter addView(arrowView, LayoutHelper.createFrame(59, 59, Gravity.RIGHT | Gravity.BOTTOM)); setArrowState(false); - sunDrawable = new RLottieDrawable(R.raw.sun, "" + R.raw.sun, AndroidUtilities.dp(28), AndroidUtilities.dp(28), true, null); - if (Theme.isCurrentThemeDay()) { - sunDrawable.setCustomEndFrame(36); - } else { - sunDrawable.setCustomEndFrame(0); - sunDrawable.setCurrentFrame(36); + boolean playDrawable; + if (playDrawable = sunDrawable == null) { + sunDrawable = new RLottieDrawable(R.raw.sun, "" + R.raw.sun, AndroidUtilities.dp(28), AndroidUtilities.dp(28), true, null); + sunDrawable.setPlayInDirectionOfCustomEndFrame(true); + if (Theme.isCurrentThemeDay()) { + sunDrawable.setCustomEndFrame(0); + sunDrawable.setCurrentFrame(0); + } else { + sunDrawable.setCurrentFrame(35); + sunDrawable.setCustomEndFrame(36); + } } - sunDrawable.setPlayInDirectionOfCustomEndFrame(true); darkThemeView = new RLottieImageView(context) { @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { @@ -189,6 +191,9 @@ public class DrawerProfileCell extends FrameLayout implements NotificationCenter darkThemeView.setBackgroundDrawable(Theme.createSelectorDrawable(darkThemeBackgroundColor = Theme.getColor(Theme.key_listSelector), 1, AndroidUtilities.dp(17))); Theme.setRippleDrawableForceSoftware((RippleDrawable) darkThemeView.getBackground()); } + if (!playDrawable && sunDrawable.getCustomEndFrame() != sunDrawable.getCurrentFrame()) { + darkThemeView.playAnimation(); + } darkThemeView.setOnClickListener(v -> { if (switchingTheme) { return; @@ -719,8 +724,15 @@ public class DrawerProfileCell extends FrameLayout implements NotificationCenter nameTextView.invalidate(); } else if (id == NotificationCenter.userEmojiStatusUpdated) { setUser((TLRPC.User) args[0], accountsShown); - } else if (id == NotificationCenter.currentUserPremiumStatusChanged || id == NotificationCenter.updateInterfaces) { + } else if (id == NotificationCenter.currentUserPremiumStatusChanged) { setUser(UserConfig.getInstance(UserConfig.selectedAccount).getCurrentUser(), accountsShown); + } else if (id == NotificationCenter.updateInterfaces) { + int flags = (int) args[0]; + if ((flags & MessagesController.UPDATE_MASK_NAME) != 0 || (flags & MessagesController.UPDATE_MASK_AVATAR) != 0 || + (flags & MessagesController.UPDATE_MASK_STATUS) != 0 || (flags & MessagesController.UPDATE_MASK_PHONE) != 0 || + (flags & MessagesController.UPDATE_MASK_EMOJI_STATUS) != 0) { + setUser(UserConfig.getInstance(UserConfig.selectedAccount).getCurrentUser(), accountsShown); + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerUserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerUserCell.java index 8c5facc69..fcff6fb5f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerUserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerUserCell.java @@ -50,7 +50,7 @@ public class DrawerUserCell extends FrameLayout implements NotificationCenter.No super(context); avatarDrawable = new AvatarDrawable(); - avatarDrawable.setTextSize(AndroidUtilities.dp(12)); + avatarDrawable.setTextSize(AndroidUtilities.dp(20)); imageView = new BackupImageView(context); imageView.setRoundRadius(AndroidUtilities.dp(18)); @@ -63,7 +63,8 @@ public class DrawerUserCell extends FrameLayout implements NotificationCenter.No textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); textView.setMaxLines(1); textView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); - addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.CENTER_VERTICAL, 72, 0, 60, 0)); + textView.setEllipsizeByGradient(24); + addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.CENTER_VERTICAL, 72, 0, 14, 0)); status = new AnimatedEmojiDrawable.SwapAnimatedEmojiDrawable(textView, AndroidUtilities.dp(20)); textView.setRightDrawable(status); @@ -141,14 +142,18 @@ public class DrawerUserCell extends FrameLayout implements NotificationCenter.No if (user.emoji_status instanceof TLRPC.TL_emojiStatusUntil && ((TLRPC.TL_emojiStatusUntil) user.emoji_status).until > (int) (System.currentTimeMillis() / 1000)) { textView.setDrawablePadding(AndroidUtilities.dp(4)); status.set(((TLRPC.TL_emojiStatusUntil) user.emoji_status).document_id, true); + textView.setRightDrawableOutside(true); } else if (user.emoji_status instanceof TLRPC.TL_emojiStatus) { textView.setDrawablePadding(AndroidUtilities.dp(4)); status.set(((TLRPC.TL_emojiStatus) user.emoji_status).document_id, true); + textView.setRightDrawableOutside(true); } else if (MessagesController.getInstance(account).isPremiumUser(user)) { textView.setDrawablePadding(AndroidUtilities.dp(6)); status.set(PremiumGradient.getInstance().premiumStarDrawableMini, true); + textView.setRightDrawableOutside(true); } else { status.set((Drawable) null, true); + textView.setRightDrawableOutside(false); } status.setColor(Theme.getColor(Theme.key_chats_verifiedBackground)); imageView.getImageReceiver().setCurrentAccount(account); @@ -163,10 +168,12 @@ public class DrawerUserCell extends FrameLayout implements NotificationCenter.No @Override protected void onDraw(Canvas canvas) { if (UserConfig.getActivatedAccountsCount() <= 1 || !NotificationsController.getInstance(accountNumber).showBadgeNumber) { + textView.setRightPadding(0); return; } int counter = MessagesStorage.getInstance(accountNumber).getMainUnreadCount(); if (counter <= 0) { + textView.setRightPadding(0); return; } @@ -181,6 +188,8 @@ public class DrawerUserCell extends FrameLayout implements NotificationCenter.No canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, Theme.dialogs_countPaint); canvas.drawText(text, rect.left + (rect.width() - textWidth) / 2, countTop + AndroidUtilities.dp(16), Theme.dialogs_countTextPaint); + + textView.setRightPadding(countWidth + AndroidUtilities.dp(14 + 12)); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCreateUserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCreateUserCell.java index e1d42eab4..0314fe763 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCreateUserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCreateUserCell.java @@ -375,7 +375,7 @@ public class GroupCreateUserCell extends FrameLayout { } } else if (currentChat.has_geo) { statusTextView.setText(LocaleController.getString("MegaLocation", R.string.MegaLocation)); - } else if (TextUtils.isEmpty(currentChat.username)) { + } else if (!ChatObject.isPublic(currentChat)) { if (ChatObject.isChannel(currentChat) && !currentChat.megagroup) { statusTextView.setText(LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate)); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ManageChatUserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ManageChatUserCell.java index 89ca2b2ac..868e6d883 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ManageChatUserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ManageChatUserCell.java @@ -316,7 +316,7 @@ public class ManageChatUserCell extends FrameLayout { } } else if (currentChat.has_geo) { statusTextView.setText(LocaleController.getString("MegaLocation", R.string.MegaLocation)); - } else if (TextUtils.isEmpty(currentChat.username)) { + } else if (!ChatObject.isPublic(currentChat)) { statusTextView.setText(LocaleController.getString("MegaPrivate", R.string.MegaPrivate)); } else { statusTextView.setText(LocaleController.getString("MegaPublic", R.string.MegaPublic)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java index 5e1b7b80b..720123540 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java @@ -19,6 +19,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ChatObject; import org.telegram.messenger.MediaDataController; import org.telegram.messenger.Emoji; import org.telegram.messenger.UserConfig; @@ -131,8 +132,9 @@ public class MentionCell extends LinearLayout { imageView.setImageDrawable(avatarDrawable); } nameTextView.setText(chat.title); - if (chat.username != null) { - usernameTextView.setText("@" + chat.username); + String username; + if ((username = ChatObject.getPublicUsername(chat)) != null) { + usernameTextView.setText("@" + username); } else { usernameTextView.setText(""); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java index f23817b98..a9ccc10f8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java @@ -18,8 +18,6 @@ import android.text.TextPaint; import android.text.TextUtils; import android.view.accessibility.AccessibilityNodeInfo; -import androidx.annotation.NonNull; - import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; @@ -44,6 +42,8 @@ import org.telegram.ui.Components.CombinedDrawable; import org.telegram.ui.Components.Premium.PremiumGradient; import org.telegram.ui.NotificationsSettingsActivity; +import java.util.Locale; + public class ProfileSearchCell extends BaseCell implements NotificationCenter.NotificationCenterDelegate { private CharSequence currentName; @@ -338,9 +338,10 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No if (drawCount) { TLRPC.Dialog dialog = MessagesController.getInstance(currentAccount).dialogs_dict.get(dialog_id); - if (dialog != null && dialog.unread_count != 0) { - lastUnreadCount = dialog.unread_count; - String countString = String.format("%d", dialog.unread_count); + int unreadCount = MessagesController.getInstance(currentAccount).getDialogUnreadCount(dialog); + if (unreadCount != 0) { + lastUnreadCount = unreadCount; + String countString = String.format(Locale.US, "%d", unreadCount); countWidth = Math.max(AndroidUtilities.dp(12), (int) Math.ceil(Theme.dialogs_countTextPaint.measureText(countString))); countLayout = new StaticLayout(countString, Theme.dialogs_countTextPaint, countWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); int w = countWidth + AndroidUtilities.dp(18); @@ -410,9 +411,9 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No } else { if (ChatObject.isChannel(chat) && !chat.megagroup) { if (chat.participants_count != 0) { - statusString = LocaleController.formatPluralString("Subscribers", chat.participants_count); + statusString = LocaleController.formatPluralStringComma("Subscribers", chat.participants_count); } else { - if (TextUtils.isEmpty(chat.username)) { + if (!ChatObject.isPublic(chat)) { statusString = LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate).toLowerCase(); } else { statusString = LocaleController.getString("ChannelPublic", R.string.ChannelPublic).toLowerCase(); @@ -420,11 +421,11 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No } } else { if (chat.participants_count != 0) { - statusString = LocaleController.formatPluralString("Members", chat.participants_count); + statusString = LocaleController.formatPluralStringComma("Members", chat.participants_count); } else { if (chat.has_geo) { statusString = LocaleController.getString("MegaLocation", R.string.MegaLocation); - } else if (TextUtils.isEmpty(chat.username)) { + } else if (!ChatObject.isPublic(chat)) { statusString = LocaleController.getString("MegaPrivate", R.string.MegaPrivate).toLowerCase(); } else { statusString = LocaleController.getString("MegaPublic", R.string.MegaPublic).toLowerCase(); @@ -554,6 +555,7 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No avatarImage.setImage(null, null, avatarDrawable, null, null, 0); } + avatarImage.setRoundRadius(chat != null && chat.forum ? AndroidUtilities.dp(16) : AndroidUtilities.dp(23)); if (mask != 0) { boolean continueUpdate = false; if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0 && user != null || (mask & MessagesController.UPDATE_MASK_CHAT_AVATAR) != 0 && chat != null) { @@ -586,7 +588,7 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No } if (!continueUpdate && drawCount && (mask & MessagesController.UPDATE_MASK_READ_DIALOG_MESSAGE) != 0) { TLRPC.Dialog dialog = MessagesController.getInstance(currentAccount).dialogs_dict.get(dialog_id); - if (dialog != null && dialog.unread_count != lastUnreadCount) { + if (dialog != null && MessagesController.getInstance(currentAccount).getDialogUnreadCount(dialog) != lastUnreadCount) { continueUpdate = true; } } @@ -667,7 +669,7 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No if (countLayout != null) { int x = countLeft - AndroidUtilities.dp(5.5f); rect.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23)); - canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, MessagesController.getInstance(currentAccount).isDialogMuted(dialog_id) ? Theme.dialogs_countGrayPaint : Theme.dialogs_countPaint); + canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, MessagesController.getInstance(currentAccount).isDialogMuted(dialog_id, 0) ? Theme.dialogs_countGrayPaint : Theme.dialogs_countPaint); canvas.save(); canvas.translate(countLeft, countTop + AndroidUtilities.dp(4)); countLayout.draw(canvas); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java index 3846c84c8..5dabeee80 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java @@ -11,6 +11,7 @@ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; import android.os.SystemClock; +import android.text.Layout; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; @@ -19,10 +20,13 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.TextView; +import androidx.dynamicanimation.animation.FloatValueHolder; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ContactsController; import org.telegram.messenger.DialogObject; -import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; @@ -30,16 +34,19 @@ import org.telegram.messenger.UserConfig; import org.telegram.messenger.UserObject; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.SimpleTextView; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.CheckBox2; +import org.telegram.ui.Components.Forum.ForumUtilities; import org.telegram.ui.Components.LayoutHelper; public class ShareDialogCell extends FrameLayout { private BackupImageView imageView; private TextView nameTextView; + private SimpleTextView topicTextView; private CheckBox2 checkBox; private AvatarDrawable avatarDrawable = new AvatarDrawable(); private TLRPC.User user; @@ -49,6 +56,8 @@ public class ShareDialogCell extends FrameLayout { private long lastUpdateTime; private long currentDialog; + private boolean topicWasVisible; + private int currentAccount = UserConfig.selectedAccount; private final Theme.ResourcesProvider resourcesProvider; @@ -80,6 +89,14 @@ public class ShareDialogCell extends FrameLayout { nameTextView.setEllipsize(TextUtils.TruncateAt.END); addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 6, currentType == TYPE_CREATE ? 58 : 66, 6, 0)); + topicTextView = new SimpleTextView(context); + topicTextView.setTextColor(getThemedColor(type == TYPE_CALL ? Theme.key_voipgroup_nameText : Theme.key_dialogTextBlack)); + topicTextView.setTextSize(12); + topicTextView.setMaxLines(2); + topicTextView.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); + topicTextView.setAlignment(Layout.Alignment.ALIGN_CENTER); + addView(topicTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 6, currentType == TYPE_CREATE ? 58 : 66, 6, 0)); + checkBox = new CheckBox2(context, 21, resourcesProvider); checkBox.setColor(Theme.key_dialogRoundCheckBox, type == TYPE_CALL ? Theme.key_voipgroup_inviteMembersBackground : Theme.key_dialogBackground, Theme.key_dialogRoundCheckBoxCheck); checkBox.setDrawUnchecked(false); @@ -122,6 +139,7 @@ public class ShareDialogCell extends FrameLayout { } imageView.setForUserOrChat(user, avatarDrawable); } + imageView.setRoundRadius(AndroidUtilities.dp(28)); } else { user = null; TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-uid); @@ -134,6 +152,7 @@ public class ShareDialogCell extends FrameLayout { } avatarDrawable.setInfo(chat); imageView.setForUserOrChat(chat, avatarDrawable); + imageView.setRoundRadius(chat != null && chat.forum ? AndroidUtilities.dp(16) : AndroidUtilities.dp(28)); } currentDialog = uid; checkBox.setChecked(checked, false); @@ -145,6 +164,59 @@ public class ShareDialogCell extends FrameLayout { public void setChecked(boolean checked, boolean animated) { checkBox.setChecked(checked, animated); + if (!checked) { + setTopic(null, true); + } + } + + public void setTopic(TLRPC.TL_forumTopic topic, boolean animate) { + boolean wasVisible = topicWasVisible; + boolean visible = topic != null; + if (wasVisible != visible || !animate) { + SpringAnimation prevSpring = (SpringAnimation) topicTextView.getTag(R.id.spring_tag); + if (prevSpring != null) { + prevSpring.cancel(); + } + + if (visible) { + topicTextView.setText(ForumUtilities.getTopicSpannedName(topic, topicTextView.getTextPaint())); + topicTextView.requestLayout(); + } + if (animate) { + SpringAnimation springAnimation = new SpringAnimation(new FloatValueHolder(visible ? 0f : 1000f)) + .setSpring(new SpringForce(visible ? 1000f : 0f) + .setStiffness(SpringForce.STIFFNESS_MEDIUM) + .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)) + .addUpdateListener((animation, value, velocity) -> { + value /= 1000f; + + topicTextView.setAlpha(value); + nameTextView.setAlpha(1f - value); + + topicTextView.setTranslationX((1f - value) * -AndroidUtilities.dp(10)); + nameTextView.setTranslationX(value * AndroidUtilities.dp(10)); + }) + .addEndListener((animation, canceled, value, velocity) -> { + topicTextView.setTag(R.id.spring_tag, null); + }); + topicTextView.setTag(R.id.spring_tag, springAnimation); + springAnimation.start(); + } else { + if (visible) { + topicTextView.setAlpha(1f); + nameTextView.setAlpha(0f); + topicTextView.setTranslationX(0); + nameTextView.setTranslationX(AndroidUtilities.dp(10)); + } else { + topicTextView.setAlpha(0f); + nameTextView.setAlpha(1f); + topicTextView.setTranslationX(-AndroidUtilities.dp(10)); + nameTextView.setTranslationX(0); + } + } + + topicWasVisible = visible; + } } @Override @@ -198,7 +270,9 @@ public class ShareDialogCell extends FrameLayout { int cy = imageView.getTop() + imageView.getMeasuredHeight() / 2; Theme.checkboxSquare_checkPaint.setColor(getThemedColor(Theme.key_dialogRoundCheckBox)); Theme.checkboxSquare_checkPaint.setAlpha((int) (checkBox.getProgress() * 255)); - canvas.drawCircle(cx, cy, AndroidUtilities.dp(currentType == TYPE_CREATE ? 24 : 28), Theme.checkboxSquare_checkPaint); + int radius = AndroidUtilities.dp(currentType == TYPE_CREATE ? 24 : 28); + AndroidUtilities.rectTmp.set(cx - radius, cy - radius, cx + radius, cy + radius); + canvas.drawRoundRect(AndroidUtilities.rectTmp, imageView.getRoundRadius()[0], imageView.getRoundRadius()[0], Theme.checkboxSquare_checkPaint); super.onDraw(canvas); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareTopicCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareTopicCell.java new file mode 100644 index 000000000..f7f13a558 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareTopicCell.java @@ -0,0 +1,109 @@ +/* + * This is the source code of Telegram for Android v. 5.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2018. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.UserConfig; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.AnimatedEmojiDrawable; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.Forum.ForumBubbleDrawable; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.LetterDrawable; + +public class ShareTopicCell extends FrameLayout { + + private BackupImageView imageView; + private TextView nameTextView; + + private long currentDialog; + private long currentTopic; + + private int currentAccount = UserConfig.selectedAccount; + private final Theme.ResourcesProvider resourcesProvider; + + public ShareTopicCell(Context context, Theme.ResourcesProvider resourcesProvider) { + super(context); + this.resourcesProvider = resourcesProvider; + + setWillNotDraw(false); + + imageView = new BackupImageView(context); + imageView.setRoundRadius(AndroidUtilities.dp(28)); + addView(imageView, LayoutHelper.createFrame(56, 56, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 7, 0, 0)); + + nameTextView = new TextView(context); + nameTextView.setTextColor(getThemedColor(Theme.key_dialogTextBlack)); + nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); + nameTextView.setMaxLines(2); + nameTextView.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); + nameTextView.setLines(2); + nameTextView.setEllipsize(TextUtils.TruncateAt.END); + addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 6, 66, 6, 0)); + + setBackground(Theme.createRadSelectorDrawable(Theme.getColor(Theme.key_listSelector), AndroidUtilities.dp(2), AndroidUtilities.dp(2))); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(103), MeasureSpec.EXACTLY)); + } + + public void setTopic(TLRPC.Dialog dialog, TLRPC.TL_forumTopic topic, boolean checked, CharSequence name) { + TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-dialog.id); + if (name != null) { + nameTextView.setText(name); + } else if (chat != null) { + nameTextView.setText(topic.title); + } else { + nameTextView.setText(""); + } + if (topic.icon_emoji_id != 0) { + imageView.setImageDrawable(null); + imageView.setAnimatedEmojiDrawable(new AnimatedEmojiDrawable(AnimatedEmojiDrawable.CACHE_TYPE_FORUM_TOPIC, UserConfig.selectedAccount, topic.icon_emoji_id)); + } else { + imageView.setAnimatedEmojiDrawable(null); + ForumBubbleDrawable forumBubbleDrawable = new ForumBubbleDrawable(topic.icon_color); + LetterDrawable letterDrawable = new LetterDrawable(null, LetterDrawable.STYLE_TOPIC_DRAWABLE); + String title = topic.title.trim().toUpperCase(); + letterDrawable.setTitle(title.length() >= 1 ? title.substring(0, 1) : ""); + letterDrawable.scale = 1.8f; + CombinedDrawable combinedDrawable = new CombinedDrawable(forumBubbleDrawable, letterDrawable, 0, 0); + combinedDrawable.setFullsize(true); + imageView.setImageDrawable(combinedDrawable); + } + imageView.setRoundRadius(chat != null && chat.forum && !checked ? AndroidUtilities.dp(16) : AndroidUtilities.dp(28)); + + currentDialog = dialog.id; + currentTopic = topic.id; + } + + public long getCurrentDialog() { + return currentDialog; + } + + public long getCurrentTopic() { + return currentTopic; + } + + private int getThemedColor(String key) { + Integer color = resourcesProvider != null ? resourcesProvider.getColor(key) : null; + return color != null ? color : Theme.getColor(key); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedLinkCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedLinkCell.java index 8724e5895..b0f25d9d4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedLinkCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedLinkCell.java @@ -205,7 +205,7 @@ public class SharedLinkCell extends FrameLayout { setWillNotDraw(false); linkImageView = new ImageReceiver(this); linkImageView.setRoundRadius(AndroidUtilities.dp(4)); - letterDrawable = new LetterDrawable(resourcesProvider); + letterDrawable = new LetterDrawable(resourcesProvider, LetterDrawable.STYLE_DEFAULT); checkBox = new CheckBox2(context, 21, resourcesProvider); checkBox.setVisibility(INVISIBLE); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerEmojiCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerEmojiCell.java index b9eae2232..771cdd7bb 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerEmojiCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerEmojiCell.java @@ -314,7 +314,7 @@ public class StickerEmojiCell extends FrameLayout implements NotificationCenter. if (v != null) { v.vibrate(200); } - AndroidUtilities.shakeView(premiumIconView, 2, 0); + AndroidUtilities.shakeView(premiumIconView); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCell.java index 1bf80bb97..75c9953ac 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCell.java @@ -23,15 +23,18 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.ui.ActionBar.SimpleTextView; import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.AnimatedTextView; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RLottieDrawable; import org.telegram.ui.Components.RLottieImageView; +import org.telegram.ui.Components.Switch; public class TextCell extends FrameLayout { public final SimpleTextView textView; - public final SimpleTextView valueTextView; + public final AnimatedTextView valueTextView; public final RLottieImageView imageView; + private Switch checkBox; private ImageView valueImageView; private int leftPadding; private boolean needDivider; @@ -40,20 +43,22 @@ public class TextCell extends FrameLayout { private boolean inDialogs; private boolean prioritizeTitleOverValue; private Theme.ResourcesProvider resourcesProvider; + private boolean attached; + public TextCell(Context context) { - this(context, 23, false, null); + this(context, 23, false, false, null); } public TextCell(Context context, Theme.ResourcesProvider resourcesProvider) { - this(context, 23, false, resourcesProvider); + this(context, 23, false, false, resourcesProvider); } public TextCell(Context context, int left, boolean dialog) { - this(context, left, dialog, null); + this(context, left, dialog, false, null); } - public TextCell(Context context, int left, boolean dialog, Theme.ResourcesProvider resourcesProvider) { + public TextCell(Context context, int left, boolean dialog, boolean needCheck, Theme.ResourcesProvider resourcesProvider) { super(context); this.resourcesProvider = resourcesProvider; @@ -66,9 +71,10 @@ public class TextCell extends FrameLayout { textView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT)); - valueTextView = new SimpleTextView(context); + valueTextView = new AnimatedTextView(context); valueTextView.setTextColor(Theme.getColor(dialog ? Theme.key_dialogTextBlue2 : Theme.key_windowBackgroundWhiteValueText, resourcesProvider)); - valueTextView.setTextSize(16); + valueTextView.setPadding(0, AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18)); + valueTextView.setTextSize(AndroidUtilities.dp(16)); valueTextView.setGravity(LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT); valueTextView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); addView(valueTextView); @@ -82,9 +88,19 @@ public class TextCell extends FrameLayout { valueImageView.setScaleType(ImageView.ScaleType.CENTER); addView(valueImageView); + if (needCheck) { + checkBox = new Switch(context, resourcesProvider); + checkBox.setColors(Theme.key_switchTrack, Theme.key_switchTrackChecked, Theme.key_windowBackgroundWhite, Theme.key_windowBackgroundWhite); + addView(checkBox, LayoutHelper.createFrame(37, 20, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL, 22, 0, 22, 0)); + } + setFocusable(true); } + public Switch getCheckBox() { + return checkBox; + } + public void setIsInDialogs() { inDialogs = true; } @@ -97,7 +113,7 @@ public class TextCell extends FrameLayout { return imageView; } - public SimpleTextView getValueTextView() { + public AnimatedTextView getValueTextView() { return valueTextView; } @@ -117,10 +133,10 @@ public class TextCell extends FrameLayout { if (prioritizeTitleOverValue) { textView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(71 + leftPadding), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); - valueTextView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(103 + leftPadding) - textView.getTextWidth(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); + valueTextView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(103 + leftPadding) - textView.getTextWidth(), LocaleController.isRTL ? MeasureSpec.AT_MOST : MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); } else { - valueTextView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(leftPadding), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); - textView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(71 + leftPadding) - valueTextView.getTextWidth(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); + valueTextView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(leftPadding), LocaleController.isRTL ? MeasureSpec.AT_MOST : MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); + textView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(71 + leftPadding) - valueTextView.width(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); } if (imageView.getVisibility() == VISIBLE) { imageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); @@ -128,9 +144,20 @@ public class TextCell extends FrameLayout { if (valueImageView.getVisibility() == VISIBLE) { valueImageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); } + if (checkBox != null) { + checkBox.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(37), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); + } setMeasuredDimension(width, AndroidUtilities.dp(50) + (needDivider ? 1 : 0)); } + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + if (checkBox != null) { + checkBox.setEnabled(enabled); + } + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int height = bottom - top; @@ -162,6 +189,11 @@ public class TextCell extends FrameLayout { viewLeft = LocaleController.isRTL ? AndroidUtilities.dp(23) : width - valueImageView.getMeasuredWidth() - AndroidUtilities.dp(23); valueImageView.layout(viewLeft, viewTop, viewLeft + valueImageView.getMeasuredWidth(), viewTop + valueImageView.getMeasuredHeight()); } + if (checkBox != null && checkBox.getVisibility() == VISIBLE) { + viewTop = (height - checkBox.getMeasuredHeight()) / 2; + viewLeft = LocaleController.isRTL ? AndroidUtilities.dp(22) : width - checkBox.getMeasuredWidth() - AndroidUtilities.dp(22); + checkBox.layout(viewLeft, viewTop, viewLeft + checkBox.getMeasuredWidth(), viewTop + checkBox.getMeasuredHeight()); + } } public void setTextColor(int color) { @@ -180,7 +212,7 @@ public class TextCell extends FrameLayout { public void setText(String text, boolean divider) { imageLeft = 21; textView.setText(text); - valueTextView.setText(null); + valueTextView.setText(null, false); imageView.setVisibility(GONE); valueTextView.setVisibility(GONE); valueImageView.setVisibility(GONE); @@ -192,7 +224,7 @@ public class TextCell extends FrameLayout { imageLeft = 21; offsetFromImage = 71; textView.setText(text); - valueTextView.setText(null); + valueTextView.setText(null, false); imageView.setImageResource(resId); imageView.setVisibility(VISIBLE); valueTextView.setVisibility(GONE); @@ -206,7 +238,7 @@ public class TextCell extends FrameLayout { offsetFromImage = 68; imageLeft = 18; textView.setText(text); - valueTextView.setText(null); + valueTextView.setText(null, false); imageView.setColorFilter(null); if (drawable instanceof RLottieDrawable) { imageView.setAnimation((RLottieDrawable) drawable); @@ -230,22 +262,33 @@ public class TextCell extends FrameLayout { } public void setTextAndValue(String text, String value, boolean divider) { + setTextAndValue(text, value, false, divider); + } + + public void setTextAndValue(String text, String value, boolean animated, boolean divider) { imageLeft = 21; offsetFromImage = 71; textView.setText(text); - valueTextView.setText(value); + valueTextView.setText(value, animated); valueTextView.setVisibility(VISIBLE); imageView.setVisibility(GONE); valueImageView.setVisibility(GONE); needDivider = divider; setWillNotDraw(!needDivider); + if (checkBox != null) { + checkBox.setVisibility(GONE); + } } public void setTextAndValueAndIcon(String text, String value, int resId, boolean divider) { + setTextAndValueAndIcon(text, value, false, resId, divider); + } + + public void setTextAndValueAndIcon(String text, String value, boolean animated, int resId, boolean divider) { imageLeft = 21; offsetFromImage = 71; textView.setText(text); - valueTextView.setText(value); + valueTextView.setText(value, animated); valueTextView.setVisibility(VISIBLE); valueImageView.setVisibility(GONE); imageView.setVisibility(VISIBLE); @@ -253,13 +296,33 @@ public class TextCell extends FrameLayout { imageView.setImageResource(resId); needDivider = divider; setWillNotDraw(!needDivider); + if (checkBox != null) { + checkBox.setVisibility(GONE); + } + } + + public void setTextAndCheckAndIcon(String text, boolean checked, int resId, boolean divider) { + imageLeft = 21; + offsetFromImage = 71; + textView.setText(text); + valueTextView.setVisibility(GONE); + valueImageView.setVisibility(GONE); + if (checkBox != null) { + checkBox.setVisibility(VISIBLE); + checkBox.setChecked(checked, false); + } + imageView.setVisibility(VISIBLE); + imageView.setPadding(0, AndroidUtilities.dp(7), 0, 0); + imageView.setImageResource(resId); + needDivider = divider; + setWillNotDraw(!needDivider); } public void setTextAndValueDrawable(String text, Drawable drawable, boolean divider) { imageLeft = 21; offsetFromImage = 71; textView.setText(text); - valueTextView.setText(null); + valueTextView.setText(null, false); valueImageView.setVisibility(VISIBLE); valueImageView.setImageDrawable(drawable); valueTextView.setVisibility(GONE); @@ -267,6 +330,9 @@ public class TextCell extends FrameLayout { imageView.setPadding(0, AndroidUtilities.dp(7), 0, 0); needDivider = divider; setWillNotDraw(!needDivider); + if (checkBox != null) { + checkBox.setVisibility(GONE); + } } @Override @@ -298,4 +364,51 @@ public class TextCell extends FrameLayout { invalidate(); } } + + public void setChecked(boolean checked) { + checkBox.setChecked(checked, true); + } + + public void showEnabledAlpha(boolean show) { + float alpha = show ? 0.5f : 1f; + if (attached) { + if (imageView != null) { + imageView.animate().alpha(alpha).start(); + } + if (textView != null) { + textView.animate().alpha(alpha).start(); + } + if (valueTextView != null) { + valueTextView.animate().alpha(alpha).start(); + } + if (valueImageView != null) { + valueImageView.animate().alpha(alpha).start(); + } + } else { + if (imageView != null) { + imageView.setAlpha(alpha); + } + if (textView != null) { + textView.setAlpha(alpha); + } + if (valueTextView != null) { + valueTextView.setAlpha(alpha); + } + if (valueImageView != null) { + valueImageView.setAlpha(alpha); + } + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + attached = true; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + attached = false; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell.java index 345b27fe6..28f9669da 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell.java @@ -28,7 +28,6 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; -import org.telegram.messenger.R; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AnimationProperties; import org.telegram.ui.Components.CubicBezierInterpolator; @@ -144,7 +143,7 @@ public class TextCheckCell extends FrameLayout { public void setTextAndCheck(String text, boolean checked, boolean divider) { textView.setText(text); isMultiline = false; - checkBox.setChecked(checked, false); + checkBox.setChecked(checked, attached); needDivider = divider; valueTextView.setVisibility(GONE); LayoutParams layoutParams = (LayoutParams) textView.getLayoutParams(); @@ -340,4 +339,18 @@ public class TextCheckCell extends FrameLayout { } info.setContentDescription(sb); } + + boolean attached; + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + attached = true; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + attached = false; + } } \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell2.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell2.java index bf6e30235..8fd240716 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell2.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell2.java @@ -33,10 +33,14 @@ public class TextCheckCell2 extends FrameLayout { private boolean isMultiline; public TextCheckCell2(Context context) { + this(context, null); + } + + public TextCheckCell2(Context context, Theme.ResourcesProvider resourcesProvider) { super(context); textView = new TextView(context); - textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setLines(1); textView.setMaxLines(1); @@ -46,7 +50,7 @@ public class TextCheckCell2 extends FrameLayout { addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 64 : 21, 0, LocaleController.isRTL ? 21 : 64, 0)); valueTextView = new TextView(context); - valueTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); + valueTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2, resourcesProvider)); valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); valueTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); valueTextView.setLines(1); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailCell.java index ac2b5240a..dd77b4b3f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailCell.java @@ -13,8 +13,11 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.text.TextUtils; +import android.util.Log; import android.util.TypedValue; import android.view.Gravity; +import android.view.HapticFeedbackConstants; +import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; @@ -24,17 +27,20 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.Emoji; import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.SimpleTextView; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.LinkSpanDrawable; public class TextDetailCell extends FrameLayout { private final TextView textView; - private final TextView valueTextView; + private final LinkSpanDrawable.LinksTextView valueTextView; private final TextView showMoreTextView = null; private final ImageView imageView; private boolean needDivider; private boolean contentDescriptionValueFirst; + private boolean multiline; private Theme.ResourcesProvider resourcesProvider; @@ -43,6 +49,10 @@ public class TextDetailCell extends FrameLayout { } public TextDetailCell(Context context, Theme.ResourcesProvider resourcesProvider) { + this(context, resourcesProvider, false); + } + + public TextDetailCell(Context context, Theme.ResourcesProvider resourcesProvider, boolean multiline) { super(context); this.resourcesProvider = resourcesProvider; @@ -57,15 +67,27 @@ public class TextDetailCell extends FrameLayout { textView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 23, 8, 23, 0)); - valueTextView = new TextView(context); + valueTextView = new LinkSpanDrawable.LinksTextView(context); + valueTextView.setOnLinkLongPressListener(span -> { + if (span != null) { + try { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + } catch (Exception ignore) {}; + span.onClick(valueTextView); + } + }); + if (this.multiline = multiline) { + setMinimumHeight(AndroidUtilities.dp(60)); + } else { + valueTextView.setLines(1); + valueTextView.setSingleLine(true); + } valueTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2, resourcesProvider)); valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); - valueTextView.setLines(1); - valueTextView.setMaxLines(1); - valueTextView.setSingleLine(true); valueTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); valueTextView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - addView(valueTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 23, 33, 23, 0)); + valueTextView.setEllipsize(TextUtils.TruncateAt.END); + addView(valueTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 23, 33, 23, 10)); imageView = new ImageView(context); imageView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); @@ -73,15 +95,24 @@ public class TextDetailCell extends FrameLayout { addView(imageView, LayoutHelper.createFrameRelatively(48, 48, Gravity.END | Gravity.CENTER_VERTICAL, 0, 0, 12, 0)); } + @Override + public boolean onTouchEvent(MotionEvent ev) { + boolean hit = valueTextView.hit((int) ev.getX() - valueTextView.getLeft(), (int) ev.getY() - valueTextView.getTop()) != null; + if (hit) { + return true; + } + return super.onTouchEvent(ev); + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure( MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(60) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY) + multiline ? heightMeasureSpec : MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(60) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY) ); } - public void setTextAndValue(String text, String value, boolean divider) { + public void setTextAndValue(CharSequence text, CharSequence value, boolean divider) { textView.setText(text); valueTextView.setText(value); needDivider = divider; @@ -93,6 +124,7 @@ public class TextDetailCell extends FrameLayout { } public void setImage(Drawable drawable, CharSequence imageContentDescription) { + ((MarginLayoutParams) valueTextView.getLayoutParams()).rightMargin = !LocaleController.isRTL && drawable != null ? AndroidUtilities.dp(28 + 12 + 12 + 6) : AndroidUtilities.dp(23); imageView.setImageDrawable(drawable); imageView.setFocusable(drawable != null); imageView.setContentDescription(imageContentDescription); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ThemePreviewMessagesCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ThemePreviewMessagesCell.java index 85df875f4..fad7ab5a9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ThemePreviewMessagesCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ThemePreviewMessagesCell.java @@ -25,8 +25,8 @@ import org.telegram.messenger.MessageObject; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.tgnet.TLRPC; -import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.BackgroundGradientDrawable; import org.telegram.ui.Components.LayoutHelper; @@ -47,13 +47,13 @@ public class ThemePreviewMessagesCell extends LinearLayout { private Drawable oldBackgroundDrawable; private ChatMessageCell[] cells = new ChatMessageCell[2]; private Drawable shadowDrawable; - private ActionBarLayout parentLayout; + private INavigationLayout parentLayout; private final int type; public BaseFragment fragment; @SuppressLint("ClickableViewAccessibility") - public ThemePreviewMessagesCell(Context context, ActionBarLayout layout, int type) { + public ThemePreviewMessagesCell(Context context, INavigationLayout layout, int type) { super(context); this.type = type; int currentAccount = UserConfig.selectedAccount; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TopicExceptionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TopicExceptionCell.java new file mode 100644 index 000000000..c25c296c7 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TopicExceptionCell.java @@ -0,0 +1,64 @@ +package org.telegram.ui.Cells; + +import android.content.Context; +import android.graphics.Canvas; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.UserConfig; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.Forum.ForumUtilities; +import org.telegram.ui.Components.LayoutHelper; + +public class TopicExceptionCell extends FrameLayout { + + public boolean drawDivider; + BackupImageView backupImageView; + TextView title; + TextView subtitle; + + public TopicExceptionCell(Context context) { + super(context); + backupImageView = new BackupImageView(context); + addView(backupImageView, LayoutHelper.createFrame(30, 30, Gravity.CENTER_VERTICAL, 20, 0, 0, 0)); + + title = new TextView(context); + title.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + title.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + title.setMaxLines(1); + addView(title, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 72, 8, 12, 0)); + + + subtitle = new TextView(context); + subtitle.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText)); + subtitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + addView(subtitle, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 72, 32, 12, 0)); + + } + + public void setTopic(long dialogId, TLRPC.TL_forumTopic topic) { + ForumUtilities.setTopicIcon(backupImageView, topic); + title.setText(topic.title); + subtitle.setText(MessagesController.getInstance(UserConfig.selectedAccount).getMutedString(dialogId, topic.id)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(56), MeasureSpec.EXACTLY)); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + if (drawDivider) { + canvas.drawLine(AndroidUtilities.dp(72), getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, Theme.dividerPaint); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TopicSearchCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TopicSearchCell.java new file mode 100644 index 000000000..bc4ba9d9d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TopicSearchCell.java @@ -0,0 +1,78 @@ +package org.telegram.ui.Cells; + +import android.content.Context; +import android.graphics.Canvas; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.Forum.ForumUtilities; +import org.telegram.ui.Components.LayoutHelper; + +public class TopicSearchCell extends FrameLayout { + + BackupImageView backupImageView; + TextView textView; + TLRPC.TL_forumTopic topic; + public boolean drawDivider; + + public TopicSearchCell(@NonNull Context context) { + super(context); + backupImageView = new BackupImageView(context); + + textView = new TextView(context); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + + if (LocaleController.isRTL) { + addView(backupImageView, LayoutHelper.createFrame(30, 30, Gravity.CENTER_VERTICAL | Gravity.RIGHT, 12, 0, 12, 0)); + addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, 12, 0, 56, 0)); + } else { + addView(backupImageView, LayoutHelper.createFrame(30, 30, Gravity.CENTER_VERTICAL, 12, 0, 12, 0)); + addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 56, 0, 12, 0)); + } + } + + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48), MeasureSpec.EXACTLY)); + } + + public void setTopic(TLRPC.TL_forumTopic topic) { + this.topic = topic; + if (TextUtils.isEmpty(topic.searchQuery)) { + textView.setText(topic.title); + } else { + textView.setText(AndroidUtilities.highlightText(topic.title, topic.searchQuery, null)); + } + ForumUtilities.setTopicIcon(backupImageView, topic); + } + + public TLRPC.TL_forumTopic getTopic() { + return topic; + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + if (drawDivider) { + int left = AndroidUtilities.dp(56); + if (LocaleController.isRTL) { + canvas.drawLine(0, getMeasuredHeight() - 1, getMeasuredWidth() - left, getMeasuredHeight() - 1, Theme.dividerPaint); + } else { + canvas.drawLine(left, getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, Theme.dividerPaint); + } + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell2.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell2.java index a8d47b71f..fda4f1713 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell2.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell2.java @@ -275,7 +275,7 @@ public class UserCell2 extends FrameLayout { if (ChatObject.isChannel(currentChat) && !currentChat.megagroup) { if (currentChat.participants_count != 0) { statusTextView.setText(LocaleController.formatPluralString("Subscribers", currentChat.participants_count)); - } else if (TextUtils.isEmpty(currentChat.username)) { + } else if (!ChatObject.isPublic(currentChat)) { statusTextView.setText(LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate)); } else { statusTextView.setText(LocaleController.getString("ChannelPublic", R.string.ChannelPublic)); @@ -285,7 +285,7 @@ public class UserCell2 extends FrameLayout { statusTextView.setText(LocaleController.formatPluralString("Members", currentChat.participants_count)); } else if (currentChat.has_geo) { statusTextView.setText(LocaleController.getString("MegaLocation", R.string.MegaLocation)); - } else if (TextUtils.isEmpty(currentChat.username)) { + } else if (!ChatObject.isPublic(currentChat)) { statusTextView.setText(LocaleController.getString("MegaPrivate", R.string.MegaPrivate)); } else { statusTextView.setText(LocaleController.getString("MegaPublic", R.string.MegaPublic)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangeBioActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangeBioActivity.java index 8e0384100..a65cf0173 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangeBioActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangeBioActivity.java @@ -120,7 +120,7 @@ public class ChangeBioActivity extends BaseFragment { if (v != null) { v.vibrate(200); } - AndroidUtilities.shakeView(checkTextView, 2, 0); + AndroidUtilities.shakeView(checkTextView); } return result; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java index 40bbcfb9e..31f5d63db 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java @@ -8,12 +8,20 @@ package org.telegram.ui; +import android.animation.ValueAnimator; import android.content.Context; import android.content.SharedPreferences; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; import android.text.Editable; import android.text.InputType; import android.text.Selection; import android.text.Spannable; +import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextPaint; @@ -21,44 +29,67 @@ import android.text.TextUtils; import android.text.TextWatcher; import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; +import android.text.style.ForegroundColorSpan; import android.util.TypedValue; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; +import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.ColorUtils; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.BotWebViewVibrationEffect; +import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.NotificationCenter; -import org.telegram.messenger.ApplicationLoader; -import org.telegram.messenger.FileLog; import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; -import org.telegram.messenger.UserConfig; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.SimpleTextView; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.HeaderCell; +import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Components.AlertsCreator; +import org.telegram.ui.Components.AnimatedFloat; +import org.telegram.ui.Components.AnimatedTextView; import org.telegram.ui.Components.BulletinFactory; +import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; +import java.util.List; public class ChangeUsernameActivity extends BaseFragment { - private EditTextBoldCursor firstNameField; private View doneButton; - private TextView checkTextView; - private TextView helpTextView; + + private RecyclerListView listView; + private LinearLayoutManager layoutManager; + private Adapter adapter; + private ItemTouchHelper itemTouchHelper; + + private boolean needReorder; private int checkReqId; private String lastCheckName; @@ -67,6 +98,10 @@ public class ChangeUsernameActivity extends BaseFragment { private boolean ignoreCheck; private CharSequence infoText; + private String username = ""; + private ArrayList notEditableUsernames = new ArrayList<>(); + private ArrayList usernames = new ArrayList<>(); + private final static int done_button = 1; public class LinkSpan extends ClickableSpan { @@ -97,7 +132,6 @@ public class ChangeUsernameActivity extends BaseFragment { } } } - private static class LinkMovementMethodMy extends LinkMovementMethod { @Override public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { @@ -125,6 +159,7 @@ public class ChangeUsernameActivity extends BaseFragment { if (id == -1) { finishFragment(); } else if (id == done_button) { + sendReorder(); saveName(); } } @@ -138,107 +173,829 @@ public class ChangeUsernameActivity extends BaseFragment { user = UserConfig.getInstance(currentAccount).getCurrentUser(); } - fragmentView = new LinearLayout(context); - LinearLayout linearLayout = (LinearLayout) fragmentView; - linearLayout.setOrientation(LinearLayout.VERTICAL); - fragmentView.setOnTouchListener((v, event) -> true); - - firstNameField = new EditTextBoldCursor(context); - firstNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - firstNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); - firstNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); - firstNameField.setBackgroundDrawable(null); - firstNameField.setLineColors(getThemedColor(Theme.key_windowBackgroundWhiteInputField), getThemedColor(Theme.key_windowBackgroundWhiteInputFieldActivated), getThemedColor(Theme.key_windowBackgroundWhiteRedText3)); - firstNameField.setMaxLines(1); - firstNameField.setLines(1); - firstNameField.setPadding(0, 0, 0, 0); - firstNameField.setSingleLine(true); - firstNameField.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - firstNameField.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); - firstNameField.setImeOptions(EditorInfo.IME_ACTION_DONE); - firstNameField.setHint(LocaleController.getString("UsernamePlaceholder", R.string.UsernamePlaceholder)); - firstNameField.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); - firstNameField.setCursorSize(AndroidUtilities.dp(20)); - firstNameField.setCursorWidth(1.5f); - firstNameField.setOnEditorActionListener((textView, i, keyEvent) -> { - if (i == EditorInfo.IME_ACTION_DONE && doneButton != null) { - doneButton.performClick(); - return true; - } - return false; - }); - firstNameField.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { - if (ignoreCheck) { - return; - } - checkUserName(firstNameField.getText().toString(), false); - } - - @Override - public void afterTextChanged(Editable editable) { - String name = firstNameField.getText().toString(); - if (name.startsWith("@")) { - name = name.substring(1); - } - if (name.length() > 0) { - String url = "https://" + MessagesController.getInstance(currentAccount).linkPrefix + "/" + name; - String text = LocaleController.formatString("UsernameHelpLink", R.string.UsernameHelpLink, url); - int index = text.indexOf(url); - SpannableStringBuilder textSpan = new SpannableStringBuilder(text); - if (index >= 0) { - textSpan.setSpan(new LinkSpan(url), index, index + url.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + if (user != null) { + username = null; + if (user.usernames != null) { + for (int i = 0; i < user.usernames.size(); ++i) { + TLRPC.TL_username u = user.usernames.get(i); + if (u != null && u.editable) { + username = u.username; + break; } - helpTextView.setText(TextUtils.concat(infoText, "\n\n", textSpan)); - } else { - helpTextView.setText(infoText); } } - }); + if (username == null && user.username != null) { + username = user.username; + } + if (username == null) { + username = ""; + } - linearLayout.addView(firstNameField, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 24, 24, 24, 0)); - - checkTextView = new TextView(context); - checkTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - checkTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - linearLayout.addView(checkTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 24, 12, 24, 0)); - - helpTextView = new TextView(context); - helpTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - helpTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText8)); - helpTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - helpTextView.setText(infoText = AndroidUtilities.replaceTags(LocaleController.getString("UsernameHelp", R.string.UsernameHelp))); - helpTextView.setLinkTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkText)); - helpTextView.setHighlightColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkSelection)); - helpTextView.setMovementMethod(new LinkMovementMethodMy()); - linearLayout.addView(helpTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 24, 10, 24, 0)); - - checkTextView.setVisibility(View.GONE); - - if (user != null && user.username != null && user.username.length() > 0) { - ignoreCheck = true; - firstNameField.setText(user.username); - firstNameField.setSelection(firstNameField.length()); - ignoreCheck = false; + notEditableUsernames.clear(); + usernames.clear(); + for (int i = 0; i < user.usernames.size(); ++i) { + if (user.usernames.get(i).active) + usernames.add(user.usernames.get(i)); + } + for (int i = 0; i < user.usernames.size(); ++i) { + if (!user.usernames.get(i).active) + usernames.add(user.usernames.get(i)); + } +// for (int i = 0; i < usernames.size(); ++i) { +// if (usernames.get(i) == null || +// username != null && username.equals(usernames.get(i).username) || +// usernames.get(i).editable) { +// notEditableUsernames.add(usernames.remove(i--)); +// } +// } } + fragmentView = new FrameLayout(context); + listView = new RecyclerListView(context) { + + private Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + @Override + protected void dispatchDraw(Canvas canvas) { + int fromIndex = 4, toIndex = 4 + usernames.size() - 1; + + int top = Integer.MAX_VALUE; + int bottom = Integer.MIN_VALUE; + + for (int i = 0; i < getChildCount(); ++i) { + View child = getChildAt(i); + if (child == null) { + continue; + } + int position = getChildAdapterPosition(child); + if (position >= fromIndex && position <= toIndex) { + top = Math.min(child.getTop(), top); + bottom = Math.max(child.getBottom(), bottom); + } + } + + if (top < bottom) { + backgroundPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhite, resourcesProvider)); + canvas.drawRect(0, top, getWidth(), bottom, backgroundPaint); + } + + super.dispatchDraw(canvas); + } + }; + + fragmentView.setBackgroundColor(getThemedColor(Theme.key_windowBackgroundGray)); + listView.setLayoutManager(layoutManager = new LinearLayoutManager(context)); + listView.setAdapter(adapter = new Adapter()); + listView.setSelectorDrawableColor(getThemedColor(Theme.key_listSelector)); + itemTouchHelper = new ItemTouchHelper(new TouchHelperCallback()); + itemTouchHelper.attachToRecyclerView(listView); + ((FrameLayout) fragmentView).addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + fragmentView.setOnTouchListener((v, event) -> true); + + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (view instanceof UsernameCell) { + TLRPC.TL_username username = ((UsernameCell) view).currentUsername; + if (username == null) { + return; + } + if (username.editable) { + listView.smoothScrollToPosition(0); + focusUsernameField(true); + return; + } + new AlertDialog.Builder(getContext(), getResourceProvider()) + .setTitle(username.active ? LocaleController.getString("UsernameDeactivateLink", R.string.UsernameDeactivateLink) : LocaleController.getString("UsernameActivateLink", R.string.UsernameActivateLink)) + .setMessage(username.active ? LocaleController.getString("UsernameDeactivateLinkProfileMessage", R.string.UsernameDeactivateLinkProfileMessage) : LocaleController.getString("UsernameActivateLinkProfileMessage", R.string.UsernameActivateLinkProfileMessage)) + .setPositiveButton(username.active ? LocaleController.getString("Hide", R.string.Hide) : LocaleController.getString("Show", R.string.Show), (di, e) -> { + + TLRPC.TL_account_toggleUsername req = new TLRPC.TL_account_toggleUsername(); + req.username = username.username; + final boolean wasActive = username.active; + req.active = (username.active = !username.active); + getConnectionsManager().sendRequest(req, (res, err) -> { + if (res instanceof TLRPC.TL_boolTrue) { + + } else if (err != null && "USERNAMES_ACTIVE_TOO_MUCH".equals(err.text)) { + AndroidUtilities.runOnUIThread(() -> { + new AlertDialog.Builder(getContext(), getResourceProvider()) + .setTitle(LocaleController.getString("UsernameActivateErrorTitle", R.string.UsernameActivateErrorTitle)) + .setMessage(LocaleController.getString("UsernameActivateErrorMessage", R.string.UsernameActivateErrorMessage)) + .setPositiveButton(LocaleController.getString("OK", R.string.OK), (d, v) -> { + toggleUsername(username, wasActive, true); + }) + .show(); + }); + } else { + AndroidUtilities.runOnUIThread(() -> { + toggleUsername(username, wasActive, true); + }); + } + }, ConnectionsManager.RequestFlagDoNotWaitFloodWait); + getConnectionsManager().sendRequest(req, (res, err) -> { + if (res instanceof TLRPC.TL_boolTrue) {} + }); + toggleUsername(position, username.active); + updateUser(); + }) + .setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), (di, e) -> { + di.dismiss(); + }) + .show(); + } else if (view instanceof InputCell) { + focusUsernameField(true); + } + } + }); + + AndroidUtilities.runOnUIThread(() -> { + if (username == null || username.length() > 0) { + ignoreCheck = true; + focusUsernameField(usernames.size() <= 0); + ignoreCheck = false; + } + }, 40); + return fragmentView; } + + public void toggleUsername(TLRPC.TL_username username, boolean newActive) { + toggleUsername(username, newActive, false); + } + + public void toggleUsername(TLRPC.TL_username username, boolean newActive, boolean shake) { + for (int i = 0; i < usernames.size(); ++i) { + if (usernames.get(i) == username) { + toggleUsername(4 + i, newActive, shake); + break; + } + } + } + + public void toggleUsername(int position, boolean newActive) { + toggleUsername(position, newActive, false); + } + + public void toggleUsername(int position, boolean newActive, boolean shake) { + if (position - 4 < 0 || position - 4 >= usernames.size()) { + return; + } + TLRPC.TL_username username = usernames.get(position - 4); + + int toIndex = -1; + if (username.active = newActive) { + int firstInactive = -1; + for (int i = 0; i < usernames.size(); ++i) { + if (!usernames.get(i).active) { + firstInactive = i; + break; + } + } + if (firstInactive >= 0) { + toIndex = 4 + Math.max(0, firstInactive - 1); + } + } else { + int lastActive = -1; + for (int i = 0; i < usernames.size(); ++i) { + if (usernames.get(i).active) { + lastActive = i; + } + } + if (lastActive >= 0) { + toIndex = 4 + Math.min(usernames.size() - 1, lastActive + 1); + } + } + + if (listView != null) { + for (int i = 0; i < listView.getChildCount(); ++i) { + View child = listView.getChildAt(i); + if (listView.getChildAdapterPosition(child) == position) { + if (shake) { + AndroidUtilities.shakeView(child); + } + if (child instanceof ChangeUsernameActivity.UsernameCell) { + ((ChangeUsernameActivity.UsernameCell) child).update(); + } + break; + } + } + } + + if (toIndex >= 0 && position != toIndex) { + adapter.moveElement(position, toIndex); + } + } + + private InputCell inputCell; + private void focusUsernameField(boolean showKeyboard) { + if (inputCell != null) { + if (!inputCell.field.isFocused()) { + inputCell.field.setSelection(inputCell.field.length()); + } + inputCell.field.requestFocus(); + if (showKeyboard) { + AndroidUtilities.showKeyboard(inputCell.field); + } + } + } + + private static final int VIEW_TYPE_HEADER = 0; + private static final int VIEW_TYPE_HELP1 = 1; + private static final int VIEW_TYPE_HELP2 = 2; + private static final int VIEW_TYPE_INPUT = 3; + private static final int VIEW_TYPE_USERNAME = 4; + + private UsernameCell editableUsernameCell; + + private class Adapter extends RecyclerListView.SelectionAdapter { + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + switch (viewType) { + case VIEW_TYPE_HEADER: + HeaderCell headerCell = new HeaderCell(getContext()); + headerCell.setBackgroundColor(getThemedColor(Theme.key_windowBackgroundWhite)); + return new RecyclerListView.Holder(headerCell); + case VIEW_TYPE_HELP1: + return new RecyclerListView.Holder(new UsernameHelpCell(getContext())); + case VIEW_TYPE_HELP2: + return new RecyclerListView.Holder(new TextInfoPrivacyCell(getContext())); + case VIEW_TYPE_INPUT: + return new RecyclerListView.Holder(new InputCell(getContext())); + case VIEW_TYPE_USERNAME: + return new RecyclerListView.Holder(new UsernameCell(getContext(), getResourceProvider()) { + { + isProfile = true; + } + @Override + protected String getUsernameEditable() { + return username; + } + }); + } + return null; + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case VIEW_TYPE_INPUT: + ignoreCheck = true; + (inputCell = (InputCell) holder.itemView).field.setText(username); + ignoreCheck = false; + break; + case VIEW_TYPE_HEADER: + ((HeaderCell) holder.itemView).setText(position == 0 ? LocaleController.getString("SetUsernameHeader", R.string.SetUsernameHeader) : LocaleController.getString("UsernamesProfileHeader", R.string.UsernamesProfileHeader)); + break; + case VIEW_TYPE_USERNAME: + TLRPC.TL_username username = usernames.get(position - 4); + UsernameCell cell = (UsernameCell) holder.itemView; + if (username.editable) { + editableUsernameCell = cell; + } else if (editableUsernameCell == cell) { + editableUsernameCell = null; + } + cell.set(username, position < getItemCount() - 2, false); + break; + case VIEW_TYPE_HELP1: + break; + case VIEW_TYPE_HELP2: + ((TextInfoPrivacyCell) holder.itemView).setText(LocaleController.getString("UsernamesProfileHelp", R.string.UsernamesProfileHelp)); + ((TextInfoPrivacyCell) holder.itemView).setBackgroundDrawable(Theme.getThemedDrawable(getContext(), R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + break; + } + } + + @Override + public int getItemCount() { + return 3 + (usernames.size() > 0 ? 1 + usernames.size() + 1 : 0); + } + + @Override + public int getItemViewType(int position) { + if (position == 0) { + return VIEW_TYPE_HEADER; + } else if (position == 1) { + return VIEW_TYPE_INPUT; + } else if (position == 2) { + return VIEW_TYPE_HELP1; + } else if (position == 3) { + return VIEW_TYPE_HEADER; + } else if (position != getItemCount() - 1) { + return VIEW_TYPE_USERNAME; + } else { + return VIEW_TYPE_HELP2; + } + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getItemViewType() == VIEW_TYPE_USERNAME; + } + + + public void swapElements(int fromIndex, int toIndex) { + int index1 = fromIndex - 4; + int index2 = toIndex - 4; + if (index1 >= usernames.size() || index2 >= usernames.size()) { + return; + } + if (fromIndex != toIndex) { + needReorder = true; + } + + swapListElements(usernames, index1, index2); + + notifyItemMoved(fromIndex, toIndex); + + int end = 4 + usernames.size() - 1; + if (fromIndex == end || toIndex == end) { + notifyItemChanged(fromIndex, 3); + notifyItemChanged(toIndex, 3); + } + } + + private void swapListElements(List list, int index1, int index2) { + TLRPC.TL_username username1 = list.get(index1); + list.set(index1, list.get(index2)); + list.set(index2, username1); + } + + public void moveElement(int fromIndex, int toIndex) { + int index1 = fromIndex - 4; + int index2 = toIndex - 4; + if (index1 >= usernames.size() || index2 >= usernames.size()) { + return; + } + + TLRPC.TL_username username = usernames.remove(index1); + usernames.add(index2, username); + + notifyItemMoved(fromIndex, toIndex); + + for (int i = 0; i < usernames.size(); ++i) + notifyItemChanged(4 + i); + } + } + + private void sendReorder() { + if (!needReorder) { + return; + } + needReorder = false; + TLRPC.TL_account_reorderUsernames req = new TLRPC.TL_account_reorderUsernames(); + ArrayList usernames = new ArrayList<>(); + for (int i = 0; i < notEditableUsernames.size(); ++i) { + if (notEditableUsernames.get(i).active) + usernames.add(notEditableUsernames.get(i).username); + } + for (int i = 0; i < this.usernames.size(); ++i) { + if (this.usernames.get(i).active) + usernames.add(this.usernames.get(i).username); + } + req.order = usernames; + getConnectionsManager().sendRequest(req, (res, err) -> { + if (res instanceof TLRPC.TL_boolTrue) {} + }); + updateUser(); + } + + private void updateUser() { + ArrayList newUsernames = new ArrayList<>(); + newUsernames.addAll(notEditableUsernames); + newUsernames.addAll(usernames); + + TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(UserConfig.getInstance(currentAccount).getClientUserId()); + user.usernames = newUsernames; + MessagesController.getInstance(currentAccount).putUser(user, false, true); + } + + private UsernameHelpCell helpCell; + private TextView statusTextView; + + private class UsernameHelpCell extends FrameLayout { + + private TextView text1View; + private TextView text2View; + + public UsernameHelpCell(Context context) { + super(context); + + helpCell = this; + + setPadding(AndroidUtilities.dp(21), AndroidUtilities.dp(10), AndroidUtilities.dp(21), AndroidUtilities.dp(17)); + setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + setClipChildren(false); + + text1View = new TextView(context); + text1View.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + text1View.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText8)); + text1View.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + text1View.setLinkTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkText)); + text1View.setHighlightColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkSelection)); + + text2View = statusTextView = new TextView(context); + text2View.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + text2View.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText8)); + text2View.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + text2View.setLinkTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkText)); + text2View.setHighlightColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkSelection)); + + addView(text1View, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP)); + addView(text2View, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP)); + + text1View.setText(AndroidUtilities.replaceTags(LocaleController.getString("UsernameHelp", R.string.UsernameHelp))); + } + + private Integer height; + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, height == null ? heightMeasureSpec : MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + } + + private ValueAnimator heightUpdateAnimator; + private void update() { + if (text2View.getVisibility() == View.VISIBLE) { + text2View.measure( + MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(9999999, MeasureSpec.AT_MOST) + ); + } + if (heightUpdateAnimator != null) { + heightUpdateAnimator.cancel(); + } + int fromHeight = height == null ? getMeasuredHeight() : height; + int newHeight = AndroidUtilities.dp(10 + 17) + text1View.getHeight() + (text2View.getVisibility() == View.VISIBLE && !TextUtils.isEmpty(text2View.getText()) ? text2View.getMeasuredHeight() + AndroidUtilities.dp(8) : 0); + float fromTranslationY = text1View.getTranslationY(); + float newTranslationY = text2View.getVisibility() == View.VISIBLE && !TextUtils.isEmpty(text2View.getText()) ? text2View.getMeasuredHeight() + AndroidUtilities.dp(8) : 0; + heightUpdateAnimator = ValueAnimator.ofFloat(0, 1); + heightUpdateAnimator.addUpdateListener(anm -> { + final float t = (float) anm.getAnimatedValue(); + text1View.setTranslationY(AndroidUtilities.lerp(fromTranslationY, newTranslationY, t)); + height = AndroidUtilities.lerp(fromHeight, newHeight, t); + requestLayout(); + }); + heightUpdateAnimator.setDuration(200); + heightUpdateAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); + heightUpdateAnimator.start(); + } + } + + private class InputCell extends FrameLayout { + public EditTextBoldCursor field; + public TextView tme; + + public InputCell(Context context) { + super(context); + + LinearLayout content = new LinearLayout(getContext()); + content.setOrientation(LinearLayout.HORIZONTAL); + field = new EditTextBoldCursor(getContext()); + field.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17); + field.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + field.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + field.setBackgroundDrawable(null); +// field.setLineColors(getThemedColor(Theme.key_windowBackgroundWhiteInputField), getThemedColor(Theme.key_windowBackgroundWhiteInputFieldActivated), getThemedColor(Theme.key_windowBackgroundWhiteRedText3)); + field.setMaxLines(1); + field.setLines(1); + field.setPadding(0, 0, 0, 0); + field.setSingleLine(true); + field.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + field.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); + field.setImeOptions(EditorInfo.IME_ACTION_DONE); + field.setHint(LocaleController.getString("UsernameLinkPlaceholder", R.string.UsernameLinkPlaceholder)); + field.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + field.setCursorSize(AndroidUtilities.dp(19)); + field.setCursorWidth(1.5f); + field.setOnEditorActionListener((textView, i, keyEvent) -> { + if (i == EditorInfo.IME_ACTION_DONE && doneButton != null) { + doneButton.performClick(); + return true; + } + return false; + }); + field.setText(username); + field.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + String wasUsername = username; + username = charSequence == null ? "" : charSequence.toString(); + updateUsernameCell(wasUsername); + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + String wasUsername = username; + username = charSequence == null ? "" : charSequence.toString(); + updateUsernameCell(wasUsername); + if (ignoreCheck) { + return; + } + checkUserName(username, false); + } + + @Override + public void afterTextChanged(Editable editable) { + if (username.startsWith("@")) { + username = username.substring(1); + } + if (username.length() > 0) { + String url = "https://" + MessagesController.getInstance(currentAccount).linkPrefix + "/" + username; + String text = LocaleController.formatString("UsernameHelpLink", R.string.UsernameHelpLink, url); + int index = text.indexOf(url); + SpannableStringBuilder textSpan = new SpannableStringBuilder(text); + if (index >= 0) { + textSpan.setSpan(new LinkSpan(url), index, index + url.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } +// helpTextView.setText(TextUtils.concat(infoText, "\n\n", textSpan)); + } else { +// helpTextView.setText(infoText); + } + } + + private void updateUsernameCell(String was) { + if (editableUsernameCell != null && was != null) { + editableUsernameCell.updateUsername(username); + } + } + }); + tme = new TextView(getContext()); + tme.setMaxLines(1); + tme.setLines(1); + tme.setPadding(0, 0, 0, 0); + tme.setSingleLine(true); + tme.setText(getMessagesController().linkPrefix + "/"); + tme.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17); + tme.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + tme.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + tme.setTranslationY(-AndroidUtilities.dp(3)); + content.addView(tme, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, 0, Gravity.CENTER_VERTICAL, 21, 15, 0, 15)); + content.addView(field, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, 1, Gravity.CENTER_VERTICAL, 0, 15, 21, 15)); + addView(content, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP)); + setBackgroundColor(getThemedColor(Theme.key_windowBackgroundWhite)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure( + MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50), MeasureSpec.EXACTLY) + ); + } + } + + private static Paint linkBackgroundActive = new Paint(Paint.ANTI_ALIAS_FLAG); + private static Paint linkBackgroundInactive = new Paint(Paint.ANTI_ALIAS_FLAG); + private static Paint dragPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + public static class UsernameCell extends FrameLayout { + + public boolean isProfile = false; + private Theme.ResourcesProvider resourcesProvider; + + private SimpleTextView usernameView; + private AnimatedTextView activeView; + + private Drawable[] linkDrawables; + + public UsernameCell(Context context, Theme.ResourcesProvider resourcesProvider) { + super(context); + + this.resourcesProvider = resourcesProvider; + + setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite, resourcesProvider)); + + usernameView = new SimpleTextView(getContext()); + usernameView.setTextSize(16); + usernameView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); + usernameView.setEllipsizeByGradient(true); + addView(usernameView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP, 70, 9, 0, 50)); + + activeView = new AnimatedTextView(getContext(), false, true, true); + activeView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2, resourcesProvider)); + activeView.setAnimationProperties(0.4f, 0, 120, CubicBezierInterpolator.EASE_OUT); + activeView.setTextSize(AndroidUtilities.dp(13)); + addView(activeView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP, 70, 23, 0, 0)); + + linkDrawables = new Drawable[] { + ContextCompat.getDrawable(context, R.drawable.msg_link_1).mutate(), + ContextCompat.getDrawable(context, R.drawable.msg_link_2).mutate() + }; + linkDrawables[0].setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY)); + linkDrawables[1].setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY)); + + linkBackgroundActive.setColor(Theme.getColor(Theme.key_featuredStickers_addButton, resourcesProvider)); + linkBackgroundInactive.setColor(Theme.getColor(Theme.key_chats_unreadCounterMuted, resourcesProvider)); + } + + + public TLRPC.TL_username currentUsername; + private boolean useDivider; + private AnimatedFloat useDividerAlpha = new AnimatedFloat(this, 300, CubicBezierInterpolator.DEFAULT); + + private float activeViewTextColorT; + private ValueAnimator activeViewTextColorAnimator; + + public boolean active; + public boolean editable; + private AnimatedFloat activeFloat = new AnimatedFloat(this, 400, CubicBezierInterpolator.EASE_OUT_QUINT); + + public void set(TLRPC.TL_username username, boolean useDivider, boolean animated) { + currentUsername = username; + this.useDivider = useDivider; + invalidate(); + if (currentUsername == null) { + active = false; + editable = false; + return; + } + + active = username.active; + editable = username.editable; + updateUsername(username.username); + if (isProfile) { + activeView.setText(editable ? LocaleController.getString("UsernameProfileLinkEditable", R.string.UsernameProfileLinkEditable) : (active ? LocaleController.getString("UsernameProfileLinkActive", R.string.UsernameProfileLinkActive) : LocaleController.getString("UsernameProfileLinkInactive", R.string.UsernameProfileLinkInactive)), animated, !active); + } else { + activeView.setText(editable ? LocaleController.getString("UsernameLinkEditable", R.string.UsernameLinkEditable) : (active ? LocaleController.getString("UsernameLinkActive", R.string.UsernameLinkActive) : LocaleController.getString("UsernameLinkInactive", R.string.UsernameLinkInactive)), animated, !active); + } + animateValueTextColor(active || editable, animated); + } + + protected String getUsernameEditable() { + return null; + } + + public void updateUsername(String username) { + String usernameString = editable ? getUsernameEditable() : username; + if (TextUtils.isEmpty(usernameString)) { + SpannableStringBuilder ssb = new SpannableStringBuilder("@"); + SpannableString sb = new SpannableString(LocaleController.getString("UsernameLinkPlaceholder", R.string.UsernameLinkPlaceholder)); + sb.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_windowBackgroundWhiteHintText, resourcesProvider)), 0, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append(sb); + usernameView.setText(ssb); + } else { + usernameView.setText("@" + usernameString); + } + } + + private void animateValueTextColor(boolean active, boolean animated) { + if (activeViewTextColorAnimator != null) { + activeViewTextColorAnimator.cancel(); + activeViewTextColorAnimator = null; + } + if (animated) { + activeViewTextColorAnimator = ValueAnimator.ofFloat(activeViewTextColorT, active ? 1f : 0f); + activeViewTextColorAnimator.addUpdateListener(anm -> { + activeViewTextColorT = (float) anm.getAnimatedValue(); + activeView.setTextColor( + ColorUtils.blendARGB( + Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2, resourcesProvider), + Theme.getColor(Theme.key_windowBackgroundWhiteBlueText, resourcesProvider), + activeViewTextColorT + ) + ); + }); + activeViewTextColorAnimator.setDuration(120); + activeViewTextColorAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT); + activeViewTextColorAnimator.start(); + } else { + activeViewTextColorT = active ? 1 : 0; + activeView.setTextColor( + ColorUtils.blendARGB( + Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2, resourcesProvider), + Theme.getColor(Theme.key_windowBackgroundWhiteBlueText, resourcesProvider), + activeViewTextColorT + ) + ); + } + } + + public void update() { + if (currentUsername != null) { + set(currentUsername, useDivider, true); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure( + MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(58), MeasureSpec.EXACTLY) + ); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + float activeValue = activeFloat.set(active ? 1f : 0f); + if (activeValue < 1) { + canvas.drawCircle(AndroidUtilities.dp(35), AndroidUtilities.dp(29), AndroidUtilities.dp(16), linkBackgroundInactive); + + linkDrawables[1].setAlpha((int) (255 * (1f - activeValue))); + linkDrawables[1].setBounds( + AndroidUtilities.dp(35) - linkDrawables[1].getIntrinsicWidth() / 2, + AndroidUtilities.dp(29) - linkDrawables[1].getIntrinsicHeight() / 2, + AndroidUtilities.dp(35) + linkDrawables[1].getIntrinsicWidth() / 2, + AndroidUtilities.dp(29) + linkDrawables[1].getIntrinsicHeight() / 2 + ); + linkDrawables[1].draw(canvas); + } + if (activeValue > 0) { + linkBackgroundActive.setAlpha((int) (255 * activeValue)); + canvas.drawCircle(AndroidUtilities.dp(35), AndroidUtilities.dp(29), activeValue * AndroidUtilities.dp(16), linkBackgroundActive); + + linkDrawables[0].setAlpha((int) (255 * activeValue)); + linkDrawables[0].setBounds( + AndroidUtilities.dp(35) - linkDrawables[0].getIntrinsicWidth() / 2, + AndroidUtilities.dp(29) - linkDrawables[0].getIntrinsicHeight() / 2, + AndroidUtilities.dp(35) + linkDrawables[0].getIntrinsicWidth() / 2, + AndroidUtilities.dp(29) + linkDrawables[0].getIntrinsicHeight() / 2 + ); + linkDrawables[0].draw(canvas); + } + + float dividerAlpha = useDividerAlpha.set(useDivider ? 1f : 0f); + if (dividerAlpha > 0) { + int wasAlpha = Theme.dividerPaint.getAlpha(); + Theme.dividerPaint.setAlpha((int) (wasAlpha * dividerAlpha)); + canvas.drawRect(AndroidUtilities.dp(70), getHeight() - 1, getWidth(), getHeight(), Theme.dividerPaint); + Theme.dividerPaint.setAlpha(wasAlpha); + } + + dragPaint.setColor(Theme.getColor(Theme.key_stickers_menu)); + dragPaint.setAlpha((int) (dragPaint.getAlpha() * activeValue)); + AndroidUtilities.rectTmp.set(getWidth() - AndroidUtilities.dp(37), AndroidUtilities.dp(25), getWidth() - AndroidUtilities.dp(21), AndroidUtilities.dp(25 + 2)); + canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(.3f), AndroidUtilities.dp(.3f), dragPaint); + + AndroidUtilities.rectTmp.set(getWidth() - AndroidUtilities.dp(37), AndroidUtilities.dp(25 + 6), getWidth() - AndroidUtilities.dp(21), AndroidUtilities.dp(25 + 2 + 6)); + canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(.3f), AndroidUtilities.dp(.3f), dragPaint); + } + } + + + public class TouchHelperCallback extends ItemTouchHelper.Callback { + + @Override + public boolean isLongPressDragEnabled() { + return true; + } + + @Override + public int getMovementFlags(@NonNull RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + if (viewHolder.getItemViewType() != VIEW_TYPE_USERNAME || !((UsernameCell) viewHolder.itemView).active) { + return makeMovementFlags(0, 0); + } + return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0); + } + + @Override + public boolean onMove(@NonNull RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) { + if (source.getItemViewType() != target.getItemViewType() || + target.itemView instanceof UsernameCell && !((UsernameCell) target.itemView).active) { + return false; + } + adapter.swapElements(source.getAdapterPosition(), target.getAdapterPosition()); + return true; + } + + @Override + public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + } + + @Override + public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { + if (actionState == ItemTouchHelper.ACTION_STATE_IDLE) { + sendReorder(); + } else { + listView.cancelClickRunnables(false); + viewHolder.itemView.setPressed(true); + } + super.onSelectedChanged(viewHolder, actionState); + } + + @Override + public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { + } + + @Override + public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { + super.clearView(recyclerView, viewHolder); + viewHolder.itemView.setPressed(false); + } + } + @Override public void onResume() { super.onResume(); SharedPreferences preferences = MessagesController.getGlobalMainSettings(); boolean animations = preferences.getBoolean("view_animations", true); if (!animations) { - firstNameField.requestFocus(); - AndroidUtilities.showKeyboard(firstNameField); + focusUsernameField(false); } } @@ -246,10 +1003,11 @@ public class ChangeUsernameActivity extends BaseFragment { if (name != null && name.startsWith("@")) { name = name.substring(1); } - if (!TextUtils.isEmpty(name)) { - checkTextView.setVisibility(View.VISIBLE); - } else { - checkTextView.setVisibility(View.GONE); + if (statusTextView != null) { + statusTextView.setVisibility(!TextUtils.isEmpty(name) ? View.VISIBLE : View.GONE); + if (helpCell != null) { + helpCell.update(); + } } if (alert && name.length() == 0) { return true; @@ -265,9 +1023,14 @@ public class ChangeUsernameActivity extends BaseFragment { lastNameAvailable = false; if (name != null) { if (name.startsWith("_") || name.endsWith("_")) { - checkTextView.setText(LocaleController.getString("UsernameInvalid", R.string.UsernameInvalid)); - checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); - checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); + if (statusTextView != null) { + statusTextView.setText(LocaleController.getString("UsernameInvalid", R.string.UsernameInvalid)); + statusTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + statusTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); + if (helpCell != null) { + helpCell.update(); + } + } return false; } for (int a = 0; a < name.length(); a++) { @@ -276,9 +1039,14 @@ public class ChangeUsernameActivity extends BaseFragment { if (alert) { AlertsCreator.showSimpleAlert(this, LocaleController.getString("UsernameInvalidStartNumber", R.string.UsernameInvalidStartNumber)); } else { - checkTextView.setText(LocaleController.getString("UsernameInvalidStartNumber", R.string.UsernameInvalidStartNumber)); - checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); - checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); + if (statusTextView != null) { + statusTextView.setText(LocaleController.getString("UsernameInvalidStartNumber", R.string.UsernameInvalidStartNumber)); + statusTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + statusTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); + if (helpCell != null) { + helpCell.update(); + } + } } return false; } @@ -286,9 +1054,14 @@ public class ChangeUsernameActivity extends BaseFragment { if (alert) { AlertsCreator.showSimpleAlert(this, LocaleController.getString("UsernameInvalid", R.string.UsernameInvalid)); } else { - checkTextView.setText(LocaleController.getString("UsernameInvalid", R.string.UsernameInvalid)); - checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); - checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); + if (statusTextView != null) { + statusTextView.setText(LocaleController.getString("UsernameInvalid", R.string.UsernameInvalid)); + statusTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + statusTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); + if (helpCell != null) { + helpCell.update(); + } + } } return false; } @@ -298,9 +1071,14 @@ public class ChangeUsernameActivity extends BaseFragment { if (alert) { AlertsCreator.showSimpleAlert(this, LocaleController.getString("UsernameInvalidShort", R.string.UsernameInvalidShort)); } else { - checkTextView.setText(LocaleController.getString("UsernameInvalidShort", R.string.UsernameInvalidShort)); - checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); - checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); + if (statusTextView != null) { + statusTextView.setText(LocaleController.getString("UsernameInvalidShort", R.string.UsernameInvalidShort)); + statusTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + statusTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); + if (helpCell != null) { + helpCell.update(); + } + } } return false; } @@ -308,9 +1086,14 @@ public class ChangeUsernameActivity extends BaseFragment { if (alert) { AlertsCreator.showSimpleAlert(this, LocaleController.getString("UsernameInvalidLong", R.string.UsernameInvalidLong)); } else { - checkTextView.setText(LocaleController.getString("UsernameInvalidLong", R.string.UsernameInvalidLong)); - checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); - checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); + if (statusTextView != null) { + statusTextView.setText(LocaleController.getString("UsernameInvalidLong", R.string.UsernameInvalidLong)); + statusTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + statusTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); + if (helpCell != null) { + helpCell.update(); + } + } } return false; } @@ -321,15 +1104,25 @@ public class ChangeUsernameActivity extends BaseFragment { currentName = ""; } if (name.equals(currentName)) { - checkTextView.setText(LocaleController.formatString("UsernameAvailable", R.string.UsernameAvailable, name)); - checkTextView.setTag(Theme.key_windowBackgroundWhiteGreenText); - checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGreenText)); + if (statusTextView != null) { + statusTextView.setText(LocaleController.formatString("UsernameAvailable", R.string.UsernameAvailable, name)); + statusTextView.setTag(Theme.key_windowBackgroundWhiteGreenText); + statusTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGreenText)); + if (helpCell != null) { + helpCell.update(); + } + } return true; } - checkTextView.setText(LocaleController.getString("UsernameChecking", R.string.UsernameChecking)); - checkTextView.setTag(Theme.key_windowBackgroundWhiteGrayText8); - checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText8)); + if (statusTextView != null) { + statusTextView.setText(LocaleController.getString("UsernameChecking", R.string.UsernameChecking)); + statusTextView.setTag(Theme.key_windowBackgroundWhiteGrayText8); + statusTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText8)); + if (helpCell != null) { + helpCell.update(); + } + } lastCheckName = name; final String nameFinal = name; checkRunnable = () -> { @@ -339,14 +1132,24 @@ public class ChangeUsernameActivity extends BaseFragment { checkReqId = 0; if (lastCheckName != null && lastCheckName.equals(nameFinal)) { if (error == null && response instanceof TLRPC.TL_boolTrue) { - checkTextView.setText(LocaleController.formatString("UsernameAvailable", R.string.UsernameAvailable, nameFinal)); - checkTextView.setTag(Theme.key_windowBackgroundWhiteGreenText); - checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGreenText)); + if (statusTextView != null) { + statusTextView.setText(LocaleController.formatString("UsernameAvailable", R.string.UsernameAvailable, nameFinal)); + statusTextView.setTag(Theme.key_windowBackgroundWhiteGreenText); + statusTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGreenText)); + if (helpCell != null) { + helpCell.update(); + } + } lastNameAvailable = true; } else { - checkTextView.setText(LocaleController.getString("UsernameInUse", R.string.UsernameInUse)); - checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); - checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); + if (statusTextView != null) { + statusTextView.setText(LocaleController.getString("UsernameInUse", R.string.UsernameInUse)); + statusTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + statusTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); + if (helpCell != null) { + helpCell.update(); + } + } lastNameAvailable = false; } } @@ -358,11 +1161,11 @@ public class ChangeUsernameActivity extends BaseFragment { } private void saveName() { - String newName = firstNameField.getText().toString(); - if (newName.startsWith("@")) { - newName = newName.substring(1); + if (username.startsWith("@")) { + username = username.substring(1); } - if (!checkUserName(newName, true)) { + if (!checkUserName(username, true)) { + shakeIfOff(); return; } TLRPC.User user = UserConfig.getInstance(currentAccount).getCurrentUser(); @@ -373,7 +1176,7 @@ public class ChangeUsernameActivity extends BaseFragment { if (currentName == null) { currentName = ""; } - if (currentName.equals(newName)) { + if (currentName.equals(username)) { finishFragment(); return; } @@ -381,7 +1184,7 @@ public class ChangeUsernameActivity extends BaseFragment { final AlertDialog progressDialog = new AlertDialog(getParentActivity(), 3); final TLRPC.TL_account_updateUsername req = new TLRPC.TL_account_updateUsername(); - req.username = newName; + req.username = username; NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, MessagesController.UPDATE_MASK_NAME); final int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { @@ -400,6 +1203,15 @@ public class ChangeUsernameActivity extends BaseFragment { UserConfig.getInstance(currentAccount).saveConfig(true); finishFragment(); }); + } else if ("USERNAME_NOT_MODIFIED".equals(error.text)) { + AndroidUtilities.runOnUIThread(() -> { + try { + progressDialog.dismiss(); + } catch (Exception e) { + FileLog.e(e); + } + finishFragment(); + }); } else { AndroidUtilities.runOnUIThread(() -> { try { @@ -408,6 +1220,7 @@ public class ChangeUsernameActivity extends BaseFragment { FileLog.e(e); } AlertsCreator.processError(currentAccount, error, ChangeUsernameActivity.this, req); + shakeIfOff(); }); } }, ConnectionsManager.RequestFlagFailOnServerErrors); @@ -420,11 +1233,28 @@ public class ChangeUsernameActivity extends BaseFragment { @Override public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen) { - firstNameField.requestFocus(); - AndroidUtilities.showKeyboard(firstNameField); + focusUsernameField(false); } } + public void shakeIfOff() { + if (listView == null) { + return; + } + for (int i = 0; i < listView.getChildCount(); ++i) { + View child = listView.getChildAt(i); + if (child instanceof HeaderCell) { + AndroidUtilities.shakeViewSpring(((HeaderCell) child).getTextView()); + } else if (child instanceof UsernameHelpCell) { + AndroidUtilities.shakeViewSpring(child); + } else if (child instanceof InputCell) { + AndroidUtilities.shakeViewSpring(((InputCell) child).field); + AndroidUtilities.shakeViewSpring(((InputCell) child).tme); + } + } + BotWebViewVibrationEffect.APP_ERROR.vibrate(); + } + @Override public ArrayList getThemeDescriptions() { ArrayList themeDescriptions = new ArrayList<>(); @@ -436,17 +1266,33 @@ public class ChangeUsernameActivity extends BaseFragment { themeDescriptions.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle)); themeDescriptions.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector)); - themeDescriptions.add(new ThemeDescription(firstNameField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); - themeDescriptions.add(new ThemeDescription(firstNameField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText)); - themeDescriptions.add(new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField)); - themeDescriptions.add(new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated)); +// themeDescriptions.add(new ThemeDescription(firstNameField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); +// themeDescriptions.add(new ThemeDescription(firstNameField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText)); +// themeDescriptions.add(new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField)); +// themeDescriptions.add(new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated)); - themeDescriptions.add(new ThemeDescription(helpTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText8)); - - themeDescriptions.add(new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteRedText4)); - themeDescriptions.add(new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteGreenText)); - themeDescriptions.add(new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText8)); +// themeDescriptions.add(new ThemeDescription(helpTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText8)); +// +// themeDescriptions.add(new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteRedText4)); +// themeDescriptions.add(new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteGreenText)); +// themeDescriptions.add(new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText8)); return themeDescriptions; } + + @Override + public void onBecomeFullyVisible() { + super.onBecomeFullyVisible(); + if (parentLayout != null && parentLayout.getDrawerLayoutContainer() != null) { + parentLayout.getDrawerLayoutContainer().setBehindKeyboardColor(getThemedColor(Theme.key_windowBackgroundGray)); + } + } + + @Override + public void onBecomeFullyHidden() { + super.onBecomeFullyHidden(); + if (parentLayout != null && parentLayout.getDrawerLayoutContainer() != null) { + parentLayout.getDrawerLayoutContainer().setBehindKeyboardColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java index 2868aa189..520b528bc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java @@ -1097,7 +1097,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio AndroidUtilities.updateViewVisibilityAnimated(progressView, false, 0.3f, true); chatListView.setEmptyView(emptyViewContainer); } - chatListView.setAnimateEmptyView(true, 1); + chatListView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA_SCALE); undoView = new UndoView(context); undoView.setAdditionalTranslationY(AndroidUtilities.dp(51)); @@ -1128,7 +1128,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio ArrayList items = new ArrayList<>(); final ArrayList options = new ArrayList<>(); - if (selectedObject.type == 0 || selectedObject.caption != null) { + if (selectedObject.type == MessageObject.TYPE_TEXT || selectedObject.caption != null) { items.add(LocaleController.getString("Copy", R.string.Copy)); options.add(3); } @@ -1385,13 +1385,13 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio if (path == null || path.length() == 0) { path = getFileLoader().getPathToMessage(selectedObject.messageOwner).toString(); } - if (selectedObject.type == 3 || selectedObject.type == 1) { + if (selectedObject.type == MessageObject.TYPE_VIDEO || selectedObject.type == MessageObject.TYPE_PHOTO) { if (Build.VERSION.SDK_INT >= 23 && (Build.VERSION.SDK_INT <= 28 || BuildVars.NO_SCOPED_STORAGE) && getParentActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { getParentActivity().requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 4); selectedObject = null; return; } - MediaController.saveFile(path, getParentActivity(), selectedObject.type == 3 ? 1 : 0, null, null); + MediaController.saveFile(path, getParentActivity(), selectedObject.type == MessageObject.TYPE_VIDEO ? 1 : 0, null, null); } break; } @@ -1571,7 +1571,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio } if (messageObject.type == 6) { return -1; - } else if (messageObject.type == 10 || messageObject.type == 11 || messageObject.type == 16) { + } else if (messageObject.type == 10 || messageObject.type == MessageObject.TYPE_ACTION_PHOTO || messageObject.type == MessageObject.TYPE_PHONE_CALL) { if (messageObject.getId() == 0) { return -1; } @@ -1619,7 +1619,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio } return 4; } - } else if (messageObject.type == 12) { + } else if (messageObject.type == MessageObject.TYPE_CONTACT) { return 8; } else if (messageObject.isMediaEmpty()) { return 3; @@ -1649,7 +1649,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio } @Override - protected void onRemoveFromParent() { + public void onRemoveFromParent() { MediaController.getInstance().setTextureView(videoTextureView, null, null, false); } @@ -1910,7 +1910,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio } @Override - protected void onBecomeFullyHidden() { + public void onBecomeFullyHidden() { if (undoView != null) { undoView.hide(true, 0); } @@ -1944,7 +1944,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - if (message.type == 3) { + if (message.type == MessageObject.TYPE_VIDEO) { builder.setMessage(LocaleController.getString("NoPlayerInstalled", R.string.NoPlayerInstalled)); } else { builder.setMessage(LocaleController.formatString("NoHandleAppInstalled", R.string.NoHandleAppInstalled, message.getDocument().mime_type)); @@ -2063,9 +2063,9 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio } @Override - public boolean needPlayMessage(MessageObject messageObject) { + public boolean needPlayMessage(MessageObject messageObject, boolean muted) { if (messageObject.isVoice() || messageObject.isRoundVideo()) { - boolean result = MediaController.getInstance().playMessage(messageObject); + boolean result = MediaController.getInstance().playMessage(messageObject, muted); MediaController.getInstance().setVoiceMessagesPlaylist(null, false); return result; } else if (messageObject.isMusic()) { @@ -2253,10 +2253,10 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio MessageObject message = cell.getMessageObject(); if (message.getInputStickerSet() != null) { showDialog(new StickersAlert(getParentActivity(), ChannelAdminLogActivity.this, message.getInputStickerSet(), null, null)); - } else if (message.isVideo() || message.type == 1 || message.type == 0 && !message.isWebpageDocument() || message.isGif()) { + } else if (message.isVideo() || message.type == MessageObject.TYPE_PHOTO || message.type == MessageObject.TYPE_TEXT && !message.isWebpageDocument() || message.isGif()) { PhotoViewer.getInstance().setParentActivity(ChannelAdminLogActivity.this); - PhotoViewer.getInstance().openPhoto(message, null, 0, 0, provider); - } else if (message.type == 3) { + PhotoViewer.getInstance().openPhoto(message, null, 0, 0, 0, provider); + } else if (message.type == MessageObject.TYPE_VIDEO) { try { File f = null; if (message.messageOwner.attachPath != null && message.messageOwner.attachPath.length() != 0) { @@ -2276,14 +2276,14 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio } catch (Exception e) { alertUserOpenError(message); } - } else if (message.type == 4) { + } else if (message.type == MessageObject.TYPE_GEO) { if (!AndroidUtilities.isMapsInstalled(ChannelAdminLogActivity.this)) { return; } LocationActivity fragment = new LocationActivity(0); fragment.setMessageObject(message); presentFragment(fragment); - } else if (message.type == 9 || message.type == 0) { + } else if (message.type == MessageObject.TYPE_FILE || message.type == MessageObject.TYPE_TEXT) { if (message.getDocumentName().toLowerCase().endsWith("attheme")) { File locFile = null; if (message.messageOwner.attachPath != null && message.messageOwner.attachPath.length() != 0) { @@ -2366,7 +2366,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio ImageLocation imageLocation = ImageLocation.getForPhoto(photoSize, message.messageOwner.action.photo); PhotoViewer.getInstance().openPhoto(photoSize.location, imageLocation, provider); } else { - PhotoViewer.getInstance().openPhoto(message, null, 0, 0, provider); + PhotoViewer.getInstance().openPhoto(message, null, 0, 0, 0, provider); } } @@ -2450,6 +2450,16 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio } + @Override + public BaseFragment getBaseFragment() { + return ChannelAdminLogActivity.this; + } + + @Override + public long getDialogId() { + return -currentChat.id; + } + @Override public void didPressReplyMessage(ChatActionCell cell, int id) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java index e7df5caab..a2334e487 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java @@ -325,7 +325,7 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC if (v != null) { v.vibrate(200); } - AndroidUtilities.shakeView(nameTextView, 2, 0); + AndroidUtilities.shakeView(nameTextView); return; } donePressed = true; @@ -350,10 +350,10 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC if (v != null) { v.vibrate(200); } - AndroidUtilities.shakeView(checkTextView, 2, 0); + AndroidUtilities.shakeView(checkTextView); return; } else { - MessagesController.getInstance(currentAccount).updateChannelUserName(chatId, lastCheckName); + MessagesController.getInstance(currentAccount).updateChannelUserName(ChannelCreateActivity.this, chatId, lastCheckName, null, null); } } } @@ -1109,9 +1109,9 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); if (channel.megagroup) { - builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlert", R.string.RevokeLinkAlert, MessagesController.getInstance(currentAccount).linkPrefix + "/" + channel.username, channel.title))); + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlert", R.string.RevokeLinkAlert, MessagesController.getInstance(currentAccount).linkPrefix + "/" + ChatObject.getPublicUsername(channel), channel.title))); } else { - builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlertChannel", R.string.RevokeLinkAlertChannel, MessagesController.getInstance(currentAccount).linkPrefix + "/" + channel.username, channel.title))); + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlertChannel", R.string.RevokeLinkAlertChannel, MessagesController.getInstance(currentAccount).linkPrefix + "/" + ChatObject.getPublicUsername(channel), channel.title))); } builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); builder.setPositiveButton(LocaleController.getString("RevokeButton", R.string.RevokeButton), (dialogInterface, i) -> { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java index 74f05aa0f..10bb268e9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java @@ -32,6 +32,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; +import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Outline; import android.graphics.Paint; @@ -41,6 +42,7 @@ import android.graphics.PorterDuffColorFilter; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.Region; import android.graphics.Shader; import android.graphics.Typeface; import android.graphics.drawable.Drawable; @@ -55,7 +57,6 @@ import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.Spanned; -import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.text.style.CharacterStyle; @@ -63,6 +64,7 @@ import android.text.style.ClickableSpan; import android.text.style.ForegroundColorSpan; import android.text.style.ImageSpan; import android.text.style.URLSpan; +import android.util.Log; import android.util.Property; import android.util.SparseArray; import android.util.SparseIntArray; @@ -96,6 +98,9 @@ import androidx.collection.LongSparseArray; import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; import androidx.core.graphics.ColorUtils; +import androidx.dynamicanimation.animation.FloatValueHolder; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; import androidx.exifinterface.media.ExifInterface; import androidx.recyclerview.widget.ChatListItemAnimator; import androidx.recyclerview.widget.GridLayoutManagerFixed; @@ -112,6 +117,7 @@ import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AccountInstance; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.BotWebViewVibrationEffect; import org.telegram.messenger.BuildVars; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ChatThemeController; @@ -159,6 +165,7 @@ import org.telegram.ui.ActionBar.BackDrawable; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.ActionBar.EmojiThemes; +import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.SimpleTextView; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; @@ -214,6 +221,9 @@ import org.telegram.ui.Components.EmojiPacksAlert; import org.telegram.ui.Components.EmojiView; import org.telegram.ui.Components.ExtendedGridLayoutManager; import org.telegram.ui.Components.FireworksOverlay; +import org.telegram.ui.Components.FloatingDebug.FloatingDebugController; +import org.telegram.ui.Components.FloatingDebug.FloatingDebugProvider; +import org.telegram.ui.Components.Forum.ForumUtilities; import org.telegram.ui.Components.ForwardingPreviewView; import org.telegram.ui.Components.FragmentContextView; import org.telegram.ui.Components.GigagroupConvertAlert; @@ -268,9 +278,11 @@ import org.telegram.ui.Components.URLSpanNoUnderline; import org.telegram.ui.Components.URLSpanReplacement; import org.telegram.ui.Components.URLSpanUserMention; import org.telegram.ui.Components.UndoView; +import org.telegram.ui.Components.UnreadCounterTextView; import org.telegram.ui.Components.ViewHelper; import org.telegram.ui.Components.voip.VoIPHelper; import org.telegram.ui.Delegates.ChatActivityMemberRequestsDelegate; +import org.telegram.ui.LNavigation.LNavigation; import java.io.BufferedWriter; import java.io.File; @@ -285,6 +297,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; @@ -293,8 +306,19 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; @SuppressWarnings("unchecked") -public class ChatActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, DialogsActivity.DialogsActivityDelegate, LocationActivity.LocationActivityDelegate, ChatAttachAlertDocumentLayout.DocumentSelectActivityDelegate { +public class ChatActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, DialogsActivity.DialogsActivityDelegate, LocationActivity.LocationActivityDelegate, ChatAttachAlertDocumentLayout.DocumentSelectActivityDelegate, FragmentContextView.ChatActivityInterface, FloatingDebugProvider { + private final static boolean PULL_DOWN_BACK_FRAGMENT = false; + private final static boolean DISABLE_PROGRESS_VIEW = true; + private final static int SKELETON_DISAPPEAR_MS = 200; + public final static int DEBUG_SHARE_ALERT_MODE_NORMAL = 0, + DEBUG_SHARE_ALERT_MODE_LESS = 1, + DEBUG_SHARE_ALERT_MODE_MORE = 2; + + public int shareAlertDebugMode = DEBUG_SHARE_ALERT_MODE_NORMAL; + public boolean shareAlertDebugTopicsSlowMotion; + + public boolean justCreatedTopic = false; protected TLRPC.Chat currentChat; protected TLRPC.User currentUser; protected TLRPC.EncryptedChat currentEncryptedChat; @@ -326,6 +350,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private RadialProgressView progressBar; private ActionBarMenuSubItem addContactItem; private ActionBarMenuSubItem clearHistoryItem; + private ActionBarMenuSubItem viewAsTopics; + private ActionBarMenuSubItem closeTopicItem; private ClippingImageView animatingImageView; private RecyclerListView chatListView; private ChatListItemAnimator chatListItemAnimator; @@ -335,6 +361,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private ImageView bottomOverlayImage; private RadialProgressView bottomOverlayProgress; private AnimatorSet bottomOverlayAnimation; + private boolean bottomOverlayChatWaitsReply; private BlurredFrameLayout bottomOverlayChat; private BlurredFrameLayout bottomMessagesActionContainer; private TextView forwardButton; @@ -384,6 +411,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private TextView addToContactsButton; private boolean addToContactsButtonArchive; private TextView reportSpamButton; + private TextView restartTopicButton; private View topViewSeparator1, topViewSeparator2; private LinkSpanDrawable.LinksTextView emojiStatusSpamHint; private ImageView closeReportSpam; @@ -483,10 +511,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private boolean threadMessageVisible = true; private ArrayList threadMessageObjects; private MessageObject replyMessageHeaderObject; + private TLRPC.TL_forumTopic forumTopic; private int threadMessageId; private int replyOriginalMessageId; private TLRPC.Chat replyOriginalChat; private boolean isComments; + public boolean isTopic; private boolean threadMessageAdded; private boolean scrollToThreadMessage; private int threadMaxInboxReadId; @@ -644,6 +674,21 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private int last_message_id = 0; private long mergeDialogId; + private long startMessageAppearTransitionMs; + private List messageSkeletons = new ArrayList<>(); + private int lastSkeletonCount; + private int lastSkeletonMessageCount; + private Paint skeletonPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Theme.MessageDrawable.PathDrawParams skeletonBackgroundCacheParams = new Theme.MessageDrawable.PathDrawParams(); + private Theme.MessageDrawable skeletonBackgroundDrawable = new Theme.MessageDrawable(Theme.MessageDrawable.TYPE_TEXT, false, false, this::getThemedColor); + private long skeletonLastUpdateTime; + private int skeletonGradientWidth; + private int skeletonTotalTranslation; + private Matrix skeletonMatrix = new Matrix(); + private LinearGradient skeletonGradient; + private int skeletonColor0; + private int skeletonColor1; + private boolean premiumInvoiceBot; private boolean showScrollToMessageError; private int startLoadFromMessageId; @@ -665,7 +710,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private boolean loadingForward; private MessageObject unreadMessageObject; private MessageObject scrollToMessage; - private int highlightMessageId = Integer.MAX_VALUE; + public int highlightMessageId = Integer.MAX_VALUE; private int scrollToMessagePosition = -10000; private Runnable unselectRunnable; @@ -789,6 +834,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private long activityResumeTime; private int transitionAnimationIndex; + private int transitionAnimationGlobalIndex; private int scrollAnimationIndex; private int scrollCallbackAnimationIndex; @@ -821,6 +867,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private TLRPC.TL_channels_sendAsPeers sendAsPeersObj; + private boolean switchFromTopics; + private boolean switchingFromTopics; + private float switchingFromTopicsProgress; + private final static int OPTION_RETRY = 0; private final static int OPTION_DELETE = 1; private final static int OPTION_FORWARD = 2; @@ -853,6 +903,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private final static int OPTION_TRANSLATE = 29; private final static int OPTION_TRANSCRIBE = 30; private final static int OPTION_HIDE_SPONSORED_MESSAGE = 31; + private final static int OPTION_VIEW_IN_TOPIC = 32; private final static int OPTION_SEND_NOW = 100; private final static int OPTION_EDIT_SCHEDULE_TIME = 102; @@ -866,7 +917,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not NotificationCenter.newDraftReceived, NotificationCenter.updateMentionsCount, NotificationCenter.didUpdateConnectionState, - NotificationCenter.updateInterfaces, + //NotificationCenter.updateInterfaces, NotificationCenter.updateDefaultSendAsPeer, NotificationCenter.closeChats, NotificationCenter.chatInfoCantLoad, @@ -975,6 +1026,37 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } + public int getTopicId() { + return isTopic ? threadMessageId : 0; + } + + @Override + public List onGetDebugItems() { + List items = new ArrayList<>(); + if (currentChat != null && ChatObject.isChannel(currentChat)) { + String mode; + switch (shareAlertDebugMode) { + default: + mode = LocaleController.getString(R.string.DebugShareAlertDialogsModeNormal); + break; + case DEBUG_SHARE_ALERT_MODE_LESS: + mode = LocaleController.getString(R.string.DebugShareAlertDialogsModeLess); + break; + case DEBUG_SHARE_ALERT_MODE_MORE: + mode = LocaleController.getString(R.string.DebugShareAlertDialogsModeMore); + break; + } + items.add(new FloatingDebugController.DebugItem(LocaleController.formatString(R.string.DebugShareAlertSwitchDialogsMode, mode), () -> { + shareAlertDebugMode++; + shareAlertDebugMode %= 3; + })); + + items.add(new FloatingDebugController.DebugItem(LocaleController.getString(R.string.DebugShareAlertTopicsSlowMotion), ()-> shareAlertDebugTopicsSlowMotion = !shareAlertDebugTopicsSlowMotion)); + } + + return items; + } + private interface ChatActivityDelegate { default void openReplyMessage(int mid) { @@ -996,229 +1078,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not ForwardingPreviewView forwardingPreviewView; - private class UnreadCounterTextView extends View { - - private int currentCounter; - private String currentCounterString; - private int textWidth; - private TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - private RectF rect = new RectF(); - private int circleWidth; - private int rippleColor; - - private StaticLayout textLayout; - private StaticLayout textLayoutOut; - private int layoutTextWidth; - private TextPaint layoutPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - - Drawable selectableBackground; - - ValueAnimator replaceAnimator; - float replaceProgress = 1f; - boolean animatedFromBottom; - int textColor; - int panelBackgroundColor; - int counterColor; - CharSequence lastText; - - public UnreadCounterTextView(Context context) { - super(context); - textPaint.setTextSize(AndroidUtilities.dp(13)); - textPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - - layoutPaint.setTextSize(AndroidUtilities.dp(15)); - layoutPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - } - - public void setText(CharSequence text, boolean animatedFromBottom) { - if (lastText == text) { - return; - } - lastText = text; - this.animatedFromBottom = animatedFromBottom; - textLayoutOut = textLayout; - layoutTextWidth = (int) Math.ceil(layoutPaint.measureText(text, 0, text.length())); - textLayout = new StaticLayout(text, layoutPaint, layoutTextWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true); - setContentDescription(text); - invalidate(); - - if (textLayoutOut != null) { - if (replaceAnimator != null) { - replaceAnimator.cancel(); - } - replaceProgress = 0; - replaceAnimator = ValueAnimator.ofFloat(0,1f); - replaceAnimator.addUpdateListener(animation -> { - replaceProgress = (float) animation.getAnimatedValue(); - invalidate(); - }); - replaceAnimator.setDuration(150); - replaceAnimator.start(); - } - } - - public void setText(CharSequence text) { - layoutTextWidth = (int) Math.ceil(layoutPaint.measureText(text, 0, text.length())); - textLayout = new StaticLayout(text, layoutPaint, layoutTextWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true); - setContentDescription(text); - invalidate(); - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - if (selectableBackground != null) { - selectableBackground.setState(getDrawableState()); - } - } - - @Override - public boolean verifyDrawable(Drawable drawable) { - if (selectableBackground != null) { - return selectableBackground == drawable || super.verifyDrawable(drawable); - } - return super.verifyDrawable(drawable); - } - - @Override - public void jumpDrawablesToCurrentState() { - super.jumpDrawablesToCurrentState(); - if (selectableBackground != null) { - selectableBackground.jumpToCurrentState(); - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (textLayout != null) { - int lineWidth = (int) Math.ceil(textLayout.getLineWidth(0)); - int contentWidth; - if (getMeasuredWidth() == ((View)getParent()).getMeasuredWidth()) { - contentWidth = getMeasuredWidth() - AndroidUtilities.dp(96); - } else { - if (botInfo != null) { - contentWidth = getMeasuredWidth(); - } else { - contentWidth = lineWidth + (circleWidth > 0 ? circleWidth + AndroidUtilities.dp(8) : 0); - contentWidth += AndroidUtilities.dp(48); - } - } - int x = (getMeasuredWidth() - contentWidth) / 2; - rect.set( - x, getMeasuredHeight() / 2f - contentWidth / 2f, - x + contentWidth, getMeasuredHeight() / 2f + contentWidth / 2f - ); - if (!rect.contains(event.getX(), event.getY())) { - setPressed(false); - return false; - } - } - } - return super.onTouchEvent(event); - } - - public void updateCounter() { - int newCount; - if (ChatObject.isChannel(currentChat) && !currentChat.megagroup && chatInfo != null && chatInfo.linked_chat_id != 0) { - TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(-chatInfo.linked_chat_id); - if (dialog != null) { - newCount = dialog.unread_count; - } else { - newCount = 0; - } - } else { - newCount = 0; - } - if (currentCounter != newCount) { - currentCounter = newCount; - if (currentCounter == 0) { - currentCounterString = null; - circleWidth = 0; - } else { - currentCounterString = AndroidUtilities.formatWholeNumber(currentCounter, 0); - textWidth = (int) Math.ceil(textPaint.measureText(currentCounterString)); - int newWidth = Math.max(AndroidUtilities.dp(20), AndroidUtilities.dp(12) + textWidth); - if (circleWidth != newWidth) { - circleWidth = newWidth; - } - } - invalidate(); - } - } - - @Override - protected void onDraw(Canvas canvas) { - Layout layout = textLayout; - int color = getThemedColor(isEnabled() ? Theme.key_chat_fieldOverlayText : Theme.key_windowBackgroundWhiteGrayText); - if (textColor != color) { - layoutPaint.setColor(textColor = color); - } - color = getThemedColor(Theme.key_chat_messagePanelBackground); - if (panelBackgroundColor != color) { - textPaint.setColor(panelBackgroundColor = color); - } - color = getThemedColor(Theme.key_chat_goDownButtonCounterBackground); - if (counterColor != color) { - paint.setColor(counterColor = color); - } - - if (getParent() != null) { - int contentWidth = getMeasuredWidth(); - int x = (getMeasuredWidth() - contentWidth) / 2; - if (rippleColor != getThemedColor(Theme.key_chat_fieldOverlayText) || selectableBackground == null) { - selectableBackground = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(60), 0, ColorUtils.setAlphaComponent(rippleColor = getThemedColor(Theme.key_chat_fieldOverlayText), 26)); - selectableBackground.setCallback(this); - } - int start = (getLeft() + x) <= 0 ? x - AndroidUtilities.dp(20) : x; - int end = x + contentWidth > ((View) getParent()).getMeasuredWidth() ? x + contentWidth + AndroidUtilities.dp(20) : x + contentWidth; - selectableBackground.setBounds( - start, getMeasuredHeight() / 2 - contentWidth / 2, - end, getMeasuredHeight() / 2 + contentWidth / 2 - ); - selectableBackground.draw(canvas); - } - if (textLayout != null) { - canvas.save(); - if (replaceProgress != 1f && textLayoutOut != null) { - int oldAlpha = layoutPaint.getAlpha(); - - canvas.save(); - canvas.translate((getMeasuredWidth() - textLayoutOut.getWidth()) / 2 - circleWidth / 2, (getMeasuredHeight() - textLayout.getHeight()) / 2); - canvas.translate(0, (animatedFromBottom ? -1f : 1f) * AndroidUtilities.dp(18) * replaceProgress); - layoutPaint.setAlpha((int) (oldAlpha * (1f - replaceProgress))); - textLayoutOut.draw(canvas); - canvas.restore(); - - canvas.save(); - canvas.translate((getMeasuredWidth() - layoutTextWidth) / 2 - circleWidth / 2, (getMeasuredHeight() - textLayout.getHeight()) / 2); - canvas.translate(0, (animatedFromBottom ? 1f : -1f) * AndroidUtilities.dp(18) * (1f - replaceProgress)); - layoutPaint.setAlpha((int) (oldAlpha * (replaceProgress))); - textLayout.draw(canvas); - canvas.restore(); - - layoutPaint.setAlpha(oldAlpha); - } else { - canvas.translate((getMeasuredWidth() - layoutTextWidth) / 2 - circleWidth / 2, (getMeasuredHeight() - textLayout.getHeight()) / 2); - textLayout.draw(canvas); - } - - canvas.restore(); - } - - if (currentCounterString != null) { - if (layout != null) { - int lineWidth = (int) Math.ceil(layout.getLineWidth(0)); - int x = (getMeasuredWidth() - lineWidth) / 2 + lineWidth - circleWidth / 2 + AndroidUtilities.dp(6); - rect.set(x, getMeasuredHeight() / 2 - AndroidUtilities.dp(10), x + circleWidth, getMeasuredHeight() / 2 + AndroidUtilities.dp(10)); - canvas.drawRoundRect(rect, AndroidUtilities.dp(10), AndroidUtilities.dp(10), paint); - canvas.drawText(currentCounterString, rect.centerX() - textWidth / 2.0f, rect.top + AndroidUtilities.dp(14.5f), textPaint); - } - } - } - } - private PhotoViewer.PhotoViewerProvider photoViewerProvider = new PhotoViewer.EmptyPhotoViewerProvider() { @Override @@ -1315,8 +1174,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private final static int text_underline = 56; private final static int text_spoiler = 57; + private final static int view_as_topics = 58; + private final static int search = 40; + private final static int topic_close = 60; + private final static int id_chat_compose_panel = 1000; RecyclerListView.OnItemLongClickListenerExtended onItemLongClickListener = new RecyclerListView.OnItemLongClickListenerExtended() { @@ -1344,6 +1207,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } }; + public RecyclerListView getChatListView() { + return chatListView; + } + private void startMultiselect(int position) { int indexOfMessage = position - chatAdapter.messagesStartRow; if (indexOfMessage < 0 || indexOfMessage >= messages.size()) { @@ -1454,6 +1321,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not Bundle bundle = new Bundle(); int date = ((ChatActionCell) view).getMessageObject().messageOwner.date; bundle.putLong("dialog_id", dialog_id); + bundle.putInt("topic_id", getTopicId()); bundle.putInt("type", CalendarActivity.TYPE_CHAT_ACTIVITY); CalendarActivity calendarActivity = new CalendarActivity(bundle, SharedMediaLayout.FILTER_PHOTOS_AND_VIDEOS, date); presentFragment(calendarActivity); @@ -1488,7 +1356,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return false; } ChatMessageCell cell = (ChatMessageCell) view; - return !cell.getMessageObject().isSending() && !cell.getMessageObject().isEditing() && cell.getMessageObject().type != 16 && !actionBar.isActionModeShowed() && !isSecretChat() && !isInScheduleMode() && !cell.getMessageObject().isSponsored(); + return !cell.getMessageObject().isSending() && !cell.getMessageObject().isEditing() && cell.getMessageObject().type != MessageObject.TYPE_PHONE_CALL && !actionBar.isActionModeShowed() && !isSecretChat() && !isInScheduleMode() && !cell.getMessageObject().isSponsored(); } @Override @@ -1586,6 +1454,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not int migrated_to = arguments.getInt("migrated_to", 0); scrollToTopOnResume = arguments.getBoolean("scrollToTopOnResume", false); needRemovePreviousSameChatActivity = arguments.getBoolean("need_remove_previous_same_chat_activity", true); + if (chatId != 0) { currentChat = getMessagesController().getChat(chatId); if (currentChat == null) { @@ -1687,6 +1556,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not dialog_id_Long = dialog_id; + transitionAnimationGlobalIndex = NotificationCenter.getGlobalInstance().setAnimationInProgress(transitionAnimationGlobalIndex, new int[0]); + themeDelegate = new ThemeDelegate(); if (themeDelegate.isThemeChangeAvailable()) { NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.needSetDayNightTheme); @@ -1716,6 +1587,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not getNotificationCenter().addObserver(this, NotificationCenter.groupCallUpdated); } else { getNotificationCenter().addObserver(this, NotificationCenter.threadMessagesRead); + if (isTopic) { + getNotificationCenter().addObserver(this, NotificationCenter.updateMentionsCount); + getNotificationCenter().addObserver(this, NotificationCenter.didLoadPinnedMessages); + } } getNotificationCenter().addObserver(this, NotificationCenter.removeAllMessagesFromDialog); getNotificationCenter().addObserver(this, NotificationCenter.messagesReadContent); @@ -1768,6 +1643,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not getNotificationCenter().addObserver(this, NotificationCenter.chatInfoCantLoad); getNotificationCenter().addObserver(this, NotificationCenter.userInfoDidLoad); getNotificationCenter().addObserver(this, NotificationCenter.pinnedInfoDidLoad); + getNotificationCenter().addObserver(this, NotificationCenter.topicsDidLoaded); NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.didSetNewWallpapper); NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.didApplyNewTheme); NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.goingToPreviewTheme); @@ -1782,6 +1658,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not getNotificationCenter().addObserver(this, NotificationCenter.chatAvailableReactionsUpdated); getNotificationCenter().addObserver(this, NotificationCenter.dialogsUnreadReactionsCounterChanged); getNotificationCenter().addObserver(this, NotificationCenter.groupStickersDidLoad); + getNotificationCenter().addObserver(this, NotificationCenter.chatSwithcedToForum); super.onFragmentCreate(); @@ -1860,30 +1737,31 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not endReached[0] = endReached[1] = true; forwardEndReached[0] = forwardEndReached[1] = true; firstLoading = false; + checkDispatchHideSkeletons(false); } if (chatMode != MODE_PINNED && !forceHistoryEmpty) { waitingForLoad.add(lastLoadIndex); if (startLoadFromDate != 0) { - getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 30, 0, startLoadFromDate, true, 0, classGuid, 4, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 30, 0, startLoadFromDate, true, 0, classGuid, 4, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); } else if (startLoadFromMessageId != 0 && (!isThreadChat() || startLoadFromMessageId == highlightMessageId)) { startLoadFromMessageIdSaved = startLoadFromMessageId; if (migrated_to != 0) { mergeDialogId = migrated_to; - getMessagesController().loadMessages(mergeDialogId, 0, loadInfo, loadingFromOldPosition ? 50 : (AndroidUtilities.isTablet() || isThreadChat() ? 30 : 20), startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(mergeDialogId, 0, loadInfo, loadingFromOldPosition ? 50 : (AndroidUtilities.isTablet() || (isThreadChat() && !isTopic) ? 30 : 20), startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); } else { - getMessagesController().loadMessages(dialog_id, mergeDialogId, loadInfo, loadingFromOldPosition ? 50 : (AndroidUtilities.isTablet() || isThreadChat() ? 30 : 20), startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, mergeDialogId, loadInfo, loadingFromOldPosition ? 50 : (AndroidUtilities.isTablet() || (isThreadChat() && !isTopic) ? 30 : 20), startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); } } else { if (historyPreloaded) { lastLoadIndex++; } else { - getMessagesController().loadMessages(dialog_id, mergeDialogId, loadInfo, AndroidUtilities.isTablet() || isThreadChat() ? 30 : 20, startLoadFromMessageId, 0, true, 0, classGuid, 2, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, mergeDialogId, loadInfo, AndroidUtilities.isTablet() || (isThreadChat() && !isTopic) ? 30 : 20, startLoadFromMessageId, 0, true, 0, classGuid, 2, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); } } } if (chatMode == 0 && !isThreadChat()) { waitingForLoad.add(lastLoadIndex); - getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 1, 0, 0, true, 0, classGuid, 2, 0, MODE_SCHEDULED, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 1, 0, 0, true, 0, classGuid, 2, 0, MODE_SCHEDULED, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); } if (chatMode == 0) { @@ -1898,7 +1776,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } } - if (AndroidUtilities.isTablet()) { + if (AndroidUtilities.isTablet() && !isComments) { getNotificationCenter().postNotificationName(NotificationCenter.openedChatChanged, dialog_id, false); } @@ -1985,16 +1863,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } public int getOtherSameChatsDiff() { - if (parentLayout == null || parentLayout.fragmentsStack == null) { + if (parentLayout == null || parentLayout.getFragmentStack() == null) { return 0; } - int cur = parentLayout.fragmentsStack.indexOf(this); + int cur = parentLayout.getFragmentStack().indexOf(this); if (cur == -1) { - cur = parentLayout.fragmentsStack.size(); + cur = parentLayout.getFragmentStack().size(); } int i = cur; - for (int a = 0; a < parentLayout.fragmentsStack.size(); a++) { - BaseFragment fragment = parentLayout.fragmentsStack.get(a); + for (int a = 0; a < parentLayout.getFragmentStack().size(); a++) { + BaseFragment fragment = parentLayout.getFragmentStack().get(a); if (fragment != this && fragment instanceof ChatActivity) { ChatActivity chatActivity = (ChatActivity) fragment; if (chatActivity.dialog_id == dialog_id) { @@ -2022,6 +1900,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatAttachAlert.dismissInternal(); } getNotificationCenter().onAnimationFinish(transitionAnimationIndex); + NotificationCenter.getGlobalInstance().onAnimationFinish(transitionAnimationGlobalIndex); getNotificationCenter().onAnimationFinish(scrollAnimationIndex); getNotificationCenter().onAnimationFinish(scrollCallbackAnimationIndex); hideUndoViews(); @@ -2084,6 +1963,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not getNotificationCenter().removeObserver(this, NotificationCenter.newDraftReceived); getNotificationCenter().removeObserver(this, NotificationCenter.userInfoDidLoad); getNotificationCenter().removeObserver(this, NotificationCenter.pinnedInfoDidLoad); + getNotificationCenter().removeObserver(this, NotificationCenter.topicsDidLoaded); NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.didSetNewWallpapper); NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.didApplyNewTheme); NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.goingToPreviewTheme); @@ -2103,6 +1983,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not getNotificationCenter().removeObserver(this, NotificationCenter.didLoadSendAsPeers); getNotificationCenter().removeObserver(this, NotificationCenter.dialogsUnreadReactionsCounterChanged); getNotificationCenter().removeObserver(this, NotificationCenter.groupStickersDidLoad); + getNotificationCenter().removeObserver(this, NotificationCenter.chatSwithcedToForum); if (currentEncryptedChat != null) { getNotificationCenter().removeObserver(this, NotificationCenter.didVerifyMessagesStickers); } @@ -2149,9 +2030,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } chatThemeBottomSheet = null; - ActionBarLayout parentLayout = getParentLayout(); - if (parentLayout != null && parentLayout.fragmentsStack != null) { - BackButtonMenu.clearPulledDialogs(this, parentLayout.fragmentsStack.indexOf(this) - (replacingChatActivity ? 0 : 1)); + INavigationLayout parentLayout = getParentLayout(); + if (parentLayout != null && parentLayout.getFragmentStack() != null) { + BackButtonMenu.clearPulledDialogs(this, parentLayout.getFragmentStack().indexOf(this) - (replacingChatActivity ? 0 : 1)); } replacingChatActivity = false; } @@ -2259,6 +2140,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not finishFragment(); } } + } else if (id == view_as_topics) { + TopicsFragment.prepareToSwitchAnimation(ChatActivity.this); } else if (id == copy) { SpannableStringBuilder str = new SpannableStringBuilder(); long previousUid = 0; @@ -2346,7 +2229,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return; } AlertsCreator.createClearOrDeleteDialogAlert(ChatActivity.this, id == clear_history, currentChat, currentUser, currentEncryptedChat != null, true, canDeleteHistory, (param) -> { - if (id == clear_history && ChatObject.isChannel(currentChat) && (!currentChat.megagroup || !TextUtils.isEmpty(currentChat.username))) { + if (id == clear_history && ChatObject.isChannel(currentChat) && (!currentChat.megagroup || ChatObject.isPublic(currentChat))) { getMessagesController().deleteDialog(dialog_id, 2, param); } else { if (id != clear_history) { @@ -2470,6 +2353,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } else if (id == change_colors) { showChatThemeBottomSheet(); + } else if (id == topic_close) { + getMessagesController().getTopicsController().toggleCloseTopic(currentChat.id, forumTopic.id, forumTopic.closed = true); + updateTopicButtons(); + updateBottomOverlay(); + updateTopPanel(true); } } }); @@ -2524,6 +2412,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not avatarContainer.allowShorterStatus = true; avatarContainer.premiumIconHiddable = true; AndroidUtilities.updateViewVisibilityAnimated(avatarContainer, true, 1f, false); + updateTopicTitleIcon(); if (inPreviewMode || inBubbleMode) { avatarContainer.setOccupyStatusBar(false); } @@ -2788,7 +2677,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not editTextItem.addSubItem(text_link, LocaleController.getString("CreateLink", R.string.CreateLink)); editTextItem.addSubItem(text_regular, LocaleController.getString("Regular", R.string.Regular)); - if (chatMode == 0 && threadMessageId == 0 && !UserObject.isReplyUser(currentUser) && reportType < 0) { + if (chatMode == 0 && (threadMessageId == 0 || isTopic) && !UserObject.isReplyUser(currentUser) && reportType < 0) { TLRPC.UserFull userFull = null; if (currentUser != null) { audioCallIconItem = menu.addItem(call, R.drawable.ic_call, themeDelegate); @@ -2818,8 +2707,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void toggleSound() { SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); - boolean enabled = !preferences.getBoolean("sound_enabled_" + dialog_id, true); - preferences.edit().putBoolean("sound_enabled_" + dialog_id, enabled).apply(); + boolean enabled = !preferences.getBoolean("sound_enabled_" + NotificationsController.getSharedPrefKey(dialog_id, getTopicId()), true); + preferences.edit().putBoolean("sound_enabled_" + NotificationsController.getSharedPrefKey(dialog_id, getTopicId()), enabled).apply(); if (BulletinFactory.canShowBulletin(ChatActivity.this)) { BulletinFactory.createSoundEnabledBulletin(ChatActivity.this, enabled ? NotificationsController.SETTING_SOUND_ON : NotificationsController.SETTING_SOUND_OFF, getResourceProvider()).show(); } @@ -2829,14 +2718,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void muteFor(int timeInSeconds) { if (timeInSeconds == 0) { - if (getMessagesController().isDialogMuted(dialog_id)) { + if (getMessagesController().isDialogMuted(dialog_id, getTopicId())) { ChatActivity.this.toggleMute(true); } if (BulletinFactory.canShowBulletin(ChatActivity.this)) { BulletinFactory.createMuteBulletin(ChatActivity.this, NotificationsController.SETTING_MUTE_UNMUTE, timeInSeconds, getResourceProvider()).show(); } } else { - getNotificationsController().muteUntil(dialog_id, timeInSeconds); + getNotificationsController().muteUntil(dialog_id, getTopicId(), timeInSeconds); if (BulletinFactory.canShowBulletin(ChatActivity.this)) { BulletinFactory.createMuteBulletin(ChatActivity.this, NotificationsController.SETTING_MUTE_CUSTOM, timeInSeconds, getResourceProvider()).show(); } @@ -2851,6 +2740,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } Bundle args = new Bundle(); args.putLong("dialog_id", dialog_id); + if (getTopicId() != 0) { + args.putInt("topic_id", getTopicId()); + } presentFragment(new ProfileNotificationsActivity(args, themeDelegate)); } } @@ -2858,12 +2750,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void toggleMute() { ChatActivity.this.toggleMute(true); - BulletinFactory.createMuteBulletin(ChatActivity.this, getMessagesController().isDialogMuted(dialog_id), themeDelegate).show(); + BulletinFactory.createMuteBulletin(ChatActivity.this, getMessagesController().isDialogMuted(dialog_id, getTopicId()), themeDelegate).show(); } }, getResourceProvider()); muteItem = headerItem.addSwipeBackItem(R.drawable.msg_mute, null, null, chatNotificationsPopupWrapper.windowLayout); muteItem.setOnClickListener(view -> { - boolean muted = MessagesController.getInstance(currentAccount).isDialogMuted(dialog_id); + boolean muted = MessagesController.getInstance(currentAccount).isDialogMuted(dialog_id, getTopicId()); if (muted) { updateTitleIcons(true); AndroidUtilities.runOnUIThread(() -> { @@ -2907,25 +2799,30 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (currentEncryptedChat != null) { timeItem2 = headerItem.addSubItem(chat_enc_timer, R.drawable.msg_autodelete, LocaleController.getString("SetTimer", R.string.SetTimer), themeDelegate); } - - clearHistoryItem = headerItem.addSubItem(clear_history, R.drawable.msg_clear, LocaleController.getString("ClearHistory", R.string.ClearHistory), themeDelegate); - + if (currentChat != null && !isTopic) { + viewAsTopics = headerItem.addSubItem(view_as_topics, R.drawable.msg_topics, LocaleController.getString("TopicViewAsTopics", R.string.TopicViewAsTopics), themeDelegate); + } + if (!isTopic) { + clearHistoryItem = headerItem.addSubItem(clear_history, R.drawable.msg_clear, LocaleController.getString("ClearHistory", R.string.ClearHistory), themeDelegate); + } if (themeDelegate.isThemeChangeAvailable()) { headerItem.addSubItem(change_colors, R.drawable.msg_colors, LocaleController.getString("ChangeColors", R.string.ChangeColors), themeDelegate); } - if (ChatObject.isChannel(currentChat) && !currentChat.creator) { - if (!ChatObject.isNotInChat(currentChat)) { - if (currentChat.megagroup) { - headerItem.addSubItem(delete_chat, R.drawable.msg_leave, LocaleController.getString("LeaveMegaMenu", R.string.LeaveMegaMenu), themeDelegate); - } else { - headerItem.addSubItem(delete_chat, R.drawable.msg_leave, LocaleController.getString("LeaveChannelMenu", R.string.LeaveChannelMenu), themeDelegate); + if (!isTopic) { + if (ChatObject.isChannel(currentChat) && !currentChat.creator) { + if (!ChatObject.isNotInChat(currentChat)) { + if (currentChat.megagroup) { + headerItem.addSubItem(delete_chat, R.drawable.msg_leave, LocaleController.getString("LeaveMegaMenu", R.string.LeaveMegaMenu), themeDelegate); + } else { + headerItem.addSubItem(delete_chat, R.drawable.msg_leave, LocaleController.getString("LeaveChannelMenu", R.string.LeaveChannelMenu), themeDelegate); + } + } + } else if (!ChatObject.isChannel(currentChat)) { + if (currentChat != null) { + headerItem.addSubItem(delete_chat, R.drawable.msg_leave, LocaleController.getString("DeleteAndExit", R.string.DeleteAndExit), themeDelegate); + } else { + headerItem.addSubItem(delete_chat, R.drawable.msg_delete, LocaleController.getString("DeleteChatUser", R.string.DeleteChatUser), themeDelegate); } - } - } else if (!ChatObject.isChannel(currentChat)) { - if (currentChat != null) { - headerItem.addSubItem(delete_chat, R.drawable.msg_leave, LocaleController.getString("DeleteAndExit", R.string.DeleteAndExit), themeDelegate); - } else { - headerItem.addSubItem(delete_chat, R.drawable.msg_delete, LocaleController.getString("DeleteChatUser", R.string.DeleteChatUser), themeDelegate); } } if (currentUser != null && currentUser.self) { @@ -2937,6 +2834,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not updateBotButtons(); } } + if (currentChat != null && forumTopic != null) { + closeTopicItem = headerItem.addSubItem(topic_close, R.drawable.msg_topic_close, LocaleController.getString("CloseTopic", R.string.CloseTopic), themeDelegate); + closeTopicItem.setVisibility(currentChat != null && ChatObject.canManageTopic(currentAccount, currentChat, forumTopic) && forumTopic != null && !forumTopic.closed ? View.VISIBLE : View.GONE); + } menu.setVisibility(inMenuMode ? View.GONE : View.VISIBLE); updateTitle(false); @@ -2944,7 +2845,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not avatarContainer.updateSubtitle(); updateTitleIcons(); - if (chatMode == 0 && !isThreadChat() && reportType < 0) { + if (chatMode == 0 && (!isThreadChat() || isTopic) && reportType < 0) { attachItem = menu.addItem(chat_menu_attach, R.drawable.ic_ab_other, themeDelegate).setOverrideMenuClick(true).setAllowCloseAnimation(false); attachItem.setContentDescription(LocaleController.getString("AccDescrMoreOptions", R.string.AccDescrMoreOptions)); attachItem.setVisibility(View.GONE); @@ -3035,6 +2936,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not ((ChatMessageCell) child).drawOutboundsContent(blurCanvas); } cell.drawForBlur = false; + } else if (child instanceof ChatActionCell) { + child.draw(blurCanvas); + ((ChatActionCell) child).drawOutboundsContent(blurCanvas); } else { child.draw(blurCanvas); } @@ -3113,11 +3017,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (mentionContainer != null) { mentionContainer.onPanTransitionUpdate(y); } + if (AndroidUtilities.isTablet() && getParentActivity() instanceof LaunchActivity) { + BaseFragment mainFragment = ((LaunchActivity)getParentActivity()).getActionBarLayout().getLastFragment(); + if (mainFragment instanceof DialogsActivity) { + ((DialogsActivity)mainFragment).setPanTranslationOffset(y); + } + } } @Override protected boolean heightAnimationEnabled() { - ActionBarLayout actionBarLayout = getParentLayout(); + INavigationLayout actionBarLayout = getParentLayout(); if (inPreviewMode || inBubbleMode || AndroidUtilities.isInMultiwindow || actionBarLayout == null || fixedKeyboardHeight > 0) { return false; } @@ -3288,6 +3198,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (toPullingDownTransition && child == chatListView) { return true; } + if (switchingFromTopics && child == actionBar) { + return true; + } if (getTag(BlurBehindDrawable.TAG_DRAWING_AS_BACKGROUND) != null) { boolean needBlur; if (((int) getTag(BlurBehindDrawable.TAG_DRAWING_AS_BACKGROUND)) == BlurBehindDrawable.STATIC_CONTENT) { @@ -3340,7 +3253,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not canvas.restore(); } else { result = super.drawChild(canvas, child, drawingTime); - if (isVideo && child == chatListView && messageObject.type != 5 && videoPlayerContainer != null && videoPlayerContainer.getTag() != null) { + if (isVideo && child == chatListView && messageObject.type != MessageObject.TYPE_ROUND_VIDEO && videoPlayerContainer != null && videoPlayerContainer.getTag() != null) { canvas.save(); float transitionOffset = 0; if (pullingDownAnimateProgress != 0) { @@ -3407,6 +3320,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } updateTextureViewPosition(false); updatePagedownButtonsPosition(); + int restoreToCount = -1; + if (switchingFromTopics) { + restoreToCount = canvas.saveLayerAlpha(0, actionBar.getBottom(), getMeasuredWidth(), getMeasuredHeight(), (int) (255 * switchingFromTopicsProgress), Canvas.ALL_SAVE_FLAG); + float s = 0.8f + 0.2f * switchingFromTopicsProgress; + canvas.scale(s, s, getMeasuredWidth() / 2f, getMeasuredHeight() / 2f); + } super.dispatchDraw(canvas); if (fragmentContextView != null && fragmentContextView.isCallStyle()) { float alpha = (blurredView != null && blurredView.getVisibility() == View.VISIBLE) ? 1f - blurredView.getAlpha() : 1f; @@ -3422,6 +3341,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not fragmentContextView.setDrawOverlay(false); canvas.restore(); } + fragmentView.invalidate(); } if (chatActivityEnterView != null) { if (chatActivityEnterView.panelAnimationInProgress() && chatActivityEnterView.getEmojiPadding() < bottomPanelTranslationY) { @@ -3493,14 +3413,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not MessageObject.GroupedMessages group; MessageObject.GroupedMessagePosition position; ChatMessageCell cell; + ChatActionCell actionCell; if (child instanceof ChatMessageCell) { cell = (ChatMessageCell) child; + actionCell = null; group = cell.getCurrentMessagesGroup(); position = cell.getCurrentPosition(); } else { position = null; group = null; cell = null; + actionCell = child instanceof ChatActionCell ? ((ChatActionCell) child) : null; } if (child != scrimView && (scrimGroup == null || scrimGroup != group) || child.getAlpha() == 0f) { continue; @@ -3560,7 +3483,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (cell == null || !cell.getTransitionParams().animateBackgroundBoundsInner) { viewClipLeft = Math.max(viewClipLeft, chatListView.getLeft() + child.getX()); - viewClipTop = Math.max(viewClipTop, chatListView.getTop() + child.getY()); + viewClipTop = Math.max(viewClipTop, chatListView.getY() + child.getY()); viewClipRight = Math.min(viewClipRight, chatListView.getLeft() + child.getX() + child.getMeasuredWidth()); viewClipBottom = Math.min(viewClipBottom, chatListView.getY() + child.getY() + child.getMeasuredHeight()); } @@ -3584,6 +3507,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (cell != null && cell.hasOutboundsContent()) { cell.drawOutboundsContent(canvas); } + if (actionCell != null) { + actionCell.drawOutboundsContent(canvas); + } canvas.restore(); @@ -3719,6 +3645,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } emojiAnimationsOverlay.draw(canvas); + + if (restoreToCount >= 0) { + canvas.restore(); + } + if (switchingFromTopics) { + canvas.save(); + canvas.translate(actionBar.getX(), actionBar.getY()); + canvas.saveLayerAlpha(0, 0, actionBar.getWidth(), actionBar.getHeight(), (int) (255 * switchingFromTopicsProgress), Canvas.ALL_SAVE_FLAG); + actionBar.draw(canvas); + canvas.restore(); + canvas.restore(); + } } @Override @@ -4191,7 +4129,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not greetingsViewContainer.setBackground(Theme.createServiceDrawable(AndroidUtilities.dp(10), greetingsViewContainer, contentView, getThemedPaint(Theme.key_paint_chatActionBackground))); emptyViewContainer.addView(greetingsViewContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 68, 0, 68, 0)); } else if (currentEncryptedChat == null) { - if (!isThreadChat() && chatMode == 0 && ((currentUser != null && currentUser.self) || (currentChat != null && currentChat.creator && !ChatObject.isChannelAndNotMegaGroup(currentChat)))) { + if (isTopic) { + CreateTopicEmptyView createTopicEmptyView = new CreateTopicEmptyView(context, contentView, themeDelegate); + emptyViewContainer.addView(createTopicEmptyView, new FrameLayout.LayoutParams(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); + } else if (!isThreadChat() && chatMode == 0 && ((currentUser != null && currentUser.self) || (currentChat != null && currentChat.creator && !ChatObject.isChannelAndNotMegaGroup(currentChat)))) { bigEmptyView = new ChatBigEmptyView(context, contentView, currentChat != null ? ChatBigEmptyView.EMPTY_VIEW_TYPE_GROUP : ChatBigEmptyView.EMPTY_VIEW_TYPE_SAVED, themeDelegate); emptyViewContainer.addView(bigEmptyView, new FrameLayout.LayoutParams(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); if (currentChat != null) { @@ -4266,7 +4207,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private final ArrayList drawCaptionAfter = new ArrayList<>(); private final ArrayList drawingGroups = new ArrayList<>(10); - private boolean slideAnimationInProgress; private int startedTrackingX; private int startedTrackingY; private int startedTrackingPointerId; @@ -4274,14 +4214,51 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private float trackAnimationProgress; private float endTrackingX; private boolean wasTrackingVibrate; - private float replyButtonProgress; - private long lastReplyButtonAnimationTime; + + private float springMultiplier = 2000f; + + private Paint outlineActionBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Paint outlineActionBackgroundDarkenPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + private FloatValueHolder slidingDrawableVisibilityProgress = new FloatValueHolder(0); + private SpringAnimation slidingDrawableVisibilitySpring = new SpringAnimation(slidingDrawableVisibilityProgress) + .setMinValue(0f) + .setMaxValue(springMultiplier) + .setSpring(new SpringForce(0) + .setStiffness(SpringForce.STIFFNESS_MEDIUM) + .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)) + .addUpdateListener((animation, value, velocity) -> invalidate()); + private FloatValueHolder slidingFillProgress = new FloatValueHolder(0); + private SpringAnimation slidingFillProgressSpring = new SpringAnimation(slidingFillProgress) + .setMinValue(0f) + .setSpring(new SpringForce(0) + .setStiffness(400f) + .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)) + .addUpdateListener((animation, value, velocity) -> invalidate()); + private FloatValueHolder slidingOuterRingProgress = new FloatValueHolder(0); + private SpringAnimation slidingOuterRingSpring = new SpringAnimation(slidingOuterRingProgress) + .setMinValue(0f) + .setSpring(new SpringForce(0) + .setStiffness(200f) + .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)) + .addUpdateListener((animation, value, velocity) -> invalidate()); + private boolean slidingBeyondMax; + private Path path = new Path(); private boolean ignoreLayout; private boolean invalidated; int lastH = 0; + { + outlineActionBackgroundPaint.setStyle(Paint.Style.STROKE); + outlineActionBackgroundPaint.setStrokeCap(Paint.Cap.ROUND); + outlineActionBackgroundPaint.setStrokeWidth(AndroidUtilities.dp(2)); + outlineActionBackgroundDarkenPaint.setStyle(Paint.Style.STROKE); + outlineActionBackgroundDarkenPaint.setStrokeCap(Paint.Cap.ROUND); + outlineActionBackgroundDarkenPaint.setStrokeWidth(AndroidUtilities.dp(2)); + } + @Override public void requestLayout() { if (ignoreLayout) { @@ -4388,66 +4365,183 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (slidingView == null) { return; } - float translationX = slidingView.getNonAnimationTranslationX(false); - long newTime = System.currentTimeMillis(); - long dt = Math.min(17, newTime - lastReplyButtonAnimationTime); - lastReplyButtonAnimationTime = newTime; - boolean showing; - if (showing = (translationX <= -AndroidUtilities.dp(50))) { - if (replyButtonProgress < 1.0f) { - replyButtonProgress += dt / 180.0f; - if (replyButtonProgress > 1.0f) { - replyButtonProgress = 1.0f; - } else { - invalidate(); - } - } - } else { - if (replyButtonProgress > 0.0f) { - replyButtonProgress -= dt / 180.0f; - if (replyButtonProgress < 0.0f) { - replyButtonProgress = 0; - } else { - invalidate(); - } - } - } - int alpha; - int alpha2; Paint chatActionBackgroundPaint = getThemedPaint(Theme.key_paint_chatActionBackground); - int oldAlpha = chatActionBackgroundPaint.getAlpha(); - float scale; - if (showing) { - if (replyButtonProgress <= 0.8f) { - scale = 1.2f * (replyButtonProgress / 0.8f); - } else { - scale = 1.2f - 0.2f * ((replyButtonProgress - 0.8f) / 0.2f); - } - alpha = (int) Math.min(255, 255 * (replyButtonProgress / 0.8f)); - alpha2 = (int) Math.min(oldAlpha, oldAlpha * (replyButtonProgress / 0.8f)); + Paint chatActionBackgroundDarkenPaint = Theme.chat_actionBackgroundGradientDarkenPaint; + if (outlineActionBackgroundPaint.getColor() != chatActionBackgroundPaint.getColor()) { + outlineActionBackgroundPaint.setColor(chatActionBackgroundPaint.getColor()); + } + if (outlineActionBackgroundDarkenPaint.getColor() != chatActionBackgroundDarkenPaint.getColor()) { + outlineActionBackgroundDarkenPaint.setColor(chatActionBackgroundDarkenPaint.getColor()); + } + if (outlineActionBackgroundPaint.getShader() != chatActionBackgroundPaint.getShader()) { + outlineActionBackgroundPaint.setShader(chatActionBackgroundPaint.getShader()); + } + if (outlineActionBackgroundDarkenPaint.getShader() != chatActionBackgroundDarkenPaint.getShader()) { + outlineActionBackgroundDarkenPaint.setShader(chatActionBackgroundDarkenPaint.getShader()); + } + + float fillProgress = slidingFillProgress.getValue() / springMultiplier; + int wasDarkenColor = outlineActionBackgroundDarkenPaint.getColor(); + + if (fillProgress > 1) { + slidingBeyondMax = true; + } + + float translationX = slidingView.getNonAnimationTranslationX(false); + if (slidingDrawableVisibilityProgress.getValue() == 0) { + slidingFillProgressSpring.cancel(); + slidingFillProgressSpring.getSpring().setFinalPosition(0); + slidingFillProgress.setValue(0f); + slidingOuterRingSpring.cancel(); + slidingOuterRingSpring.getSpring().setFinalPosition(0); + slidingOuterRingProgress.setValue(0f); + slidingBeyondMax = false; + } + float progress; + if (slidingFillProgressSpring.getSpring().getFinalPosition() != springMultiplier) { + progress = androidx.core.math.MathUtils.clamp((-translationX - AndroidUtilities.dp(20)) / AndroidUtilities.dp(30), 0, 1); } else { - scale = replyButtonProgress; - alpha = (int) Math.min(255, 255 * replyButtonProgress); - alpha2 = (int) Math.min(oldAlpha, oldAlpha * replyButtonProgress); + progress = 1f; } - chatActionBackgroundPaint.setAlpha(alpha2); - float x = getMeasuredWidth() + slidingView.getNonAnimationTranslationX(false) / 2; - float y = slidingView.getTop() + slidingView.getMeasuredHeight() / 2; + if (progress == 1f && slidingFillProgressSpring.getSpring().getFinalPosition() != springMultiplier) { + slidingFillProgressSpring.getSpring().setFinalPosition(springMultiplier); + slidingFillProgressSpring.start(); + + slidingOuterRingSpring.getSpring().setFinalPosition(springMultiplier); + slidingOuterRingSpring.start(); + } + + boolean visible = translationX <= -AndroidUtilities.dp(20); + float endVisibleValue = visible ? springMultiplier : 0; + if (endVisibleValue != slidingDrawableVisibilitySpring.getSpring().getFinalPosition()) { + slidingDrawableVisibilitySpring.getSpring().setFinalPosition(endVisibleValue); + if (!slidingDrawableVisibilitySpring.isRunning()) { + slidingDrawableVisibilitySpring.start(); + } + } + + float iconProgress = slidingDrawableVisibilityProgress.getValue() / springMultiplier; + float x = getMeasuredWidth() + translationX * (slidingView != null && slidingView.getMessageObject() != null && slidingView.getMessageObject().isOut() ? 0.5f : 1f); + float y = slidingView.getTop() + slidingView.getMeasuredHeight() / 2f; + float scale = slidingBeyondMax ? fillProgress : iconProgress; + + float clearScale = slidingBeyondMax ? 0f : 1f - fillProgress; + + boolean isDark = ColorUtils.calculateLuminance(getThemedColor(Theme.key_windowBackgroundWhite)) <= 0.5f; + if (iconProgress != 0) { + AndroidUtilities.rectTmp.set((int) (x - AndroidUtilities.dp(16) * scale + outlineActionBackgroundPaint.getStrokeWidth() / 2f), (int) (y - AndroidUtilities.dp(16) * scale + outlineActionBackgroundPaint.getStrokeWidth() / 2f), (int) (x + AndroidUtilities.dp(16) * scale - outlineActionBackgroundPaint.getStrokeWidth() / 2f), (int) (y + AndroidUtilities.dp(16) * scale - outlineActionBackgroundPaint.getStrokeWidth() / 2f)); + Theme.applyServiceShaderMatrix(getMeasuredWidth(), AndroidUtilities.displaySize.y, 0, getY() + AndroidUtilities.rectTmp.top); + if (fillProgress == 0) { + int outlineAlpha = outlineActionBackgroundPaint.getAlpha(); + outlineActionBackgroundPaint.setAlpha((int) (outlineAlpha * iconProgress)); + canvas.drawArc(AndroidUtilities.rectTmp, -90, 360 * progress, false, outlineActionBackgroundPaint); + outlineActionBackgroundPaint.setAlpha(outlineAlpha); + + if (themeDelegate.hasGradientService()) { + outlineAlpha = outlineActionBackgroundDarkenPaint.getAlpha(); + if (isDark) { + outlineActionBackgroundDarkenPaint.setColor(Color.WHITE); + } + outlineActionBackgroundDarkenPaint.setAlpha((int) (outlineAlpha * iconProgress)); + canvas.drawArc(AndroidUtilities.rectTmp, -90, 360 * progress, false, outlineActionBackgroundDarkenPaint); + } + } + } AndroidUtilities.rectTmp.set((int) (x - AndroidUtilities.dp(16) * scale), (int) (y - AndroidUtilities.dp(16) * scale), (int) (x + AndroidUtilities.dp(16) * scale), (int) (y + AndroidUtilities.dp(16) * scale)); - Theme.applyServiceShaderMatrix(getMeasuredWidth(), AndroidUtilities.displaySize.y, 0, getY() + AndroidUtilities.rectTmp.top); - canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(16), AndroidUtilities.dp(16), chatActionBackgroundPaint); - if (themeDelegate.hasGradientService()) { - canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(16), AndroidUtilities.dp(16), Theme.chat_actionBackgroundGradientDarkenPaint); - } - chatActionBackgroundPaint.setAlpha(oldAlpha); + path.rewind(); + path.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(16) * scale, AndroidUtilities.dp(16) * scale, Path.Direction.CW); + int wasAlpha = chatActionBackgroundPaint.getAlpha(); + chatActionBackgroundPaint.setAlpha((int) (iconProgress * 0.6f * progress * wasAlpha)); + canvas.drawPath(path, chatActionBackgroundPaint); + chatActionBackgroundPaint.setAlpha(wasAlpha); + + if (themeDelegate.hasGradientService()) { + wasAlpha = Theme.chat_actionBackgroundGradientDarkenPaint.getAlpha(); + if (isDark) { + Theme.chat_actionBackgroundGradientDarkenPaint.setColor(Color.WHITE); + } + Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha((int) (iconProgress * 0.6f * progress * wasAlpha)); + canvas.drawPath(path, Theme.chat_actionBackgroundGradientDarkenPaint); + Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha(wasAlpha); + } + + if (clearScale != 0f) { + AndroidUtilities.rectTmp.set((int) (x - AndroidUtilities.dp(16) * clearScale), (int) (y - AndroidUtilities.dp(16) * clearScale), (int) (x + AndroidUtilities.dp(16) * clearScale), (int) (y + AndroidUtilities.dp(16) * clearScale)); + path.rewind(); + path.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(16), AndroidUtilities.dp(16), Path.Direction.CW); + + canvas.save(); + canvas.clipPath(path, Region.Op.DIFFERENCE); + } + + AndroidUtilities.rectTmp.set((int) (x - AndroidUtilities.dp(16) * scale), (int) (y - AndroidUtilities.dp(16) * scale), (int) (x + AndroidUtilities.dp(16) * scale), (int) (y + AndroidUtilities.dp(16) * scale)); + Theme.applyServiceShaderMatrix(getMeasuredWidth(), AndroidUtilities.displaySize.y, 0, getY() + AndroidUtilities.rectTmp.top); + path.rewind(); + path.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(16) * scale, AndroidUtilities.dp(16) * scale, Path.Direction.CW); + + wasAlpha = chatActionBackgroundPaint.getAlpha(); + chatActionBackgroundPaint.setAlpha((int) (iconProgress * 0.4f * wasAlpha)); + canvas.drawPath(path, chatActionBackgroundPaint); + chatActionBackgroundPaint.setAlpha(wasAlpha); + + if (themeDelegate.hasGradientService()) { + wasAlpha = Theme.chat_actionBackgroundGradientDarkenPaint.getAlpha(); + if (isDark) { + Theme.chat_actionBackgroundGradientDarkenPaint.setColor(Color.WHITE); + } + Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha((int) (iconProgress * 0.4f * wasAlpha)); + canvas.drawPath(path, Theme.chat_actionBackgroundGradientDarkenPaint); + Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha(wasAlpha); + } + if (clearScale != 0f) { + canvas.restore(); + } + + float outerRingProgress = slidingOuterRingProgress.getValue() / springMultiplier; + if (outerRingProgress != 0 && outerRingProgress != 1) { + float outScale = 1f + outerRingProgress; + + float wasWidth = outlineActionBackgroundPaint.getStrokeWidth(); + float width = (1f - outerRingProgress) * wasWidth; + if (width != 0f) { + AndroidUtilities.rectTmp.set((int) (x - AndroidUtilities.dp(16) * outScale + width), (int) (y - AndroidUtilities.dp(16) * outScale + width), (int) (x + AndroidUtilities.dp(16) * outScale - width), (int) (y + AndroidUtilities.dp(16) * outScale - width)); + Theme.applyServiceShaderMatrix(getMeasuredWidth(), AndroidUtilities.displaySize.y, 0, getY() + AndroidUtilities.rectTmp.top); + + wasAlpha = outlineActionBackgroundPaint.getAlpha(); + outlineActionBackgroundPaint.setAlpha((int) (wasAlpha * iconProgress)); + + outlineActionBackgroundPaint.setStrokeWidth(width); + canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(16) * outScale, AndroidUtilities.dp(16) * outScale, outlineActionBackgroundPaint); + outlineActionBackgroundPaint.setStrokeWidth(wasWidth); + + outlineActionBackgroundPaint.setAlpha(wasAlpha); + + if (themeDelegate.hasGradientService()) { + wasAlpha = outlineActionBackgroundDarkenPaint.getAlpha(); + if (isDark) { + outlineActionBackgroundDarkenPaint.setColor(Color.WHITE); + } + outlineActionBackgroundDarkenPaint.setAlpha((int) (wasAlpha * iconProgress)); + + outlineActionBackgroundDarkenPaint.setStrokeWidth(width); + canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(16) * outScale, AndroidUtilities.dp(16) * outScale, outlineActionBackgroundDarkenPaint); + outlineActionBackgroundDarkenPaint.setStrokeWidth(wasWidth); + } + } + } + + int alpha = (int) (iconProgress * 0xFF); Drawable replyIconDrawable = getThemedDrawable(Theme.key_drawable_replyIcon); replyIconDrawable.setAlpha(alpha); replyIconDrawable.setBounds((int) (x - AndroidUtilities.dp(7) * scale), (int) (y - AndroidUtilities.dp(6) * scale), (int) (x + AndroidUtilities.dp(7) * scale), (int) (y + AndroidUtilities.dp(5) * scale)); replyIconDrawable.draw(canvas); replyIconDrawable.setAlpha(255); + + outlineActionBackgroundDarkenPaint.setColor(wasDarkenColor); + chatActionBackgroundDarkenPaint.setColor(wasDarkenColor); } private void processTouchEvent(MotionEvent e) { @@ -4465,7 +4559,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (chatMode != 0 || threadMessageObjects != null && threadMessageObjects.contains(message) || getMessageType(message) == 1 && (message.getDialogId() == mergeDialogId || message.needDrawBluredPreview()) || currentEncryptedChat == null && message.getId() < 0 || - bottomOverlayChat != null && bottomOverlayChat.getVisibility() == View.VISIBLE || + bottomOverlayChat != null && bottomOverlayChat.getVisibility() == View.VISIBLE && !(bottomOverlayChatWaitsReply && message != null && (MessageObject.getTopicId(message.messageOwner) != 0 || message.wasJustSent)) || currentChat != null && (ChatObject.isNotInChat(currentChat) && !isThreadChat() || ChatObject.isChannel(currentChat) && !ChatObject.canPost(currentChat) && !currentChat.megagroup || !ChatObject.canSendMessages(currentChat)) || textSelectionHelper.isSelectionMode()) { slidingView.setSlidingOffset(0); @@ -4703,6 +4797,217 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + private boolean isSkeletonVisible() { + if (justCreatedTopic) { + return false; + } + int childHeight = 0; + int maxTop = Integer.MAX_VALUE; + for (int i = 0; i < getChildCount(); i++) { + childHeight += getChildAt(i).getHeight(); + int top = getChildAt(i).getTop(); + if (top < maxTop) { + maxTop = top; + } + } + if (maxTop <= 0) { + checkDispatchHideSkeletons(true); + } + + boolean visible = (!endReached[0] || mergeDialogId != 0 && !endReached[1] || messages.isEmpty()) && loading && maxTop > 0 && (messages.isEmpty() ? animateProgressViewTo : childHeight != 0); + if (!visible && startMessageAppearTransitionMs == 0) { + startMessageAppearTransitionMs = System.currentTimeMillis(); + } + return visible || startMessageAppearTransitionMs != 0 && System.currentTimeMillis() - startMessageAppearTransitionMs <= SKELETON_DISAPPEAR_MS; + } + + @Override + public void draw(Canvas canvas) { + if ((startMessageAppearTransitionMs == 0 || System.currentTimeMillis() - startMessageAppearTransitionMs <= SKELETON_DISAPPEAR_MS) && !AndroidUtilities.isTablet() && !isComments && currentUser == null) { + boolean noAvatar = currentChat == null || ChatObject.isChannelAndNotMegaGroup(currentChat); + if (pullingDownOffset != 0) { + canvas.save(); + canvas.translate(0, -pullingDownOffset); + } + updateSkeletonColors(); + updateSkeletonGradient(); + + int lastTop = getHeight() - blurredViewBottomOffset; + int j = 0; + + int childMaxTop = Integer.MAX_VALUE; + for (int i = 0; i < getChildCount(); i++) { + int top = getChildAt(i).getTop(); + if (top < childMaxTop) { + childMaxTop = top; + } + } + if (startMessageAppearTransitionMs == 0 && childMaxTop <= 0) { + startMessageAppearTransitionMs = System.currentTimeMillis(); + } + + Paint servicePaint = getThemedPaint(Theme.key_paint_chatActionBackground); + + for (int i = 0; i < getChildCount(); i++) { + View v = getChildAt(i); +// if (v instanceof ChatMessageCell) { +// ChatMessageCell cell = (ChatMessageCell) v; +// if ((cell.getCurrentMessagesGroup() == null || cell.getCurrentMessagesGroup().findPrimaryMessageObject() == cell.getMessageObject())) { +// if (cell.shouldDrawAlphaLayer() || System.currentTimeMillis() - startMessageAppearTransitionMs >= SKELETON_DISAPPEAR_MS) { +// float progress = cell.getAlpha(); +// +// MessageSkeleton skeleton; +// if (j >= messageSkeletons.size()) { +// skeleton = getNewSkeleton(noAvatar); +// messageSkeletons.add(skeleton); +// } else { +// skeleton = messageSkeletons.get(j); +// } +// +// Rect bounds = cell.getCurrentBackgroundDrawable(true).getBounds(); +// MessageObject.GroupedMessages group = cell.getCurrentMessagesGroup(); +// +// int alpha = skeletonPaint.getAlpha(); +// int wasServiceAlpha = servicePaint.getAlpha(); +// servicePaint.setAlpha((int) (wasServiceAlpha * 0.4f * (1f - progress))); +// skeletonPaint.setAlpha((int) (alpha * (1f - progress))); +// int bottom = (int) AndroidUtilities.lerp(Math.min(skeleton.lastBottom, lastTop - AndroidUtilities.dp(3f)), v.getBottom() + (group != null ? group.transitionParams.top + group.transitionParams.offsetTop : 0), progress); +// int left = noAvatar ? AndroidUtilities.dp(3f) : AndroidUtilities.dp(51); +// int top = (int) AndroidUtilities.lerp(bottom - skeleton.height, bounds.top + v.getTop() + (group != null ? group.transitionParams.top + group.transitionParams.offsetTop : 0), progress); +// int right = skeleton.width; +// +// boolean lerp = cell.getMessageObject() == null || !cell.getMessageObject().isOut(); +// skeletonBackgroundDrawable.setBounds(lerp ? AndroidUtilities.lerp(left, cell.getBackgroundDrawableLeft(), progress) : left, top, +// lerp ? AndroidUtilities.lerp(right, cell.getBackgroundDrawableRight(), progress) : right, bottom); +// Theme.applyServiceShaderMatrix(getMeasuredWidth(), AndroidUtilities.displaySize.y, 0, getY() + skeletonBackgroundDrawable.getBounds().top); +// skeletonBackgroundDrawable.drawCached(canvas, skeletonBackgroundCacheParams, servicePaint); +// skeletonBackgroundDrawable.drawCached(canvas, skeletonBackgroundCacheParams, skeletonPaint); +// if (!noAvatar) { +// Theme.applyServiceShaderMatrix(getMeasuredWidth(), AndroidUtilities.displaySize.y, 0, getY() + bottom - AndroidUtilities.dp(42)); +// canvas.drawCircle(AndroidUtilities.dp(48 - 21), bottom - AndroidUtilities.dp(21), AndroidUtilities.dp(21), servicePaint); +// canvas.drawCircle(AndroidUtilities.dp(48 - 21), bottom - AndroidUtilities.dp(21), AndroidUtilities.dp(21), skeletonPaint); +// } +// servicePaint.setAlpha(wasServiceAlpha); +// skeletonPaint.setAlpha(alpha); +// j++; +// +// if (top < lastTop) { +// lastTop = top; +// } +// +// continue; +// } +// j++; +// } +// } + if (v instanceof ChatMessageCell) { + MessageObject.GroupedMessages group = ((ChatMessageCell) v).getCurrentMessagesGroup(); + Rect bounds = ((ChatMessageCell) v).getCurrentBackgroundDrawable(true).getBounds(); + int newTop = (int) (v.getTop() + bounds.top + (group != null ? group.transitionParams.top + group.transitionParams.offsetTop : 0)); + int top = messages.size() <= 2 && isSkeletonVisible() ? AndroidUtilities.lerp(lastTop, newTop, v.getAlpha()) : v.getAlpha() == 1f ? newTop : lastTop; + if (top < lastTop) { + lastTop = top; + } + } else if (v instanceof ChatActionCell) { + int top = messages.size() <= 2 && isSkeletonVisible() ? AndroidUtilities.lerp(lastTop, v.getTop(), v.getAlpha()) : v.getAlpha() == 1f ? v.getTop() : lastTop; + if (top < lastTop) { + lastTop = top; + } + } + } + + if (isSkeletonVisible()) { + float topSkeletonAlpha = startMessageAppearTransitionMs != 0 ? 1f - (System.currentTimeMillis() - startMessageAppearTransitionMs) / (float) SKELETON_DISAPPEAR_MS : 1f; + int alpha = skeletonPaint.getAlpha(); + int wasServiceAlpha = servicePaint.getAlpha(); + servicePaint.setAlpha((int) (wasServiceAlpha * 0.4f * topSkeletonAlpha)); + skeletonPaint.setAlpha((int) (topSkeletonAlpha * alpha)); + while (lastTop > blurredViewTopOffset) { + lastTop -= AndroidUtilities.dp(3f); + + MessageSkeleton skeleton; + if (j >= messageSkeletons.size()) { + skeleton = getNewSkeleton(noAvatar); + messageSkeletons.add(skeleton); + } else { + skeleton = messageSkeletons.get(j); + } + skeleton.lastBottom = startMessageAppearTransitionMs != 0 ? skeleton.lastBottom : lastTop; + + lastTop -= skeleton.height; + + j++; + } + + if (startMessageAppearTransitionMs == 0) { + for (int i = 0; i < (messages.size() - lastSkeletonMessageCount) && !messageSkeletons.isEmpty(); i++) { + messageSkeletons.remove(0); + } + } + + for (int i = 0; i < messageSkeletons.size(); i++) { + MessageSkeleton skeleton = messageSkeletons.get(i); + + int bottom = skeleton.lastBottom; + skeletonBackgroundDrawable.setBounds(noAvatar ? AndroidUtilities.dp(3f) : AndroidUtilities.dp(51), bottom - skeleton.height, skeleton.width, bottom); + Theme.applyServiceShaderMatrix(getMeasuredWidth(), AndroidUtilities.displaySize.y, 0, getY() + skeletonBackgroundDrawable.getBounds().top); + skeletonBackgroundDrawable.drawCached(canvas, skeletonBackgroundCacheParams, servicePaint); + skeletonBackgroundDrawable.drawCached(canvas, skeletonBackgroundCacheParams, skeletonPaint); + + if (!noAvatar) { + Theme.applyServiceShaderMatrix(getMeasuredWidth(), AndroidUtilities.displaySize.y, 0, getY() + skeleton.lastBottom - AndroidUtilities.dp(42)); + canvas.drawCircle(AndroidUtilities.dp(48 - 21), skeleton.lastBottom - AndroidUtilities.dp(21), AndroidUtilities.dp(21), servicePaint); + canvas.drawCircle(AndroidUtilities.dp(48 - 21), skeleton.lastBottom - AndroidUtilities.dp(21), AndroidUtilities.dp(21), skeletonPaint); + } + } + + servicePaint.setAlpha(wasServiceAlpha); + skeletonPaint.setAlpha(alpha); + invalidate(); + } else if (System.currentTimeMillis() - startMessageAppearTransitionMs > SKELETON_DISAPPEAR_MS) { + messageSkeletons.clear(); + } + lastSkeletonCount = messageSkeletons.size(); + lastSkeletonMessageCount = messages.size(); + if (pullingDownOffset != 0) { + canvas.restore(); + } + } + super.draw(canvas); + } + + private void updateSkeletonColors() { + int color0 = ColorUtils.setAlphaComponent(getThemedColor(Theme.key_listSelector), 30); + int color1 = ColorUtils.setAlphaComponent(getThemedColor(Theme.key_listSelector), 20); + if (skeletonColor1 != color1 || skeletonColor0 != color0) { + skeletonColor0 = color0; + skeletonColor1 = color1; + skeletonGradient = new LinearGradient(0, 0, skeletonGradientWidth = AndroidUtilities.dp(200), 0, new int[]{color1, color0, color0, color1}, new float[]{0.0f, 0.4f, 0.6f, 1f}, Shader.TileMode.CLAMP); + skeletonPaint.setShader(skeletonGradient); + } + } + + private void updateSkeletonGradient() { + long newUpdateTime = SystemClock.elapsedRealtime(); + long dt = Math.abs(skeletonLastUpdateTime - newUpdateTime); + if (dt > 17) { + dt = 16; + } + if (dt < 4) { + dt = 0; + } + int width = getWidth(); + skeletonLastUpdateTime = newUpdateTime; + skeletonTotalTranslation += dt * width / 400.0f; + if (skeletonTotalTranslation >= width * 2) { + skeletonTotalTranslation = -skeletonGradientWidth * 2; + } + skeletonMatrix.setTranslate(skeletonTotalTranslation, 0); + if (skeletonGradient != null) { + skeletonGradient.setLocalMatrix(skeletonMatrix); + } + } + @Override protected void dispatchDraw(Canvas canvas) { drawLaterRoundProgressCell = null; @@ -5060,10 +5365,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public boolean drawChild(Canvas canvas, View child, long drawingTime) { + if (isSkeletonVisible()) { + invalidate(); + } + int clipLeft = 0; int clipBottom = 0; boolean skipDraw = child == scrimView; ChatMessageCell cell; + ChatActionCell actionCell = null; float cilpTop = chatListViewPaddingTop - chatListViewPaddingVisibleOffset - AndroidUtilities.dp(4); if (child.getY() > getMeasuredHeight() || child.getY() + child.getMeasuredHeight() < cilpTop) { @@ -5098,6 +5408,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (skipDraw) { cell.getPhotoImage().skipDraw(); } + } else if (child instanceof ChatActionCell) { + actionCell = (ChatActionCell) child; + cell = null; } else { cell = null; } @@ -5139,6 +5452,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not canvas.translate(cell.getX(), cell.getY()); cell.drawOutboundsContent(canvas); canvas.restore(); + } else if (actionCell != null) { + canvas.save(); + canvas.translate(actionCell.getX(), actionCell.getY()); + actionCell.drawOutboundsContent(canvas); + canvas.restore(); } } else { result = false; @@ -5185,7 +5503,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } - if (videoPlayerContainer != null && (message.isRoundVideo() || message.isVideo()) && MediaController.getInstance().isPlayingMessage(message)) { + if (videoPlayerContainer != null && (message.isRoundVideo() || message.isVideo()) && !message.isVoiceTranscriptionOpen() && MediaController.getInstance().isPlayingMessage(message)) { ImageReceiver imageReceiver = cell.getPhotoImage(); float newX = imageReceiver.getImageX() + cell.getX(); float newY = cell.getY() + imageReceiver.getImageY() + chatListView.getY() - videoPlayerContainer.getTop(); @@ -5440,6 +5758,26 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not super.invalidate(); contentView.invalidateBlur(); } + + @Override + public void onScrolled(int dx, int dy) { + super.onScrolled(dx, dy); + } + + @Override + public void onScrollStateChanged(int state) { + super.onScrollStateChanged(state); + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + } + + @Override + public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { + super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); + } }; if (currentEncryptedChat != null && Build.VERSION.SDK_INT >= 19) { chatListView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); @@ -5452,7 +5790,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatListView.setVerticalScrollBarEnabled(!SharedConfig.chatBlurEnabled()); chatListView.setAdapter(chatAdapter = new ChatActivityAdapter(context)); chatListView.setClipToPadding(false); - chatListView.setAnimateEmptyView(true, 1); + chatListView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA_SCALE); chatListView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); chatListViewPaddingTop = 0; invalidateChatListViewTopPadding(); @@ -5529,7 +5867,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public int getStarForFixGap() { int padding = (int) chatListViewPaddingTop; - if (isThreadChat() && pinnedMessageView != null && pinnedMessageView.getVisibility() == View.VISIBLE) { + if (isThreadChat() && !isTopic && pinnedMessageView != null && pinnedMessageView.getVisibility() == View.VISIBLE) { padding -= Math.max(0, AndroidUtilities.dp(48) + pinnedMessageEnterOffset); } return padding; @@ -5674,7 +6012,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not for (int i = 0; i < n; i++) { View child = chatListView.getChildAt(i); float padding = chatListViewPaddingTop; - if (isThreadChat() && pinnedMessageView != null && pinnedMessageView.getVisibility() == View.VISIBLE) { + if (isThreadChat() && !isTopic && pinnedMessageView != null && pinnedMessageView.getVisibility() == View.VISIBLE) { padding -= Math.max(0, AndroidUtilities.dp(48) + pinnedMessageEnterOffset); } if (chatListView.getChildAdapterPosition(child) == chatAdapter.getItemCount() - 1) { @@ -6072,7 +6410,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not contentView.addView(pinnedMessageView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 50, Gravity.TOP | Gravity.LEFT)); pinnedMessageView.setOnClickListener(v -> { wasManualScroll = true; - if (isThreadChat()) { + if (isThreadChat() && !isTopic) { scrollToMessageId(threadMessageId, 0, true, 0, true, 0); } else if (currentPinnedMessageId != 0) { int currentPinned = currentPinnedMessageId; @@ -6103,6 +6441,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not pinnedLineView = new PinnedLineView(context, themeDelegate); pinnedMessageView.addView(pinnedLineView, LayoutHelper.createFrame(2, 48, Gravity.LEFT | Gravity.TOP, 8, 0, 0, 0)); + pinnedMessageView.setClipChildren(false); pinnedCounterTextView = new NumberTextView(context); pinnedCounterTextView.setAddNumber(); @@ -6366,7 +6705,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not editor.putBoolean("dialog_bar_report" + dialog_id, false); editor.commit(); updateTopPanel(false); - getNotificationsController().clearDialogNotificationsSettings(dialog_id); + getNotificationsController().clearDialogNotificationsSettings(dialog_id, getTopicId()); } else if (addToContactsButton.getTag() != null && (Integer) addToContactsButton.getTag() == 4) { if (chatInfo != null && chatInfo.participants != null) { LongSparseArray users = new LongSparseArray<>(); @@ -6376,9 +6715,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not long chatId = chatInfo.id; InviteMembersBottomSheet bottomSheet = new InviteMembersBottomSheet(context, currentAccount, users, chatInfo.id, ChatActivity.this, themeDelegate); bottomSheet.setDelegate((users1, fwdCount) -> { - for (int a = 0, N = users1.size(); a < N; a++) { + int N = users1.size(); + int[] finished = new int[1]; + for (int a = 0; a < N; a++) { TLRPC.User user = users1.get(a); - getMessagesController().addUserToChat(chatId, user, fwdCount, null, ChatActivity.this, null); + getMessagesController().addUserToChat(chatId, user, fwdCount, null, ChatActivity.this, () -> { + if (++finished[0] == N) { + BulletinFactory.of(ChatActivity.this).createUsersAddedBulletin(users1, currentChat).show(); + } + }); } getMessagesController().hidePeerSettingsBar(dialog_id, currentUser, currentChat); updateTopPanel(true); @@ -6398,6 +6743,27 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } }); + restartTopicButton = new TextView(context); + restartTopicButton.setTextColor(getThemedColor(Theme.key_chat_addContact)); + restartTopicButton.setVisibility(View.GONE); + restartTopicButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + restartTopicButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + restartTopicButton.setSingleLine(true); + restartTopicButton.setMaxLines(1); + restartTopicButton.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); + restartTopicButton.setGravity(Gravity.CENTER); + restartTopicButton.setText(LocaleController.getString("RestartTopic", R.string.RestartTopic).toUpperCase()); + if (Build.VERSION.SDK_INT >= 21) { + restartTopicButton.setBackground(Theme.createSelectorDrawable(getThemedColor(Theme.key_chat_addContact) & 0x19ffffff, 3)); + } + topChatPanelView.addView(restartTopicButton, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 1)); + restartTopicButton.setOnClickListener(v -> { + getMessagesController().getTopicsController().toggleCloseTopic(currentChat.id, forumTopic.id, forumTopic.closed = false); + updateTopicButtons(); + updateBottomOverlay(); + updateTopPanel(true); + }); + closeReportSpam = new ImageView(context); closeReportSpam.setImageResource(R.drawable.miniplayer_close); closeReportSpam.setContentDescription(LocaleController.getString("Close", R.string.Close)); @@ -6445,21 +6811,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not pagedownButton = new FrameLayout(context); pagedownButton.setVisibility(View.INVISIBLE); contentView.addView(pagedownButton, LayoutHelper.createFrame(66, 61, Gravity.RIGHT | Gravity.BOTTOM, 0, 0, -3, 5)); - pagedownButton.setOnClickListener(view -> { - wasManualScroll = true; - textSelectionHelper.cancelTextSelectionRunnable(); - if (createUnreadMessageAfterId != 0) { - scrollToMessageId(createUnreadMessageAfterId, 0, false, returnToLoadIndex, true, 0); - } else if (returnToMessageId > 0) { - scrollToMessageId(returnToMessageId, 0, true, returnToLoadIndex, true, 0); - } else { - scrollToLastMessage(false); - if (!pinnedMessageIds.isEmpty()) { - forceScrollToFirst = true; - forceNextPinnedMessageId = pinnedMessageIds.get(0); - } - } - }); + pagedownButton.setOnClickListener(view -> onPageDownClicked()); mentiondownButton = new FrameLayout(context); mentiondownButton.setVisibility(View.INVISIBLE); @@ -6469,7 +6821,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private void loadLastUnreadMention() { wasManualScroll = true; if (hasAllMentionsLocal) { - getMessagesStorage().getUnreadMention(dialog_id, param -> { + getMessagesStorage().getUnreadMention(dialog_id, getTopicId(), param -> { if (param == 0) { hasAllMentionsLocal = false; loadLastUnreadMention(); @@ -6482,6 +6834,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not TLRPC.TL_messages_getUnreadMentions req = new TLRPC.TL_messages_getUnreadMentions(); req.peer = getMessagesController().getInputPeer(dialog_id); req.limit = 1; + if (isTopic) { + req.top_msg_id = threadMessageId; + req.flags |= 1; + } req.add_offset = newMentionsCount - 1; getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; @@ -6491,7 +6847,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { newMentionsCount = 0; } - messagesStorage.resetMentionsCount(dialog_id, newMentionsCount); + messagesStorage.resetMentionsCount(dialog_id, getTopicId(), newMentionsCount); if (newMentionsCount == 0) { hasAllMentionsLocal = true; showMentionDownButton(false, true); @@ -6520,7 +6876,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not }); mentiondownButton.setOnLongClickListener(view -> { - scrimPopupWindow = ReadAllMentionsMenu.show(ReadAllMentionsMenu.TYPE_MENTIONS, getParentActivity(), contentView, view, getResourceProvider(), () -> { + scrimPopupWindow = ReadAllMentionsMenu.show(ReadAllMentionsMenu.TYPE_MENTIONS, getParentActivity(), getParentLayout(), contentView, view, getResourceProvider(), () -> { for (int a = 0; a < messages.size(); a++) { MessageObject messageObject = messages.get(a); if (messageObject.messageOwner.mentioned && !messageObject.isContentUnread()) { @@ -6528,7 +6884,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } newMentionsCount = 0; - getMessagesController().markMentionsAsRead(dialog_id); + getMessagesController().markMentionsAsRead(dialog_id, getTopicId()); hasAllMentionsLocal = true; showMentionDownButton(false, true); if (scrimPopupWindow != null) { @@ -6605,6 +6961,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not processExternalUrl(0, result.content.url, false); } } + + private boolean wasAtTop = true; + @Override + protected void onScrolled(boolean atTop, boolean atBottom) { + if (wasAtTop != atTop) { + AndroidUtilities.updateViewShow(suggestEmojiPanel, atTop, false, true); + wasAtTop = atTop; + } + } }; contentView.addView(mentionContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 110, Gravity.LEFT | Gravity.BOTTOM)); @@ -6691,8 +7056,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (searchingForUser && searchContainer.getVisibility() == View.VISIBLE) { searchUserMessages(null, chat); } else { - if (chat.username != null) { - chatActivityEnterView.replaceWithText(start, len, "@" + chat.username + " ", false); + String username = ChatObject.getPublicUsername(chat); + if (username != null) { + chatActivityEnterView.replaceWithText(start, len, "@" + username + " ", false); } } } else if (object instanceof TLRPC.User) { @@ -6810,7 +7176,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity(), themeDelegate); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setMessage(LocaleController.getString("ClearSearch", R.string.ClearSearch)); - builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton).toUpperCase(), (dialogInterface, i) -> mentionContainer.getAdapter().clearRecentHashtags()); + builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton), (dialogInterface, i) -> mentionContainer.getAdapter().clearRecentHashtags()); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showDialog(builder.create()); return true; @@ -6897,11 +7263,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not reactionsMentiondownButton.setOnClickListener(view -> { wasManualScroll = true; - getMessagesController().getNextReactionMention(dialog_id, reactionsMentionCount, (messageId) -> { + getMessagesController().getNextReactionMention(dialog_id, getTopicId(), reactionsMentionCount, (messageId) -> { if (messageId == 0) { reactionsMentionCount = 0; updateReactionsMentionButton(true); - getMessagesController().markReactionsAsRead(dialog_id); + getMessagesController().markReactionsAsRead(dialog_id, getTopicId()); } else { updateReactionsMentionButton(true); scrollToMessageId(messageId, 0, false, 0, true, 0); @@ -6909,13 +7275,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not }); }); reactionsMentiondownButton.setOnLongClickListener(view -> { - scrimPopupWindow = ReadAllMentionsMenu.show(ReadAllMentionsMenu.TYPE_REACTIONS, getParentActivity(), contentView, view, getResourceProvider(), () -> { + scrimPopupWindow = ReadAllMentionsMenu.show(ReadAllMentionsMenu.TYPE_REACTIONS, getParentActivity(), getParentLayout(), contentView, view, getResourceProvider(), () -> { for (int i = 0; i < messages.size(); i++) { messages.get(i).markReactionsAsRead(); } reactionsMentionCount = 0; updateReactionsMentionButton(true); - getMessagesController().markReactionsAsRead(dialog_id); + getMessagesController().markReactionsAsRead(dialog_id, getTopicId()); if (scrimPopupWindow != null) { scrimPopupWindow.dismiss(); } @@ -7250,10 +7616,36 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } updateScheduledInterface(false); } + if (!TextUtils.isEmpty(message) && forwardingMessages != null && !forwardingMessages.messages.isEmpty()) { + ArrayList messagesToForward = new ArrayList<>(); + forwardingMessages.getSelectedMessages(messagesToForward); + boolean showReplyHint = messagesToForward.size() > 0; + TLRPC.Peer toPeer = getMessagesController().getPeer(dialog_id); + for (int i = 0; i < messagesToForward.size(); ++i) { + MessageObject msg = messagesToForward.get(i); + if (msg != null && msg.messageOwner != null && !MessageObject.peersEqual(msg.messageOwner.peer_id, toPeer)) { + showReplyHint = false; + break; + } + } + + if (showReplyHint && topUndoView != null) { + topUndoView.showWithAction(0, UndoView.ACTION_HINT_SWIPE_TO_REPLY, null, null); + } + } hideFieldPanel(notify, scheduleDate, true); if (chatActivityEnterView != null && chatActivityEnterView.getEmojiView() != null) { chatActivityEnterView.getEmojiView().onMessageSend(); } + + if (!getMessagesController().premiumLocked && !getMessagesController().didPressTranscribeButtonEnough() && !getUserConfig().isPremium() && !TextUtils.isEmpty(message) && messages != null) { + for (int i = 1; i < Math.min(5, messages.size()); ++i) { + MessageObject msg = messages.get(i); + if (msg != null && !msg.isOutOwner() && (msg.isVoice() || msg.isRoundVideo()) && msg.isContentUnread()) { + TranscribeButton.showOffTranscribe(msg); + } + } + } } @Override @@ -7311,7 +7703,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not editTextItem.setTag(1); if (editTextItem.getVisibility() != View.VISIBLE) { - if (chatMode == 0 && threadMessageId == 0 && !UserObject.isReplyUser(currentUser) && reportType < 0) { + if (chatMode == 0 && (threadMessageId == 0 || isTopic) && !UserObject.isReplyUser(currentUser) && reportType < 0) { editTextItem.setVisibility(View.VISIBLE); headerItem.setVisibility(View.GONE); attachItem.setVisibility(View.GONE); @@ -7343,7 +7735,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (editTextItem.getTag() != null) { editTextItem.setTag(null); if (editTextItem.getVisibility() != View.GONE) { - if (chatMode == 0 && threadMessageId == 0 && !UserObject.isReplyUser(currentUser) && reportType < 0) { + if (chatMode == 0 && (threadMessageId == 0 || isTopic) && !UserObject.isReplyUser(currentUser) && reportType < 0) { editTextItem.setVisibility(View.GONE); if (chatActivityEnterView.hasText() && TextUtils.isEmpty(chatActivityEnterView.getSlowModeTimer())) { @@ -7407,7 +7799,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not AndroidUtilities.runOnUIThread(waitingForCharaterEnterRunnable, AndroidUtilities.WEB_URL == null ? 3000 : 1000); } } - emojiAnimationsOverlay.cancelAllAnimations(); + if (emojiAnimationsOverlay != null) { + emojiAnimationsOverlay.cancelAllAnimations(); + } ReactionsEffectOverlay.dismissAll(); } @@ -8046,7 +8440,34 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not bottomOverlayChat.setClipChildren(false); contentView.addView(bottomOverlayChat, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 51, Gravity.BOTTOM)); - bottomOverlayChatText = new UnreadCounterTextView(context); + bottomOverlayChatText = new UnreadCounterTextView(context) { + @Override + protected void updateCounter() { + if (ChatObject.isChannel(currentChat) && !currentChat.megagroup && chatInfo != null && chatInfo.linked_chat_id != 0) { + TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(-chatInfo.linked_chat_id); + if (dialog != null) { + setCounter(dialog.unread_count); + return; + } + } + setCounter(0); + } + + @Override + protected boolean isTouchFullWidth() { + return botInfo != null; + } + + @Override + protected Theme.ResourcesProvider getResourceProvider() { + return themeDelegate; + } + + @Override + protected float getTopOffset() { + return -AndroidUtilities.dp(2); + } + }; bottomOverlayChat.addView(bottomOverlayChatText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0, 0, 1.5f, 0, 0)); bottomOverlayChatText.setOnClickListener(view -> { if (getParentActivity() == null || pullingDownOffset != 0) { @@ -8424,14 +8845,35 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not }; actionBar.setDrawBlurBackground(contentView); - TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(dialog_id); - if (dialog != null) { - reactionsMentionCount = dialog.unread_reactions_count; + if (isTopic) { + reactionsMentionCount = forumTopic.unread_reactions_count; updateReactionsMentionButton(false); + } else { + TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(dialog_id); + if (dialog != null) { + reactionsMentionCount = dialog.unread_reactions_count; + updateReactionsMentionButton(false); + } } return fragmentView; } + public void onPageDownClicked() { + wasManualScroll = true; + textSelectionHelper.cancelTextSelectionRunnable(); + if (createUnreadMessageAfterId != 0) { + scrollToMessageId(createUnreadMessageAfterId, 0, false, returnToLoadIndex, true, 0); + } else if (returnToMessageId > 0) { + scrollToMessageId(returnToMessageId, 0, true, returnToLoadIndex, true, 0); + } else { + scrollToLastMessage(false, true); + if (!pinnedMessageIds.isEmpty()) { + forceScrollToFirst = true; + forceNextPinnedMessageId = pinnedMessageIds.get(0); + } + } + } + public ActionBarMenuItem getHeaderItem() { return headerItem; } @@ -8683,6 +9125,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not args.putInt("hasPoll", hasPoll); args.putBoolean("hasInvoice", hasInvoice); args.putInt("messagesCount", forwardingMessages.messages.size()); + args.putBoolean("canSelectTopics", true); DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate(ChatActivity.this); presentFragment(fragment); @@ -8741,14 +9184,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (getParentLayout() == null) { return; } - int stackIndex = getParentLayout().fragmentsStack.indexOf(this); + int stackIndex = getParentLayout().getFragmentStack().indexOf(this); BackButtonMenu.addToPulledDialogs(this, stackIndex, currentChat, currentUser, dialog_id, dialogFilterId, dialogFolderId); } private void addToPulledDialogs(TLRPC.Chat chat, long dialogId, int folderId, int filterId) { if (getParentLayout() == null) { return; } - int stackIndex = getParentLayout().fragmentsStack.indexOf(this); + int stackIndex = getParentLayout().getFragmentStack().indexOf(this); BackButtonMenu.addToPulledDialogs(this, stackIndex, chat, null, dialogId, folderId, filterId); } @@ -8756,6 +9199,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not this.fromPullingDownTransition = fromPullingDownTransition; } + public void setSwitchFromTopics(boolean switchFromTopics) { + this.switchFromTopics = switchFromTopics; + } private void updateBulletinLayout() { Bulletin bulletin = Bulletin.getVisibleBulletin(); if (bulletin != null && bulletinDelegate != null) { @@ -8952,7 +9398,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } fragment.loadedPinnedMessagesCount = loadedPinnedMessagesCount; - fragment.totalPinnedMessagesCount = totalPinnedMessagesCount; + fragment.totalPinnedMessagesCount = isTopic ? pinnedMessageIds.size() : totalPinnedMessagesCount; fragment.pinnedEndReached = pinnedEndReached; fragment.userInfo = userInfo; fragment.chatInfo = chatInfo; @@ -9000,7 +9446,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not }, () -> { if (!hide) { - getMessagesController().unpinAllMessages(currentChat, currentUser); + if (isTopic) { + for (int i = 0; i < pinnedMessageIds.size(); i++) { + getMessagesController().pinMessage(currentChat, currentUser, pinnedMessageIds.get(i), true, false, false); + } + } else { + getMessagesController().unpinAllMessages(currentChat, currentUser); + } } if (tag == pinBullerinTag) { pinBulletin = null; @@ -9024,7 +9476,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } private void checkShowBlur(boolean animated) { - boolean show = (parentLayout != null && parentLayout.isInPreviewMode() && !inPreviewMode) || (forwardingPreviewView != null && forwardingPreviewView.isShowing()); + boolean show = (parentLayout != null && parentLayout.isInPreviewMode() && !parentLayout.hasIntegratedBlurInPreview() && !inPreviewMode) || (forwardingPreviewView != null && forwardingPreviewView.isShowing()); if (show && (blurredView == null || blurredView.getTag() == null)) { @@ -9075,24 +9527,28 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - protected int getPreviewHeight() { + public int getPreviewHeight() { if (chatMode == MODE_PINNED && messages.size() == 2) { return getHeightForMessage(messages.get(0)) + AndroidUtilities.dp(80) + ActionBar.getCurrentActionBarHeight(); } return super.getPreviewHeight(); } - boolean animateTo; + boolean animateProgressViewTo; private void showProgressView(boolean show) { if (progressView == null) { return; } + if (DISABLE_PROGRESS_VIEW && !AndroidUtilities.isTablet() && !isComments && currentUser == null) { + animateProgressViewTo = show; + return; + } if (fragmentOpened && SharedConfig.animationsEnabled()) { - if (show == animateTo) { + if (show == animateProgressViewTo) { return; } - animateTo = show; + animateProgressViewTo = show; if (show) { if (progressView.getVisibility() != View.VISIBLE) { progressView.setVisibility(View.VISIBLE); @@ -9112,7 +9568,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not }).start(); } } else { - animateTo = show; + animateProgressViewTo = show; progressView.setVisibility(show ? View.VISIBLE : View.INVISIBLE); } } @@ -9251,7 +9707,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not int adapterPosition = chatListView.getChildAdapterPosition(child); if (adapterPosition == chatAdapter.getItemCount() - 1) { float padding = chatListViewPaddingTop; - if (isThreadChat() && pinnedMessageView != null && pinnedMessageView.getVisibility() == View.VISIBLE) { + if (isThreadChat() && !isTopic && pinnedMessageView != null && pinnedMessageView.getVisibility() == View.VISIBLE) { padding -= Math.max(0, AndroidUtilities.dp(48) + pinnedMessageEnterOffset); } if (child.getTop() > padding) { @@ -9529,6 +9985,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not args.putInt("messagesCount", canForwardMessagesCount); args.putInt("hasPoll", hasPoll); args.putBoolean("hasInvoice", hasInvoice); + args.putBoolean("canSelectTopics", true); DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate(ChatActivity.this); presentFragment(fragment); @@ -9687,7 +10144,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not waitingForLoad.add(lastLoadIndex); postponedScrollMessageId = 0; postponedScrollIsCanceled = false; - getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 30, 0, date, true, 0, classGuid, 4, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 30, 0, date, true, 0, classGuid, 4, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); floatingDateView.setAlpha(0.0f); floatingDateView.setTag(null); } @@ -10733,7 +11190,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - protected void onRemoveFromParent() { + public void onRemoveFromParent() { MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); if (messageObject != null && messageObject.isVideo()) { MediaController.getInstance().cleanupPlayer(true, true); @@ -10762,8 +11219,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (chatLayoutManager == null || paused || chatAdapter.isFrozen) { return; } - int firstVisibleItem = chatLayoutManager.findFirstVisibleItemPosition(); - int visibleItemCount = firstVisibleItem == RecyclerView.NO_POSITION ? 0 : Math.abs(chatLayoutManager.findLastVisibleItemPosition() - firstVisibleItem) + 1; + int firstVisibleItem = RecyclerListView.NO_POSITION; + int visibleItemCount = 0; + for (int i = 0; i < chatListView.getChildCount(); i++) { + int position = chatListView.getChildAdapterPosition(chatListView.getChildAt(i)); + if (position != RecyclerListView.NO_POSITION) { + if (firstVisibleItem == RecyclerListView.NO_POSITION || position < firstVisibleItem) { + firstVisibleItem = position; + } + visibleItemCount++; + } + } int totalItemCount = chatAdapter.getItemCount(); int checkLoadCount; if (scroll) { @@ -10776,24 +11242,24 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not loading = true; waitingForLoad.add(lastLoadIndex); if (messagesByDays.size() != 0) { - getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 50, maxMessageId[0], 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 50, maxMessageId[0], 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); } else { - getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 50, 0, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 50, 0, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); } } else if (mergeDialogId != 0 && !endReached[1]) { loading = true; waitingForLoad.add(lastLoadIndex); - getMessagesController().loadMessages(mergeDialogId, 0, false, 50, maxMessageId[1], 0, !cacheEndReached[1], minDate[1], classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(mergeDialogId, 0, false, 50, maxMessageId[1], 0, !cacheEndReached[1], minDate[1], classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); } } if (visibleItemCount > 0 && !loadingForward && firstVisibleItem <= 10) { if (mergeDialogId != 0 && !forwardEndReached[1]) { waitingForLoad.add(lastLoadIndex); - getMessagesController().loadMessages(mergeDialogId, 0, false, 50, minMessageId[1], 0, true, maxDate[1], classGuid, 1, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(mergeDialogId, 0, false, 50, minMessageId[1], 0, true, maxDate[1], classGuid, 1, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); loadingForward = true; } else if (!forwardEndReached[0]) { waitingForLoad.add(lastLoadIndex); - getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 50, minMessageId[0], 0, true, maxDate[0], classGuid, 1, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 50, minMessageId[0], 0, true, maxDate[0], classGuid, 1, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); loadingForward = true; } } @@ -11160,7 +11626,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if ((scheduleDate != 0) == (chatMode == MODE_SCHEDULED)) { waitingForSendingMessageLoad = true; } - int result = getSendMessagesHelper().sendMessage(arrayList, dialog_id, fromMyName, hideCaption, notify, scheduleDate); + int result = getSendMessagesHelper().sendMessage(arrayList, dialog_id, fromMyName, hideCaption, notify, scheduleDate, getThreadMessage()); AlertsCreator.showSendMediaAlert(result, this, themeDelegate); if (result != 0) { AndroidUtilities.runOnUIThread(() -> { @@ -11226,7 +11692,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (chatActivityEnterView == null) { return; } - boolean showHint = false; + boolean showHint = false, showReplyHint = false; if (show) { if (messageObjectToReply == null && messageObjectsToForward == null && messageObjectToEdit == null && webPage == null) { return; @@ -11251,9 +11717,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatActivityEnterTopView.setEditMode(false); if (messageObjectToEdit != null) { forwardingMessages = null; - if (threadMessageId == 0) { - replyingMessageObject = null; + if (threadMessageId == 0 || isTopic) { + if (isTopic) { + replyingMessageObject = threadMessageObject; + } else { + replyingMessageObject = null; + } chatActivityEnterView.setReplyingMessageObject(null); + updateBottomOverlay(); } editingMessageObject = messageObjectToEdit; final boolean mediaEmpty = messageObjectToEdit.isMediaEmpty(); @@ -11361,6 +11832,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (!TextUtils.isEmpty(restrictionReason)) { replyObjectText = restrictionReason; sourceText = restrictionReason; + } else if (MessageObject.isTopicActionMessage(messageObjectToReply)) { + ForumUtilities.applyTopicToMessage(messageObjectToReply); + if (messageObjectToReply.messageTextForReply != null) { + replyObjectText = messageObjectToReply.messageTextForReply; + } else { + replyObjectText = messageObjectToReply.messageTextShort; + } + AnimatedEmojiSpan.applyFontMetricsForString(replyObjectText, replyObjectTextView.getPaint()); } else if (messageObjectToReply.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { replyObjectText = Emoji.replaceEmoji(messageObjectToReply.messageOwner.media.game.title, replyObjectTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(14), false); sourceText = messageObjectToReply.messageOwner.media.game.title; @@ -11383,13 +11862,19 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not replyObjectTextView.setText(AnimatedEmojiSpan.cloneSpans(replyObjectText)); } + updateBottomOverlay(); } else if (messageObjectsToForward != null) { if (messageObjectsToForward.isEmpty()) { return; } - if (threadMessageId == 0) { - replyingMessageObject = null; + if (threadMessageId == 0 || isTopic) { + if (isTopic) { + replyingMessageObject = threadMessageObject; + } else { + replyingMessageObject = null; + } chatActivityEnterView.setReplyingMessageObject(null); + updateBottomOverlay(); } editingMessageObject = null; chatActivityEnterView.setEditingMessageObject(null, false); @@ -11665,6 +12150,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not editingMessageObject = null; replyImageLocation = null; replyImageLocationObject = null; + updateBottomOverlay(); } if (showHint) { @@ -11691,6 +12177,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not replyObjectTextView.setAlpha(1f); replyObjectHintTextView.setAlpha(0); } + + if (showReplyHint) { + + } else { + + } } private void moveScrollToLastMessage(boolean skipSponsored) { @@ -11772,7 +12264,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } - public void scrollToLastMessage(boolean skipSponsored) { + public void scrollToLastMessage(boolean skipSponsored, boolean top) { if (chatListView.isFastScrollAnimationRunning()) { return; } @@ -11798,7 +12290,19 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not position++; } } - chatScrollHelper.scrollToPosition(position, 0, true, true); + if (top && messages != null && messages.get(position) != null) { + long groupId = messages.get(position).getGroupId(); + while (groupId != 0 && position + 1 < messages.size()) { + if (groupId != messages.get(position + 1).getGroupId()) { + break; + } + position++; + } + } + if (messages != null) { + position = Math.min(position, messages.size() - 1); + } + chatScrollHelper.scrollToPosition(chatScrollHelperCallback.position = position, chatScrollHelperCallback.offset = 0, chatScrollHelperCallback.bottom = !top, true); } } else { if (progressDialog != null) { @@ -11815,7 +12319,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not waitingForLoad.clear(); waitingForLoad.add(lastLoadIndex); - getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 30, 0, 0, true, 0, classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 30, 0, 0, true, 0, classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); } } @@ -11830,7 +12334,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (view instanceof ChatMessageCell) { ChatMessageCell messageCell = (ChatMessageCell) view; MessageObject messageObject = messageCell.getMessageObject(); - if (videoPlayerContainer != null && (messageObject.isRoundVideo() || messageObject.isVideo()) && MediaController.getInstance().isPlayingMessage(messageObject)) { + if (videoPlayerContainer != null && (messageObject.isRoundVideo() || messageObject.isVideo()) && !messageObject.isVoiceTranscriptionOpen() && MediaController.getInstance().isPlayingMessage(messageObject)) { ImageReceiver imageReceiver = messageCell.getPhotoImage(); videoPlayerContainer.setTranslationX(imageReceiver.getImageX() + messageCell.getX()); float translationY = messageCell.getY() + imageReceiver.getImageY() + chatListView.getY() - videoPlayerContainer.getTop(); @@ -11899,6 +12403,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } } + } else if (!needScroll) { + MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); + if (messageObject != null && messageObject.eventId == 0 && messageObject.isVoiceTranscriptionOpen()) { + MediaController.getInstance().setCurrentVideoVisible(false); + } } } @@ -12057,7 +12566,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (!threadMessageVisible && threadMessageObject != null && messageObject == threadMessageObject && messageCell.getBottom() > chatListViewPaddingTop) { threadMessageVisible = true; } - if (videoPlayerContainer != null && (messageObject.isVideo() || messageObject.isRoundVideo()) && MediaController.getInstance().isPlayingMessage(messageObject)) { + if (videoPlayerContainer != null && (messageObject.isVideo() || messageObject.isRoundVideo()) && !messageObject.isVoiceTranscriptionOpen() && MediaController.getInstance().isPlayingMessage(messageObject)) { ImageReceiver imageReceiver = messageCell.getPhotoImage(); if (top + imageReceiver.getImageY2() < 0) { foundTextureViewMessage = false; @@ -12086,9 +12595,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (fragmentOpened && openAnimationEnded && (chatListItemAnimator == null || !chatListItemAnimator.isRunning()) && messageCell.checkUnreadReactions(clipTop, chatListView.getMeasuredHeight() - blurredViewBottomOffset)) { reactionsMentionCount--; - getMessagesStorage().markMessageReactionsAsRead(getDialogId(), messageCell.getMessageObject().getId(), true); + getMessagesStorage().markMessageReactionsAsRead(getDialogId(), getTopicId(), messageCell.getMessageObject().getId(), true); if (reactionsMentionCount <= 0) { - getMessagesController().markReactionsAsRead(dialog_id); + getMessagesController().markReactionsAsRead(dialog_id, getTopicId()); } if (reactionsMentionCount >= 0) { TLRPC.MessagePeerReaction reaction = messageCell.getMessageObject().getRandomUnreadReaction(); @@ -12313,7 +12822,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not hideFloatingDateView(true); floatingDateViewOffset = 0; } - if (isThreadChat()) { + if (isThreadChat() && !isTopic) { if (previousThreadMessageVisible != threadMessageVisible) { updatePinnedMessageView(openAnimationStartTime != 0 && SystemClock.elapsedRealtime() >= openAnimationStartTime + 150); } @@ -12403,13 +12912,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not inlineUpdate2(); } getMessagesController().markDialogAsRead(dialog_id, minMessageId[0], minMessageId[0], maxDate[0], false, threadId, 0, true, scheduledRead); + if (isTopic) { + getMessagesStorage().updateRepliesMaxReadId(replyOriginalChat.id, replyOriginalMessageId, Math.max(maxPositiveUnreadId, replyMaxReadId), 0, true); + } firstUnreadSent = true; } } if (threadId != 0 && maxPositiveUnreadId > 0 && replyMaxReadId != maxPositiveUnreadId) { replyMaxReadId = maxPositiveUnreadId; - getMessagesStorage().updateRepliesMaxReadId(replyOriginalChat.id, replyOriginalMessageId, replyMaxReadId, true); - getNotificationCenter().postNotificationName(NotificationCenter.commentsRead, replyOriginalChat.id, replyOriginalMessageId, replyMaxReadId); + getMessagesStorage().updateRepliesMaxReadId(replyOriginalChat.id, replyOriginalMessageId, replyMaxReadId, newUnreadMessageCount, true); + if (!isTopic) { + getNotificationCenter().postNotificationName(NotificationCenter.commentsRead, replyOriginalChat.id, replyOriginalMessageId, replyMaxReadId); + } } } } @@ -12429,17 +12943,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } private void toggleMute(boolean instant) { - boolean muted = getMessagesController().isDialogMuted(dialog_id); + boolean muted = getMessagesController().isDialogMuted(dialog_id, getTopicId()); if (!muted) { if (instant) { - getNotificationsController().muteDialog(dialog_id, true); + getNotificationsController().muteDialog(dialog_id, getTopicId(), true); } else { - BottomSheet alert = AlertsCreator.createMuteAlert(this, dialog_id, themeDelegate); + BottomSheet alert = AlertsCreator.createMuteAlert(this, dialog_id, getTopicId(), themeDelegate); alert.setCalcMandatoryInsets(isKeyboardVisible()); showDialog(alert); } } else { - getNotificationsController().muteDialog(dialog_id, false); + getNotificationsController().muteDialog(dialog_id, getTopicId(), false); if (!instant) { BulletinFactory.createMuteBulletin(this, false, themeDelegate).show(); } @@ -12619,7 +13133,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatScrollHelperCallback.lastItemOffset = yOffset; chatScrollHelperCallback.lastPadding = (int) chatListViewPaddingTop; chatScrollHelper.setScrollDirection(scrollDirection); - chatScrollHelper.scrollToPosition(position, yOffset, false, true); + chatScrollHelper.scrollToPosition(chatScrollHelperCallback.position = position, chatScrollHelperCallback.offset = yOffset, chatScrollHelperCallback.bottom = false, true); canShowPagedownButton = true; updatePagedownButtonVisibility(true); } @@ -12659,7 +13173,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not postponedScrollToLastMessageQueryIndex = lastLoadIndex; postponedScrollMinMessageId = minMessageId[0]; postponedScrollMessageId = id; - getMessagesController().loadMessages(loadIndex == 0 ? dialog_id : mergeDialogId, 0, false, isThreadChat() || AndroidUtilities.isTablet() ? 30 : 20, startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(loadIndex == 0 ? dialog_id : mergeDialogId, 0, false, ((isThreadChat() && !isTopic) || AndroidUtilities.isTablet()) ? 30 : 20, startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); } else { View child = chatListView.getChildAt(0); if (child != null && child.getTop() <= 0) { @@ -12978,6 +13492,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not avatarContainer.showTimeItem(animated); } } + if (viewAsTopics != null) { + viewAsTopics.setVisibility(currentChat != null && currentChat.forum ? View.VISIBLE : View.GONE); + } if (avatarContainer != null) { if (currentEncryptedChat != null) { avatarContainer.setTime(currentEncryptedChat.ttl, animated); @@ -12988,7 +13505,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } if (clearHistoryItem != null && chatInfo != null) { - boolean visible = chatInfo.can_delete_channel || !ChatObject.isChannel(currentChat) || currentChat.megagroup && TextUtils.isEmpty(currentChat.username); + boolean visible = chatInfo.can_delete_channel || !ChatObject.isChannel(currentChat) || currentChat.megagroup && !ChatObject.isPublic(currentChat); clearHistoryItem.setVisibility(visible ? View.VISIBLE : View.GONE); } checkAndUpdateAvatar(); @@ -13016,7 +13533,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return 2; } else if (messageObject.type == 6) { return -1; - } else if (messageObject.type == 10 || messageObject.type == 11) { + } else if (messageObject.type == 10 || messageObject.type == MessageObject.TYPE_ACTION_PHOTO) { if (messageObject.getId() == 0) { return -1; } @@ -13064,7 +13581,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } return 4; } - } else if (messageObject.type == 12) { + } else if (messageObject.type == MessageObject.TYPE_CONTACT) { return 8; } else if (messageObject.isMediaEmpty()) { return 3; @@ -13086,7 +13603,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { return 20; } - } else if (messageObject.type == 10 || messageObject.type == 11) { + } else if (messageObject.type == 10 || messageObject.type == MessageObject.TYPE_ACTION_PHOTO) { if (messageObject.getId() == 0 || messageObject.isSending()) { return -1; } else { @@ -13127,7 +13644,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return 4; } } - } else if (messageObject.type == 12) { + } else if (messageObject.type == MessageObject.TYPE_CONTACT) { return 8; } else if (messageObject.isMediaEmpty()) { return 3; @@ -13181,7 +13698,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (selectedMessagesIds[index].indexOfKey(messageObject.getId()) >= 0) { selectedMessagesIds[index].remove(messageObject.getId()); if (reportType < 0) { - if ((messageObject.type == 0 || messageObject.isAnimatedEmoji() || messageObject.caption != null) && !(messageObject.messageOwner != null && messageObject.messageOwner.noforwards)) { + if ((messageObject.type == MessageObject.TYPE_TEXT || messageObject.isAnimatedEmoji() || messageObject.caption != null) && !(messageObject.messageOwner != null && messageObject.messageOwner.noforwards)) { selectedMessagesCanCopyIds[index].remove(messageObject.getId()); } if (!messageObject.isAnimatedEmoji() && (messageObject.isSticker() || messageObject.isAnimatedSticker()) && MessageObject.isStickerHasSet(messageObject.getDocument())) { @@ -13209,7 +13726,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } else { if (selectedMessagesIds[0].size() + selectedMessagesIds[1].size() >= 100) { - AndroidUtilities.shakeView(selectedMessagesCountTextView, 2, 0); + AndroidUtilities.shakeView(selectedMessagesCountTextView); Vibrator vibrator = (Vibrator) ApplicationLoader.applicationContext.getSystemService(Context.VIBRATOR_SERVICE); if (vibrator != null) { vibrator.vibrate(200); @@ -13218,7 +13735,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } selectedMessagesIds[index].put(messageObject.getId(), messageObject); if (reportType < 0) { - if ((messageObject.type == 0 || messageObject.isAnimatedEmoji() || messageObject.caption != null) && !(messageObject.messageOwner != null && messageObject.messageOwner.noforwards)) { + if ((messageObject.type == MessageObject.TYPE_TEXT || messageObject.isAnimatedEmoji() || messageObject.caption != null) && !(messageObject.messageOwner != null && messageObject.messageOwner.noforwards)) { selectedMessagesCanCopyIds[index].put(messageObject.getId(), messageObject); } if (!messageObject.isAnimatedEmoji() && (messageObject.isSticker() || messageObject.isAnimatedSticker()) && MessageObject.isStickerHasSet(messageObject.getDocument())) { @@ -13346,7 +13863,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not final int newEditVisibility = canEditMessagesCount == 1 && selectedCount == 1 ? View.VISIBLE : View.GONE; if (replyButton != null) { boolean allowChatActions = true; - if (bottomOverlayChat != null && bottomOverlayChat.getVisibility() == View.VISIBLE || + if (bottomOverlayChat != null && bottomOverlayChat.getVisibility() == View.VISIBLE && !bottomOverlayChatWaitsReply || currentChat != null && (ChatObject.isNotInChat(currentChat) && !isThreadChat() || ChatObject.isChannel(currentChat) && !ChatObject.canPost(currentChat) && !currentChat.megagroup || !ChatObject.canSendMessages(currentChat))) { allowChatActions = false; } @@ -13525,7 +14042,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return; } if (isThreadChat()) { - if (isComments) { + if (isTopic) { + updateTopicHeader(); + } else if (isComments) { if (threadMessageObject.hasReplies()) { avatarContainer.setTitle(LocaleController.formatPluralString("Comments", threadMessageObject.getRepliesCount())); } else { @@ -13562,10 +14081,23 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not setParentActivityTitle(avatarContainer.getTitleTextView().getText()); } + private void updateTopicTitleIcon() { + if (forumTopic != null && avatarContainer != null) { + avatarContainer.getAvatarImageView().setVisibility(View.VISIBLE); + ForumUtilities.setTopicIcon(avatarContainer.getAvatarImageView(), forumTopic); + } + } + private int getPinnedMessagesCount() { return Math.max(loadedPinnedMessagesCount, totalPinnedMessagesCount); } + private void updateTopicButtons() { + if (closeTopicItem != null) { + closeTopicItem.setVisibility(currentChat != null && ChatObject.canManageTopic(currentAccount, currentChat, forumTopic) && forumTopic != null && !forumTopic.closed ? View.VISIBLE : View.GONE); + } + } + private void updateBotButtons() { if (headerItem == null || currentUser == null || currentEncryptedChat != null || !currentUser.bot) { return; @@ -13608,12 +14140,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (avatarContainer == null || chatMode != 0) { return; } - boolean isMuted = getMessagesController().isDialogMuted(dialog_id); + boolean isMuted = getMessagesController().isDialogMuted(dialog_id, getTopicId()); if (forceToggleMuted) { isMuted = !isMuted; } Drawable rightIcon = null; - if (!UserObject.isReplyUser(currentUser) && !isThreadChat() && isMuted) { + if (!UserObject.isReplyUser(currentUser) && (!isThreadChat() || isTopic) && isMuted) { rightIcon = getThemedDrawable(Theme.key_drawable_muteIconDrawable); } avatarContainer.setTitleIcons(currentEncryptedChat != null ? getThemedDrawable(Theme.key_drawable_lockIconDrawable) : null, rightIcon); @@ -13623,7 +14155,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not muteItem.setTextAndIcon(LocaleController.getString("Unmute", R.string.Unmute), R.drawable.msg_mute); } else { muteItem.getRightIcon().setVisibility(View.VISIBLE); - if (getMessagesController().isDialogNotificationsSoundEnabled(dialog_id)) { + if (getMessagesController().isDialogNotificationsSoundEnabled(dialog_id, getTopicId())) { muteItem.setTextAndIcon(LocaleController.getString("Mute", R.string.Mute), R.drawable.msg_unmute); } else { muteItem.setTextAndIcon(LocaleController.getString("Mute", R.string.Mute), R.drawable.msg_silent); @@ -13631,11 +14163,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } if (chatNotificationsPopupWrapper != null) { - chatNotificationsPopupWrapper.update(dialog_id); + chatNotificationsPopupWrapper.update(dialog_id, getTopicId(), null); } } public void checkAndUpdateAvatar() { + if (isTopic) { + return; + } if (currentUser != null) { TLRPC.User user = getMessagesController().getUser(currentUser.id); if (user == null) { @@ -14099,6 +14634,19 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not currentPicturePath = args.getString("path"); } + private void checkDispatchHideSkeletons(boolean animate) { + if (startMessageAppearTransitionMs == 0) { + if (animate && !messageSkeletons.isEmpty()) { + startMessageAppearTransitionMs = System.currentTimeMillis(); + } else { + startMessageAppearTransitionMs = 1; + } + if (chatListView != null) { + chatListView.invalidate(); + } + } + } + private void removeUnreadPlane(boolean scrollToEnd) { if (unreadMessageObject != null) { if (scrollToEnd) { @@ -14151,7 +14699,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (chatMode == MODE_SCHEDULED && mode == MODE_SCHEDULED && !isCache) { waitingForReplyMessageLoad = true; waitingForLoad.add(lastLoadIndex); - getMessagesController().loadMessages(dialog_id, mergeDialogId, false, AndroidUtilities.isTablet() ? 30 : 20, 0, 0, true, 0, classGuid, 2, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, mergeDialogId, false, AndroidUtilities.isTablet() ? 30 : 20, 0, 0, true, 0, classGuid, 2, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); } return; } else if (!doNotRemoveLoadIndex) { @@ -14164,11 +14712,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not forwardEndReached[0] = forwardEndReached[1] = true; firstLoading = false; showProgressView(false); + checkDispatchHideSkeletons(true); if (chatListView != null) { if (!fragmentOpened) { chatListView.setAnimateEmptyView(false, 1); chatListView.setEmptyView(emptyViewContainer); - chatListView.setAnimateEmptyView(true, 1); + chatListView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA_SCALE); } else { chatListView.setEmptyView(emptyViewContainer); } @@ -14259,7 +14808,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (load_type == 0 && isCache && messArr.size() < count) { postponedScrollToLastMessageQueryIndex = lastLoadIndex; waitingForLoad.add(lastLoadIndex); - getMessagesController().loadMessages(dialog_id, mergeDialogId, false, count, 0, 0, false, 0, classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, mergeDialogId, false, count, 0, 0, false, 0, classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); return; } @@ -14338,7 +14887,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } first_unread_id = 0; } else { - first_unread_id = fnid; + if (isTopic) { + if (first_unread_id == 0) { + for (int i = messArr.size() - 1; i >= 0; i--) { + if (messArr.get(i).getId() > threadMaxInboxReadId) { + first_unread_id = messArr.get(i).getId(); + break; + } + } + } + } else { + first_unread_id = fnid; + } if (!chatWasReset) { unread_to_load = (Integer) args[6]; } @@ -14358,7 +14918,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } int newRowsCount = 0; - if (load_type != 0 && (isThreadChat() && first_unread_id != 0 || startLoadFromMessageId != 0 || last_message_id != 0)) { + if (load_type != 0 && (isThreadChat() && !isTopic && first_unread_id != 0 || startLoadFromMessageId != 0 || last_message_id != 0)) { forwardEndReached[loadIndex] = false; hideForwardEndReached = false; } @@ -14367,12 +14927,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not forwardEndReached[0] = false; hideForwardEndReached = false; minMessageId[0] = 0; + checkDispatchHideSkeletons(true); } if (chatMode == MODE_SCHEDULED) { endReached[0] = cacheEndReached[0] = true; forwardEndReached[0] = forwardEndReached[0] = true; } - if (!isThreadChat() && ChatObject.isChannel(currentChat) && !getMessagesController().dialogs_dict.containsKey(dialog_id) && load_type == 2 && loadIndex == 0) { + if ((!isThreadChat() || isTopic) && ChatObject.isChannel(currentChat) && !getMessagesController().dialogs_dict.containsKey(dialog_id) && load_type == 2 && loadIndex == 0) { forwardEndReached[0] = false; hideForwardEndReached = true; } @@ -14409,7 +14970,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not }); } - if (isThreadChat() && (load_type == 2 || load_type == 3) && !isCache) { + if (isThreadChat() && !isTopic && (load_type == 2 || load_type == 3) && !isCache) { if (load_type == 3 && scrollToThreadMessage) { startLoadFromMessageId = threadMessageId; } @@ -14439,6 +15000,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (beforMax < num) { endReached[0] = true; + checkDispatchHideSkeletons(true); } if (!chatWasReset && afterMax < count - num) { forwardEndReached[0] = true; @@ -14479,7 +15041,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } - if (!threadMessageAdded && isThreadChat() && (load_type == 0 && messArr.size() < count || (load_type == 2 || load_type == 3) && endReached[0])) { + if (!isTopic && !threadMessageAdded && isThreadChat() && (load_type == 0 && messArr.size() < count || (load_type == 2 || load_type == 3) && endReached[0])) { TLRPC.Message msg = new TLRPC.TL_message(); if (threadMessageObject.getRepliesCount() == 0) { if (isComments) { @@ -14509,7 +15071,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not Collections.reverse(messArr); } if (currentEncryptedChat == null) { - getMediaDataController().loadReplyMessagesForMessages(messArr, dialog_id, chatMode == MODE_SCHEDULED, null); + getMediaDataController().loadReplyMessagesForMessages(messArr, dialog_id, chatMode == MODE_SCHEDULED, 0, null); } int approximateHeightSum = 0; if (!chatWasReset && (load_type == 2 || load_type == 1) && messArr.isEmpty() && !isCache) { @@ -14560,6 +15122,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (threadMessageId != 0 && obj.messageOwner instanceof TLRPC.TL_messageEmpty) { continue; } + if (getTopicId() != 0 && obj.messageOwner.action instanceof TLRPC.TL_messageActionTopicCreate) { + if (obj.isUnread()) { + obj.setIsRead(); + newUnreadMessageCount--; + if (newUnreadMessageCount < 0) { + newUnreadMessageCount = 0; + } + getMessagesStorage().updateRepliesMaxReadId(replyOriginalChat.id, replyOriginalMessageId, obj.getId(), newUnreadMessageCount, true); + } + endReached[0] = true; + continue; + } if (currentEncryptedChat != null && obj.messageOwner.stickerVerified == 0) { getMediaDataController().verifyAnimatedStickerMessage(obj.messageOwner); } @@ -14862,7 +15436,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not onChatMessagesLoaded.run(); onChatMessagesLoaded = null; } - loadSendAsPeers(false); + loadSendAsPeers(fragmentBeginToShow); if (chatListView != null && chatScrollHelper != null) { if (first || scrollToTopOnResume || forceScrollToTop) { @@ -14983,9 +15557,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (first) { if (chatListView != null) { if (!fragmentBeginToShow) { - chatListView.setAnimateEmptyView(false, 1); + chatListView.setAnimateEmptyView(false, 0); chatListView.setEmptyView(emptyViewContainer); - chatListView.setAnimateEmptyView(true, 1); + chatListView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA_SCALE); } else { chatListView.setEmptyView(emptyViewContainer); } @@ -15081,7 +15655,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatScrollHelperCallback.lastBottom = true; chatScrollHelperCallback.lastItemOffset = 0; chatScrollHelperCallback.lastPadding = (int) chatListViewPaddingTop; - chatScrollHelper.scrollToPosition(0, 0, true, true); + chatScrollHelper.scrollToPosition(chatScrollHelperCallback.position = 0, chatScrollHelperCallback.offset = 0, chatScrollHelperCallback.bottom = true, true); } else { MessageObject object = messagesDict[loadIndex].get(postponedScrollMessageId); if (object != null) { @@ -15120,7 +15694,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatScrollHelperCallback.lastBottom = false; chatScrollHelperCallback.lastItemOffset = yOffset; chatScrollHelperCallback.lastPadding = (int) chatListViewPaddingTop; - chatScrollHelper.scrollToPosition(chatAdapter.messagesStartRow + k, yOffset, false, true); + chatScrollHelper.scrollToPosition(chatScrollHelperCallback.position = chatAdapter.messagesStartRow + k, chatScrollHelperCallback.offset = yOffset, chatScrollHelperCallback.bottom = false, true); } } } @@ -15286,7 +15860,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not finishFragment(); } } else { - if (AndroidUtilities.isTablet() && parentLayout != null && parentLayout.fragmentsStack.size() > 1) { + if (AndroidUtilities.isTablet() && parentLayout != null && parentLayout.getFragmentStack().size() > 1) { finishFragment(); } else { removeSelfFromStack(); @@ -15496,7 +16070,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not maxDate[0] = maxDate[1] = Integer.MIN_VALUE; minDate[0] = minDate[1] = 0; waitingForLoad.add(lastLoadIndex); - getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 30, 0, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 30, 0, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); loading = true; } else { if (botButtons != null) { @@ -15621,7 +16195,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not ArrayList messArr = new ArrayList<>(); messArr.add(obj); if (currentEncryptedChat == null) { - getMediaDataController().loadReplyMessagesForMessages(messArr, dialog_id, chatMode == MODE_SCHEDULED, null); + getMediaDataController().loadReplyMessagesForMessages(messArr, dialog_id, chatMode == MODE_SCHEDULED, 0, null); } if (chatAdapter != null) { chatAdapter.updateRowWithMessageObject(obj, false); @@ -15695,9 +16269,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (ChatObject.isChannel(currentChat) && currentChat.megagroup && fragmentContextView != null) { fragmentContextView.checkCall(openAnimationStartTime == 0 || SystemClock.elapsedRealtime() < openAnimationStartTime + 150); } - loadSendAsPeers(false); + loadSendAsPeers(fragmentBeginToShow); if (chatActivityEnterView != null) { - chatActivityEnterView.updateSendAsButton(false); + chatActivityEnterView.updateSendAsButton(fragmentBeginToShow); chatActivityEnterView.updateFieldHint(false); } if (chatAdapter != null) { @@ -15727,7 +16301,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (mentionContainer != null && mentionContainer.getAdapter() != null) { mentionContainer.getAdapter().setChatInfo(chatInfo); } - if (!isThreadChat()) { + if (!isThreadChat() || isTopic) { if (avatarContainer != null) { avatarContainer.updateOnlineCount(); avatarContainer.updateSubtitle(); @@ -15812,7 +16386,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return; } AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity(), themeDelegate); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setTitle(LocaleController.getString(R.string.AppName)); + Map colorsReplacement = new HashMap<>(); + colorsReplacement.put("info1.**", getThemedColor(Theme.key_dialogTopBackground)); + colorsReplacement.put("info2.**", getThemedColor(Theme.key_dialogTopBackground)); + builder.setTopAnimation(R.raw.not_available, AlertsCreator.NEW_DENY_DIALOG_TOP_ICON_SIZE, false, getThemedColor(Theme.key_dialogTopBackground), colorsReplacement); + builder.setTopAnimationIsNew(true); if (reason == 0) { if (currentChat instanceof TLRPC.TL_channelForbidden) { builder.setMessage(LocaleController.getString("ChannelCantOpenBannedByAdmin", R.string.ChannelCantOpenBannedByAdmin)); @@ -15828,7 +16407,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not builder.setTitle(LocaleController.getString(R.string.ChannelPrivate)); builder.setMessage(LocaleController.getString("JoinByPeekChannelText", R.string.JoinByPeekChannelText)); } - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + builder.setPositiveButton(LocaleController.getString(R.string.Close), null); if (showDialog(closeChatDialog = builder.create()) == null) { showCloseChatDialogLater = true; } @@ -15943,8 +16522,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not sendSecretMessageRead(messageObject, true); if ((messageObject.isRoundVideo() || messageObject.isVideo()) && fragmentView != null && fragmentView.getParent() != null) { - MediaController.getInstance().setTextureView(createTextureView(true), aspectRatioFrameLayout, videoPlayerContainer, true); - updateTextureViewPosition(true); + if (!messageObject.isVoiceTranscriptionOpen()) { + MediaController.getInstance().setTextureView(createTextureView(true), aspectRatioFrameLayout, videoPlayerContainer, true); + updateTextureViewPosition(true); + } else { + MediaController.getInstance().setTextureView(createTextureView(true), aspectRatioFrameLayout, videoPlayerContainer, true, true); + updateTextureViewPosition(false); + } } if (chatListView != null) { @@ -15984,7 +16568,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatAdapter.notifyItemChanged(position); } } - } else if (messageObject1.isVoice() || messageObject1.isMusic()) { + } else if (messageObject1.isVoice() || messageObject1.isMusic() || messageObject1.isRoundVideo()) { cell.updateButtonState(false, true, false); } } @@ -15997,7 +16581,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (view instanceof ContextLinkCell) { ContextLinkCell cell = (ContextLinkCell) view; MessageObject messageObject1 = cell.getMessageObject(); - if (messageObject1 != null && (messageObject1.isVoice() || messageObject1.isMusic())) { + if (messageObject1 != null && (messageObject1.isVoice() || messageObject1.isMusic() || messageObject1.isRoundVideo())) { cell.updateButtonState(false, true); } } @@ -16055,17 +16639,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not ChatMessageCell cell = (ChatMessageCell) view; MessageObject messageObject = cell.getMessageObject(); if (messageObject != null) { - if (messageObject.isVoice() || messageObject.isMusic()) { - cell.updateButtonState(false, true, false); - } else if (messageObject.isVideo()) { - cell.updateButtonState(false, true, false); - if (!MediaController.getInstance().isPlayingMessage(messageObject) && !MediaController.getInstance().isGoingToShowMessageObject(messageObject)) { - AnimatedFileDrawable animation = cell.getPhotoImage().getAnimation(); - if (animation != null) { - animation.start(); - } - } - } else if (messageObject.isRoundVideo()) { + if (messageObject.isRoundVideo()) { if (!MediaController.getInstance().isPlayingMessage(messageObject)) { Bitmap bitmap = null; if (id == NotificationCenter.messagePlayingDidReset && cell.getMessageObject().getId() == messageId && videoTextureView != null) { @@ -16081,6 +16655,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (position >= 0) { chatAdapter.notifyItemChanged(position); } + } else if (messageObject.isVoice() || messageObject.isMusic()) { + cell.updateButtonState(false, true, false); + } else if (messageObject.isVideo()) { + cell.updateButtonState(false, true, false); + if (!MediaController.getInstance().isPlayingMessage(messageObject) && !MediaController.getInstance().isGoingToShowMessageObject(messageObject)) { + AnimatedFileDrawable animation = cell.getPhotoImage().getAnimation(); + if (animation != null) { + animation.start(); + } + } } } } @@ -16092,7 +16676,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (view instanceof ContextLinkCell) { ContextLinkCell cell = (ContextLinkCell) view; MessageObject messageObject = cell.getMessageObject(); - if (messageObject != null && (messageObject.isVoice() || messageObject.isMusic())) { + if (messageObject != null && (messageObject.isVoice() || messageObject.isRoundVideo() || messageObject.isMusic())) { cell.updateButtonState(false, true); } } @@ -16267,12 +16851,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not MessageObject messageObject = (MessageObject) args[0]; long transcriptionId = 0; String transcriptionText = null; - if (args[1] != null) { + if (args.length > 1 && args[1] != null) { transcriptionId = (Long) args[1]; transcriptionText = (String) args[2]; } ArrayList messages = chatAdapter.isFrozen ? chatAdapter.frozenMessages : ChatActivity.this.messages; - if (messages != null && !messages.contains(messageObject) && args[1] != null) { + if (messages != null && !messages.contains(messageObject) && args.length > 1 && args[1] != null) { for (int a = 0; a < messages.size(); ++a) { if (messages.get(a) != null && messages.get(a).messageOwner != null && (messages.get(a).messageOwner.voiceTranscriptionId == transcriptionId || messageObject != null && messageObject.getId() == messages.get(a).getId() && messageObject.getDialogId() == messages.get(a).getDialogId())) { messageObject = messages.get(a); @@ -16284,7 +16868,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (transcriptionText != null && messageObject.messageOwner != null) { messageObject.messageOwner.voiceTranscription = transcriptionText; } - boolean wasOpen = messageObject.isVoiceTranscriptionOpen(); if (args.length > 3 && args[3] != null) { messageObject.messageOwner.voiceTranscriptionOpen = (Boolean) args[3]; } @@ -16296,18 +16879,24 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (index >= 0 && index < messages.size()) { int position = index + chatAdapter.messagesStartRow; chatAdapter.updateRowAtPosition(position); - for (int i = 0; i < chatListView.getChildCount(); ++i) { - View child = chatListView.getChildAt(i); - if (child instanceof ChatMessageCell && ((ChatMessageCell) child).getMessageObject() == messageObject) { - int top = child.getTop() - (int) chatListViewPaddingTop; - int halfHeight = (int) ((chatListView.getMeasuredHeight() - chatListViewPaddingTop) / 2); - if (messageObject.measureVoiceTranscriptionHeight() > halfHeight * .4f) { - chatLayoutManager.scrollToPositionWithOffset(position, (top > halfHeight * .6f && messageObject.isVoiceTranscriptionOpen() ? (int) (halfHeight * .6f) : top), false); + if (args.length > 3 && args[3] != null) { + for (int i = 0; i < chatListView.getChildCount(); ++i) { + View child = chatListView.getChildAt(i); + if (child instanceof ChatMessageCell && ((ChatMessageCell) child).getMessageObject() == messageObject) { + int top = child.getTop() - (int) chatListViewPaddingTop; + int halfHeight = (int) ((chatListView.getMeasuredHeight() - chatListViewPaddingTop) / 2); + if (messageObject.measureVoiceTranscriptionHeight() > halfHeight * .4f) { + chatLayoutManager.scrollToPositionWithOffset(position, (top > halfHeight * .6f && messageObject.isVoiceTranscriptionOpen() ? (int) (halfHeight * .6f) : top), false); + } + break; } - break; } } } + + AndroidUtilities.runOnUIThread(() -> { + updateMessagesVisiblePart(false); + }); } } } else if (id == NotificationCenter.animatedEmojiDocumentLoaded) { @@ -16386,7 +16975,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } boolean updated = false; if (arrayList != null) { - getMediaDataController().loadReplyMessagesForMessages(arrayList, dialog_id, false, null); + getMediaDataController().loadReplyMessagesForMessages(arrayList, dialog_id, false, 0, null); } for (int a = 0, N = ids.size(); a < N; a++) { Integer mid = ids.get(a); @@ -16394,7 +16983,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (pinnedMessageObjects.containsKey(mid)) { continue; } - pinnedMessageIds.add(mid); + MessageObject object = oldPinned.get(mid); if (object == null) { object = messagesDict[0].get(mid); @@ -16414,9 +17003,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (object == null && replaceObjects != null) { object = replaceObjects.get(mid); } - pinnedMessageObjects.put(mid, object); - if (replaceObjects == null) { - totalPinnedMessagesCount++; + if (object != null && (!isTopic || getTopicId() == MessageObject.getTopicId(object.messageOwner))) { + pinnedMessageIds.add(mid); + pinnedMessageObjects.put(mid, object); + if (replaceObjects == null) { + totalPinnedMessagesCount++; + } } } else { if (!pinnedMessageObjects.containsKey(mid)) { @@ -16476,7 +17068,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } loadingPinnedMessages.remove(message.getId()); } - getMediaDataController().loadReplyMessagesForMessages(arrayList, dialog_id, false, null); + getMediaDataController().loadReplyMessagesForMessages(arrayList, dialog_id, false, 0, null); updateMessagesVisiblePart(false); } else { pinnedMessageIds.clear(); @@ -16686,8 +17278,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not boolean hasChatInBack = false; if (threadMessageObject != null && parentLayout != null) { - for (int a = 0, N = parentLayout.fragmentsStack.size() - 1; a < N; a++) { - BaseFragment fragment = parentLayout.fragmentsStack.get(a); + for (int a = 0, N = parentLayout.getFragmentStack().size() - 1; a < N; a++) { + BaseFragment fragment = parentLayout.getFragmentStack().get(a); if (fragment != this && fragment instanceof ChatActivity) { ChatActivity chatActivity = (ChatActivity) fragment; if (chatActivity.needRemovePreviousSameChatActivity && chatActivity.dialog_id == dialog_id && chatActivity.getChatMode() == getChatMode()) { @@ -16796,15 +17388,32 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not pinnedMessageIds = pinnedMessages; pinnedMessageObjects = (HashMap) args[2]; } + int fallbackId = pinnedMessageIds.isEmpty() ? 0 : pinnedMessageIds.get(0); + if (isTopic) { + for (int i = 0; i < pinnedMessageIds.size(); i++) { + int messageId = pinnedMessages.get(i); + if (pinnedMessageObjects.get(messageId) == null) { + pinnedMessageIds.remove(i); + i--; + continue; + } + int messageTopicId = MessageObject.getTopicId(pinnedMessageObjects.get(messageId).messageOwner); + if (getTopicId() != messageTopicId) { + pinnedMessageObjects.remove(messageId); + pinnedMessageIds.remove(i); + i--; + } + } + } loadedPinnedMessagesCount = pinnedMessageIds.size(); totalPinnedMessagesCount = (Integer) args[3]; pinnedEndReached = (Boolean) args[4]; - getMediaDataController().loadReplyMessagesForMessages(new ArrayList<>(pinnedMessageObjects.values()), dialog_id, false, null); + getMediaDataController().loadReplyMessagesForMessages(new ArrayList<>(pinnedMessageObjects.values()), dialog_id, false, 0, null); if (!inMenuMode && !loadingPinnedMessagesList && totalPinnedMessagesCount == 0 && !pinnedEndReached) { - getMediaDataController().loadPinnedMessages(dialog_id, 0, pinnedMessageIds.isEmpty() ? 0 : pinnedMessageIds.get(0)); + getMediaDataController().loadPinnedMessages(dialog_id, 0, fallbackId); loadingPinnedMessagesList = true; } } @@ -16916,8 +17525,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } } else if (id == NotificationCenter.updateMentionsCount) { - if (dialog_id == (Long) args[0]) { - int count = (int) args[1]; + long dialogId = (Long) args[0]; + int topicId = (Integer) args[1]; + if (dialog_id == dialogId && this.getTopicId() == topicId) { + int count = (int) args[2]; if (newMentionsCount > count) { newMentionsCount = count; if (newMentionsCount <= 0) { @@ -16981,7 +17592,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (id == NotificationCenter.dialogDeleted) { long did = (Long) args[0]; if (did == dialog_id) { - if (parentLayout != null && parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 1) == this) { + if (parentLayout != null && parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 1) == this) { finishFragment(); } else { removeSelfFromStack(); @@ -16998,16 +17609,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } else if (id == NotificationCenter.chatAvailableReactionsUpdated) { long chatId = (long) args[0]; - if (chatId == -dialog_id) { + int topicId = (int) args[1]; + if (chatId == -dialog_id && (!isTopic || getTopicId() == topicId)) { chatInfo = getMessagesController().getChatFull(chatId); } } else if (id == NotificationCenter.dialogsUnreadReactionsCounterChanged) { long dialogId = (long) args[0]; - if (dialogId == dialog_id) { - reactionsMentionCount = (int) args[1]; + int topicId = (int) args[1]; + if (dialogId == dialog_id && (!isTopic || topicId == getTopicId())) { + reactionsMentionCount = (int) args[2]; ArrayList messages = null; if (args[2] != null) { - messages = (ArrayList) args[2]; + messages = (ArrayList) args[3]; } if (messages != null) { for (int i = 0; i < messages.size(); i++) { @@ -17015,7 +17628,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not ChatMessageCell cell = findMessageCell(messageId, true); if (cell != null && reactionsMentionCount > 0) { reactionsMentionCount--; - getMessagesStorage().markMessageReactionsAsRead(getDialogId(), cell.getMessageObject().getId(), true); + getMessagesStorage().markMessageReactionsAsRead(getDialogId(), getTopicId(), cell.getMessageObject().getId(), true); AndroidUtilities.runOnUIThread(() -> { playReactionAnimation(messageId); }, 200); @@ -17024,15 +17637,36 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (reactionsMentionCount <= 0) { reactionsMentionCount = 0; - getMessagesController().markReactionsAsRead(dialogId); + getMessagesController().markReactionsAsRead(dialogId, getTopicId()); } updateReactionsMentionButton(true); } + } else if (id == NotificationCenter.topicsDidLoaded) { + if (isTopic) { + updateTopicTitleIcon(); + updateBottomOverlay(); + updateTopPanel(true); + } + } else if (id == NotificationCenter.chatSwithcedToForum) { +// long chatId = (long) args[0]; +// if (-dialog_id == chatId) { +// if (chatMode == 0 && getMessagesController().getChat(-dialog_id).forum) { +// if (getParentLayout() != null) { +// if (getParentLayout().checkTransitionAnimation()) { +// AndroidUtilities.runOnUIThread(() -> { +// TopicsFragment.prepareToSwitchAnimation(ChatActivity.this); +// }, 500); +// } else { +// TopicsFragment.prepareToSwitchAnimation(ChatActivity.this); +// } +// } + + } } private void checkSecretMessageForLocation(MessageObject messageObject) { - if (messageObject.type != 4 || locationAlertShown || SharedConfig.isSecretMapPreviewSet()) { + if (messageObject.type != MessageObject.TYPE_GEO || locationAlertShown || SharedConfig.isSecretMapPreviewSet()) { return; } locationAlertShown = true; @@ -17043,7 +17677,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (view instanceof ChatMessageCell) { ChatMessageCell cell = (ChatMessageCell) view; MessageObject message = cell.getMessageObject(); - if (message.type == 4) { + if (message.type == MessageObject.TYPE_GEO) { cell.forceResetMessageObject(); } } @@ -17061,28 +17695,32 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + private int sponsoredMessagesPostsBetween; private boolean sponsoredMessagesAdded; private void addSponsoredMessages(boolean animated) { if (sponsoredMessagesAdded || chatMode != 0 || !ChatObject.isChannel(currentChat) || !forwardEndReached[0] || getUserConfig().isPremium()) { return; } - ArrayList arrayList = getMessagesController().getSponsoredMessages(dialog_id); - if (arrayList == null) { + MessagesController.SponsoredMessagesInfo res = getMessagesController().getSponsoredMessages(dialog_id); + if (res == null || res.messages == null) { return; } - for (int i = 0; i < arrayList.size(); i++) { - MessageObject messageObject = arrayList.get(i); + for (int i = 0; i < res.messages.size(); i++) { + MessageObject messageObject = res.messages.get(i); messageObject.resetLayout(); long dialogId = MessageObject.getPeerId(messageObject.messageOwner.from_id); - int messageId = 0 ; + int messageId = 0; if (messageObject.sponsoredChannelPost != 0) { messageId = messageObject.sponsoredChannelPost; } getMessagesController().ensureMessagesLoaded(dialogId, messageId, null); - } sponsoredMessagesAdded = true; - processNewMessages(arrayList); + sponsoredMessagesPostsBetween = res.posts_between != null ? res.posts_between : 0; + if (notPushedSponsoredMessages != null) { + notPushedSponsoredMessages.clear(); + } + processNewMessages(res.messages); } private void checkGroupCallJoin(boolean fromServer) { @@ -17275,8 +17913,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatActivityEnterView.setFieldText(query); } else { getMediaDataController().saveDraft(inlineReturn, 0, query, null, null, false); - if (parentLayout.fragmentsStack.size() > 1) { - BaseFragment prevFragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2); + if (parentLayout.getFragmentStack().size() > 1) { + BaseFragment prevFragment = parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 2); if (prevFragment instanceof ChatActivity && ((ChatActivity) prevFragment).dialog_id == inlineReturn) { finishFragment(); } else { @@ -17390,6 +18028,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + private ArrayList notPushedSponsoredMessages; private void processNewMessages(ArrayList arr) { long currentUserId = getUserConfig().getClientUserId(); boolean updateChat = false; @@ -17552,7 +18191,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not needAnimateToMessage = obj; } if (obj.isOut() && obj.wasJustSent) { - scrollToLastMessage(true); + scrollToLastMessage(true, false); return; } if (obj.type < 0 || messagesDict[0].indexOfKey(messageId) >= 0) { @@ -17567,7 +18206,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (threadMessageId != 0 && obj.messageOwner instanceof TLRPC.TL_messageEmpty) { continue; } - if (threadMessageObject != null && obj.isReply() && !(obj.messageOwner.action instanceof TLRPC.TL_messageActionPinMessage)) { + if (!isTopic && threadMessageObject != null && obj.isReply() && !(obj.messageOwner.action instanceof TLRPC.TL_messageActionPinMessage)) { int mid = obj.getReplyAnyMsgId(); if (threadMessageObject.getId() == mid) { threadMessageObject.messageOwner.replies.replies++; @@ -17582,7 +18221,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not last_message_id = Math.min(last_message_id, messageId); } - if (threadMessageId == 0) { + if (threadMessageId == 0 || isTopic) { if (obj.messageOwner.mentioned && obj.isContentUnread()) { newMentionsCount++; } @@ -17590,7 +18229,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (!isAd) { newUnreadMessageCount++; } - if (obj.type == 10 || obj.type == 11) { + if (obj.type == 10 || obj.type == MessageObject.TYPE_ACTION_PHOTO) { updateChat = true; } } @@ -17616,6 +18255,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } MessageObject lastActionSetChatThemeMessageObject = null; + int lastAdIndex = -1; for (int a = 0; a < arr.size(); a++) { MessageObject obj = arr.get(a); if (obj.scheduled != (chatMode == MODE_SCHEDULED) || threadMessageId != 0 && threadMessageId != obj.getReplyTopMsgId() && threadMessageId != obj.getReplyMsgId()) { @@ -17743,6 +18383,35 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } } + if (isAd && sponsoredMessagesPostsBetween > 0) { + if (lastAdIndex < 0) { + placeToPaste = lastAdIndex = 0; + } else { + placeToPaste = lastAdIndex + 1; + int h = 0, interval = 0; + boolean good = false; + for (; placeToPaste < messages.size(); ++placeToPaste) { + MessageObject msg = messages.get(placeToPaste); + if (msg != null && !msg.isSponsored()) { + h += msg.getApproximateHeight(); + interval++; + } + if (interval > sponsoredMessagesPostsBetween && h > AndroidUtilities.displaySize.y) { + placeToPaste = Math.min(placeToPaste + 1, messages.size()); + good = true; + break; + } + } + if (!good || placeToPaste > messages.size()) { + if (notPushedSponsoredMessages == null) { + notPushedSponsoredMessages = new ArrayList<>(); + } + notPushedSponsoredMessages.add(obj); + continue; + } + lastAdIndex = placeToPaste; + } + } if (currentEncryptedChat != null && obj.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && obj.messageOwner.media.webpage instanceof TLRPC.TL_webPageUrlPending) { if (webpagesToReload == null) { webpagesToReload = new HashMap<>(); @@ -17871,6 +18540,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (placeToPaste == 0 && !obj.isSponsored()) { needMoveScrollToLastMessage = true; } + if (notPushedSponsoredMessages != null && notPushedSponsoredMessages.contains(obj)) { + notPushedSponsoredMessages.remove(obj); + } if (chatAdapter != null) { chatAdapter.notifyItemChanged(placeToPaste); chatAdapter.notifyItemInserted(placeToPaste); @@ -17884,7 +18556,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not changeBoundAnimator.start(); } } - if (threadMessageId == 0) { + if (threadMessageId == 0 || isTopic) { if (!obj.isOut() && obj.messageOwner.mentioned && obj.isContentUnread()) { newMentionsCount++; } @@ -17892,7 +18564,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (!isAd) { newUnreadMessageCount++; } - if (obj.type == 10 || obj.type == 11) { + if (obj.type == 10 || obj.type == MessageObject.TYPE_ACTION_PHOTO) { updateChat = true; } if (obj.messageOwner.action instanceof TLRPC.TL_messageActionSetChatTheme) { @@ -18005,6 +18677,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } checkWaitingForReplies(); updateReplyMessageHeader(true); + + if (!isAd && notPushedSponsoredMessages != null && !notPushedSponsoredMessages.isEmpty() && arr != notPushedSponsoredMessages) { + processNewMessages(notPushedSponsoredMessages); + } } private int getSponsoredMessagesCount() { @@ -18043,8 +18719,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not boolean updatedReplies = false; if (threadMessageObject != null && parentLayout != null) { - for (int a = 0, N = parentLayout.fragmentsStack.size() - 1; a < N; a++) { - BaseFragment fragment = parentLayout.fragmentsStack.get(a); + for (int a = 0, N = parentLayout.getFragmentStack().size() - 1; a < N; a++) { + BaseFragment fragment = parentLayout.getFragmentStack().get(a); if (fragment != this && fragment instanceof ChatActivity) { ChatActivity chatActivity = (ChatActivity) fragment; if (chatActivity.needRemovePreviousSameChatActivity && chatActivity.dialog_id == dialog_id && chatActivity.getChatMode() == getChatMode()) { @@ -18201,7 +18877,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not maxDate[0] = maxDate[1] = Integer.MIN_VALUE; minDate[0] = minDate[1] = 0; waitingForLoad.add(lastLoadIndex); - getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 30, 0, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 30, 0, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); loading = true; } else { if (botButtons != null) { @@ -18416,14 +19092,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return; } final long channelId = obj.messageOwner.action.channel_id; - final BaseFragment lastFragment = parentLayout.fragmentsStack.size() > 0 ? parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 1) : null; - int index = parentLayout.fragmentsStack.indexOf(ChatActivity.this); + final BaseFragment lastFragment = parentLayout.getFragmentStack().size() > 0 ? parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 1) : null; + int index = parentLayout.getFragmentStack().indexOf(ChatActivity.this); - final ActionBarLayout actionBarLayout = parentLayout; + INavigationLayout actionBarLayout = parentLayout; if (index > 0 && !(lastFragment instanceof ChatActivity) && !(lastFragment instanceof ProfileActivity) && currentChat.creator) { - for (int a = index, N = actionBarLayout.fragmentsStack.size() - 1; a < N; a++) { - BaseFragment fragment = actionBarLayout.fragmentsStack.get(a); + for (int a = index, N = actionBarLayout.getFragmentStack().size() - 1; a < N; a++) { + BaseFragment fragment = actionBarLayout.getFragmentStack().get(a); if (fragment instanceof ChatActivity) { final Bundle bundle = new Bundle(); bundle.putLong("chat_id", channelId); @@ -18457,7 +19133,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not getNotificationCenter().postNotificationName(NotificationCenter.closeChats); final Bundle bundle = new Bundle(); bundle.putLong("chat_id", obj.messageOwner.action.channel_id); - actionBarLayout.addFragmentToStack(new ChatActivity(bundle), actionBarLayout.fragmentsStack.size() - 1); + actionBarLayout.addFragmentToStack(new ChatActivity(bundle), actionBarLayout.getFragmentStack().size() - 1); lastFragment.finishFragment(); }); } @@ -18537,8 +19213,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public boolean needDelayOpenAnimation() { - if (chatMode != MODE_SCHEDULED && getParentLayout().fragmentsStack.size() > 1) { - BaseFragment previousFragment = getParentLayout().fragmentsStack.get(getParentLayout().fragmentsStack.size() - 2); + if (chatMode != MODE_SCHEDULED && getParentLayout() != null && getParentLayout().getFragmentStack().size() > 1) { + BaseFragment previousFragment = getParentLayout().getFragmentStack().get(getParentLayout().getFragmentStack().size() - 2); if (previousFragment instanceof ChatActivity && ((ChatActivity) previousFragment).isKeyboardVisible()) { return false; } @@ -18547,7 +19223,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - protected void onBecomeFullyVisible() { + public void onBecomeFullyVisible() { isFullyVisible = true; super.onBecomeFullyVisible(); if (showCloseChatDialogLater) { @@ -18559,12 +19235,21 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - protected void onBecomeFullyHidden() { + public void onBecomeFullyHidden() { + if (!getMessagesController().premiumLocked && !getMessagesController().didPressTranscribeButtonEnough() && !getUserConfig().isPremium() && messages != null) { + for (int i = 0; i < messages.size(); ++i) { + MessageObject msg = messages.get(i); + if (msg != null && !msg.isOutOwner() && (msg.isVoice() || msg.isRoundVideo()) && !msg.isUnread() && (msg.isContentUnread() || ChatObject.isChannelAndNotMegaGroup(currentChat))) { + TranscribeButton.showOffTranscribe(msg, false); + } + } + } isFullyVisible = false; hideUndoViews(); if (parentLayout != null && parentLayout.getDrawerLayoutContainer() != null) { parentLayout.getDrawerLayoutContainer().setBehindKeyboardColor(Theme.getColor(Theme.key_windowBackgroundWhite)); } + TranscribeButton.resetVideoTranscriptionsOpen(); } public void saveKeyboardPositionBeforeTransition() { @@ -18590,9 +19275,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + long startMs; @Override public void onTransitionAnimationStart(boolean isOpen, boolean backward) { super.onTransitionAnimationStart(isOpen, backward); + startMs = System.currentTimeMillis(); int[] alowedNotifications = null; if (isOpen) { if (!fragmentOpened) { @@ -18633,6 +19320,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { super.onTransitionAnimationEnd(isOpen, backward); + if (getParentLayout() instanceof LNavigation && ((LNavigation) getParentLayout()).doShowOpenChat()) { + Toast.makeText(getParentActivity(), "Opened chat fragment in " + (System.currentTimeMillis() - startMs) + "ms", Toast.LENGTH_SHORT).show(); + } if (isOpen) { if (backward) { if (showPinBulletin && pinBulletin != null) { @@ -18649,6 +19339,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not checkShowBlur(false); openAnimationEnded = true; getNotificationCenter().onAnimationFinish(transitionAnimationIndex); + NotificationCenter.getGlobalInstance().onAnimationFinish(transitionAnimationGlobalIndex); if (Build.VERSION.SDK_INT >= 21) { createChatAttachView(); } @@ -18671,8 +19362,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (!backward && parentLayout != null && needRemovePreviousSameChatActivity) { - for (int a = 0, N = parentLayout.fragmentsStack.size() - 1; a < N; a++) { - BaseFragment fragment = parentLayout.fragmentsStack.get(a); + for (int a = 0, N = parentLayout.getFragmentStack().size() - 1; a < N; a++) { + BaseFragment fragment = parentLayout.getFragmentStack().get(a); if (fragment != this && fragment instanceof ChatActivity) { ChatActivity chatActivity = (ChatActivity) fragment; if (chatActivity.needRemovePreviousSameChatActivity && chatActivity.dialog_id == dialog_id && chatActivity.getChatMode() == getChatMode() && chatActivity.threadMessageId == threadMessageId && chatActivity.reportType == reportType) { @@ -18686,7 +19377,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not showScheduledOrNoSoundHint(); if (!backward && firstOpen) { - if (chatActivityEnterView != null && threadMessageObject != null && threadMessageObject.getRepliesCount() == 0 && ChatObject.canSendMessages(currentChat)) { + if (chatActivityEnterView != null && threadMessageObject != null && threadMessageObject.getRepliesCount() == 0 && ChatObject.canSendMessages(currentChat) && !isTopic) { chatActivityEnterView.setFieldFocused(); chatActivityEnterView.openKeyboard(); } @@ -18731,11 +19422,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not firstOpen = false; } - if (!backward && fromPullingDownTransition && parentLayout != null && parentLayout.fragmentsStack.size() >= 2) { - BaseFragment fragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2); - if (fragment instanceof ChatActivity) { - backToPreviousFragment = (ChatActivity) fragment; - parentLayout.fragmentsStack.remove(backToPreviousFragment); + if (PULL_DOWN_BACK_FRAGMENT) { + if (!backward && fromPullingDownTransition && parentLayout != null && parentLayout.getFragmentStack().size() >= 2) { + BaseFragment fragment = parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 2); + if (fragment instanceof ChatActivity) { + backToPreviousFragment = (ChatActivity) fragment; + parentLayout.removeFragmentFromStack(backToPreviousFragment); + } } } @@ -18745,6 +19438,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not updateMessagesVisiblePart(false); } else { getNotificationCenter().onAnimationFinish(transitionAnimationIndex); + NotificationCenter.getGlobalInstance().onAnimationFinish(transitionAnimationGlobalIndex); } contentView.invalidate(); @@ -18824,8 +19518,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not protected void onDialogDismiss(Dialog dialog) { if (closeChatDialog != null && dialog == closeChatDialog) { getMessagesController().deleteDialog(dialog_id, 0); - if (parentLayout != null && !parentLayout.fragmentsStack.isEmpty() && parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 1) != this) { - BaseFragment fragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 1); + if (parentLayout != null && !parentLayout.getFragmentStack().isEmpty() && parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 1) != this) { + BaseFragment fragment = parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 1); removeSelfFromStack(); fragment.finishFragment(); } else { @@ -18894,6 +19588,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (bottomOverlayChatText == null || chatMode == MODE_SCHEDULED) { return; } + boolean haveBeenWaiting = bottomOverlayChatWaitsReply; + bottomOverlayChatWaitsReply = false; if (reportType >= 0) { updateActionModeTitle(); } else if (chatMode == MODE_PINNED) { @@ -18918,8 +19614,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (currentChat != null) { long requestedTime = MessagesController.getNotificationsSettings(currentAccount).getLong("dialog_join_requested_time_" + dialog_id, -1); boolean shouldApply = false; - if (ChatObject.isChannel(currentChat) && !(currentChat instanceof TLRPC.TL_channelForbidden)) { - if (ChatObject.isNotInChat(currentChat) && (!isThreadChat() || currentChat.join_to_send)) { + if (currentChat.forum && !isTopic && (replyingMessageObject == null || haveBeenWaiting)) { + bottomOverlayChatWaitsReply = true; + showBottomOverlayProgress(false, false); + bottomOverlayChatText.setTextInfo(LocaleController.getString("ForumReplyToMessagesInTopic", R.string.ForumReplyToMessagesInTopic)); + bottomOverlayChatText.setEnabled(false); + } else if (ChatObject.isChannel(currentChat) && !(currentChat instanceof TLRPC.TL_channelForbidden)) { + if (ChatObject.isNotInChat(currentChat) && (!isThreadChat() || currentChat.join_to_send || ChatObject.isForum(currentChat) && currentChat.join_request)) { if (getMessagesController().isJoiningChannel(currentChat.id)) { showBottomOverlayProgress(true, false); } else { @@ -18939,7 +19640,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not showBottomOverlayProgress(false, false); } } else if (!isThreadChat()) { - if (!getMessagesController().isDialogMuted(dialog_id)) { + if (!getMessagesController().isDialogMuted(dialog_id, getTopicId())) { bottomOverlayChatText.setText(LocaleController.getString("ChannelMute", R.string.ChannelMute), false); bottomOverlayChatText.setEnabled(true); } else { @@ -18947,6 +19648,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not bottomOverlayChatText.setEnabled(true); } showBottomOverlayProgress(false, bottomOverlayProgress.getTag() != null); + } else if (forumTopic != null && forumTopic.closed) { + if (!ChatObject.canManageTopic(currentAccount, currentChat, forumTopic)) { + Drawable lock = getContext().getResources().getDrawable(R.drawable.msg_mini_lock2).mutate(); + lock.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_windowBackgroundWhiteGrayText), PorterDuff.Mode.MULTIPLY)); + bottomOverlayChatText.setTextInfo(lock, LocaleController.getString("TopicClosedByAdmin", R.string.TopicClosedByAdmin)); + bottomOverlayChatText.setEnabled(false); + } + showBottomOverlayProgress(false, false); } } else if (!isThreadChat()) { bottomOverlayChatText.setText(LocaleController.getString("DeleteThisGroup", R.string.DeleteThisGroup)); @@ -18974,7 +19683,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } } else if (UserObject.isReplyUser(currentUser)) { - if (!getMessagesController().isDialogMuted(dialog_id)) { + if (!getMessagesController().isDialogMuted(dialog_id, getTopicId())) { bottomOverlayChatText.setText(LocaleController.getString("ChannelMute", R.string.ChannelMute), false); } else { bottomOverlayChatText.setText(LocaleController.getString("ChannelUnmute", R.string.ChannelUnmute), true); @@ -19107,15 +19816,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not bottomOverlayChat.setVisibility(View.VISIBLE); chatActivityEnterView.setVisibility(View.INVISIBLE); } else if (chatMode == MODE_PINNED || - currentChat != null && (ChatObject.isNotInChat(currentChat) || !ChatObject.canWriteToChat(currentChat)) && (currentChat.join_to_send || !isThreadChat()) || + currentChat != null && ((ChatObject.isNotInChat(currentChat) || !ChatObject.canWriteToChat(currentChat)) && (currentChat.join_to_send || !isThreadChat() || ChatObject.isForum(currentChat) && currentChat.join_request) || forumTopic != null && forumTopic.closed && !ChatObject.canManageTopic(currentAccount, currentChat, forumTopic) || currentChat.forum && !isTopic && replyingMessageObject == null) || currentUser != null && (UserObject.isDeleted(currentUser) || userBlocked || UserObject.isReplyUser(currentUser))) { if (chatActivityEnterView.isEditingMessage()) { chatActivityEnterView.setVisibility(View.VISIBLE); + AndroidUtilities.updateViewShow(bottomOverlayChat, false, false, true); bottomOverlayChat.setVisibility(View.INVISIBLE); chatActivityEnterView.setFieldFocused(); AndroidUtilities.runOnUIThread(() -> chatActivityEnterView.openKeyboard(), 100); } else { bottomOverlayChat.setVisibility(View.VISIBLE); + AndroidUtilities.updateViewShow(bottomOverlayChat, true, false, true); chatActivityEnterView.setFieldFocused(false); chatActivityEnterView.setVisibility(View.INVISIBLE); chatActivityEnterView.closeKeyboard(); @@ -19137,8 +19848,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not bottomOverlayChat.setVisibility(View.VISIBLE); chatActivityEnterView.setVisibility(View.INVISIBLE); } else { - chatActivityEnterView.setVisibility(View.VISIBLE); bottomOverlayChat.setVisibility(View.INVISIBLE); + chatActivityEnterView.setVisibility(View.VISIBLE); } } if (topViewWasVisible == 1) { @@ -19151,7 +19862,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not public void updateReplyMessageHeader(boolean notify) { if (avatarContainer != null && threadMessageId != 0) { - if (isComments) { + if (isTopic) { + updateTopicHeader(); + } else if (isComments) { if (threadMessageObject.hasReplies()) { avatarContainer.setTitle(LocaleController.formatPluralString("Comments", threadMessageObject.getRepliesCount())); } else { @@ -19180,6 +19893,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + private void updateTopicHeader() { + avatarContainer.setTitle(forumTopic.title); + if (currentChat != null) { + avatarContainer.setSubtitle(LocaleController.formatString("TopicProfileStatus", R.string.TopicProfileStatus, currentChat.title)); + } + updateTopicTitleIcon(); + } + public void showAlert(String name, String message) { if (alertView == null || name == null || message == null) { return; @@ -19330,7 +20051,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } private void updatePinnedListButton(boolean animated) { - if (isThreadChat() || pinnedListButton == null) { + if ((isThreadChat() && !isTopic) || pinnedListButton == null) { return; } boolean show = pinnedMessageIds.size() > 1 && !pinnedMessageButtonShown; @@ -19406,7 +20127,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not pinnedProgress.setTag(showProgress ? 1 : null); } if (pinnedLineView != null) { - if (isThreadChat()) { + if (isThreadChat() && !isTopic) { pinnedLineView.set(0, 1, false); } else { int position = Collections.binarySearch(pinnedMessageIds, currentPinnedMessageId, Comparator.reverseOrder()); @@ -19432,7 +20153,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not int pinned_msg_id; boolean changed = false; MessageObject pinnedMessageObject; - if (isThreadChat()) { + if (isThreadChat() && !isTopic) { if (!threadMessageVisible) { pinnedMessageObject = threadMessageObject; pinned_msg_id = threadMessageId; @@ -19632,7 +20353,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not pinnedNameTextView[animateToNext != 0 ? 0 : 1].setTrackWidth(false); nameTextView.setTrackWidth(true); nameTextView.setVisibility(View.VISIBLE); - if (threadMessageId != 0) { + if (threadMessageId != 0 && !isTopic) { MessagesController messagesController = getMessagesController(); TLRPC.MessageFwdHeader fwd_from = threadMessageObject.messageOwner.fwd_from; TLRPC.User user = null; @@ -19690,14 +20411,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not nameTextView.setText(LocaleController.getString("PreviousPinnedMessage", R.string.PreviousPinnedMessage), true); } if (currentPinnedMessageIndex[0] != 0) { - int total = getPinnedMessagesCount(); - pinnedCounterTextView.setNumber(Math.min(total - 1, Math.max(1, total - currentPinnedMessageIndex[0])), animated && pinnedCounterTextView.getTag() == null); + if (isTopic) { + pinnedCounterTextView.setNumber(Math.max(1, currentPinnedMessageIndex[0]), animated && pinnedCounterTextView.getTag() == null); + } else { + int total = getPinnedMessagesCount(); + pinnedCounterTextView.setNumber(Math.min(total - 1, Math.max(1, total - currentPinnedMessageIndex[0])), animated && pinnedCounterTextView.getTag() == null); + } showCounter = true; } } } CharSequence pinnedText = null; - if (pinnedMessageObject.type == 14) { + if (pinnedMessageObject.type == MessageObject.TYPE_MUSIC) { pinnedText = String.format("%s - %s", pinnedMessageObject.getMusicAuthor(), pinnedMessageObject.getMusicTitle()); } else if (pinnedMessageObject.type == MessageObject.TYPE_POLL) { TLRPC.TL_messageMediaPoll poll = (TLRPC.TL_messageMediaPoll) pinnedMessageObject.messageOwner.media; @@ -20054,7 +20779,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not pinnedMessageImageView[1].setScaleY(1f); pinnedMessageImageView[1].setTranslationY(0); } - if (isThreadChat()) { + if (isThreadChat() && !isTopic) { pinnedLineView.set(0, 1, false); } else { int position = Collections.binarySearch(pinnedMessageIds, currentPinnedMessageId, Comparator.reverseOrder()); @@ -20105,6 +20830,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + private boolean shownRestartTopic; private void updateTopPanel(boolean animated) { if (topChatPanelView == null || chatMode != 0) { return; @@ -20133,11 +20859,21 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not int chatWithAdminDate = preferences.getInt("dialog_bar_chat_with_date" + did, 0); boolean showAddMembersToGroup = preferences.getBoolean("dialog_bar_invite" + did, false); TLRPC.EmojiStatus showEmojiStatusReport = currentUser != null && (showReport || showBlock) && (currentUser.emoji_status instanceof TLRPC.TL_emojiStatus || currentUser.emoji_status instanceof TLRPC.TL_emojiStatusUntil && ((TLRPC.TL_emojiStatusUntil) currentUser.emoji_status).until > (int) (System.currentTimeMillis() / 1000)) ? currentUser.emoji_status : null; + boolean showRestartTopic = !isInPreviewMode() && forumTopic != null && forumTopic.closed && ChatObject.canManageTopic(currentAccount, currentChat, forumTopic); + if (showRestartTopic) { + shownRestartTopic = true; + } - if (showReport || showBlock || showGeo) { - reportSpamButton.setVisibility(View.VISIBLE); - } else { - reportSpamButton.setVisibility(View.GONE); + reportSpamButton.setVisibility(showReport || showBlock || showGeo ? View.VISIBLE : View.GONE); + restartTopicButton.setVisibility(showRestartTopic || shownRestartTopic ? View.VISIBLE : View.GONE); + closeReportSpam.setVisibility(showRestartTopic || shownRestartTopic ? View.GONE : View.VISIBLE); + + if (!showRestartTopic) { + shownRestartTopic = false; + } + + if (showRestartTopic) { + show = true; } addToContactsButtonArchive = false; @@ -20256,7 +20992,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (chatWithAdminTextView != null) { chatWithAdminTextView.setVisibility(isChatWithAdmin ? View.VISIBLE : View.GONE); } - if (userBlocked || (addToContactsButton.getVisibility() == View.GONE && reportSpamButton.getVisibility() == View.GONE && (chatWithAdminTextView == null || chatWithAdminTextView.getVisibility() == View.GONE))) { + if (userBlocked || (addToContactsButton.getVisibility() == View.GONE && reportSpamButton.getVisibility() == View.GONE && (chatWithAdminTextView == null || chatWithAdminTextView.getVisibility() == View.GONE) && restartTopicButton.getVisibility() == View.GONE)) { show = false; } @@ -20446,7 +21182,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - protected void setInPreviewMode(boolean value) { + public void setInPreviewMode(boolean value) { super.setInPreviewMode(value); if (currentUser != null && audioCallIconItem != null) { TLRPC.UserFull userFull = getMessagesController().getUserFull(currentUser.id); @@ -20494,7 +21230,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not hasAllMentionsLocal = true; showMentionDownButton(false, true); } else { - mentiondownButtonCounter.setText(String.format("%d", newMentionsCount)); + mentiondownButtonCounter.setText(String.format(Locale.US, "%d", newMentionsCount)); } getMessagesController().markMentionMessageAsRead(message.getId(), ChatObject.isChannel(currentChat) ? currentChat.id : 0, dialog_id); message.setContentIsRead(); @@ -20585,7 +21321,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (chatMode == 0) { - getNotificationsController().setOpenedDialogId(dialog_id); + getNotificationsController().setOpenedDialogId(dialog_id, getTopicId()); } getMessagesController().setLastVisibleDialogId(dialog_id, chatMode == MODE_SCHEDULED, true); if (scrollToTopOnResume) { @@ -20687,7 +21423,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not paused = true; wasPaused = true; if (chatMode == 0) { - getNotificationsController().setOpenedDialogId(0); + getNotificationsController().setOpenedDialogId(0, 0); } Bulletin.removeDelegate(this); getMessagesController().setLastVisibleDialogId(dialog_id, chatMode == MODE_SCHEDULED, false); @@ -20714,7 +21450,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (chatMode == 0) { CharSequence[] message = new CharSequence[]{draftMessage}; ArrayList entities = getMediaDataController().getEntities(message, currentEncryptedChat == null || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 101); - getMediaDataController().saveDraft(dialog_id, threadMessageId, message[0], entities, replyMessage != null ? replyMessage.messageOwner : null, !searchWebpage); + getMediaDataController().saveDraft(dialog_id, threadMessageId, message[0], entities, (replyMessage != null && !replyMessage.isTopicMainMessage) ? replyMessage.messageOwner : null, !searchWebpage); getMessagesController().cancelTyping(0, dialog_id, threadMessageId); if (!pausedOnLastMessage && !firstLoading) { @@ -20898,9 +21634,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatActivityEnterView.setFieldText(""); hideFieldPanel(true); } - if (replyingMessageObject == null && draftReplyMessage != null) { + if ((replyingMessageObject == null || threadMessageObject == replyingMessageObject) && draftReplyMessage != null && !(threadMessageObject != null && threadMessageObject.getId() == draftReplyMessage.id)) { replyingMessageObject = new MessageObject(currentAccount, draftReplyMessage, getMessagesController().getUsers(), false, false); showFieldPanelForReply(replyingMessageObject); + updateBottomOverlay(); } } @@ -20971,7 +21708,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (AndroidUtilities.isSmallTablet() && ApplicationLoader.applicationContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { actionBar.setBackButtonDrawable(new BackDrawable(false)); } else { - actionBar.setBackButtonDrawable(new BackDrawable(parentLayout == null || parentLayout.fragmentsStack.isEmpty() || parentLayout.fragmentsStack.get(0) == ChatActivity.this || parentLayout.fragmentsStack.size() == 1)); + actionBar.setBackButtonDrawable(new BackDrawable(parentLayout == null || parentLayout.getFragmentStack().isEmpty() || parentLayout.getFragmentStack().get(0) == ChatActivity.this || parentLayout.getFragmentStack().size() == 1)); } return false; } @@ -21089,7 +21826,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not getFileLoader().setLoadingVideoForPlayer(message.getDocument(), false); MediaController.getInstance().cleanupPlayer(true, true, false, true); - if (PhotoViewer.getInstance().openPhoto(message, message.type != 0 ? dialog_id : 0, message.type != 0 ? mergeDialogId : 0, photoViewerProvider, false)) { + if (PhotoViewer.getInstance().openPhoto(message, message.type != 0 ? dialog_id : 0, message.type != 0 ? mergeDialogId : 0, message.type != 0 ? getTopicId() : 0, photoViewerProvider, false)) { PhotoViewer.getInstance().setParentChatActivity(ChatActivity.this); } hideHints(false); @@ -21293,7 +22030,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not boolean allowChatActions = true; boolean allowPin; - if (chatMode == MODE_SCHEDULED || isThreadChat()) { + if (chatMode == MODE_SCHEDULED || (isThreadChat() && !isTopic)) { allowPin = false; } else if (currentChat != null) { allowPin = message.getDialogId() != mergeDialogId && ChatObject.canPinMessages(currentChat); @@ -21329,7 +22066,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not message.isSponsored() || type == 1 && message.getDialogId() == mergeDialogId || message.messageOwner.action instanceof TLRPC.TL_messageActionSecureValuesSent || currentEncryptedChat == null && message.getId() < 0 || - bottomOverlayChat != null && bottomOverlayChat.getVisibility() == View.VISIBLE || + bottomOverlayChat != null && bottomOverlayChat.getVisibility() == View.VISIBLE && !(bottomOverlayChatWaitsReply && selectedObject != null && (MessageObject.getTopicId(selectedObject.messageOwner) != 0 || selectedObject.wasJustSent)) || currentChat != null && (ChatObject.isNotInChat(currentChat) && !isThreadChat() || ChatObject.isChannel(currentChat) && !ChatObject.canPost(currentChat) && !currentChat.megagroup || !ChatObject.canSendMessages(currentChat))) { allowChatActions = false; } @@ -21390,7 +22127,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not icons.add(R.drawable.msg_block2); } if (type == -1) { - if ((selectedObject.type == 0 || selectedObject.isAnimatedEmoji() || selectedObject.isAnimatedEmojiStickers() || getMessageCaption(selectedObject, selectedObjectGroup) != null) && !noforwards) { + if ((selectedObject.type == MessageObject.TYPE_TEXT || selectedObject.isAnimatedEmoji() || selectedObject.isAnimatedEmojiStickers() || getMessageCaption(selectedObject, selectedObjectGroup) != null) && !noforwards) { items.add(LocaleController.getString("Copy", R.string.Copy)); options.add(OPTION_COPY); icons.add(R.drawable.msg_copy); @@ -21417,6 +22154,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not options.add(OPTION_VIEW_REPLIES_OR_THREAD); icons.add(R.drawable.msg_viewreplies); } + if (selectedObject != null && selectedObject.messageOwner != null && selectedObject.messageOwner.action == null && currentChat != null && currentChat.forum && !isTopic && selectedObject.messageOwner != null && selectedObject.messageOwner.reply_to != null && selectedObject.messageOwner.reply_to.forum_topic) { + items.add(LocaleController.getString("ViewInTopic", R.string.ViewInTopic)); + options.add(OPTION_VIEW_IN_TOPIC); + icons.add(R.drawable.msg_viewintopic); + } if (allowUnpin) { items.add(LocaleController.getString("UnpinMessage", R.string.UnpinMessage)); options.add(OPTION_UNPIN); @@ -21448,7 +22190,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not icons.add(R.drawable.msg_reply); } } - if (message.canDeleteMessage(chatMode == MODE_SCHEDULED, currentChat) && (threadMessageObjects == null || !threadMessageObjects.contains(message))) { + if (message.canDeleteMessage(chatMode == MODE_SCHEDULED, currentChat) && (threadMessageObjects == null || !threadMessageObjects.contains(message)) && !(message != null && message.messageOwner != null && message.messageOwner.action instanceof TLRPC.TL_messageActionTopicCreate)) { items.add(LocaleController.getString("Delete", R.string.Delete)); options.add(OPTION_DELETE); icons.add(selectedObject.messageOwner.ttl_period != 0 ? R.drawable.msg_delete_auto : R.drawable.msg_delete); @@ -21488,7 +22230,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not options.add(OPTION_REPLY); icons.add(R.drawable.msg_reply); } - if ((selectedObject.type == 0 || selectedObject.isDice() || selectedObject.isAnimatedEmoji() || selectedObject.isAnimatedEmojiStickers() || getMessageCaption(selectedObject, selectedObjectGroup) != null) && !noforwards) { + if ((selectedObject.type == MessageObject.TYPE_TEXT || selectedObject.isDice() || selectedObject.isAnimatedEmoji() || selectedObject.isAnimatedEmojiStickers() || getMessageCaption(selectedObject, selectedObjectGroup) != null) && !noforwards) { items.add(LocaleController.getString("Copy", R.string.Copy)); options.add(OPTION_COPY); icons.add(R.drawable.msg_copy); @@ -21507,6 +22249,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not options.add(OPTION_COPY_LINK); icons.add(R.drawable.msg_link); } + if (selectedObject != null && selectedObject.messageOwner != null && selectedObject.messageOwner.action == null && currentChat != null && currentChat.forum && !isTopic && selectedObject.messageOwner != null && selectedObject.messageOwner.reply_to != null && selectedObject.messageOwner.reply_to.forum_topic) { + items.add(LocaleController.getString("ViewInTopic", R.string.ViewInTopic)); + options.add(OPTION_VIEW_IN_TOPIC); + icons.add(R.drawable.msg_viewintopic); + } if (type == 2) { if (chatMode != MODE_SCHEDULED) { if (selectedObject.type == MessageObject.TYPE_POLL && !message.isPollClosed()) { @@ -21672,7 +22419,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } if (!selectedObject.isSponsored() && chatMode != MODE_SCHEDULED && (!selectedObject.needDrawBluredPreview() || selectedObject.hasExtendedMediaPreview()) && - !selectedObject.isLiveLocation() && selectedObject.type != 16 && !noforwards && + !selectedObject.isLiveLocation() && selectedObject.type != MessageObject.TYPE_PHONE_CALL && !noforwards && selectedObject.type != MessageObject.TYPE_GIFT_PREMIUM) { items.add(LocaleController.getString("Forward", R.string.Forward)); options.add(OPTION_FORWARD); @@ -21724,7 +22471,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not options.add(OPTION_REPLY); icons.add(R.drawable.msg_reply); } - if ((selectedObject.type == 0 || selectedObject.isAnimatedEmoji() || selectedObject.isAnimatedEmojiStickers() || getMessageCaption(selectedObject, selectedObjectGroup) != null) && !noforwards) { + if ((selectedObject.type == MessageObject.TYPE_TEXT || selectedObject.isAnimatedEmoji() || selectedObject.isAnimatedEmojiStickers() || getMessageCaption(selectedObject, selectedObjectGroup) != null) && !noforwards) { items.add(LocaleController.getString("Copy", R.string.Copy)); options.add(OPTION_COPY); icons.add(R.drawable.msg_copy); @@ -21738,6 +22485,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not options.add(OPTION_VIEW_REPLIES_OR_THREAD); icons.add(R.drawable.msg_viewreplies); } + if (selectedObject != null && selectedObject.messageOwner != null && selectedObject.messageOwner.action == null && currentChat != null && currentChat.forum && !isTopic && selectedObject.messageOwner != null && selectedObject.messageOwner.reply_to != null && selectedObject.messageOwner.reply_to.forum_topic) { + items.add(LocaleController.getString("ViewInTopic", R.string.ViewInTopic)); + options.add(OPTION_VIEW_IN_TOPIC); + icons.add(R.drawable.msg_viewintopic); + } if (type == 4 && !noforwards && !selectedObject.hasRevealedExtendedMedia()) { if (selectedObject.isVideo()) { items.add(LocaleController.getString("SaveToGallery", R.string.SaveToGallery)); @@ -21959,7 +22711,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (index >= 0) { reactionCount = counters.get(index); } - ReactedUsersListView v = new ReactedUsersListView(container.getContext(), themeDelegate, currentAccount, message, reactionCount, true) + ReactedUsersListView v = new ReactedUsersListView(container.getContext(), themeDelegate, currentAccount, message, reactionCount, false) .setSeenUsers(reactedView.getSeenUsers()) .setOnCustomEmojiSelectedListener((reactedUsersListView1, customEmojiStickerSets) -> { EmojiPacksAlert alert = new EmojiPacksAlert(ChatActivity.this, getParentActivity(), themeDelegate, customEmojiStickerSets) { @@ -22046,7 +22798,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not gap.setBackgroundColor(getThemedColor(Theme.key_actionBarDefaultSubmenuSeparator)); linearLayout.addView(gap, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 8)); - reactedUsersListView = new ReactedUsersListView(contentView.getContext(), themeDelegate, currentAccount, message, null, true) + reactedUsersListView = new ReactedUsersListView(contentView.getContext(), themeDelegate, currentAccount, message, null, false) .setSeenUsers(reactedView.getSeenUsers()) .setOnCustomEmojiSelectedListener((reactedUsersListView1, customEmojiStickerSets) -> { EmojiPacksAlert alert = new EmojiPacksAlert(ChatActivity.this, getParentActivity(), themeDelegate, customEmojiStickerSets) { @@ -22108,6 +22860,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not backContainer.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { + Bulletin.hideVisible(); popupLayout.getSwipeBack().closeForeground(); } }); @@ -22135,16 +22888,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not int totalHeight = contentView.getHeightWithKeyboard(); int availableHeight = totalHeight - scrimPopupY - AndroidUtilities.dp(46 + 16) - (isReactionsAvailable ? AndroidUtilities.dp(52) : 0); - if (SharedConfig.messageSeenHintCount > 0 && contentView.getKeyboardHeight() < AndroidUtilities.dp(20)) { +// if (SharedConfig.messageSeenHintCount > 0 && contentView.getKeyboardHeight() < AndroidUtilities.dp(20)) { availableHeight -= AndroidUtilities.dp(52); - Bulletin bulletin = BulletinFactory.of(ChatActivity.this).createErrorBulletin(AndroidUtilities.replaceTags(LocaleController.getString("MessageSeenTooltipMessage", R.string.MessageSeenTooltipMessage))); - bulletin.tag = 1; + Bulletin bulletin = BulletinFactory.of(Bulletin.BulletinWindow.make(getContext()), themeDelegate).createErrorBulletin(AndroidUtilities.replaceTags(LocaleController.getString("MessageSeenTooltipMessage", R.string.MessageSeenTooltipMessage))); bulletin.setDuration(4000); bulletin.show(); SharedConfig.updateMessageSeenHintCount(SharedConfig.messageSeenHintCount - 1); - } else if (contentView.getKeyboardHeight() > AndroidUtilities.dp(20)) { - availableHeight -= contentView.getKeyboardHeight() / 3f; - } +// } else if (contentView.getKeyboardHeight() > AndroidUtilities.dp(20)) { +// availableHeight -= contentView.getKeyboardHeight() / 3f; +// } listView2.requestLayout(); linearLayout.requestLayout(); @@ -22365,7 +23117,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not fromLang[0] = lang; if (fromLang[0] != null && (!fromLang[0].equals(toLang) || fromLang[0].equals("und")) && ( translateButtonEnabled && !RestrictedLanguagesSelectActivity.getRestrictedLanguages().contains(fromLang[0]) || - (currentChat != null && (currentChat.has_link || currentChat.username != null)) && ("uk".equals(fromLang[0]) || "ru".equals(fromLang[0])) + (currentChat != null && (currentChat.has_link || ChatObject.isPublic(currentChat))) && ("uk".equals(fromLang[0]) || "ru".equals(fromLang[0])) )) { cell.setVisibility(View.VISIBLE); } @@ -22596,6 +23348,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (scrimPopupWindow != this) { return; } + Bulletin.hideVisible(); scrimPopupWindow = null; menuDeleteItem = null; scrimPopupWindowItems = null; @@ -23156,6 +23909,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not args.putInt("messagesCount", forwardingMessageGroup == null ? 1 : forwardingMessageGroup.messages.size()); args.putInt("hasPoll", forwardingMessage.isPoll() ? (forwardingMessage.isPublicPoll() ? 2 : 1) : 0); args.putBoolean("hasInvoice", forwardingMessage.isInvoice()); + args.putBoolean("canSelectTopics", true); DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate(this); presentFragment(fragment); @@ -23443,8 +24197,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not CheckBoxCell cell = new CheckBoxCell(getParentActivity(), 1, themeDelegate); cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); cell.setText(LocaleController.formatString("PinAlsoFor", R.string.PinAlsoFor, UserObject.getFirstName(currentUser)), "", false, false); - cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(8) : 0, 0, LocaleController.isRTL ? 0 : AndroidUtilities.dp(8), 0); - frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT, 8, 0, 8, 0)); + cell.setPadding(AndroidUtilities.dp(LocaleController.isRTL ? 16 : 8), 0, AndroidUtilities.dp(LocaleController.isRTL ? 8 : 16), 0); + frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT)); cell.setOnClickListener(v -> { CheckBoxCell cell1 = (CheckBoxCell) v; checks[1] = !checks[1]; @@ -23457,14 +24211,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not builder.setMessage(LocaleController.getString("PinOldMessageAlert", R.string.PinOldMessageAlert)); checks = new boolean[]{false, true}; } else { - builder.setMessage(LocaleController.getString("PinMessageAlert", R.string.PinMessageAlert)); + if (isTopic) { + builder.setMessage(LocaleController.getString("PinMessageInTopicAlert", R.string.PinMessageInTopicAlert)); + } else { + builder.setMessage(LocaleController.getString("PinMessageAlert", R.string.PinMessageAlert)); + } checks = new boolean[]{true, true}; FrameLayout frameLayout = new FrameLayout(getParentActivity()); CheckBoxCell cell = new CheckBoxCell(getParentActivity(), 1, themeDelegate); cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); cell.setText(LocaleController.getString("PinNotify", R.string.PinNotify), "", true, false); - cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(8) : 0, 0, LocaleController.isRTL ? 0 : AndroidUtilities.dp(8), 0); - frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT, 8, 0, 8, 0)); + cell.setPadding(AndroidUtilities.dp(LocaleController.isRTL ? 16 : 8), 0, AndroidUtilities.dp(LocaleController.isRTL ? 8 : 16), 0); + frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT)); cell.setOnClickListener(v -> { CheckBoxCell cell1 = (CheckBoxCell) v; checks[0] = !checks[0]; @@ -23558,7 +24316,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { req.id = selectedObject.getId(); req.channel = MessagesController.getInputChannel(currentChat); - req.thread = isReplyChatComment(); + req.thread = isReplyChatComment() || isTopic; } getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (response != null) { @@ -23679,6 +24437,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not openDiscussionMessageChat(currentChat.id, null, selectedObject.getId(), 0, -1, 0, null); break; } + case OPTION_VIEW_IN_TOPIC: { + int topicId = MessageObject.getTopicId(selectedObject.messageOwner); + if (topicId != 0) { + TLRPC.TL_forumTopic topic = getMessagesController().getTopicsController().findTopic(currentChat.id, topicId); + if (topic != null) { + ForumUtilities.openTopic(ChatActivity.this, currentChat.id, topic, selectedObject.getId()); + } + } + break; + } case OPTION_STATISTICS: { presentFragment(new MessageStatisticActivity(selectedObject)); break; @@ -23745,7 +24513,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - public void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param) { + public void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param) { if (forwardingMessage == null && selectedMessagesIds[0].size() == 0 && selectedMessagesIds[1].size() == 0) { return; } @@ -23781,11 +24549,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not updateVisibleRows(); } - if (dids.size() > 1 || dids.get(0) == getUserConfig().getClientUserId() || message != null) { + if (dids.size() > 1 || dids.get(0).dialogId == getUserConfig().getClientUserId() || message != null) { forwardingMessages = null; hideFieldPanel(false); for (int a = 0; a < dids.size(); a++) { - long did = dids.get(a); + long did = dids.get(a).dialogId; if (message != null) { getSendMessagesHelper().sendMessage(message.toString(), did, null, null, null, true, null, null, null, true, 0, null, false); } @@ -23793,12 +24561,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } fragment.finishFragment(); if (dids.size() == 1) { - undoView.showWithAction(dids.get(0), UndoView.ACTION_FWD_MESSAGES, fmessages.size()); + undoView.showWithAction(dids.get(0).dialogId, UndoView.ACTION_FWD_MESSAGES, fmessages.size()); } else { undoView.showWithAction(0, UndoView.ACTION_FWD_MESSAGES, fmessages.size(), dids.size(), null, null); } } else { - long did = dids.get(0); + MessagesStorage.TopicKey topicKey = dids.get(0); + long did = topicKey.dialogId; if (did != dialog_id || chatMode == MODE_PINNED) { Bundle args = new Bundle(); args.putBoolean("scrollToTopOnResume", scrollToTopOnResume); @@ -23816,6 +24585,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } addToPulledDialogsMyself(); ChatActivity chatActivity = new ChatActivity(args); + if (topicKey.topicId != 0) { + ForumUtilities.applyTopic(chatActivity, topicKey); + } if (presentFragment(chatActivity, true)) { chatActivity.showFieldPanelForForward(true, fmessages); if (!AndroidUtilities.isTablet()) { @@ -23894,7 +24666,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return false; } if (backToPreviousFragment != null) { - parentLayout.fragmentsStack.add(parentLayout.fragmentsStack.size() - 1, backToPreviousFragment); + parentLayout.addFragmentToStack(backToPreviousFragment, parentLayout.getFragmentStack().size() - 1); backToPreviousFragment = null; } return true; @@ -23918,7 +24690,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } - public void setThreadMessages(ArrayList messageObjects, TLRPC.Chat originalChat, int originalMessage, int maxInboxReadId, int maxOutboxReadId) { + public void setThreadMessages(ArrayList messageObjects, TLRPC.Chat originalChat, int originalMessage, int maxInboxReadId, int maxOutboxReadId, TLRPC.TL_forumTopic forumTopic) { + this.forumTopic = forumTopic; threadMessageObjects = messageObjects; replyingMessageObject = threadMessageObject = threadMessageObjects.get(threadMessageObjects.size() - 1); threadMaxInboxReadId = maxInboxReadId; @@ -23927,7 +24700,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not threadMessageId = threadMessageObject.getId(); replyOriginalMessageId = originalMessage; replyOriginalChat = originalChat; - isComments = replyingMessageObject.messageOwner.fwd_from != null && replyingMessageObject.messageOwner.fwd_from.channel_post != 0; + isTopic = forumTopic != null; + isComments = replyingMessageObject.messageOwner.fwd_from != null && replyingMessageObject.messageOwner.fwd_from.channel_post != 0 && !isTopic; + if (isTopic) { + replyingMessageObject.isTopicMainMessage = true; + } + updateTopPanel(false); + updateBottomOverlay(); } public void setHighlightMessageId(int id) { @@ -24070,7 +24849,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity(), themeDelegate); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - if (message.type == 3) { + if (message.type == MessageObject.TYPE_VIDEO) { builder.setMessage(LocaleController.getString("NoPlayerInstalled", R.string.NoPlayerInstalled)); } else { builder.setMessage(LocaleController.formatString("NoHandleAppInstalled", R.string.NoHandleAppInstalled, message.getDocument().mime_type)); @@ -24103,7 +24882,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not updateSearchButtons(0, 0, -1); updateBottomOverlay(); } - if (threadMessageId == 0 && !UserObject.isReplyUser(currentUser)) { + if ((threadMessageId == 0 || isTopic) && !UserObject.isReplyUser(currentUser)) { openSearchKeyboard = text == null; if (searchItem != null) { searchItem.openSearch(openSearchKeyboard); @@ -24253,7 +25032,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not showDialog(builder.create()); } else { if (Build.VERSION.SDK_INT >= 21 && !AndroidUtilities.isTablet() && WebviewActivity.supportWebview()) { - if (parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 1) == this) { + if (parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 1) == this) { presentFragment(new WebviewActivity(urlStr, user != null && !TextUtils.isEmpty(user.username) ? user.username : "", game.title, game.short_name, messageObject)); } } else { @@ -24308,7 +25087,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not args.putInt("unread_count", discussionMessage.unread_count); args.putBoolean("historyPreloaded", history != null); ChatActivity chatActivity = new ChatActivity(args); - chatActivity.setThreadMessages(arrayList, originalChat, req.msg_id, discussionMessage.read_inbox_max_id, discussionMessage.read_outbox_max_id); + chatActivity.setThreadMessages(arrayList, originalChat, req.msg_id, discussionMessage.read_inbox_max_id, discussionMessage.read_outbox_max_id, null); if (highlightMsgId != 0) { chatActivity.highlightMessageId = highlightMsgId; } @@ -24362,7 +25141,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not }; NotificationCenter.getInstance(currentAccount).addObserver(observer, NotificationCenter.messagesDidLoad); Utilities.stageQueue.postRunnable(() -> { - getMessagesController().processLoadedMessages(historyFinal, historyFinal.messages.size(), dialogId, 0, 30, (highlightMsgId > 0 ? highlightMsgId : maxReadId), 0, false, commentsClassGuid, fnidFinal, 0, 0, 0, (highlightMsgId > 0 ? 3 : 2), true, 0, arrayList.get(arrayList.size() - 1).getId(), 1, false, 0, true); + getMessagesController().processLoadedMessages(historyFinal, historyFinal.messages.size(), dialogId, 0, 30, (highlightMsgId > 0 ? highlightMsgId : maxReadId), 0, false, commentsClassGuid, fnidFinal, 0, 0, 0, (highlightMsgId > 0 ? 3 : 2), true, 0, arrayList.get(arrayList.size() - 1).getId(), 1, false, 0, true, isTopic); }); } else { openCommentsChat.run(); @@ -24463,13 +25242,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not savedHistory = (TLRPC.messages_Messages) response2; } else { if ("CHANNEL_PRIVATE".equals(error2.text)) { - if (getParentActivity() != null) { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity(), themeDelegate); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setMessage(LocaleController.getString("JoinByPeekChannelText", R.string.JoinByPeekChannelText)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - showDialog(builder.create()); - } + MessagesController.showCantOpenAlert(this, LocaleController.getString("ChannelCantOpenBannedByAdmin", R.string.ChannelCantOpenBannedByAdmin)); commentLoadingMessageId = 0; chatListView.invalidateViews(); return; @@ -24622,7 +25395,19 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } - public void openVCard(TLRPC.User user, String vcard, String first_name, String last_name) { + public void openVCard(TLRPC.User user, String phone, String vcard, String first_name, String last_name) { + if (user != null) { + Bundle args = new Bundle(); + args.putLong("user_id", user.id); + args.putBoolean("show_add_to_contacts", true); + args.putString("vcard", vcard); + args.putString("vcard_phone", phone); + args.putString("vcard_first_name", first_name); + args.putString("vcard_last_name", last_name); + presentFragment(new ProfileActivity(args)); + return; + } + try { File f = AndroidUtilities.getSharingDirectory(); f.mkdirs(); @@ -24705,8 +25490,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (DialogObject.isChatDialog(dialogId)) { TLRPC.Chat currentChat = MessagesController.getInstance(currentAccount).getChat(-dialogId); - if (currentChat != null && currentChat.username != null) { - link = "https://t.me/" + currentChat.username + "/" + messageId + "?t=" + finalTimestamp; + String username = ChatObject.getPublicUsername(currentChat); + if (currentChat != null && username != null) { + link = "https://t.me/" + username + "/" + messageId + "?t=" + finalTimestamp; } } else { TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(dialogId); @@ -24738,26 +25524,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not }); showDialog(builder.create()); } else { - if (str.startsWith("@")) { - String username = str.substring(1).toLowerCase(); - if (currentChat != null && !TextUtils.isEmpty(currentChat.username) && username.equals(currentChat.username.toLowerCase()) || + String username = Browser.extractUsername(str); + if (username != null) { + username = username.toLowerCase(); + if (ChatObject.hasPublicLink(currentChat, username) || currentUser != null && !TextUtils.isEmpty(currentUser.username) && username.equals(currentUser.username.toLowerCase())) { - Bundle args = new Bundle(); - if (currentChat != null) { - args.putLong("chat_id", currentChat.id); - } else if (currentUser != null) { - args.putLong("user_id", currentUser.id); - if (currentEncryptedChat != null) { - args.putLong("dialog_id", dialog_id); - } - } - ProfileActivity fragment = new ProfileActivity(args, avatarContainer.getSharedMediaPreloader()); - fragment.setPlayProfileAnimation(1); - fragment.setChatInfo(chatInfo); - fragment.setUserInfo(userInfo); - presentFragment(fragment); - } else { + shakeContent(); + } else if (str.startsWith("@")) { getMessagesController().openByUserName(username, ChatActivity.this, 0); + } else { + processExternalUrl(0, str, false); } } else if (str.startsWith("#") || str.startsWith("$")) { if (ChatObject.isChannel(currentChat)) { @@ -24778,6 +25554,21 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + public void shakeContent() { + ViewGroup v = getChatListView(); + AndroidUtilities.shakeViewSpring(v, 5); + BotWebViewVibrationEffect.APP_ERROR.vibrate(); + + v = getChatActivityEnterView(); + for (int i = 0; i < v.getChildCount(); i++) { + AndroidUtilities.shakeViewSpring(v.getChildAt(i), 5); + } + v = getActionBar(); + for (int i = 0; i < v.getChildCount(); i++) { + AndroidUtilities.shakeViewSpring(v.getChildAt(i), 5); + } + } + private void processExternalUrl(int type, String url, boolean forceAlert) { try { Uri uri = Uri.parse(url); @@ -25052,18 +25843,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } MediaController.getInstance().cleanupPlayer(true, true, false, playingObject.equals(message)); } - if (chatMode == MODE_SCHEDULED && (message.isVideo() || message.type == 1)) { + if (chatMode == MODE_SCHEDULED && (message.isVideo() || message.type == MessageObject.TYPE_PHOTO)) { PhotoViewer.getInstance().setParentChatActivity(ChatActivity.this); ArrayList arrayList = new ArrayList<>(); for (int a = 0, N = messages.size(); a < N; a++) { MessageObject m = messages.get(a); - if (m.isVideo() || m.type == 1) { + if (m.isVideo() || m.type == MessageObject.TYPE_PHOTO) { arrayList.add(0, m); } } - PhotoViewer.getInstance().openPhoto(arrayList, arrayList.indexOf(message), dialog_id, 0, photoViewerProvider); + PhotoViewer.getInstance().openPhoto(arrayList, arrayList.indexOf(message), dialog_id, 0, getTopicId(),photoViewerProvider); } else { - PhotoViewer.getInstance().openPhoto(message, ChatActivity.this, message.type != 0 ? dialog_id : 0, message.type != 0 ? mergeDialogId : 0, photoViewerProvider); + PhotoViewer.getInstance().openPhoto(message, ChatActivity.this, message.type != 0 ? dialog_id : 0, message.type != 0 ? mergeDialogId : 0, message.type != 0 ? getTopicId() : 0, photoViewerProvider); } hideHints(false); MediaController.getInstance().resetGoingToShowMessageObject(); @@ -25157,7 +25948,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not botInfoRow = -5; } - if (!endReached[0] || mergeDialogId != 0 && !endReached[1]) { + if ((!endReached[0] || mergeDialogId != 0 && !endReached[1]) && !(DISABLE_PROGRESS_VIEW && !AndroidUtilities.isTablet() && !isComments && currentUser == null)) { loadingUpRow = rowCount++; } else { loadingUpRow = -5; @@ -25325,9 +26116,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - protected void onSend(LongSparseArray dids, int count) { + protected void onSend(LongSparseArray dids, int count, TLRPC.TL_forumTopic topic) { if (dids.size() == 1) { - undoView.showWithAction(dids.valueAt(0).id, UndoView.ACTION_FWD_MESSAGES, count); + undoView.showWithAction(dids.valueAt(0).id, UndoView.ACTION_FWD_MESSAGES, count, topic, null, null); } else { undoView.showWithAction(0, UndoView.ACTION_FWD_MESSAGES, count, dids.size(), null, null); } @@ -25339,10 +26130,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - public boolean needPlayMessage(MessageObject messageObject) { + public boolean needPlayMessage(MessageObject messageObject, boolean muted) { if (messageObject.isVoice() || messageObject.isRoundVideo()) { - boolean result = MediaController.getInstance().playMessage(messageObject); + boolean result = MediaController.getInstance().playMessage(messageObject, muted); MediaController.getInstance().setVoiceMessagesPlaylist(result ? createVoiceMessagesPlaylist(messageObject, false) : null, false); +// if (messageObject.isRoundVideo() && messageObject.isVoiceTranscriptionOpen()) { +// AndroidUtilities.runOnUIThread(() -> updateMessagesVisiblePart(false), 450); +// } return result; } else if (messageObject.isMusic()) { return MediaController.getInstance().setPlaylist(messages, messageObject, mergeDialogId); @@ -25384,7 +26178,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void didPressOther(ChatMessageCell cell, float otherX, float otherY) { MessageObject messageObject = cell.getMessageObject(); - if (messageObject.type == 16) { + if (messageObject.type == MessageObject.TYPE_PHONE_CALL) { if (currentUser != null) { VoIPHelper.startCall(currentUser, messageObject.isVideoCall(), userInfo != null && userInfo.video_calls_available, getParentActivity(), getMessagesController().getUserFull(currentUser.id), getAccountInstance()); } @@ -25399,6 +26193,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not processRowSelect(cell, true, touchX, touchY); return; } + if (cell != null && cell.getMessageObject() != null && cell.getMessageObject().isSponsored()) { + didPressInstantButton(cell, 10); + return; + } openProfile(user); } @@ -25565,6 +26363,19 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not presentFragment(new PremiumPreviewFragment(source)); } + @Override + public void needShowPremiumBulletin(int type) { + if (type == 0) { + if (topUndoView == null) { + return; + } + topUndoView.showWithAction(0, UndoView.ACTION_PREMIUM_TRANSCRIPTION, null, () -> { + new PremiumFeatureBottomSheet(ChatActivity.this, PremiumPreviewFragment.PREMIUM_FEATURE_VOICE_TO_TEXT, false).show(); + getMessagesController().pressTranscribeButton(); + }); + } + } + @Override public void didLongPressBotButton(ChatMessageCell cell, TLRPC.KeyboardButton button) { if (getParentActivity() == null || bottomOverlayChat.getVisibility() == View.VISIBLE && @@ -25607,6 +26418,21 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST)); } + + Path path = new Path(); + @Override + protected void dispatchDraw(Canvas canvas) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + canvas.save(); + path.rewind(); + path.addRoundRect(AndroidUtilities.dp(8), AndroidUtilities.dp(8), getWidth() - AndroidUtilities.dp(8), getHeight() - AndroidUtilities.dp(8), AndroidUtilities.dp(6), AndroidUtilities.dp(6), Path.Direction.CW); + canvas.clipPath(path); + super.dispatchDraw(canvas); + canvas.restore(); + } else { + super.dispatchDraw(canvas); + } + } }; scrimPopupContainerLayout.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); @@ -25796,7 +26622,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public boolean didPressAnimatedEmoji(AnimatedEmojiSpan span) { - if (span == null || span.standard) { + if (getMessagesController().premiumLocked || span == null || span.standard) { return false; } long documentId = span.getDocumentId(); @@ -25914,9 +26740,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not StickersAlert alert = new StickersAlert(getParentActivity(), ChatActivity.this, message.getInputStickerSet(), null, bottomOverlayChat.getVisibility() != View.VISIBLE && (currentChat == null || ChatObject.canSendStickers(currentChat)) ? chatActivityEnterView : null, themeDelegate); alert.setCalcMandatoryInsets(isKeyboardVisible()); showDialog(alert); - } else if (message.isVideo() || message.type == 1 || message.type == 0 && !message.isWebpageDocument() || message.isGif()) { + } else if (message.isVideo() || message.type == MessageObject.TYPE_PHOTO || message.type == MessageObject.TYPE_TEXT && !message.isWebpageDocument() || message.isGif()) { openPhotoViewerForMessage(cell, message); - } else if (message.type == 3) { + } else if (message.type == MessageObject.TYPE_VIDEO) { sendSecretMessageRead(message, true); try { File f = null; @@ -25938,7 +26764,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not FileLog.e(e); alertUserOpenError(message); } - } else if (message.type == 4) { + } else if (message.type == MessageObject.TYPE_GEO) { if (!AndroidUtilities.isMapsInstalled(ChatActivity.this)) { return; } @@ -25953,7 +26779,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not fragment.setMessageObject(message); presentFragment(fragment); } - } else if (message.type == 9 || message.type == 0) { + } else if (message.type == MessageObject.TYPE_FILE || message.type == MessageObject.TYPE_TEXT) { if (message.getDocumentName().toLowerCase().endsWith("attheme")) { File locFile = null; if (message.messageOwner.attachPath != null && message.messageOwner.attachPath.length() != 0) { @@ -25979,7 +26805,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not boolean handled = false; if (message.canPreviewDocument()) { PhotoViewer.getInstance().setParentActivity(ChatActivity.this, themeDelegate); - PhotoViewer.getInstance().openPhoto(message, ChatActivity.this, message.type != 0 ? dialog_id : 0, message.type != 0 ? mergeDialogId : 0, photoViewerProvider); + PhotoViewer.getInstance().openPhoto(message, ChatActivity.this, message.type != 0 ? dialog_id : 0, message.type != 0 ? mergeDialogId : 0, message.type != 0 ? getTopicId() : 0, photoViewerProvider); handled = true; } if (!handled) { @@ -26009,7 +26835,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (uid != 0) { user = MessagesController.getInstance(currentAccount).getUser(uid); } - openVCard(user, messageObject.messageOwner.media.vcard, messageObject.messageOwner.media.first_name, messageObject.messageOwner.media.last_name); + openVCard(user, messageObject.messageOwner.media.phone_number, messageObject.messageOwner.media.vcard, messageObject.messageOwner.media.first_name, messageObject.messageOwner.media.last_name); } else { if (messageObject.isSponsored()) { Bundle args = new Bundle(); @@ -26064,7 +26890,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public String getAdminRank(long uid) { if (ChatObject.isChannel(currentChat) && currentChat.megagroup) { - return getMessagesController().getAdminRank(currentChat.id, uid); + String rank = getMessagesController().getAdminRank(currentChat.id, uid); + if (rank != null) { + return rank; + } + } + if (forumTopic != null && forumTopic.from_id != null && (forumTopic.from_id.user_id == uid || forumTopic.from_id.channel_id == uid || forumTopic.from_id.chat_id == uid)) { + return LocaleController.getString("TopicCreator", R.string.TopicCreator); } return null; } @@ -26172,10 +27004,20 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not ImageLocation imageLocation = ImageLocation.getForPhoto(photoSize, message.messageOwner.action.photo); PhotoViewer.getInstance().openPhoto(photoSize.location, imageLocation, photoViewerProvider); } else { - PhotoViewer.getInstance().openPhoto(message, null, 0, 0, photoViewerProvider); + PhotoViewer.getInstance().openPhoto(message, null, 0, 0, 0, photoViewerProvider); } } + @Override + public BaseFragment getBaseFragment() { + return ChatActivity.this; + } + + @Override + public int getTopicId() { + return ChatActivity.this.getTopicId(); + } + @Override public boolean didLongPress(ChatActionCell cell, float x, float y) { return createMenu(cell, false, false, x, y); @@ -26218,6 +27060,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } chatActivityEnterView.didPressedBotButton(button, messageObject, messageObject); } + + @Override + public boolean canDrawOutboundsContent() { + return false; + } }); } else if (viewType == 2) { view = new ChatUnreadCell(mContext, themeDelegate); @@ -26271,7 +27118,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not messageCell.isChat = currentChat != null || UserObject.isUserSelf(currentUser) || UserObject.isReplyUser(currentUser); messageCell.isBot = currentUser != null && currentUser.bot; messageCell.isMegagroup = ChatObject.isChannel(currentChat) && currentChat.megagroup; - messageCell.isThreadChat = threadMessageId != 0; + messageCell.isThreadChat = threadMessageId != 0 || currentChat != null && currentChat.forum && isTopic; messageCell.hasDiscussion = chatMode != MODE_SCHEDULED && ChatObject.isChannel(currentChat) && currentChat.has_link && !currentChat.megagroup; messageCell.isPinned = chatMode == 0 && (pinnedMessageObjects.containsKey(message.getId()) || groupedMessages != null && !groupedMessages.messages.isEmpty() && pinnedMessageObjects.containsKey(groupedMessages.messages.get(0).getId())); messageCell.linkedChatId = chatMode != MODE_SCHEDULED && chatInfo != null ? chatInfo.linked_chat_id : 0; @@ -26856,6 +27703,20 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } catch (Exception e) { FileLog.e(e); } + boolean hideSkeletons = false; + for (int i = messages.size() - 1; i >= 0; i--) { + MessageObject message = messages.get(i); + if (message.isDateObject) { + continue; + } + if (message.messageOwner != null && (message.messageOwner.action instanceof TLRPC.TL_messageActionTopicCreate || message.messageOwner.action instanceof TLRPC.TL_messageActionChannelCreate)) { + hideSkeletons = true; + } + break; + } + if ((endReached[0] && (mergeDialogId == 0 || endReached[1])) || hideSkeletons) { + checkDispatchHideSkeletons(fragmentBeginToShow); + } } @@ -27030,8 +27891,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } return true; } - } else if (currentChat.username != null) { - String username = currentChat.username.toLowerCase(); + } else if (ChatObject.getPublicUsername(currentChat) != null) { + String username = ChatObject.getPublicUsername(currentChat).toLowerCase(); if (publicMsgUrlPattern == null) { publicMsgUrlPattern = Pattern.compile("(https://)?t.me/([0-9a-zA-Z_]+)/([0-9]+)"); voiceChatUrlPattern = Pattern.compile("(https://)?t.me/([0-9a-zA-Z_]+)\\?(voicechat+)"); @@ -27126,7 +27987,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - protected void setInMenuMode(boolean value) { + public void setInMenuMode(boolean value) { super.setInMenuMode(value); if (actionBar != null) { actionBar.createMenu().setVisibility(inMenuMode ? View.GONE : View.VISIBLE); @@ -27141,6 +28002,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not public class ChatScrollCallback extends RecyclerAnimationScrollHelper.AnimationCallback { private MessageObject scrollTo; + private int position = 0; + private boolean bottom = true; + private int offset = 0; private int lastItemOffset; private boolean lastBottom; private int lastPadding; @@ -27164,7 +28028,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } else { chatAdapter.updateRowsSafe(); - chatLayoutManager.scrollToPositionWithOffset(0, 0, true); + chatLayoutManager.scrollToPositionWithOffset(position, offset, bottom); } scrollTo = null; checkTextureViewPosition = true; @@ -27739,19 +28603,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not themeDescriptions.add(new ThemeDescription(chatListView, ThemeDescription.FLAG_SERVICEBACKGROUND, new Class[]{ChatLoadingCell.class}, new String[]{"textView"}, null, null, null, Theme.key_chat_serviceBackground)); themeDescriptions.add(new ThemeDescription(chatListView, ThemeDescription.FLAG_PROGRESSBAR, new Class[]{ChatLoadingCell.class}, new String[]{"textView"}, null, null, null, Theme.key_chat_serviceText)); - themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), ThemeDescription.FLAG_TEXTCOLOR, new Class[]{BotSwitchCell.class}, new String[]{"textView"}, null, null, null, Theme.key_chat_botSwitchToInlineText)); - - themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), ThemeDescription.FLAG_TEXTCOLOR, new Class[]{MentionCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); - themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), ThemeDescription.FLAG_TEXTCOLOR, new Class[]{MentionCell.class}, new String[]{"usernameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3)); - - themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), 0, new Class[]{ContextLinkCell.class}, null, new Drawable[]{Theme.chat_inlineResultFile, Theme.chat_inlineResultAudio, Theme.chat_inlineResultLocation}, null, Theme.key_chat_inlineResultIcon)); - themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2)); - themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_windowBackgroundWhiteLinkText)); - themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); - themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_chat_inAudioProgress)); - themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_chat_inAudioSelectedProgress)); - themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_divider)); - + if (mentionContainer != null) { + themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), ThemeDescription.FLAG_TEXTCOLOR, new Class[]{BotSwitchCell.class}, new String[]{"textView"}, null, null, null, Theme.key_chat_botSwitchToInlineText)); + themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), ThemeDescription.FLAG_TEXTCOLOR, new Class[]{MentionCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); + themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), ThemeDescription.FLAG_TEXTCOLOR, new Class[]{MentionCell.class}, new String[]{"usernameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3)); + themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), 0, new Class[]{ContextLinkCell.class}, null, new Drawable[]{Theme.chat_inlineResultFile, Theme.chat_inlineResultAudio, Theme.chat_inlineResultLocation}, null, Theme.key_chat_inlineResultIcon)); + themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2)); + themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_windowBackgroundWhiteLinkText)); + themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); + themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_chat_inAudioProgress)); + themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_chat_inAudioSelectedProgress)); + themeDescriptions.add(new ThemeDescription(mentionContainer.getListView(), 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_divider)); + } themeDescriptions.add(new ThemeDescription(gifHintTextView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chat_gifSaveHintBackground)); themeDescriptions.add(new ThemeDescription(gifHintTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_gifSaveHintText)); @@ -27868,9 +28731,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - protected AnimatorSet onCustomTransitionAnimation(boolean isOpen, Runnable callback) { - if (isOpen && fromPullingDownTransition && getParentLayout().fragmentsStack.size() > 1) { - BaseFragment previousFragment = getParentLayout().fragmentsStack.get(getParentLayout().fragmentsStack.size() - 2); + public AnimatorSet onCustomTransitionAnimation(boolean isOpen, Runnable callback) { + if (isOpen && fromPullingDownTransition && getParentLayout() != null && getParentLayout().getFragmentStack().size() > 1) { + BaseFragment previousFragment = getParentLayout().getFragmentStack().get(getParentLayout().getFragmentStack().size() - 2); if (previousFragment instanceof ChatActivity) { wasManualScroll = true; ChatActivity previousChat = (ChatActivity) previousFragment; @@ -27974,6 +28837,63 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return fragmentTransition; } } + if (switchFromTopics && getParentLayout() != null && getParentLayout().getFragmentStack().size() > 1) { + BaseFragment previousFragment = getParentLayout().getFragmentStack().get(getParentLayout().getFragmentStack().size() - 2); + if (!(previousFragment instanceof TopicsFragment)) { + return null; + } + ValueAnimator valueAnimator = isOpen ? ValueAnimator.ofFloat(0f, 1f) : ValueAnimator.ofFloat(1f, 0f); + int width = previousFragment.getFragmentView().getWidth(); + + switchingFromTopicsProgress = isOpen ? 0f : 1f; + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + switchingFromTopicsProgress = (float) animation.getAnimatedValue(); + contentView.invalidate(); + } + }); + + switchingFromTopics = true; + actionBar.invalidate(); + contentView.invalidate(); + + fragmentTransition = new AnimatorSet(); + fragmentTransition.addListener(new AnimatorListenerAdapter() { + + int index; + + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + index = NotificationCenter.getInstance(currentAccount).setAnimationInProgress(index, null); + } + + @Override + public void onAnimationEnd(Animator animation) { + fragmentOpened = true; + fragmentBeginToShow = true; + fragmentTransition = null; + if (isOpen) { + switchingFromTopics = false; + } + actionBar.invalidate(); + contentView.invalidate(); + AndroidUtilities.runOnUIThread(() -> { + NotificationCenter.getInstance(currentAccount).onAnimationFinish(index); + }, 32); + callback.run(); + } + }); + fragmentTransition.setDuration(150); + fragmentTransition.playTogether(valueAnimator); + if (isOpen) { + AndroidUtilities.runOnUIThread(fragmentTransitionRunnable, 200); + } else { + fragmentTransition.start(); + } + return fragmentTransition; + } return null; } @@ -28111,7 +29031,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (!setup && ThemeEditorView.getInstance() == null) { Theme.refreshThemeColors(true, true); } else { - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.didSetNewTheme, false, true, parentLayout != null && !parentLayout.isTransitionAnimationInProgress())); + AndroidUtilities.runOnUIThread(() -> NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.didSetNewTheme, false, true, true)); } } @@ -28295,9 +29215,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not animationSettings.beforeAnimationRunnable = () -> { animatingColors = new HashMap<>(); animatingMessageDrawable = (Theme.MessageDrawable) getThemedDrawable(Theme.key_drawable_msgOut); - animatingMessageDrawable.crossfadeFromDrawable = parentLayout.messageDrawableOutStart; + animatingMessageDrawable.crossfadeFromDrawable = parentLayout.getMessageDrawableOutStart(); animatingMessageMediaDrawable = (Theme.MessageDrawable) getThemedDrawable(Theme.key_drawable_msgOutMedia); - animatingMessageMediaDrawable.crossfadeFromDrawable = parentLayout.messageDrawableOutMediaStart; + animatingMessageMediaDrawable.crossfadeFromDrawable = parentLayout.getMessageDrawableOutMediaStart(); animatingMessageDrawable.crossfadeProgress = 0f; animatingMessageMediaDrawable.crossfadeProgress = 0f; updateMessagesVisiblePart(false); @@ -28750,4 +29670,21 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not public MessageObject.GroupedMessages getGroup(long id) { return groupedMessagesMap.get(id); } + + private MessageSkeleton getNewSkeleton(boolean noAvatar) { + MessageSkeleton skeleton = new MessageSkeleton(); + if (currentChat != null && ChatObject.isChannelAndNotMegaGroup(currentChat)) { + skeleton.height = AndroidUtilities.dp(128) + Utilities.fastRandom.nextInt(AndroidUtilities.dp(64)); + } else { + skeleton.height = AndroidUtilities.dp(64) + Utilities.fastRandom.nextInt(AndroidUtilities.dp(64)); + } + skeleton.width = (int) Math.min(chatListView.getWidth() * 0.8f - (noAvatar ? 0 : AndroidUtilities.dp(42)), AndroidUtilities.dp(42) + (0.4f + Utilities.fastRandom.nextFloat() * 0.35f) * chatListView.getWidth()); + return skeleton; + } + + private final static class MessageSkeleton { + int width; + int height; + int lastBottom; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java index 9b2de5a07..205c78a3c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java @@ -12,11 +12,14 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.app.Dialog; import android.content.Context; import android.content.Intent; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.os.Vibrator; @@ -27,6 +30,7 @@ import android.text.TextUtils; import android.text.TextWatcher; import android.util.TypedValue; import android.view.Gravity; +import android.view.HapticFeedbackConstants; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; @@ -36,6 +40,7 @@ import android.widget.ScrollView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; +import org.telegram.messenger.Emoji; import org.telegram.messenger.FileLog; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocaleController; @@ -63,6 +68,10 @@ import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.Bulletin; +import org.telegram.ui.Components.BulletinFactory; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.EditTextEmoji; import org.telegram.ui.Components.ImageUpdater; @@ -73,6 +82,7 @@ import org.telegram.ui.Components.SizeNotifierFrameLayout; import org.telegram.ui.Components.UndoView; import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CountDownLatch; public class ChatEditActivity extends BaseFragment implements ImageUpdater.ImageUpdaterDelegate, NotificationCenter.NotificationCenterDelegate { @@ -91,20 +101,22 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image private AvatarDrawable avatarDrawable; private ImageUpdater imageUpdater; private EditTextEmoji nameTextView; + private LinearLayout linearLayout; private LinearLayout settingsContainer; private EditTextBoldCursor descriptionTextView; private LinearLayout typeEditContainer; private ShadowSectionCell settingsTopSectionCell; - private TextDetailCell locationCell; - private TextDetailCell typeCell; - private TextDetailCell linkedCell; - private TextDetailCell historyCell; + private TextCell locationCell; + private TextCell typeCell; + private TextCell linkedCell; + private TextCell historyCell; private TextCell reactionsCell; - private ShadowSectionCell settingsSectionCell; + private TextInfoPrivacyCell settingsSectionCell; - private TextCheckCell signCell; + private TextCell signCell; + private TextCell forumsCell; private FrameLayout stickersContainer; private TextCell stickersCell; @@ -129,6 +141,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image private TLRPC.ChatFull info; private long chatId; private boolean signMessages; + private boolean forum, canForum; private boolean isChannel; @@ -214,6 +227,8 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image imageUpdater.parentFragment = this; imageUpdater.setDelegate(this); signMessages = currentChat.signatures; + forum = currentChat.forum; + canForum = (forum || Math.max(info == null ? 0 : info.participants_count, currentChat.participants_count) >= getMessagesController().forumUpgradeParticipantsMin) && (info == null || info.linked_chat_id == 0); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatInfoDidLoad); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.updateInterfaces); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatAvailableReactionsUpdated); @@ -234,7 +249,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image TLRPC.TL_messages_exportedChatInvites invites = (TLRPC.TL_messages_exportedChatInvites) response; info.invitesCount = invites.count; getMessagesStorage().saveChatLinksCount(chatId, info.invitesCount); - updateFields(false); + updateFields(false, false); } })); } @@ -261,7 +276,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image nameTextView.getEditText().requestFocus(); } AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid); - updateFields(true); + updateFields(true, true); imageUpdater.onResume(); } @@ -278,7 +293,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image } @Override - protected void onBecomeFullyHidden() { + public void onBecomeFullyHidden() { if (undoView != null) { undoView.hide(true, 0); } @@ -460,7 +475,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image scrollView.setFillViewport(true); sizeNotifierFrameLayout.addView(scrollView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - LinearLayout linearLayout1 = new LinearLayout(context); + LinearLayout linearLayout1 = linearLayout = new LinearLayout(context); scrollView.addView(linearLayout1, new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); linearLayout1.setOrientation(LinearLayout.VERTICAL); @@ -492,7 +507,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image super.invalidate(l, t, r, b); } }; - avatarImage.setRoundRadius(AndroidUtilities.dp(32)); + avatarImage.setRoundRadius(forum ? AndroidUtilities.dp(16) : AndroidUtilities.dp(32)); if (ChatObject.canChangeChatInfo(currentChat)) { frameLayout.addView(avatarImage, LayoutHelper.createFrame(64, 64, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 0 : 16, 12, LocaleController.isRTL ? 16 : 0, 8)); @@ -664,12 +679,11 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image typeEditContainer = new LinearLayout(context); typeEditContainer.setOrientation(LinearLayout.VERTICAL); - typeEditContainer.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); linearLayout1.addView(typeEditContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); if (currentChat.megagroup && (info == null || info.can_set_location)) { - locationCell = new TextDetailCell(context); - locationCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + locationCell = new TextCell(context); + locationCell.setBackgroundDrawable(Theme.getSelectorDrawable(true)); typeEditContainer.addView(locationCell, LayoutHelper.createLinear(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); locationCell.setOnClickListener(v -> { if (!AndroidUtilities.isMapsInstalled(ChatEditActivity.this)) { @@ -687,7 +701,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image info.location = channelLocation; info.flags |= 32768; - updateFields(false); + updateFields(false, true); getMessagesController().loadFullChat(chatId, 0, true); }); presentFragment(fragment); @@ -695,8 +709,8 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image } if (currentChat.creator && (info == null || info.can_set_username)) { - typeCell = new TextDetailCell(context); - typeCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + typeCell = new TextCell(context); + typeCell.setBackgroundDrawable(Theme.getSelectorDrawable(true)); typeEditContainer.addView(typeCell, LayoutHelper.createLinear(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); typeCell.setOnClickListener(v -> { ChatEditTypeActivity fragment = new ChatEditTypeActivity(chatId, locationCell != null && locationCell.getVisibility() == View.VISIBLE); @@ -706,8 +720,8 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image } if (ChatObject.isChannel(currentChat) && (isChannel && ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_CHANGE_INFO) || !isChannel && ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_PIN))) { - linkedCell = new TextDetailCell(context); - linkedCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + linkedCell = new TextCell(context); + linkedCell.setBackgroundDrawable(Theme.getSelectorDrawable(true)); typeEditContainer.addView(linkedCell, LayoutHelper.createLinear(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); linkedCell.setOnClickListener(v -> { ChatLinkActivity fragment = new ChatLinkActivity(chatId); @@ -717,8 +731,8 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image } if (!isChannel && ChatObject.canBlockUsers(currentChat) && (ChatObject.isChannel(currentChat) || currentChat.creator)) { - historyCell = new TextDetailCell(context); - historyCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + historyCell = new TextCell(context); + historyCell.setBackgroundDrawable(Theme.getSelectorDrawable(true)); typeEditContainer.addView(historyCell, LayoutHelper.createLinear(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); historyCell.setOnClickListener(v -> { BottomSheet.Builder builder = new BottomSheet.Builder(context); @@ -758,7 +772,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image buttons[1].setChecked(tag == 1, true); historyHidden = tag == 1; builder.getDismissRunnable().run(); - updateFields(true); + updateFields(true, true); }); } @@ -768,13 +782,36 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image } if (isChannel) { - signCell = new TextCheckCell(context); - signCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); - signCell.setTextAndValueAndCheck(LocaleController.getString("ChannelSignMessages", R.string.ChannelSignMessages), LocaleController.getString("ChannelSignMessagesInfo", R.string.ChannelSignMessagesInfo), signMessages, true, false); + signCell = new TextCell(context, 23, false, true, null); + signCell.setBackgroundDrawable(Theme.getSelectorDrawable(true)); + signCell.setTextAndCheckAndIcon(LocaleController.getString("ChannelSignMessages", R.string.ChannelSignMessages), signMessages, R.drawable.msg_signed, false); typeEditContainer.addView(signCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); signCell.setOnClickListener(v -> { signMessages = !signMessages; - ((TextCheckCell) v).setChecked(signMessages); + ((TextCell) v).setChecked(signMessages); + }); + } else if (currentChat.creator) { + forumsCell = new TextCell(context, 23, false, true, null); + forumsCell.setBackgroundDrawable(Theme.getSelectorDrawable(true)); + forumsCell.setTextAndCheckAndIcon(LocaleController.getString("ChannelTopics", R.string.ChannelTopics), forum, R.drawable.msg_topics, false); + forumsCell.getCheckBox().setIcon(canForum ? 0 : R.drawable.permission_locked); + typeEditContainer.addView(forumsCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + forumsCell.setOnClickListener(v -> { + if (!canForum) { + CharSequence text; + if (!(info == null || info.linked_chat_id == 0)) { + text = AndroidUtilities.replaceTags(LocaleController.getString("ChannelTopicsDiscussionForbidden", R.string.ChannelTopicsDiscussionForbidden)); + } else { + text = AndroidUtilities.replaceTags(LocaleController.formatPluralString("ChannelTopicsForbidden", getMessagesController().forumUpgradeParticipantsMin)); + } + BulletinFactory.of(this).createSimpleBulletin(R.raw.topics, text).show(); + frameLayout.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); + return; + } + forum = !forum; + avatarImage.animateToRoundRadius(forum ? AndroidUtilities.dp(16) : AndroidUtilities.dp(32)); + ((TextCell) v).setChecked(forum); + updateFields(false, true); }); } @@ -784,9 +821,17 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image doneButton.setContentDescription(LocaleController.getString("Done", R.string.Done)); } - if (locationCell != null || signCell != null || historyCell != null || typeCell != null || linkedCell != null) { - settingsSectionCell = new ShadowSectionCell(context); + if (locationCell != null || signCell != null || historyCell != null || typeCell != null || linkedCell != null || forumsCell != null) { + settingsSectionCell = new TextInfoPrivacyCell(context); + Drawable shadowDrawable = Theme.getThemedDrawable(context, R.drawable.greydivider, Theme.getColor(Theme.key_windowBackgroundGrayShadow, getResourceProvider())); + Drawable background = new ColorDrawable(getThemedColor(Theme.key_windowBackgroundGray)); + CombinedDrawable combinedDrawable = new CombinedDrawable(background, shadowDrawable, 0, 0); + combinedDrawable.setFullsize(true); + settingsSectionCell.setBackground(combinedDrawable); linearLayout1.addView(settingsSectionCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + if (forumsCell != null) { + settingsSectionCell.setText(LocaleController.getString(R.string.ForumToggleDescription)); + } } infoContainer = new LinearLayout(context); @@ -956,13 +1001,13 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image undoView = new UndoView(context); sizeNotifierFrameLayout.addView(undoView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT, 8, 0, 8, 8)); - nameTextView.setText(currentChat.title); + nameTextView.setText(Emoji.replaceEmoji(currentChat.title, nameTextView.getEditText().getPaint().getFontMetricsInt(), AndroidUtilities.dp(16), true)); nameTextView.setSelection(nameTextView.length()); if (info != null) { descriptionTextView.setText(info.about); } setAvatar(); - updateFields(true); + updateFields(true, false); return fragmentView; } @@ -1006,6 +1051,13 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image } } + private void updateCanForum() { + canForum = (forum || Math.max(info == null ? 0 : info.participants_count, currentChat.participants_count) >= getMessagesController().forumUpgradeParticipantsMin) && (info == null || info.linked_chat_id == 0); + if (forumsCell != null) { + forumsCell.getCheckBox().setIcon(canForum ? 0 : R.drawable.permission_locked); + } + } + @Override public void didReceivedNotification(int id, int account, Object... args) { if (id == NotificationCenter.chatInfoDidLoad) { @@ -1016,8 +1068,9 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image } boolean infoWasEmpty = info == null; info = chatFull; + updateCanForum(); historyHidden = !ChatObject.isChannel(currentChat) || info.hidden_prehistory; - updateFields(false); + updateFields(false, false); if (infoWasEmpty) { loadLinksCount(); } @@ -1034,7 +1087,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image if (info != null) { availableReactions = info.available_reactions; } - updateReactionsCell(); + updateReactionsCell(true); } } } @@ -1102,7 +1155,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image if (info != null && ChatObject.isChannel(currentChat) && info.hidden_prehistory != historyHidden || nameTextView != null && !currentChat.title.equals(nameTextView.getText().toString()) || descriptionTextView != null && !about.equals(descriptionTextView.getText().toString()) || - signMessages != currentChat.signatures) { + signMessages != currentChat.signatures || forum != currentChat.forum) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("UserRestrictionsApplyChanges", R.string.UserRestrictionsApplyChanges)); if (isChannel) { @@ -1142,11 +1195,11 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image if (v != null) { v.vibrate(200); } - AndroidUtilities.shakeView(nameTextView, 2, 0); + AndroidUtilities.shakeView(nameTextView); return; } donePressed = true; - if (!ChatObject.isChannel(currentChat) && !historyHidden) { + if (!ChatObject.isChannel(currentChat) && (!historyHidden || forum)) { getMessagesController().convertToMegaGroup(getParentActivity(), chatId, this, param -> { if (param == 0) { donePressed = false; @@ -1193,6 +1246,22 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image currentChat.signatures = true; getMessagesController().toggleChannelSignatures(chatId, signMessages); } + if (forum != currentChat.forum) { + getMessagesController().toggleChannelForum(chatId, forum); + List fragments = getParentLayout().getFragmentStack(); + for (int i = 0; i < fragments.size(); i++) { + if (fragments.get(i) instanceof ChatActivity) { + ChatActivity chatActivity = (ChatActivity) fragments.get(i); + if (chatActivity.getArguments().getLong("chat_id") == chatId) { + getParentLayout().removeFragmentFromStack(i); + Bundle bundle = new Bundle(); + bundle.putLong("chat_id",chatId); + TopicsFragment topicsFragment = new TopicsFragment(bundle); + getParentLayout().addFragmentToStack(topicsFragment, i); + } + } + } + } finishFragment(); } @@ -1286,22 +1355,14 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image } } - private void updateFields(boolean updateChat) { + private void updateFields(boolean updateChat, boolean animated) { if (updateChat) { TLRPC.Chat chat = getMessagesController().getChat(chatId); if (chat != null) { currentChat = chat; } } - boolean isPrivate = TextUtils.isEmpty(currentChat.username); - - if (historyCell != null) { - if (info != null && info.location instanceof TLRPC.TL_channelLocation) { - historyCell.setVisibility(View.GONE); - } else { - historyCell.setVisibility(isPrivate && (info == null || info.linked_chat_id == 0) ? View.VISIBLE : View.GONE); - } - } + boolean isPrivate = !ChatObject.isPublic(currentChat); if (settingsSectionCell != null) { settingsSectionCell.setVisibility(signCell == null && typeCell == null && (linkedCell == null || linkedCell.getVisibility() != View.VISIBLE) && (historyCell == null || historyCell.getVisibility() != View.VISIBLE) && (locationCell == null || locationCell.getVisibility() != View.VISIBLE) ? View.GONE : View.VISIBLE); @@ -1317,23 +1378,24 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image } else { linkedCell.setVisibility(View.VISIBLE); if (info.linked_chat_id == 0) { - linkedCell.setTextAndValue(LocaleController.getString("Discussion", R.string.Discussion), LocaleController.getString("DiscussionInfo", R.string.DiscussionInfo), true); + linkedCell.setTextAndValueAndIcon(LocaleController.getString("Discussion", R.string.Discussion), LocaleController.getString("DiscussionInfoShort", R.string.DiscussionInfoShort), R.drawable.msg_discuss, true); } else { TLRPC.Chat chat = getMessagesController().getChat(info.linked_chat_id); if (chat == null) { linkedCell.setVisibility(View.GONE); } else { + String username; if (isChannel) { - if (TextUtils.isEmpty(chat.username)) { - linkedCell.setTextAndValue(LocaleController.getString("Discussion", R.string.Discussion), chat.title, true); + if (TextUtils.isEmpty(username = ChatObject.getPublicUsername(chat))) { + linkedCell.setTextAndValueAndIcon(LocaleController.getString("Discussion", R.string.Discussion), chat.title, R.drawable.msg_discuss,true); } else { - linkedCell.setTextAndValue(LocaleController.getString("Discussion", R.string.Discussion), "@" + chat.username, true); + linkedCell.setTextAndValueAndIcon(LocaleController.getString("Discussion", R.string.Discussion), "@" + username, R.drawable.msg_discuss,true); } } else { - if (TextUtils.isEmpty(chat.username)) { - linkedCell.setTextAndValue(LocaleController.getString("LinkedChannel", R.string.LinkedChannel), chat.title, false); + if (TextUtils.isEmpty(username = ChatObject.getPublicUsername(chat))) { + linkedCell.setTextAndValueAndIcon(LocaleController.getString("LinkedChannel", R.string.LinkedChannel), chat.title, R.drawable.msg_channel, forumsCell != null && forumsCell.getVisibility() == View.VISIBLE); } else { - linkedCell.setTextAndValue(LocaleController.getString("LinkedChannel", R.string.LinkedChannel), "@" + chat.username, false); + linkedCell.setTextAndValueAndIcon(LocaleController.getString("LinkedChannel", R.string.LinkedChannel), "@" + username, R.drawable.msg_channel, forumsCell != null && forumsCell.getVisibility() == View.VISIBLE); } } } @@ -1346,9 +1408,9 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image locationCell.setVisibility(View.VISIBLE); if (info.location instanceof TLRPC.TL_channelLocation) { TLRPC.TL_channelLocation location = (TLRPC.TL_channelLocation) info.location; - locationCell.setTextAndValue(LocaleController.getString("AttachLocation", R.string.AttachLocation), location.address, true); + locationCell.setTextAndValue(LocaleController.getString("AttachLocation", R.string.AttachLocation), location.address, animated, true); } else { - locationCell.setTextAndValue(LocaleController.getString("AttachLocation", R.string.AttachLocation), "Unknown address", true); + locationCell.setTextAndValue(LocaleController.getString("AttachLocation", R.string.AttachLocation), "Unknown address", animated, true); } } else { locationCell.setVisibility(View.GONE); @@ -1361,9 +1423,9 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image if (isPrivate) { link = LocaleController.getString("TypeLocationGroupEdit", R.string.TypeLocationGroupEdit); } else { - link = String.format("https://" + getMessagesController().linkPrefix + "/%s", currentChat.username); + link = String.format("https://" + getMessagesController().linkPrefix + "/%s", ChatObject.getPublicUsername(currentChat)); } - typeCell.setTextAndValue(LocaleController.getString("TypeLocationGroup", R.string.TypeLocationGroup), link, historyCell != null && historyCell.getVisibility() == View.VISIBLE || linkedCell != null && linkedCell.getVisibility() == View.VISIBLE); + typeCell.setTextAndValueAndIcon(LocaleController.getString("TypeLocationGroup", R.string.TypeLocationGroup), link, R.drawable.msg_channel, historyCell != null && historyCell.getVisibility() == View.VISIBLE || linkedCell != null && linkedCell.getVisibility() == View.VISIBLE || forumsCell != null && forumsCell.getVisibility() == View.VISIBLE); } else { String type; boolean isRestricted = currentChat.noforwards; @@ -1373,16 +1435,18 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image type = isPrivate ? isRestricted ? LocaleController.getString("TypePrivateGroupRestrictedForwards", R.string.TypePrivateGroupRestrictedForwards) : LocaleController.getString("TypePrivateGroup", R.string.TypePrivateGroup) : LocaleController.getString("TypePublicGroup", R.string.TypePublicGroup); } if (isChannel) { - typeCell.setTextAndValue(LocaleController.getString("ChannelType", R.string.ChannelType), type, historyCell != null && historyCell.getVisibility() == View.VISIBLE || linkedCell != null && linkedCell.getVisibility() == View.VISIBLE); + typeCell.setTextAndValueAndIcon(LocaleController.getString("ChannelType", R.string.ChannelType), type, R.drawable.msg_channel, historyCell != null && historyCell.getVisibility() == View.VISIBLE || linkedCell != null && linkedCell.getVisibility() == View.VISIBLE || forumsCell != null && forumsCell.getVisibility() == View.VISIBLE); } else { - typeCell.setTextAndValue(LocaleController.getString("GroupType", R.string.GroupType), type, historyCell != null && historyCell.getVisibility() == View.VISIBLE || linkedCell != null && linkedCell.getVisibility() == View.VISIBLE); + typeCell.setTextAndValueAndIcon(LocaleController.getString("GroupType", R.string.GroupType), type, R.drawable.msg_groups, historyCell != null && historyCell.getVisibility() == View.VISIBLE || linkedCell != null && linkedCell.getVisibility() == View.VISIBLE || forumsCell != null && forumsCell.getVisibility() == View.VISIBLE); } } } if (historyCell != null) { - String type = historyHidden ? LocaleController.getString("ChatHistoryHidden", R.string.ChatHistoryHidden) : LocaleController.getString("ChatHistoryVisible", R.string.ChatHistoryVisible); - historyCell.setTextAndValue(LocaleController.getString("ChatHistory", R.string.ChatHistory), type, false); + String type = historyHidden && !forum ? LocaleController.getString("ChatHistoryHidden", R.string.ChatHistoryHidden) : LocaleController.getString("ChatHistoryVisible", R.string.ChatHistoryVisible); + historyCell.setTextAndValueAndIcon(LocaleController.getString("ChatHistoryShort", R.string.ChatHistoryShort), type, animated, R.drawable.msg_discuss, forumsCell != null); + historyCell.setEnabled(!forum); + updateHistoryShow(!forum && isPrivate && (info == null || info.linked_chat_id == 0) && !(info != null && info.location instanceof TLRPC.TL_channelLocation), animated); } if (membersCell != null) { @@ -1429,13 +1493,16 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image if (!currentChat.default_banned_rights.invite_users) { count++; } + if (forum && !currentChat.default_banned_rights.manage_topics) { + count++; + } if (!currentChat.default_banned_rights.change_info) { count++; } } else { - count = 8; + count = forum ? 9 : 8; } - blockCell.setTextAndValueAndIcon(LocaleController.getString("ChannelPermissions", R.string.ChannelPermissions), String.format("%d/%d", count, 8), R.drawable.msg_permissions, true); + blockCell.setTextAndValueAndIcon(LocaleController.getString("ChannelPermissions", R.string.ChannelPermissions), String.format("%d/%d", count, forum ? 9 : 8), animated, R.drawable.msg_permissions, true); } if (memberRequestsCell != null) { memberRequestsCell.setTextAndValueAndIcon(LocaleController.getString("MemberRequests", R.string.MemberRequests), String.format("%d", info.requests_pending), R.drawable.msg_requests, logCell != null && logCell.getVisibility() == View.VISIBLE); @@ -1457,7 +1524,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image adminCell.setTextAndIcon(LocaleController.getString("ChannelAdministrators", R.string.ChannelAdministrators), R.drawable.msg_admins, true); } reactionsCell.setVisibility(ChatObject.canChangeChatInfo(currentChat) ? View.VISIBLE : View.GONE); - updateReactionsCell(); + updateReactionsCell(animated); if (info == null || !ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_INVITE) || (!isPrivate && currentChat.creator)) { inviteLinksCell.setVisibility(View.GONE); } else { @@ -1474,7 +1541,79 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image } } - private void updateReactionsCell() { + private ValueAnimator updateHistoryShowAnimator; + private void updateHistoryShow(boolean show, boolean animated) { + final boolean finalShow = show; + if (updateHistoryShowAnimator != null) { + updateHistoryShowAnimator.cancel(); + } + if (historyCell.getAlpha() <= 0 && !show) { + historyCell.setVisibility(View.GONE); + return; + } else if (historyCell.getVisibility() == View.VISIBLE && historyCell.getAlpha() >= 1 && show) { + return; + } + ArrayList nextViews = new ArrayList<>(); + boolean afterme = false; + for (int i = 0; i < typeEditContainer.getChildCount(); ++i) { + if (!afterme && typeEditContainer.getChildAt(i) == historyCell) { + afterme = true; + } else if (afterme) { + nextViews.add(typeEditContainer.getChildAt(i)); + } + } + afterme = false; + for (int i = 0; i < linearLayout.getChildCount(); ++i) { + if (!afterme && linearLayout.getChildAt(i) == typeEditContainer) { + afterme = true; + } else if (afterme) { + nextViews.add(linearLayout.getChildAt(i)); + } + } + if (historyCell.getVisibility() != View.VISIBLE) { + historyCell.setAlpha(0); + historyCell.setTranslationY(-historyCell.getHeight() / 2f); + } + historyCell.setVisibility(View.VISIBLE); + for (int i = 0; i < nextViews.size(); ++i) { + nextViews.get(i).setTranslationY(-historyCell.getHeight() * (1f - historyCell.getAlpha())); + } + if (animated) { + updateHistoryShowAnimator = ValueAnimator.ofFloat(historyCell.getAlpha(), show ? 1f : 0f); + updateHistoryShowAnimator.addUpdateListener(anm -> { + float t = (float) anm.getAnimatedValue(); + historyCell.setAlpha(t); + historyCell.setTranslationY(-historyCell.getHeight() / 2f * (1f - t)); + historyCell.setScaleY(.2f + .8f * t); + for (int i = 0; i < nextViews.size(); ++i) { + nextViews.get(i).setTranslationY(-historyCell.getHeight() * (1f - t)); + } + }); + updateHistoryShowAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + historyCell.setVisibility(finalShow ? View.VISIBLE : View.GONE); + for (int i = 0; i < nextViews.size(); ++i) { + nextViews.get(i).setTranslationY(0); + } + } + }); + updateHistoryShowAnimator.setDuration(320); + updateHistoryShowAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); + updateHistoryShowAnimator.start(); + } else { + historyCell.setAlpha(show ? 1f : 0f); + historyCell.setTranslationY(-historyCell.getHeight() / 2f * (show ? 0 : 1f)); + historyCell.setScaleY(.2f + .8f * (show ? 1f : 0f)); + historyCell.setVisibility(finalShow ? View.VISIBLE : View.GONE); + for (int i = 0; i < nextViews.size(); ++i) { + nextViews.get(i).setTranslationY(0); + } + updateHistoryShowAnimator = null; + } + } + + private void updateReactionsCell(boolean animated) { String finalString; if (availableReactions == null || availableReactions instanceof TLRPC.TL_chatReactionsNone) { finalString = LocaleController.getString("ReactionsOff", R.string.ReactionsOff); @@ -1499,7 +1638,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image } - reactionsCell.setTextAndValueAndIcon(LocaleController.getString("Reactions", R.string.Reactions), finalString, R.drawable.msg_reactions2, true); + reactionsCell.setTextAndValueAndIcon(LocaleController.getString("Reactions", R.string.Reactions), finalString, animated, R.drawable.msg_reactions2, true); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatEditTypeActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatEditTypeActivity.java index 859d77823..f391aed22 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatEditTypeActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatEditTypeActivity.java @@ -8,14 +8,21 @@ package org.telegram.ui; +import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.os.Vibrator; import android.text.Editable; import android.text.InputType; import android.text.TextUtils; import android.text.TextWatcher; import android.util.TypedValue; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; @@ -23,6 +30,11 @@ import android.view.inputmethod.EditorInfo; import android.widget.LinearLayout; import android.widget.ScrollView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; import org.telegram.messenger.LocaleController; @@ -47,15 +59,20 @@ import org.telegram.ui.Cells.TextCell; import org.telegram.ui.Cells.TextCheckCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.CircularProgressDrawable; +import org.telegram.ui.Components.CrossfadeDrawable; +import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.InviteLinkBottomSheet; import org.telegram.ui.Components.JoinToSendSettingsView; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.LinkActionView; import org.telegram.ui.Components.Premium.LimitReachedBottomSheet; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.concurrent.CountDownLatch; public class ChatEditTypeActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { @@ -69,6 +86,7 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe private TextInfoPrivacyCell checkTextView; private LinearLayout linearLayout; private ActionBarMenuItem doneButton; + private CrossfadeDrawable doneButtonDrawable; private LinearLayout linearLayoutTypeContainer; private RadioButtonCell radioButtonCell1; @@ -84,6 +102,13 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe private TextInfoPrivacyCell infoCell; private TextSettingsCell textCell; private TextSettingsCell textCell2; + private UsernamesListView usernamesListView; + + private boolean ignoreScroll; + + private ArrayList editableUsernames = new ArrayList<>(); + private ArrayList usernames = new ArrayList<>(); + private ChangeUsernameActivity.UsernameCell editableUsernameCell; // Saving content restrictions block private LinearLayout saveContainer; @@ -144,10 +169,10 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe } } } - isPrivate = !isForcePublic && TextUtils.isEmpty(currentChat.username); + isPrivate = !isForcePublic && !ChatObject.isPublic(currentChat); isChannel = ChatObject.isChannel(currentChat) && !currentChat.megagroup; isSaveRestricted = currentChat.noforwards; - if (isForcePublic && TextUtils.isEmpty(currentChat.username) || isPrivate && currentChat.creator) { + if (isForcePublic && !ChatObject.isPublic(currentChat) || isPrivate && currentChat.creator) { TLRPC.TL_channels_checkUsername req = new TLRPC.TL_channels_checkUsername(); req.username = "1"; req.channel = new TLRPC.TL_inputChannelEmpty(); @@ -161,6 +186,26 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe if (isPrivate && info != null) { getMessagesController().loadFullChat(chatId, classGuid, true); } + if (currentChat != null) { + editableUsernames.clear(); + usernames.clear(); + for (int i = 0; i < currentChat.usernames.size(); ++i) { + if (currentChat.usernames.get(i).active) + usernames.add(currentChat.usernames.get(i)); + } + for (int i = 0; i < currentChat.usernames.size(); ++i) { + if (!currentChat.usernames.get(i).active) + usernames.add(currentChat.usernames.get(i)); + } +// for (int i = 0; i < usernames.size(); ++i) { +// if (usernames.get(i) == null || +// currentChat.username != null && currentChat.username.equals(usernames.get(i).username) || +// usernames.get(i).editable +// ) { +// editableUsernames.add(usernames.remove(i--)); +// } +// } + } getNotificationCenter().addObserver(this, NotificationCenter.chatInfoDidLoad); return super.onFragmentCreate(); } @@ -191,7 +236,7 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe } @Override - protected void onBecomeFullyVisible() { + public void onBecomeFullyVisible() { super.onBecomeFullyVisible(); if (isForcePublic && usernameTextView != null) { usernameTextView.requestFocus(); @@ -216,7 +261,10 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe }); ActionBarMenu menu = actionBar.createMenu(); - doneButton = menu.addItemWithWidth(done_button, R.drawable.ic_ab_done, AndroidUtilities.dp(56), LocaleController.getString("Done", R.string.Done)); + Drawable checkmark = context.getResources().getDrawable(R.drawable.ic_ab_done).mutate(); + checkmark.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_actionBarDefaultIcon), PorterDuff.Mode.MULTIPLY)); + doneButtonDrawable = new CrossfadeDrawable(checkmark, new CircularProgressDrawable(Theme.getColor(Theme.key_actionBarDefaultIcon))); + doneButton = menu.addItemWithWidth(done_button, doneButtonDrawable, AndroidUtilities.dp(56), LocaleController.getString("Done", R.string.Done)); fragmentView = new ScrollView(context) { @Override @@ -224,6 +272,21 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe rectangle.bottom += AndroidUtilities.dp(60); return super.requestChildRectangleOnScreen(child, rectangle, immediate); } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + return !ignoreScroll && super.onTouchEvent(ev); + default: + return super.onTouchEvent(ev); + } + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return !ignoreScroll && super.onInterceptTouchEvent(ev); + } }; fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); ScrollView scrollView = (ScrollView) fragmentView; @@ -367,7 +430,11 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe if (ignoreTextChanges) { return; } - checkUserName(usernameTextView.getText().toString()); + String username = usernameTextView.getText().toString(); + if (editableUsernameCell != null) { + editableUsernameCell.updateUsername(username); + } + checkUserName(username); } @Override @@ -416,6 +483,8 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe adminedInfoCell = new ShadowSectionCell(context); linearLayout.addView(adminedInfoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + linearLayout.addView(usernamesListView = new UsernamesListView(context), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + usernamesListView.setVisibility(isPrivate || usernames.isEmpty() ? View.GONE : View.VISIBLE); manageLinksTextView = new TextCell(context); manageLinksTextView.setBackgroundDrawable(Theme.getSelectorDrawable(true)); @@ -460,10 +529,11 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe saveContainer.addView(saveRestrictInfoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - if (!isPrivate && currentChat.username != null) { + String username = ChatObject.getPublicUsername(currentChat, true); + if (!isPrivate && username != null) { ignoreTextChanges = true; - usernameTextView.setText(currentChat.username); - usernameTextView.setSelection(currentChat.username.length()); + usernameTextView.setText(username); + usernameTextView.setSelection(username.length()); ignoreTextChanges = false; } updatePrivatePublic(); @@ -471,6 +541,27 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe return fragmentView; } + private Runnable enableDoneLoading = () -> updateDoneProgress(true); + private ValueAnimator doneButtonDrawableAnimator; + private void updateDoneProgress(boolean loading) { + if (!loading) { + AndroidUtilities.cancelRunOnUIThread(enableDoneLoading); + } + if (doneButtonDrawable != null) { + if (doneButtonDrawableAnimator != null) { + doneButtonDrawableAnimator.cancel(); + } + doneButtonDrawableAnimator = ValueAnimator.ofFloat(doneButtonDrawable.getProgress(), loading ? 1f : 0); + doneButtonDrawableAnimator.addUpdateListener(a -> { + doneButtonDrawable.setProgress((float) a.getAnimatedValue()); + doneButtonDrawable.invalidateSelf(); + }); + doneButtonDrawableAnimator.setDuration((long) (200 * Math.abs(doneButtonDrawable.getProgress() - (loading ? 1f : 0)))); + doneButtonDrawableAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT); + doneButtonDrawableAnimator.start(); + } + } + private void showPremiumIncreaseLimitDialog() { if (getParentActivity() == null) { return; @@ -508,6 +599,7 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe } private void processDone() { + AndroidUtilities.runOnUIThread(enableDoneLoading, 200); if (currentChat.noforwards != isSaveRestricted) { getMessagesController().toggleChatNoForwards(chatId, currentChat.noforwards = isSaveRestricted); } @@ -544,22 +636,412 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe } } + private Boolean editableUsernameWasActive, editableUsernameUpdated; + + private class UsernamesListView extends RecyclerListView { + private final int VIEW_TYPE_HEADER = 0; + private final int VIEW_TYPE_USERNAME = 1; + private final int VIEW_TYPE_HELP = 2; + + private Adapter adapter; + private LinearLayoutManager layoutManager; + private ItemTouchHelper itemTouchHelper; + + public UsernamesListView(Context context) { + super(context); + + setAdapter(adapter = new Adapter()); + setLayoutManager(layoutManager = new LinearLayoutManager(context)); + setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (view instanceof ChangeUsernameActivity.UsernameCell) { + TLRPC.TL_username username = ((ChangeUsernameActivity.UsernameCell) view).currentUsername; + if (username == null) { + return; + } + if (username.editable) { + if (fragmentView instanceof ScrollView) { + ((ScrollView) fragmentView).smoothScrollTo(0, linkContainer.getTop() - AndroidUtilities.dp(128)); + } + usernameTextView.requestFocus(); + AndroidUtilities.showKeyboard(usernameTextView); + return; + } + + new AlertDialog.Builder(getContext(), getResourceProvider()) + .setTitle(username.active ? LocaleController.getString("UsernameDeactivateLink", R.string.UsernameDeactivateLink) : LocaleController.getString("UsernameActivateLink", R.string.UsernameActivateLink)) + .setMessage(username.active ? LocaleController.getString("UsernameDeactivateLinkChannelMessage", R.string.UsernameDeactivateLinkChannelMessage) : LocaleController.getString("UsernameActivateLinkChannelMessage", R.string.UsernameActivateLinkChannelMessage)) + .setPositiveButton(username.active ? LocaleController.getString("Hide", R.string.Hide) : LocaleController.getString("Show", R.string.Show), (di, e) -> { + if (username.editable) { + if (editableUsernameWasActive == null) { + editableUsernameWasActive = username.active; + } + editableUsernameUpdated = (username.active = !username.active); + } else { + TLRPC.TL_channels_toggleUsername req = new TLRPC.TL_channels_toggleUsername(); + TLRPC.TL_inputChannel inputChannel = new TLRPC.TL_inputChannel(); + inputChannel.channel_id = currentChat.id; + inputChannel.access_hash = currentChat.access_hash; + req.channel = inputChannel; + req.username = username.username; + final boolean wasActive = username.active; + req.active = (username.active = !username.active); + getConnectionsManager().sendRequest(req, (res, err) -> { + if (res instanceof TLRPC.TL_boolTrue) { + + } else if (err != null && "USERNAMES_ACTIVE_TOO_MUCH".equals(err.text)) { + AndroidUtilities.runOnUIThread(() -> { + new AlertDialog.Builder(getContext(), resourcesProvider) + .setTitle(LocaleController.getString("UsernameActivateErrorTitle", R.string.UsernameActivateErrorTitle)) + .setMessage(LocaleController.getString("UsernameActivateErrorMessage", R.string.UsernameActivateErrorMessage)) + .setPositiveButton(LocaleController.getString("OK", R.string.OK), (d, v) -> { + toggleUsername(username, wasActive, true); + checkDoneButton(); + }) + .show(); + }); + } else { + AndroidUtilities.runOnUIThread(() -> { + toggleUsername(username, wasActive, true); + checkDoneButton(); + }); + } + }, ConnectionsManager.RequestFlagDoNotWaitFloodWait); + } + ((ChangeUsernameActivity.UsernameCell) view).update(); + checkDoneButton(); + toggleUsername(username, username.active); + }) + .setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), (di, e) -> { + di.dismiss(); + }) + .show(); + } + } + }); + + itemTouchHelper = new ItemTouchHelper(new TouchHelperCallback()); + itemTouchHelper.attachToRecyclerView(this); + } + + public void toggleUsername(TLRPC.TL_username username, boolean newActive) { + toggleUsername(username, newActive, false); + } + + public void toggleUsername(TLRPC.TL_username username, boolean newActive, boolean shake) { + for (int i = 0; i < usernames.size(); ++i) { + if (usernames.get(i) == username) { + toggleUsername(1 + i, newActive, shake); + break; + } + } + } + + public void toggleUsername(int position, boolean newActive) { + toggleUsername(position, newActive, false); + } + + public void toggleUsername(int position, boolean newActive, boolean shake) { + if (position - 1 < 0 || position - 1 >= usernames.size()) { + return; + } + TLRPC.TL_username username = usernames.get(position - 1); + + int toIndex = -1; + if (username.active = newActive) { + int firstInactive = -1; + for (int i = 0; i < usernames.size(); ++i) { + if (!usernames.get(i).active) { + firstInactive = i; + break; + } + } + if (firstInactive >= 0) { + toIndex = 1 + Math.max(0, firstInactive - 1); + } + } else { + int lastActive = -1; + for (int i = 0; i < usernames.size(); ++i) { + if (usernames.get(i).active) { + lastActive = i; + } + } + if (lastActive >= 0) { + toIndex = 1 + Math.min(usernames.size() - 1, lastActive + 1); + } + } + + for (int i = 0; i < getChildCount(); ++i) { + View child = getChildAt(i); + if (getChildAdapterPosition(child) == position) { + if (shake) { + AndroidUtilities.shakeView(child); + } + if (child instanceof ChangeUsernameActivity.UsernameCell) { + ((ChangeUsernameActivity.UsernameCell) child).update(); + } + break; + } + } + + if (toIndex >= 0 && position != toIndex) { + adapter.moveElement(position, toIndex); + } + } + + @Override + protected void onMeasure(int widthSpec, int heightSpec) { + super.onMeasure(widthSpec, MeasureSpec.makeMeasureSpec(999999999, MeasureSpec.AT_MOST)); + } + + public class TouchHelperCallback extends ItemTouchHelper.Callback { + + @Override + public boolean isLongPressDragEnabled() { + return true; + } + + @Override + public int getMovementFlags(@NonNull RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + if (viewHolder.getItemViewType() != VIEW_TYPE_USERNAME || !((ChangeUsernameActivity.UsernameCell) viewHolder.itemView).active) { + return makeMovementFlags(0, 0); + } + return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0); + } + + @Override + public boolean onMove(@NonNull RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) { + if (source.getItemViewType() != target.getItemViewType() || + target.itemView instanceof ChangeUsernameActivity.UsernameCell && !((ChangeUsernameActivity.UsernameCell) target.itemView).active) { + return false; + } + adapter.swapElements(source.getAdapterPosition(), target.getAdapterPosition()); + return true; + } + + @Override + public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + } + + @Override + public void onSelectedChanged(ViewHolder viewHolder, int actionState) { + if (actionState == ItemTouchHelper.ACTION_STATE_IDLE) { + ignoreScroll = false; + sendReorder(); + } else { + ignoreScroll = true; + cancelClickRunnables(false); + viewHolder.itemView.setPressed(true); + } + super.onSelectedChanged(viewHolder, actionState); + } + + @Override + public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { + } + + @Override + public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { + super.clearView(recyclerView, viewHolder); + viewHolder.itemView.setPressed(false); + } + } + + private boolean needReorder = false; + private void sendReorder() { + if (!needReorder || currentChat == null) { + return; + } + needReorder = false; + TLRPC.TL_channels_reorderUsernames req = new TLRPC.TL_channels_reorderUsernames(); + TLRPC.TL_inputChannel inputChannel = new TLRPC.TL_inputChannel(); + inputChannel.channel_id = currentChat.id; + inputChannel.access_hash = currentChat.access_hash; + req.channel = inputChannel; + ArrayList usernames = new ArrayList<>(); + for (int i = 0; i < editableUsernames.size(); ++i) { + if (editableUsernames.get(i).active) + usernames.add(editableUsernames.get(i).username); + } + for (int i = 0; i < ChatEditTypeActivity.this.usernames.size(); ++i) { + if (ChatEditTypeActivity.this.usernames.get(i).active) + usernames.add(ChatEditTypeActivity.this.usernames.get(i).username); + } + req.order = usernames; + getConnectionsManager().sendRequest(req, (res, err) -> { + if (res instanceof TLRPC.TL_boolTrue) {} + }); + updateChat(); + } + + private void updateChat() { + currentChat.usernames.clear(); + currentChat.usernames.addAll(editableUsernames); + currentChat.usernames.addAll(ChatEditTypeActivity.this.usernames); + getMessagesController().putChat(currentChat, true); + } + + private class Adapter extends RecyclerListView.SelectionAdapter { + + public void swapElements(int fromIndex, int toIndex) { + int index1 = fromIndex - 1; + int index2 = toIndex - 1; + if (index1 >= usernames.size() || index2 >= usernames.size()) { + return; + } + if (fromIndex != toIndex) { + needReorder = true; + } + + swapListElements(usernames, index1, index2); + + notifyItemMoved(fromIndex, toIndex); + + int end = 1 + usernames.size() - 1; + if (fromIndex == end || toIndex == end) { + notifyItemChanged(fromIndex, 3); + notifyItemChanged(toIndex, 3); + } + } + + private void swapListElements(List list, int index1, int index2) { + TLRPC.TL_username username1 = list.get(index1); + list.set(index1, list.get(index2)); + list.set(index2, username1); + } + + public void moveElement(int fromIndex, int toIndex) { + int index1 = fromIndex - 1; + int index2 = toIndex - 1; + if (index1 >= usernames.size() || index2 >= usernames.size()) { + return; + } + + TLRPC.TL_username username = usernames.remove(index1); + usernames.add(index2, username); + + notifyItemMoved(fromIndex, toIndex); + + for (int i = 0; i < usernames.size(); ++i) + notifyItemChanged(1 + i); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + switch (viewType) { + case VIEW_TYPE_HEADER: + return new RecyclerListView.Holder(new HeaderCell(getContext(), resourcesProvider)); + case VIEW_TYPE_USERNAME: + return new RecyclerListView.Holder(new ChangeUsernameActivity.UsernameCell(getContext(), resourcesProvider) { + @Override + protected String getUsernameEditable() { + if (usernameTextView == null) + return null; + return usernameTextView.getText().toString(); + } + }); + case VIEW_TYPE_HELP: + return new RecyclerListView.Holder(new TextInfoPrivacyCell(getContext(), resourcesProvider)); + } + return null; + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case VIEW_TYPE_HEADER: + ((HeaderCell) holder.itemView).setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite, resourcesProvider)); + ((HeaderCell) holder.itemView).setText(LocaleController.getString("UsernamesChannelHeader", R.string.UsernamesChannelHeader)); + break; + case VIEW_TYPE_USERNAME: + TLRPC.TL_username username = usernames.get(position - 1); + if (((ChangeUsernameActivity.UsernameCell) holder.itemView).editable) { + editableUsernameCell = null; + } + ((ChangeUsernameActivity.UsernameCell) holder.itemView).set(username, position < usernames.size(), false); + if (username != null && username.editable) { + editableUsernameCell = (ChangeUsernameActivity.UsernameCell) holder.itemView; + } + break; + case VIEW_TYPE_HELP: + ((TextInfoPrivacyCell) holder.itemView).setText(LocaleController.getString("UsernamesChannelHelp", R.string.UsernamesChannelHelp)); + ((TextInfoPrivacyCell) holder.itemView).setBackgroundDrawable(Theme.getThemedDrawable(getContext(), R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + break; + } + } + + @Override + public int getItemViewType(int position) { + if (position == 0) { + return VIEW_TYPE_HEADER; + } else if (position <= usernames.size()) { + return VIEW_TYPE_USERNAME; + } else { + return VIEW_TYPE_HELP; + } + } + + @Override + public int getItemCount() { + return 2 + usernames.size(); + } + + @Override + public boolean isEnabled(ViewHolder holder) { + return holder.getItemViewType() == VIEW_TYPE_USERNAME; + } + } + + private Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + @Override + protected void dispatchDraw(Canvas canvas) { + int fromIndex = 1, toIndex = 1 + usernames.size() - 1; + + int top = Integer.MAX_VALUE; + int bottom = Integer.MIN_VALUE; + + for (int i = 0; i < getChildCount(); ++i) { + View child = getChildAt(i); + if (child == null) { + continue; + } + int position = getChildAdapterPosition(child); + if (position >= fromIndex && position <= toIndex) { + top = Math.min(child.getTop(), top); + bottom = Math.max(child.getBottom(), bottom); + } + } + + if (top < bottom) { + backgroundPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhite, resourcesProvider)); + canvas.drawRect(0, top, getWidth(), bottom, backgroundPaint); + } + + super.dispatchDraw(canvas); + } + } + private boolean trySetUsername() { if (getParentActivity() == null) { return false; } - if (!isPrivate && ((currentChat.username == null && usernameTextView.length() != 0) || (currentChat.username != null && !currentChat.username.equalsIgnoreCase(usernameTextView.getText().toString())))) { + String wasUsername = ChatObject.getPublicUsername(currentChat, true); + if (!isPrivate && ((wasUsername == null && usernameTextView.length() != 0) || (wasUsername != null && !wasUsername.equalsIgnoreCase(usernameTextView.getText().toString())))) { if (usernameTextView.length() != 0 && !lastNameAvailable) { Vibrator v = (Vibrator) getParentActivity().getSystemService(Context.VIBRATOR_SERVICE); if (v != null) { v.vibrate(200); } - AndroidUtilities.shakeView(checkTextView, 2, 0); + AndroidUtilities.shakeView(checkTextView); + updateDoneProgress(false); return false; } } - String oldUserName = currentChat.username != null ? currentChat.username : ""; + String oldUserName = wasUsername != null ? wasUsername : ""; String newUserName = isPrivate ? "" : usernameTextView.getText().toString(); if (!oldUserName.equals(newUserName)) { if (!ChatObject.isChannel(currentChat)) { @@ -572,13 +1054,91 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe }); return false; } else { - getMessagesController().updateChannelUserName(chatId, newUserName); - currentChat.username = newUserName; + getMessagesController().updateChannelUserName(this, chatId, newUserName, () -> { + currentChat = getMessagesController().getChat(chatId); + processDone(); + }, () -> { + updateDoneProgress(false); + }); + return false; } } + + if (!tryDeactivateAllLinks()/* || !tryActivateEditableUsername()*/) { + return false; + } return true; } + private boolean deactivatingLinks = false; + private boolean tryDeactivateAllLinks() { + if (!isPrivate || currentChat.usernames == null) { + return true; + } + if (deactivatingLinks) { + return false; + } + deactivatingLinks = true; + boolean hasActive = false; + for (int i = 0; i < currentChat.usernames.size(); ++i) { + final TLRPC.TL_username username = currentChat.usernames.get(i); + if (username != null && username.active && !username.editable) { + hasActive = true; + } + } + if (hasActive) { + TLRPC.TL_channels_deactivateAllUsernames req = new TLRPC.TL_channels_deactivateAllUsernames(); + req.channel = MessagesController.getInputChannel(currentChat); + getConnectionsManager().sendRequest(req, (res, err) -> { + if (res instanceof TLRPC.TL_boolTrue) { + for (int i = 0; i < currentChat.usernames.size(); ++i) { + final TLRPC.TL_username username = currentChat.usernames.get(i); + if (username != null && username.active && !username.editable) { + username.active = false; + } + } + } + deactivatingLinks = false; + AndroidUtilities.runOnUIThread(this::processDone); + }); + } + return !hasActive; + } + + private boolean activatingEditableLink = false; + private boolean tryActivateEditableUsername() { + if (isPrivate || usernames == null || editableUsernameWasActive == null || editableUsernameUpdated == null || editableUsernameWasActive == editableUsernameUpdated) { + return true; + } + if (activatingEditableLink) { + return false; + } + activatingEditableLink = true; + String username = null; + for (int i = 0; i < usernames.size(); ++i) { + if (usernames.get(i) != null && usernames.get(i).editable) { + username = usernames.get(i).username; + } + } + if (username == null) { + activatingEditableLink = false; + return true; + } + TLRPC.TL_channels_toggleUsername req = new TLRPC.TL_channels_toggleUsername(); + req.channel = MessagesController.getInputChannel(currentChat); + req.active = editableUsernameUpdated; + req.username = username; + getConnectionsManager().sendRequest(req, (res, err) -> { + activatingEditableLink = false; + if (err == null) { + AndroidUtilities.runOnUIThread(this::processDone); + } else { + updateDoneProgress(false); + } + }); + return false; + } + private void loadAdminedChannels() { if (loadingAdminedChannels || adminnedChannelsLayout == null) { return; @@ -605,9 +1165,9 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); if (isChannel) { - builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlertChannel", R.string.RevokeLinkAlertChannel, getMessagesController().linkPrefix + "/" + channel.username, channel.title))); + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlertChannel", R.string.RevokeLinkAlertChannel, getMessagesController().linkPrefix + "/" + ChatObject.getPublicUsername(channel), channel.title))); } else { - builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlert", R.string.RevokeLinkAlert, getMessagesController().linkPrefix + "/" + channel.username, channel.title))); + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlert", R.string.RevokeLinkAlert, getMessagesController().linkPrefix + "/" + ChatObject.getPublicUsername(channel), channel.title))); } builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); builder.setPositiveButton(LocaleController.getString("RevokeButton", R.string.RevokeButton), (dialogInterface, i) -> { @@ -704,11 +1264,14 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe joinContainer.setVisibility(!isChannel && !isPrivate ? View.VISIBLE : View.GONE); joinContainer.showJoinToSend(info != null && info.linked_chat_id != 0); } + if (usernamesListView != null) { + usernamesListView.setVisibility(isPrivate || usernames.isEmpty() ? View.GONE : View.VISIBLE); + } checkDoneButton(); } private void checkDoneButton() { - if (isPrivate || usernameTextView.length() > 0) { + if (isPrivate || usernameTextView.length() > 0 || hasActiveLink()) { doneButton.setEnabled(true); doneButton.setAlpha(1.0f); } else { @@ -717,6 +1280,19 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe } } + public boolean hasActiveLink() { + if (usernames == null) { + return false; + } + for (int i = 0; i < usernames.size(); ++i) { + TLRPC.TL_username u = usernames.get(i); + if (u != null && u.active && !TextUtils.isEmpty(u.username)) { + return true; + } + } + return false; + } + private boolean checkUserName(final String name) { if (name != null && name.length() > 0) { checkTextView.setVisibility(View.VISIBLE); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatLinkActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatLinkActivity.java index 0437e2ddd..0208698ba 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatLinkActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatLinkActivity.java @@ -205,7 +205,7 @@ public class ChatLinkActivity extends BaseFragment implements NotificationCenter detailRow = rowCount++; if (!isChannel || chats.size() > 0 && info.linked_chat_id != 0) { TLRPC.Chat chat = isChannel ? chats.get(0) : currentChat; - if (chat != null && (TextUtils.isEmpty(chat.username) || isChannel) && (chat.creator || chat.admin_rights != null && chat.admin_rights.ban_users)) { + if (chat != null && (!ChatObject.isPublic(chat) || isChannel) && (chat.creator || chat.admin_rights != null && chat.admin_rights.ban_users)) { joinToSendRow = rowCount++; } } @@ -499,10 +499,10 @@ public class ChatLinkActivity extends BaseFragment implements NotificationCenter messageTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); messageTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); String message; - if (TextUtils.isEmpty(chat.username)) { + if (!ChatObject.isPublic(chat)) { message = LocaleController.formatString("DiscussionLinkGroupPublicPrivateAlert", R.string.DiscussionLinkGroupPublicPrivateAlert, chat.title, currentChat.title); } else { - if (TextUtils.isEmpty(currentChat.username)) { + if (!ChatObject.isPublic(currentChat)) { message = LocaleController.formatString("DiscussionLinkGroupPrivateAlert", R.string.DiscussionLinkGroupPrivateAlert, chat.title, currentChat.title); } else { message = LocaleController.formatString("DiscussionLinkGroupPublicAlert", R.string.DiscussionLinkGroupPublicAlert, chat.title, currentChat.title); @@ -733,19 +733,30 @@ public class ChatLinkActivity extends BaseFragment implements NotificationCenter tName = null; } + String username = null; int found = 0; for (String q : search) { if (name.startsWith(q) || name.contains(" " + q) || tName != null && (tName.startsWith(q) || tName.contains(" " + q))) { found = 1; } else if (chat.username != null && chat.username.startsWith(q)) { found = 2; + username = chat.username; + } else if (chat.usernames != null && !chat.usernames.isEmpty()) { + for (int i = 0; i < chat.usernames.size(); ++i) { + TLRPC.TL_username u = chat.usernames.get(i); + if (u.active && u.username.startsWith(q)) { + found = 2; + username = u.username; + break; + } + } } if (found != 0) { if (found == 1) { resultArrayNames.add(AndroidUtilities.generateSearchName(chat.title, null, q)); } else { - resultArrayNames.add(AndroidUtilities.generateSearchName("@" + chat.username, null, "@" + q)); + resultArrayNames.add(AndroidUtilities.generateSearchName("@" + username, null, "@" + q)); } resultArray.add(chat); break; @@ -800,7 +811,7 @@ public class ChatLinkActivity extends BaseFragment implements NotificationCenter @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { TLRPC.Chat chat = searchResult.get(position); - String un = chat.username; + String un = ChatObject.getPublicUsername(chat); CharSequence username = null; CharSequence name = searchResultNames.get(position); if (name != null && !TextUtils.isEmpty(un)) { @@ -958,7 +969,8 @@ public class ChatLinkActivity extends BaseFragment implements NotificationCenter ManageChatUserCell userCell = (ManageChatUserCell) holder.itemView; userCell.setTag(position); TLRPC.Chat chat = chats.get(position - chatStartRow); - userCell.setData(chat, null, TextUtils.isEmpty(chat.username) ? null : "@" + chat.username, position != chatEndRow - 1 || info.linked_chat_id != 0); + String username; + userCell.setData(chat, null, TextUtils.isEmpty(username = ChatObject.getPublicUsername(chat)) ? null : "@" + username, position != chatEndRow - 1 || info.linked_chat_id != 0); break; case 1: TextInfoPrivacyCell privacyCell = (TextInfoPrivacyCell) holder.itemView; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatRightsEditActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatRightsEditActivity.java index 012a01d92..c4426ad15 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatRightsEditActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatRightsEditActivity.java @@ -93,6 +93,7 @@ public class ChatRightsEditActivity extends BaseFragment { private TLRPC.Chat currentChat; private int currentType; private boolean isChannel; + private boolean isForum; private boolean loading = false; private boolean canEdit; @@ -121,6 +122,7 @@ public class ChatRightsEditActivity extends BaseFragment { private int banUsersRow; private int addUsersRow; private int pinMessagesRow; + private int manageTopicsRow; private int rightsShadowRow; private int removeAdminRow; private int removeAdminShadowRow; @@ -175,6 +177,7 @@ public class ChatRightsEditActivity extends BaseFragment { initialRank = currentRank = rank; if (currentChat != null) { isChannel = ChatObject.isChannel(currentChat) && !currentChat.megagroup; + isForum = ChatObject.isForum(currentChat); myAdminRights = currentChat.admin_rights; } if (myAdminRights == null) { @@ -198,6 +201,7 @@ public class ChatRightsEditActivity extends BaseFragment { rightsAdmin.anonymous = rightsAdmin.anonymous || botDefaultRights.anonymous; rightsAdmin.edit_messages = rightsAdmin.edit_messages || botDefaultRights.edit_messages; rightsAdmin.manage_call = rightsAdmin.manage_call || botDefaultRights.manage_call; + rightsAdmin.manage_topics = rightsAdmin.manage_topics || botDefaultRights.manage_topics; rightsAdmin.other = rightsAdmin.other || botDefaultRights.other; } } @@ -220,6 +224,7 @@ public class ChatRightsEditActivity extends BaseFragment { adminRights.ban_users = myAdminRights.ban_users; adminRights.invite_users = myAdminRights.invite_users; adminRights.pin_messages = myAdminRights.pin_messages; + adminRights.manage_topics = myAdminRights.manage_topics; adminRights.other = myAdminRights.other; initialIsSet = false; } @@ -234,13 +239,14 @@ public class ChatRightsEditActivity extends BaseFragment { adminRights.ban_users = rightsAdmin.ban_users; adminRights.invite_users = rightsAdmin.invite_users; adminRights.pin_messages = rightsAdmin.pin_messages; + adminRights.manage_topics = rightsAdmin.manage_topics; adminRights.add_admins = rightsAdmin.add_admins; adminRights.anonymous = rightsAdmin.anonymous; adminRights.other = rightsAdmin.other; initialIsSet = adminRights.change_info || adminRights.post_messages || adminRights.edit_messages || adminRights.delete_messages || adminRights.ban_users || adminRights.invite_users || - adminRights.pin_messages || adminRights.add_admins || adminRights.manage_call || adminRights.anonymous || adminRights.other; + adminRights.pin_messages || adminRights.add_admins || adminRights.manage_call || adminRights.anonymous || adminRights.manage_topics || adminRights.other; if (type == TYPE_ADD_BOT) { asAdmin = isChannel || initialIsSet; @@ -257,7 +263,8 @@ public class ChatRightsEditActivity extends BaseFragment { defaultBannedRights.view_messages = defaultBannedRights.send_media = defaultBannedRights.send_messages = defaultBannedRights.embed_links = defaultBannedRights.send_stickers = defaultBannedRights.send_gifs = defaultBannedRights.send_games = defaultBannedRights.send_inline = defaultBannedRights.send_polls = - defaultBannedRights.invite_users = defaultBannedRights.change_info = defaultBannedRights.pin_messages = true; + defaultBannedRights.invite_users = defaultBannedRights.change_info = defaultBannedRights.pin_messages = + defaultBannedRights.manage_topics = true; } if (!defaultBannedRights.change_info) { @@ -273,7 +280,8 @@ public class ChatRightsEditActivity extends BaseFragment { defaultBannedRights.view_messages = defaultBannedRights.send_media = defaultBannedRights.send_messages = defaultBannedRights.embed_links = defaultBannedRights.send_stickers = defaultBannedRights.send_gifs = defaultBannedRights.send_games = defaultBannedRights.send_inline = defaultBannedRights.send_polls = - defaultBannedRights.invite_users = defaultBannedRights.change_info = defaultBannedRights.pin_messages = false; + defaultBannedRights.invite_users = defaultBannedRights.change_info = defaultBannedRights.pin_messages = + defaultBannedRights.manage_topics = false; } bannedRights = new TLRPC.TL_chatBannedRights(); @@ -281,7 +289,8 @@ public class ChatRightsEditActivity extends BaseFragment { bannedRights.view_messages = bannedRights.send_media = bannedRights.send_messages = bannedRights.embed_links = bannedRights.send_stickers = bannedRights.send_gifs = bannedRights.send_games = bannedRights.send_inline = bannedRights.send_polls = - bannedRights.invite_users = bannedRights.change_info = bannedRights.pin_messages = false; + bannedRights.invite_users = bannedRights.change_info = bannedRights.pin_messages = + bannedRights.manage_topics = false; } else { bannedRights.view_messages = rightsBanned.view_messages; bannedRights.send_messages = rightsBanned.send_messages; @@ -296,6 +305,7 @@ public class ChatRightsEditActivity extends BaseFragment { bannedRights.change_info = rightsBanned.change_info; bannedRights.pin_messages = rightsBanned.pin_messages; bannedRights.until_date = rightsBanned.until_date; + bannedRights.manage_topics = rightsBanned.manage_topics; } if (defaultBannedRights.view_messages) { bannedRights.view_messages = true; @@ -333,6 +343,9 @@ public class ChatRightsEditActivity extends BaseFragment { if (defaultBannedRights.pin_messages) { bannedRights.pin_messages = true; } + if (defaultBannedRights.manage_topics) { + bannedRights.manage_topics = true; + } currentBannedRights = ChatObject.getBannedRightsString(bannedRights); @@ -345,7 +358,7 @@ public class ChatRightsEditActivity extends BaseFragment { TLRPC.TL_chatAdminRights adminRights = new TLRPC.TL_chatAdminRights(); adminRights.change_info = adminRights.post_messages = adminRights.edit_messages = adminRights.delete_messages = adminRights.ban_users = adminRights.invite_users = - adminRights.pin_messages = adminRights.add_admins = adminRights.manage_call = value; + adminRights.pin_messages = adminRights.add_admins = adminRights.manage_call = adminRights.manage_topics = value; return adminRights; } @@ -475,6 +488,7 @@ public class ChatRightsEditActivity extends BaseFragment { bannedRights.send_polls = true; bannedRights.invite_users = true; bannedRights.change_info = true; + bannedRights.manage_topics = true; bannedRights.until_date = 0; onDonePressed(); } @@ -670,6 +684,12 @@ public class ChatRightsEditActivity extends BaseFragment { value = adminRights.ban_users = !adminRights.ban_users; } else if (position == startVoiceChatRow) { value = adminRights.manage_call = !adminRights.manage_call; + } else if (position == manageTopicsRow) { + if (currentType == TYPE_ADMIN || currentType == TYPE_ADD_BOT) { + value = adminRights.manage_topics = !adminRights.manage_topics; + } else { + value = bannedRights.manage_topics = !bannedRights.manage_topics; + } } else if (position == addUsersRow) { if (currentType == TYPE_ADMIN || currentType == TYPE_ADD_BOT) { value = adminRights.invite_users = !adminRights.invite_users; @@ -764,15 +784,15 @@ public class ChatRightsEditActivity extends BaseFragment { } private boolean isDefaultAdminRights() { - return adminRights.change_info && adminRights.delete_messages && adminRights.ban_users && adminRights.invite_users && adminRights.pin_messages && adminRights.manage_call && !adminRights.add_admins && !adminRights.anonymous || - !adminRights.change_info && !adminRights.delete_messages && !adminRights.ban_users && !adminRights.invite_users && !adminRights.pin_messages && !adminRights.manage_call && !adminRights.add_admins && !adminRights.anonymous; + return adminRights.change_info && adminRights.delete_messages && adminRights.ban_users && adminRights.invite_users && adminRights.pin_messages && (!isForum || adminRights.manage_topics) && adminRights.manage_call && !adminRights.add_admins && !adminRights.anonymous || + !adminRights.change_info && !adminRights.delete_messages && !adminRights.ban_users && !adminRights.invite_users && !adminRights.pin_messages && (!isForum || !adminRights.manage_topics) && !adminRights.manage_call && !adminRights.add_admins && !adminRights.anonymous; } private boolean hasAllAdminRights() { if (isChannel) { return adminRights.change_info && adminRights.post_messages && adminRights.edit_messages && adminRights.delete_messages && adminRights.invite_users && adminRights.add_admins && adminRights.manage_call; } else { - return adminRights.change_info && adminRights.delete_messages && adminRights.ban_users && adminRights.invite_users && adminRights.pin_messages && adminRights.add_admins && adminRights.manage_call; + return adminRights.change_info && adminRights.delete_messages && adminRights.ban_users && adminRights.invite_users && adminRights.pin_messages && adminRights.add_admins && adminRights.manage_call && (!isForum || adminRights.manage_topics); } } @@ -969,6 +989,7 @@ public class ChatRightsEditActivity extends BaseFragment { untilSectionRow = -1; untilDateRow = -1; addBotButtonRow = -1; + manageTopicsRow = -1; rowCount = 3; permissionsStartRow = rowCount; @@ -993,6 +1014,9 @@ public class ChatRightsEditActivity extends BaseFragment { startVoiceChatRow = rowCount++; addAdminsRow = rowCount++; anonymousRow = rowCount++; + if (isForum) { + manageTopicsRow = rowCount++; + } } } else if (currentType == TYPE_BANNED) { sendMessagesRow = rowCount++; @@ -1003,6 +1027,9 @@ public class ChatRightsEditActivity extends BaseFragment { addUsersRow = rowCount++; pinMessagesRow = rowCount++; changeInfoRow = rowCount++; + if (isForum) { + manageTopicsRow = rowCount++; + } untilSectionRow = rowCount++; untilDateRow = rowCount++; } @@ -1085,7 +1112,7 @@ public class ChatRightsEditActivity extends BaseFragment { } RecyclerView.ViewHolder holder = listView.findViewHolderForAdapterPosition(rankHeaderRow); if (holder != null) { - AndroidUtilities.shakeView(holder.itemView, 2, 0); + AndroidUtilities.shakeView(holder.itemView); } return; } @@ -1095,7 +1122,7 @@ public class ChatRightsEditActivity extends BaseFragment { adminRights.post_messages = adminRights.edit_messages = false; } if (!adminRights.change_info && !adminRights.post_messages && !adminRights.edit_messages && - !adminRights.delete_messages && !adminRights.ban_users && !adminRights.invite_users && + !adminRights.delete_messages && !adminRights.ban_users && !adminRights.invite_users && (!isForum || !adminRights.manage_topics) && !adminRights.pin_messages && !adminRights.add_admins && !adminRights.anonymous && !adminRights.manage_call) { adminRights.other = true; } else { @@ -1110,7 +1137,7 @@ public class ChatRightsEditActivity extends BaseFragment { if (delegate != null) { delegate.didSetRights( adminRights.change_info || adminRights.post_messages || adminRights.edit_messages || - adminRights.delete_messages || adminRights.ban_users || adminRights.invite_users || + adminRights.delete_messages || adminRights.ban_users || adminRights.invite_users || (isForum && adminRights.manage_topics) || adminRights.pin_messages || adminRights.add_admins || adminRights.anonymous || adminRights.manage_call || adminRights.other ? 1 : 0, adminRights, bannedRights, currentRank); finishFragment(); @@ -1316,6 +1343,7 @@ public class ChatRightsEditActivity extends BaseFragment { if (position == untilSectionRow) return 26; if (position == untilDateRow) return 27; if (position == addBotButtonRow) return 28; + if (position == manageTopicsRow) return 29; return 0; } else { return super.getItemId(position); @@ -1359,6 +1387,8 @@ public class ChatRightsEditActivity extends BaseFragment { return myAdminRights.invite_users; } else if (position == pinMessagesRow) { return myAdminRights.pin_messages && (defaultBannedRights == null || defaultBannedRights.pin_messages); + } else if (position == manageTopicsRow) { + return myAdminRights.manage_topics; } } } @@ -1533,7 +1563,7 @@ public class ChatRightsEditActivity extends BaseFragment { checkCell.setIcon(myAdminRights.change_info || isCreator ? 0 : R.drawable.permission_locked); } } else if (currentType == TYPE_BANNED) { - checkCell.setTextAndCheck(LocaleController.getString("UserRestrictionsChangeInfo", R.string.UserRestrictionsChangeInfo), !bannedRights.change_info && !defaultBannedRights.change_info, false); + checkCell.setTextAndCheck(LocaleController.getString("UserRestrictionsChangeInfo", R.string.UserRestrictionsChangeInfo), !bannedRights.change_info && !defaultBannedRights.change_info, manageTopicsRow != -1); checkCell.setIcon(defaultBannedRights.change_info ? R.drawable.permission_locked : 0); } } else if (position == postMessagesRow) { @@ -1561,7 +1591,7 @@ public class ChatRightsEditActivity extends BaseFragment { checkCell.setIcon(myAdminRights.add_admins || isCreator ? 0 : R.drawable.permission_locked); } } else if (position == anonymousRow) { - checkCell.setTextAndCheck(LocaleController.getString("EditAdminSendAnonymously", R.string.EditAdminSendAnonymously), asAdminValue && adminRights.anonymous, false); + checkCell.setTextAndCheck(LocaleController.getString("EditAdminSendAnonymously", R.string.EditAdminSendAnonymously), asAdminValue && adminRights.anonymous, manageTopicsRow != -1); if (currentType == TYPE_ADD_BOT) { checkCell.setIcon(myAdminRights.anonymous || isCreator ? 0 : R.drawable.permission_locked); } @@ -1575,6 +1605,16 @@ public class ChatRightsEditActivity extends BaseFragment { if (currentType == TYPE_ADD_BOT) { checkCell.setIcon(myAdminRights.manage_call || isCreator ? 0 : R.drawable.permission_locked); } + } else if (position == manageTopicsRow) { + if (currentType == TYPE_ADMIN) { + checkCell.setTextAndCheck(LocaleController.getString("ManageTopicsPermission", R.string.ManageTopicsPermission), asAdminValue && adminRights.manage_topics, false); + } else if (currentType == TYPE_BANNED) { + checkCell.setTextAndCheck(LocaleController.getString("CreateTopicsPermission", R.string.CreateTopicsPermission), !bannedRights.manage_topics && !defaultBannedRights.manage_topics, false); + checkCell.setIcon(defaultBannedRights.manage_topics ? R.drawable.permission_locked : 0); + } else if (currentType == TYPE_ADD_BOT) { + checkCell.setTextAndCheck(LocaleController.getString("ManageTopicsPermission", R.string.ManageTopicsPermission), asAdminValue && adminRights.manage_topics, false); + checkCell.setIcon(myAdminRights.manage_topics || isCreator ? 0 : R.drawable.permission_locked); + } } else if (position == addUsersRow) { if (currentType == TYPE_ADMIN) { if (ChatObject.isActionBannedByDefault(currentChat, ChatObject.ACTION_INVITE)) { @@ -1698,7 +1738,7 @@ public class ChatRightsEditActivity extends BaseFragment { } else if (position == changeInfoRow || position == postMessagesRow || position == editMesagesRow || position == deleteMessagesRow || position == addAdminsRow || position == banUsersRow || position == addUsersRow || position == pinMessagesRow || position == sendMessagesRow || position == sendMediaRow || position == sendStickersRow || position == embedLinksRow || - position == sendPollsRow || position == anonymousRow || position == startVoiceChatRow || position == manageRow) { + position == sendPollsRow || position == anonymousRow || position == startVoiceChatRow || position == manageRow || position == manageTopicsRow) { return VIEW_TYPE_SWITCH_CELL; } else if (position == cantEditInfoRow || position == rankInfoRow) { return VIEW_TYPE_INFO_CELL; @@ -1768,6 +1808,9 @@ public class ChatRightsEditActivity extends BaseFragment { } else if (childPosition == anonymousRow) { childValue = adminRights.anonymous; childEnabled = myAdminRights.anonymous || (currentChat != null && currentChat.creator); + } else if (childPosition == manageTopicsRow) { + childValue = adminRights.manage_topics; + childEnabled = myAdminRights.manage_topics; } ((TextCheckCell2) child).setChecked(childValue); ((TextCheckCell2) child).setEnabled(childEnabled, animated); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java index 22baec4c7..bd7e37f31 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java @@ -94,7 +94,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente private TLRPC.Chat currentChat; private TLRPC.ChatFull info; - private boolean isChannel; + private boolean isChannel, isForum; private String initialBannedRights; private TLRPC.TL_chatBannedRights defaultBannedRights = new TLRPC.TL_chatBannedRights(); @@ -122,6 +122,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente private int changeInfoRow; private int addUsersRow; private int pinMessagesRow; + private int manageTopicsRow; private int gigaHeaderRow; private int gigaConvertRow; @@ -427,10 +428,12 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente defaultBannedRights.pin_messages = currentChat.default_banned_rights.pin_messages; defaultBannedRights.send_polls = currentChat.default_banned_rights.send_polls; defaultBannedRights.invite_users = currentChat.default_banned_rights.invite_users; + defaultBannedRights.manage_topics = currentChat.default_banned_rights.manage_topics; defaultBannedRights.change_info = currentChat.default_banned_rights.change_info; } initialBannedRights = ChatObject.getBannedRightsString(defaultBannedRights); isChannel = ChatObject.isChannel(currentChat) && !currentChat.megagroup; + isForum = ChatObject.isForum(currentChat); } private void updateRows() { @@ -459,6 +462,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente sendPollsRow = -1; embedLinksRow = -1; addUsersRow = -1; + manageTopicsRow = -1; pinMessagesRow = -1; changeInfoRow = -1; removedUsersRow = -1; @@ -487,6 +491,9 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente addUsersRow = rowCount++; pinMessagesRow = rowCount++; changeInfoRow = rowCount++; + if (isForum) { + manageTopicsRow = rowCount++; + } if (ChatObject.isChannel(currentChat) && currentChat.creator && currentChat.megagroup && !currentChat.gigagroup) { int count = Math.max(currentChat.participants_count, info != null ? info.participants_count : 0); @@ -841,7 +848,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente }; listView.setItemAnimator(itemAnimator); itemAnimator.setSupportsChangeAnimations(false); - listView.setAnimateEmptyView(true, 0); + listView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); listView.setAdapter(listViewAdapter = new ListAdapter(context)); listView.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); @@ -1055,7 +1062,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente protected void onCovert() { getMessagesController().convertToGigaGroup(getParentActivity(), currentChat, ChatUsersActivity.this, (result) -> { if (result && parentLayout != null) { - BaseFragment editActivity = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2); + BaseFragment editActivity = parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 2); if (editActivity instanceof ChatEditActivity) { editActivity.removeSelfFromStack(); @@ -1063,7 +1070,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente args.putLong("chat_id", chatId); ChatEditActivity fragment = new ChatEditActivity(args); fragment.setInfo(info); - parentLayout.addFragmentToStack(fragment, parentLayout.fragmentsStack.size() - 1); + parentLayout.addFragmentToStack(fragment, parentLayout.getFragmentStack().size() - 1); finishFragment(); fragment.showConvertTooltip(); } else { @@ -1085,13 +1092,13 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente presentFragment(fragment); } return; - } else if (position > permissionsSectionRow && position <= changeInfoRow) { + } else if (position > permissionsSectionRow && position <= Math.max(manageTopicsRow, changeInfoRow)) { TextCheckCell2 checkCell = (TextCheckCell2) view; if (!checkCell.isEnabled()) { return; } if (checkCell.hasIcon()) { - if (!TextUtils.isEmpty(currentChat.username) && (position == pinMessagesRow || position == changeInfoRow)) { + if (ChatObject.isPublic(currentChat) && (position == pinMessagesRow || position == changeInfoRow)) { BulletinFactory.of(this).createErrorBulletin(LocaleController.getString("EditCantEditPermissionsPublic", R.string.EditCantEditPermissionsPublic)).show(); } else { BulletinFactory.of(this).createErrorBulletin(LocaleController.getString("EditCantEditPermissions", R.string.EditCantEditPermissions)).show(); @@ -1103,6 +1110,8 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente defaultBannedRights.change_info = !defaultBannedRights.change_info; } else if (position == addUsersRow) { defaultBannedRights.invite_users = !defaultBannedRights.invite_users; + } else if (position == manageTopicsRow) { + defaultBannedRights.manage_topics = !defaultBannedRights.manage_topics; } else if (position == pinMessagesRow) { defaultBannedRights.pin_messages = !defaultBannedRights.pin_messages; } else { @@ -1190,7 +1199,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente adminRights = new TLRPC.TL_chatAdminRights(); adminRights.change_info = adminRights.post_messages = adminRights.edit_messages = adminRights.delete_messages = adminRights.ban_users = adminRights.invite_users = - adminRights.pin_messages = adminRights.add_admins = true; + adminRights.manage_topics = adminRights.pin_messages = adminRights.add_admins = true; if (!isChannel) { adminRights.manage_call = true; } @@ -1204,7 +1213,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente adminRights = new TLRPC.TL_chatAdminRights(); adminRights.change_info = adminRights.post_messages = adminRights.edit_messages = adminRights.delete_messages = adminRights.ban_users = adminRights.invite_users = - adminRights.pin_messages = adminRights.add_admins = true; + adminRights.manage_topics = adminRights.pin_messages = adminRights.add_admins = true; if (!isChannel) { adminRights.manage_call = true; } @@ -1291,6 +1300,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente bannedRights.pin_messages = true; bannedRights.send_polls = true; bannedRights.invite_users = true; + bannedRights.manage_topics = true; bannedRights.change_info = true; } ChatRightsEditActivity fragment = new ChatRightsEditActivity(peerId, chatId, adminRights, defaultBannedRights, bannedRights, rank, type == TYPE_ADMIN ? ChatRightsEditActivity.TYPE_ADMIN : ChatRightsEditActivity.TYPE_BANNED, canEdit, participant == null, null); @@ -1430,7 +1440,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente public void setIgnoresUsers(LongSparseArray participants) { ignoredUsers = participants; } - + private void onOwnerChaged(TLRPC.User user) { undoView.showWithAction(-chatId, isChannel ? UndoView.ACTION_OWNER_TRANSFERED_CHANNEL : UndoView.ACTION_OWNER_TRANSFERED_GROUP, user); boolean foundAny = false; @@ -1475,7 +1485,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente admin.admin_rights = new TLRPC.TL_chatAdminRights(); admin.admin_rights.change_info = admin.admin_rights.post_messages = admin.admin_rights.edit_messages = admin.admin_rights.delete_messages = admin.admin_rights.ban_users = admin.admin_rights.invite_users = - admin.admin_rights.pin_messages = admin.admin_rights.add_admins = true; + admin.admin_rights.manage_topics = admin.admin_rights.pin_messages = admin.admin_rights.add_admins = true; if (!isChannel) { admin.admin_rights.manage_call = true; } @@ -1520,7 +1530,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente final boolean isAdmin = participant instanceof TLRPC.TL_channelParticipantAdmin || participant instanceof TLRPC.TL_chatParticipantAdmin; ChatRightsEditActivity fragment = new ChatRightsEditActivity(peerId, chatId, adminRights, defaultBannedRights, bannedRights, rank, type, true, false, null) { @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (!isOpen && backward && needShowBulletin[0] && BulletinFactory.canShowBulletin(ChatUsersActivity.this)) { if (peerId > 0) { TLRPC.User user = getMessagesController().getUser(peerId); @@ -2026,7 +2036,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente public void setDelegate(ChatUsersActivityDelegate chatUsersActivityDelegate) { delegate = chatUsersActivityDelegate; } - + private int getCurrentSlowmode() { if (info != null) { if (info.slowmode_seconds == 10) { @@ -2436,7 +2446,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente } updateRows(); if (listViewAdapter != null) { - listView.setAnimateEmptyView(openTransitionStarted, 0); + listView.setAnimateEmptyView(openTransitionStarted, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); listViewAdapter.notifyDataSetChanged(); if (emptyView != null && listViewAdapter.getItemCount() == 0 && firstLoaded) { @@ -2539,7 +2549,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente } @Override - protected void onBecomeFullyHidden() { + public void onBecomeFullyHidden() { if (undoView != null) { undoView.hide(true, 0); } @@ -2550,7 +2560,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente } // @Override -// protected void onTransitionAnimationStart(boolean isOpen, boolean backward) { +// public void onTransitionAnimationStart(boolean isOpen, boolean backward) { // super.onTransitionAnimationStart(isOpen, backward); // if (isOpen) { // openTransitionStarted = true; @@ -2558,7 +2568,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente // } @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen) { openTransitionStarted = true; } @@ -2816,7 +2826,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente globalStartRow = -1; } if (searching && listView != null && listView.getAdapter() != searchListViewAdapter) { - listView.setAnimateEmptyView(true, 0); + listView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); listView.setAdapter(searchListViewAdapter); listView.setFastScrollVisible(false); listView.setVerticalScrollBarEnabled(true); @@ -2917,7 +2927,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente } else { TLRPC.Chat chat = getMessagesController().getChat(-peerId); if (chat != null) { - un = chat.username; + un = ChatObject.getPublicUsername(chat); } peerObject = chat; } @@ -3355,11 +3365,11 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente case 7: TextCheckCell2 checkCell = (TextCheckCell2) holder.itemView; if (position == changeInfoRow) { - checkCell.setTextAndCheck(LocaleController.getString("UserRestrictionsChangeInfo", R.string.UserRestrictionsChangeInfo), !defaultBannedRights.change_info && TextUtils.isEmpty(currentChat.username), false); + checkCell.setTextAndCheck(LocaleController.getString("UserRestrictionsChangeInfo", R.string.UserRestrictionsChangeInfo), !defaultBannedRights.change_info && !ChatObject.isPublic(currentChat), manageTopicsRow != -1); } else if (position == addUsersRow) { checkCell.setTextAndCheck(LocaleController.getString("UserRestrictionsInviteUsers", R.string.UserRestrictionsInviteUsers), !defaultBannedRights.invite_users, true); } else if (position == pinMessagesRow) { - checkCell.setTextAndCheck(LocaleController.getString("UserRestrictionsPinMessages", R.string.UserRestrictionsPinMessages), !defaultBannedRights.pin_messages && TextUtils.isEmpty(currentChat.username), true); + checkCell.setTextAndCheck(LocaleController.getString("UserRestrictionsPinMessages", R.string.UserRestrictionsPinMessages), !defaultBannedRights.pin_messages && !ChatObject.isPublic(currentChat), true); } else if (position == sendMessagesRow) { checkCell.setTextAndCheck(LocaleController.getString("UserRestrictionsSend", R.string.UserRestrictionsSend), !defaultBannedRights.send_messages, true); } else if (position == sendMediaRow) { @@ -3370,6 +3380,8 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente checkCell.setTextAndCheck(LocaleController.getString("UserRestrictionsEmbedLinks", R.string.UserRestrictionsEmbedLinks), !defaultBannedRights.embed_links, true); } else if (position == sendPollsRow) { checkCell.setTextAndCheck(LocaleController.getString("UserRestrictionsSendPolls", R.string.UserRestrictionsSendPolls), !defaultBannedRights.send_polls, true); + } else if (position == manageTopicsRow) { + checkCell.setTextAndCheck(LocaleController.getString("CreateTopicsPermission", R.string.CreateTopicsPermission), !defaultBannedRights.manage_topics, false); } if (position == sendMediaRow || position == sendStickersRow || position == embedLinksRow || position == sendPollsRow) { @@ -3381,7 +3393,8 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente if (position == addUsersRow && !ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_INVITE) || position == pinMessagesRow && !ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_PIN) || position == changeInfoRow && !ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_CHANGE_INFO) || - !TextUtils.isEmpty(currentChat.username) && (position == pinMessagesRow || position == changeInfoRow)) { + position == manageTopicsRow && !ChatObject.canManageTopics(currentChat) || + ChatObject.isPublic(currentChat) && (position == pinMessagesRow || position == changeInfoRow)) { checkCell.setIcon(R.drawable.permission_locked); } else { checkCell.setIcon(0); @@ -3447,7 +3460,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente } else if (position == removedUsersRow) { return 6; } else if (position == changeInfoRow || position == addUsersRow || position == pinMessagesRow || position == sendMessagesRow || - position == sendMediaRow || position == sendStickersRow || position == embedLinksRow || position == sendPollsRow) { + position == sendMediaRow || position == sendStickersRow || position == embedLinksRow || position == sendPollsRow || position == manageTopicsRow) { return 7; } else if (position == membersHeaderRow || position == contactsHeaderRow || position == botHeaderRow || position == loadingHeaderRow) { return 8; @@ -3593,6 +3606,9 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente put(++pointer, embedLinksRow, sparseIntArray); put(++pointer, addUsersRow, sparseIntArray); put(++pointer, pinMessagesRow, sparseIntArray); + if (isForum) { + put(++pointer, manageTopicsRow, sparseIntArray); + } put(++pointer, changeInfoRow, sparseIntArray); put(++pointer, removedUsersRow, sparseIntArray); put(++pointer, contactsHeaderRow, sparseIntArray); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatsWidgetConfigActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatsWidgetConfigActivity.java index abe812fa4..5698a6d74 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatsWidgetConfigActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatsWidgetConfigActivity.java @@ -34,11 +34,11 @@ public class ChatsWidgetConfigActivity extends ExternalActionActivity { }); if (AndroidUtilities.isTablet()) { - if (layersActionBarLayout.fragmentsStack.isEmpty()) { + if (layersActionBarLayout.getFragmentStack().isEmpty()) { layersActionBarLayout.addFragmentToStack(fragment); } } else { - if (actionBarLayout.fragmentsStack.isEmpty()) { + if (actionBarLayout.getFragmentStack().isEmpty()) { actionBarLayout.addFragmentToStack(fragment); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CodeNumberField.java b/TMessagesProj/src/main/java/org/telegram/ui/CodeNumberField.java index e28b8188d..18f43eddc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CodeNumberField.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CodeNumberField.java @@ -253,7 +253,7 @@ public class CodeNumberField extends EditTextBoldCursor { clipboard.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0); int i = -1; - String text = item.getText().toString(); + String text = item == null || item.getText() == null ? "" : item.getText().toString(); try { i = Integer.parseInt(text); } catch (Exception e) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java index 06df0c870..e9c2580a2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java @@ -49,6 +49,7 @@ import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.WindowManager; import android.view.inputmethod.EditorInfo; @@ -120,6 +121,7 @@ import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; @@ -439,7 +441,7 @@ public class AlertsCreator { } else { showSimpleAlert(fragment, LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred)); } - } else if (request instanceof TLRPC.TL_updateUserName) { + } else if (request instanceof TLRPC.TL_account_updateUsername) { switch (error.text) { case "USERNAME_INVALID": showSimpleAlert(fragment, LocaleController.getString("UsernameInvalid", R.string.UsernameInvalid)); @@ -638,6 +640,19 @@ public class AlertsCreator { return false; } + public static AlertDialog.Builder createNoAccessAlert(Context context, String title, String message, Theme.ResourcesProvider resourcesProvider) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(title); + Map colorsReplacement = new HashMap<>(); + colorsReplacement.put("info1.**", Theme.getColor(Theme.key_dialogTopBackground, resourcesProvider)); + colorsReplacement.put("info2.**", Theme.getColor(Theme.key_dialogTopBackground, resourcesProvider)); + builder.setTopAnimation(R.raw.not_available, AlertsCreator.NEW_DENY_DIALOG_TOP_ICON_SIZE, false, Theme.getColor(Theme.key_dialogTopBackground, resourcesProvider), colorsReplacement); + builder.setTopAnimationIsNew(true); + builder.setPositiveButton(LocaleController.getString(R.string.Close), null); + builder.setMessage(message); + return builder; + } + public static AlertDialog.Builder createSimpleAlert(Context context, final String text) { return createSimpleAlert(context, null, text); } @@ -847,11 +862,11 @@ public class AlertsCreator { } } - public static void showCustomNotificationsDialog(BaseFragment parentFragment, long did, int globalType, ArrayList exceptions, int currentAccount, MessagesStorage.IntCallback callback) { - showCustomNotificationsDialog(parentFragment, did, globalType, exceptions, currentAccount, callback, null); + public static void showCustomNotificationsDialog(BaseFragment parentFragment, long did, int topicId, int globalType, ArrayList exceptions, int currentAccount, MessagesStorage.IntCallback callback) { + showCustomNotificationsDialog(parentFragment, did, topicId, globalType, exceptions, currentAccount, callback, null); } - public static void showCustomNotificationsDialog(BaseFragment parentFragment, long did, int globalType, ArrayList exceptions, int currentAccount, MessagesStorage.IntCallback callback, MessagesStorage.IntCallback resultCallback) { + public static void showCustomNotificationsDialog(BaseFragment parentFragment, long did, int topicId, int globalType, ArrayList exceptions, int currentAccount, MessagesStorage.IntCallback callback, MessagesStorage.IntCallback resultCallback) { if (parentFragment == null || parentFragment.getParentActivity() == null) { return; } @@ -920,7 +935,7 @@ public class AlertsCreator { if (dialog != null) { dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); } - NotificationsController.getInstance(currentAccount).updateServerNotificationsSettings(did); + NotificationsController.getInstance(currentAccount).updateServerNotificationsSettings(did, topicId); if (resultCallback != null) { if (defaultEnabled) { resultCallback.run(0); @@ -948,7 +963,7 @@ public class AlertsCreator { } else if (i == 4) { untilTime = Integer.MAX_VALUE; } - NotificationsController.getInstance(currentAccount).muteUntil(did, untilTime); + NotificationsController.getInstance(currentAccount).muteUntil(did, topicId, untilTime); if (did != 0 && resultCallback != null) { if (i == 4 && !defaultEnabled) { resultCallback.run(0); @@ -1340,7 +1355,7 @@ public class AlertsCreator { messageTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); messageTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); - boolean clearingCache = !canDeleteHistory && ChatObject.isChannel(chat) && !TextUtils.isEmpty(chat.username); + boolean clearingCache = !canDeleteHistory && ChatObject.isChannel(chat) && ChatObject.isPublic(chat); FrameLayout frameLayout = new FrameLayout(context) { @Override @@ -1403,7 +1418,7 @@ public class AlertsCreator { } } frameLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? 21 : 76), 11, (LocaleController.isRTL ? 76 : 21), 0)); - frameLayout.addView(messageTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 57, 24, 9)); + frameLayout.addView(messageTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 57, 24, 1)); boolean canRevokeInbox = user != null && !user.bot && user.id != selfUserId && MessagesController.getInstance(account).canRevokePmInbox; int revokeTimeLimit; @@ -1417,8 +1432,8 @@ public class AlertsCreator { boolean deleteChatForAll = false; boolean lastMessageIsJoined = false; - MessageObject dialogMessage = user != null ? MessagesController.getInstance(account).dialogMessage.get(user.id) : null; - if (dialogMessage != null && dialogMessage.messageOwner != null && (dialogMessage.messageOwner.action instanceof TLRPC.TL_messageActionUserJoined || dialogMessage.messageOwner.action instanceof TLRPC.TL_messageActionContactSignUp)) { + ArrayList dialogMessages = user != null ? MessagesController.getInstance(account).dialogMessage.get(user.id) : null; + if (dialogMessages != null && dialogMessages.size() == 1 && dialogMessages.get(0) != null && dialogMessages.get(0).messageOwner != null && (dialogMessages.get(0).messageOwner.action instanceof TLRPC.TL_messageActionUserJoined || dialogMessages.get(0).messageOwner.action instanceof TLRPC.TL_messageActionContactSignUp)) { lastMessageIsJoined = true; } @@ -1487,7 +1502,7 @@ public class AlertsCreator { } } } else { - if (!ChatObject.isChannel(chat) || chat.megagroup && TextUtils.isEmpty(chat.username)) { + if (!ChatObject.isChannel(chat) || chat.megagroup && !ChatObject.isPublic(chat)) { messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("AreYouSureClearHistoryWithChat", R.string.AreYouSureClearHistoryWithChat, chat.title))); } else if (chat.megagroup) { messageTextView.setText(LocaleController.getString("AreYouSureClearHistoryGroup", R.string.AreYouSureClearHistoryGroup)); @@ -1664,10 +1679,10 @@ public class AlertsCreator { } final boolean[] deleteForAll = new boolean[]{false}; - if (chat != null && canDeleteHistory && !TextUtils.isEmpty(chat.username)) { + if (chat != null && canDeleteHistory && ChatObject.isPublic(chat)) { deleteForAll[0] = true; } - if ((user != null && user.id != selfUserId) || (chat != null && canDeleteHistory && TextUtils.isEmpty(chat.username) && !ChatObject.isChannelAndNotMegaGroup(chat))) { + if ((user != null && user.id != selfUserId) || (chat != null && canDeleteHistory && !ChatObject.isPublic(chat) && !ChatObject.isChannelAndNotMegaGroup(chat))) { cell[0] = new CheckBoxCell(context, 1, resourcesProvider); cell[0].setBackgroundDrawable(Theme.getSelectorDrawable(false)); if (chat != null) { @@ -1688,7 +1703,7 @@ public class AlertsCreator { } String deleteText = LocaleController.getString("Delete", R.string.Delete); - if (chat != null && canDeleteHistory && !TextUtils.isEmpty(chat.username) && !ChatObject.isChannelAndNotMegaGroup(chat)) { + if (chat != null && canDeleteHistory && ChatObject.isPublic(chat) && !ChatObject.isChannelAndNotMegaGroup(chat)) { deleteText = LocaleController.getString("ClearForAll", R.string.ClearForAll); } builder.setPositiveButton(deleteText, (dialogInterface, i) -> { @@ -1797,7 +1812,7 @@ public class AlertsCreator { if (v != null) { v.vibrate(200); } - AndroidUtilities.shakeView(checkTextView, 2, 0); + AndroidUtilities.shakeView(checkTextView); } return result; } @@ -2084,6 +2099,29 @@ public class AlertsCreator { }); } + public static void showDiscardTopicDialog(BaseFragment baseFragment, Theme.ResourcesProvider resourcesProvider, Runnable onDiscard) { + if (baseFragment == null || baseFragment.getParentActivity() == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(baseFragment.getParentActivity(), resourcesProvider); + builder.setTitle(LocaleController.getString("DiscardTopic", R.string.DiscardTopic)); + builder.setMessage(LocaleController.getString("DiscardTopicMessage", R.string.DiscardTopicMessage)); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + builder.setPositiveButton(LocaleController.getString("Discard", R.string.Discard), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + onDiscard.run(); + } + }); + baseFragment.showDialog(builder.create()); + } + public interface BlockDialogCallback { void run(boolean report, boolean delete); } @@ -3699,7 +3737,7 @@ public class AlertsCreator { return builder; } - public static BottomSheet createMuteAlert(BaseFragment fragment, final long dialog_id, Theme.ResourcesProvider resourcesProvider) { + public static BottomSheet createMuteAlert(BaseFragment fragment, final long dialog_id, int topicId, Theme.ResourcesProvider resourcesProvider) { if (fragment == null || fragment.getParentActivity() == null) { return null; } @@ -3723,7 +3761,7 @@ public class AlertsCreator { } else { setting = NotificationsController.SETTING_MUTE_FOREVER; } - NotificationsController.getInstance(UserConfig.selectedAccount).setDialogNotificationsSettings(dialog_id, setting); + NotificationsController.getInstance(UserConfig.selectedAccount).setDialogNotificationsSettings(dialog_id, topicId, setting); if (BulletinFactory.canShowBulletin(fragment)) { BulletinFactory.createMuteBulletin(fragment, setting, 0, resourcesProvider).show(); } @@ -4104,16 +4142,17 @@ public class AlertsCreator { fragment.showDialog(builder.create(), true, null); } - public static Dialog createColorSelectDialog(Activity parentActivity, final long dialog_id, final int globalType, final Runnable onSelect) { - return createColorSelectDialog(parentActivity, dialog_id, globalType, onSelect, null); + public static Dialog createColorSelectDialog(Activity parentActivity, final long dialog_id, final int topicId, final int globalType, final Runnable onSelect) { + return createColorSelectDialog(parentActivity, dialog_id, topicId, globalType, onSelect, null); } - public static Dialog createColorSelectDialog(Activity parentActivity, final long dialog_id, final int globalType, final Runnable onSelect, Theme.ResourcesProvider resourcesProvider) { + public static Dialog createColorSelectDialog(Activity parentActivity, final long dialog_id, final int topicId, final int globalType, final Runnable onSelect, Theme.ResourcesProvider resourcesProvider) { int currentColor; SharedPreferences preferences = MessagesController.getNotificationsSettings(UserConfig.selectedAccount); + String key = NotificationsController.getSharedPrefKey(dialog_id, topicId); if (dialog_id != 0) { - if (preferences.contains("color_" + dialog_id)) { - currentColor = preferences.getInt("color_" + dialog_id, 0xff0000ff); + if (preferences.contains("color_" + key)) { + currentColor = preferences.getInt("color_" + key, 0xff0000ff); } else { if (DialogObject.isChatDialog(dialog_id)) { currentColor = preferences.getInt("GroupLed", 0xff0000ff); @@ -4163,8 +4202,8 @@ public class AlertsCreator { final SharedPreferences preferences1 = MessagesController.getNotificationsSettings(UserConfig.selectedAccount); SharedPreferences.Editor editor = preferences1.edit(); if (dialog_id != 0) { - editor.putInt("color_" + dialog_id, selectedColor[0]); - NotificationsController.getInstance(UserConfig.selectedAccount).deleteNotificationChannel(dialog_id); + editor.putInt("color_" + key, selectedColor[0]); + NotificationsController.getInstance(UserConfig.selectedAccount).deleteNotificationChannel(dialog_id, topicId); } else { if (globalType == NotificationsController.TYPE_PRIVATE) { editor.putInt("MessagesLed", selectedColor[0]); @@ -4201,7 +4240,7 @@ public class AlertsCreator { builder.setNegativeButton(LocaleController.getString("Default", R.string.Default), (dialog, which) -> { final SharedPreferences preferences13 = MessagesController.getNotificationsSettings(UserConfig.selectedAccount); SharedPreferences.Editor editor = preferences13.edit(); - editor.remove("color_" + dialog_id); + editor.remove("color_" + key); editor.commit(); if (onSelect != null) { onSelect.run(); @@ -4211,25 +4250,25 @@ public class AlertsCreator { return builder.create(); } - public static Dialog createVibrationSelectDialog(Activity parentActivity, final long dialogId, final boolean globalGroup, final boolean globalAll, final Runnable onSelect) { - return createVibrationSelectDialog(parentActivity, dialogId, globalGroup, globalAll, onSelect, null); + public static Dialog createVibrationSelectDialog(Activity parentActivity, final long dialogId, int topicId, final boolean globalGroup, final boolean globalAll, final Runnable onSelect) { + return createVibrationSelectDialog(parentActivity, dialogId, topicId, globalGroup, globalAll, onSelect, null); } - public static Dialog createVibrationSelectDialog(Activity parentActivity, final long dialogId, final boolean globalGroup, final boolean globalAll, final Runnable onSelect, Theme.ResourcesProvider resourcesProvider) { + public static Dialog createVibrationSelectDialog(Activity parentActivity, final long dialogId, int topicId, final boolean globalGroup, final boolean globalAll, final Runnable onSelect, Theme.ResourcesProvider resourcesProvider) { String prefix; if (dialogId != 0) { prefix = "vibrate_" + dialogId; } else { prefix = globalGroup ? "vibrate_group" : "vibrate_messages"; } - return createVibrationSelectDialog(parentActivity, dialogId, prefix, onSelect, resourcesProvider); + return createVibrationSelectDialog(parentActivity, dialogId, topicId, prefix, onSelect, resourcesProvider); } - public static Dialog createVibrationSelectDialog(Activity parentActivity, final long dialogId, final String prefKeyPrefix, final Runnable onSelect) { - return createVibrationSelectDialog(parentActivity, dialogId, prefKeyPrefix, onSelect, null); + public static Dialog createVibrationSelectDialog(Activity parentActivity, final long dialogId, int topicId, final String prefKeyPrefix, final Runnable onSelect) { + return createVibrationSelectDialog(parentActivity, dialogId, topicId, prefKeyPrefix, onSelect, null); } - public static Dialog createVibrationSelectDialog(Activity parentActivity, final long dialogId, final String prefKeyPrefix, final Runnable onSelect, Theme.ResourcesProvider resourcesProvider) { + public static Dialog createVibrationSelectDialog(Activity parentActivity, final long dialogId, int topicId, final String prefKeyPrefix, final Runnable onSelect, Theme.ResourcesProvider resourcesProvider) { SharedPreferences preferences = MessagesController.getNotificationsSettings(UserConfig.selectedAccount); final int[] selected = new int[1]; String[] descriptions; @@ -4290,7 +4329,7 @@ public class AlertsCreator { } else if (selected[0] == 3) { editor.putInt(prefKeyPrefix, 2); } - NotificationsController.getInstance(UserConfig.selectedAccount).deleteNotificationChannel(dialogId); + NotificationsController.getInstance(UserConfig.selectedAccount).deleteNotificationChannel(dialogId, topicId); } else { if (selected[0] == 0) { editor.putInt(prefKeyPrefix, 2); @@ -4632,11 +4671,11 @@ public class AlertsCreator { return builder.create(); } - public static Dialog createPrioritySelectDialog(Activity parentActivity, final long dialog_id, final int globalType, final Runnable onSelect) { - return createPrioritySelectDialog(parentActivity, dialog_id, globalType, onSelect, null); + public static Dialog createPrioritySelectDialog(Activity parentActivity, final long dialog_id, int topicId, final int globalType, final Runnable onSelect) { + return createPrioritySelectDialog(parentActivity, dialog_id, topicId, globalType, onSelect, null); } - public static Dialog createPrioritySelectDialog(Activity parentActivity, final long dialog_id, final int globalType, final Runnable onSelect, Theme.ResourcesProvider resourcesProvider) { + public static Dialog createPrioritySelectDialog(Activity parentActivity, final long dialog_id, int topicId, final int globalType, final Runnable onSelect, Theme.ResourcesProvider resourcesProvider) { SharedPreferences preferences = MessagesController.getNotificationsSettings(UserConfig.selectedAccount); final int[] selected = new int[1]; String[] descriptions; @@ -4715,7 +4754,7 @@ public class AlertsCreator { option = 1; } editor.putInt("priority_" + dialog_id, option); - NotificationsController.getInstance(UserConfig.selectedAccount).deleteNotificationChannel(dialog_id); + NotificationsController.getInstance(UserConfig.selectedAccount).deleteNotificationChannel(dialog_id, topicId); } else { int option; if (selected[0] == 0) { @@ -5394,7 +5433,7 @@ public class AlertsCreator { if (vibrator != null) { vibrator.vibrate(200); } - AndroidUtilities.shakeView(editText, 2, 0); + AndroidUtilities.shakeView(editText); return; } if (fragment instanceof ThemePreviewActivity) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiDrawable.java index e82c6938d..3e0c968a2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiDrawable.java @@ -10,6 +10,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Looper; import android.text.TextUtils; +import android.util.Log; import android.view.View; import android.view.animation.LinearInterpolator; import android.view.animation.OvershootInterpolator; @@ -57,6 +58,7 @@ public class AnimatedEmojiDrawable extends Drawable { public static final int CACHE_TYPE_EMOJI_STATUS = 7; public static final int STANDARD_LOTTIE_FRAME = 8; public static final int CACHE_TYPE_ALERT_EMOJI_STATUS = 9; + public static final int CACHE_TYPE_FORUM_TOPIC = 10; private static HashMap> globalEmojiCache; @NonNull @@ -454,7 +456,7 @@ public class AnimatedEmojiDrawable extends Drawable { imageReceiver.setImage(mediaLocation, mediaFilter, ImageLocation.getForDocument(thumb, document), sizedp + "_" + sizedp, null, null, thumbDrawable, document.size, null, document, 1); } - if (cacheType == CACHE_TYPE_EMOJI_STATUS || cacheType == CACHE_TYPE_ALERT_EMOJI_STATUS) { + if (cacheType == CACHE_TYPE_EMOJI_STATUS || cacheType == CACHE_TYPE_ALERT_EMOJI_STATUS || cacheType == CACHE_TYPE_FORUM_TOPIC) { imageReceiver.setAutoRepeatCount(2); } @@ -742,7 +744,7 @@ public class AnimatedEmojiDrawable extends Drawable { } } - public static class SwapAnimatedEmojiDrawable extends Drawable { + public static class SwapAnimatedEmojiDrawable extends Drawable implements AnimatedEmojiSpan.InvalidateHolder { public boolean center = false; @@ -751,17 +753,27 @@ public class AnimatedEmojiDrawable extends Drawable { private AnimatedFloat changeProgress = new AnimatedFloat((View) null, 300, CubicBezierInterpolator.EASE_OUT); private Drawable[] drawables = new Drawable[2]; private View parentView; + private boolean invalidateParent; private int size; private int alpha = 255; public SwapAnimatedEmojiDrawable(View parentView, int size) { - this(parentView, size, CACHE_TYPE_EMOJI_STATUS); + this(parentView, false, size, CACHE_TYPE_EMOJI_STATUS); + } + + public SwapAnimatedEmojiDrawable(View parentView, boolean invalidateParent, int size) { + this(parentView, invalidateParent, size, CACHE_TYPE_EMOJI_STATUS); } public SwapAnimatedEmojiDrawable(View parentView, int size, int cacheType) { + this(parentView, false, size, cacheType); + } + + public SwapAnimatedEmojiDrawable(View parentView, boolean invalidateParent, int size, int cacheType) { changeProgress.setParent(this.parentView = parentView); this.size = size; this.cacheType = cacheType; + this.invalidateParent = invalidateParent; } public void setParentView(View parentView) { @@ -794,7 +806,7 @@ public class AnimatedEmojiDrawable extends Drawable { AnimatedEmojiDrawable drawable = (AnimatedEmojiDrawable) getDrawable(); ImageReceiver imageReceiver = drawable.getImageReceiver(); if (imageReceiver != null) { - if (drawable.cacheType == CACHE_TYPE_EMOJI_STATUS || drawable.cacheType == CACHE_TYPE_ALERT_EMOJI_STATUS) { + if (drawable.cacheType == CACHE_TYPE_EMOJI_STATUS || drawable.cacheType == CACHE_TYPE_ALERT_EMOJI_STATUS || drawable.cacheType == CACHE_TYPE_FORUM_TOPIC) { imageReceiver.setAutoRepeatCount(2); } imageReceiver.startAnimation(); @@ -901,25 +913,23 @@ public class AnimatedEmojiDrawable extends Drawable { changeProgress.set(0, true); if (drawables[1] != null) { if (drawables[1] instanceof AnimatedEmojiDrawable) { - ((AnimatedEmojiDrawable) drawables[1]).removeView(parentView); + ((AnimatedEmojiDrawable) drawables[1]).removeView(this); } drawables[1] = null; } drawables[1] = drawables[0]; drawables[0] = AnimatedEmojiDrawable.make(UserConfig.selectedAccount, cacheType, documentId); - ((AnimatedEmojiDrawable) drawables[0]).addView(parentView); + ((AnimatedEmojiDrawable) drawables[0]).addView(this); } else { changeProgress.set(1, true); detach(); drawables[0] = AnimatedEmojiDrawable.make(UserConfig.selectedAccount, cacheType, documentId); - ((AnimatedEmojiDrawable) drawables[0]).addView(parentView); + ((AnimatedEmojiDrawable) drawables[0]).addView(this); } lastColor = 0xffffffff; colorFilter = null; play(); - if (parentView != null) { - parentView.invalidate(); - } + invalidate(); } public void set(TLRPC.Document document, boolean animated) { @@ -934,14 +944,14 @@ public class AnimatedEmojiDrawable extends Drawable { changeProgress.set(0, true); if (drawables[1] != null) { if (drawables[1] instanceof AnimatedEmojiDrawable) { - ((AnimatedEmojiDrawable) drawables[1]).removeView(parentView); + ((AnimatedEmojiDrawable) drawables[1]).removeView(this); } drawables[1] = null; } drawables[1] = drawables[0]; if (document != null) { drawables[0] = AnimatedEmojiDrawable.make(UserConfig.selectedAccount, cacheType, document); - ((AnimatedEmojiDrawable) drawables[0]).addView(parentView); + ((AnimatedEmojiDrawable) drawables[0]).addView(this); } else { drawables[0] = null; } @@ -950,7 +960,7 @@ public class AnimatedEmojiDrawable extends Drawable { detach(); if (document != null) { drawables[0] = AnimatedEmojiDrawable.make(UserConfig.selectedAccount, cacheType, document); - ((AnimatedEmojiDrawable) drawables[0]).addView(parentView); + ((AnimatedEmojiDrawable) drawables[0]).addView(this); } else { drawables[0] = null; } @@ -958,9 +968,7 @@ public class AnimatedEmojiDrawable extends Drawable { lastColor = 0xffffffff; colorFilter = null; play(); - if (parentView != null) { - parentView.invalidate(); - } + invalidate(); } public void set(Drawable drawable, boolean animated) { @@ -971,7 +979,7 @@ public class AnimatedEmojiDrawable extends Drawable { changeProgress.set(0, true); if (drawables[1] != null) { if (drawables[1] instanceof AnimatedEmojiDrawable) { - ((AnimatedEmojiDrawable) drawables[1]).removeView(parentView); + ((AnimatedEmojiDrawable) drawables[1]).removeView(this); } drawables[1] = null; } @@ -985,26 +993,24 @@ public class AnimatedEmojiDrawable extends Drawable { lastColor = 0xffffffff; colorFilter = null; play(); - if (parentView != null) { - parentView.invalidate(); - } + invalidate(); } public void detach() { if (drawables[0] instanceof AnimatedEmojiDrawable) { - ((AnimatedEmojiDrawable) drawables[0]).removeView(parentView); + ((AnimatedEmojiDrawable) drawables[0]).removeView(this); } if (drawables[1] instanceof AnimatedEmojiDrawable) { - ((AnimatedEmojiDrawable) drawables[1]).removeView(parentView); + ((AnimatedEmojiDrawable) drawables[1]).removeView(this); } } public void attach() { if (drawables[0] instanceof AnimatedEmojiDrawable) { - ((AnimatedEmojiDrawable) drawables[0]).addView(parentView); + ((AnimatedEmojiDrawable) drawables[0]).addView(this); } if (drawables[1] instanceof AnimatedEmojiDrawable) { - ((AnimatedEmojiDrawable) drawables[1]).addView(parentView); + ((AnimatedEmojiDrawable) drawables[1]).addView(this); } } @@ -1028,5 +1034,16 @@ public class AnimatedEmojiDrawable extends Drawable { public int getOpacity() { return PixelFormat.TRANSPARENT; } + + @Override + public void invalidate() { + if (parentView != null) { + if (invalidateParent && parentView.getParent() instanceof View) { + ((View) parentView.getParent()).invalidate(); + } else { + parentView.invalidate(); + } + } + } } } \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiSpan.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiSpan.java index f4779be48..3c6c25f5e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiSpan.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiSpan.java @@ -68,6 +68,17 @@ public class AnimatedEmojiSpan extends ReplacementSpan { } } + public static void applyFontMetricsForString(CharSequence text, Paint textPaint) { + if (text instanceof Spannable) { + AnimatedEmojiSpan[] spans = ((Spannable) text).getSpans(0, text.length(), AnimatedEmojiSpan.class); + if (spans != null) { + for (int k = 0; k < spans.length; ++k) { + spans[k].applyFontMetrics(textPaint.getFontMetricsInt()); + } + } + } + } + public long getDocumentId() { return document != null ? document.id : documentId; } @@ -83,6 +94,10 @@ public class AnimatedEmojiSpan extends ReplacementSpan { this.cacheType = cacheType; } + public void applyFontMetrics(Paint.FontMetricsInt newMetrics) { + fontMetrics = newMetrics; + } + @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { @@ -533,6 +548,10 @@ public class AnimatedEmojiSpan extends ReplacementSpan { HashMap groupedByLayout = new HashMap<>(); ArrayList backgroundDrawingArray = new ArrayList<>(); + public boolean isEmpty() { + return holders.isEmpty(); + } + public void add(Layout layout, AnimatedEmojiHolder holder) { holders.add(holder); SpansChunk chunkByLayout = groupedByLayout.get(layout); @@ -565,8 +584,7 @@ public class AnimatedEmojiSpan extends ReplacementSpan { } public void remove(int i) { - AnimatedEmojiHolder holder = holders.get(i); - holders.remove(i); + AnimatedEmojiHolder holder = holders.remove(i); SpansChunk chunkByLayout = groupedByLayout.get(holder.layout); if (chunkByLayout != null) { chunkByLayout.remove(holder); @@ -725,6 +743,7 @@ public class AnimatedEmojiSpan extends ReplacementSpan { public void draw(Canvas canvas, List spoilers, long time, float boundTop, float boundBottom, float drawingYOffset, float alpha, ColorFilter colorFilter) { for (int i = 0; i < holders.size(); ++i) { AnimatedEmojiHolder holder = holders.get(i); + if (holder == null) { continue; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedTextView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedTextView.java index cbe457d95..43738e13d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedTextView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedTextView.java @@ -1,6 +1,5 @@ package org.telegram.ui.Components; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.TimeInterpolator; @@ -18,6 +17,7 @@ import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; +import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; @@ -113,10 +113,14 @@ public class AnimatedTextView extends View { if (isRTL) { x = -x + 2 * lwidth - currentLayout[i].getWidth() - fullWidth; } - if ((gravity & Gravity.CENTER_HORIZONTAL) > 0) { - x += (fullWidth - lwidth) / 2f; - } else if ((gravity & Gravity.RIGHT) > 0 || isRTL) { - x += fullWidth - lwidth; + if ((gravity | ~Gravity.LEFT) != ~0) { + if ((gravity | ~Gravity.RIGHT) == ~0) { + x += fullWidth - lwidth; + } else if ((gravity | ~Gravity.CENTER_HORIZONTAL) == ~0) { + x += (fullWidth - lwidth) / 2f; + } else if (isRTL) { + x += fullWidth - lwidth; + } } canvas.translate(x, y); currentLayout[i].draw(canvas); @@ -134,10 +138,14 @@ public class AnimatedTextView extends View { if (isRTL) { x = -x + 2 * oldWidth - oldLayout[i].getWidth() - fullWidth; } - if ((gravity & Gravity.CENTER_HORIZONTAL) > 0) { - x += (fullWidth - oldWidth) / 2f; - } else if ((gravity & Gravity.RIGHT) > 0 || isRTL) { - x += fullWidth - oldWidth; + if ((gravity | ~Gravity.LEFT) != ~0) { + if ((gravity | ~Gravity.RIGHT) == ~0) { + x += fullWidth - oldWidth; + } else if ((gravity | ~Gravity.CENTER_HORIZONTAL) == ~0) { + x += (fullWidth - oldWidth) / 2f; + } else if (isRTL) { + x += fullWidth - oldWidth; + } } canvas.translate(x, y); oldLayout[i].draw(canvas); @@ -153,10 +161,14 @@ public class AnimatedTextView extends View { if (isRTL) { x = -x + 2 * currentWidth - currentLayout[i].getWidth() - fullWidth; } - if ((gravity & Gravity.CENTER_HORIZONTAL) > 0) { - x += (fullWidth - currentWidth) / 2f; - } else if ((gravity & Gravity.RIGHT) > 0 || isRTL) { - x += fullWidth - currentWidth; + if ((gravity | ~Gravity.LEFT) != ~0) { + if ((gravity | ~Gravity.RIGHT) == ~0) { + x += fullWidth - currentWidth; + } else if ((gravity | ~Gravity.CENTER_HORIZONTAL) == ~0) { + x += (fullWidth - currentWidth) / 2f; + } else if (isRTL) { + x += fullWidth - currentWidth; + } } canvas.translate(x, 0); currentLayout[i].draw(canvas); @@ -287,6 +299,10 @@ public class AnimatedTextView extends View { isRTL = this.oldLayout[0].isRtlCharAt(0); } + if (animator != null) { + animator.cancel(); + } + this.moveDown = moveDown; animator = ValueAnimator.ofFloat(t = 0f, 1f); animator.addUpdateListener(anm -> { @@ -687,9 +703,7 @@ public class AnimatedTextView extends View { private boolean toSetMoveDown; public AnimatedTextView(Context context) { - super(context); - drawable = new AnimatedTextDrawable(); - drawable.setCallback(this); + this(context, false, false, false); } public AnimatedTextView(Context context, boolean splitByWords, boolean preserveIndex, boolean startFromEnd) { @@ -768,12 +782,17 @@ public class AnimatedTextView extends View { return drawable.getText(); } + public int getTextHeight() { + return getPaint().getFontMetricsInt().descent - getPaint().getFontMetricsInt().ascent; + } + public void setTextSize(float textSizePx) { drawable.setTextSize(textSizePx); } public void setTextColor(int color) { drawable.setTextColor(color); + invalidate(); } public void setTypeface(Typeface typeface) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioPlayerAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioPlayerAlert.java index 44a90021d..9c8d14ee9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioPlayerAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioPlayerAlert.java @@ -1390,9 +1390,9 @@ public class AudioPlayerAlert extends BottomSheet implements NotificationCenter. final ArrayList fmessages = new ArrayList<>(); fmessages.add(messageObject); fragment.setDelegate((fragment1, dids, message, param) -> { - if (dids.size() > 1 || dids.get(0) == UserConfig.getInstance(currentAccount).getClientUserId() || message != null) { + if (dids.size() > 1 || dids.get(0) .dialogId== UserConfig.getInstance(currentAccount).getClientUserId() || message != null) { for (int a = 0; a < dids.size(); a++) { - long did = dids.get(a); + long did = dids.get(a).dialogId; if (message != null) { SendMessagesHelper.getInstance(currentAccount).sendMessage(message.toString(), did, null, null, null, true, null, null, null, true, 0, null, false); } @@ -1400,7 +1400,7 @@ public class AudioPlayerAlert extends BottomSheet implements NotificationCenter. } fragment1.finishFragment(); } else { - long did = dids.get(0); + long did = dids.get(0).dialogId; Bundle args1 = new Bundle(); args1.putBoolean("scrollToTopOnResume", true); if (DialogObject.isEncryptedDialog(did)) { @@ -1515,7 +1515,7 @@ public class AudioPlayerAlert extends BottomSheet implements NotificationCenter. blurredView.setTag(1); bigAlbumConver.setImageBitmap(coverContainer.getImageReceiver().getBitmap()); blurredAnimationInProgress = true; - BaseFragment fragment = parentActivity.getActionBarLayout().fragmentsStack.get(parentActivity.getActionBarLayout().fragmentsStack.size() - 1); + BaseFragment fragment = parentActivity.getActionBarLayout().getFragmentStack().get(parentActivity.getActionBarLayout().getFragmentStack().size() - 1); View fragmentView = fragment.getFragmentView(); int w = (int) (fragmentView.getMeasuredWidth() / 6.0f); int h = (int) (fragmentView.getMeasuredHeight() / 6.0f); @@ -2142,7 +2142,7 @@ public class AudioPlayerAlert extends BottomSheet implements NotificationCenter. break; } TLRPC.Document document; - if (messageObject.type == 0) { + if (messageObject.type == MessageObject.TYPE_TEXT) { document = messageObject.messageOwner.media.webpage.document; } else { document = messageObject.messageOwner.media.document; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioVisualizerDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioVisualizerDrawable.java index 49e84a009..566210e02 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioVisualizerDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioVisualizerDrawable.java @@ -110,6 +110,17 @@ public class AudioVisualizerDrawable { float rotation; + public void draw(Canvas canvas, float cx, float cy, boolean outOwner, float alpha, Theme.ResourcesProvider resourcesProvider) { + if (outOwner) { + p1.setColor(Theme.getColor(Theme.key_chat_outLoader, resourcesProvider)); + p1.setAlpha((int) (ALPHA * alpha)); + } else { + p1.setColor(Theme.getColor(Theme.key_chat_inLoader, resourcesProvider)); + p1.setAlpha((int) (ALPHA * alpha)); + } + this.draw(canvas, cx, cy); + } + public void draw(Canvas canvas, float cx, float cy, boolean outOwner, Theme.ResourcesProvider resourcesProvider) { if (outOwner) { p1.setColor(Theme.getColor(Theme.key_chat_outLoader, resourcesProvider)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java index 0a98108f8..d06e70a52 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java @@ -18,21 +18,17 @@ import android.os.Build; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; -import android.util.Log; -import android.util.LruCache; -import android.util.Pair; import androidx.core.graphics.ColorUtils; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.Emoji; import org.telegram.messenger.FileLog; import org.telegram.messenger.UserObject; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; -import java.util.Objects; - public class AvatarDrawable extends Drawable { private TextPaint namePaint; @@ -48,6 +44,7 @@ public class AvatarDrawable extends Drawable { private float archivedAvatarProgress; private boolean smallSize; private StringBuilder stringBuilder = new StringBuilder(5); + private int roundRadius = -1; public static final int AVATAR_TYPE_NORMAL = 0; public static final int AVATAR_TYPE_SAVED = 1; @@ -282,7 +279,8 @@ public class AvatarDrawable extends Drawable { } if (stringBuilder.length() > 0) { - String text = stringBuilder.toString().toUpperCase(); + CharSequence text = stringBuilder.toString().toUpperCase(); + text = Emoji.replaceEmoji(text, namePaint.getFontMetricsInt(), AndroidUtilities.dp(16), true); try { textLayout = new StaticLayout(text, namePaint, AndroidUtilities.dp(100), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (textLayout.getLineCount() > 0) { @@ -309,7 +307,12 @@ public class AvatarDrawable extends Drawable { Theme.avatar_backgroundPaint.setColor(ColorUtils.setAlphaComponent(getColor(), alpha)); canvas.save(); canvas.translate(bounds.left, bounds.top); - canvas.drawCircle(size / 2.0f, size / 2.0f, size / 2.0f, Theme.avatar_backgroundPaint); + if (roundRadius > 0) { + AndroidUtilities.rectTmp.set(0, 0, size, size); + canvas.drawRoundRect(AndroidUtilities.rectTmp, roundRadius, roundRadius, Theme.avatar_backgroundPaint); + } else { + canvas.drawCircle(size / 2.0f, size / 2.0f, size / 2.0f, Theme.avatar_backgroundPaint); + } if (avatarType == AVATAR_TYPE_ARCHIVED) { if (archivedAvatarProgress != 0) { @@ -436,4 +439,8 @@ public class AvatarDrawable extends Drawable { Integer color = resourcesProvider != null ? resourcesProvider.getColor(key) : null; return color != null ? color : Theme.getColor(key); } + + public void setRoundRadius(int roundRadius) { + this.roundRadius = roundRadius; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarsDarawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarsDarawable.java index b06e27caa..53d5f76d8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarsDarawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarsDarawable.java @@ -266,6 +266,17 @@ public class AvatarsDarawable { xRefP.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); } + public void setAvatarsTextSize(int newTextSize) { + for (int a = 0; a < 3; a++) { + if (currentStates[a] != null && currentStates[a].avatarDrawable != null) { + currentStates[a].avatarDrawable.setTextSize(newTextSize); + } + if (animatingStates[a] != null && animatingStates[a].avatarDrawable != null) { + animatingStates[a].avatarDrawable.setTextSize(newTextSize); + } + } + } + public void setObject(int index, int account, TLObject object) { animatingStates[index].id = 0; animatingStates[index].participant = null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BackButtonMenu.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BackButtonMenu.java index 132777900..52b919761 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BackButtonMenu.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BackButtonMenu.java @@ -7,7 +7,6 @@ import android.os.Bundle; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; -import android.view.HapticFeedbackConstants; import android.view.View; import android.view.WindowManager; import android.widget.FrameLayout; @@ -19,16 +18,16 @@ import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; import org.telegram.messenger.UserObject; import org.telegram.tgnet.TLRPC; -import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.ActionBarPopupWindow; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ChatActivity; import org.telegram.ui.ProfileActivity; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; public class BackButtonMenu { @@ -46,7 +45,7 @@ public class BackButtonMenu { if (thisFragment == null) { return null; } - final ActionBarLayout parentLayout = thisFragment.getParentLayout(); + final INavigationLayout parentLayout = thisFragment.getParentLayout(); final Context context = thisFragment.getParentActivity(); final View fragmentView = thisFragment.getFragmentView(); if (parentLayout == null || context == null || fragmentView == null) { @@ -125,10 +124,10 @@ public class BackButtonMenu { } if (pDialog.stackIndex >= 0) { Long nextFragmentDialogId = null; - if (parentLayout == null || parentLayout.fragmentsStack == null || pDialog.stackIndex >= parentLayout.fragmentsStack.size()) { + if (parentLayout == null || parentLayout.getFragmentStack() == null || pDialog.stackIndex >= parentLayout.getFragmentStack().size()) { nextFragmentDialogId = null; } else { - BaseFragment nextFragment = parentLayout.fragmentsStack.get(pDialog.stackIndex); + BaseFragment nextFragment = parentLayout.getFragmentStack().get(pDialog.stackIndex); if (nextFragment instanceof ChatActivity) { nextFragmentDialogId = ((ChatActivity) nextFragment).getDialogId(); } else if (nextFragment instanceof ProfileActivity) { @@ -136,18 +135,18 @@ public class BackButtonMenu { } } if (nextFragmentDialogId != null && nextFragmentDialogId != pDialog.dialogId) { - for (int j = parentLayout.fragmentsStack.size() - 2; j > pDialog.stackIndex; --j) { + for (int j = parentLayout.getFragmentStack().size() - 2; j > pDialog.stackIndex; --j) { parentLayout.removeFragmentFromStack(j); } } else { - if (parentLayout != null && parentLayout.fragmentsStack != null) { - for (int j = parentLayout.fragmentsStack.size() - 2; j > pDialog.stackIndex; --j) { - if (j >= 0 && j < parentLayout.fragmentsStack.size()) { + if (parentLayout != null && parentLayout.getFragmentStack() != null) { + for (int j = parentLayout.getFragmentStack().size() - 2; j > pDialog.stackIndex; --j) { + if (j >= 0 && j < parentLayout.getFragmentStack().size()) { parentLayout.removeFragmentFromStack(j); } } - if (pDialog.stackIndex < parentLayout.fragmentsStack.size()) { - parentLayout.showFragment(pDialog.stackIndex); + if (pDialog.stackIndex < parentLayout.getFragmentStack().size()) { + parentLayout.bringToFront(pDialog.stackIndex); parentLayout.closeLastFragment(true); return; } @@ -214,11 +213,11 @@ public class BackButtonMenu { ArrayList dialogs = new ArrayList<>(); if (thisFragment == null) return dialogs; - final ActionBarLayout parentLayout = thisFragment.getParentLayout(); + final INavigationLayout parentLayout = thisFragment.getParentLayout(); if (parentLayout == null) return dialogs; - ArrayList fragmentsStack = parentLayout.fragmentsStack; - ArrayList pulledDialogs = parentLayout.pulledDialogs; + List fragmentsStack = parentLayout.getFragmentStack(); + List pulledDialogs = parentLayout.getPulledDialogs(); if (fragmentsStack != null) { final int count = fragmentsStack.size(); for (int i = 0; i < count; ++i) { @@ -306,15 +305,15 @@ public class BackButtonMenu { if (thisFragment == null) { return; } - final ActionBarLayout parentLayout = thisFragment.getParentLayout(); + INavigationLayout parentLayout = thisFragment.getParentLayout(); if (parentLayout == null) { return; } - if (parentLayout.pulledDialogs == null) { - parentLayout.pulledDialogs = new ArrayList<>(); + if (parentLayout.getPulledDialogs() == null) { + parentLayout.setPulledDialogs(new ArrayList<>()); } boolean alreadyAdded = false; - for (PulledDialog d : parentLayout.pulledDialogs) { + for (PulledDialog d : parentLayout.getPulledDialogs()) { if (d.dialogId == dialogId) { alreadyAdded = true; break; @@ -330,21 +329,21 @@ public class BackButtonMenu { d.folderId = folderId; d.chat = chat; d.user = user; - parentLayout.pulledDialogs.add(d); + parentLayout.getPulledDialogs().add(d); } } public static void clearPulledDialogs(BaseFragment thisFragment, int fromIndex) { if (thisFragment == null) { return; } - final ActionBarLayout parentLayout = thisFragment.getParentLayout(); + final INavigationLayout parentLayout = thisFragment.getParentLayout(); if (parentLayout == null) { return; } - if (parentLayout.pulledDialogs != null) { - for (int i = 0; i < parentLayout.pulledDialogs.size(); ++i) { - if (parentLayout.pulledDialogs.get(i).stackIndex > fromIndex) { - parentLayout.pulledDialogs.remove(i); + if (parentLayout.getPulledDialogs() != null) { + for (int i = 0; i < parentLayout.getPulledDialogs().size(); ++i) { + if (parentLayout.getPulledDialogs().get(i).stackIndex > fromIndex) { + parentLayout.getPulledDialogs().remove(i); i--; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java index 1f6eb0486..c502a65e5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java @@ -8,6 +8,7 @@ package org.telegram.ui.Components; +import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -220,4 +221,23 @@ public class BackupImageView extends View { animatedEmojiDrawable.addView(this); } } + + ValueAnimator roundRadiusAnimator; + + public void animateToRoundRadius(int animateToRad) { + if (getRoundRadius()[0] != animateToRad) { + if (roundRadiusAnimator != null) { + roundRadiusAnimator.cancel(); + } + roundRadiusAnimator = ValueAnimator.ofInt(getRoundRadius()[0], animateToRad); + roundRadiusAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + setRoundRadius((Integer) animation.getAnimatedValue()); + } + }); + roundRadiusAnimator.setDuration(200); + roundRadiusAnimator.start(); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewContainer.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewContainer.java index af8be0ea2..4fa5cf839 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewContainer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewContainer.java @@ -866,9 +866,15 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent return isBackButtonVisible; } - @SuppressWarnings("deprecation") public void evaluateJs(String script) { - checkCreateWebView(); + evaluateJs(script, true); + } + + @SuppressWarnings("deprecation") + public void evaluateJs(String script, boolean create) { + if (create) { + checkCreateWebView(); + } if (webView == null) { return; } @@ -904,7 +910,7 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent } private void notifyEvent(String event, JSONObject eventData) { - evaluateJs("window.Telegram.WebView.receiveEvent('" + event + "', " + eventData + ");"); + evaluateJs("window.Telegram.WebView.receiveEvent('" + event + "', " + eventData + ");", false); } public void setWebViewScrollListener(WebViewScrollListener webViewScrollListener) { @@ -1170,11 +1176,7 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent } } if (vibrationEffect != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - AndroidUtilities.getVibrator().vibrate(vibrationEffect.getVibrationEffectForOreo()); - } else { - AndroidUtilities.getVibrator().vibrate(vibrationEffect.fallbackTimings, -1); - } + vibrationEffect.vibrate(); } } catch (Exception e) { FileLog.e(e); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Bulletin.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Bulletin.java index 711740eeb..2bc3a952f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Bulletin.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Bulletin.java @@ -6,15 +6,20 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; +import android.app.Dialog; import android.content.Context; import android.content.res.Configuration; import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; +import android.text.StaticLayout; +import android.text.TextPaint; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.util.Property; @@ -24,6 +29,8 @@ import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; @@ -59,6 +66,7 @@ public class Bulletin { public static final int DURATION_SHORT = 1500; public static final int DURATION_LONG = 2750; + public static final int DURATION_PROLONG = 5000; public static final int TYPE_STICKER = 0; public static final int TYPE_ERROR = 1; @@ -71,7 +79,7 @@ public class Bulletin { public int hash; public static Bulletin make(@NonNull FrameLayout containerLayout, @NonNull Layout contentLayout, int duration) { - return new Bulletin(containerLayout, contentLayout, duration); + return new Bulletin(null, containerLayout, contentLayout, duration); } @SuppressLint("RtlHardcoded") @@ -81,7 +89,7 @@ public class Bulletin { } else if (fragment instanceof DialogsActivity) { contentLayout.setWideScreenParams(ViewGroup.LayoutParams.MATCH_PARENT, Gravity.NO_GRAVITY); } - return new Bulletin(fragment.getLayoutContainer(), contentLayout, duration); + return new Bulletin(fragment, fragment.getLayoutContainer(), contentLayout, duration); } public static Bulletin find(@NonNull FrameLayout containerLayout) { @@ -106,12 +114,14 @@ public class Bulletin { } private static final HashMap delegates = new HashMap<>(); + private static final HashMap fragmentDelegates = new HashMap<>(); @SuppressLint("StaticFieldLeak") private static Bulletin visibleBulletin; private final Layout layout; private final ParentLayout parentLayout; + private final BaseFragment containerFragment; private final FrameLayout containerLayout; private final Runnable hideRunnable = this::hide; private int duration; @@ -125,10 +135,11 @@ public class Bulletin { private Bulletin() { layout = null; parentLayout = null; + containerFragment = null; containerLayout = null; } - private Bulletin(@NonNull FrameLayout containerLayout, @NonNull Layout layout, int duration) { + private Bulletin(BaseFragment fragment, @NonNull FrameLayout containerLayout, @NonNull Layout layout, int duration) { this.layout = layout; this.parentLayout = new ParentLayout(layout) { @Override @@ -144,6 +155,7 @@ public class Bulletin { hide(); } }; + this.containerFragment = fragment; this.containerLayout = containerLayout; this.duration = duration; } @@ -163,8 +175,13 @@ public class Bulletin { } public Bulletin show() { + return show(false); + } + + public Bulletin show(boolean top) { if (!showing && containerLayout != null) { showing = true; + layout.setTop(top); CharSequence text = layout.getAccessibilityText(); if (text != null) { @@ -183,11 +200,11 @@ public class Bulletin { layout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + public void onLayoutChange(View v, int left, int t, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { layout.removeOnLayoutChangeListener(this); if (showing) { layout.onShow(); - currentDelegate = delegates.get(containerLayout); + currentDelegate = findDelegate(containerFragment, containerLayout); currentBottomOffset = currentDelegate != null ? currentDelegate.getBottomOffset(tag) : 0; if (currentDelegate != null) { currentDelegate.onShow(Bulletin.this); @@ -202,13 +219,13 @@ public class Bulletin { layout.onEnterTransitionEnd(); setCanHide(true); }, offset -> { - if (currentDelegate != null) { - currentDelegate.onOffsetChange(layout.getHeight() - offset); + if (currentDelegate != null && !top) { + currentDelegate.onBottomOffsetChange(layout.getHeight() - offset); } }, currentBottomOffset); } else { - if (currentDelegate != null) { - currentDelegate.onOffsetChange(layout.getHeight() - currentBottomOffset); + if (currentDelegate != null && !top) { + currentDelegate.onBottomOffsetChange(layout.getHeight() - currentBottomOffset); } updatePosition(); layout.onEnterTransitionStart(); @@ -223,6 +240,7 @@ public class Bulletin { @Override public void onViewAttachedToWindow(View v) { } + @Override public void onViewDetachedFromWindow(View v) { layout.removeOnAttachStateChangeListener(this); @@ -288,8 +306,8 @@ public class Bulletin { ensureLayoutTransitionCreated(); } layoutTransition.animateExit(layout, layout::onExitTransitionStart, () -> { - if (currentDelegate != null) { - currentDelegate.onOffsetChange(0); + if (currentDelegate != null && !layout.top) { + currentDelegate.onBottomOffsetChange(0); currentDelegate.onHide(this); } layout.transitionRunningExit = false; @@ -298,16 +316,16 @@ public class Bulletin { containerLayout.removeView(parentLayout); layout.onDetach(); }, offset -> { - if (currentDelegate != null) { - currentDelegate.onOffsetChange(layout.getHeight() - offset); + if (currentDelegate != null && !layout.top) { + currentDelegate.onBottomOffsetChange(layout.getHeight() - offset); } }, bottomOffset); return; } } - if (currentDelegate != null) { - currentDelegate.onOffsetChange(0); + if (currentDelegate != null && !layout.top) { + currentDelegate.onBottomOffsetChange(0); currentDelegate.onHide(this); } layout.onExitTransitionStart(); @@ -468,26 +486,32 @@ public class Bulletin { } protected abstract void onPressedStateChanged(boolean pressed); + protected abstract void onHide(); } //region Offset Providers public static void addDelegate(@NonNull BaseFragment fragment, @NonNull Delegate delegate) { - final FrameLayout containerLayout = fragment.getLayoutContainer(); - if (containerLayout != null) { - addDelegate(containerLayout, delegate); - } + fragmentDelegates.put(fragment, delegate); } public static void addDelegate(@NonNull FrameLayout containerLayout, @NonNull Delegate delegate) { delegates.put(containerLayout, delegate); } - public static void removeDelegate(@NonNull BaseFragment fragment) { - final FrameLayout containerLayout = fragment.getLayoutContainer(); - if (containerLayout != null) { - removeDelegate(containerLayout); + private static Delegate findDelegate(BaseFragment probableFragment, FrameLayout probableContainer) { + Delegate delegate; + if ((delegate = fragmentDelegates.get(probableFragment)) != null) { + return delegate; } + if ((delegate = delegates.get(probableContainer)) != null) { + return delegate; + } + return null; + } + + public static void removeDelegate(@NonNull BaseFragment fragment) { + fragmentDelegates.remove(fragment); } public static void removeDelegate(@NonNull FrameLayout containerLayout) { @@ -500,7 +524,11 @@ public class Bulletin { return 0; } - default void onOffsetChange(float offset) { + default int getTopOffset(int tag) { + return 0; + } + + default void onBottomOffsetChange(float offset) { } default void onShow(Bulletin bulletin) { @@ -522,6 +550,7 @@ public class Bulletin { protected Bulletin bulletin; Drawable background; + private boolean top; public boolean isTransitionRunning() { return transitionRunningEnter || transitionRunningExit; @@ -539,12 +568,12 @@ public class Bulletin { setMinimumHeight(AndroidUtilities.dp(48)); setBackground(getThemedColor(Theme.key_undo_background)); updateSize(); - setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8)); + setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8)); setWillNotDraw(false); } protected void setBackground(int color) { - background = Theme.createRoundRectDrawable(AndroidUtilities.dp(6), color); + background = Theme.createRoundRectDrawable(AndroidUtilities.dp(10), color); } public final static FloatPropertyCompat IN_OUT_OFFSET_Y = new FloatPropertyCompat("offsetY") { @@ -578,9 +607,14 @@ public class Bulletin { updateSize(); } + private void setTop(boolean top) { + this.top = top; + updateSize(); + } + private void updateSize() { final boolean isWideScreen = isWideScreen(); - setLayoutParams(LayoutHelper.createFrame(isWideScreen ? wideScreenWidth : LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, isWideScreen ? Gravity.BOTTOM | wideScreenGravity : Gravity.BOTTOM)); + setLayoutParams(LayoutHelper.createFrame(isWideScreen ? wideScreenWidth : LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, isWideScreen ? (top ? Gravity.TOP : Gravity.BOTTOM) | wideScreenGravity : (top ? Gravity.TOP : Gravity.BOTTOM))); } private boolean isWideScreen() { @@ -682,6 +716,7 @@ public class Bulletin { callbacks.get(i).onExitTransitionStart(this); } } + @CallSuper protected void onExitTransitionEnd() { for (int i = 0, size = callbacks.size(); i < size; i++) { @@ -699,22 +734,41 @@ public class Bulletin { } public void updatePosition() { - float tranlsation = 0; + float translation = 0; if (delegate != null) { - tranlsation += delegate.getBottomOffset(bulletin != null ? bulletin.tag : 0) ; + if (top) { + translation -= delegate.getTopOffset(bulletin != null ? bulletin.tag : 0); + } else { + translation += delegate.getBottomOffset(bulletin != null ? bulletin.tag : 0); + } } - setTranslationY(-tranlsation + inOutOffset); + setTranslationY(-translation + inOutOffset * (top ? -1 : 1)); } public interface Callback { - default void onAttach(@NonNull Layout layout, @NonNull Bulletin bulletin) {} - default void onDetach(@NonNull Layout layout) {} - default void onShow(@NonNull Layout layout) {} - default void onHide(@NonNull Layout layout) {} - default void onEnterTransitionStart(@NonNull Layout layout) {} - default void onEnterTransitionEnd(@NonNull Layout layout) {} - default void onExitTransitionStart(@NonNull Layout layout) {} - default void onExitTransitionEnd(@NonNull Layout layout) {} + default void onAttach(@NonNull Layout layout, @NonNull Bulletin bulletin) { + } + + default void onDetach(@NonNull Layout layout) { + } + + default void onShow(@NonNull Layout layout) { + } + + default void onHide(@NonNull Layout layout) { + } + + default void onEnterTransitionStart(@NonNull Layout layout) { + } + + default void onEnterTransitionEnd(@NonNull Layout layout) { + } + + default void onExitTransitionStart(@NonNull Layout layout) { + } + + default void onExitTransitionEnd(@NonNull Layout layout) { + } } //endregion @@ -726,6 +780,7 @@ public class Bulletin { public interface Transition { void animateEnter(@NonNull Layout layout, @Nullable Runnable startAction, @Nullable Runnable endAction, @Nullable Consumer onUpdate, int bottomOffset); + void animateExit(@NonNull Layout layout, @Nullable Runnable startAction, @Nullable Runnable endAction, @Nullable Consumer onUpdate, int bottomOffset); } @@ -826,7 +881,7 @@ public class Bulletin { } @Override - public void animateExit(@NonNull Layout layout, @Nullable Runnable startAction, @Nullable Runnable endAction, @Nullable Consumer onUpdate,int bottomOffset) { + public void animateExit(@NonNull Layout layout, @Nullable Runnable startAction, @Nullable Runnable endAction, @Nullable Consumer onUpdate, int bottomOffset) { final SpringAnimation springAnimation = new SpringAnimation(layout, IN_OUT_OFFSET_Y, layout.getHeight()); springAnimation.getSpring().setDampingRatio(DAMPING_RATIO); springAnimation.getSpring().setStiffness(STIFFNESS); @@ -859,10 +914,13 @@ public class Bulletin { } background.setBounds(AndroidUtilities.dp(8), AndroidUtilities.dp(8), getMeasuredWidth() - AndroidUtilities.dp(8), getMeasuredHeight() - AndroidUtilities.dp(8)); if (isTransitionRunning() && delegate != null) { - int clipBottom = ((View) getParent()).getMeasuredHeight() - delegate.getBottomOffset(bulletin.tag); - int viewBottom = (int) (getY() + getMeasuredHeight()); canvas.save(); - canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight() - (viewBottom - clipBottom)); + canvas.clipRect( + 0, + delegate.getTopOffset(bulletin.tag) - getY(), + getMeasuredWidth(), + ((View) getParent()).getMeasuredHeight() - delegate.getBottomOffset(bulletin.tag) - getY() + ); background.draw(canvas); super.dispatchDraw(canvas); canvas.restore(); @@ -884,11 +942,14 @@ public class Bulletin { public static class ButtonLayout extends Layout { private Button button; + public TimerView timerView; private int childrenMeasuredWidth; + Theme.ResourcesProvider resourcesProvider; public ButtonLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider) { super(context, resourcesProvider); + this.resourcesProvider = resourcesProvider; } @Override @@ -927,6 +988,12 @@ public class Bulletin { addView(button, 0, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.END | Gravity.CENTER_VERTICAL)); } } + + public void setTimer() { + timerView = new TimerView(getContext(), resourcesProvider); + timerView.timeLeft = 5000; + addView(timerView, LayoutHelper.createFrameRelatively(20, 20, Gravity.START | Gravity.CENTER_VERTICAL, 21, 0, 21, 0)); + } } public static class SimpleLayout extends ButtonLayout { @@ -1150,6 +1217,51 @@ public class Bulletin { return textView.getText(); } } + + public static class UsersLayout extends ButtonLayout { + + public AvatarsImageView avatarsImageView; + public TextView textView; + + public UsersLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider) { + super(context, resourcesProvider); + + avatarsImageView = new AvatarsImageView(context, false); + avatarsImageView.setStyle(AvatarsDarawable.STYLE_MESSAGE_SEEN); + addView(avatarsImageView, LayoutHelper.createFrameRelatively(24 + 12 + 12 + 8, 48, Gravity.START | Gravity.CENTER_VERTICAL, 12, 0, 0, 0)); + + textView = new LinkSpanDrawable.LinksTextView(context); + textView.setSingleLine(); + textView.setTypeface(Typeface.SANS_SERIF); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setPadding(0, AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8)); + addView(textView, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 12 + 56 + 2, 0, 8, 0)); + + textView.setLinkTextColor(getThemedColor(Theme.key_undo_cancelColor)); + setTextColor(getThemedColor(Theme.key_undo_infoColor)); + setBackground(getThemedColor(Theme.key_undo_background)); + } + + public UsersLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider, int backgroundColor, int textColor) { + this(context, resourcesProvider); + setBackground(backgroundColor); + setTextColor(textColor); + } + + public void setTextColor(int textColor) { + textView.setTextColor(textColor); + } + + @Override + protected void onShow() { + super.onShow(); + } + + public CharSequence getAccessibilityText() { + return textView.getText(); + } + } //endregion //region Buttons @@ -1296,4 +1408,170 @@ public class Bulletin { return this; } } + + private static class TimerView extends View { + + private final Paint progressPaint; + private long timeLeft; + private int prevSeconds; + private String timeLeftString; + private int textWidth; + + StaticLayout timeLayout; + StaticLayout timeLayoutOut; + int textWidthOut; + + float timeReplaceProgress = 1f; + + private TextPaint textPaint; + private long lastUpdateTime; + RectF rect = new RectF(); + + public TimerView(Context context, Theme.ResourcesProvider resourcesProvider) { + super(context); + + textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + textPaint.setTextSize(AndroidUtilities.dp(12)); + textPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + textPaint.setColor(Theme.getColor(Theme.key_undo_infoColor, resourcesProvider)); + + progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + progressPaint.setStyle(Paint.Style.STROKE); + progressPaint.setStrokeWidth(AndroidUtilities.dp(2)); + progressPaint.setStrokeCap(Paint.Cap.ROUND); + progressPaint.setColor(Theme.getColor(Theme.key_undo_infoColor, resourcesProvider)); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + int newSeconds = timeLeft > 0 ? (int) Math.ceil(timeLeft / 1000.0f) : 0; + rect.set(AndroidUtilities.dp(1), AndroidUtilities.dp(1), getMeasuredWidth() - AndroidUtilities.dp(1), getMeasuredHeight() - AndroidUtilities.dp(1)); + if (prevSeconds != newSeconds) { + prevSeconds = newSeconds; + timeLeftString = String.format("%d", Math.max(1, newSeconds)); + if (timeLayout != null) { + timeLayoutOut = timeLayout; + timeReplaceProgress = 0; + textWidthOut = textWidth; + } + textWidth = (int) Math.ceil(textPaint.measureText(timeLeftString)); + timeLayout = new StaticLayout(timeLeftString, textPaint, Integer.MAX_VALUE, android.text.Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } + + if (timeReplaceProgress < 1f) { + timeReplaceProgress += 16f / 150f; + if (timeReplaceProgress > 1f) { + timeReplaceProgress = 1f; + } else { + invalidate(); + } + } + + int alpha = textPaint.getAlpha(); + + if (timeLayoutOut != null && timeReplaceProgress < 1f) { + textPaint.setAlpha((int) (alpha * (1f - timeReplaceProgress))); + canvas.save(); + canvas.translate(rect.centerX() - textWidthOut / 2f, rect.centerY() - timeLayoutOut.getHeight() / 2f + AndroidUtilities.dp(10) * timeReplaceProgress); + timeLayoutOut.draw(canvas); + textPaint.setAlpha(alpha); + canvas.restore(); + } + + if (timeLayout != null) { + if (timeReplaceProgress != 1f) { + textPaint.setAlpha((int) (alpha * timeReplaceProgress)); + } + canvas.save(); + canvas.translate(rect.centerX() - textWidth / 2f, rect.centerY() - timeLayout.getHeight() / 2f - AndroidUtilities.dp(10) * (1f - timeReplaceProgress)); + timeLayout.draw(canvas); + if (timeReplaceProgress != 1f) { + textPaint.setAlpha(alpha); + } + canvas.restore(); + } + + canvas.drawArc(rect, -90, -360 * (timeLeft / 5000.0f), false, progressPaint); + + if (lastUpdateTime != 0) { + long newTime = System.currentTimeMillis(); + long dt = newTime - lastUpdateTime; + timeLeft -= dt; + lastUpdateTime = newTime; + } else { + lastUpdateTime = System.currentTimeMillis(); + } + invalidate(); + } + } + + // to make bulletin above everything + // use as BulletinFactory.of(BulletinWindow.make(context), resourcesProvider)... + public static class BulletinWindow extends Dialog { + public static FrameLayout make(Context context) { + return new BulletinWindow(context).container; + } + + private final FrameLayout container; + private BulletinWindow(Context context) { + super(context); + setContentView( + container = new FrameLayout(context) { + @Override + public void addView(View child) { + super.addView(child); + BulletinWindow.this.show(); + } + + @Override + public void removeView(View child) { + super.removeView(child); + BulletinWindow.this.dismiss(); + removeDelegate(container); + } + }, + new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + ); + + addDelegate(container, new Delegate() { + @Override + public int getBottomOffset(int tag) { + return 0; + } + + @Override + public int getTopOffset(int tag) { + return AndroidUtilities.statusBarHeight; + } + }); + + try { + Window window = getWindow(); + window.setWindowAnimations(R.style.DialogNoAnimation); + window.setBackgroundDrawable(null); + WindowManager.LayoutParams params = window.getAttributes(); + params.width = ViewGroup.LayoutParams.MATCH_PARENT; + params.gravity = Gravity.TOP | Gravity.LEFT; + params.dimAmount = 0; + params.flags &= ~WindowManager.LayoutParams.FLAG_DIM_BEHIND; + params.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + params.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; + } + params.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + if (Build.VERSION.SDK_INT >= 21) { + params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | + WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR | + WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + } + params.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN; + params.height = ViewGroup.LayoutParams.MATCH_PARENT; + if (Build.VERSION.SDK_INT >= 28) { + params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + } + window.setAttributes(params); + } catch (Exception ignore) {} + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BulletinFactory.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BulletinFactory.java index 0c164048b..ae23adf56 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BulletinFactory.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BulletinFactory.java @@ -1,15 +1,9 @@ package org.telegram.ui.Components; -import android.app.DownloadManager; import android.content.Context; -import android.content.Intent; import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Environment; import android.text.Spannable; -import android.text.SpannableString; import android.text.SpannableStringBuilder; -import android.text.Spanned; import android.text.TextPaint; import android.text.style.ClickableSpan; import android.util.TypedValue; @@ -19,10 +13,9 @@ import android.widget.FrameLayout; import androidx.annotation.CheckResult; import androidx.annotation.NonNull; -import androidx.core.content.FileProvider; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.BuildConfig; +import org.telegram.messenger.ChatObject; import org.telegram.messenger.DialogObject; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaDataController; @@ -36,7 +29,7 @@ import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.PremiumPreviewFragment; -import java.io.File; +import java.util.ArrayList; public final class BulletinFactory { @@ -143,13 +136,13 @@ public final class BulletinFactory { this.resourcesProvider = resourcesProvider; } - public Bulletin createSimpleBulletin(int iconRawId, String text) { + public Bulletin createSimpleBulletin(int iconRawId, CharSequence text) { final Bulletin.LottieLayout layout = new Bulletin.LottieLayout(getContext(), resourcesProvider); layout.setAnimation(iconRawId, 36, 36); layout.textView.setText(text); layout.textView.setSingleLine(false); layout.textView.setMaxLines(2); - return create(layout, Bulletin.DURATION_SHORT); + return create(layout, text.length() < 20 ? Bulletin.DURATION_SHORT : Bulletin.DURATION_LONG); } public Bulletin createSimpleBulletin(int iconRawId, CharSequence text, CharSequence subtext) { @@ -157,10 +150,14 @@ public final class BulletinFactory { layout.setAnimation(iconRawId, 36, 36); layout.titleTextView.setText(text); layout.subtitleTextView.setText(subtext); - return create(layout, Bulletin.DURATION_SHORT); + return create(layout, text.length() < 20 ? Bulletin.DURATION_SHORT : Bulletin.DURATION_LONG); } public Bulletin createSimpleBulletin(int iconRawId, CharSequence text, CharSequence button, Runnable onButtonClick) { + return createSimpleBulletin(iconRawId, text, button, text.length() < 20 ? Bulletin.DURATION_SHORT : Bulletin.DURATION_LONG, onButtonClick); + } + + public Bulletin createSimpleBulletin(int iconRawId, CharSequence text, CharSequence button, int duration, Runnable onButtonClick) { final Bulletin.LottieLayout layout = new Bulletin.LottieLayout(getContext(), resourcesProvider); layout.setAnimation(iconRawId, 36, 36); layout.textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); @@ -168,7 +165,7 @@ public final class BulletinFactory { layout.textView.setMaxLines(3); layout.textView.setText(text); layout.setButton(new Bulletin.UndoButton(getContext(), true, resourcesProvider).setText(button).setUndoAction(onButtonClick)); - return create(layout, Bulletin.DURATION_SHORT); + return create(layout, duration); } public Bulletin createSimpleBulletin(Drawable drawable, CharSequence text, String button, Runnable onButtonClick) { @@ -181,6 +178,58 @@ public final class BulletinFactory { return create(layout, Bulletin.DURATION_LONG); } + public Bulletin createUndoBulletin(CharSequence text, Runnable onUndo, Runnable onAction) { + final Bulletin.LottieLayout layout = new Bulletin.LottieLayout(getContext(), resourcesProvider); + layout.textView.setText(text); + layout.textView.setSingleLine(false); + layout.textView.setMaxLines(2); + layout.setTimer(); + layout.setButton(new Bulletin.UndoButton(getContext(), true, resourcesProvider).setText(LocaleController.getString("Undo", R.string.Undo)).setUndoAction(onUndo).setDelayedAction(onAction)); + return create(layout, Bulletin.DURATION_PROLONG); + } + + public Bulletin createUsersBulletin(ArrayList users, CharSequence text) { + final Bulletin.UsersLayout layout = new Bulletin.UsersLayout(getContext(), resourcesProvider); + int count = 0; + if (users != null) { + for (int i = 0; i < users.size(); ++i) { + if (count >= 3) + break; + TLRPC.User user = users.get(i); + if (user != null) { + layout.avatarsImageView.setCount(++count); + layout.avatarsImageView.setObject(count - 1, UserConfig.selectedAccount, user); + } + } + } + layout.avatarsImageView.commitTransition(false); + layout.textView.setSingleLine(true); + layout.textView.setLines(1); + layout.textView.setText(text); + layout.textView.setTranslationX(-(3 - count) * AndroidUtilities.dp(12)); + return create(layout, Bulletin.DURATION_LONG); + } + + public Bulletin createUsersAddedBulletin(ArrayList users, TLRPC.Chat chat) { + CharSequence text; + if (users == null || users.size() == 0) { + text = null; + } else if (users.size() == 1) { + if (ChatObject.isChannelAndNotMegaGroup(chat)) { + text = AndroidUtilities.replaceTags(LocaleController.formatString("HasBeenAddedToChannel", R.string.HasBeenAddedToChannel, "**" + UserObject.getUserName(users.get(0)) + "**")); + } else { + text = AndroidUtilities.replaceTags(LocaleController.formatString("HasBeenAddedToGroup", R.string.HasBeenAddedToGroup, "**" + UserObject.getUserName(users.get(0)) + "**")); + } + } else { + if (ChatObject.isChannelAndNotMegaGroup(chat)) { + text = AndroidUtilities.replaceTags(LocaleController.formatPluralString("AddedMembersToChannel", users.size())); + } else { + text = AndroidUtilities.replaceTags(LocaleController.formatPluralString("AddedMembersToGroup", users.size())); + } + } + return createUsersBulletin(users, text); + } + public Bulletin createEmojiBulletin(String emoji, String text) { final Bulletin.LottieLayout layout = new Bulletin.LottieLayout(getContext(), resourcesProvider); layout.setAnimation(MediaDataController.getInstance(UserConfig.selectedAccount).getEmojiAnimatedSticker(emoji), 36, 36); @@ -462,6 +511,11 @@ public final class BulletinFactory { return of(containerLayout, null).createDownloadBulletin(video ? FileType.VIDEO : FileType.PHOTO, 1, backgroundColor, textColor); } + @CheckResult + public static Bulletin createSaveToGalleryBulletin(FrameLayout containerLayout, int amount, boolean video, int backgroundColor, int textColor) { + return of(containerLayout, null).createDownloadBulletin(video ? (amount > 1 ? FileType.VIDEOS : FileType.VIDEO) : (amount > 1 ? FileType.PHOTOS : FileType.PHOTO), amount, backgroundColor, textColor); + } + @CheckResult public static Bulletin createPromoteToAdminBulletin(BaseFragment fragment, String userFirstName) { final Bulletin.LottieLayout layout = new Bulletin.LottieLayout(fragment.getParentActivity(), fragment.getResourceProvider()); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java index f86913ee7..3724f852c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java @@ -2575,7 +2575,7 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific notifySilentDrawable.setCrossOut(silent, true); notifyButton.setImageDrawable(notifySilentDrawable); MessagesController.getNotificationsSettings(currentAccount).edit().putBoolean("silent_" + dialog_id, silent).commit(); - NotificationsController.getInstance(currentAccount).updateServerNotificationsSettings(dialog_id); + NotificationsController.getInstance(currentAccount).updateServerNotificationsSettings(dialog_id, fragment == null ? 0 :fragment.getTopicId()); try { if (visibleToast != null) { visibleToast.cancel(); @@ -2644,7 +2644,7 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific return; } - ViewGroup fl = parentFragment.getParentLayout(); + ViewGroup fl = parentFragment.getParentLayout().getOverlayContainerView(); senderSelectPopupWindow = new SenderSelectPopup(context, parentFragment, controller, chatFull, delegate.getSendAsPeers(), (recyclerView, senderView, peer) -> { if (senderSelectPopupWindow == null) return; @@ -4385,7 +4385,7 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific updateScheduleButton(false); checkRoundVideo(); updateFieldHint(false); - updateSendAsButton(false); + updateSendAsButton(parentFragment != null && parentFragment.getFragmentBeginToShow()); } public void setChatInfo(TLRPC.ChatFull chatInfo) { @@ -4460,7 +4460,7 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific if (anonymously) { messageEditText.setHintText(LocaleController.getString("SendAnonymously", R.string.SendAnonymously)); } else { - if (parentFragment != null && parentFragment.isThreadChat()) { + if (parentFragment != null && parentFragment.isThreadChat() && !parentFragment.isTopic) { if (parentFragment.isReplyChatComment()) { messageEditText.setHintText(LocaleController.getString("Comment", R.string.Comment)); } else { @@ -4816,7 +4816,7 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific return; } if (currentLimit - codePointCount < 0) { - AndroidUtilities.shakeView(captionLimitView, 2, 0); + AndroidUtilities.shakeView(captionLimitView); Vibrator v = (Vibrator) captionLimitView.getContext().getSystemService(Context.VIBRATOR_SERVICE); if (v != null) { v.vibrate(200); @@ -6718,7 +6718,7 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific if (AndroidUtilities.isTablet()) { if (parentActivity instanceof LaunchActivity) { LaunchActivity launchActivity = (LaunchActivity) parentActivity; - View layout = launchActivity.getLayersActionBarLayout(); + View layout = launchActivity.getLayersActionBarLayout().getView(); allowFocus = layout == null || layout.getVisibility() != View.VISIBLE; } else { allowFocus = true; @@ -7098,10 +7098,10 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific showPopup = false; } } + botKeyboardView.setButtons(botReplyMarkup); if (showPopup && messageEditText.length() == 0 && !isPopupShowing()) { showPopup(1, 1); } - botKeyboardView.setButtons(botReplyMarkup); } else { if (isPopupShowing() && currentPopupContentType == POPUP_CONTENT_BOT_KEYBOARD) { if (openKeyboard) { @@ -7205,7 +7205,7 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific fragment1.finishFragment(); return; } - long did = dids.get(0); + long did = dids.get(0).dialogId; MediaDataController.getInstance(currentAccount).saveDraft(did, 0, "@" + user.username + " " + button.query, null, null, true); if (did != dialog_id) { if (!DialogObject.isEncryptedDialog(did)) { @@ -7462,7 +7462,7 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity, resourcesProvider); builder.setTitle(LocaleController.getString("ClearRecentEmojiTitle", R.string.ClearRecentEmojiTitle)); builder.setMessage(LocaleController.getString("ClearRecentEmojiText", R.string.ClearRecentEmojiText)); - builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearForAll).toUpperCase(), (dialogInterface, i) -> emojiView.clearRecentEmoji()); + builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearForAll), (dialogInterface, i) -> emojiView.clearRecentEmoji()); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); parentFragment.showDialog(builder.create()); } @@ -8217,7 +8217,7 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific checkBotMenu(); if (keyboardVisible && isPopupShowing() && stickersExpansionAnim == null) { showPopup(0, currentPopupContentType); - } else if (!keyboardVisible && !isPopupShowing() && botButtonsMessageObject != null && replyingMessageObject != botButtonsMessageObject && (!hasBotWebView() || !botCommandsMenuIsShowing()) && TextUtils.isEmpty(messageEditText.getText())) { + } else if (!keyboardVisible && !isPopupShowing() && botButtonsMessageObject != null && replyingMessageObject != botButtonsMessageObject && (!hasBotWebView() || !botCommandsMenuIsShowing()) && TextUtils.isEmpty(messageEditText.getText()) && botReplyMarkup != null && !botReplyMarkup.rows.isEmpty()) { if (sizeNotifierLayout.adjustPanLayoutHelper.animationInProgress()) { sizeNotifierLayout.adjustPanLayoutHelper.stopTransition(); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java index 4ea969133..1f6d9e9fc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java @@ -2304,7 +2304,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N writeButtonContainer.addView(writeButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, Gravity.LEFT | Gravity.TOP, Build.VERSION.SDK_INT >= 21 ? 2 : 0, 0, 0, 0)); writeButton.setOnClickListener(v -> { if (currentLimit - codepointCount < 0) { - AndroidUtilities.shakeView(captionLimitView, 2, 0); + AndroidUtilities.shakeView(captionLimitView); Vibrator vibrator = (Vibrator) captionLimitView.getContext().getSystemService(Context.VIBRATOR_SERVICE); if (vibrator != null) { vibrator.vibrate(200); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertDocumentLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertDocumentLayout.java index c1e633ac1..4d1ff0685 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertDocumentLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertDocumentLayout.java @@ -2044,7 +2044,10 @@ public class ChatAttachAlertDocumentLayout extends ChatAttachAlert.AttachAlertLa if (section < sections.size()) { ArrayList arrayList = sectionArrays.get(sections.get(section)); if (arrayList != null) { - return arrayList.get(position - (section == 0 && searchResult.isEmpty() ? 0 : 1)); + int p = position - (section == 0 && searchResult.isEmpty() ? 0 : 1); + if (p >= 0 && p < arrayList.size()) { + return arrayList.get(p); + } } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAvatarContainer.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAvatarContainer.java index b253f6b62..b5ff92e87 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAvatarContainer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAvatarContainer.java @@ -46,10 +46,12 @@ import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarPopupWindow; +import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.SimpleTextView; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ChatActivity; import org.telegram.ui.ProfileActivity; +import org.telegram.ui.TopicsFragment; public class ChatAvatarContainer extends FrameLayout implements NotificationCenter.NotificationCenterDelegate { @@ -93,14 +95,16 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent private AnimatedEmojiDrawable.SwapAnimatedEmojiDrawable emojiStatusDrawable; - public ChatAvatarContainer(Context context, ChatActivity chatActivity, boolean needTime) { - this(context, chatActivity, needTime, null); + public ChatAvatarContainer(Context context, BaseFragment baseFragment, boolean needTime) { + this(context, baseFragment, needTime, null); } - public ChatAvatarContainer(Context context, ChatActivity chatActivity, boolean needTime, Theme.ResourcesProvider resourcesProvider) { + public ChatAvatarContainer(Context context, BaseFragment baseFragment, boolean needTime, Theme.ResourcesProvider resourcesProvider) { super(context); this.resourcesProvider = resourcesProvider; - parentFragment = chatActivity; + if (baseFragment instanceof ChatActivity) { + parentFragment = (ChatActivity) baseFragment; + } final boolean avatarClickable = parentFragment != null && parentFragment.getChatMode() == 0 && !UserObject.isReplyUser(parentFragment.getCurrentUser()); avatarImageView = new BackupImageView(context) { @@ -117,9 +121,9 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent } } }; - if (parentFragment != null) { - sharedMediaPreloader = new SharedMediaLayout.SharedMediaPreloader(chatActivity); - if (parentFragment.isThreadChat() || parentFragment.getChatMode() == 2) { + if (baseFragment instanceof ChatActivity || baseFragment instanceof TopicsFragment) { + sharedMediaPreloader = new SharedMediaLayout.SharedMediaPreloader(baseFragment); + if (parentFragment != null && (parentFragment.isThreadChat() || parentFragment.getChatMode() == 2)) { avatarImageView.setVisibility(GONE); } } @@ -175,6 +179,11 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent } super.setTranslationY(translationY); } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + } }; subtitleTextView.setEllipsizeByGradient(true); subtitleTextView.setTextColor(getThemedColor(Theme.key_actionBarDefaultSubtitle)); @@ -211,7 +220,7 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent } if (parentFragment != null && parentFragment.getChatMode() == 0) { - if (!parentFragment.isThreadChat() && !UserObject.isReplyUser(parentFragment.getCurrentUser())) { + if ((!parentFragment.isThreadChat() || parentFragment.isTopic) && !UserObject.isReplyUser(parentFragment.getCurrentUser())) { setOnClickListener(v -> openProfile(false)); } @@ -351,6 +360,9 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent } else if (chat != null) { Bundle args = new Bundle(); args.putLong("chat_id", chat.id); + if (parentFragment.isTopic) { + args.putInt("topic_id", parentFragment.getThreadMessage().getId()); + } ProfileActivity fragment = new ProfileActivity(args, sharedMediaPreloader); fragment.setChatInfo(parentFragment.getCurrentChatInfo()); fragment.setPlayProfileAnimation(byAvatar ? 2 : 1); @@ -672,7 +684,7 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent CharSequence newSubtitle; boolean useOnlineColor = false; if (printString == null || printString.length() == 0 || ChatObject.isChannel(chat) && !chat.megagroup) { - if (parentFragment.isThreadChat()) { + if (parentFragment.isThreadChat() && !parentFragment.isTopic) { if (titleTextView.getTag() != null) { return; } @@ -710,7 +722,9 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent return; } setTypingAnimation(false); - if (chat != null) { + if (parentFragment.isTopic && chat != null) { + newSubtitle = LocaleController.formatString("TopicProfileStatus", R.string.TopicProfileStatus, chat.title); + } else if (chat != null) { TLRPC.ChatFull info = parentFragment.getCurrentChatInfo(); if (ChatObject.isChannel(chat)) { if (info != null && info.participants_count != 0) { @@ -736,14 +750,14 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent } else { if (chat.has_geo) { newSubtitle = LocaleController.getString("MegaLocation", R.string.MegaLocation).toLowerCase(); - } else if (!TextUtils.isEmpty(chat.username)) { + } else if (ChatObject.isPublic(chat)) { newSubtitle = LocaleController.getString("MegaPublic", R.string.MegaPublic).toLowerCase(); } else { newSubtitle = LocaleController.getString("MegaPrivate", R.string.MegaPrivate).toLowerCase(); } } } else { - if ((chat.flags & TLRPC.CHAT_FLAG_IS_PUBLIC) != 0) { + if (ChatObject.isPublic(chat)) { newSubtitle = LocaleController.getString("ChannelPublic", R.string.ChannelPublic).toLowerCase(); } else { newSubtitle = LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate).toLowerCase(); @@ -849,7 +863,9 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent avatarDrawable.setInfo(chat); if (avatarImageView != null) { avatarImageView.setForUserOrChat(chat, avatarDrawable); + avatarImageView.setRoundRadius(chat != null && chat.forum ? AndroidUtilities.dp(16) : AndroidUtilities.dp(21)); } + } public void setUserAvatar(TLRPC.User user) { @@ -909,6 +925,7 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent if (avatarImageView != null) { avatarImageView.setForUserOrChat(chat, avatarDrawable); } + avatarImageView.setRoundRadius(chat.forum ? AndroidUtilities.dp(16) : AndroidUtilities.dp(21)); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatNotificationsPopupWrapper.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatNotificationsPopupWrapper.java index 8ad10cde3..1db79549f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatNotificationsPopupWrapper.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatNotificationsPopupWrapper.java @@ -2,7 +2,12 @@ package org.telegram.ui.Components; import android.content.Context; import android.content.SharedPreferences; +import android.graphics.Canvas; +import android.graphics.Path; +import android.util.TypedValue; import android.view.View; +import android.widget.FrameLayout; +import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; @@ -14,6 +19,8 @@ import org.telegram.ui.ActionBar.ActionBarPopupWindow; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; +import java.util.HashSet; + public class ChatNotificationsPopupWrapper { View backItem; @@ -32,12 +39,32 @@ public class ChatNotificationsPopupWrapper { private final boolean isProfile; private int muteForLastSelected2Time; private int muteForLastSelected1Time; + private final View gap; + private final TextView topicsExceptionsTextView; + + public final static int TYPE_PREVIEW_MENU = 1; + + public int type; public ChatNotificationsPopupWrapper(Context context, int currentAccount, PopupSwipeBackLayout swipeBackLayout, boolean createBackground, boolean isProfile, Callback callback, Theme.ResourcesProvider resourcesProvider) { this.currentAccount = currentAccount; this.callback = callback; this.isProfile = isProfile; - windowLayout = new ActionBarPopupWindow.ActionBarPopupWindowLayout(context, createBackground ? R.drawable.popup_fixed_alert : 0, resourcesProvider); + windowLayout = new ActionBarPopupWindow.ActionBarPopupWindowLayout(context, createBackground ? R.drawable.popup_fixed_alert : 0, resourcesProvider) { + Path path = new Path(); + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + canvas.save(); + path.rewind(); + AndroidUtilities.rectTmp.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom()); + path.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(6), AndroidUtilities.dp(6), Path.Direction.CW); + canvas.clipPath(path); + boolean draw = super.drawChild(canvas, child, drawingTime); + canvas.restore(); + return draw; + } + }; windowLayout.setFitItems(true); if (swipeBackLayout != null) { @@ -47,7 +74,6 @@ public class ChatNotificationsPopupWrapper { }); } - soundToggle = ActionBarMenuItem.addItem(windowLayout, R.drawable.msg_tone_on, LocaleController.getString("SoundOn", R.string.SoundOn), false, resourcesProvider); soundToggle.setOnClickListener(view -> { dismiss(); @@ -102,6 +128,27 @@ public class ChatNotificationsPopupWrapper { }); }); + + gap = new FrameLayout(context); + gap.setBackgroundColor(Theme.getColor(Theme.key_actionBarDefaultSubmenuSeparator, resourcesProvider)); + windowLayout.addView(gap, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 8)); + + topicsExceptionsTextView = new TextView(context); + topicsExceptionsTextView.setPadding(AndroidUtilities.dp(13), AndroidUtilities.dp(8), AndroidUtilities.dp(13), AndroidUtilities.dp(8)); + topicsExceptionsTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + topicsExceptionsTextView.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSubmenuItem, resourcesProvider)); + + gap.setTag(R.id.fit_width_tag, 1); + topicsExceptionsTextView.setTag(R.id.fit_width_tag, 1); + windowLayout.addView(topicsExceptionsTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); + + topicsExceptionsTextView.setBackground(Theme.createRadSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector, resourcesProvider), 0,6)); + topicsExceptionsTextView.setOnClickListener(v -> { + if (callback != null) { + callback.openExceptions(); + } + dismiss(); + }); } private void dismiss() { @@ -113,14 +160,15 @@ public class ChatNotificationsPopupWrapper { lastDismissTime = System.currentTimeMillis(); } - public void update(long dialogId) { + public void update(long dialogId, int topicId, HashSet topicExceptions) { if (System.currentTimeMillis() - lastDismissTime < 200) { + //do on popup close AndroidUtilities.runOnUIThread(() -> { - update(dialogId); + update(dialogId, topicId, topicExceptions); }); return; } - boolean muted = MessagesController.getInstance(currentAccount).isDialogMuted(dialogId); + boolean muted = MessagesController.getInstance(currentAccount).isDialogMuted(dialogId, topicId); int color; if (muted) { @@ -131,7 +179,7 @@ public class ChatNotificationsPopupWrapper { muteUnmuteButton.setTextAndIcon(LocaleController.getString("MuteNotifications", R.string.MuteNotifications), R.drawable.msg_mute); color = Theme.getColor(Theme.key_dialogTextRed); soundToggle.setVisibility(View.VISIBLE); - boolean soundOn = MessagesController.getInstance(currentAccount).isDialogNotificationsSoundEnabled(dialogId); + boolean soundOn = MessagesController.getInstance(currentAccount).isDialogNotificationsSoundEnabled(dialogId, topicId); if (soundOn) { soundToggle.setTextAndIcon(LocaleController.getString("SoundOff", R.string.SoundOff), R.drawable.msg_tone_off); } else { @@ -139,9 +187,13 @@ public class ChatNotificationsPopupWrapper { } } + if (type == TYPE_PREVIEW_MENU) { + backItem.setVisibility(View.GONE); + } + int time1; int time2; - if (muted) { + if (muted || type == TYPE_PREVIEW_MENU) { time1 = 0; time2 = 0; } else { @@ -167,9 +219,21 @@ public class ChatNotificationsPopupWrapper { muteForLastSelected2.setVisibility(View.GONE); } - muteUnmuteButton.setColors(color, color); + if (topicExceptions == null || topicExceptions.isEmpty()) { + gap.setVisibility(View.GONE); + topicsExceptionsTextView.setVisibility(View.GONE); + } else { + gap.setVisibility(View.VISIBLE); + topicsExceptionsTextView.setVisibility(View.VISIBLE); + topicsExceptionsTextView.setText(AndroidUtilities.replaceSingleTag( + LocaleController.formatPluralString("TopicNotificationsExceptions", topicExceptions.size()), + Theme.key_windowBackgroundWhiteBlueText, + AndroidUtilities.REPLACING_TAG_TYPE_BOLD, + null + )); + } } private String formatMuteForTime(int time) { @@ -237,6 +301,10 @@ public class ChatNotificationsPopupWrapper { void showCustomize(); void toggleMute(); + + default void openExceptions() { + + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ClearHistoryAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ClearHistoryAlert.java index 14e2ebbfb..c9d30b27d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ClearHistoryAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ClearHistoryAlert.java @@ -262,7 +262,7 @@ public class ClearHistoryAlert extends BottomSheet { if (user != null) { messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("AreYouSureClearHistoryWithUser", R.string.AreYouSureClearHistoryWithUser, UserObject.getUserName(user)))); } else { - if (!ChatObject.isChannel(chat) || chat.megagroup && TextUtils.isEmpty(chat.username)) { + if (!ChatObject.isChannel(chat) || chat.megagroup && !ChatObject.isPublic(chat)) { messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("AreYouSureClearHistoryWithChat", R.string.AreYouSureClearHistoryWithChat, chat.title))); } else if (chat.megagroup) { messageTextView.setText(LocaleController.getString("AreYouSureClearHistoryGroup", R.string.AreYouSureClearHistoryGroup)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ColoredImageSpan.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ColoredImageSpan.java index bca7e4832..3156ea1a9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ColoredImageSpan.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ColoredImageSpan.java @@ -69,7 +69,7 @@ public class ColoredImageSpan extends ReplacementSpan { public void setColorKey(String colorKey) { this.colorKey = colorKey; - usePaintColor = false; + usePaintColor = colorKey == null; } public void setTopOffset(int topOffset) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/CombinedDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/CombinedDrawable.java index 5e5eafabb..1882e348c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/CombinedDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/CombinedDrawable.java @@ -182,4 +182,8 @@ public class CombinedDrawable extends Drawable implements Drawable.Callback { public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { unscheduleSelf(what); } + + public Drawable getBackgroundDrawable() { + return background; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextEmoji.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextEmoji.java index dbf4a7a5a..ab084ed9d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextEmoji.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextEmoji.java @@ -607,7 +607,7 @@ public class EditTextEmoji extends FrameLayout implements NotificationCenter.Not AlertDialog.Builder builder = new AlertDialog.Builder(getContext(), resourcesProvider); builder.setTitle(LocaleController.getString("ClearRecentEmojiTitle", R.string.ClearRecentEmojiTitle)); builder.setMessage(LocaleController.getString("ClearRecentEmojiText", R.string.ClearRecentEmojiText)); - builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton).toUpperCase(), (dialogInterface, i) -> emojiView.clearRecentEmoji()); + builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton), (dialogInterface, i) -> emojiView.clearRecentEmoji()); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); if (parentFragment != null) { parentFragment.showDialog(builder.create()); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmbedBottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmbedBottomSheet.java index c908e48e1..b91c02f1d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmbedBottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmbedBottomSheet.java @@ -231,7 +231,7 @@ public class EmbedBottomSheet extends BottomSheet { String youtubeId = message != null && message.messageOwner.media != null && message.messageOwner.media.webpage != null ? WebPlayerView.getYouTubeVideoId(url) : null; if (youtubeId != null) { PhotoViewer.getInstance().setParentActivity(fragment); - PhotoViewer.getInstance().openPhoto(message, seekTime, null, 0, 0, photoViewerProvider); + PhotoViewer.getInstance().openPhoto(message, seekTime, null, 0, 0, 0, photoViewerProvider); } else { EmbedBottomSheet sheet = new EmbedBottomSheet(fragment.getParentActivity(), title, description, originalUrl, url, w, h, seekTime); sheet.setCalcMandatoryInsets(keyboardVisible); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiPacksAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiPacksAlert.java index b4a9d4935..3ec209b4c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiPacksAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiPacksAlert.java @@ -13,7 +13,6 @@ import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.SystemClock; import android.text.Editable; @@ -47,7 +46,6 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.Emoji; import org.telegram.messenger.FileLog; import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.LocaleController; @@ -74,7 +72,6 @@ import org.telegram.ui.Components.Premium.PremiumFeatureBottomSheet; import org.telegram.ui.LaunchActivity; import org.telegram.ui.PremiumPreviewFragment; import org.telegram.ui.ProfileActivity; -import org.telegram.ui.SelectAnimatedEmojiDialog; import java.util.ArrayList; import java.util.regex.Matcher; @@ -1106,7 +1103,7 @@ public class EmojiPacksAlert extends BottomSheet implements NotificationCenter.N } ShareAlert alert = new ShareAlert(context, null, stickersUrl, false, stickersUrl, false, resourcesProvider) { @Override - protected void onSend(androidx.collection.LongSparseArray dids, int count) { + protected void onSend(androidx.collection.LongSparseArray dids, int count, TLRPC.TL_forumTopic topic) { AndroidUtilities.runOnUIThread(() -> { UndoView undoView; if (fragment instanceof ChatActivity) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiTabsStrip.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiTabsStrip.java index fb664e927..4a6570aef 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiTabsStrip.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiTabsStrip.java @@ -10,11 +10,10 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; -import android.graphics.RectF; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Build; -import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -35,6 +34,7 @@ import org.telegram.messenger.UserConfig; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.Premium.PremiumLockIconView; +import org.telegram.ui.SelectAnimatedEmojiDialog; import java.util.ArrayList; import java.util.HashMap; @@ -90,7 +90,8 @@ public class EmojiTabsStrip extends ScrollableHorizontalScrollView { public boolean updateButtonDrawables = true; - public EmojiTabsStrip(Context context, Theme.ResourcesProvider resourcesProvider, boolean includeStandard, boolean includeAnimated, Runnable onSettingsOpen) { + + public EmojiTabsStrip(Context context, Theme.ResourcesProvider resourcesProvider, boolean includeStandard, boolean includeAnimated, int type, Runnable onSettingsOpen) { super(context); this.includeAnimated = includeAnimated; this.resourcesProvider = resourcesProvider; @@ -249,6 +250,9 @@ public class EmojiTabsStrip extends ScrollableHorizontalScrollView { setHorizontalScrollBarEnabled(false); addView(contentView); + if (type == SelectAnimatedEmojiDialog.TYPE_TOPIC_ICON) { + recentDrawableId = R.drawable.msg_emoji_smiles; + } contentView.addView(recentTab = new EmojiTabButton(context, recentDrawableId, false, false)); recentTab.id = "recent".hashCode(); if (!includeAnimated) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java index a8482e9d6..c7e957196 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java @@ -1595,7 +1595,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } }); - emojiTabs = new EmojiTabsStrip(context, resourcesProvider, true, needAnimatedEmoji, fragment != null ? () -> { + emojiTabs = new EmojiTabsStrip(context, resourcesProvider, true, needAnimatedEmoji, 0, fragment != null ? () -> { if (delegate != null) { delegate.onEmojiSettingsClick(emojiAdapter.frozenEmojiPacks); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FilterTabsView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FilterTabsView.java index ea2c782a9..c91950566 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/FilterTabsView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FilterTabsView.java @@ -38,6 +38,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; import androidx.recyclerview.widget.DefaultItemAnimator; @@ -239,13 +240,18 @@ public class FilterTabsView extends FrameLayout { @SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { - boolean reorderEnabled = (!currentTab.isDefault || UserConfig.getInstance(UserConfig.selectedAccount).isPremium()); + boolean reorderEnabled = true; boolean showRemove = !currentTab.isDefault && reorderEnabled; if (reorderEnabled && editingAnimationProgress != 0) { canvas.save(); float p = editingAnimationProgress * (currentPosition % 2 == 0 ? 1.0f : -1.0f); - canvas.translate(AndroidUtilities.dp(0.66f) * p, 0); - canvas.rotate(p, getMeasuredWidth() / 2f, getMeasuredHeight() / 2f); + float s = (float) Math.sin((p + (currentPosition % 2)) * Math.PI * 2.5f); + float a = (float) (SystemClock.elapsedRealtime() / 400f * Math.PI * (currentPosition % 2 == 0 ? 1.0f : -1.0f)); + canvas.translate( + (float) (Math.cos(a) * AndroidUtilities.dp(0.33f) * (currentPosition % 2 == 0 ? 1.0f : -1.0f)), + (float) (Math.sin(a) * -AndroidUtilities.dp(0.33f)) + ); + canvas.rotate(1.4f * s, getMeasuredWidth() / 2f, getMeasuredHeight() / 2f); } String key; String animateToKey; @@ -1270,7 +1276,7 @@ public class FilterTabsView extends FrameLayout { if (isEditing || editingAnimationProgress != 0.0f) { if (editingForwardAnimation) { boolean lessZero = editingAnimationProgress <= 0; - editingAnimationProgress += dt / 120.0f; + editingAnimationProgress += dt / 420.0f; if (!isEditing && lessZero && editingAnimationProgress >= 0) { editingAnimationProgress = 0; } @@ -1280,7 +1286,7 @@ public class FilterTabsView extends FrameLayout { } } else { boolean greaterZero = editingAnimationProgress >= 0; - editingAnimationProgress -= dt / 120.0f; + editingAnimationProgress -= dt / 420.0f; if (!isEditing && greaterZero && editingAnimationProgress <= 0) { editingAnimationProgress = 0; } @@ -1622,6 +1628,46 @@ public class FilterTabsView extends FrameLayout { listView.setItemAnimator(itemAnimator); notifyItemMoved(fromIndex, toIndex); } + + public void moveElementToStart(int theIndex) { + int count = tabs.size(); + if (theIndex < 0 || theIndex >= count) { + return; + } + ArrayList filters = MessagesController.getInstance(UserConfig.selectedAccount).dialogFilters; + int temp = positionToStableId.get(theIndex), + temp2 = tabs.get(theIndex).id; + for (int i = theIndex - 1; i >= 0; --i) { +// notifyItemMoved(i, i + 1); + positionToStableId.put(i + 1, positionToStableId.get(i)); + } + MessagesController.DialogFilter filter = filters.remove(theIndex); + filter.order = 0; + filters.add(0, filter); + positionToStableId.put(0, temp); + tabs.add(0, tabs.remove(theIndex)); + tabs.get(0).id = temp2; + for (int i = 0; i <= theIndex; ++i) { + tabs.get(i).id = i; + filters.get(i).order = i; + } + for (int i = 0; i <= theIndex; ++i) { + if (currentPosition == i) { + currentPosition = selectedTabId = i == theIndex ? 0 : i + 1; + } + if (previousPosition == i) { + previousPosition = previousId = i == theIndex ? 0 : i + 1; + } + } + notifyItemMoved(theIndex, 0); + + delegate.onPageReorder(tabs.get(theIndex).id, temp2); + + updateTabsWidths(); + + orderChanged = true; + listView.setItemAnimator(itemAnimator); + } } public class TouchHelperCallback extends ItemTouchHelper.Callback { @@ -1633,7 +1679,7 @@ public class FilterTabsView extends FrameLayout { @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { - if (!isEditing || (viewHolder.getAdapterPosition() == 0 && tabs.get(0).isDefault && !UserConfig.getInstance(UserConfig.selectedAccount).isPremium())) { + if (MessagesController.getInstance(UserConfig.selectedAccount).premiumLocked && (!isEditing || (viewHolder.getAdapterPosition() == 0 && tabs.get(0).isDefault && !UserConfig.getInstance(UserConfig.selectedAccount).isPremium()))) { return makeMovementFlags(0, 0); } return makeMovementFlags(ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT, 0); @@ -1641,19 +1687,36 @@ public class FilterTabsView extends FrameLayout { @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) { - if ((source.getAdapterPosition() == 0 || target.getAdapterPosition() == 0) && !UserConfig.getInstance(UserConfig.selectedAccount).isPremium()) { + if (MessagesController.getInstance(UserConfig.selectedAccount).premiumLocked && ((source.getAdapterPosition() == 0 || target.getAdapterPosition() == 0) && !UserConfig.getInstance(UserConfig.selectedAccount).isPremium())) { return false; } adapter.swapElements(source.getAdapterPosition(), target.getAdapterPosition()); return true; } + private void resetDefaultPosition() { + if (UserConfig.getInstance(UserConfig.selectedAccount).isPremium()) { + return; + } + for (int i = 0; i < tabs.size(); ++i) { + if (tabs.get(i).isDefault && i != 0) { + adapter.moveElementToStart(i); + listView.scrollToPosition(0); + onDefaultTabMoved(); + break; + } + } + } + @Override public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { listView.cancelClickRunnables(false); viewHolder.itemView.setPressed(true); viewHolder.itemView.setBackgroundColor(Theme.getColor(backgroundColorKey)); + } else { + AndroidUtilities.cancelRunOnUIThread(this::resetDefaultPosition); + AndroidUtilities.runOnUIThread(this::resetDefaultPosition, 320); } super.onSelectedChanged(viewHolder, actionState); } @@ -1724,4 +1787,8 @@ public class FilterTabsView extends FrameLayout { } } + protected void onDefaultTabMoved() { + + } + } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FiltersListBottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FiltersListBottomSheet.java index 72c9acaea..62846ead3 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/FiltersListBottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FiltersListBottomSheet.java @@ -153,11 +153,21 @@ public class FiltersListBottomSheet extends BottomSheet implements NotificationC } if (statusBarHeight > 0) { - int color1 = Theme.getColor(Theme.key_dialogBackground); - int finalColor = Color.argb(0xff, (int) (Color.red(color1) * 0.8f), (int) (Color.green(color1) * 0.8f), (int) (Color.blue(color1) * 0.8f)); - Theme.dialogs_onlineCirclePaint.setColor(finalColor); + Theme.dialogs_onlineCirclePaint.setColor(Theme.getColor(Theme.key_dialogBackground)); canvas.drawRect(backgroundPaddingLeft, AndroidUtilities.statusBarHeight - statusBarHeight, getMeasuredWidth() - backgroundPaddingLeft, AndroidUtilities.statusBarHeight, Theme.dialogs_onlineCirclePaint); } + updateLightStatusBar(statusBarHeight > AndroidUtilities.statusBarHeight / 2); + } + + private Boolean statusBarOpen; + private void updateLightStatusBar(boolean open) { + if (statusBarOpen != null && statusBarOpen == open) { + return; + } + boolean openBgLight = AndroidUtilities.computePerceivedBrightness(getThemedColor(Theme.key_dialogBackground)) > .721f; + boolean closedBgLight = AndroidUtilities.computePerceivedBrightness(Theme.blendOver(getThemedColor(Theme.key_actionBarDefault), 0x33000000)) > .721f; + boolean isLight = (statusBarOpen = open) ? openBgLight : closedBgLight; + AndroidUtilities.setLightStatusBar(getWindow(), isLight); } }; containerView.setWillNotDraw(false); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FireworksOverlay.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FireworksOverlay.java index 00f148c88..917795a89 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/FireworksOverlay.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FireworksOverlay.java @@ -149,7 +149,7 @@ public class FireworksOverlay extends View { rotation -= 360; } } - return y >= getMeasuredHeight(); + return y >= getHeightForAnimation(); } } @@ -170,6 +170,20 @@ public class FireworksOverlay extends View { } } + private int getHeightForAnimation() { + if (getMeasuredHeight() == 0) { + return ((View)getParent()).getHeight(); + } + return getMeasuredHeight(); + } + + private int getWidthForAnimation() { + if (getMeasuredWidth() == 0) { + return ((View)getParent()).getWidth(); + } + return getMeasuredWidth(); + } + private Particle createParticle(boolean fall) { Particle particle = new Particle(); particle.type = (byte) Utilities.random.nextInt(2); @@ -187,16 +201,16 @@ public class FireworksOverlay extends View { particle.typeSize = (byte) (4 + Utilities.random.nextFloat() * 4); } if (fall) { - particle.y = -Utilities.random.nextFloat() * getMeasuredHeight() * 1.2f; - particle.x = AndroidUtilities.dp(5) + Utilities.random.nextInt(getMeasuredWidth() - AndroidUtilities.dp(10)); + particle.y = -Utilities.random.nextFloat() * getHeightForAnimation() * 1.2f; + particle.x = AndroidUtilities.dp(5) + Utilities.random.nextInt(getWidthForAnimation() - AndroidUtilities.dp(10)); particle.xFinished = particle.finishedStart; } else { int xOffset = AndroidUtilities.dp(4 + Utilities.random.nextInt(10)); - int yOffset = getMeasuredHeight() / 4; + int yOffset = getHeightForAnimation() / 4; if (particle.side == 0) { particle.x = -xOffset; } else { - particle.x = getMeasuredWidth() + xOffset; + particle.x = getWidthForAnimation() + xOffset; } particle.moveX = (particle.side == 0 ? 1 : -1) * (AndroidUtilities.dp(1.2f) + Utilities.random.nextFloat() * AndroidUtilities.dp(4)); particle.moveY = -(AndroidUtilities.dp(4) + Utilities.random.nextFloat() * AndroidUtilities.dp(4)); @@ -242,6 +256,10 @@ public class FireworksOverlay extends View { } } + protected void onStop() { + + } + @Override protected void onDraw(Canvas canvas) { long newTime = SystemClock.elapsedRealtime(); @@ -277,6 +295,7 @@ public class FireworksOverlay extends View { } }); } + onStop(); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FlatCheckBox.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FlatCheckBox.java index 3e0ef0e45..7e96afaf5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/FlatCheckBox.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FlatCheckBox.java @@ -177,6 +177,6 @@ public class FlatCheckBox extends View { } public void denied() { - AndroidUtilities.shakeView(this, 2, 0); + AndroidUtilities.shakeView(this); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FlickerLoadingView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FlickerLoadingView.java index dd3d57604..e7619961b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/FlickerLoadingView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FlickerLoadingView.java @@ -41,6 +41,7 @@ public class FlickerLoadingView extends View { public final static int LIMIT_REACHED_GROUPS = 21; public final static int LIMIT_REACHED_LINKS = 22; public final static int REACTED_TYPE_WITH_EMOJI_HINT = 23; + public static final int TOPIC_CELL_TYPE = 24; private int gradientWidth; private LinearGradient gradient; @@ -186,6 +187,41 @@ public class FlickerLoadingView extends View { canvas.drawRoundRect(rectF, AndroidUtilities.dp(4), AndroidUtilities.dp(4), paint); } + h += getCellHeight(getMeasuredWidth()); + k++; + if (isSingleCell && k >= itemsCount) { + break; + } + } + } else if (getViewType() == TOPIC_CELL_TYPE) { + int k = 0; + while (h <= getMeasuredHeight()) { + int r = AndroidUtilities.dp(14); + canvas.drawCircle(checkRtl(AndroidUtilities.dp(10) + r), h + AndroidUtilities.dp(10) + r, r, paint); + + canvas.save(); + canvas.translate(0, -AndroidUtilities.dp(4)); + rectF.set(AndroidUtilities.dp(50), h + AndroidUtilities.dp(16), AndroidUtilities.dp(148), h + AndroidUtilities.dp(24)); + checkRtl(rectF); + canvas.drawRoundRect(rectF, AndroidUtilities.dp(4), AndroidUtilities.dp(4), paint); + + rectF.set(AndroidUtilities.dp(50), h + AndroidUtilities.dp(38), AndroidUtilities.dp(268), h + AndroidUtilities.dp(46)); + checkRtl(rectF); + canvas.drawRoundRect(rectF, AndroidUtilities.dp(4), AndroidUtilities.dp(4), paint); + + if (SharedConfig.useThreeLinesLayout) { + rectF.set(AndroidUtilities.dp(50), h + AndroidUtilities.dp(46 + 8), AndroidUtilities.dp(220), h + AndroidUtilities.dp(46 + 8 + 8)); + checkRtl(rectF); + canvas.drawRoundRect(rectF, AndroidUtilities.dp(4), AndroidUtilities.dp(4), paint); + } + + if (showDate) { + rectF.set(getMeasuredWidth() - AndroidUtilities.dp(50), h + AndroidUtilities.dp(16), getMeasuredWidth() - AndroidUtilities.dp(12), h + AndroidUtilities.dp(24)); + checkRtl(rectF); + canvas.drawRoundRect(rectF, AndroidUtilities.dp(4), AndroidUtilities.dp(4), paint); + } + canvas.restore(); + h += getCellHeight(getMeasuredWidth()); k++; if (isSingleCell && k >= itemsCount) { @@ -716,6 +752,8 @@ public class FlickerLoadingView extends View { switch (getViewType()) { case DIALOG_CELL_TYPE: return AndroidUtilities.dp((SharedConfig.useThreeLinesLayout ? 78 : 72) + 1); + case TOPIC_CELL_TYPE: + return AndroidUtilities.dp((SharedConfig.useThreeLinesLayout ? 76 : 64) + 1); case DIALOG_TYPE: return AndroidUtilities.dp(78) + 1; case PHOTOS_TYPE: diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FloatingDebug/FloatingDebugController.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FloatingDebug/FloatingDebugController.java new file mode 100644 index 000000000..0977ea6c4 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FloatingDebug/FloatingDebugController.java @@ -0,0 +1,69 @@ +package org.telegram.ui.Components.FloatingDebug; + +import android.annotation.SuppressLint; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import org.telegram.messenger.SharedConfig; +import org.telegram.ui.LaunchActivity; + +public class FloatingDebugController { + private static FloatingDebugView debugView; + + public static boolean isActive() { + return SharedConfig.isFloatingDebugActive; + } + + public static boolean onBackPressed() { + return debugView != null && debugView.onBackPressed(); + } + + public static void onDestroy() { + if (debugView != null) { + debugView.saveConfig(); + } + debugView = null; + } + + public static void setActive(LaunchActivity activity, boolean active) { + setActive(activity, active, true); + } + + @SuppressLint("WrongConstant") + public static void setActive(LaunchActivity activity, boolean active, boolean saveConfig) { + if (active == (debugView != null)) { + return; + } + + if (active) { + debugView = new FloatingDebugView(activity); + activity.getMainContainerFrameLayout().addView(debugView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + debugView.showFab(); + } else { + debugView.dismiss(() -> { + activity.getMainContainerFrameLayout().removeView(debugView); + debugView = null; + }); + } + if (saveConfig) { + SharedConfig.isFloatingDebugActive = active; + SharedConfig.saveConfig(); + } + } + + public static class DebugItem { + final CharSequence title; + final DebugItemType type; + final Runnable action; + + public DebugItem(CharSequence title, Runnable action) { + this.type = DebugItemType.SIMPLE; + this.title = title; + this.action = action; + } + } + + public enum DebugItemType { + SIMPLE + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FloatingDebug/FloatingDebugProvider.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FloatingDebug/FloatingDebugProvider.java new file mode 100644 index 000000000..0b58a86ad --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FloatingDebug/FloatingDebugProvider.java @@ -0,0 +1,7 @@ +package org.telegram.ui.Components.FloatingDebug; + +import java.util.List; + +public interface FloatingDebugProvider { + List onGetDebugItems(); +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FloatingDebug/FloatingDebugView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FloatingDebug/FloatingDebugView.java new file mode 100644 index 000000000..a64401f68 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FloatingDebug/FloatingDebugView.java @@ -0,0 +1,514 @@ +package org.telegram.ui.Components.FloatingDebug; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.HapticFeedbackConstants; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.core.graphics.ColorUtils; +import androidx.core.math.MathUtils; +import androidx.core.view.GestureDetectorCompat; +import androidx.dynamicanimation.animation.FloatValueHolder; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.SharedConfig; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.INavigationLayout; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.LaunchActivity; + +import java.util.ArrayList; +import java.util.List; + +public class FloatingDebugView extends FrameLayout implements NotificationCenter.NotificationCenterDelegate { + private FrameLayout floatingButtonContainer; + private Drawable floatingButtonBackground; + + private SpringAnimation fabXSpring, fabYSpring; + private SharedPreferences mPrefs; + + private boolean isScrolling; + private boolean isScrollDisallowed; + private boolean isFromFling; + + private boolean inLongPress; + private Runnable onLongPress = () -> { + inLongPress = true; + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + }; + + private boolean isBigMenuShown; + private int wasStatusBar; + private LinearLayout bigLayout; + private TextView titleView; + private RecyclerListView listView; + + private List debugItems = new ArrayList<>(); + + private int touchSlop; + private GestureDetector.OnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() { + private float startX, startY; + + @Override + public boolean onDown(MotionEvent e) { + return true; + } + + @Override + public boolean onSingleTapUp(MotionEvent e) { + if (inLongPress) { + return false; + } + if (!isBigMenuShown) { + showBigMenu(true); + return true; + } + return false; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + if (isScrolling && !inLongPress) { + fabXSpring.getSpring().setFinalPosition(fabXSpring.getSpring().getFinalPosition() + velocityX / 7f >= getWidth() / 2f ? clampX(getResources().getDisplayMetrics(), Integer.MAX_VALUE) : clampX(getResources().getDisplayMetrics(), Integer.MIN_VALUE)); + fabYSpring.getSpring().setFinalPosition(clampY(getResources().getDisplayMetrics(), fabYSpring.getSpring().getFinalPosition() + velocityY / 10f)); + fabXSpring.start(); + fabYSpring.start(); + return isFromFling = true; + } + return false; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + if (!inLongPress) { + AndroidUtilities.cancelRunOnUIThread(onLongPress); + } + if (!isScrolling && !isScrollDisallowed) { + if (Math.abs(distanceX) >= touchSlop || Math.abs(distanceY) >= touchSlop) { + startX = fabXSpring.getSpring().getFinalPosition(); + startY = fabYSpring.getSpring().getFinalPosition(); + isScrolling = true; + } else { + isScrollDisallowed = false; + } + } + if (isScrolling) { + if (inLongPress) { + // TODO: Show additional actions + } else { + fabXSpring.getSpring().setFinalPosition(startX + e2.getRawX() - e1.getRawX()); + fabYSpring.getSpring().setFinalPosition(startY + e2.getRawY() - e1.getRawY()); + fabXSpring.start(); + fabYSpring.start(); + } + } + + return isScrolling; + } + }; + + public FloatingDebugView(@NonNull Context context) { + super(context); + + mPrefs = context.getSharedPreferences("floating_debug", 0); + + touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + GestureDetectorCompat gestureDetector = new GestureDetectorCompat(context, gestureListener); + gestureDetector.setIsLongpressEnabled(false); + floatingButtonContainer = new FrameLayout(context) { + @Override + public void invalidate() { + super.invalidate(); + FloatingDebugView.this.invalidate(); + } + + @Override + public void setTranslationX(float translationX) { + super.setTranslationX(translationX); + FloatingDebugView.this.invalidate(); + } + + @Override + public void setTranslationY(float translationY) { + super.setTranslationY(translationY); + FloatingDebugView.this.invalidate(); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean detector = gestureDetector.onTouchEvent(event); + if (event.getAction() == MotionEvent.ACTION_DOWN) { + AndroidUtilities.runOnUIThread(onLongPress, 200); + } else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) { + AndroidUtilities.cancelRunOnUIThread(onLongPress); + + if (!isFromFling) { + fabXSpring.getSpring().setFinalPosition(fabXSpring.getSpring().getFinalPosition() >= getWidth() / 2f ? clampX(getResources().getDisplayMetrics(), Integer.MAX_VALUE) : clampX(getResources().getDisplayMetrics(), Integer.MIN_VALUE)); + fabYSpring.getSpring().setFinalPosition(clampY(getResources().getDisplayMetrics(), fabYSpring.getSpring().getFinalPosition())); + fabXSpring.start(); + fabYSpring.start(); + } + inLongPress = false; + isScrolling = false; + isScrollDisallowed = false; + isFromFling = false; + } + return detector; + } + }; + ImageView actionIcon = new ImageView(context); + actionIcon.setImageResource(R.drawable.device_phone_android); + actionIcon.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chats_actionIcon), PorterDuff.Mode.SRC_IN)); + floatingButtonContainer.addView(actionIcon); + floatingButtonContainer.setVisibility(GONE); + addView(floatingButtonContainer, LayoutHelper.createFrame(56, 56)); + + bigLayout = new LinearLayout(context); + bigLayout.setOrientation(LinearLayout.VERTICAL); + bigLayout.setVisibility(GONE); + + titleView = new TextView(context); + titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); + titleView.setText(LocaleController.getString(R.string.DebugMenu)); + titleView.setTypeface(AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM)); + titleView.setPadding(AndroidUtilities.dp(24), AndroidUtilities.dp(19), AndroidUtilities.dp(24), AndroidUtilities.dp(19)); + bigLayout.addView(titleView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + listView = new RecyclerListView(context); + listView.setLayoutManager(new LinearLayoutManager(context)); + listView.setAdapter(new RecyclerListView.SelectionAdapter() { + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + FloatingDebugController.DebugItemType type = FloatingDebugController.DebugItemType.values()[holder.getItemViewType()]; + + return type == FloatingDebugController.DebugItemType.SIMPLE; + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View v; + switch (FloatingDebugController.DebugItemType.values()[viewType]) { + default: + case SIMPLE: + v = new AlertDialog.AlertDialogCell(context, null); + v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + break; + } + return new RecyclerListView.Holder(v); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + FloatingDebugController.DebugItem item = debugItems.get(position); + switch (item.type) { + case SIMPLE: + AlertDialog.AlertDialogCell cell = (AlertDialog.AlertDialogCell) holder.itemView; + cell.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + cell.setTextAndIcon(item.title, 0); + break; + } + } + + @Override + public int getItemViewType(int position) { + return debugItems.get(position).type.ordinal(); + } + + @Override + public int getItemCount() { + return debugItems.size(); + } + }); + listView.setOnItemClickListener((view, position) -> { + FloatingDebugController.DebugItem item = debugItems.get(position); + if (item.action != null) { + item.action.run(); + showBigMenu(false); + } + }); + bigLayout.addView(listView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 0, 1f)); + + addView(bigLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.NO_GRAVITY, 8, 8, 8, 8)); + + updateDrawables(); + + setFitsSystemWindows(true); + setWillNotDraw(false); + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + if (child == bigLayout) { + canvas.drawColor(Color.argb((int)(0x7A * bigLayout.getAlpha()), 0, 0, 0)); + } + return super.drawChild(canvas, child, drawingTime); + } + + public boolean onBackPressed() { + if (isBigMenuShown) { + showBigMenu(false); + return true; + } + return false; + } + + @SuppressLint("ApplySharedPref") + public void saveConfig() { + mPrefs.edit() + .putFloat("x", fabXSpring.getSpring().getFinalPosition()) + .putFloat("y", fabYSpring.getSpring().getFinalPosition()) + .commit(); + } + + private void updateDrawables() { + Drawable drawable = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(56), Theme.getColor(Theme.key_chats_actionBackground), Theme.getColor(Theme.key_chats_actionPressedBackground)); + Drawable shadowDrawable = getResources().getDrawable(R.drawable.floating_shadow).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(0xff000000, PorterDuff.Mode.MULTIPLY)); + CombinedDrawable combinedDrawable = new CombinedDrawable(shadowDrawable, drawable, 0, 0); + combinedDrawable.setIconSize(AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + drawable = combinedDrawable; + floatingButtonBackground = drawable; + + drawable = getResources().getDrawable(R.drawable.popup_fixed_alert3); + drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogBackground), PorterDuff.Mode.MULTIPLY)); + bigLayout.setBackground(drawable); + + titleView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + + invalidate(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return isBigMenuShown; + } + + @SuppressLint("NotifyDataSetChanged") + @Override + public void didReceivedNotification(int id, int account, Object... args) { + if (id == NotificationCenter.didSetNewTheme) { + updateDrawables(); + listView.getAdapter().notifyDataSetChanged(); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + float savedX = mPrefs.getFloat("x", -1), savedY = mPrefs.getFloat("y", -1); + DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + floatingButtonContainer.setTranslationX(savedX == -1 || savedX >= displayMetrics.widthPixels / 2f ? clampX(displayMetrics, Integer.MAX_VALUE) : clampX(displayMetrics, Integer.MIN_VALUE)); + floatingButtonContainer.setTranslationY(savedY == -1 ? clampY(displayMetrics, Integer.MAX_VALUE) : clampY(displayMetrics, savedY)); + + fabXSpring = new SpringAnimation(floatingButtonContainer, SpringAnimation.TRANSLATION_X, floatingButtonContainer.getTranslationX()) + .setSpring(new SpringForce(floatingButtonContainer.getTranslationX()) + .setStiffness(650f) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); + fabYSpring = new SpringAnimation(floatingButtonContainer, SpringAnimation.TRANSLATION_Y, floatingButtonContainer.getTranslationY()) + .setSpring(new SpringForce(floatingButtonContainer.getTranslationY()) + .setStiffness(650f) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); + + NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.didSetNewTheme); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + fabXSpring.cancel(); + fabYSpring.cancel(); + + NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.didSetNewTheme); + } + + @SuppressLint("NotifyDataSetChanged") + public void showBigMenu(boolean show) { + if (isBigMenuShown == show) { + return; + } + isBigMenuShown = show; + + if (show) { + bigLayout.setVisibility(VISIBLE); + + debugItems.clear(); + if (getContext() instanceof LaunchActivity) { + INavigationLayout layout = ((LaunchActivity) getContext()).getActionBarLayout(); + if (layout instanceof FloatingDebugProvider) { + debugItems.addAll(((FloatingDebugProvider) layout).onGetDebugItems()); + } + layout = ((LaunchActivity) getContext()).getRightActionBarLayout(); + if (layout instanceof FloatingDebugProvider) { + debugItems.addAll(((FloatingDebugProvider) layout).onGetDebugItems()); + } + layout = ((LaunchActivity) getContext()).getLayersActionBarLayout(); + if (layout instanceof FloatingDebugProvider) { + debugItems.addAll(((FloatingDebugProvider) layout).onGetDebugItems()); + } + } + debugItems.addAll(getBuiltInDebugItems()); + listView.getAdapter().notifyDataSetChanged(); + } + + Window window = ((Activity) getContext()).getWindow(); + if (show && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + wasStatusBar = window.getStatusBarColor(); + } + float startX = floatingButtonContainer.getTranslationX(); + float startY = floatingButtonContainer.getTranslationY(); + new SpringAnimation(new FloatValueHolder(show ? 0f : 1000f)) + .setSpring(new SpringForce(1000f) + .setStiffness(900) + .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY) + .setFinalPosition(show ? 1000f : 0f)) + .addUpdateListener((animation, value, velocity) -> { + value /= 1000f; + + bigLayout.setAlpha(value); + bigLayout.setTranslationX(AndroidUtilities.lerp(startX - AndroidUtilities.dp(8), 0, value)); + bigLayout.setTranslationY(AndroidUtilities.lerp(startY - AndroidUtilities.dp(8), 0, value)); + + bigLayout.setPivotX(floatingButtonContainer.getTranslationX() + AndroidUtilities.dp(28)); + bigLayout.setPivotY(floatingButtonContainer.getTranslationY() + AndroidUtilities.dp(28)); + if (bigLayout.getWidth() != 0) { + bigLayout.setScaleX(AndroidUtilities.lerp(floatingButtonContainer.getWidth() / (float) bigLayout.getWidth(), 1f, value)); + } + if (bigLayout.getHeight() != 0) { + bigLayout.setScaleY(AndroidUtilities.lerp(floatingButtonContainer.getHeight() / (float) bigLayout.getHeight(), 1f, value)); + } + + floatingButtonContainer.setTranslationX(AndroidUtilities.lerp(startX, getWidth() / 2f - AndroidUtilities.dp(28), value)); + floatingButtonContainer.setTranslationY(AndroidUtilities.lerp(startY, getHeight() / 2f - AndroidUtilities.dp(28), value)); + floatingButtonContainer.setAlpha(1f - value); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + window.setStatusBarColor(ColorUtils.blendARGB(wasStatusBar, 0x7A000000, value)); + } + + invalidate(); + }) + .addEndListener((animation, canceled, value, velocity) -> { + floatingButtonContainer.setTranslationX(startX); + floatingButtonContainer.setTranslationY(startY); + + if (!show) { + bigLayout.setVisibility(GONE); + } + }) + .start(); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + fabXSpring.cancel(); + fabYSpring.cancel(); + DisplayMetrics metrics = getResources().getDisplayMetrics(); + floatingButtonContainer.setTranslationX(floatingButtonContainer.getTranslationX() >= metrics.widthPixels / 2f ? clampX(metrics, Integer.MAX_VALUE) : clampX(metrics, Integer.MIN_VALUE)); + floatingButtonContainer.setTranslationY(clampY(metrics, floatingButtonContainer.getTranslationY())); + fabXSpring.getSpring().setFinalPosition(floatingButtonContainer.getTranslationX()); + fabYSpring.getSpring().setFinalPosition(floatingButtonContainer.getTranslationY()); + } + + private List getBuiltInDebugItems() { + List items = new ArrayList<>(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + items.add(new FloatingDebugController.DebugItem(LocaleController.getString(SharedConfig.debugWebView ? R.string.DebugMenuDisableWebViewDebug : R.string.DebugMenuEnableWebViewDebug), ()->{ + SharedConfig.toggleDebugWebView(); + Toast.makeText(getContext(), LocaleController.getString(SharedConfig.debugWebView ? R.string.DebugMenuWebViewDebugEnabled : R.string.DebugMenuWebViewDebugDisabled), Toast.LENGTH_SHORT).show(); + })); + } + items.add(new FloatingDebugController.DebugItem(LocaleController.getString(SharedConfig.useLNavigation ? R.string.AltNavigationDisable : R.string.AltNavigationEnable), () -> { + SharedConfig.useLNavigation = !SharedConfig.useLNavigation; + SharedConfig.saveConfig(); + if (getContext() instanceof Activity) { + ((Activity) getContext()).recreate(); + } + })); + return items; + } + + private float clampX(DisplayMetrics displayMetrics, float x) { + return MathUtils.clamp(x, AndroidUtilities.dp(16), displayMetrics.widthPixels - AndroidUtilities.dp(56 + 16)); + } + + private float clampY(DisplayMetrics displayMetrics, float y) { + return MathUtils.clamp(y, AndroidUtilities.dp(16), displayMetrics.heightPixels - AndroidUtilities.dp(56 + 16)); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + canvas.save(); + canvas.translate(floatingButtonContainer.getTranslationX(), floatingButtonContainer.getTranslationY()); + canvas.scale(floatingButtonContainer.getScaleX(), floatingButtonContainer.getScaleY(), floatingButtonContainer.getPivotX(), floatingButtonContainer.getPivotY()); + floatingButtonBackground.setAlpha((int) (floatingButtonContainer.getAlpha() * 0xFF)); + floatingButtonBackground.setBounds(floatingButtonContainer.getLeft(), floatingButtonContainer.getTop(), + floatingButtonContainer.getRight(), floatingButtonContainer.getBottom()); + floatingButtonBackground.draw(canvas); + canvas.restore(); + } + + public void showFab() { + floatingButtonContainer.setVisibility(VISIBLE); + + new SpringAnimation(new FloatValueHolder(0)) + .setSpring(new SpringForce(1000f) + .setStiffness(750f) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)) + .addUpdateListener((animation, value, velocity) -> { + value /= 1000; + + floatingButtonContainer.setPivotX(AndroidUtilities.dp(28)); + floatingButtonContainer.setPivotY(AndroidUtilities.dp(28)); + floatingButtonContainer.setScaleX(value); + floatingButtonContainer.setScaleY(value); + floatingButtonContainer.setAlpha(MathUtils.clamp(value, 0, 1)); + invalidate(); + }) + .start(); + } + + public void dismiss(Runnable callback) { + callback.run(); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Forum/ForumBubbleDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Forum/ForumBubbleDrawable.java new file mode 100644 index 000000000..b2ac09801 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Forum/ForumBubbleDrawable.java @@ -0,0 +1,197 @@ +package org.telegram.ui.Components.Forum; + + +import android.animation.ValueAnimator; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.LinearGradient; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Shader; +import android.graphics.drawable.Drawable; +import android.util.SparseArray; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.graphics.ColorUtils; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.R; +import org.telegram.messenger.SvgHelper; +import org.telegram.ui.ActionBar.Theme; + +import java.util.ArrayList; + +public class ForumBubbleDrawable extends Drawable { + + + SvgHelper.SvgDrawable svgDrawable; + LinearGradient gradient; + Matrix gradientMatrix = new Matrix(); + ArrayList parents = new ArrayList<>(); + int colorIndex; + + private final Paint strokePaint; + private final Paint topPaint; + + private int currentColors[]; + + public static final int[] serverSupportedColor = new int[]{ + 0x6FB9F0, + 0xFFD67E, + 0xCB86DB, + 0x8EEE98, + 0xFF93B2, + 0xFB6F5F, + }; + + static final SparseArray colorsMap = new SparseArray<>(); + + static { + colorsMap.put(0x6FB9F0, new int[]{0xff015EC1, 0xff4BB7FF}); //blue + colorsMap.put(0xFFD67E, new int[]{0xffEA5800, 0xffFFDB5C}); //yellow + colorsMap.put(0xCB86DB, new int[]{0xffA438BB, 0xffE57AFF}); //violet + colorsMap.put(0x8EEE98, new int[]{0xff11B411, 0xff97E334}); //green + colorsMap.put(0xFF93B2, new int[]{0xffE4215A, 0xffFF7999}); //rose + colorsMap.put(0xFB6F5F, new int[]{0xffC61505, 0xffFF714C}); //orange + } + + + public ForumBubbleDrawable(int color) { + svgDrawable = SvgHelper.getDrawable(R.raw.topic_bubble, Color.WHITE); + svgDrawable.copyCommandFromPosition(0); + topPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + strokePaint.setStrokeWidth(AndroidUtilities.dp(1)); + strokePaint.setStyle(Paint.Style.STROKE); + svgDrawable.setPaint(topPaint, 1); + svgDrawable.setPaint(strokePaint, 2); + + setColor(color); + } + + @Override + public void draw(@NonNull Canvas canvas) { + gradientMatrix.reset(); + gradientMatrix.setScale(1f, getBounds().height() / 100f); + gradient.setLocalMatrix(gradientMatrix); + svgDrawable.setBounds(getBounds()); + svgDrawable.draw(canvas); + } + + @Override + public void setAlpha(int alpha) { + + } + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) { + + } + + @Override + public int getIntrinsicHeight() { + return AndroidUtilities.dp(24); + } + + @Override + public int getIntrinsicWidth() { + return AndroidUtilities.dp(24); + } + + @Override + public int getOpacity() { + return 0; + } + + public int colorDistance(int a, int b) { + return Math.abs(Color.red(a) - Color.red(b)) + Math.abs(Color.green(a) - Color.green(b)) + Math.abs(Color.blue(a) - Color.blue(b)); + } + + public int moveNexColor() { + colorIndex++; + if (colorIndex > serverSupportedColor.length - 1) { + colorIndex = 0; + } + int[] fromColors = currentColors; + color = serverSupportedColor[colorIndex]; + currentColors = colorsMap.get(serverSupportedColor[colorIndex]); + if (Theme.isCurrentThemeDark()) { + currentColors = new int[]{ + ColorUtils.blendARGB(currentColors[0], Color.WHITE, 0.2f), ColorUtils.blendARGB(currentColors[1], Color.WHITE, 0.2f) + }; + } + + + invalidateSelf(); + ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1f); + valueAnimator.addUpdateListener(animation -> { + float v = (float) animation.getAnimatedValue(); + + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + + gradient = new LinearGradient(0, 100, 0, 0, new int[]{ColorUtils.blendARGB(fromColors[0], currentColors[0], v), ColorUtils.blendARGB(fromColors[1], currentColors[1], v)}, null, Shader.TileMode.CLAMP); + gradient.setLocalMatrix(gradientMatrix); + paint.setShader(gradient); + svgDrawable.setPaint(paint, 0); + + topPaint.setColor(ColorUtils.blendARGB(ColorUtils.blendARGB(fromColors[1], currentColors[1], v), Color.WHITE, 0.1f)); + strokePaint.setColor(ColorUtils.blendARGB(ColorUtils.blendARGB(fromColors[0], currentColors[0], v), Color.BLACK, 0.1f)); + invalidateSelf(); + }); + valueAnimator.setDuration(200); + valueAnimator.start(); + + return serverSupportedColor[colorIndex]; + } + + public void addParent(View view) { + parents.add(view); + } + + @Override + public void invalidateSelf() { + super.invalidateSelf(); + for (int i = 0; i < parents.size(); i++) { + parents.get(i).invalidate(); + } + } + + int color = -1; + + public void setColor(int color) { + if (this.color == color && this.color == -1) { + return; + } + this.color = color; + int colorDistance = colorDistance(serverSupportedColor[0], color); + colorIndex = 0; + for (int i = 0; i < serverSupportedColor.length; i++) { + int distanceLocal = colorDistance(serverSupportedColor[i], color); + if (distanceLocal < colorDistance) { + colorDistance = distanceLocal; + colorIndex = i; + } + } + int[] colors = colorsMap.get(serverSupportedColor[colorIndex]); + if (Theme.isCurrentThemeDark()) { + colors = new int[]{ + ColorUtils.blendARGB(colors[0], Color.WHITE, 0.2f), ColorUtils.blendARGB(colors[1], Color.WHITE, 0.2f) + }; + } + currentColors = colors; + + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + gradient = new LinearGradient(0, 100, 0, 0, colors, null, Shader.TileMode.CLAMP); + gradient.setLocalMatrix(gradientMatrix); + paint.setShader(gradient); + svgDrawable.setPaint(paint, 0); + + + topPaint.setColor(ColorUtils.blendARGB(colors[1], Color.WHITE, 0.1f)); + strokePaint.setColor(ColorUtils.blendARGB(colors[0], Color.BLACK, 0.1f)); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Forum/ForumUtilities.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Forum/ForumUtilities.java new file mode 100644 index 000000000..13d6bb0ef --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Forum/ForumUtilities.java @@ -0,0 +1,279 @@ +package org.telegram.ui.Components.Forum; + +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.style.ImageSpan; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.DialogObject; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessageObject; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.MessagesStorage; +import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.INavigationLayout; +import org.telegram.ui.ChatActivity; +import org.telegram.ui.Components.AnimatedEmojiDrawable; +import org.telegram.ui.Components.AnimatedEmojiSpan; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.ColoredImageSpan; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.LetterDrawable; +import org.telegram.ui.TopicsFragment; + +import java.util.ArrayList; + +public class ForumUtilities { + + + public static void setTopicIcon(BackupImageView backupImageView, TLRPC.TL_forumTopic forumTopic) { + setTopicIcon(backupImageView, forumTopic, false); + } + public static void setTopicIcon(BackupImageView backupImageView, TLRPC.TL_forumTopic forumTopic, boolean largeIcon) { + if (forumTopic == null || backupImageView == null) { + return; + } + if (forumTopic.icon_emoji_id != 0) { + backupImageView.setImageDrawable(null); + backupImageView.setAnimatedEmojiDrawable(new AnimatedEmojiDrawable(AnimatedEmojiDrawable.CACHE_TYPE_FORUM_TOPIC, UserConfig.selectedAccount, forumTopic.icon_emoji_id)); + } else { + backupImageView.setAnimatedEmojiDrawable(null); + backupImageView.setImageDrawable(createTopicDrawable(forumTopic)); + } + } + + + public static Drawable createTopicDrawable(TLRPC.TL_forumTopic topic) { + return topic == null ? null : createTopicDrawable(topic.title, topic.icon_color); + } + + public static Drawable createTopicDrawable(String text) { + return createTopicDrawable(text, 0); + } + + public static Drawable createTopicDrawable(String text, int color) { + ForumBubbleDrawable forumBubbleDrawable = new ForumBubbleDrawable(color); + LetterDrawable letterDrawable = new LetterDrawable(null, LetterDrawable.STYLE_TOPIC_DRAWABLE); + String title = text.trim().toUpperCase(); + letterDrawable.setTitle(title.length() >= 1 ? title.substring(0, 1) : ""); + CombinedDrawable combinedDrawable = new CombinedDrawable(forumBubbleDrawable, letterDrawable, 0, 0); + combinedDrawable.setFullsize(true); + return combinedDrawable; + } + + public static void openTopic(BaseFragment baseFragment, long chatId, TLRPC.TL_forumTopic topic, int fromMessageId) { + if (baseFragment == null) { + return; + } + TLRPC.Chat chatLocal = baseFragment.getMessagesController().getChat(chatId); + Bundle args = new Bundle(); + args.putLong("chat_id", chatId); + if (fromMessageId != 0) { + args.putInt("message_id", fromMessageId); + } + args.putInt("unread_count", topic.unread_count); + args.putBoolean("historyPreloaded", false); + ChatActivity chatActivity = new ChatActivity(args); + TLRPC.Message message = topic.topicStartMessage; + if (message == null) { + TLRPC.TL_forumTopic topicLocal = baseFragment.getMessagesController().getTopicsController().findTopic(chatId, topic.id); + if (topicLocal != null) { + topic = topicLocal; + message = topic.topicStartMessage; + } + } + if (message == null) { + return; + } + ArrayList messageObjects = new ArrayList<>(); + messageObjects.add(new MessageObject(baseFragment.getCurrentAccount(), message, false, false)); + chatActivity.setThreadMessages(messageObjects, chatLocal, topic.id, topic.read_inbox_max_id, topic.read_outbox_max_id, topic); + if (fromMessageId != 0) { + chatActivity.highlightMessageId = fromMessageId; + } + baseFragment.presentFragment(chatActivity); + } + + public static CharSequence getTopicSpannedName(TLRPC.ForumTopic topic, TextPaint paint) { + return getTopicSpannedName(topic, paint, null); + } + + public static CharSequence getTopicSpannedName(TLRPC.ForumTopic topic, TextPaint paint, ForumBubbleDrawable[] drawableToSet) { + SpannableStringBuilder sb = new SpannableStringBuilder(); + if (topic instanceof TLRPC.TL_forumTopic) { + TLRPC.TL_forumTopic forumTopic = (TLRPC.TL_forumTopic) topic; + if (forumTopic.icon_emoji_id != 0) { + sb.append(" "); + AnimatedEmojiSpan span; + sb.setSpan(span = new AnimatedEmojiSpan(forumTopic.icon_emoji_id, .95f, paint == null ? null : paint.getFontMetricsInt()), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + span.cacheType = AnimatedEmojiDrawable.CACHE_TYPE_EMOJI_STATUS; + } else { + sb.append(" "); + Drawable drawable = ForumUtilities.createTopicDrawable(forumTopic); + if (drawableToSet != null) { + drawableToSet[0] = (ForumBubbleDrawable) ((CombinedDrawable) drawable).getBackgroundDrawable(); + } + drawable.setBounds(0, 0, (int) (drawable.getIntrinsicWidth() * 0.65f), (int) (drawable.getIntrinsicHeight() * 0.65f)); + if (drawable instanceof CombinedDrawable && ((CombinedDrawable) drawable).getIcon() instanceof LetterDrawable) { + ((LetterDrawable) ((CombinedDrawable) drawable).getIcon()).scale = .7f; + } + if (paint != null) { + ColoredImageSpan imageSpan = new ColoredImageSpan(drawable); + imageSpan.setSize((int) (Math.abs(paint.getFontMetrics().descent) + Math.abs(paint.getFontMetrics().ascent))); + sb.setSpan(imageSpan, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + sb.setSpan(new ImageSpan(drawable), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + if (!TextUtils.isEmpty(forumTopic.title)) { + sb.append(" "); + sb.append(forumTopic.title); + } + } else { + return "DELETED"; + } + return sb; + } + + public static void applyTopic(ChatActivity chatActivity, MessagesStorage.TopicKey topicKey) { + TLRPC.TL_forumTopic topic = chatActivity.getMessagesController().getTopicsController().findTopic(-topicKey.dialogId, topicKey.topicId); + if (topic == null) { + return; + } + TLRPC.Chat chatLocal = chatActivity.getMessagesController().getChat(-topicKey.dialogId); + ArrayList messageObjects = new ArrayList<>(); + messageObjects.add(new MessageObject(chatActivity.getCurrentAccount(), topic.topicStartMessage, false, false)); + chatActivity.setThreadMessages(messageObjects, chatLocal, topic.id, topic.read_inbox_max_id, topic.read_outbox_max_id, topic); + } + + public static CharSequence createActionTextWithTopic(TLRPC.TL_forumTopic topic, MessageObject messageObject) { + if (topic == null) { + return null; + } + if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionTopicCreate) { + return AndroidUtilities.replaceCharSequence("%s", LocaleController.getString(R.string.TopicWasCreatedAction), ForumUtilities.getTopicSpannedName(topic, null)); + } + if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionTopicEdit) { + TLRPC.TL_messageActionTopicEdit topicEdit = (TLRPC.TL_messageActionTopicEdit) messageObject.messageOwner.action; + long fromId = messageObject.getFromChatId(); + TLRPC.User fromUser = null; + TLRPC.Chat fromChat = null; + if (DialogObject.isUserDialog(fromId)) { + fromUser = MessagesController.getInstance(messageObject.currentAccount).getUser(fromId); + } else { + fromChat = MessagesController.getInstance(messageObject.currentAccount).getChat(-fromId); + } + String name = null; + if (fromUser != null) { + name = ContactsController.formatName(fromUser.first_name, fromUser.last_name); + } else if (fromChat != null) { + name = fromChat.title; + } + + if ((topicEdit.flags & 4) != 0) { + CharSequence charSequence = AndroidUtilities.replaceCharSequence("%2$s", topicEdit.closed ? LocaleController.getString(R.string.TopicWasClosedAction) : LocaleController.getString(R.string.TopicWasReopenedAction), ForumUtilities.getTopicSpannedName(topic, null)); + return AndroidUtilities.replaceCharSequence("%1$s", charSequence, name); + } + if ((topicEdit.flags & 1) != 0 && (topicEdit.flags & 2) != 0) { + TLRPC.TL_forumTopic forumTopic = new TLRPC.TL_forumTopic(); + forumTopic.icon_emoji_id = topicEdit.icon_emoji_id; + forumTopic.title = topicEdit.title; + CharSequence charSequence = AndroidUtilities.replaceCharSequence("%2$s", LocaleController.getString(R.string.TopicWasRenamedToAction2), ForumUtilities.getTopicSpannedName(forumTopic, null)); + return AndroidUtilities.replaceCharSequence("%1$s", charSequence, name); + } + if ((topicEdit.flags & 1) != 0) { + CharSequence charSequence = AndroidUtilities.replaceCharSequence("%2$s", LocaleController.getString(R.string.TopicWasRenamedToAction), topicEdit.title); + return AndroidUtilities.replaceCharSequence("%1$s", charSequence, name); + } + if ((topicEdit.flags & 2) != 0) { + TLRPC.TL_forumTopic forumTopic = new TLRPC.TL_forumTopic(); + forumTopic.icon_emoji_id = topicEdit.icon_emoji_id; + forumTopic.title = ""; + CharSequence charSequence = AndroidUtilities.replaceCharSequence("%2$s", LocaleController.getString(R.string.TopicWasIconChangedToAction), ForumUtilities.getTopicSpannedName(forumTopic, null)); + return AndroidUtilities.replaceCharSequence("%1$s", charSequence, name); + } + + } + return null; + } + + public static boolean isTopicCreateMessage(MessageObject message) { + return message != null && message.messageOwner.action instanceof TLRPC.TL_messageActionTopicCreate; + } + + public static void applyTopicToMessage(MessageObject messageObject) { + if (messageObject.getDialogId() > 0) { + return; + } + TLRPC.TL_forumTopic topic = MessagesController.getInstance(messageObject.currentAccount).getTopicsController().findTopic(-messageObject.getDialogId(), MessageObject.getTopicId(messageObject.messageOwner)); + if (topic != null && messageObject.topicIconDrawable[0] != null) { + messageObject.topicIconDrawable[0].setColor(topic.icon_color); + } + } + + public static void switchAllFragmentsInStackToForum(long chatId, INavigationLayout actionBarLayout) { + +// List fragmentStack = actionBarLayout.getFragmentStack(); +// for (int i = 0; i < fragmentStack.size() - 1; i++) { +// if (fragmentStack.get(i) instanceof ChatActivity) { +// ChatActivity chatActivity = (ChatActivity) fragmentStack.get(i); +// if (-chatActivity.getDialogId() == chatId) { +// Bundle bundle = new Bundle(); +// bundle.putLong("chat_id", chatId); +// actionBarLayout.removeFragmentFromStack(i); +// actionBarLayout.addFragmentToStack(new TopicsFragment(bundle), i); +// } +// } else if (fragmentStack.get(i) instanceof TopicsFragment) { +// TopicsFragment chatActivity = (TopicsFragment) fragmentStack.get(i); +// if (-chatActivity.getDialogId() == chatId) { +// Bundle bundle = new Bundle(); +// bundle.putLong("dialog_id", -chatId); +// actionBarLayout.removeFragmentFromStack(i); +// actionBarLayout.addFragmentToStack(new ChatActivity(bundle), i); +// } +// } +// } + + BaseFragment lastFragment = actionBarLayout.getLastFragment(); + if (lastFragment instanceof ChatActivity) { + ChatActivity chatActivity = (ChatActivity) lastFragment; + if (-chatActivity.getDialogId() == chatId) { + if (chatActivity.getMessagesController().getChat(chatId).forum) { + if (chatActivity.getParentLayout() != null) { + if (chatActivity.getParentLayout().checkTransitionAnimation()) { + AndroidUtilities.runOnUIThread(() -> { + if (chatActivity.getParentLayout() != null) { + TopicsFragment.prepareToSwitchAnimation(chatActivity); + } + }, 500); + } else { + TopicsFragment.prepareToSwitchAnimation(chatActivity); + } + } + } + } + } + if (lastFragment instanceof TopicsFragment) { + TopicsFragment topicsFragment = (TopicsFragment) lastFragment; + if (-topicsFragment.getDialogId() == chatId && !topicsFragment.getMessagesController().getChat(chatId).forum) { + if (topicsFragment.getParentLayout() != null && topicsFragment.getParentLayout().checkTransitionAnimation()) { + AndroidUtilities.runOnUIThread(() -> { + if (topicsFragment.getParentLayout() != null) { + topicsFragment.switchToChat(true); + } + }, 500); + } else { + topicsFragment.switchToChat(true); + } + } + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextView.java index 481f43336..06ffb6325 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextView.java @@ -104,7 +104,8 @@ public class FragmentContextView extends FrameLayout implements NotificationCent STYLE_INACTIVE_GROUP_CALL, STYLE_IMPORTING_MESSAGES }) - public @interface Style {} + public @interface Style { + } private ImageView playButton; private PlayPauseDrawable playPauseDrawable; @@ -112,7 +113,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent private AudioPlayerAlert.ClippingTextViewSwitcher subtitleTextView; private AnimatorSet animatorSet; private BaseFragment fragment; - private ChatActivity chatActivity; + private ChatActivityInterface chatActivity; private View applyingView; private FrameLayout frameLayout; private View shadow; @@ -123,6 +124,8 @@ public class FragmentContextView extends FrameLayout implements NotificationCent private ImageView closeButton; private ActionBarMenuItem playbackSpeedButton; private ActionBarMenuSubItem[] speedItems = new ActionBarMenuSubItem[4]; + private FrameLayout silentButton; + private ImageView silentButtonImage; private FragmentContextView additionalContextView; private TextView joinButton; private CellFlickerDrawable joinButtonFlicker; @@ -132,7 +135,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent private int currentProgress = -1; private MessageObject lastMessageObject; - private float topPadding; + protected float topPadding; private boolean visible; @Style private int currentStyle = STYLE_NOT_SET; @@ -156,7 +159,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent scheduleRunnableScheduled = false; return; } - ChatObject.Call call = ((ChatActivity) fragment).getGroupCall(); + ChatObject.Call call = chatActivity.getGroupCall(); if (call == null || !call.isScheduled()) { timeLayout = null; scheduleRunnableScheduled = false; @@ -241,6 +244,9 @@ public class FragmentContextView extends FrameLayout implements NotificationCent this.resourcesProvider = resourcesProvider; fragment = parentFragment; + if (parentFragment instanceof ChatActivityInterface) { + chatActivity = (ChatActivityInterface) parentFragment; + } SizeNotifierFrameLayout sizeNotifierFrameLayout = null; if (fragment.getFragmentView() instanceof SizeNotifierFrameLayout) { sizeNotifierFrameLayout = (SizeNotifierFrameLayout) fragment.getFragmentView(); @@ -273,7 +279,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent gradientPaint.setShader(linearGradient); gradientWidth = width; } - ChatObject.Call call = ((ChatActivity) fragment).getGroupCall(); + ChatObject.Call call = chatActivity.getGroupCall(); float moveProgress = 0.0f; if (fragment != null && call != null && call.isScheduled()) { long diff = ((long) call.call.schedule_date) * 1000 - fragment.getConnectionsManager().getCurrentTimeMillis(); @@ -414,9 +420,26 @@ public class FragmentContextView extends FrameLayout implements NotificationCent addView(joinButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 28, Gravity.TOP | Gravity.RIGHT, 0, 10, 14, 0)); joinButton.setOnClickListener(v -> FragmentContextView.this.callOnClick()); + silentButton = new FrameLayout(context); + silentButtonImage = new ImageView(context); + silentButtonImage.setImageResource(R.drawable.msg_mute); + silentButtonImage.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_inappPlayerClose), PorterDuff.Mode.MULTIPLY)); + silentButton.addView(silentButtonImage, LayoutHelper.createFrame(20, 20, Gravity.CENTER)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + silentButton.setBackground(Theme.createSelectorDrawable(getThemedColor(Theme.key_inappPlayerClose) & 0x19ffffff, 1, AndroidUtilities.dp(14))); + } + silentButton.setContentDescription(LocaleController.getString("Unmute", R.string.Unmute)); + silentButton.setOnClickListener(e -> { + MediaController.getInstance().updateSilent(false); + }); + silentButton.setVisibility(View.GONE); + addView(silentButton, LayoutHelper.createFrame(36, 36, Gravity.RIGHT | Gravity.TOP, 0, 0, 36, 0)); + if (!location) { playbackSpeedButton = new ActionBarMenuItem(context, null, 0, getThemedColor(Theme.key_dialogTextBlack), resourcesProvider); playbackSpeedButton.setLongClickEnabled(false); + playbackSpeedButton.setVisibility(GONE); + playbackSpeedButton.setTag(null); playbackSpeedButton.setShowSubmenuByMove(false); playbackSpeedButton.setContentDescription(LocaleController.getString("AccDescrPlayerSpeed", R.string.AccDescrPlayerSpeed)); playbackSpeedButton.setDelegate(id -> { @@ -610,9 +633,8 @@ public class FragmentContextView extends FrameLayout implements NotificationCent if (fragment instanceof DialogsActivity) { builder.setMessage(LocaleController.getString("StopLiveLocationAlertAllText", R.string.StopLiveLocationAlertAllText)); } else { - ChatActivity activity = (ChatActivity) fragment; - TLRPC.Chat chat = activity.getCurrentChat(); - TLRPC.User user = activity.getCurrentUser(); + TLRPC.Chat chat = chatActivity.getCurrentChat(); + TLRPC.User user = chatActivity.getCurrentUser(); if (chat != null) { builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("StopLiveLocationAlertToGroupText", R.string.StopLiveLocationAlertToGroupText, chat.title))); } else if (user != null) { @@ -627,7 +649,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent LocationController.getInstance(a).removeAllLocationSharings(); } } else { - LocationController.getInstance(fragment.getCurrentAccount()).removeSharingLocation(((ChatActivity) fragment).getDialogId()); + LocationController.getInstance(fragment.getCurrentAccount()).removeSharingLocation(chatActivity.getDialogId()); } }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); @@ -652,11 +674,11 @@ public class FragmentContextView extends FrameLayout implements NotificationCent } } else { long dialogId = 0; - if (fragment instanceof ChatActivity) { - dialogId = ((ChatActivity) fragment).getDialogId(); + if (chatActivity != null) { + dialogId = chatActivity.getDialogId(); } if (messageObject.getDialogId() == dialogId) { - ((ChatActivity) fragment).scrollToMessageId(messageObject.getId(), 0, false, 0, true, 0); + chatActivity.scrollToMessageId(messageObject.getId(), 0, false, 0, true, 0); } else { dialogId = messageObject.getDialogId(); Bundle args = new Bundle(); @@ -678,8 +700,8 @@ public class FragmentContextView extends FrameLayout implements NotificationCent } else if (currentStyle == STYLE_LIVE_LOCATION) { long did = 0; int account = UserConfig.selectedAccount; - if (fragment instanceof ChatActivity) { - did = ((ChatActivity) fragment).getDialogId(); + if (chatActivity != null) { + did = chatActivity.getDialogId(); account = fragment.getCurrentAccount(); } else if (LocationController.getLocationsCount() == 1) { for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { @@ -705,12 +727,11 @@ public class FragmentContextView extends FrameLayout implements NotificationCent if (fragment.getParentActivity() == null) { return; } - ChatActivity chatActivity = (ChatActivity) fragment; ChatObject.Call call = chatActivity.getGroupCall(); if (call == null) { return; } - VoIPHelper.startCall(chatActivity.getMessagesController().getChat(call.chatId), null, null, false, call.call != null && !call.call.rtmp_stream, fragment.getParentActivity(), fragment, fragment.getAccountInstance()); + VoIPHelper.startCall(fragment.getMessagesController().getChat(call.chatId), null, null, false, call.call != null && !call.call.rtmp_stream, fragment.getParentActivity(), fragment, fragment.getAccountInstance()); } else if (currentStyle == STYLE_IMPORTING_MESSAGES) { SendMessagesHelper.ImportingHistory importingHistory = parentFragment.getSendMessagesHelper().getImportingHistory(((ChatActivity) parentFragment).getDialogId()); if (importingHistory == null) { @@ -804,15 +825,15 @@ public class FragmentContextView extends FrameLayout implements NotificationCent if (fragment instanceof DialogsActivity) { show = LocationController.getLocationsCount() != 0; } else { - show = LocationController.getInstance(fragment.getCurrentAccount()).isSharingLocation(((ChatActivity) fragment).getDialogId()); + show = LocationController.getInstance(fragment.getCurrentAccount()).isSharingLocation(chatActivity.getDialogId()); } } else { if (VoIPService.getSharedInstance() != null && !VoIPService.getSharedInstance().isHangingUp() && VoIPService.getSharedInstance().getCallState() != VoIPService.STATE_WAITING_INCOMING) { show = true; startJoinFlickerAnimation(); - } else if (fragment instanceof ChatActivity && fragment.getSendMessagesHelper().getImportingHistory(((ChatActivity) fragment).getDialogId()) != null && !isPlayingVoice()) { + } else if (chatActivity != null && fragment.getSendMessagesHelper().getImportingHistory(chatActivity.getDialogId()) != null && !isPlayingVoice()) { show = true; - } else if (fragment instanceof ChatActivity && ((ChatActivity) fragment).getGroupCall() != null && ((ChatActivity) fragment).getGroupCall().shouldShowPanel() && !GroupCallPip.isShowing() && !isPlayingVoice()) { + } else if (chatActivity != null && chatActivity.getGroupCall() != null && chatActivity.getGroupCall().shouldShowPanel() && !GroupCallPip.isShowing() && !isPlayingVoice()) { show = true; startJoinFlickerAnimation(); } else { @@ -844,6 +865,17 @@ public class FragmentContextView extends FrameLayout implements NotificationCent } + private void updateSilent() { + if (currentStyle == STYLE_AUDIO_PLAYER) { + boolean isSilent = MediaController.getInstance().isSilent; + AndroidUtilities.updateViewShow(silentButton, isSilent); + AndroidUtilities.updateViewShow(playbackSpeedButton, !isSilent); + } else { + AndroidUtilities.updateViewShow(silentButton, false, true, false); + AndroidUtilities.updateViewShow(playbackSpeedButton, false, true, false); + } + } + private void updateStyle(@Style int style) { if (currentStyle == style) { return; @@ -898,6 +930,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent closeButton.setContentDescription(LocaleController.getString("AccDescrClosePlayer", R.string.AccDescrClosePlayer)); if (playbackSpeedButton != null) { playbackSpeedButton.setVisibility(GONE); + playbackSpeedButton.setTag(null); } titleTextView.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.LEFT | Gravity.TOP, 35, 0, 36, 0)); } else if (style == STYLE_AUDIO_PLAYER || style == STYLE_LIVE_LOCATION) { @@ -929,6 +962,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent titleTextView.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.LEFT | Gravity.TOP, 35, 0, 36, 0)); if (playbackSpeedButton != null) { playbackSpeedButton.setVisibility(VISIBLE); + playbackSpeedButton.setTag(1); } closeButton.setContentDescription(LocaleController.getString("AccDescrClosePlayer", R.string.AccDescrClosePlayer)); } else { @@ -960,8 +994,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent importingImageView.stopAnimation(); boolean isRtmpStream = false; - if (fragment instanceof ChatActivity) { - ChatActivity chatActivity = (ChatActivity) fragment; + if (chatActivity != null) { isRtmpStream = chatActivity.getGroupCall() != null && chatActivity.getGroupCall().call != null && chatActivity.getGroupCall().call.rtmp_stream; } @@ -977,6 +1010,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent playButton.setVisibility(GONE); if (playbackSpeedButton != null) { playbackSpeedButton.setVisibility(GONE); + playbackSpeedButton.setTag(null); } } else if (style == STYLE_CONNECTING_GROUP_CALL || style == STYLE_ACTIVE_GROUP_CALL) { selector.setBackground(null); @@ -1028,6 +1062,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent titleTextView.setPadding(AndroidUtilities.dp(112), 0, AndroidUtilities.dp(112), 0); if (playbackSpeedButton != null) { playbackSpeedButton.setVisibility(GONE); + playbackSpeedButton.setTag(null); } } } @@ -1106,9 +1141,9 @@ public class FragmentContextView extends FrameLayout implements NotificationCent if (VoIPService.getSharedInstance() != null && !VoIPService.getSharedInstance().isHangingUp() && VoIPService.getSharedInstance().getCallState() != VoIPService.STATE_WAITING_INCOMING && !GroupCallPip.isShowing()) { checkCall(true); - } else if (fragment instanceof ChatActivity && fragment.getSendMessagesHelper().getImportingHistory(((ChatActivity) fragment).getDialogId()) != null && !isPlayingVoice()) { + } else if (chatActivity != null && fragment.getSendMessagesHelper().getImportingHistory(chatActivity.getDialogId()) != null && !isPlayingVoice()) { checkImport(true); - } else if (fragment instanceof ChatActivity && ((ChatActivity) fragment).getGroupCall() != null && ((ChatActivity) fragment).getGroupCall().shouldShowPanel() && !GroupCallPip.isShowing() && !isPlayingVoice()) { + } else if (chatActivity != null && chatActivity.getGroupCall() != null && chatActivity.getGroupCall().shouldShowPanel() && !GroupCallPip.isShowing() && !isPlayingVoice()) { checkCall(true); } else { checkCall(true); @@ -1141,8 +1176,8 @@ public class FragmentContextView extends FrameLayout implements NotificationCent setTopPadding(AndroidUtilities.dp2(getStyleHeight())); } - speakerAmplitude = 0; - micAmplitude = 0; + speakerAmplitude = 0; + micAmplitude = 0; } @Override @@ -1155,9 +1190,9 @@ public class FragmentContextView extends FrameLayout implements NotificationCent if (id == NotificationCenter.liveLocationsChanged) { checkLiveLocation(false); } else if (id == NotificationCenter.liveLocationsCacheChanged) { - if (fragment instanceof ChatActivity) { + if (chatActivity != null) { long did = (Long) args[0]; - if (((ChatActivity) fragment).getDialogId() == did) { + if (chatActivity.getDialogId() == did) { checkLocationString(); } } @@ -1190,14 +1225,14 @@ public class FragmentContextView extends FrameLayout implements NotificationCent } } else if (id == NotificationCenter.groupCallTypingsUpdated) { if (visible && currentStyle == STYLE_INACTIVE_GROUP_CALL) { - ChatObject.Call call = ((ChatActivity) fragment).getGroupCall(); + ChatObject.Call call = chatActivity.getGroupCall(); if (call != null) { if (call.isScheduled()) { subtitleTextView.setText(LocaleController.formatStartsTime(call.call.schedule_date, 4), false); } else if (call.call.participants_count == 0) { subtitleTextView.setText(LocaleController.getString(call.call.rtmp_stream ? R.string.ViewersWatchingNobody : R.string.MembersTalkingNobody), false); } else { - subtitleTextView.setText(LocaleController.formatPluralString(call.call.rtmp_stream ? "ViewersWatching" : "Participants", call.call.participants_count), false); + subtitleTextView.setText(LocaleController.formatPluralString(call.call.rtmp_stream ? "ViewersWatching" : "Participants", call.call.participants_count), false); } } updateAvatars(true); @@ -1253,7 +1288,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent if (fragment instanceof DialogsActivity) { show = LocationController.getLocationsCount() != 0; } else { - show = LocationController.getInstance(fragment.getCurrentAccount()).isSharingLocation(((ChatActivity) fragment).getDialogId()); + show = LocationController.getInstance(fragment.getCurrentAccount()).isSharingLocation(chatActivity.getDialogId()); } if (!show) { lastLocationSharingCount = -1; @@ -1364,12 +1399,11 @@ public class FragmentContextView extends FrameLayout implements NotificationCent } private void checkLocationString() { - if (!(fragment instanceof ChatActivity) || titleTextView == null) { + if (chatActivity == null || titleTextView == null) { return; } - ChatActivity chatActivity = (ChatActivity) fragment; long dialogId = chatActivity.getDialogId(); - int currentAccount = chatActivity.getCurrentAccount(); + int currentAccount = fragment.getCurrentAccount(); ArrayList messages = LocationController.getInstance(currentAccount).locationsCache.get(dialogId); if (!firstLocationsLoaded) { LocationController.getInstance(currentAccount).loadLiveLocations(dialogId); @@ -1459,8 +1493,8 @@ public class FragmentContextView extends FrameLayout implements NotificationCent if (messageObject == null || messageObject.getId() == 0 || messageObject.isVideo()) { lastMessageObject = null; boolean callAvailable = supportsCalls && VoIPService.getSharedInstance() != null && !VoIPService.getSharedInstance().isHangingUp() && VoIPService.getSharedInstance().getCallState() != VoIPService.STATE_WAITING_INCOMING && !GroupCallPip.isShowing(); - if (!isPlayingVoice() && !callAvailable && fragment instanceof ChatActivity && !GroupCallPip.isShowing()) { - ChatObject.Call call = ((ChatActivity) fragment).getGroupCall(); + if (!isPlayingVoice() && !callAvailable && chatActivity != null && !GroupCallPip.isShowing()) { + ChatObject.Call call = chatActivity.getGroupCall(); callAvailable = call != null && call.shouldShowPanel(); } if (callAvailable) { @@ -1638,11 +1672,10 @@ public class FragmentContextView extends FrameLayout implements NotificationCent } public void checkImport(boolean create) { - if (!(fragment instanceof ChatActivity) || visible && (currentStyle == STYLE_CONNECTING_GROUP_CALL || currentStyle == STYLE_ACTIVE_GROUP_CALL)) { + if (chatActivity == null || visible && (currentStyle == STYLE_CONNECTING_GROUP_CALL || currentStyle == STYLE_ACTIVE_GROUP_CALL)) { return; } - ChatActivity chatActivity = (ChatActivity) fragment; - SendMessagesHelper.ImportingHistory importingHistory = chatActivity.getSendMessagesHelper().getImportingHistory(chatActivity.getDialogId()); + SendMessagesHelper.ImportingHistory importingHistory = fragment.getSendMessagesHelper().getImportingHistory(chatActivity.getDialogId()); View fragmentView = fragment.getFragmentView(); if (!create && fragmentView != null) { if (fragmentView.getParent() == null || ((View) fragmentView.getParent()).getVisibility() != VISIBLE) { @@ -1650,7 +1683,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent } } - Dialog dialog = chatActivity.getVisibleDialog(); + Dialog dialog = fragment.getVisibleDialog(); if ((isPlayingVoice() || chatActivity.shouldShowImport() || dialog instanceof ImportingAlert && !((ImportingAlert) dialog).isDismissed()) && importingHistory != null) { importingHistory = null; } @@ -1793,8 +1826,8 @@ public class FragmentContextView extends FrameLayout implements NotificationCent callAvailable = false; } groupActive = false; - if (!isPlayingVoice() && !GroupCallActivity.groupCallUiVisible && supportsCalls && !callAvailable && fragment instanceof ChatActivity) { - ChatObject.Call call = ((ChatActivity) fragment).getGroupCall(); + if (!isPlayingVoice() && !GroupCallActivity.groupCallUiVisible && supportsCalls && !callAvailable && chatActivity != null) { + ChatObject.Call call = chatActivity.getGroupCall(); if (call != null && call.shouldShowPanel()) { callAvailable = true; groupActive = true; @@ -1848,7 +1881,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent setVisibility(GONE); } - if (create && fragment instanceof ChatActivity && ((ChatActivity) fragment).openedWithLivestream() && !GroupCallPip.isShowing()) { + if (create && chatActivity != null && chatActivity.openedWithLivestream() && !GroupCallPip.isShowing()) { BulletinFactory.of(fragment).createSimpleBulletin(R.raw.linkbroken, LocaleController.getString("InviteExpired", R.string.InviteExpired)).show(); } } else { @@ -1893,8 +1926,8 @@ public class FragmentContextView extends FrameLayout implements NotificationCent boolean updateAnimated = currentStyle == STYLE_INACTIVE_GROUP_CALL && visible; updateStyle(STYLE_INACTIVE_GROUP_CALL); - ChatObject.Call call = ((ChatActivity) fragment).getGroupCall(); - TLRPC.Chat chat = ((ChatActivity) fragment).getCurrentChat(); + ChatObject.Call call = chatActivity.getGroupCall(); + TLRPC.Chat chat = chatActivity.getCurrentChat(); if (call.isScheduled()) { if (gradientPaint == null) { gradientTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); @@ -1935,7 +1968,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent if (call.call.participants_count == 0) { subtitleTextView.setText(LocaleController.getString(call.call.rtmp_stream ? R.string.ViewersWatchingNobody : R.string.MembersTalkingNobody), false); } else { - subtitleTextView.setText(LocaleController.formatPluralString(call.call.rtmp_stream ? "ViewersWatching" : "Participants", call.call.participants_count), false); + subtitleTextView.setText(LocaleController.formatPluralString(call.call.rtmp_stream ? "ViewersWatching" : "Participants", call.call.participants_count), false); } frameLayout.invalidate(); } @@ -2021,10 +2054,9 @@ public class FragmentContextView extends FrameLayout implements NotificationCent if (avatars.avatarsDarawable.transitionProgressAnimator == null) { int currentAccount; if (currentStyle == STYLE_INACTIVE_GROUP_CALL) { - if (fragment instanceof ChatActivity) { - ChatActivity chatActivity = (ChatActivity) fragment; + if (chatActivity != null) { call = chatActivity.getGroupCall(); - currentAccount = chatActivity.getCurrentAccount(); + currentAccount = fragment.getCurrentAccount(); } else { call = null; currentAccount = account; @@ -2033,7 +2065,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent } else { if (VoIPService.getSharedInstance() != null) { call = VoIPService.getSharedInstance().groupCall; - userCall = fragment instanceof ChatActivity ? null : VoIPService.getSharedInstance().getUser(); + userCall = chatActivity != null ? null : VoIPService.getSharedInstance().getUser(); currentAccount = VoIPService.getSharedInstance().getAccount(); } else { call = null; @@ -2189,13 +2221,13 @@ public class FragmentContextView extends FrameLayout implements NotificationCent if (service != null && (currentStyle == STYLE_CONNECTING_GROUP_CALL || currentStyle == STYLE_ACTIVE_GROUP_CALL)) { int currentCallState = service.getCallState(); if (!service.isSwitchingStream() && (currentCallState == VoIPService.STATE_WAIT_INIT || currentCallState == VoIPService.STATE_WAIT_INIT_ACK || currentCallState == VoIPService.STATE_CREATING || currentCallState == VoIPService.STATE_RECONNECTING)) { - titleTextView.setText(LocaleController.getString("VoipGroupConnecting", R.string. VoipGroupConnecting), false); + titleTextView.setText(LocaleController.getString("VoipGroupConnecting", R.string.VoipGroupConnecting), false); } else if (service.getChat() != null) { if (!TextUtils.isEmpty(service.groupCall.call.title)) { titleTextView.setText(service.groupCall.call.title, false); } else { - if (fragment instanceof ChatActivity && ((ChatActivity) fragment).getCurrentChat() != null && ((ChatActivity) fragment).getCurrentChat().id == service.getChat().id) { - TLRPC.Chat chat = ((ChatActivity) fragment).getCurrentChat(); + if (chatActivity != null && chatActivity.getCurrentChat() != null && chatActivity.getCurrentChat().id == service.getChat().id) { + TLRPC.Chat chat = chatActivity.getCurrentChat(); if (VoIPService.hasRtmpStream()) { titleTextView.setText(LocaleController.getString(R.string.VoipChannelViewVoiceChat), false); } else { @@ -2211,7 +2243,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent } } else if (service.getUser() != null) { TLRPC.User user = service.getUser(); - if (fragment instanceof ChatActivity && ((ChatActivity) fragment).getCurrentUser() != null && ((ChatActivity) fragment).getCurrentUser().id == user.id) { + if (chatActivity != null && chatActivity.getCurrentUser() != null && chatActivity.getCurrentUser().id == user.id) { titleTextView.setText(LocaleController.getString("ReturnToCall", R.string.ReturnToCall)); } else { titleTextView.setText(ContactsController.formatName(user.first_name, user.last_name)); @@ -2233,4 +2265,41 @@ public class FragmentContextView extends FrameLayout implements NotificationCent Integer color = resourcesProvider != null ? resourcesProvider.getColor(key) : null; return color != null ? color : Theme.getColor(key); } + + public interface ChatActivityInterface { + + default ChatObject.Call getGroupCall() { + return null; + } + + default TLRPC.Chat getCurrentChat() { + return null; + } + + default TLRPC.User getCurrentUser() { + return null; + } + + long getDialogId(); + + default void scrollToMessageId(int id, int i, boolean b, int i1, boolean b1, int i2) { + + } + + default boolean shouldShowImport() { + return false; + } + + default boolean openedWithLivestream() { + return false; + } + + default long getMergeDialogId() { + return 0; + } + + default int getTopicId() { + return 0; + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextViewWavesDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextViewWavesDrawable.java index 409038154..ab2db19ca 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextViewWavesDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextViewWavesDrawable.java @@ -63,7 +63,7 @@ public class FragmentContextViewWavesDrawable { if (parentView == null) { update = false; } else { - update = parents.size() > 0 && parentView == parents.get(0); + update = parents.size() > 0; } if (top > bottom) { return; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupVoipInviteAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupVoipInviteAlert.java index 85b26f219..418e6737c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupVoipInviteAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupVoipInviteAlert.java @@ -17,6 +17,9 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import androidx.collection.LongSparseArray; +import androidx.recyclerview.widget.RecyclerView; + import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; @@ -42,9 +45,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; -import androidx.collection.LongSparseArray; -import androidx.recyclerview.widget.RecyclerView; - public class GroupVoipInviteAlert extends UsersAlertBase { private final SearchAdapter searchAdapter; @@ -151,7 +151,7 @@ public class GroupVoipInviteAlert extends UsersAlertBase { rowCount = 0; emptyRow = rowCount++; - if (!TextUtils.isEmpty(currentChat.username) || ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_INVITE)) { + if (ChatObject.isPublic(currentChat) || ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_INVITE)) { addNewRow = rowCount++; } if (!loadingUsers || firstLoaded) { @@ -487,7 +487,7 @@ public class GroupVoipInviteAlert extends UsersAlertBase { emptyView.showProgress(true, true); listView.setAnimateEmptyView(false, 0); notifyDataSetChanged(); - listView.setAnimateEmptyView(true, 0); + listView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); searchInProgress = true; int searchId = ++lastSearchId; AndroidUtilities.runOnUIThread(searchRunnable = () -> { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/InstantCameraView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/InstantCameraView.java index 6285bee50..deff75a55 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/InstantCameraView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/InstantCameraView.java @@ -67,7 +67,6 @@ import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.util.Log; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; @@ -1052,7 +1051,9 @@ public class InstantCameraView extends FrameLayout implements NotificationCenter } cameraSession.setInitied(); if (updateScale) { - cameraThread.reinitForNewCamera(); + if (cameraThread != null) { + cameraThread.reinitForNewCamera(); + } } } }, () -> cameraThread.setCurrentSession(cameraSession)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/InviteLinkBottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/InviteLinkBottomSheet.java index 0976a0ab7..72dfbaa38 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/InviteLinkBottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/InviteLinkBottomSheet.java @@ -202,11 +202,21 @@ public class InviteLinkBottomSheet extends BottomSheet { } if (statusBarHeight > 0) { - int color1 = Theme.getColor(Theme.key_dialogBackground); - int finalColor = Color.argb(0xff, (int) (Color.red(color1) * 0.8f), (int) (Color.green(color1) * 0.8f), (int) (Color.blue(color1) * 0.8f)); - Theme.dialogs_onlineCirclePaint.setColor(finalColor); + Theme.dialogs_onlineCirclePaint.setColor(Theme.getColor(Theme.key_dialogBackground)); canvas.drawRect(backgroundPaddingLeft, AndroidUtilities.statusBarHeight - statusBarHeight, getMeasuredWidth() - backgroundPaddingLeft, AndroidUtilities.statusBarHeight, Theme.dialogs_onlineCirclePaint); } + updateLightStatusBar(statusBarHeight > AndroidUtilities.statusBarHeight / 2); + } + + private Boolean statusBarOpen; + private void updateLightStatusBar(boolean open) { + if (statusBarOpen != null && statusBarOpen == open) { + return; + } + boolean openBgLight = AndroidUtilities.computePerceivedBrightness(getThemedColor(Theme.key_dialogBackground)) > .721f; + boolean closedBgLight = AndroidUtilities.computePerceivedBrightness(Theme.blendOver(getThemedColor(Theme.key_actionBarDefault), 0x33000000)) > .721f; + boolean isLight = (statusBarOpen = open) ? openBgLight : closedBgLight; + AndroidUtilities.setLightStatusBar(getWindow(), isLight); } }; containerView.setWillNotDraw(false); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/InviteMembersBottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/InviteMembersBottomSheet.java index 3f4a9baa0..a0455d135 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/InviteMembersBottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/InviteMembersBottomSheet.java @@ -37,6 +37,7 @@ import androidx.recyclerview.widget.RecyclerView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; import org.telegram.messenger.DialogObject; import org.telegram.messenger.LocaleController; @@ -173,9 +174,9 @@ public class InviteMembersBottomSheet extends UsersAlertBase implements Notifica if (position == copyLinkRow) { TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(chatId); TLRPC.ChatFull chatInfo = MessagesController.getInstance(currentAccount).getChatFull(chatId); - String link = null; - if (chat != null && !TextUtils.isEmpty(chat.username)) { - link = "https://t.me/" + chat.username; + String link = null, username; + if (chat != null && !TextUtils.isEmpty(username = ChatObject.getPublicUsername(chat))) { + link = "https://" + MessagesController.getInstance(currentAccount).linkPrefix + "/" + username; } else if (chatInfo != null && chatInfo.exported_invite != null) { link = chatInfo.exported_invite.link; } else { @@ -509,12 +510,15 @@ public class InviteMembersBottomSheet extends UsersAlertBase implements Notifica private void updateRows() { contactsStartRow = -1; contactsEndRow = -1; + copyLinkRow = -1; noContactsStubRow = -1; rowCount = 0; emptyRow = rowCount++; if (dialogsDelegate == null) { - copyLinkRow = rowCount++; + if (hasLink()) { + copyLinkRow = rowCount++; + } if (contacts.size() != 0) { contactsStartRow = rowCount; rowCount += contacts.size(); @@ -523,7 +527,6 @@ public class InviteMembersBottomSheet extends UsersAlertBase implements Notifica noContactsStubRow = rowCount++; } } else { - copyLinkRow = -1; if (dialogsServerOnly.size() != 0) { contactsStartRow = rowCount; rowCount += dialogsServerOnly.size(); @@ -536,6 +539,21 @@ public class InviteMembersBottomSheet extends UsersAlertBase implements Notifica lastRow = rowCount++; } + protected boolean hasLink() { + TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(chatId); + TLRPC.ChatFull chatInfo = MessagesController.getInstance(currentAccount).getChatFull(chatId); + if (chat != null && !TextUtils.isEmpty(ChatObject.getPublicUsername(chat)) || + chatInfo != null && chatInfo.exported_invite != null) { + return true; + } else { + return canGenerateLink(); + } + } + + protected boolean canGenerateLink() { + return true; + } + @Override public void didReceivedNotification(int id, int account, Object... args) { if (id == NotificationCenter.dialogsNeedReload) { @@ -774,7 +792,7 @@ public class InviteMembersBottomSheet extends UsersAlertBase implements Notifica if (object instanceof TLRPC.User) { objectUserName = ((TLRPC.User) object).username; } else { - objectUserName = ((TLRPC.Chat) object).username; + objectUserName = ChatObject.getPublicUsername((TLRPC.Chat) object); } if (position < localCount) { name = searchResultNames.get(position); @@ -939,7 +957,7 @@ public class InviteMembersBottomSheet extends UsersAlertBase implements Notifica } else { TLRPC.Chat chat = (TLRPC.Chat) object; name = chat.title; - username = chat.username; + username = ChatObject.getPublicUsername(chat); } String tName = LocaleController.getInstance().getTranslitString(name); if (name.equals(tName)) { @@ -994,7 +1012,7 @@ public class InviteMembersBottomSheet extends UsersAlertBase implements Notifica Activity activity = AndroidUtilities.findActivity(getContext()); BaseFragment fragment = null; if (activity instanceof LaunchActivity) { - fragment = ((LaunchActivity) activity).getActionBarLayout().fragmentsStack.get(((LaunchActivity) activity).getActionBarLayout().fragmentsStack.size() - 1); + fragment = ((LaunchActivity) activity).getActionBarLayout().getFragmentStack().get(((LaunchActivity) activity).getActionBarLayout().getFragmentStack().size() - 1); } if (fragment instanceof ChatActivity) { boolean keyboardVisible = ((ChatActivity) fragment).needEnterText(); @@ -1091,6 +1109,16 @@ public class InviteMembersBottomSheet extends UsersAlertBase implements Notifica if (holder != null) { listView.getAdapter().notifyItemChanged(0); layoutManager.scrollToPositionWithOffset(0, holder.itemView.getTop() - listView.getPaddingTop()); + if (listView.getItemAnimator() != null) { + ValueAnimator va = ValueAnimator.ofFloat(0, 1); + va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + listView.updateSelector(); + } + }); + va.setDuration(listView.getItemAnimator().getChangeDuration()).start(); + } } } } @@ -1315,7 +1343,7 @@ public class InviteMembersBottomSheet extends UsersAlertBase implements Notifica if (enterEventSent) { Activity activity = AndroidUtilities.findActivity(getContext()); if (activity instanceof LaunchActivity) { - BaseFragment fragment = ((LaunchActivity) activity).getActionBarLayout().fragmentsStack.get(((LaunchActivity) activity).getActionBarLayout().fragmentsStack.size() - 1); + BaseFragment fragment = ((LaunchActivity) activity).getActionBarLayout().getFragmentStack().get(((LaunchActivity) activity).getActionBarLayout().getFragmentStack().size() - 1); if (fragment instanceof ChatActivity) { ((ChatActivity) fragment).onEditTextDialogClose(true, true); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LetterDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LetterDrawable.java index 459507dff..09eda6319 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/LetterDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/LetterDrawable.java @@ -9,11 +9,13 @@ package org.telegram.ui.Components; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.text.Layout; import android.text.StaticLayout; @@ -27,6 +29,7 @@ public class LetterDrawable extends Drawable { public static Paint paint = new Paint(); private static TextPaint namePaint; + private static TextPaint namePaintTopic; private RectF rect = new RectF(); private StaticLayout textLayout; @@ -35,19 +38,37 @@ public class LetterDrawable extends Drawable { private float textLeft; private StringBuilder stringBuilder = new StringBuilder(5); + public static final int STYLE_DEFAULT = 0; + public static final int STYLE_TOPIC_DRAWABLE = 1; + int style; + final TextPaint textPaint; + public float scale = 1f; + public LetterDrawable() { - this(null); + this(null, 0); } - public LetterDrawable(Theme.ResourcesProvider resourcesProvider) { - super(); - if (namePaint == null) { - namePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + public LetterDrawable(Theme.ResourcesProvider resourcesProvider, int style) { + super(); + this.style = style; + if (style == STYLE_DEFAULT) { + if (namePaint == null) { + namePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + } + namePaint.setTextSize(AndroidUtilities.dp(28)); + paint.setColor(Theme.getColor(Theme.key_sharedMedia_linkPlaceholder, resourcesProvider)); + namePaint.setColor(Theme.getColor(Theme.key_sharedMedia_linkPlaceholderText, resourcesProvider)); + textPaint = namePaint; + } else { + if (namePaintTopic == null) { + namePaintTopic = new TextPaint(Paint.ANTI_ALIAS_FLAG); + } + namePaintTopic.setColor(Color.WHITE); + namePaintTopic.setTextSize(AndroidUtilities.dp(13)); + namePaintTopic.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD)); + textPaint = namePaintTopic; } - namePaint.setTextSize(AndroidUtilities.dp(28)); - paint.setColor(Theme.getColor(Theme.key_sharedMedia_linkPlaceholder, resourcesProvider)); - namePaint.setColor(Theme.getColor(Theme.key_sharedMedia_linkPlaceholderText, resourcesProvider)); } public void setBackgroundColor(int value) { @@ -55,7 +76,7 @@ public class LetterDrawable extends Drawable { } public void setColor(int value) { - namePaint.setColor(value); + textPaint.setColor(value); } public void setTitle(String title) { @@ -67,7 +88,7 @@ public class LetterDrawable extends Drawable { if (stringBuilder.length() > 0) { String text = stringBuilder.toString().toUpperCase(); try { - textLayout = new StaticLayout(text, namePaint, AndroidUtilities.dp(100), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + textLayout = new StaticLayout(text, textPaint, AndroidUtilities.dp(100), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (textLayout.getLineCount() > 0) { textLeft = textLayout.getLineLeft(0); textWidth = textLayout.getLineWidth(0); @@ -87,9 +108,14 @@ public class LetterDrawable extends Drawable { if (bounds == null) { return; } - rect.set(bounds.left, bounds.top, bounds.right, bounds.bottom); - canvas.drawRoundRect(rect, AndroidUtilities.dp(4), AndroidUtilities.dp(4), paint); + if (style == STYLE_DEFAULT) { + rect.set(bounds.left, bounds.top, bounds.right, bounds.bottom); + canvas.drawRoundRect(rect, AndroidUtilities.dp(4), AndroidUtilities.dp(4), paint); + } canvas.save(); + if (scale != 1) { + canvas.scale(scale, scale, bounds.centerX(), bounds.centerY()); + } if (textLayout != null) { int size = bounds.width(); canvas.translate(bounds.left + (size - textWidth) / 2 - textLeft, bounds.top + (size - textHeight) / 2); @@ -100,7 +126,7 @@ public class LetterDrawable extends Drawable { @Override public void setAlpha(int alpha) { - namePaint.setAlpha(alpha); + textPaint.setAlpha(alpha); paint.setAlpha(alpha); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkActionView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkActionView.java index 6f0b52600..12dffb0f2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkActionView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkActionView.java @@ -241,7 +241,7 @@ public class LinkActionView extends LinearLayout { FrameLayout container; if (bottomSheet == null) { - container = fragment.getParentLayout(); + container = (FrameLayout) fragment.getParentLayout().getOverlayContainerView(); } else { container = bottomSheet.getContainer(); } @@ -344,6 +344,9 @@ public class LinkActionView extends LinearLayout { if (v instanceof ScrollView) { y -= v.getScrollY(); } + if (!(v.getParent() instanceof View)) { + break; + } v = (View) v.getParent(); if (!(v instanceof ViewGroup)) { return; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkSpanDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkSpanDrawable.java index 2723ae1f1..9ed640c71 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkSpanDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkSpanDrawable.java @@ -6,16 +6,12 @@ import android.graphics.CornerPathEffect; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; -import android.graphics.Region; -import android.os.Build; import android.os.SystemClock; import android.text.Layout; import android.text.Spannable; import android.text.SpannableString; -import android.text.StaticLayout; import android.text.style.CharacterStyle; import android.text.style.ClickableSpan; -import android.util.Log; import android.util.Pair; import android.view.MotionEvent; import android.view.View; @@ -405,38 +401,51 @@ public class LinkSpanDrawable { onLongPressListener = listener; } + public ClickableSpan hit(int x, int y) { + Layout textLayout = getLayout(); + if (textLayout == null) { + return null; + } + x -= getPaddingLeft(); + y -= getPaddingTop(); + final int line = textLayout.getLineForVertical(y); + final int off = textLayout.getOffsetForHorizontal(line, x); + final float left = getLayout().getLineLeft(line); + ClickableSpan span = null; + if (left <= x && left + textLayout.getLineWidth(line) >= x && y >= 0 && y <= textLayout.getHeight()) { + Spannable buffer = new SpannableString(textLayout.getText()); + ClickableSpan[] spans = buffer.getSpans(off, off, ClickableSpan.class); + if (spans.length != 0 && !AndroidUtilities.isAccessibilityScreenReaderEnabled()) { + return spans[0]; + } + } + return null; + } + @Override public boolean onTouchEvent(MotionEvent event) { if (links != null) { Layout textLayout = getLayout(); - int x = (int) (event.getX() - getPaddingLeft()); - int y = (int) (event.getY() - getPaddingTop()); - final int line = textLayout.getLineForVertical(y); - final int off = textLayout.getOffsetForHorizontal(line, x); - final float left = getLayout().getLineLeft(line); - ClickableSpan span = null; - if (left <= x && left + textLayout.getLineWidth(line) >= x && y >= 0 && y <= textLayout.getHeight()) { - Spannable buffer = new SpannableString(textLayout.getText()); - ClickableSpan[] spans = buffer.getSpans(off, off, ClickableSpan.class); - if (spans.length != 0 && !AndroidUtilities.isAccessibilityScreenReaderEnabled()) { - span = spans[0]; - if (event.getAction() == MotionEvent.ACTION_DOWN) { - pressedLink = new LinkSpanDrawable(span, resourcesProvider, event.getX(), event.getY()); - links.addLink(pressedLink); - int start = buffer.getSpanStart(pressedLink.getSpan()); - int end = buffer.getSpanEnd(pressedLink.getSpan()); - LinkPath path = pressedLink.obtainNewPath(); - path.setCurrentLayout(textLayout, start, getPaddingTop()); - textLayout.getSelectionPath(start, end, path); - AndroidUtilities.runOnUIThread(() -> { - if (onLongPressListener != null) { - onLongPressListener.run(spans[0]); - pressedLink = null; - links.clear(); - } - }, ViewConfiguration.getLongPressTimeout()); - return true; - } + ClickableSpan span; + if ((span = hit((int) event.getX(), (int) event.getY())) != null) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + final LinkSpanDrawable link = new LinkSpanDrawable(span, resourcesProvider, event.getX(), event.getY()); + pressedLink = link; + links.addLink(pressedLink); + Spannable buffer = new SpannableString(textLayout.getText()); + int start = buffer.getSpanStart(pressedLink.getSpan()); + int end = buffer.getSpanEnd(pressedLink.getSpan()); + LinkPath path = pressedLink.obtainNewPath(); + path.setCurrentLayout(textLayout, start, getPaddingTop()); + textLayout.getSelectionPath(start, end, path); + AndroidUtilities.runOnUIThread(() -> { + if (onLongPressListener != null && pressedLink == link) { + onLongPressListener.run(span); + pressedLink = null; + links.clear(); + } + }, ViewConfiguration.getLongPressTimeout()); + return true; } } if (event.getAction() == MotionEvent.ACTION_UP) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LoadingAnimatedTextView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LoadingAnimatedTextView.java new file mode 100644 index 000000000..329e36cb6 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/LoadingAnimatedTextView.java @@ -0,0 +1,87 @@ +package org.telegram.ui.Components; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.text.SpannableStringBuilder; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.view.View; + +import org.checkerframework.checker.units.qual.C; +import org.telegram.messenger.AndroidUtilities; + +import java.util.ArrayList; + +public class LoadingAnimatedTextView extends View { + + private static class State { + private StaticLayout[] words; + private int[] hashCodes; + private int[] lengths; + } + + private TextPaint paint; + private State currentState; + private State nextState; + private Rect loadingRect; + private float stateTransition = 0; + private ValueAnimator stateTransitionAnimator; + + public LoadingAnimatedTextView(Context context, TextPaint textPaint) { + super(context); + paint = textPaint; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + + private static CharSequence[] splitToWords(CharSequence text) { + ArrayList spaces = new ArrayList<>(); + for (int i = 0; i < text.length(); ++i) + if (text.charAt(i) == ' ') + spaces.add(i); + CharSequence[] words = new CharSequence[spaces.size() + 1]; + for (int i = 0, s = -1; i < words.length; ++i) + words[i] = text.subSequence(++s, s = i >= spaces.size() ? text.length() : spaces.get(i)); + return words; + } + + private static State makeState(CharSequence before, CharSequence loaded, CharSequence after) { + State state = new State(); + CharSequence[] beforeWords = splitToWords(before); + CharSequence[] loadedWords = loaded == null ? null : splitToWords(loaded); + CharSequence[] afterWords = splitToWords(after); + return state; + } + + + public void setText(CharSequence before, CharSequence after) { + setText(before, null, after); + } + + public void setText(CharSequence before, CharSequence loaded, CharSequence after) { + if (stateTransitionAnimator != null) { + stateTransitionAnimator.cancel(); + } + + stateTransitionAnimator = ValueAnimator.ofFloat(stateTransition, loaded == null ? 1f : 0f); + stateTransitionAnimator.addUpdateListener(anm -> { + stateTransition = (float) anm.getAnimatedValue(); + invalidate(); + }); + stateTransitionAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator anm) { + stateTransition = (float) stateTransitionAnimator.getAnimatedValue(); + invalidate(); + stateTransitionAnimator = null; + } + }); + stateTransitionAnimator.start(); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActivity.java index 5bd75b048..e805ff2a4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActivity.java @@ -297,6 +297,22 @@ public class MediaActivity extends BaseFragment implements SharedMediaLayout.Sha return fragmentView; } + @Override + public boolean isSwipeBackEnabled(MotionEvent event) { + if (!sharedMediaLayout.isSwipeBackEnabled()) { + return false; + } + return sharedMediaLayout.isCurrentTabFirst(); + } + + @Override + public boolean canBeginSlide() { + if (!sharedMediaLayout.isSwipeBackEnabled()) { + return false; + } + return super.canBeginSlide(); + } + private void updateMediaCount() { int id = sharedMediaLayout.getClosestTab(); int[] mediaCount = sharedMediaPreloader.getLastMediaCount(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/MemberRequestsBottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/MemberRequestsBottomSheet.java index 361aaf8a5..102662253 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/MemberRequestsBottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/MemberRequestsBottomSheet.java @@ -132,7 +132,7 @@ public class MemberRequestsBottomSheet extends UsersAlertBase { Activity activity = AndroidUtilities.findActivity(getContext()); BaseFragment fragment = null; if (activity instanceof LaunchActivity) { - fragment = ((LaunchActivity) activity).getActionBarLayout().fragmentsStack.get(((LaunchActivity) activity).getActionBarLayout().fragmentsStack.size() - 1); + fragment = ((LaunchActivity) activity).getActionBarLayout().getFragmentStack().get(((LaunchActivity) activity).getActionBarLayout().getFragmentStack().size() - 1); } if (fragment instanceof ChatActivity) { boolean keyboardVisible = ((ChatActivity) fragment).needEnterText(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/MentionsContainerView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/MentionsContainerView.java index 2f480e29a..41f3cec83 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/MentionsContainerView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/MentionsContainerView.java @@ -239,6 +239,9 @@ public class MentionsContainerView extends BlurredFrameLayout { public void onPanTransitionEnd() {} + protected void onScrolled(boolean atTop, boolean atBottom) { + + } public MentionsListView getListView() { return listView; @@ -520,6 +523,8 @@ public class MentionsContainerView extends BlurredFrameLayout { if (visibleItemCount > 0 && lastVisibleItem > adapter.getLastItemCount() - 5) { adapter.searchForContextBotForNextOffset(); } + + MentionsContainerView.this.onScrolled(!canScrollVertically(-1), !canScrollVertically(1)); } }); addItemDecoration(new RecyclerView.ItemDecoration() { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/MotionBackgroundDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/MotionBackgroundDrawable.java index 662f13b37..240d45d7e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/MotionBackgroundDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/MotionBackgroundDrawable.java @@ -281,7 +281,7 @@ public class MotionBackgroundDrawable extends Drawable { } legacyCanvas2.drawBitmap(legacyBitmap, 0, 0, null); } - } catch (Exception e) { + } catch (Throwable e) { FileLog.e(e); if (legacyBitmap2 != null) { legacyBitmap2.recycle(); @@ -475,7 +475,7 @@ public class MotionBackgroundDrawable extends Drawable { legacyBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); legacyCanvas = new Canvas(legacyBitmap); invalidateLegacy = true; - } catch (Exception e) { + } catch (Throwable e) { if (legacyBitmap != null) { legacyBitmap.recycle(); legacyBitmap = null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/OverlayActionBarLayoutDialog.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/OverlayActionBarLayoutDialog.java index a65634d5d..7b09d5c4f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/OverlayActionBarLayoutDialog.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/OverlayActionBarLayoutDialog.java @@ -17,47 +17,46 @@ import androidx.core.graphics.ColorUtils; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.R; -import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.Theme; import java.util.ArrayList; -public class OverlayActionBarLayoutDialog extends Dialog implements ActionBarLayout.ActionBarLayoutDelegate { +public class OverlayActionBarLayoutDialog extends Dialog implements INavigationLayout.INavigationLayoutDelegate { private Theme.ResourcesProvider resourcesProvider; - private ActionBarLayout actionBarLayout; + private INavigationLayout actionBarLayout; private FrameLayout frameLayout; public OverlayActionBarLayoutDialog(@NonNull Context context, Theme.ResourcesProvider resourcesProvider) { super(context, R.style.TransparentDialog); this.resourcesProvider = resourcesProvider; - actionBarLayout = new ActionBarLayout(context) { - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (AndroidUtilities.isTablet() && !AndroidUtilities.isInMultiwindow && !AndroidUtilities.isSmallTablet()) { - super.onMeasure(MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(530), MeasureSpec.getSize(widthMeasureSpec)), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(528), MeasureSpec.getSize(heightMeasureSpec)), MeasureSpec.EXACTLY)); - } else { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - }; - actionBarLayout.init(new ArrayList<>()); - actionBarLayout.presentFragment(new EmptyFragment(), false, true, false, false); + actionBarLayout = INavigationLayout.newLayout(context); + actionBarLayout.setFragmentStack(new ArrayList<>()); + actionBarLayout.presentFragment(new INavigationLayout.NavigationParams(new EmptyFragment()).setNoAnimation(true)); actionBarLayout.setDelegate(this); frameLayout = new FrameLayout(context); frameLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - frameLayout.addView(actionBarLayout, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER)); + frameLayout.addView(actionBarLayout.getView(), new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER)); if (AndroidUtilities.isTablet() && !AndroidUtilities.isInMultiwindow && !AndroidUtilities.isSmallTablet()) { frameLayout.setBackgroundColor(0x99000000); frameLayout.setOnClickListener(v -> onBackPressed()); actionBarLayout.setRemoveActionBarExtraHeight(true); - VerticalPositionAutoAnimator.attach(actionBarLayout); + VerticalPositionAutoAnimator.attach(actionBarLayout.getView()); } setContentView(frameLayout); } + @Override + public void onMeasureOverride(int[] measureSpec) { + if (AndroidUtilities.isTablet() && !AndroidUtilities.isInMultiwindow && !AndroidUtilities.isSmallTablet()) { + measureSpec[0] = View.MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(530), View.MeasureSpec.getSize(measureSpec[0])), View.MeasureSpec.EXACTLY); + measureSpec[1] = View.MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(528), View.MeasureSpec.getSize(measureSpec[1])), View.MeasureSpec.EXACTLY); + } + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -107,7 +106,7 @@ public class OverlayActionBarLayoutDialog extends Dialog implements ActionBarLay @Override public void onBackPressed() { actionBarLayout.onBackPressed(); - if (actionBarLayout.fragmentsStack.size() <= 1) { + if (actionBarLayout.getFragmentStack().size() <= 1) { dismiss(); } } @@ -118,25 +117,25 @@ public class OverlayActionBarLayoutDialog extends Dialog implements ActionBarLay } @Override - public boolean needPresentFragment(BaseFragment fragment, boolean removeLast, boolean forceWithoutAnimation, ActionBarLayout layout) { + public boolean needPresentFragment(BaseFragment fragment, boolean removeLast, boolean forceWithoutAnimation, INavigationLayout layout) { return true; } @Override - public boolean needAddFragmentToStack(BaseFragment fragment, ActionBarLayout layout) { + public boolean needAddFragmentToStack(BaseFragment fragment, INavigationLayout layout) { return true; } @Override - public boolean needCloseLastFragment(ActionBarLayout layout) { - if (layout.fragmentsStack.size() <= 1) { + public boolean needCloseLastFragment(INavigationLayout layout) { + if (layout.getFragmentStack().size() <= 1) { dismiss(); } return true; } @Override - public void onRebuildAllFragments(ActionBarLayout layout, boolean last) {} + public void onRebuildAllFragments(INavigationLayout layout, boolean last) {} private static final class EmptyFragment extends BaseFragment { @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java index 5cd382fb1..9a3eb170a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java @@ -1514,7 +1514,7 @@ public class PasscodeView extends FrameLayout implements NotificationCenter.Noti if (v != null) { v.vibrate(200); } - AndroidUtilities.shakeView(fingerprintStatusTextView, 2, 0); + AndroidUtilities.shakeView(fingerprintStatusTextView); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PermanentLinkBottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PermanentLinkBottomSheet.java index 0ee85f44f..42e60fd87 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PermanentLinkBottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PermanentLinkBottomSheet.java @@ -10,6 +10,7 @@ import androidx.core.graphics.ColorUtils; import androidx.core.widget.NestedScrollView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ChatObject; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; @@ -98,8 +99,8 @@ public class PermanentLinkBottomSheet extends BottomSheet { setCustomView(scrollView); TLRPC.Chat chat = MessagesController.getInstance(UserConfig.selectedAccount).getChat(chatId); - if (chat != null && chat.username != null) { - linkActionView.setLink("https://t.me/" + chat.username); + if (chat != null && ChatObject.isPublic(chat)) { + linkActionView.setLink("https://t.me/" + ChatObject.getPublicUsername(chat)); manage.setVisibility(View.GONE); } else if (info != null && info.exported_invite != null) { linkActionView.setLink(info.exported_invite.link); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java index 7816ffaa9..2d9b22aea 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java @@ -71,6 +71,10 @@ public class PhotoViewerCaptionEnterView extends FrameLayout implements Notifica return MessagesController.getInstance(currentAccount).getCaptionMaxLengthLimit() - codePointCount; } + public int getCodePointCount() { + return codePointCount; + } + public interface PhotoViewerCaptionEnterViewDelegate { void onCaptionEnter(); void onTextChanged(CharSequence text); @@ -368,7 +372,7 @@ public class PhotoViewerCaptionEnterView extends FrameLayout implements Notifica textFieldContainer.addView(doneButton, LayoutHelper.createLinear(48, 48, Gravity.BOTTOM)); doneButton.setOnClickListener(view -> { if (MessagesController.getInstance(currentAccount).getCaptionMaxLengthLimit() - codePointCount < 0) { - AndroidUtilities.shakeView(captionLimitView, 2, 0); + AndroidUtilities.shakeView(captionLimitView); Vibrator v = (Vibrator) captionLimitView.getContext().getSystemService(Context.VIBRATOR_SERVICE); if (v != null) { v.vibrate(200); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PollVotesAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PollVotesAlert.java index 1fdc09f7a..c16d58503 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PollVotesAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PollVotesAlert.java @@ -36,6 +36,7 @@ import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.animation.OvershootInterpolator; import android.widget.FrameLayout; import android.widget.TextView; @@ -63,8 +64,10 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.Objects; import androidx.annotation.Keep; +import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -141,7 +144,7 @@ public class PollVotesAlert extends BottomSheet { private TextView textView; private TextView middleTextView; - private TextView righTextView; + private AnimatedTextView righTextView; public SectionCell(Context context) { super(context); @@ -161,7 +164,7 @@ public class PollVotesAlert extends BottomSheet { middleTextView.setTextColor(Theme.getColor(Theme.key_graySectionText)); middleTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); - righTextView = new TextView(getContext()) { + righTextView = new AnimatedTextView(getContext()) { @Override public boolean post(Runnable action) { return containerView.post(action); @@ -171,10 +174,18 @@ public class PollVotesAlert extends BottomSheet { public boolean postDelayed(Runnable action, long delayMillis) { return containerView.postDelayed(action, delayMillis); } + + @Override + public void invalidate() { + super.invalidate(); + if (SectionCell.this == listView.getPinnedHeader()) { + listView.invalidate(); + } + } }; - righTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + righTextView.setTextSize(AndroidUtilities.dp(14)); righTextView.setTextColor(Theme.getColor(Theme.key_graySectionText)); - righTextView.setGravity((LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL); + righTextView.setGravity((LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT)); righTextView.setOnClickListener(v -> onCollapseClick()); addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? 0 : 16), 0, (LocaleController.isRTL ? 16 : 0), 0)); @@ -208,7 +219,7 @@ public class PollVotesAlert extends BottomSheet { } - public void setText(String left, int percent, int votesCount, int collapsed) { + public void setText(String left, int percent, int votesCount, int collapsed, boolean animated) { textView.setText(Emoji.replaceEmoji(left, textView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(14), false)); String p = String.format("%d", percent); SpannableStringBuilder builder; @@ -221,14 +232,14 @@ public class PollVotesAlert extends BottomSheet { middleTextView.setText(builder); if (collapsed == 0) { if (poll.quiz) { - righTextView.setText(LocaleController.formatPluralString("Answer", votesCount)); + righTextView.setText(LocaleController.formatPluralString("Answer", votesCount), animated); } else { - righTextView.setText(LocaleController.formatPluralString("Vote", votesCount)); + righTextView.setText(LocaleController.formatPluralString("Vote", votesCount), animated); } } else if (collapsed == 1) { - righTextView.setText(LocaleController.getString("PollExpand", R.string.PollExpand)); + righTextView.setText(LocaleController.getString("PollExpand", R.string.PollExpand), animated); } else { - righTextView.setText(LocaleController.getString("PollCollapse", R.string.PollCollapse)); + righTextView.setText(LocaleController.getString("PollCollapse", R.string.PollCollapse), animated); } } } @@ -732,8 +743,22 @@ public class PollVotesAlert extends BottomSheet { super.dispatchDraw(canvas); } }; + DefaultItemAnimator itemAnimator = new DefaultItemAnimator(); + itemAnimator.setAddDuration(150); + itemAnimator.setMoveDuration(350); + itemAnimator.setChangeDuration(0); + itemAnimator.setRemoveDuration(0); + itemAnimator.setDelayAnimations(false); + itemAnimator.setMoveInterpolator(new OvershootInterpolator(1.1f)); + itemAnimator.setTranslationInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); + listView.setItemAnimator(itemAnimator); listView.setClipToPadding(false); - listView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false)); + listView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false) { + @Override + protected int getExtraLayoutSpace(RecyclerView.State state) { + return AndroidUtilities.dp(4000); + } + }); listView.setHorizontalScrollBarEnabled(false); listView.setVerticalScrollBarEnabled(false); listView.setSectionsType(RecyclerListView.SECTIONS_TYPE_DATE); @@ -759,7 +784,8 @@ public class PollVotesAlert extends BottomSheet { if (votesList.collapsedCount == votesList.votes.size()) { votesList.collapsed = false; } - listAdapter.notifyDataSetChanged(); + animateSectionUpdates(null); + listAdapter.update(true); return; } loadingMore.add(votesList); @@ -781,7 +807,8 @@ public class PollVotesAlert extends BottomSheet { parentFragment.getMessagesController().putUsers(res.users, false); votesList.votes.addAll(res.votes); votesList.next_offset = res.next_offset; - listAdapter.notifyDataSetChanged(); + animateSectionUpdates(null); + listAdapter.update(true); } })); } else if (view instanceof UserCell) { @@ -1014,7 +1041,17 @@ public class PollVotesAlert extends BottomSheet { } public Object getItem(int section, int position) { - return null; + if (section == 0) { + return 293145; + } + section--; + if (position == 0) { + return -928312; + } else if (section >= 0 && section < voters.size() && position - 1 < voters.get(section).getCount()) { + return Objects.hash(voters.get(section).votes.get(position - 1).user_id); + } else { + return -182734; + } } @Override @@ -1052,7 +1089,8 @@ public class PollVotesAlert extends BottomSheet { if (list.collapsed) { list.collapsedCount = 10; } - listAdapter.notifyDataSetChanged(); + animateSectionUpdates(this); + listAdapter.update(true); } }; } @@ -1069,12 +1107,14 @@ public class PollVotesAlert extends BottomSheet { section -= 1; view.setAlpha(1.0f); VotesList votesList = voters.get(section); - TLRPC.MessageUserVote vote = votesList.votes.get(0); for (int a = 0, N = poll.answers.size(); a < N; a++) { TLRPC.TL_pollAnswer answer = poll.answers.get(a); if (Arrays.equals(answer.option, votesList.option)) { Button button = votesPercents.get(votesList); - sectionCell.setText(answer.text, button.percent, button.votesCount, votesList.getCollapsed()); + if (button == null) { + continue; + } + sectionCell.setText(answer.text, calcPercent(votesList.option), votesList.count, votesList.getCollapsed(), false); sectionCell.setTag(R.id.object_tag, votesList); break; } @@ -1107,6 +1147,7 @@ public class PollVotesAlert extends BottomSheet { default: { TextCell textCell = new TextCell(mContext, 23, true); textCell.setOffsetFromImage(65); + textCell.setBackgroundColor(getThemedColor(Theme.key_dialogBackground)); textCell.setColors(Theme.key_switchTrackChecked, Theme.key_windowBackgroundWhiteBlueText4); view = textCell; break; @@ -1127,7 +1168,10 @@ public class PollVotesAlert extends BottomSheet { TLRPC.TL_pollAnswer answer = poll.answers.get(a); if (Arrays.equals(answer.option, votesList.option)) { Button button = votesPercents.get(votesList); - sectionCell.setText(answer.text, button.percent, button.votesCount, votesList.getCollapsed()); + if (button == null) { + continue; + } + sectionCell.setText(answer.text, calcPercent(votesList.option), votesList.count, votesList.getCollapsed(), false); sectionCell.setTag(R.id.object_tag, votesList); break; } @@ -1195,6 +1239,51 @@ public class PollVotesAlert extends BottomSheet { } } + public int calcPercent(byte[] option) { + if (option == null) { + return 0; + } + int all = 0; + int count = 0; + for (int i = 0; i < voters.size(); ++i) { + VotesList votesList = voters.get(i); + if (votesList != null) { + all += votesList.count; + if (Arrays.equals(votesList.option, option)) { + count += votesList.count; + } + } + } + if (all <= 0) { + return 0; + } + return (int) Math.round(count / (float) all * 100); + } + + public void animateSectionUpdates(View view) { + for (int i = -2; i < listView.getChildCount(); ++i) { + View child = i == -2 ? view : (i == -1 ? listView.getPinnedHeader() : listView.getChildAt(i)); + if (child instanceof SectionCell && child.getTag(R.id.object_tag) instanceof VotesList) { + SectionCell sectionCell = (SectionCell) child; + VotesList votesList = (VotesList) child.getTag(R.id.object_tag); + for (int a = 0, N = poll.answers.size(); a < N; a++) { + TLRPC.TL_pollAnswer answer = poll.answers.get(a); + if (Arrays.equals(answer.option, votesList.option)) { + Button button = votesPercents.get(votesList); + if (button == null) { + continue; + } + sectionCell.setText(answer.text, calcPercent(votesList.option), votesList.count, votesList.getCollapsed(), true); + sectionCell.setTag(R.id.object_tag, votesList); + break; + } + } + } + } + listView.relayoutPinnedHeader(); + listView.invalidate(); + } + @Override public ArrayList getThemeDescriptions() { ArrayList themeDescriptions = new ArrayList<>(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/GiftPremiumBottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/GiftPremiumBottomSheet.java index e67d1bcc9..626208332 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/GiftPremiumBottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/GiftPremiumBottomSheet.java @@ -35,8 +35,8 @@ import org.telegram.messenger.R; import org.telegram.messenger.browser.Browser; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; -import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.ChatActivity; @@ -234,9 +234,9 @@ public class GiftPremiumBottomSheet extends BottomSheetWithRecyclerListView { } if (getBaseFragment() != null) { - List fragments = new ArrayList<>(((LaunchActivity) getBaseFragment().getParentActivity()).getActionBarLayout().fragmentsStack); + List fragments = new ArrayList<>(((LaunchActivity) getBaseFragment().getParentActivity()).getActionBarLayout().getFragmentStack()); - ActionBarLayout layout = getBaseFragment().getParentLayout(); + INavigationLayout layout = getBaseFragment().getParentLayout(); ChatActivity lastChatActivity = null; for (BaseFragment fragment : fragments) { if (fragment instanceof ChatActivity) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/LimitReachedBottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/LimitReachedBottomSheet.java index 81d7d42a1..54d817c5b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/LimitReachedBottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/LimitReachedBottomSheet.java @@ -604,9 +604,9 @@ public class LimitReachedBottomSheet extends BottomSheetWithRecyclerListView { if (channels.size() == 1) { TLRPC.Chat channel = channels.get(0); if (parentIsChannel) { - builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlertChannel", R.string.RevokeLinkAlertChannel, MessagesController.getInstance(currentAccount).linkPrefix + "/" + channel.username, channel.title))); + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlertChannel", R.string.RevokeLinkAlertChannel, MessagesController.getInstance(currentAccount).linkPrefix + "/" + ChatObject.getPublicUsername(channel), channel.title))); } else { - builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlert", R.string.RevokeLinkAlert, MessagesController.getInstance(currentAccount).linkPrefix + "/" + channel.username, channel.title))); + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlert", R.string.RevokeLinkAlert, MessagesController.getInstance(currentAccount).linkPrefix + "/" + ChatObject.getPublicUsername(channel), channel.title))); } } else { if (parentIsChannel) { @@ -689,6 +689,9 @@ public class LimitReachedBottomSheet extends BottomSheetWithRecyclerListView { ((LinearLayoutManager) recyclerListView.getLayoutManager()).scrollToPositionWithOffset(headerRow + 1, savedTop); } + if (limitParams == null) { + limitParams = getLimitParams(type, currentAccount); + } int currentValue = Math.max(inactiveChats.size(), limitParams.defaultLimit); limitPreviewView.setIconValue(currentValue); limitPreviewView.setBagePosition(currentValue / (float) limitParams.premiumLimit); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/PremiumFeatureBottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/PremiumFeatureBottomSheet.java index 05515c55c..fce378d44 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/PremiumFeatureBottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/PremiumFeatureBottomSheet.java @@ -277,9 +277,9 @@ public class PremiumFeatureBottomSheet extends BottomSheet implements Notificati } containerViewsProgress = progress; containerViewsForward = toPosition > selectedPosition; - if (premiumFeatures.get(selectedPosition).type == PremiumPreviewFragment.PREMIUM_FEATURE_LIMITS) { + if (selectedPosition >= 0 && selectedPosition < premiumFeatures.size() && premiumFeatures.get(selectedPosition).type == PremiumPreviewFragment.PREMIUM_FEATURE_LIMITS) { progressToFullscreenView = 1f - progress; - } else if (premiumFeatures.get(toPosition).type == PremiumPreviewFragment.PREMIUM_FEATURE_LIMITS) { + } else if (toPosition >= 0 && toPosition < premiumFeatures.size() && premiumFeatures.get(toPosition).type == PremiumPreviewFragment.PREMIUM_FEATURE_LIMITS) { progressToFullscreenView = progress; } else { progressToFullscreenView = 0; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/PremiumPreviewBottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/PremiumPreviewBottomSheet.java index a28c32cc2..7e731bf05 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/PremiumPreviewBottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/PremiumPreviewBottomSheet.java @@ -222,16 +222,49 @@ public class PremiumPreviewBottomSheet extends BottomSheetWithRecyclerListView i container.getLocationOnScreen(coords); } - private LinkSpanDrawable.LinksTextView titleView; + private FrameLayout titleViewContainer; + private LinkSpanDrawable.LinksTextView titleView[]; + private void titleLoaded(CharSequence newText, boolean animated) { + if (titleView == null) { + return; + } + titleView[1].setText(newText); + if (titleView[1].getVisibility() != View.VISIBLE) { + if (animated) { + titleView[1].setAlpha(0); + titleView[1].setVisibility(View.VISIBLE); + titleView[1].animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(200).start(); + titleView[0].animate().alpha(0).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(200).withEndAction(() -> { + titleView[0].setVisibility(View.GONE); + }).start(); + + ValueAnimator heightUpdate = ValueAnimator.ofFloat(0, 1); + heightUpdate.addUpdateListener(anm -> { + float t = (float) anm.getAnimatedValue(); + titleViewContainer.getLayoutParams().height = AndroidUtilities.lerp(titleView[0].getHeight(), titleView[1].getHeight(), t); + titleViewContainer.requestLayout(); + }); + heightUpdate.setInterpolator(CubicBezierInterpolator.DEFAULT); + heightUpdate.setDuration(200); + heightUpdate.start(); + } else { + titleView[1].setAlpha(1); + titleView[1].setVisibility(View.VISIBLE); + titleView[0].setAlpha(0); + titleView[0].setVisibility(View.GONE); + } + } + } + private TextView subtitleView; - public void setTitle() { + public void setTitle(boolean animated) { if (titleView == null || subtitleView == null) { return; } if (statusStickerSet != null) { final String stickerSetPlaceholder = ""; String string = LocaleController.formatString(R.string.TelegramPremiumUserStatusDialogTitle, ContactsController.formatName(user.first_name, user.last_name), stickerSetPlaceholder); - CharSequence charSequence = AndroidUtilities.replaceSingleTag(string, Theme.key_windowBackgroundWhiteBlueButton, null); + CharSequence charSequence = AndroidUtilities.replaceSingleTag(string, Theme.key_windowBackgroundWhiteBlueButton, AndroidUtilities.REPLACING_TAG_TYPE_LINK, null); SpannableStringBuilder title = charSequence instanceof SpannableStringBuilder ? ((SpannableStringBuilder) charSequence) : new SpannableStringBuilder(charSequence); int index = charSequence.toString().indexOf(stickerSetPlaceholder); if (index >= 0) { @@ -251,14 +284,14 @@ public class PremiumPreviewBottomSheet extends BottomSheetWithRecyclerListView i SpannableStringBuilder replaceWith; if (sticker != null) { SpannableStringBuilder animatedEmoji = new SpannableStringBuilder("x"); - animatedEmoji.setSpan(new AnimatedEmojiSpan(sticker, titleView.getPaint().getFontMetricsInt()), 0, animatedEmoji.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + animatedEmoji.setSpan(new AnimatedEmojiSpan(sticker, titleView[0].getPaint().getFontMetricsInt()), 0, animatedEmoji.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); if (stickerSet != null && stickerSet.set != null) { animatedEmoji.append("\u00A0").append(stickerSet.set.title); } replaceWith = animatedEmoji; } else { SpannableStringBuilder loading = new SpannableStringBuilder("xxxxxx"); - loading.setSpan(new LoadingSpan(titleView, AndroidUtilities.dp(100)), 0, loading.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + loading.setSpan(new LoadingSpan(titleView[0], AndroidUtilities.dp(100)), 0, loading.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); replaceWith = loading; } title.replace(index, index + stickerSetPlaceholder.length(), replaceWith); @@ -271,7 +304,7 @@ public class PremiumPreviewBottomSheet extends BottomSheetWithRecyclerListView i @Override public void onClick(@NonNull View view) {} }, index, index + replaceWith.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - titleView.setOnLinkPressListener(l -> { + titleView[1].setOnLinkPressListener(l -> { ArrayList inputStickerSets = new ArrayList<>(); inputStickerSets.add(statusStickerSet); BaseFragment overridenFragment = new BaseFragment() { @@ -310,22 +343,26 @@ public class PremiumPreviewBottomSheet extends BottomSheetWithRecyclerListView i } }.show(); }); + if (sticker != null) { + titleLoaded(title, animated); + } else { + titleView[0].setText(title, null); + } } - titleView.setText(title, null); subtitleView.setText(AndroidUtilities.replaceTags(LocaleController.getString(R.string.TelegramPremiumUserStatusDialogSubtitle))); } else if (isEmojiStatus) { - titleView.setText(AndroidUtilities.replaceTags(LocaleController.formatString(R.string.TelegramPremiumUserStatusDefaultDialogTitle, ContactsController.formatName(user.first_name, user.last_name)))); + titleView[0].setText(AndroidUtilities.replaceTags(LocaleController.formatString(R.string.TelegramPremiumUserStatusDefaultDialogTitle, ContactsController.formatName(user.first_name, user.last_name)))); subtitleView.setText(AndroidUtilities.replaceTags(LocaleController.formatString(R.string.TelegramPremiumUserStatusDialogSubtitle, ContactsController.formatName(user.first_name, user.last_name)))); } else if (giftTier != null) { if (isOutboundGift) { - titleView.setText(AndroidUtilities.replaceSingleTag(LocaleController.formatString(R.string.TelegramPremiumUserGiftedPremiumOutboundDialogTitleWithPlural, user != null ? user.first_name : "", LocaleController.formatPluralString("GiftMonths", giftTier.getMonths())), Theme.key_windowBackgroundWhiteBlueButton, null)); - subtitleView.setText(AndroidUtilities.replaceSingleTag(LocaleController.formatString(R.string.TelegramPremiumUserGiftedPremiumOutboundDialogSubtitle, user != null ? user.first_name : ""), Theme.key_windowBackgroundWhiteBlueButton, null)); + titleView[0].setText(AndroidUtilities.replaceSingleTag(LocaleController.formatString(R.string.TelegramPremiumUserGiftedPremiumOutboundDialogTitleWithPlural, user != null ? user.first_name : "", LocaleController.formatPluralString("GiftMonths", giftTier.getMonths())), Theme.key_windowBackgroundWhiteBlueButton, AndroidUtilities.REPLACING_TAG_TYPE_LINK, null)); + subtitleView.setText(AndroidUtilities.replaceSingleTag(LocaleController.formatString(R.string.TelegramPremiumUserGiftedPremiumOutboundDialogSubtitle, user != null ? user.first_name : ""), Theme.key_windowBackgroundWhiteBlueButton, AndroidUtilities.REPLACING_TAG_TYPE_LINK, null)); } else { - titleView.setText(AndroidUtilities.replaceSingleTag(LocaleController.formatString(R.string.TelegramPremiumUserGiftedPremiumDialogTitleWithPlural, user != null ? user.first_name : "", LocaleController.formatPluralString("GiftMonths", giftTier.getMonths())), Theme.key_windowBackgroundWhiteBlueButton, null)); + titleView[0].setText(AndroidUtilities.replaceSingleTag(LocaleController.formatString(R.string.TelegramPremiumUserGiftedPremiumDialogTitleWithPlural, user != null ? user.first_name : "", LocaleController.formatPluralString("GiftMonths", giftTier.getMonths())), Theme.key_windowBackgroundWhiteBlueButton, AndroidUtilities.REPLACING_TAG_TYPE_LINK, null)); subtitleView.setText(AndroidUtilities.replaceTags(LocaleController.getString(R.string.TelegramPremiumUserGiftedPremiumDialogSubtitle))); } } else { - titleView.setText(AndroidUtilities.replaceSingleTag(LocaleController.formatString(R.string.TelegramPremiumUserDialogTitle, ContactsController.formatName(user.first_name, user.last_name)), Theme.key_windowBackgroundWhiteBlueButton, null)); + titleView[0].setText(AndroidUtilities.replaceSingleTag(LocaleController.formatString(R.string.TelegramPremiumUserDialogTitle, ContactsController.formatName(user.first_name, user.last_name)), Theme.key_windowBackgroundWhiteBlueButton, AndroidUtilities.REPLACING_TAG_TYPE_LINK, null)); subtitleView.setText(AndroidUtilities.replaceTags(LocaleController.getString(R.string.TelegramPremiumUserDialogSubtitle))); } } @@ -389,38 +426,51 @@ public class PremiumPreviewBottomSheet extends BottomSheetWithRecyclerListView i linearLayout.addView(overrideTitleIcon, LayoutHelper.createLinear(140, 140, Gravity.CENTER_HORIZONTAL, Gravity.CENTER, 10, 10, 10, 10)); } - if (titleView == null) { + if (titleViewContainer == null) { + titleViewContainer = new FrameLayout(context); + titleViewContainer.setClipChildren(false); + final ColorFilter colorFilter = new PorterDuffColorFilter(ColorUtils.setAlphaComponent(getThemedColor(Theme.key_windowBackgroundWhiteLinkText), 178), PorterDuff.Mode.MULTIPLY); - titleView = new LinkSpanDrawable.LinksTextView(context, resourcesProvider) { - private Layout lastLayout; - AnimatedEmojiSpan.EmojiGroupedSpans stack; + titleView = new LinkSpanDrawable.LinksTextView[2]; + for (int a = 0; a < 2; ++a) { + titleView[a] = new LinkSpanDrawable.LinksTextView(context, resourcesProvider) { + private Layout lastLayout; + AnimatedEmojiSpan.EmojiGroupedSpans stack; - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - AnimatedEmojiSpan.release(this, stack); - lastLayout = null; - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - if (lastLayout != getLayout()) { - stack = AnimatedEmojiSpan.update(AnimatedEmojiDrawable.CACHE_TYPE_ALERT_PREVIEW, this, stack, lastLayout = getLayout()); + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + AnimatedEmojiSpan.release(this, stack); + lastLayout = null; } - AnimatedEmojiSpan.drawAnimatedEmojis(canvas, getLayout(), stack, 0, null, 0, 0, 0, 1f, colorFilter); - } - }; - titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - titleView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - titleView.setGravity(Gravity.CENTER_HORIZONTAL); - titleView.setTextColor(getThemedColor(Theme.key_windowBackgroundWhiteBlackText)); - titleView.setLinkTextColor(getThemedColor(Theme.key_windowBackgroundWhiteLinkText)); + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + if (lastLayout != getLayout()) { + stack = AnimatedEmojiSpan.update(AnimatedEmojiDrawable.CACHE_TYPE_ALERT_PREVIEW, this, stack, lastLayout = getLayout()); + } + AnimatedEmojiSpan.drawAnimatedEmojis(canvas, getLayout(), stack, 0, null, 0, 0, 0, 1f, colorFilter); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(99999999, MeasureSpec.AT_MOST)); + } + }; + titleView[a].setVisibility(a == 0 ? View.VISIBLE : View.GONE); + titleView[a].setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + titleView[a].setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + titleView[a].setGravity(Gravity.CENTER_HORIZONTAL); + titleView[a].setTextColor(getThemedColor(Theme.key_windowBackgroundWhiteBlackText)); + titleView[a].setLinkTextColor(getThemedColor(Theme.key_windowBackgroundWhiteLinkText)); + titleViewContainer.addView(titleView[a], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } } - if (titleView.getParent() != null) { - ((ViewGroup) titleView.getParent()).removeView(titleView); + if (titleViewContainer.getParent() != null) { + ((ViewGroup) titleViewContainer.getParent()).removeView(titleViewContainer); } - linearLayout.addView(titleView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, 0, Gravity.CENTER_HORIZONTAL, 40, 0, 40, 0)); + linearLayout.addView(titleViewContainer, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, 0, Gravity.CENTER_HORIZONTAL, 40, 0, 40, 0)); if (subtitleView == null) { subtitleView = new TextView(context); @@ -434,7 +484,7 @@ public class PremiumPreviewBottomSheet extends BottomSheetWithRecyclerListView i } linearLayout.addView(subtitleView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, 16, 9, 16, 20)); - setTitle(); + setTitle(false); starParticlesView = new StarParticlesView(context) { @Override @@ -596,6 +646,12 @@ public class PremiumPreviewBottomSheet extends BottomSheetWithRecyclerListView i float cxFrom = -coords[0] + startEnterFromX1 + points[0]; float cyFrom = -coords[1] + startEnterFromY1 + points[1]; + if (AndroidUtilities.isTablet()) { + View v = fragment.getParentLayout().getView(); + cxFrom += v.getX() + v.getPaddingLeft(); + cyFrom += v.getY() + v.getPaddingTop(); + } + float fromSize = startEnterFromScale * startEnterFromDrawable.getIntrinsicWidth(); float toSize = titleIcon.getMeasuredHeight() * 0.8f; float toSclale = toSize / fromSize; @@ -697,7 +753,7 @@ public class PremiumPreviewBottomSheet extends BottomSheetWithRecyclerListView i public void didReceivedNotification(int id, int account, Object... args) { if (id == NotificationCenter.groupStickersDidLoad) { if (statusStickerSet != null && statusStickerSet.id == (long) args[0]) { - setTitle(); + setTitle(true); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/RLottieDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/RLottieDrawable.java index 9954d1b66..c6085094e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/RLottieDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/RLottieDrawable.java @@ -21,7 +21,6 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; import android.text.TextUtils; -import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.View; @@ -1243,7 +1242,7 @@ public class RLottieDrawable extends BitmapDrawable implements Animatable, Bitma @Override public void prepareForGenerateCache() { generateCacheNativePtr = create(args.file.toString(), args.json, width, height, new int[3], false, args.colorReplacement, false, args.fitzModifier); - if (generateCacheNativePtr == 0) { + if (generateCacheNativePtr == 0 && file != null) { file.delete(); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/RadialProgress2.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/RadialProgress2.java index f150e1281..4d26811f7 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/RadialProgress2.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/RadialProgress2.java @@ -58,6 +58,7 @@ public class RadialProgress2 { private int circleRadius; private boolean isPressed; private boolean isPressedMini; + public float overrideCircleAlpha = 1f; private int backgroundStroke; @@ -112,6 +113,10 @@ public class RadialProgress2 { invalidateParent(); } + public int getRadius() { + return circleRadius; + } + public void setBackgroundDrawable(Theme.MessageDrawable drawable) { mediaActionDrawable.setBackgroundDrawable(drawable); miniMediaActionDrawable.setBackgroundDrawable(drawable); @@ -174,6 +179,10 @@ public class RadialProgress2 { progressRect.set(left, top, right, bottom); } + public void setProgressRect(float left, float top, float right, float bottom) { + progressRect.set(left, top, right, bottom); + } + public RectF getProgressRect() { return progressRect; } @@ -365,7 +374,7 @@ public class RadialProgress2 { } int originalAlpha = circlePaint.getAlpha(); - circlePaint.setAlpha((int) (originalAlpha * wholeAlpha * overrideAlpha)); + circlePaint.setAlpha((int) (originalAlpha * wholeAlpha * overrideAlpha * overrideCircleAlpha)); originalAlpha = circleMiniPaint.getAlpha(); circleMiniPaint.setAlpha((int) (originalAlpha * wholeAlpha * overrideAlpha)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ReactedUsersListView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ReactedUsersListView.java index 04507d8b0..bfd825657 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ReactedUsersListView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ReactedUsersListView.java @@ -36,6 +36,7 @@ import org.telegram.messenger.SvgHelper; import org.telegram.messenger.UserObject; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.SimpleTextView; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.Reactions.ReactionsLayoutInBubble; @@ -369,7 +370,7 @@ public class ReactedUsersListView extends FrameLayout { private final class ReactedUserHolderView extends FrameLayout { BackupImageView avatarView; - TextView titleView; + SimpleTextView titleView; BackupImageView reactView; AvatarDrawable avatarDrawable = new AvatarDrawable(); View overlaySelectorView; @@ -382,13 +383,14 @@ public class ReactedUsersListView extends FrameLayout { avatarView.setRoundRadius(AndroidUtilities.dp(32)); addView(avatarView, LayoutHelper.createFrameRelatively(36, 36, Gravity.START | Gravity.CENTER_VERTICAL, 8, 0, 0, 0)); - titleView = new TextView(context); - titleView.setLines(1); - titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + titleView = new SimpleTextView(context); + titleView.setTextSize(16); titleView.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSubmenuItem)); - titleView.setEllipsize(TextUtils.TruncateAt.END); + titleView.setEllipsizeByGradient(true); titleView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - addView(titleView, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 58, 0, 44, 0)); + titleView.setWidthWrapContent(true); + titleView.setPadding(0, AndroidUtilities.dp(12), 0, AndroidUtilities.dp(12)); + addView(titleView, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 58, 0, 36 + 6, 0)); reactView = new BackupImageView(context); addView(reactView, LayoutHelper.createFrameRelatively(24, 24, Gravity.END | Gravity.CENTER_VERTICAL, 0, 0, 12, 0)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Reactions/CustomEmojiReactionsWindow.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Reactions/CustomEmojiReactionsWindow.java index bc754f9bd..76c4318d6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Reactions/CustomEmojiReactionsWindow.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Reactions/CustomEmojiReactionsWindow.java @@ -23,8 +23,6 @@ import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; -import com.google.android.exoplayer2.util.Log; - import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.LocaleController; @@ -87,7 +85,9 @@ public class CustomEmojiReactionsWindow { @Override public boolean dispatchKeyEvent(KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_UP && event.getKeyCode() == KeyEvent.KEYCODE_BACK) { - dismiss(); + if (enterTransitionFinished) { + dismiss(); + } return true; } return super.dispatchKeyEvent(event); @@ -108,7 +108,11 @@ public class CustomEmojiReactionsWindow { } }; - windowView.setOnClickListener(v -> dismiss()); + windowView.setOnClickListener(v -> { + if (enterTransitionFinished) { + dismiss(); + } + }); // sizeNotifierFrameLayout.setFitsSystemWindows(true); @@ -223,6 +227,7 @@ public class CustomEmojiReactionsWindow { } int[] location = new int[2]; + int animationIndex; private void createTransition(boolean enter) { fromRect.set(reactionsContainerLayout.rect); @@ -233,7 +238,7 @@ public class CustomEmojiReactionsWindow { reactionsContainerLayout.getLocationOnScreen(location); } windowView.getLocationOnScreen(windowLocation); - float y = location[1] - windowLocation[1] - AndroidUtilities.dp(44) - AndroidUtilities.dp(34); + float y = location[1] - windowLocation[1] - AndroidUtilities.dp(44); if (y + containerView.getMeasuredHeight() > windowView.getMeasuredHeight() - AndroidUtilities.dp(32)) { y = windowView.getMeasuredHeight() - AndroidUtilities.dp(32) - containerView.getMeasuredHeight(); } @@ -249,8 +254,6 @@ public class CustomEmojiReactionsWindow { containerView.setTranslationY(yTranslation); } - - Log.d("kek", "" + reactionsContainerLayout.rect.top + " " + location[1] + " " + windowLocation[1] + " " + containerView.getY()); fromRect.offset(location[0] - windowLocation[0] - containerView.getX(), location[1] - windowLocation[1] - containerView.getY()); reactionsContainerLayout.setCustomEmojiEnterProgress(enterTransitionProgress); @@ -258,6 +261,8 @@ public class CustomEmojiReactionsWindow { if (enter) { enterTransitionFinished = false; } + int account = UserConfig.selectedAccount; + animationIndex = NotificationCenter.getInstance(account).setAnimationInProgress(animationIndex, null); ValueAnimator valueAnimator = ValueAnimator.ofFloat(enterTransitionProgress, enter ? 1f : 0); valueAnimator.addUpdateListener(animation -> { enterTransitionProgress = (float) animation.getAnimatedValue(); @@ -265,9 +270,13 @@ public class CustomEmojiReactionsWindow { invalidatePath = true; containerView.invalidate(); }); + if (!enter) { + syncReactionFrames(enter); + } valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { + NotificationCenter.getInstance(account).onAnimationFinish(animationIndex); enterTransitionProgress = enter ? 1f : 0f; if (enter) { enterTransitionFinished = true; @@ -276,6 +285,9 @@ public class CustomEmojiReactionsWindow { containerView.invalidate(); } reactionsContainerLayout.setCustomEmojiEnterProgress(enterTransitionProgress); + if (enter) { + syncReactionFrames(enter); + } if (!enter) { reactionsContainerLayout.setSkipDraw(false); } @@ -290,6 +302,20 @@ public class CustomEmojiReactionsWindow { valueAnimator.start(); } + private void syncReactionFrames(boolean enter) { + HashMap transitionReactions = new HashMap<>(); + + for (int i = 0; i < selectAnimatedEmojiDialog.emojiGridView.getChildCount(); i++) { + if (selectAnimatedEmojiDialog.emojiGridView.getChildAt(i) instanceof SelectAnimatedEmojiDialog.ImageViewEmoji) { + SelectAnimatedEmojiDialog.ImageViewEmoji imageViewEmoji = (SelectAnimatedEmojiDialog.ImageViewEmoji) selectAnimatedEmojiDialog.emojiGridView.getChildAt(i); + if (imageViewEmoji.reaction != null) { + imageViewEmoji.notDraw = false; + imageViewEmoji.invalidate(); + } + } + } + } + public void removeView() { NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.startAllHeavyOperations, 7); AndroidUtilities.runOnUIThread(() -> { @@ -413,23 +439,32 @@ public class CustomEmojiReactionsWindow { float enterTransitionScalePx = 0; float enterTransitionScalePy = 0; - if (reactionsContainerLayout != null && enterTransitionProgress != 1f) { + if (reactionsContainerLayout != null) { for (int i = 0; i < selectAnimatedEmojiDialog.emojiGridView.getChildCount(); i++) { if (selectAnimatedEmojiDialog.emojiGridView.getChildAt(i) instanceof SelectAnimatedEmojiDialog.ImageViewEmoji) { SelectAnimatedEmojiDialog.ImageViewEmoji imageViewEmoji = (SelectAnimatedEmojiDialog.ImageViewEmoji) selectAnimatedEmojiDialog.emojiGridView.getChildAt(i); if (imageViewEmoji.reaction != null) { transitionReactions.put(imageViewEmoji.reaction, imageViewEmoji); - imageViewEmoji.notDraw = false; - imageViewEmoji.invalidate(); } } } - canvas.save(); + int restoreCount = canvas.save(); canvas.translate(drawingRect.left, drawingRect.top + reactionsContainerLayout.expandSize() * (1f - enterTransitionProgress)); + + float alpha = Math.max(selectAnimatedEmojiDialog.emojiGridView.getAlpha(), 1f - enterTransitionProgress); + if (alpha != 1f) { + canvas.saveLayerAlpha(0, 0, drawingRect.width(), drawingRect.height(), (int) (255 * alpha), Canvas.ALL_SAVE_FLAG); + } + int top = (int) (selectAnimatedEmojiDialog.getX() + selectAnimatedEmojiDialog.emojiGridView.getX()); + int left = (int) (selectAnimatedEmojiDialog.getY() + selectAnimatedEmojiDialog.emojiGridView.getY()); + canvas.clipRect(left, top + AndroidUtilities.dp(36) * enterTransitionProgress, left + selectAnimatedEmojiDialog.emojiGridView.getMeasuredHeight(), top + selectAnimatedEmojiDialog.emojiGridView.getMeasuredWidth()); for (int i = -1; i < reactionsContainerLayout.recyclerListView.getChildCount(); i++) { View child; + if (enterTransitionProgress == 1 && i == -1) { + continue; + } if (i == -1) { child = reactionsContainerLayout.nextRecentReaction; } else { @@ -455,14 +490,14 @@ public class CustomEmojiReactionsWindow { float scale = 1f; if (toImageView != null) { - float fromX = child.getX() + holderView.loopImageView.getX(); - float fromY = child.getY() + holderView.loopImageView.getY(); + float fromX = child.getX(); + float fromY = child.getY(); if (i == -1) { fromX -= reactionsContainerLayout.recyclerListView.getX(); fromY -= reactionsContainerLayout.recyclerListView.getY(); } - float toX = toImageView.getX() + selectAnimatedEmojiDialog.getX() + selectAnimatedEmojiDialog.emojiGridView.getX(); - float toY = toImageView.getY() + selectAnimatedEmojiDialog.getY() + selectAnimatedEmojiDialog.gridViewContainer.getY() + selectAnimatedEmojiDialog.emojiGridView.getY(); + float toX = toImageView.getX() + selectAnimatedEmojiDialog.getX() + selectAnimatedEmojiDialog.emojiGridView.getX() - holderView.loopImageView.getX() - AndroidUtilities.dp(1); + float toY = toImageView.getY() + selectAnimatedEmojiDialog.getY() + selectAnimatedEmojiDialog.gridViewContainer.getY() + selectAnimatedEmojiDialog.emojiGridView.getY() - holderView.loopImageView.getY(); float toImageViewSize = toImageView.getMeasuredWidth(); if (toImageView.selected) { float sizeAfterScale = toImageViewSize * (0.8f + 0.2f * 0.3f); @@ -497,10 +532,10 @@ public class CustomEmojiReactionsWindow { canvas.translate(child.getX() + holderView.loopImageView.getX(), child.getY() + holderView.loopImageView.getY()); } - if (holderView.loopImageView.getVisibility() == View.VISIBLE && toImageView != null && imageIsEquals(holderView.loopImageView, toImageView)) { + if (toImageView != null) { if (toImageView.selected) { - float cx = holderView.loopImageView.getMeasuredWidth() / 2f; - float cy = holderView.loopImageView.getMeasuredHeight() / 2f; + float cx = holderView.getMeasuredWidth() / 2f; + float cy = holderView.getMeasuredHeight() / 2f; float fromSize = holderView.getMeasuredWidth() - AndroidUtilities.dp(2); float toSize = toImageView.getMeasuredWidth() - AndroidUtilities.dp(2); float finalSize = AndroidUtilities.lerp(fromSize, toSize / scale, enterTransitionProgress); @@ -508,6 +543,7 @@ public class CustomEmojiReactionsWindow { float rectRadius = AndroidUtilities.lerp(fromSize / 2f, AndroidUtilities.dp(4), enterTransitionProgress); canvas.drawRoundRect(AndroidUtilities.rectTmp, rectRadius, rectRadius, selectAnimatedEmojiDialog.selectorPaint); } + holderView.drawSelected = false; if (fromRoundRadiusLb != 0 || toRoundRadiusLb != 0) { ImageReceiver imageReceiver = holderView.loopImageView.getImageReceiver(); if (holderView.loopImageView.animatedEmojiDrawable != null && holderView.loopImageView.animatedEmojiDrawable.getImageReceiver() != null) { @@ -523,18 +559,18 @@ public class CustomEmojiReactionsWindow { (int) AndroidUtilities.lerp(fromRoundRadiusRb, toRoundRadiusRb, enterTransitionProgress), (int) AndroidUtilities.lerp(fromRoundRadiusLb, toRoundRadiusLb, enterTransitionProgress) ); - holderView.loopImageView.draw(canvas); - holderView.loopImageView.draw(canvas); + holderView.draw(canvas); imageReceiver.setRoundRadius(radiusTmp); } else { - holderView.loopImageView.draw(canvas); + holderView.draw(canvas); } + holderView.drawSelected = true; if (!toImageView.notDraw) { toImageView.notDraw = true; toImageView.invalidate(); } } else { - if (holderView.hasEnterAnimation) { + if (holderView.hasEnterAnimation && holderView.loopImageView.getImageReceiver().getLottieAnimation() == null) { float oldAlpha = holderView.enterImageView.getImageReceiver().getAlpha(); holderView.enterImageView.getImageReceiver().setAlpha(oldAlpha * (1f - enterTransitionProgress)); holderView.enterImageView.draw(canvas); @@ -559,7 +595,7 @@ public class CustomEmojiReactionsWindow { } canvas.restore(); } - canvas.restore(); + canvas.restoreToCount(restoreCount); } if (invalidatePath) { @@ -588,8 +624,8 @@ public class CustomEmojiReactionsWindow { } private boolean imageIsEquals(BackupImageView loopImageView, SelectAnimatedEmojiDialog.ImageViewEmoji toImageView) { - if (toImageView.span == null) { - return toImageView.imageReceiver.getLottieAnimation() == loopImageView.getImageReceiver().getLottieAnimation(); + if (toImageView.span == null && loopImageView.getImageReceiver().getLottieAnimation() != null && toImageView.imageReceiver.getLottieAnimation() != null) { + return true; } if (loopImageView.animatedEmojiDrawable != null) { return toImageView.span.getDocumentId() == loopImageView.animatedEmojiDrawable.getDocumentId(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Reactions/ReactionsLayoutInBubble.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Reactions/ReactionsLayoutInBubble.java index e16b7641b..5e0c28988 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Reactions/ReactionsLayoutInBubble.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Reactions/ReactionsLayoutInBubble.java @@ -13,6 +13,7 @@ import android.view.ViewConfiguration; import androidx.core.graphics.ColorUtils; import androidx.recyclerview.widget.ChatListItemAnimator; +import org.checkerframework.checker.units.qual.A; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.DocumentObject; @@ -41,7 +42,7 @@ public class ReactionsLayoutInBubble { private final static int ANIMATION_TYPE_IN = 1; private final static int ANIMATION_TYPE_OUT = 2; private final static int ANIMATION_TYPE_MOVE = 3; - public boolean drawServiceShaderBackground; + public float drawServiceShaderBackground; private static Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); private static TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); @@ -265,7 +266,7 @@ public class ReactionsLayoutInBubble { width = maxWidth; } height = currentY + (reactionButtons.size() == 0 ? 0 : AndroidUtilities.dp(26)); - drawServiceShaderBackground = false; + drawServiceShaderBackground = 0f; } public void draw(Canvas canvas, float animationProgress, String drawOnlyReaction) { @@ -580,13 +581,13 @@ public class ReactionsLayoutInBubble { } AndroidUtilities.rectTmp.set(0, 0, w, height); float rad = height / 2f; - if (drawServiceShaderBackground) { + if (drawServiceShaderBackground > 0) { Paint paint1 = getThemedPaint(Theme.key_paint_chatActionBackground); Paint paint2 = Theme.chat_actionBackgroundGradientDarkenPaint; int oldAlpha = paint1.getAlpha(); int oldAlpha2 = paint2.getAlpha(); - paint1.setAlpha((int) (oldAlpha * alpha)); - paint2.setAlpha((int) (oldAlpha2 * alpha)); + paint1.setAlpha((int) (oldAlpha * alpha * drawServiceShaderBackground)); + paint2.setAlpha((int) (oldAlpha2 * alpha * drawServiceShaderBackground)); canvas.drawRoundRect(AndroidUtilities.rectTmp, rad, rad, paint1); if (hasGradientService()) { canvas.drawRoundRect(AndroidUtilities.rectTmp, rad, rad, paint2); @@ -594,7 +595,7 @@ public class ReactionsLayoutInBubble { paint1.setAlpha(oldAlpha); paint2.setAlpha(oldAlpha2); } - if (!drawServiceShaderBackground && drawOverlayScrim) { + if (drawServiceShaderBackground < 1 && drawOverlayScrim) { Theme.MessageDrawable messageBackground = parentView.getCurrentBackgroundDrawable(false); if (messageBackground != null) { canvas.drawRoundRect(AndroidUtilities.rectTmp, rad, rad, messageBackground.getPaint()); @@ -637,13 +638,8 @@ public class ReactionsLayoutInBubble { } private void updateColors(float progress) { - if (drawServiceShaderBackground) { - lastDrawnTextColor = ColorUtils.blendARGB(fromTextColor, serviceTextColor, progress); - lastDrawnBackgroundColor = ColorUtils.blendARGB(fromBackgroundColor, serviceBackgroundColor, progress); - } else { - lastDrawnTextColor = ColorUtils.blendARGB(fromTextColor, textColor, progress); - lastDrawnBackgroundColor = ColorUtils.blendARGB(fromBackgroundColor, backgroundColor, progress); - } + lastDrawnTextColor = ColorUtils.blendARGB(fromTextColor, ColorUtils.blendARGB(textColor, serviceTextColor, drawServiceShaderBackground), progress); + lastDrawnBackgroundColor = ColorUtils.blendARGB(fromBackgroundColor, ColorUtils.blendARGB(backgroundColor, serviceBackgroundColor, drawServiceShaderBackground), progress); } private void drawImage(Canvas canvas, float alpha) { @@ -699,6 +695,7 @@ public class ReactionsLayoutInBubble { avatarsDarawable.setSize(AndroidUtilities.dp(20)); avatarsDarawable.width = AndroidUtilities.dp(100); avatarsDarawable.height = height; + avatarsDarawable.setAvatarsTextSize(AndroidUtilities.dp(22)); if (attached) { avatarsDarawable.onAttachedToWindow(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ReactionsContainerLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ReactionsContainerLayout.java index a4f705b60..8821c616c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ReactionsContainerLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ReactionsContainerLayout.java @@ -159,7 +159,7 @@ public class ReactionsContainerLayout extends FrameLayout implements Notificatio this.currentAccount = currentAccount; this.fragment = fragment; - nextRecentReaction = new ReactionHolderView(context); + nextRecentReaction = new ReactionHolderView(context, false); nextRecentReaction.setVisibility(View.GONE); nextRecentReaction.touchable = false; nextRecentReaction.pressedBackupImageView.setVisibility(View.GONE); @@ -287,7 +287,7 @@ public class ReactionsContainerLayout extends FrameLayout implements Notificatio switch (viewType) { default: case 0: - view = new ReactionHolderView(context); + view = new ReactionHolderView(context, true); break; case 1: premiumLockContainer = new FrameLayout(context); @@ -1167,6 +1167,7 @@ public class ReactionsContainerLayout extends FrameLayout implements Notificatio } public final class ReactionHolderView extends FrameLayout { + private final boolean recyclerReaction; public BackupImageView enterImageView; public BackupImageView loopImageView; public BackupImageView pressedBackupImageView; @@ -1177,6 +1178,7 @@ public class ReactionsContainerLayout extends FrameLayout implements Notificatio public boolean shouldSwitchToLoopView; public boolean switchedToLoopView; public boolean selected; + public boolean drawSelected = true; public int position; Runnable playRunnable = new Runnable() { @@ -1202,13 +1204,17 @@ public class ReactionsContainerLayout extends FrameLayout implements Notificatio } } - ReactionHolderView(Context context) { + ReactionHolderView(Context context, boolean recyclerReaction) { super(context); + this.recyclerReaction = recyclerReaction; enterImageView = new BackupImageView(context) { @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); + if (imageReceiver.getLottieAnimation() != null) { + imageReceiver.getLottieAnimation().start(); + } if (shouldSwitchToLoopView && !switchedToLoopView && imageReceiver.getLottieAnimation() != null && imageReceiver.getLottieAnimation().isLastFrame() && loopImageView.imageReceiver.getLottieAnimation() != null && loopImageView.imageReceiver.getLottieAnimation().hasBitmap()) { switchedToLoopView = true; loopImageView.imageReceiver.getLottieAnimation().setCurrentFrame(0, false, true); @@ -1217,6 +1223,7 @@ public class ReactionsContainerLayout extends FrameLayout implements Notificatio enterImageView.setVisibility(View.INVISIBLE); }); } + invalidate(); } @Override @@ -1255,29 +1262,38 @@ public class ReactionsContainerLayout extends FrameLayout implements Notificatio resetAnimation(); currentReaction = react; selected = selectedReactions.contains(react); + hasEnterAnimation = false;//currentReaction.emojicon != null && (!showCustomEmojiReaction() || allReactionsIsDefault); if (currentReaction.emojicon != null) { TLRPC.TL_availableReaction defaultReaction = MediaDataController.getInstance(currentAccount).getReactionsMap().get(currentReaction.emojicon); if (defaultReaction != null) { + if (recyclerReaction) { + loopImageView.getImageReceiver().setUniqKeyPrefix("r"); + } SvgHelper.SvgDrawable svgThumb = DocumentObject.getSvgThumb(defaultReaction.activate_animation, Theme.key_windowBackgroundGray, 1.0f); enterImageView.getImageReceiver().setImage(ImageLocation.getForDocument(defaultReaction.appear_animation), ReactionsUtils.APPEAR_ANIMATION_FILTER, null, null, svgThumb, 0, "tgs", react, 0); pressedBackupImageView.getImageReceiver().setImage(ImageLocation.getForDocument(defaultReaction.select_animation), ReactionsUtils.SELECT_ANIMATION_FILTER, null, null, svgThumb, 0, "tgs", react, 0); - loopImageView.getImageReceiver().setImage(ImageLocation.getForDocument(defaultReaction.select_animation), ReactionsUtils.SELECT_ANIMATION_FILTER, null, null, null, 0, "tgs", currentReaction, 0); + loopImageView.getImageReceiver().setImage(ImageLocation.getForDocument(defaultReaction.select_animation), ReactionsUtils.SELECT_ANIMATION_FILTER, null, null, hasEnterAnimation ? null : svgThumb, 0, "tgs", currentReaction, 0); loopImageView.setAnimatedEmojiDrawable(null); + pressedBackupImageView.setAnimatedEmojiDrawable(null); + if (enterImageView.getImageReceiver().getLottieAnimation() != null) { + enterImageView.getImageReceiver().getLottieAnimation().setCurrentFrame(0, false); + } } } else { pressedBackupImageView.getImageReceiver().clearImage(); loopImageView.getImageReceiver().clearImage(); pressedBackupImageView.setAnimatedEmojiDrawable(new AnimatedEmojiDrawable(AnimatedEmojiDrawable.CACHE_TYPE_ALERT_PREVIEW_LARGE, currentAccount, currentReaction.documentId)); loopImageView.setAnimatedEmojiDrawable(new AnimatedEmojiDrawable(AnimatedEmojiDrawable.CACHE_TYPE_ALERT_PREVIEW, currentAccount, currentReaction.documentId)); - } + enterImageView.setLayerNum(Integer.MAX_VALUE); + loopImageView.setLayerNum(Integer.MAX_VALUE); setFocusable(true); - hasEnterAnimation = currentReaction.emojicon != null && (!showCustomEmojiReaction() || allReactionsIsDefault); shouldSwitchToLoopView = hasEnterAnimation && showCustomEmojiReaction(); if (!hasEnterAnimation) { enterImageView.setVisibility(View.GONE); loopImageView.setVisibility(View.VISIBLE); + switchedToLoopView = true; } else { switchedToLoopView = false; enterImageView.setVisibility(View.VISIBLE); @@ -1417,7 +1433,7 @@ public class ReactionsContainerLayout extends FrameLayout implements Notificatio @Override protected void dispatchDraw(Canvas canvas) { - if (selected) { + if (selected && drawSelected) { canvas.drawCircle(getMeasuredWidth() >> 1, getMeasuredHeight() >> 1, (getMeasuredWidth() >> 1) - AndroidUtilities.dp(1), selectedPaint); } if (loopImageView.animatedEmojiDrawable != null && loopImageView.animatedEmojiDrawable.getImageReceiver() != null) { @@ -1570,12 +1586,11 @@ public class ReactionsContainerLayout extends FrameLayout implements Notificatio int color = ColorUtils.blendARGB(Theme.getColor(Theme.key_actionBarDefaultSubmenuItemIcon, resourcesProvider), Theme.getColor(Theme.key_dialogBackground, resourcesProvider), 0.7f); backgroundPaint.setColor(color); - int cy = getMeasuredHeight() >> 1; - int cx = getMeasuredWidth() >> 1; + float cy = getMeasuredHeight() / 2f; + float cx = getMeasuredWidth() / 2f; View child = getChildAt(0); - int sizeHalf = (getMeasuredWidth() - AndroidUtilities.dp(6)) >> 1; - float pullingLeftOffsetProgress = getPullingLeftProgress(); + float sizeHalf = (getMeasuredWidth() - AndroidUtilities.dpf2(6)) / 2f; float expandSize = expandSize(); AndroidUtilities.rectTmp.set(cx - sizeHalf, cy - sizeHalf - expandSize, cx + sizeHalf, cy + sizeHalf + expandSize); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java index b81f3cee3..590e7c779 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java @@ -47,6 +47,7 @@ import androidx.annotation.IntDef; import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; import androidx.core.util.Consumer; +import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -63,6 +64,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; +import java.util.Objects; public class RecyclerListView extends RecyclerView { public final static int SECTIONS_TYPE_SIMPLE = 0, @@ -79,6 +81,9 @@ public class RecyclerListView extends RecyclerView { }) public @interface SectionsType {} + public final static int EMPTY_VIEW_ANIMATION_TYPE_ALPHA_SCALE = 1; + public final static int EMPTY_VIEW_ANIMATION_TYPE_ALPHA = 0; + private OnItemClickListener onItemClickListener; private OnItemClickListenerExtended onItemClickListenerExtended; private OnItemLongClickListener onItemLongClickListener; @@ -121,6 +126,7 @@ public class RecyclerListView extends RecyclerView { private int selectorType = 2; protected Drawable selectorDrawable; protected int selectorPosition; + protected View selectorView; protected android.graphics.Rect selectorRect = new android.graphics.Rect(); private boolean isChildViewEnabled; @@ -277,6 +283,8 @@ public class RecyclerListView extends RecyclerView { private int sectionCount; private int count; + private ArrayList hashes = new ArrayList<>(); + private void cleanupCache() { if (sectionCache == null) { sectionCache = new SparseIntArray(); @@ -302,8 +310,7 @@ public class RecyclerListView extends RecyclerView { @Override public void notifyDataSetChanged() { - cleanupCache(); - super.notifyDataSetChanged(); + update(false); } @Override @@ -392,6 +399,46 @@ public class RecyclerListView extends RecyclerView { return -1; } + public void update(boolean diff) { + cleanupCache(); + + ArrayList oldHashes = new ArrayList<>(hashes); + hashes.clear(); + + for (int i = 0, N = internalGetSectionCount(); i < N; i++) { + int count = internalGetCountForSection(i); + for (int j = 0; j < count; ++j) { + hashes.add(Objects.hash(i * -49612, getItem(i, j))); + } + } + + if (diff) { + DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return oldHashes.size(); + } + + @Override + public int getNewListSize() { + return hashes.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + return Objects.equals(oldHashes.get(oldItemPosition), hashes.get(newItemPosition)); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + return areItemsTheSame(oldItemPosition, newItemPosition); + } + }, true).dispatchUpdatesTo(this); + } else { + super.notifyDataSetChanged(); + } + } + public abstract int getSectionCount(); public abstract int getCountForSection(int section); @@ -1686,6 +1733,23 @@ public class RecyclerListView extends RecyclerView { return onItemClickListener; } + public void clickItem(View item, int position) { + if (onItemClickListener != null) { + onItemClickListener.onItemClick(item, position); + } else if (onItemClickListenerExtended != null) { + onItemClickListenerExtended.onItemClick(item, position, 0, 0); + } + } + + public boolean longClickItem(View item, int position) { + if (onItemLongClickListener != null) { + return onItemLongClickListener.onItemClick(item, position); + } else if (onItemLongClickListenerExtended != null) { + return onItemLongClickListenerExtended.onItemClick(item, position, 0, 0); + } + return false; + } + public void setOnItemLongClickListener(OnItemLongClickListener listener) { setOnItemLongClickListener(listener, ViewConfiguration.getLongPressTimeout()); } @@ -1989,6 +2053,13 @@ public class RecyclerListView extends RecyclerView { positionSelector(position, sel, false, -1, -1); } + public void updateSelector() { + if (selectorPosition != NO_POSITION && selectorView != null) { + positionSelector(selectorPosition, selectorView); + invalidate(); + } + } + private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) { if (removeHighlighSelectionRunnable != null) { AndroidUtilities.cancelRunOnUIThread(removeHighlighSelectionRunnable); @@ -2008,6 +2079,7 @@ public class RecyclerListView extends RecyclerView { if (position != NO_POSITION) { selectorPosition = position; } + selectorView = sel; if (selectorType == 8) { Theme.setMaskDrawableRad(selectorDrawable, selectorRadius, 0); } else if (topBottomSelectorRadius > 0 && getAdapter() != null) { @@ -2134,6 +2206,7 @@ public class RecyclerListView extends RecyclerView { } currentFirst = -1; selectorPosition = NO_POSITION; + selectorView = null; selectorRect.setEmpty(); pinnedHeader = null; if (adapter instanceof SectionsAdapter) { @@ -2311,10 +2384,19 @@ public class RecyclerListView extends RecyclerView { } } + public void relayoutPinnedHeader() { + if (pinnedHeader != null) { + pinnedHeader.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.UNSPECIFIED)); + pinnedHeader.layout(0, 0, pinnedHeader.getMeasuredWidth(), pinnedHeader.getMeasuredHeight()); + invalidate(); + } + } + @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); selectorPosition = NO_POSITION; + selectorView = null; selectorRect.setEmpty(); if (itemsEnterAnimator != null) { itemsEnterAnimator.onDetached(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ReplaceableIconDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ReplaceableIconDrawable.java index 1e5502635..6f9cab685 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ReplaceableIconDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ReplaceableIconDrawable.java @@ -1,9 +1,6 @@ package org.telegram.ui.Components; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; @@ -12,13 +9,14 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.View; -import android.widget.ImageView; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; +import java.util.ArrayList; + public class ReplaceableIconDrawable extends Drawable implements Animator.AnimatorListener { private Context context; @@ -30,6 +28,7 @@ public class ReplaceableIconDrawable extends Drawable implements Animator.Animat private ValueAnimator animation; private float progress = 1f; + ArrayList parentViews = new ArrayList<>(); public ReplaceableIconDrawable(Context context) { this.context = context; @@ -198,4 +197,18 @@ public class ReplaceableIconDrawable extends Drawable implements Animator.Animat public void onAnimationRepeat(Animator animation) { } + + public void addView(View view) { + parentViews.add(view); + } + + @Override + public void invalidateSelf() { + super.invalidateSelf(); + if (parentViews != null) { + for (int i = 0; i < parentViews.size(); i++) { + parentViews.get(i).invalidate(); + } + } + } } \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/RoundVideoPlayingDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/RoundVideoPlayingDrawable.java index 3e4dc417d..7c0499ed1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/RoundVideoPlayingDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/RoundVideoPlayingDrawable.java @@ -15,6 +15,8 @@ import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.view.View; +import androidx.core.graphics.ColorUtils; + import org.telegram.messenger.AndroidUtilities; import org.telegram.ui.ActionBar.Theme; @@ -30,6 +32,8 @@ public class RoundVideoPlayingDrawable extends Drawable { private int progress2Direction = 1; private int progress3Direction = 1; private View parentView; + public float colorProgress; + public int timeColor; int alpha = 255; private final Theme.ResourcesProvider resourcesProvider; @@ -95,7 +99,7 @@ public class RoundVideoPlayingDrawable extends Drawable { @Override public void draw(Canvas canvas) { - paint.setColor(getThemedColor(Theme.key_chat_serviceText)); + paint.setColor(ColorUtils.blendARGB(getThemedColor(Theme.key_chat_serviceText), timeColor, colorProgress)); if (alpha != 255) { paint.setAlpha((int) (alpha * (paint.getAlpha() / 255f))); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SearchDownloadsContainer.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SearchDownloadsContainer.java index e4572713b..4feb1e632 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SearchDownloadsContainer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SearchDownloadsContainer.java @@ -155,7 +155,7 @@ public class SearchDownloadsContainer extends FrameLayout implements Notificatio ArrayList documents = new ArrayList<>(); documents.add(message); PhotoViewer.getInstance().setParentActivity(parentFragment); - PhotoViewer.getInstance().openPhoto(documents, 0, 0, 0, new PhotoViewer.EmptyPhotoViewerProvider()); + PhotoViewer.getInstance().openPhoto(documents, 0, 0, 0, 0, new PhotoViewer.EmptyPhotoViewerProvider()); return; } AndroidUtilities.openDocument(message, parentActivity, parentFragment); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SearchViewPager.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SearchViewPager.java index f12b33e02..8516283d5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SearchViewPager.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SearchViewPager.java @@ -164,7 +164,7 @@ public class SearchViewPager extends ViewPagerFixed implements FilteredSearchVie searchListView.setInstantClick(true); searchListView.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); searchListView.setLayoutManager(searchLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); - searchListView.setAnimateEmptyView(true, 0); + searchListView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); searchListView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { @@ -447,9 +447,9 @@ public class SearchViewPager extends ViewPagerFixed implements FilteredSearchVie showActionMode(false); - if (dids.size() > 1 || dids.get(0) == AccountInstance.getInstance(currentAccount).getUserConfig().getClientUserId() || message != null) { + if (dids.size() > 1 || dids.get(0).dialogId == AccountInstance.getInstance(currentAccount).getUserConfig().getClientUserId() || message != null) { for (int a = 0; a < dids.size(); a++) { - long did = dids.get(a); + long did = dids.get(a).dialogId; if (message != null) { AccountInstance.getInstance(currentAccount).getSendMessagesHelper().sendMessage(message.toString(), did, null, null, null, true, null, null, null, true, 0, null, false); } @@ -457,7 +457,7 @@ public class SearchViewPager extends ViewPagerFixed implements FilteredSearchVie } fragment1.finishFragment(); } else { - long did = dids.get(0); + long did = dids.get(0).dialogId; Bundle args1 = new Bundle(); args1.putBoolean("scrollToTopOnResume", true); if (DialogObject.isEncryptedDialog(did)) { @@ -706,7 +706,7 @@ public class SearchViewPager extends ViewPagerFixed implements FilteredSearchVie for (int i = 0; i < n; i++) { View v = viewsByType.valueAt(i); if (v instanceof FilteredSearchView) { - ((FilteredSearchView) v).messagesDeleted(channelId, markAsDeletedMessages); + ((FilteredSearchView) v).messagesDeleted(channelId, markAsDeletedMessages); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBar.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBar.java index ed1478bb2..0f2c4896b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBar.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBar.java @@ -16,6 +16,7 @@ import android.view.MotionEvent; import android.view.View; import org.telegram.messenger.AndroidUtilities; +import org.telegram.ui.ActionBar.Theme; public class SeekBar { @@ -24,10 +25,14 @@ public class SeekBar { default void onSeekBarContinuousDrag(float progress) { } + + default void onSeekBarPressed() {} + default void onSeekBarReleased() {} } private static Paint paint; private static int thumbWidth; + private float thumbProgress; private int thumbX = 0; private int draggingThumbX = 0; private int thumbDX = 0; @@ -47,6 +52,7 @@ public class SeekBar { private float currentRadius; private long lastUpdateTime; private View parentView; + private float alpha = 1f; public SeekBar(View parent) { if (paint == null) { @@ -112,8 +118,13 @@ public class SeekBar { backgroundSelectedColor = selected; } + public void setAlpha(float alpha) { + this.alpha = alpha; + } + public void setProgress(float progress) { - thumbX = (int) Math.ceil((width - thumbWidth) * progress); + thumbProgress = progress; + thumbX = (int) Math.ceil((width - thumbWidth) * thumbProgress); if (thumbX < 0) { thumbX = 0; } else if (thumbX > width - thumbWidth) { @@ -144,6 +155,7 @@ public class SeekBar { public void setSize(int w, int h) { width = w; height = h; + setProgress(thumbProgress); } public int getWidth() { @@ -155,6 +167,12 @@ public class SeekBar { } public void draw(Canvas canvas) { + if (alpha <= 0) { + return; + } + if (alpha < 1) { + canvas.saveLayerAlpha(0, 0, width, height, (int) (255 * alpha), Canvas.ALL_SAVE_FLAG); + } rect.set(thumbWidth / 2, height / 2 - lineHeight / 2, width - thumbWidth / 2, height / 2 + lineHeight / 2); paint.setColor(selected ? backgroundSelectedColor : backgroundColor); canvas.drawRoundRect(rect, thumbWidth / 2, thumbWidth / 2, paint); @@ -192,5 +210,9 @@ public class SeekBar { } canvas.drawCircle((pressed ? draggingThumbX : thumbX) + thumbWidth / 2, height / 2, currentRadius, paint); + + if (alpha < 1) { + canvas.restore(); + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarWaveform.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarWaveform.java index 67051aa7a..ce66c1e6e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarWaveform.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarWaveform.java @@ -21,10 +21,10 @@ import android.view.View; import androidx.core.graphics.ColorUtils; import androidx.core.math.MathUtils; + import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.MediaController; import org.telegram.messenger.MessageObject; -import org.telegram.messenger.MessagesController; import java.util.ArrayList; @@ -50,6 +50,7 @@ public class SeekBarWaveform { private int innerColor; private int outerColor; private int selectedColor; + private float alpha = 1f; private float clearProgress = 1f; private boolean isUnread; @@ -106,6 +107,10 @@ public class SeekBarWaveform { messageObject = object; } + public void setAlpha(float alpha) { + this.alpha = alpha; + } + public void setParentView(View view) { parentView = view; loadingFloat.setParent(view); @@ -123,6 +128,7 @@ public class SeekBarWaveform { pressed = true; thumbDX = (int) (x - thumbX); startDraging = false; + delegate.onSeekBarPressed(); return true; } } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { @@ -131,6 +137,7 @@ public class SeekBarWaveform { delegate.onSeekBarDrag((float) thumbX / (float) width); } pressed = false; + delegate.onSeekBarReleased(); return true; } } else if (action == MotionEvent.ACTION_MOVE) { @@ -190,6 +197,7 @@ public class SeekBarWaveform { } public void setSize(int w, int h, int fromW, int toW) { + int wasWidth = width; width = w; height = h; if (heights == null || heights.length != (int) (width / AndroidUtilities.dpf2(3))) { @@ -260,7 +268,7 @@ public class SeekBarWaveform { } public void draw(Canvas canvas, View parentView) { - if (waveformBytes == null || width == 0) { + if (waveformBytes == null || width == 0 || alpha <= 0) { return; } float totalBarsCount = width / AndroidUtilities.dpf2(3); @@ -330,13 +338,13 @@ public class SeekBarWaveform { if (alpha > 0) { canvas.save(); canvas.clipPath(alphaPath); - drawFill(canvas, alpha); + drawFill(canvas, alpha * this.alpha); canvas.restore(); } canvas.save(); canvas.clipPath(path); - drawFill(canvas, 1f); + drawFill(canvas, this.alpha); canvas.restore(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SenderSelectPopup.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SenderSelectPopup.java index 861152453..0ba5302fb 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SenderSelectPopup.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SenderSelectPopup.java @@ -325,7 +325,11 @@ public class SenderSelectPopup extends ActionBarPopupWindow { bulletinContainer.animate().alpha(0).setDuration(150).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - windowManager.removeViewImmediate(bulletinContainer); + try { + windowManager.removeViewImmediate(bulletinContainer); + } catch (Exception e) { + + } if (bulletinHideCallback != null) { AndroidUtilities.cancelRunOnUIThread(bulletinHideCallback); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java index 42627f167..b2cc752bb 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java @@ -52,8 +52,12 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.collection.LongSparseArray; import androidx.core.view.ViewCompat; +import androidx.dynamicanimation.animation.FloatValueHolder; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -82,6 +86,7 @@ import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.NativeByteBuffer; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenuSubItem; import org.telegram.ui.ActionBar.ActionBarPopupWindow; import org.telegram.ui.ActionBar.AdjustPanLayoutHelper; @@ -94,6 +99,7 @@ import org.telegram.ui.Adapters.SearchAdapterHelper; import org.telegram.ui.Cells.GraySectionCell; import org.telegram.ui.Cells.HintDialogCell; import org.telegram.ui.Cells.ShareDialogCell; +import org.telegram.ui.Cells.ShareTopicCell; import org.telegram.ui.ChatActivity; import org.telegram.ui.DialogsActivity; import org.telegram.ui.LaunchActivity; @@ -101,8 +107,11 @@ import org.telegram.ui.MessageStatisticActivity; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; public class ShareAlert extends BottomSheet implements NotificationCenter.NotificationCenterDelegate { @@ -114,11 +123,14 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi private TextView pickerBottomLayout; private LinearLayout sharesCountLayout; private AnimatorSet animatorSet; + private RecyclerListView topicsGridView; private RecyclerListView gridView; private RecyclerListView searchGridView; private GridLayoutManager layoutManager; + private GridLayoutManager topicsLayoutManager; private FillLastGridLayoutManager searchLayoutManager; private ShareDialogsAdapter listAdapter; + private ShareTopicsAdapter shareTopicsAdapter; private ShareSearchAdapter searchAdapter; protected ArrayList sendingMessageObjects; private String[] sendingText = new String[2]; @@ -128,6 +140,7 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi private View[] shadow = new View[2]; private AnimatorSet[] shadowAnimation = new AnimatorSet[2]; protected LongSparseArray selectedDialogs = new LongSparseArray<>(); + protected Map selectedDialogTopics = new HashMap<>(); private SwitchView switchView; private int containerViewTop = -1; private boolean fullyShown = false; @@ -162,8 +175,12 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi RecyclerItemsEnterAnimator recyclerItemsEnterAnimator; SearchField searchView; + ActionBar topicsBackActionBar; private boolean updateSearchAdapter; + private SpringAnimation topicsAnimation; + private TLRPC.Dialog selectedTopicDialog; + private ArrayList recentSearchObjects = new ArrayList<>(); private LongSparseArray recentSearchObjectsById = new LongSparseArray<>(); private final Theme.ResourcesProvider resourcesProvider; @@ -642,10 +659,17 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi int size = Math.max(searchAdapter.getItemCount(), listAdapter.getItemCount() - 1); int contentSize = AndroidUtilities.dp(103) + AndroidUtilities.dp(48) + Math.max(2, (int) Math.ceil(size / 4.0f)) * AndroidUtilities.dp(103) + backgroundPaddingTop; + if (topicsGridView.getVisibility() != View.GONE) { + int topicsSize = AndroidUtilities.dp(103) + AndroidUtilities.dp(48) + Math.max(2, (int) Math.ceil((shareTopicsAdapter.getItemCount() - 1) / 4.0f)) * AndroidUtilities.dp(103) + backgroundPaddingTop; + if (topicsSize > contentSize) { + contentSize = AndroidUtilities.lerp(contentSize, topicsSize, topicsGridView.getAlpha()); + } + } int padding = (contentSize < availableHeight ? 0 : availableHeight - (availableHeight / 5 * 3)) + AndroidUtilities.dp(8); if (gridView.getPaddingTop() != padding) { ignoreLayout = true; gridView.setPadding(0, padding, 0, AndroidUtilities.dp(48)); + topicsGridView.setPadding(0, padding, 0, AndroidUtilities.dp(48)); ignoreLayout = false; } @@ -929,6 +953,92 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi searchView = new SearchField(context); frameLayout.addView(searchView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 58, Gravity.BOTTOM | Gravity.LEFT)); + topicsBackActionBar = new ActionBar(context); + topicsBackActionBar.setOccupyStatusBar(false); + topicsBackActionBar.setBackButtonImage(R.drawable.ic_ab_back); + topicsBackActionBar.setTitleColor(getThemedColor(Theme.key_dialogTextBlack)); + topicsBackActionBar.setSubtitleColor(getThemedColor(Theme.key_dialogTextGray2)); + topicsBackActionBar.setItemsColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2), false); + topicsBackActionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_actionBarWhiteSelector), false); + topicsBackActionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + onBackPressed(); + } + }); + topicsBackActionBar.setVisibility(View.GONE); + frameLayout.addView(topicsBackActionBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 58, Gravity.BOTTOM | Gravity.LEFT)); + + topicsGridView = new RecyclerListView(context, resourcesProvider); + topicsGridView.setLayoutManager(topicsLayoutManager = new GridLayoutManager(context, 4)); + topicsLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { + @Override + public int getSpanSize(int position) { + if (position == 0) { + return topicsLayoutManager.getSpanCount(); + } + return 1; + } + }); + topicsGridView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + if (dy != 0) { + updateLayout(); + previousScrollOffsetY = scrollOffsetY; + } + } + }); + topicsGridView.setAdapter(shareTopicsAdapter = new ShareTopicsAdapter(context)); + topicsGridView.setGlowColor(getThemedColor(darkTheme ? Theme.key_voipgroup_inviteMembersBackground : Theme.key_dialogScrollGlow)); + topicsGridView.setVerticalScrollBarEnabled(false); + topicsGridView.setHorizontalScrollBarEnabled(false); + topicsGridView.setOverScrollMode(View.OVER_SCROLL_NEVER); + topicsGridView.setSelectorDrawableColor(0); + topicsGridView.setPadding(0, 0, 0, AndroidUtilities.dp(48)); + topicsGridView.setClipToPadding(false); + topicsGridView.addItemDecoration(new RecyclerView.ItemDecoration() { + @Override + public void getItemOffsets(@NonNull android.graphics.Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { + RecyclerListView.Holder holder = (RecyclerListView.Holder) parent.getChildViewHolder(view); + if (holder != null) { + int pos = holder.getAdapterPosition(); + outRect.left = pos % 4 == 0 ? 0 : AndroidUtilities.dp(4); + outRect.right = pos % 4 == 3 ? 0 : AndroidUtilities.dp(4); + } else { + outRect.left = AndroidUtilities.dp(4); + outRect.right = AndroidUtilities.dp(4); + } + } + }); + topicsGridView.setOnItemClickListener((view, position) -> { + TLRPC.TL_forumTopic topic = shareTopicsAdapter.getItem(position); + if (topic == null || selectedTopicDialog == null) { + return; + } + + for (int i = 0; i < gridView.getChildCount(); i++) { + View child = gridView.getChildAt(i); + + if (child instanceof ShareDialogCell && ((ShareDialogCell) child).getCurrentDialog() == selectedTopicDialog.id) { + ShareDialogCell cell = (ShareDialogCell) child; + + long dialogId = ((ShareDialogCell) child).getCurrentDialog(); + TLRPC.Dialog dialog = listAdapter.dialogsMap.get(dialogId); + + selectedDialogs.put(dialogId, dialog); + selectedDialogTopics.put(dialog, topic); + if (cell != null) { + cell.setTopic(topic, true); + cell.setChecked(true, true); + } + updateSelectedCount(2); + } + } + collapseTopics(); + }); + topicsGridView.setVisibility(View.GONE); + containerView.addView(topicsGridView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); gridView = new RecyclerListView(context, resourcesProvider) { @@ -936,6 +1046,18 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi protected boolean allowSelectChildAtPosition(float x, float y) { return y >= AndroidUtilities.dp(darkTheme && linkToCopy[1] != null ? 111 : 58) + (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); } + + @Override + public void draw(Canvas canvas) { + if (topicsGridView.getVisibility() != View.GONE) { + canvas.save(); + canvas.clipRect(0, scrollOffsetY + AndroidUtilities.dp(darkTheme && linkToCopy[1] != null ? 111 : 58), getWidth(), getHeight()); + } + super.draw(canvas); + if (topicsGridView.getVisibility() != View.GONE) { + canvas.restore(); + } + } }; gridView.setSelectorDrawableColor(0); gridView.setPadding(0, 0, 0, AndroidUtilities.dp(48)); @@ -952,6 +1074,7 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi }); gridView.setHorizontalScrollBarEnabled(false); gridView.setVerticalScrollBarEnabled(false); + gridView.setOverScrollMode(View.OVER_SCROLL_NEVER); gridView.addItemDecoration(new RecyclerView.ItemDecoration() { @Override public void getItemOffsets(android.graphics.Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { @@ -989,13 +1112,24 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi } }); - searchGridView = new RecyclerListView(context, resourcesProvider) { @Override protected boolean allowSelectChildAtPosition(float x, float y) { return y >= AndroidUtilities.dp(darkTheme && linkToCopy[1] != null ? 111 : 58) + (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); } + + @Override + public void draw(Canvas canvas) { + if (topicsGridView.getVisibility() != View.GONE) { + canvas.save(); + canvas.clipRect(0, scrollOffsetY + AndroidUtilities.dp(darkTheme && linkToCopy[1] != null ? 111 : 58), getWidth(), getHeight()); + } + super.draw(canvas); + if (topicsGridView.getVisibility() != View.GONE) { + canvas.restore(); + } + } }; searchGridView.setSelectorDrawableColor(0); searchGridView.setPadding(0, 0, 0, AndroidUtilities.dp(48)); @@ -1064,7 +1198,7 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi searchEmptyView.title.setText(LocaleController.getString("NoResult", R.string.NoResult)); searchGridView.setEmptyView(searchEmptyView); searchGridView.setHideIfEmpty(false); - searchGridView.setAnimateEmptyView(true, 0); + searchGridView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); containerView.addView(searchEmptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 52, 0, 0)); containerView.addView(searchGridView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 0)); @@ -1386,6 +1520,10 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi } private void selectDialog(ShareDialogCell cell, TLRPC.Dialog dialog) { + if (topicsGridView.getVisibility() != View.GONE) { + return; + } + if (DialogObject.isChatDialog(dialog.id)) { TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-dialog.id); if (ChatObject.isChannel(chat) && !chat.megagroup && (!ChatObject.isCanWriteToChannel(-dialog.id, currentAccount) || hasPoll == 2)) { @@ -1420,11 +1558,86 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi } if (selectedDialogs.indexOfKey(dialog.id) >= 0) { selectedDialogs.remove(dialog.id); + selectedDialogTopics.remove(dialog); if (cell != null) { cell.setChecked(false, true); } updateSelectedCount(1); } else { + if (DialogObject.isChatDialog(dialog.id) && MessagesController.getInstance(currentAccount).getChat(-dialog.id) != null && MessagesController.getInstance(currentAccount).getChat(-dialog.id).forum) { + selectedTopicDialog = dialog; + topicsLayoutManager.scrollToPositionWithOffset(0, scrollOffsetY - topicsGridView.getPaddingTop()); + AtomicReference timeoutRef = new AtomicReference<>(); + NotificationCenter.NotificationCenterDelegate delegate = new NotificationCenter.NotificationCenterDelegate() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void didReceivedNotification(int id, int account, Object... args) { + long chatId = (long) args[0]; + if (chatId == -dialog.id) { + boolean animate = shareTopicsAdapter.topics == null && MessagesController.getInstance(currentAccount).getTopicsController().getTopics(-dialog.id) != null || timeoutRef.get() == null; + + shareTopicsAdapter.topics = MessagesController.getInstance(currentAccount).getTopicsController().getTopics(-dialog.id); + if (animate) { + shareTopicsAdapter.notifyDataSetChanged(); + } + + if (shareTopicsAdapter.topics != null) { + NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.topicsDidLoaded); + } + + if (animate) { + topicsGridView.setVisibility(View.VISIBLE); + topicsGridView.setAlpha(0); + topicsBackActionBar.setVisibility(View.VISIBLE); + topicsBackActionBar.setAlpha(0); + topicsBackActionBar.setTitle(MessagesController.getInstance(currentAccount).getChat(-dialog.id).title); + topicsBackActionBar.setSubtitle(LocaleController.getString(R.string.SelectTopic)); + + if (topicsAnimation != null) { + topicsAnimation.cancel(); + } + + int[] loc = new int[2]; + topicsAnimation = new SpringAnimation(new FloatValueHolder(0)) + .setSpring(new SpringForce(1000) + .setStiffness(parentFragment != null && parentFragment.shareAlertDebugTopicsSlowMotion ? 10f : 800f) + .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)); + topicsAnimation.addUpdateListener((animation, value, velocity) -> { + value /= 1000; + + invalidateTopicsAnimation(cell, loc, value); + }); + topicsAnimation.addEndListener((animation, canceled, value, velocity) -> { + gridView.setVisibility(View.GONE); + searchGridView.setVisibility(View.GONE); + searchView.setVisibility(View.GONE); + + topicsAnimation = null; + }); + topicsAnimation.start(); + + if (timeoutRef.get() != null) { + AndroidUtilities.cancelRunOnUIThread(timeoutRef.get()); + timeoutRef.set(null); + } + } + } + } + }; + timeoutRef.set(() -> { + timeoutRef.set(null); + delegate.didReceivedNotification(NotificationCenter.topicsDidLoaded, currentAccount, -dialog.id); + }); + NotificationCenter.getInstance(currentAccount).addObserver(delegate, NotificationCenter.topicsDidLoaded); + if (MessagesController.getInstance(currentAccount).getTopicsController().getTopics(-dialog.id) != null) { + delegate.didReceivedNotification(NotificationCenter.topicsDidLoaded, currentAccount, -dialog.id); + } else { + MessagesController.getInstance(currentAccount).getTopicsController().loadTopics(-dialog.id); + AndroidUtilities.runOnUIThread(timeoutRef.get(), 300); + } + return; + } + selectedDialogs.put(dialog.id, dialog); if (cell != null) { cell.setChecked(true, true); @@ -1452,6 +1665,102 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi } } + @SuppressLint("NotifyDataSetChanged") + private void collapseTopics() { + if (selectedTopicDialog == null) { + return; + } + TLRPC.Dialog dialog = selectedTopicDialog; + selectedTopicDialog = null; + + View cell = null; + for (int i = 0; i < getMainGridView().getChildCount(); i++) { + View child = getMainGridView().getChildAt(i); + + if (child instanceof ShareDialogCell && ((ShareDialogCell) child).getCurrentDialog() == dialog.id) { + cell = child; + } + } + + if (cell == null) { + return; + } + + if (topicsAnimation != null) { + topicsAnimation.cancel(); + } + + getMainGridView().setVisibility(View.VISIBLE); + searchView.setVisibility(View.VISIBLE); + + int[] loc = new int[2]; + View finalCell = cell; + topicsAnimation = new SpringAnimation(new FloatValueHolder(1000)) + .setSpring(new SpringForce(0) + .setStiffness(parentFragment != null && parentFragment.shareAlertDebugTopicsSlowMotion ? 10f : 800f) + .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)); + topicsAnimation.addUpdateListener((animation, value, velocity) -> { + value /= 1000; + + invalidateTopicsAnimation(finalCell, loc, value); + }); + topicsAnimation.addEndListener((animation, canceled, value, velocity) -> { + topicsGridView.setVisibility(View.GONE); + topicsBackActionBar.setVisibility(View.GONE); + + shareTopicsAdapter.topics = null; + shareTopicsAdapter.notifyDataSetChanged(); + + topicsAnimation = null; + }); + topicsAnimation.start(); + } + + private void invalidateTopicsAnimation(View cell, int[] loc, float value) { + topicsGridView.setPivotX(cell.getX() + cell.getWidth() / 2f); + topicsGridView.setPivotY(cell.getY() + cell.getHeight() / 2f); + topicsGridView.setScaleX(0.75f + value * 0.25f); + topicsGridView.setScaleY(0.75f + value * 0.25f); + topicsGridView.setAlpha(value); + + RecyclerListView mainGridView = getMainGridView(); + mainGridView.setPivotX(cell.getX() + cell.getWidth() / 2f); + mainGridView.setPivotY(cell.getY() + cell.getHeight() / 2f); + mainGridView.setScaleX(1f + value * 0.25f); + mainGridView.setScaleY(1f + value * 0.25f); + mainGridView.setAlpha(1f - value); + + searchView.setPivotX(searchView.getWidth() / 2f); + searchView.setPivotY(0); + searchView.setScaleX(0.9f + (1f - value) * 0.1f); + searchView.setScaleY(0.9f + (1f - value) * 0.1f); + searchView.setAlpha(1f - value); + + topicsBackActionBar.getBackButton().setTranslationX(-AndroidUtilities.dp(16) * (1f - value)); + topicsBackActionBar.getTitleTextView().setTranslationY(AndroidUtilities.dp(16) * (1f - value)); + topicsBackActionBar.getSubtitleTextView().setTranslationY(AndroidUtilities.dp(16) * (1f - value)); + topicsBackActionBar.setAlpha(value); + + topicsGridView.getLocationInWindow(loc); + float moveValue = CubicBezierInterpolator.EASE_OUT.getInterpolation(value); + for (int i = 0; i < mainGridView.getChildCount(); i++) { + View v = mainGridView.getChildAt(i); + if (v instanceof ShareDialogCell) { + v.setTranslationX((v.getX() - cell.getX()) * 0.75f * moveValue); + v.setTranslationY((v.getY() - cell.getY()) * 0.75f * moveValue); + } + } + for (int i = 0; i < topicsGridView.getChildCount(); i++) { + View v = topicsGridView.getChildAt(i); + if (v instanceof ShareTopicCell) { + v.setTranslationX((float) (-(v.getX() - cell.getX()) * Math.pow(1f - moveValue, 2))); + v.setTranslationY((float) (-(v.getY() + topicsGridView.getTranslationY() - cell.getY()) * Math.pow(1f - moveValue, 2))); + } + } + containerView.requestLayout(); + mainGridView.invalidate(); + } + @Override public int getContainerViewHeight() { return containerView.getMeasuredHeight() - containerViewTop; @@ -1623,10 +1932,12 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi List removeKeys = new ArrayList<>(); for (int a = 0; a < selectedDialogs.size(); a++) { long key = selectedDialogs.keyAt(a); + TLRPC.TL_forumTopic topic = selectedDialogTopics.get(selectedDialogs.get(key)); + MessageObject replyTopMsg = topic != null ? new MessageObject(currentAccount, topic.topicStartMessage, false, false) : null; if (frameLayout2.getTag() != null && commentTextView.length() > 0) { - SendMessagesHelper.getInstance(currentAccount).sendMessage(text[0] == null ? null : text[0].toString(), key, null, null, null, true, entities, null, null, withSound, 0, null, false); + SendMessagesHelper.getInstance(currentAccount).sendMessage(text[0] == null ? null : text[0].toString(), key, null, replyTopMsg, null, true, entities, null, null, withSound, 0, null, false); } - int result = SendMessagesHelper.getInstance(currentAccount).sendMessage(sendingMessageObjects, key, !showSendersName,false, withSound, 0); + int result = SendMessagesHelper.getInstance(currentAccount).sendMessage(sendingMessageObjects, key, !showSendersName,false, withSound, 0, replyTopMsg); if (result != 0) { removeKeys.add(key); } @@ -1639,10 +1950,14 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi } } for (long key : removeKeys) { + TLRPC.Dialog dialog = selectedDialogs.get(key); selectedDialogs.remove(key); + if (dialog != null) { + selectedDialogTopics.remove(dialog); + } } if (!selectedDialogs.isEmpty()) { - onSend(selectedDialogs, sendingMessageObjects.size()); + onSend(selectedDialogs, sendingMessageObjects.size(), selectedDialogs.size() == 1 ? selectedDialogTopics.get(selectedDialogs.valueAt(0)) : null); } } else { int num; @@ -1654,13 +1969,16 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi if (sendingText[num] != null) { for (int a = 0; a < selectedDialogs.size(); a++) { long key = selectedDialogs.keyAt(a); + TLRPC.TL_forumTopic topic = selectedDialogTopics.get(selectedDialogs.get(key)); + MessageObject replyTopMsg = topic != null ? new MessageObject(currentAccount, topic.topicStartMessage, false, false) : null; + if (frameLayout2.getTag() != null && commentTextView.length() > 0) { - SendMessagesHelper.getInstance(currentAccount).sendMessage(text[0] == null ? null : text[0].toString(), key, null, null, null, true, entities, null, null, withSound, 0, null, false); + SendMessagesHelper.getInstance(currentAccount).sendMessage(text[0] == null ? null : text[0].toString(), key, null, replyTopMsg, null, true, entities, null, null, withSound, 0, null, false); } - SendMessagesHelper.getInstance(currentAccount).sendMessage(sendingText[num], key, null, null, null, true, null, null, null, withSound, 0, null, false); + SendMessagesHelper.getInstance(currentAccount).sendMessage(sendingText[num], key, null, replyTopMsg, null, true, null, null, null, withSound, 0, null, false); } } - onSend(selectedDialogs, 1); + onSend(selectedDialogs, 1, selectedDialogTopics.get(selectedDialogs.valueAt(0))); } if (delegate != null) { delegate.didShare(); @@ -1668,7 +1986,7 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi dismiss(); } - protected void onSend(LongSparseArray dids, int count) { + protected void onSend(LongSparseArray dids, int count, TLRPC.TL_forumTopic topic) { } @@ -1683,6 +2001,10 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi return -1000; } + private RecyclerListView getMainGridView() { + return searchIsVisible ? searchGridView : gridView; + } + public void setDelegate(ShareAlertDelegate shareAlertDelegate) { delegate = shareAlertDelegate; } @@ -1697,6 +2019,10 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi @Override public void onBackPressed() { + if (selectedTopicDialog != null) { + collapseTopics(); + return; + } if (commentTextView != null && commentTextView.isPopupShowing()) { commentTextView.hidePopup(true); return; @@ -1751,10 +2077,39 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi lastOffset = Integer.MAX_VALUE; runShadowAnimation(0, true); } + + if (topicsGridView.getVisibility() == View.VISIBLE) { + listView = topicsGridView; + + if (listView.getChildCount() <= 0) { + return; + } + child = listView.getChildAt(0); + for (int i = 0; i < listView.getChildCount(); i++) { + if (listView.getChildAt(i).getTop() < child.getTop()) { + child = listView.getChildAt(i); + } + } + holder = (RecyclerListView.Holder) listView.findContainingViewHolder(child); + + int topicsTop = child.getTop() - AndroidUtilities.dp(8); + int topicsNewOffset = topicsTop > 0 && holder != null && holder.getAdapterPosition() == 0 ? topicsTop : 0; + if (topicsTop >= 0 && holder != null && holder.getAdapterPosition() == 0) { + lastOffset = child.getTop(); + topicsNewOffset = topicsTop; + runShadowAnimation(0, false); + } else { + lastOffset = Integer.MAX_VALUE; + runShadowAnimation(0, true); + } + newOffset = AndroidUtilities.lerp(newOffset, topicsNewOffset, topicsGridView.getAlpha()); + } + if (scrollOffsetY != newOffset) { previousScrollOffsetY = scrollOffsetY; gridView.setTopGlowOffset(scrollOffsetY = (int) (newOffset + currentPanTranslationY)); searchGridView.setTopGlowOffset(scrollOffsetY = (int) (newOffset + currentPanTranslationY)); + topicsGridView.setTopGlowOffset(scrollOffsetY = (int) (newOffset + currentPanTranslationY)); frameLayout.setTranslationY(scrollOffsetY + currentPanTranslationY); searchEmptyView.setTranslationY(scrollOffsetY + currentPanTranslationY); containerView.invalidate(); @@ -1966,6 +2321,20 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi } } dialogs.addAll(archivedDialogs); + if (parentFragment != null) { + switch (parentFragment.shareAlertDebugMode) { + case ChatActivity.DEBUG_SHARE_ALERT_MODE_LESS: + List sublist = new ArrayList<>(dialogs.subList(0, Math.min(4, dialogs.size()))); + dialogs.clear(); + dialogs.addAll(sublist); + break; + case ChatActivity.DEBUG_SHARE_ALERT_MODE_MORE: + while (!dialogs.isEmpty() && dialogs.size() < 80) { + dialogs.add(dialogs.get(dialogs.size() - 1)); + } + break; + } + } notifyDataSetChanged(); } @@ -2018,6 +2387,7 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi if (holder.getItemViewType() == 0) { ShareDialogCell cell = (ShareDialogCell) holder.itemView; TLRPC.Dialog dialog = getItem(position); + cell.setTopic(selectedDialogTopics.get(dialog), false); cell.setDialog(dialog.id, selectedDialogs.indexOfKey(dialog.id) >= 0, null); } } @@ -2031,6 +2401,67 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi } } + private class ShareTopicsAdapter extends RecyclerListView.SelectionAdapter { + + private Context context; + private List topics; + + public ShareTopicsAdapter(Context context) { + this.context = context; + } + + @Override + public int getItemCount() { + return topics == null ? 0 : topics.size() + 1; + } + + public TLRPC.TL_forumTopic getItem(int position) { + position--; + if (topics == null || position < 0 || position >= topics.size()) { + return null; + } + return topics.get(position); + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getItemViewType() != 1; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: { + view = new ShareTopicCell(context, resourcesProvider); + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, AndroidUtilities.dp(100))); + break; + } + case 1: + default: { + view = new View(context); + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, ActionBar.getCurrentActionBarHeight())); + break; + } + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder.getItemViewType() == 0) { + ShareTopicCell cell = (ShareTopicCell) holder.itemView; + TLRPC.TL_forumTopic topic = getItem(position); + cell.setTopic(selectedTopicDialog, topic, selectedDialogs.indexOfKey(topic.id) >= 0, null); + } + } + + @Override + public int getItemViewType(int position) { + return position == 0 ? 1 : 0; + } + } + public static class DialogSearchResult { public TLRPC.Dialog dialog = new TLRPC.TL_dialog(); public TLObject object; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SharedMediaLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SharedMediaLayout.java index eb97cde2f..91074c466 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SharedMediaLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SharedMediaLayout.java @@ -68,6 +68,7 @@ import org.telegram.messenger.MediaController; import org.telegram.messenger.MediaDataController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; +import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.SharedConfig; @@ -105,6 +106,7 @@ import org.telegram.ui.Cells.SharedPhotoVideoCell; import org.telegram.ui.Cells.SharedPhotoVideoCell2; import org.telegram.ui.Cells.UserCell; import org.telegram.ui.ChatActivity; +import org.telegram.ui.Components.Forum.ForumUtilities; import org.telegram.ui.DialogsActivity; import org.telegram.ui.PhotoViewer; import org.telegram.ui.ProfileActivity; @@ -159,6 +161,7 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter ActionBarPopupWindow optionsWindow; FlickerLoadingView globalGradientView; private final int viewType; + private int topicId; private UndoView undoView; @@ -523,6 +526,7 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter private int[] lastLoadMediaCount = new int[]{-1, -1, -1, -1, -1, -1, -1, -1}; private SharedMediaData[] sharedMediaData; private long dialogId; + private int topicId; private long mergeDialogId; private BaseFragment parentFragment; private ArrayList delegates = new ArrayList<>(); @@ -530,13 +534,15 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter public SharedMediaPreloader(BaseFragment fragment) { parentFragment = fragment; - if (fragment instanceof ChatActivity) { - ChatActivity chatActivity = (ChatActivity) fragment; + if (fragment instanceof FragmentContextView.ChatActivityInterface) { + FragmentContextView.ChatActivityInterface chatActivity = (FragmentContextView.ChatActivityInterface) fragment; dialogId = chatActivity.getDialogId(); mergeDialogId = chatActivity.getMergeDialogId(); + topicId = chatActivity.getTopicId(); } else if (fragment instanceof ProfileActivity) { ProfileActivity profileActivity = (ProfileActivity) fragment; dialogId = profileActivity.getDialogId(); + topicId = profileActivity.getTopicId(); } else if (fragment instanceof MediaActivity) { MediaActivity mediaActivity = (MediaActivity) fragment; dialogId = mediaActivity.getDialogId(); @@ -598,8 +604,9 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter public void didReceivedNotification(int id, int account, Object... args) { if (id == NotificationCenter.mediaCountsDidLoad) { long did = (Long) args[0]; - if (did == dialogId || did == mergeDialogId) { - int[] counts = (int[]) args[1]; + int topicId = (int) args[1]; + if (this.topicId == topicId && (did == dialogId || did == mergeDialogId)) { + int[] counts = (int[]) args[2]; if (did == dialogId) { mediaCount = counts; } else { @@ -622,7 +629,7 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter type = MediaDataController.MEDIA_VIDEOS_ONLY; } } - parentFragment.getMediaDataController().loadMedia(did, lastLoadMediaCount[a] == -1 ? 30 : 20, 0, 0, type, 2, parentFragment.getClassGuid(), 0); + parentFragment.getMediaDataController().loadMedia(did, lastLoadMediaCount[a] == -1 ? 30 : 20, 0, 0, type, topicId,2, parentFragment.getClassGuid(), 0); lastLoadMediaCount[a] = mediaCount[a]; } } @@ -633,9 +640,10 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter } } else if (id == NotificationCenter.mediaCountDidLoad) { long did = (Long) args[0]; - if (did == dialogId || did == mergeDialogId) { - int type = (Integer) args[3]; - int mCount = (Integer) args[1]; + long topicId = (Integer) args[1]; + if ((did == dialogId || did == mergeDialogId) && this.topicId == topicId) { + int type = (Integer) args[4]; + int mCount = (Integer) args[2]; if (did == dialogId) { mediaCount[type] = mCount; } else { @@ -662,6 +670,9 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter ArrayList arr = (ArrayList) args[1]; for (int a = 0; a < arr.size(); a++) { MessageObject obj = arr.get(a); + if (topicId != 0 && topicId != MessageObject.getTopicId(obj.messageOwner)) { + continue; + } if (MessageObject.getMedia(obj.messageOwner) == null || obj.needDrawBluredPreview()) { continue; } @@ -678,7 +689,9 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter if (sharedMediaData[type].startReached) { sharedMediaData[type].addMessage(obj, 0, true, enc); } - sharedMediaData[type].totalCount++; + if (topicId == 0) { + sharedMediaData[type].totalCount++; + } for (int i = 0; i < sharedMediaData[type].fastScrollPeriods.size(); i++) { sharedMediaData[type].fastScrollPeriods.get(i).startOffset++; } @@ -747,7 +760,7 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter for (int b = 0; b < sharedMediaData.length; b++) { MessageObject messageObject = sharedMediaData[b].deleteMessage(markAsDeletedMessages.get(a), 0); if (messageObject != null) { - if (messageObject.getDialogId() == dialogId) { + if (messageObject.getDialogId() == dialogId && (topicId == 0 || MessageObject.getTopicId(messageObject.messageOwner) == topicId)) { if (mediaCount[b] > 0) { mediaCount[b]--; } @@ -785,7 +798,11 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter for (int b = 0, N = messageObjects.size(); b < N; b++) { MessageObject messageObject = messageObjects.get(b); int mid = messageObject.getId(); + int topicId = MessageObject.getTopicId(messageObject.messageOwner); int type = MediaDataController.getMediaType(messageObject.messageOwner); + if (this.topicId != 0 && topicId != this.topicId) { + continue; + } for (int a = 0; a < sharedMediaData.length; a++) { MessageObject old = sharedMediaData[a].messagesDict[loadIndex].get(mid); if (old != null) { @@ -832,16 +849,16 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter } private void loadMediaCounts() { - parentFragment.getMediaDataController().getMediaCounts(dialogId, parentFragment.getClassGuid()); + parentFragment.getMediaDataController().getMediaCounts(dialogId, topicId, parentFragment.getClassGuid()); if (mergeDialogId != 0) { - parentFragment.getMediaDataController().getMediaCounts(mergeDialogId, parentFragment.getClassGuid()); + parentFragment.getMediaDataController().getMediaCounts(mergeDialogId, topicId, parentFragment.getClassGuid()); } } private void setChatInfo(TLRPC.ChatFull chatInfo) { if (chatInfo != null && chatInfo.migrated_from_chat_id != 0 && mergeDialogId == 0) { mergeDialogId = -chatInfo.migrated_from_chat_id; - parentFragment.getMediaDataController().getMediaCounts(mergeDialogId, parentFragment.getClassGuid()); + parentFragment.getMediaDataController().getMediaCounts(mergeDialogId, topicId, parentFragment.getClassGuid()); } } @@ -1077,6 +1094,9 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter sections.remove(messageObject.monthKey); } totalCount--; + if (totalCount < 0) { + totalCount = 0; + } return messageObject; } @@ -1164,8 +1184,9 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter sharedMediaPreloader = preloader; this.delegate = delegate; int[] mediaCount = preloader.getLastMediaCount(); - hasMedia = new int[]{mediaCount[0], mediaCount[1], mediaCount[2], mediaCount[3], mediaCount[4], mediaCount[5], commonGroupsCount}; - if (membersFirst) { + topicId = sharedMediaPreloader.topicId; + hasMedia = new int[]{mediaCount[0], mediaCount[1], mediaCount[2], mediaCount[3], mediaCount[4], mediaCount[5], topicId == 0 ? commonGroupsCount : 0}; + if (membersFirst && topicId == 0) { initialTab = 7; } else { for (int a = 0; a < hasMedia.length; a++) { @@ -1180,6 +1201,7 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter mergeDialogId = -info.migrated_from_chat_id; } dialog_id = did; + for (int a = 0; a < sharedMediaData.length; a++) { sharedMediaData[a] = new SharedMediaData(); sharedMediaData[a].max_id[0] = DialogObject.isEncryptedDialog(dialog_id) ? Integer.MIN_VALUE : Integer.MAX_VALUE; @@ -1557,8 +1579,10 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter groupUsersSearchAdapter = new GroupUsersSearchAdapter(context); commonGroupsAdapter = new CommonGroupsAdapter(context); chatUsersAdapter = new ChatUsersAdapter(context); - chatUsersAdapter.sortedUsers = sortedUsers; - chatUsersAdapter.chatInfo = membersFirst ? chatInfo : null; + if (topicId == 0) { + chatUsersAdapter.sortedUsers = sortedUsers; + chatUsersAdapter.chatInfo = membersFirst ? chatInfo : null; + } linksAdapter = new SharedLinksAdapter(context); setWillNotDraw(false); @@ -2230,7 +2254,7 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter mediaPages[a].emptyView.addView(mediaPages[a].progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); mediaPages[a].listView.setEmptyView(mediaPages[a].emptyView); - mediaPages[a].listView.setAnimateEmptyView(true, 0); + mediaPages[a].listView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); mediaPages[a].scrollHelper = new RecyclerAnimationScrollHelper(mediaPages[a].listView, mediaPages[a].layoutManager); } @@ -2401,6 +2425,7 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter } Bundle bundle = new Bundle(); bundle.putLong("dialog_id", dialog_id); + bundle.putInt("topic_id", topicId); int date = 0; if (fromFastScroll) { MediaPage mediaPage = getMediaPage(0); @@ -2919,10 +2944,10 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter } if (!sharedMediaData[mediaPage.selectedType].endReached[0]) { sharedMediaData[mediaPage.selectedType].loading = true; - profileActivity.getMediaDataController().loadMedia(dialog_id, 50, sharedMediaData[mediaPage.selectedType].max_id[0], 0, type, 1, profileActivity.getClassGuid(), sharedMediaData[mediaPage.selectedType].requestIndex); + profileActivity.getMediaDataController().loadMedia(dialog_id, 50, sharedMediaData[mediaPage.selectedType].max_id[0], 0, type, topicId, 1, profileActivity.getClassGuid(), sharedMediaData[mediaPage.selectedType].requestIndex); } else if (mergeDialogId != 0 && !sharedMediaData[mediaPage.selectedType].endReached[1]) { sharedMediaData[mediaPage.selectedType].loading = true; - profileActivity.getMediaDataController().loadMedia(mergeDialogId, 50, sharedMediaData[mediaPage.selectedType].max_id[1], 0, type, 1, profileActivity.getClassGuid(), sharedMediaData[mediaPage.selectedType].requestIndex); + profileActivity.getMediaDataController().loadMedia(mergeDialogId, 50, sharedMediaData[mediaPage.selectedType].max_id[1], 0, type, topicId, 1, profileActivity.getClassGuid(), sharedMediaData[mediaPage.selectedType].requestIndex); } } @@ -2972,7 +2997,7 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter type = MediaDataController.MEDIA_URL; } sharedMediaData[selectedType].loading = true; - profileActivity.getMediaDataController().loadMedia(dialog_id, 50, 0, sharedMediaData[selectedType].min_id, type, 1, profileActivity.getClassGuid(), sharedMediaData[selectedType].requestIndex); + profileActivity.getMediaDataController().loadMedia(dialog_id, 50, 0, sharedMediaData[selectedType].min_id, type, topicId, 1, profileActivity.getClassGuid(), sharedMediaData[selectedType].requestIndex); } public ActionBarMenuItem getSearchItem() { @@ -3061,6 +3086,9 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter } private void loadFastScrollData(boolean force) { + if (topicId != 0) { + return; + } for (int k = 0; k < supportedFastScrollTypes.length; k++) { int type = supportedFastScrollTypes[k]; if ((sharedMediaData[type].fastScrollDataLoaded && !force) || DialogObject.isEncryptedDialog(dialog_id)) { @@ -3160,7 +3188,9 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter } public void setCommonGroupsCount(int count) { - hasMedia[6] = count; + if (topicId == 0) { + hasMedia[6] = count; + } updateTabs(true); checkCurrentTabValid(); } @@ -3224,10 +3254,10 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter cantDeleteMessagesCount = 0; showActionMode(false); - if (dids.size() > 1 || dids.get(0) == profileActivity.getUserConfig().getClientUserId() || message != null) { + if (dids.size() > 1 || dids.get(0).dialogId == profileActivity.getUserConfig().getClientUserId() || message != null) { updateRowsSelection(); for (int a = 0; a < dids.size(); a++) { - long did = dids.get(a); + long did = dids.get(a).dialogId; if (message != null) { profileActivity.getSendMessagesHelper().sendMessage(message.toString(), did, null, null, null, true, null, null, null, true, 0, null, false); } @@ -3240,13 +3270,13 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter } if (undoView != null) { if (dids.size() == 1) { - undoView.showWithAction(dids.get(0), UndoView.ACTION_FWD_MESSAGES, fmessages.size()); + undoView.showWithAction(dids.get(0).dialogId, UndoView.ACTION_FWD_MESSAGES, fmessages.size()); } else { undoView.showWithAction(0, UndoView.ACTION_FWD_MESSAGES, fmessages.size(), dids.size(), null, null); } } } else { - long did = dids.get(0); + long did = dids.get(0).dialogId; Bundle args1 = new Bundle(); args1.putBoolean("scrollToTopOnResume", true); if (DialogObject.isEncryptedDialog(did)) { @@ -3291,7 +3321,13 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter } args.putInt("message_id", messageObject.getId()); args.putBoolean("need_remove_previous_same_chat_activity", false); - profileActivity.presentFragment(new ChatActivity(args), false); + ChatActivity chatActivity = new ChatActivity(args); + chatActivity.highlightMessageId = messageObject.getId(); + if (topicId != 0) { + ForumUtilities.applyTopic(chatActivity, MessagesStorage.TopicKey.of(dialogId, topicId)); + args.putInt("message_id", messageObject.getId()); + } + profileActivity.presentFragment(chatActivity, false); } } @@ -3753,7 +3789,7 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter } if (!fromStart && loadIndex == 0 && sharedMediaData[type].endReached[loadIndex] && mergeDialogId != 0) { sharedMediaData[type].loading = true; - profileActivity.getMediaDataController().loadMedia(mergeDialogId, 50, sharedMediaData[type].max_id[1], 0, type, 1, profileActivity.getClassGuid(), sharedMediaData[type].requestIndex); + profileActivity.getMediaDataController().loadMedia(mergeDialogId, 50, sharedMediaData[type].max_id[1], 0, type, topicId, 1, profileActivity.getClassGuid(), sharedMediaData[type].requestIndex); } if (adapter != null) { RecyclerListView listView = null; @@ -4175,8 +4211,10 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter } public void setChatUsers(ArrayList sortedUsers, TLRPC.ChatFull chatInfo) { - chatUsersAdapter.chatInfo = chatInfo; - chatUsersAdapter.sortedUsers = sortedUsers; + if (topicId == 0) { + chatUsersAdapter.chatInfo = chatInfo; + chatUsersAdapter.sortedUsers = sortedUsers; + } updateTabs(true); for (int a = 0; a < mediaPages.length; a++) { if (mediaPages[a].selectedType == 7) { @@ -4582,7 +4620,7 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter type = MediaDataController.MEDIA_VIDEOS_ONLY; } } - profileActivity.getMediaDataController().loadMedia(dialog_id, 50, 0, 0, type, 1, profileActivity.getClassGuid(), sharedMediaData[mediaPages[a].selectedType].requestIndex); + profileActivity.getMediaDataController().loadMedia(dialog_id, 50, 0, 0, type, topicId, 1, profileActivity.getClassGuid(), sharedMediaData[mediaPages[a].selectedType].requestIndex); } } mediaPages[a].listView.setVisibility(View.VISIBLE); @@ -4695,7 +4733,7 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter int i = index - sharedMediaData[selectedMode].startOffset; if (i >= 0 && i < sharedMediaData[selectedMode].messages.size()) { PhotoViewer.getInstance().setParentActivity(profileActivity); - PhotoViewer.getInstance().openPhoto(sharedMediaData[selectedMode].messages, i, dialog_id, mergeDialogId, provider); + PhotoViewer.getInstance().openPhoto(sharedMediaData[selectedMode].messages, i, dialog_id, mergeDialogId, topicId, provider); } } else if (selectedMode == 2 || selectedMode == 4) { if (view instanceof SharedAudioCell) { @@ -4707,9 +4745,9 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter if (index < 0) { ArrayList documents = new ArrayList<>(); documents.add(message); - PhotoViewer.getInstance().openPhoto(documents, 0, 0, 0, provider); + PhotoViewer.getInstance().openPhoto(documents, 0, 0, 0, 0, provider); } else { - PhotoViewer.getInstance().openPhoto(sharedMediaData[selectedMode].messages, index, dialog_id, mergeDialogId, provider); + PhotoViewer.getInstance().openPhoto(sharedMediaData[selectedMode].messages, index, dialog_id, mergeDialogId, topicId, provider); } } else if (selectedMode == 1) { if (view instanceof SharedDocumentCell) { @@ -4722,9 +4760,9 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter if (index < 0) { ArrayList documents = new ArrayList<>(); documents.add(message); - PhotoViewer.getInstance().openPhoto(documents, 0, 0, 0, provider); + PhotoViewer.getInstance().openPhoto(documents, 0, 0, 0, 0, provider); } else { - PhotoViewer.getInstance().openPhoto(sharedMediaData[selectedMode].messages, index, dialog_id, mergeDialogId, provider); + PhotoViewer.getInstance().openPhoto(sharedMediaData[selectedMode].messages, index, dialog_id, mergeDialogId, topicId, provider); } return; } @@ -5725,7 +5763,7 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter } if (currentType == 4) { TLRPC.Document document; - if (messageObject.type == 0) { + if (messageObject.type == MessageObject.TYPE_TEXT) { document = MessageObject.getMedia(messageObject.messageOwner).webpage.document; } else { document = MessageObject.getMedia(messageObject.messageOwner).document; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java index fb7285fc2..209a0093d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java @@ -34,8 +34,8 @@ import org.telegram.messenger.FileLog; import org.telegram.messenger.SharedConfig; import org.telegram.messenger.Utilities; import org.telegram.ui.ActionBar.ActionBar; -import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.AdjustPanLayoutHelper; +import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.BlurSettingsBottomSheet; @@ -57,7 +57,7 @@ public class SizeNotifierFrameLayout extends FrameLayout { private int backgroundTranslationY; private boolean paused = true; private Drawable oldBackgroundDrawable; - private ActionBarLayout parentLayout; + private INavigationLayout parentLayout; public AdjustPanLayoutHelper adjustPanLayoutHelper; private int emojiHeight; private float emojiOffset; @@ -113,7 +113,7 @@ public class SizeNotifierFrameLayout extends FrameLayout { this(context, null); } - public SizeNotifierFrameLayout(Context context, ActionBarLayout layout) { + public SizeNotifierFrameLayout(Context context, INavigationLayout layout) { super(context); setWillNotDraw(false); parentLayout = layout; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerEmptyView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerEmptyView.java index 5c33d5134..63b520f5f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerEmptyView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerEmptyView.java @@ -107,7 +107,7 @@ public class StickerEmptyView extends FrameLayout implements NotificationCenter. linearLayout.addView(stickerView, LayoutHelper.createLinear(117, 117, Gravity.CENTER_HORIZONTAL)); linearLayout.addView(title, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 0, 12, 0, 0)); linearLayout.addView(subtitle, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 0, 8, 0, 0)); - addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER, 56, 0, 56, 30)); + addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER, 46, 0, 46, 30)); if (progressView == null) { progressBar = new RadialProgressView(context, resourcesProvider); @@ -243,7 +243,7 @@ public class StickerEmptyView extends FrameLayout implements NotificationCenter. ImageLocation imageLocation = ImageLocation.getForDocument(document); stickerView.setImage(imageLocation, imageFilter, "tgs", svgThumb, set); - if (stickerType == 9) { + if (stickerType == 9 || stickerType == STICKER_TYPE_NO_CONTACTS) { stickerView.getImageReceiver().setAutoRepeat(1); } else { stickerView.getImageReceiver().setAutoRepeat(2); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java index a9a6c2caf..8139dc3f0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java @@ -472,7 +472,9 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not adapter.notifyDataSetChanged(); } else { dismiss(); - BulletinFactory.of(parentFragment).createErrorBulletin(LocaleController.getString("AddStickersNotFound", R.string.AddStickersNotFound)).show(); + if (parentFragment != null) { + BulletinFactory.of(parentFragment).createErrorBulletin(LocaleController.getString("AddStickersNotFound", R.string.AddStickersNotFound)).show(); + } } })); } else { @@ -614,7 +616,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not } boolean openBgLight = AndroidUtilities.computePerceivedBrightness(getThemedColor(Theme.key_dialogBackground)) > .721f; boolean closedBgLight = AndroidUtilities.computePerceivedBrightness(Theme.blendOver(getThemedColor(Theme.key_actionBarDefault), 0x33000000)) > .721f; - boolean isLight = open ? openBgLight : closedBgLight; + boolean isLight = (statusBarOpen = open) ? openBgLight : closedBgLight; AndroidUtilities.setLightStatusBar(getWindow(), isLight); } @@ -1008,7 +1010,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not } @Override - protected void onSend(LongSparseArray dids, int count) { + protected void onSend(LongSparseArray dids, int count, TLRPC.TL_forumTopic topic) { AndroidUtilities.runOnUIThread(() -> { UndoView undoView; if (parentFragment instanceof ChatActivity) { @@ -1351,7 +1353,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not } else if (state[0] == 2) { state[0] = 3; if (!lastNameAvailable) { - AndroidUtilities.shakeView(editText, 2, 0); + AndroidUtilities.shakeView(editText); editText.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } AndroidUtilities.hideKeyboard(editText); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Switch.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Switch.java index e5f41c3ff..2c749e5d6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Switch.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Switch.java @@ -307,6 +307,7 @@ public class Switch extends View { } else { iconDrawable = null; } + invalidate(); } public boolean hasIcon() { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeEditorView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeEditorView.java index 0592be078..d550e1ba1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeEditorView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeEditorView.java @@ -68,6 +68,7 @@ import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.TextColorThemeCell; @@ -744,11 +745,21 @@ public class ThemeEditorView { canvas.drawRoundRect(rect1, AndroidUtilities.dp(2), AndroidUtilities.dp(2), Theme.dialogs_onlineCirclePaint); if (statusBarHeight > 0) { - int color1 = 0xffffffff; - int finalColor = Color.argb(0xff, (int) (Color.red(color1) * 0.8f), (int) (Color.green(color1) * 0.8f), (int) (Color.blue(color1) * 0.8f)); - Theme.dialogs_onlineCirclePaint.setColor(finalColor); + Theme.dialogs_onlineCirclePaint.setColor(Theme.getColor(Theme.key_dialogBackground)); canvas.drawRect(backgroundPaddingLeft, AndroidUtilities.statusBarHeight - statusBarHeight, getMeasuredWidth() - backgroundPaddingLeft, AndroidUtilities.statusBarHeight, Theme.dialogs_onlineCirclePaint); } + updateLightStatusBar(statusBarHeight > AndroidUtilities.statusBarHeight / 2); + } + + private Boolean statusBarOpen; + private void updateLightStatusBar(boolean open) { + if (statusBarOpen != null && statusBarOpen == open) { + return; + } + boolean openBgLight = AndroidUtilities.computePerceivedBrightness(getThemedColor(Theme.key_dialogBackground)) > .721f; + boolean closedBgLight = AndroidUtilities.computePerceivedBrightness(Theme.blendOver(getThemedColor(Theme.key_actionBarDefault), 0x33000000)) > .721f; + boolean isLight = (statusBarOpen = open) ? openBgLight : closedBgLight; + AndroidUtilities.setLightStatusBar(getWindow(), isLight); } }; containerView.setWillNotDraw(false); @@ -1407,16 +1418,16 @@ public class ThemeEditorView { if (editorAlert == null) { LaunchActivity launchActivity = (LaunchActivity) parentActivity; - ActionBarLayout actionBarLayout = null; + INavigationLayout actionBarLayout = null; if (AndroidUtilities.isTablet()) { actionBarLayout = launchActivity.getLayersActionBarLayout(); - if (actionBarLayout != null && actionBarLayout.fragmentsStack.isEmpty()) { + if (actionBarLayout != null && actionBarLayout.getFragmentStack().isEmpty()) { actionBarLayout = null; } if (actionBarLayout == null) { actionBarLayout = launchActivity.getRightActionBarLayout(); - if (actionBarLayout != null && actionBarLayout.fragmentsStack.isEmpty()) { + if (actionBarLayout != null && actionBarLayout.getFragmentStack().isEmpty()) { actionBarLayout = null; } } @@ -1426,8 +1437,8 @@ public class ThemeEditorView { } if (actionBarLayout != null) { BaseFragment fragment; - if (!actionBarLayout.fragmentsStack.isEmpty()) { - fragment = actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1); + if (!actionBarLayout.getFragmentStack().isEmpty()) { + fragment = actionBarLayout.getFragmentStack().get(actionBarLayout.getFragmentStack().size() - 1); } else { fragment = null; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeSmallPreviewView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeSmallPreviewView.java index b0f35b442..e9a90736e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeSmallPreviewView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeSmallPreviewView.java @@ -257,12 +257,16 @@ public class ThemeSmallPreviewView extends FrameLayout implements NotificationCe ImageLoader.getInstance().loadImageForImageReceiver(imageReceiver); } } else if (accent != null && accent.info == null) { + int intensity = (int) (accent.patternIntensity * 100); + if (item.previewDrawable instanceof MotionBackgroundDrawable) { + ((MotionBackgroundDrawable) item.previewDrawable).setPatternBitmap(intensity); + } ChatThemeController.chatThemeQueue.postRunnable(() -> { Bitmap bitmap = SvgHelper.getBitmap(R.raw.default_pattern, AndroidUtilities.dp(PATTERN_BITMAP_MAXWIDTH), AndroidUtilities.dp(PATTERN_BITMAP_MAXHEIGHT), Color.BLACK, AndroidUtilities.density); AndroidUtilities.runOnUIThread(() -> { if (item.previewDrawable instanceof MotionBackgroundDrawable) { MotionBackgroundDrawable motionBackgroundDrawable = (MotionBackgroundDrawable) item.previewDrawable; - motionBackgroundDrawable.setPatternBitmap(100, prescaleBitmap(bitmap), true); + motionBackgroundDrawable.setPatternBitmap(intensity, prescaleBitmap(bitmap), true); motionBackgroundDrawable.setPatternColorFilter(patternColor); invalidate(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/TranscribeButton.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/TranscribeButton.java index bf173f9e7..a0ae92c12 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/TranscribeButton.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/TranscribeButton.java @@ -14,12 +14,14 @@ import android.os.SystemClock; import android.text.TextPaint; import android.text.TextUtils; import android.text.style.ImageSpan; +import android.util.Log; import android.util.StateSet; import android.view.MotionEvent; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.graphics.ColorUtils; +import androidx.core.math.MathUtils; import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import org.telegram.messenger.AccountInstance; @@ -32,12 +34,14 @@ import org.telegram.messenger.MessagesController; import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.ChatMessageCell; import org.telegram.ui.PremiumPreviewFragment; +import java.util.ArrayList; import java.util.HashMap; import java.util.Objects; @@ -46,13 +50,16 @@ public class TranscribeButton { private final static int[] pressedState = new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}; private int backgroundColor, color, iconColor, rippleColor; + private float backgroundBack; private Paint backgroundPaint, strokePaint; private Path progressClipPath; private boolean loading; private AnimatedFloat loadingFloat; + private int inIconDrawableAlpha; private RLottieDrawable inIconDrawable; + private int outIconDrawableAlpha; private RLottieDrawable outIconDrawable; private Drawable selectorDrawable; @@ -61,6 +68,7 @@ public class TranscribeButton { private long start; private Rect bounds, pressBounds; + private boolean clickedToOpen = false; private boolean premium; private boolean isOpen, shouldBeOpen; @@ -98,7 +106,7 @@ public class TranscribeButton { this.isOpen = false; this.shouldBeOpen = false; - premium = AccountInstance.getInstance(parent.getMessageObject().currentAccount).getUserConfig().isPremium(); + premium = parent.getMessageObject() != null && UserConfig.getInstance(parent.getMessageObject().currentAccount).isPremium(); loadingFloat = new AnimatedFloat(parent, 250, CubicBezierInterpolator.EASE_OUT_QUINT); } @@ -118,7 +126,13 @@ public class TranscribeButton { } } + protected void onOpen() {} + public void setOpen(boolean open, boolean animated) { + if (!shouldBeOpen && open && clickedToOpen) { + clickedToOpen = false; + onOpen(); + } boolean wasShouldBeOpen = shouldBeOpen; shouldBeOpen = open; if (animated) { @@ -171,6 +185,7 @@ public class TranscribeButton { } public void onTap() { + clickedToOpen = false; boolean processClick, toOpen = !shouldBeOpen; if (!shouldBeOpen) { processClick = !loading; @@ -190,28 +205,37 @@ public class TranscribeButton { if (processClick) { if (!premium && toOpen) { if (parent.getDelegate() != null) { - parent.getDelegate().needShowPremiumFeatures(PremiumPreviewFragment.featureTypeToServerString(PremiumPreviewFragment.PREMIUM_FEATURE_VOICE_TO_TEXT)); + parent.getDelegate().needShowPremiumBulletin(0); } } else { + if (toOpen) { + clickedToOpen = true; + } transcribePressed(parent.getMessageObject(), toOpen); } } } - public void setColor(boolean isOut, int color, int grayColor) { + public void drawGradientBackground(Canvas canvas, Rect bounds, float alpha) { + + } + + public void setColor(int color, int grayColor, boolean isOut, float bgBack) { boolean disabled = !premium; // if (disabled) { // color = ColorUtils.blendARGB(color, grayColor, isOut ? .6f : .8f); -// color = ColorUtils.setAlphaComponent(color, (int) (Color.alpha(color) * .8f)); +// color = ColorUtils.setAlphaComponent(color, (int) (Color.alpha(color) * .6f)); // } boolean newColor = this.color != color; this.iconColor = this.color = color; this.backgroundColor = ColorUtils.setAlphaComponent(color, (int) (Color.alpha(color) * 0.156f)); + this.backgroundBack = bgBack; this.rippleColor = Theme.blendOver(this.backgroundColor, ColorUtils.setAlphaComponent(color, (int) (Color.alpha(color) * (Theme.isCurrentThemeDark() ? .3f : .2f)))); if (backgroundPaint == null) { backgroundPaint = new Paint(); } backgroundPaint.setColor(this.backgroundColor); + backgroundPaint.setAlpha((int) (backgroundPaint.getAlpha() * (1f - bgBack))); if (newColor || selectorDrawable == null) { selectorDrawable = Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(8), 0, this.rippleColor); selectorDrawable.setCallback(parent); @@ -222,38 +246,67 @@ public class TranscribeButton { inIconDrawable.commitApplyLayerColors(); inIconDrawable.setAllowDecodeSingleFrame(true); inIconDrawable.updateCurrentFrame(0, false); + inIconDrawable.setAlpha(inIconDrawableAlpha = (int) (Color.alpha(color))); outIconDrawable.beginApplyLayerColors(); outIconDrawable.setLayerColor("Artboard Outlines.**", this.iconColor); outIconDrawable.commitApplyLayerColors(); outIconDrawable.setAllowDecodeSingleFrame(true); outIconDrawable.updateCurrentFrame(0, false); + outIconDrawable.setAlpha(outIconDrawableAlpha = (int) (Color.alpha(color))); } -// inIconDrawable.setAlpha(disabled ? 125 : 255); -// outIconDrawable.setAlpha(disabled ? 125 : 255); if (strokePaint == null) { strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); strokePaint.setStyle(Paint.Style.STROKE); + strokePaint.setStrokeCap(Paint.Cap.ROUND); } strokePaint.setColor(color); } private final FastOutSlowInInterpolator interpolator = new FastOutSlowInInterpolator(); private Path boundsPath; + private Path loadingPath; + private int radius, diameter; - public void draw(Canvas canvas) { - this.bounds.set(0, AndroidUtilities.dp(3), AndroidUtilities.dp(30), AndroidUtilities.dp(3 + 24)); + private float a, b; + public void setBounds(int x, int y, int w, int h, int r) { + if (w != this.bounds.width() || h != this.bounds.height()) { + a = (float) (Math.atan((w/2f-r) / (h/2f)) * 180f / Math.PI); + b = (float) (Math.atan((w/2f) / (h/2f-r)) * 180f / Math.PI); + } + this.bounds.set(x, y, x + w, y + h); + this.radius = Math.min(Math.min(w, h) / 2, r); + this.diameter = this.radius * 2; + } + + public int width() { + return this.bounds.width(); + } + + public int height() { + return this.bounds.height(); + } + + public void draw(Canvas canvas, float alpha) { this.pressBounds.set(this.bounds.left - AndroidUtilities.dp(8), this.bounds.top - AndroidUtilities.dp(8), this.bounds.right + AndroidUtilities.dp(8), this.bounds.bottom + AndroidUtilities.dp(8)); if (boundsPath == null) { boundsPath = new Path(); - AndroidUtilities.rectTmp.set(this.bounds); - boundsPath.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(8), AndroidUtilities.dp(8), Path.Direction.CW); + } else { + boundsPath.rewind(); } + AndroidUtilities.rectTmp.set(this.bounds); + boundsPath.addRoundRect(AndroidUtilities.rectTmp, this.radius, this.radius, Path.Direction.CW); canvas.save(); canvas.clipPath(boundsPath); - if (backgroundPaint != null) { - canvas.drawRect(this.bounds, backgroundPaint); + if (backgroundBack * alpha > 0) { + drawGradientBackground(canvas, this.bounds, backgroundBack * alpha); } - if (selectorDrawable != null && premium) { + if (backgroundPaint != null) { + int wasAlpha = backgroundPaint.getAlpha(); + backgroundPaint.setAlpha((int) (wasAlpha * alpha)); + canvas.drawRect(this.bounds, backgroundPaint); + backgroundPaint.setAlpha(wasAlpha); + } + if (selectorDrawable != null) { selectorDrawable.setBounds(bounds); selectorDrawable.draw(canvas); } @@ -263,30 +316,47 @@ public class TranscribeButton { if (loadingT > 0f) { float[] segments = getSegments((long) ((SystemClock.elapsedRealtime() - start) * .75f)); - canvas.save(); if (progressClipPath == null) { progressClipPath = new Path(); + } else { + progressClipPath.rewind(); } - progressClipPath.reset(); - AndroidUtilities.rectTmp.set(pressBounds); float segmentLength = Math.max(40 * loadingT, segments[1] - segments[0]); - progressClipPath.addArc(AndroidUtilities.rectTmp, segments[0] + segmentLength * (1f - loadingT) * (loading ? 0f : 1f), segmentLength * loadingT); - progressClipPath.lineTo(AndroidUtilities.rectTmp.centerX(), AndroidUtilities.rectTmp.centerY()); - progressClipPath.close(); - canvas.clipPath(progressClipPath); - AndroidUtilities.rectTmp.set(bounds); + float from = segments[0] + segmentLength * (1f - loadingT) * (loading ? 0f : 1f), to = from + segmentLength * loadingT; + + from = from % 360; + to = to % 360; + if (from < 0) + from += 360; + if (to < 0) + to += 360; + + addLine(progressClipPath, bounds.centerX(), bounds.top, bounds.right - radius, bounds.top, from, to, 0, a); + addCorner(progressClipPath, bounds.right, bounds.top, diameter, 1, from, to, a, b); + addLine(progressClipPath, bounds.right, bounds.top + radius, bounds.right, bounds.bottom - radius, from, to, b, 180 - b); + addCorner(progressClipPath, bounds.right, bounds.bottom, diameter, 2, from, to, 180 - b, 180 - a); + addLine(progressClipPath, bounds.right - radius, bounds.bottom, bounds.left + radius, bounds.bottom, from, to, 180 - a, 180 + a); + addCorner(progressClipPath, bounds.left, bounds.bottom, diameter, 3, from, to, 180 + a, 180 + b); + addLine(progressClipPath, bounds.left, bounds.bottom - radius, bounds.left, bounds.top + radius, from, to, 180 + b, 360 - b); + addCorner(progressClipPath, bounds.left, bounds.top, diameter, 4, from, to, 360 - b, 360 - a); + addLine(progressClipPath, bounds.left + radius, bounds.top, bounds.centerX(), bounds.top, from, to, 360 - a, 360); + strokePaint.setStrokeWidth(AndroidUtilities.dp(1.5f)); - canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(8), AndroidUtilities.dp(8), strokePaint); - canvas.restore(); + int wasAlpha = strokePaint.getAlpha(); + strokePaint.setAlpha((int) (wasAlpha * alpha)); + canvas.drawPath(progressClipPath, strokePaint); + strokePaint.setAlpha(wasAlpha); parent.invalidate(); } canvas.save(); - canvas.translate(AndroidUtilities.dp(2), AndroidUtilities.dp(2)); + canvas.translate(bounds.centerX() + AndroidUtilities.dp(2 - 15), bounds.centerY() + AndroidUtilities.dp(-1 - 12)); if (isOpen) { + inIconDrawable.setAlpha((int) (inIconDrawableAlpha * alpha)); inIconDrawable.draw(canvas); } else { + outIconDrawable.setAlpha((int) (outIconDrawableAlpha * alpha)); outIconDrawable.draw(canvas); } canvas.restore(); @@ -300,16 +370,103 @@ public class TranscribeButton { final long t = d % 5400L; segments[0] = 1520 * t / 5400f - 20; segments[1] = 1520 * t / 5400f; - float fraction; for (int i = 0; i < 4; ++i) { - fraction = (t - i * 1350) / 667f; - segments[1] += interpolator.getInterpolation(fraction) * 250; - fraction = (t - (667 + i * 1350)) / 667f; - segments[0] += interpolator.getInterpolation(fraction) * 250; + segments[1] += interpolator.getInterpolation((t - i * 1350) / 667f) * 250; + segments[0] += interpolator.getInterpolation((t - (667 + i * 1350)) / 667f) * 250; } return segments; } + private void addLine( + Path path, + int x1, + int y1, + int x2, + int y2, + float L, float R, + float l, float r + ) { + if (x1 == x2 && y1 == y2) { + return; + } + if (L > R) { + addLine(path, x1, y1, x2, y2, (L - l) / (r - l), 1f); + addLine(path, x1, y1, x2, y2, 0, (R - l) / (r - l)); + } else { + addLine(path, x1, y1, x2, y2, Math.max(0, L - l) / (r - l), (Math.min(R, r) - l) / (r - l)); + } + } + + private void addLine( + Path path, + int x1, + int y1, + int x2, + int y2, + float a, + float b + ) { + if (x1 == x2 && y1 == y2) { + return; + } + a = MathUtils.clamp(a, 0, 1); + b = MathUtils.clamp(b, 0, 1); + if (b - a <= 0) { + return; + } + path.moveTo( + AndroidUtilities.lerp(x1, x2, a), + AndroidUtilities.lerp(y1, y2, a) + ); + path.lineTo( + AndroidUtilities.lerp(x1, x2, b), + AndroidUtilities.lerp(y1, y2, b) + ); + } + + private void addCorner( + Path path, + int x1, + int y1, + int d, + int side, + float L, float R, + float l, float r + ) { + if (L > R) { + addCorner(path, x1, y1, d, side, (L - l) / (r - l), 1f); + addCorner(path, x1, y1, d, side, 0, (R - l) / (r - l)); + } else { + addCorner(path, x1, y1, d, side, Math.max(0, L - l) / (r - l), (Math.min(R, r) - l) / (r - l)); + } + } + + private void addCorner( + Path path, + int cx, // stands for x of corner, not center + int cy, + int d, + int side, + float a, + float b + ) { + a = MathUtils.clamp(a, 0, 1); + b = MathUtils.clamp(b, 0, 1); + if (b - a <= 0) { + return; + } + if (side == 1) { // top-right + AndroidUtilities.rectTmp.set(cx-d,cy,cx,cy+d); + } else if (side == 2) { // bottom-right + AndroidUtilities.rectTmp.set(cx-d,cy-d,cx,cy); + } else if (side == 3) { // bottom-left + AndroidUtilities.rectTmp.set(cx,cy-d,cx+d,cy); + } else if (side == 4) { // top-left + AndroidUtilities.rectTmp.set(cx,cy,cx+d,cy+d); + } + path.addArc(AndroidUtilities.rectTmp, -180+side*90+(90*a), 90*(b-a)); + } + public static class LoadingPointsSpan extends ImageSpan { private static LoadingPointsDrawable drawable; @@ -389,6 +546,27 @@ public class TranscribeButton { private static HashMap transcribeOperationsById; private static HashMap transcribeOperationsByDialogPosition; + private static ArrayList videoTranscriptionsOpen; + + public static void openVideoTranscription(MessageObject messageObject) { + if (messageObject == null || isVideoTranscriptionOpen(messageObject)) { + return; + } + if (videoTranscriptionsOpen == null) { + videoTranscriptionsOpen = new ArrayList<>(1); + } + videoTranscriptionsOpen.add(reqInfoHash(messageObject)); + } + + public static boolean isVideoTranscriptionOpen(MessageObject messageObject) { + return videoTranscriptionsOpen != null && (!messageObject.isRoundVideo() || videoTranscriptionsOpen.contains(reqInfoHash(messageObject))); + } + + public static void resetVideoTranscriptionsOpen() { + if (videoTranscriptionsOpen != null) { + videoTranscriptionsOpen.clear(); + } + } public static boolean isTranscribing(MessageObject messageObject) { return ( @@ -408,12 +586,16 @@ public class TranscribeButton { int messageId = messageObject.messageOwner.id; if (open) { if (messageObject.messageOwner.voiceTranscription != null && messageObject.messageOwner.voiceTranscriptionFinal) { + TranscribeButton.openVideoTranscription(messageObject); messageObject.messageOwner.voiceTranscriptionOpen = true; MessagesStorage.getInstance(account).updateMessageVoiceTranscriptionOpen(dialogId, messageId, messageObject.messageOwner); AndroidUtilities.runOnUIThread(() -> { NotificationCenter.getInstance(account).postNotificationName(NotificationCenter.voiceTranscriptionUpdate, messageObject, null, null, (Boolean) true, (Boolean) true); }); } else { + if (BuildVars.LOGS_ENABLED) { + FileLog.d("sending Transcription request, msg_id=" + messageId + " dialog_id=" + dialogId); + } TLRPC.TL_messages_transcribeAudio req = new TLRPC.TL_messages_transcribeAudio(); req.peer = peer; req.msg_id = messageId; @@ -445,10 +627,11 @@ public class TranscribeButton { final String finalText = text; final long finalId = id; final long duration = SystemClock.elapsedRealtime() - start; + TranscribeButton.openVideoTranscription(messageObject); messageObject.messageOwner.voiceTranscriptionOpen = true; messageObject.messageOwner.voiceTranscriptionFinal = isFinal; if (BuildVars.LOGS_ENABLED) { - FileLog.e("Transcription request sent, received final=" + isFinal + " id=" + finalId + " text=" + finalText); + FileLog.d("Transcription request sent, received final=" + isFinal + " id=" + finalId + " text=" + finalText); } MessagesStorage.getInstance(account).updateMessageVoiceTranscription(dialogId, messageId, finalText, messageObject.messageOwner); @@ -494,4 +677,22 @@ public class TranscribeButton { } catch (Exception ignore) {} return false; } + + public static void showOffTranscribe(MessageObject messageObject) { + showOffTranscribe(messageObject, true); + } + + public static void showOffTranscribe(MessageObject messageObject, boolean notify) { + if (messageObject == null || messageObject.messageOwner == null) { + return; + } + final MessageObject finalMessageObject = messageObject; + messageObject.messageOwner.voiceTranscriptionForce = true; + MessagesStorage.getInstance(messageObject.currentAccount).updateMessageVoiceTranscriptionOpen(messageObject.getDialogId(), messageObject.getId(), messageObject.messageOwner); + if (notify) { + AndroidUtilities.runOnUIThread(() -> { + NotificationCenter.getInstance(finalMessageObject.currentAccount).postNotificationName(NotificationCenter.voiceTranscriptionUpdate, finalMessageObject); + }); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/UndoView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/UndoView.java index 4731a8e0a..6eb4f069e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/UndoView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/UndoView.java @@ -12,7 +12,6 @@ import android.graphics.PorterDuffColorFilter; import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; -import android.os.Build; import android.os.SystemClock; import android.text.Layout; import android.text.Selection; @@ -36,6 +35,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.DrawableRes; import androidx.annotation.Keep; import org.telegram.messenger.AndroidUtilities; @@ -177,6 +177,9 @@ public class UndoView extends FrameLayout { public final static int ACTION_PREVIEW_MEDIA_DESELECTED = 82; public static int ACTION_RINGTONE_ADDED = 83; + public final static int ACTION_PREMIUM_TRANSCRIPTION = 84; + public final static int ACTION_HINT_SWIPE_TO_REPLY = 85; + public final static int ACTION_PREMIUM_ALL_FOLDER = 86; private CharSequence infoText; private int hideAnimationType = 1; @@ -303,7 +306,7 @@ public class UndoView extends FrameLayout { textPaint.setColor(getThemedColor(Theme.key_undo_infoColor)); setWillNotDraw(false); - backgroundDrawable = Theme.createRoundRectDrawable(AndroidUtilities.dp(6), getThemedColor(Theme.key_undo_background)); + backgroundDrawable = Theme.createRoundRectDrawable(AndroidUtilities.dp(10), getThemedColor(Theme.key_undo_background)); setOnTouchListener((v, event) -> true); @@ -334,7 +337,7 @@ public class UndoView extends FrameLayout { private boolean hasSubInfo() { return currentAction == ACTION_QR_SESSION_ACCEPTED || currentAction == ACTION_PROXIMITY_SET || currentAction == ACTION_ARCHIVE_HIDDEN || currentAction == ACTION_ARCHIVE_HINT || currentAction == ACTION_ARCHIVE_FEW_HINT || currentAction == ACTION_QUIZ_CORRECT || currentAction == ACTION_QUIZ_INCORRECT || - currentAction == ACTION_REPORT_SENT || currentAction == ACTION_ARCHIVE_PINNED && MessagesController.getInstance(currentAccount).dialogFilters.isEmpty() || currentAction == ACTION_RINGTONE_ADDED; + currentAction == ACTION_REPORT_SENT || currentAction == ACTION_ARCHIVE_PINNED && MessagesController.getInstance(currentAccount).dialogFilters.isEmpty() || currentAction == ACTION_RINGTONE_ADDED || currentAction == ACTION_HINT_SWIPE_TO_REPLY; } public boolean isMultilineSubInfo() { @@ -456,6 +459,8 @@ public class UndoView extends FrameLayout { undoTextView.setText(LocaleController.getString("Undo", R.string.Undo).toUpperCase()); undoImageView.setVisibility(VISIBLE); leftImageView.setPadding(0, 0, 0, 0); + leftImageView.setScaleX(1); + leftImageView.setScaleY(1); infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); avatarImageView.setVisibility(GONE); @@ -490,6 +495,7 @@ public class UndoView extends FrameLayout { if (isTooltipAction()) { CharSequence infoText; CharSequence subInfoText; + @DrawableRes int icon; int size = 36; boolean iconIsDrawable = false; @@ -1016,7 +1022,7 @@ public class UndoView extends FrameLayout { timeLeft = 3000; } else if (currentAction == ACTION_FWD_MESSAGES) { Integer count = (Integer) infoObject; - if (infoObject2 == null) { + if (infoObject2 == null || infoObject2 instanceof TLRPC.TL_forumTopic) { if (did == UserConfig.getInstance(currentAccount).clientUserId) { if (count == 1) { infoTextView.setText(AndroidUtilities.replaceTags(LocaleController.getString("FwdMessageToSavedMessages", R.string.FwdMessageToSavedMessages))); @@ -1027,10 +1033,11 @@ public class UndoView extends FrameLayout { } else { if (DialogObject.isChatDialog(did)) { TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-did); + TLRPC.TL_forumTopic topic = (TLRPC.TL_forumTopic) infoObject2; if (count == 1) { - infoTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("FwdMessageToGroup", R.string.FwdMessageToGroup, chat.title))); + infoTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("FwdMessageToGroup", R.string.FwdMessageToGroup, topic != null ? topic.title : chat.title))); } else { - infoTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("FwdMessagesToGroup", R.string.FwdMessagesToGroup, chat.title))); + infoTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("FwdMessagesToGroup", R.string.FwdMessagesToGroup, topic != null ? topic.title : chat.title))); } } else { TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(did); @@ -1166,7 +1173,7 @@ public class UndoView extends FrameLayout { leftImageView.playAnimation(); } else if (currentAction == ACTION_FILTERS_AVAILABLE) { timeLeft = 10000; - undoTextView.setText(LocaleController.getString("Open", R.string.Open).toUpperCase()); + undoTextView.setText(LocaleController.getString("Open", R.string.Open)); infoTextView.setText(LocaleController.getString("FilterAvailableTitle", R.string.FilterAvailableTitle)); leftImageView.setAnimation(R.raw.filter_new, 36, 36); int margin = (int) Math.ceil(undoTextView.getPaint().measureText(undoTextView.getText().toString())) + AndroidUtilities.dp(26); @@ -1206,7 +1213,7 @@ public class UndoView extends FrameLayout { if ("\uD83C\uDFB2".equals(emoji)) { infoTextView.setText(AndroidUtilities.replaceTags(LocaleController.getString("DiceInfo2", R.string.DiceInfo2))); leftImageView.setImageResource(R.drawable.dice); - } else{ + } else { if ("\uD83C\uDFAF".equals(emoji)) { infoTextView.setText(AndroidUtilities.replaceTags(LocaleController.getString("DartInfo", R.string.DartInfo))); } else { @@ -1299,6 +1306,47 @@ public class UndoView extends FrameLayout { undoTextView.setVisibility(GONE); undoButton.setVisibility(VISIBLE); leftImageView.setVisibility(VISIBLE); + } else if (currentAction == ACTION_PREMIUM_TRANSCRIPTION) { + infoTextView.setVisibility(VISIBLE); + infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + infoTextView.setTypeface(Typeface.DEFAULT); + infoTextView.setText(AndroidUtilities.replaceTags(LocaleController.getString("UnlockPremiumTranscriptionHint", R.string.UnlockPremiumTranscriptionHint))); + leftImageView.setVisibility(VISIBLE); + leftImageView.setAnimation(R.raw.voice_to_text, 36, 36); + leftImageView.setProgress(0); + leftImageView.playAnimation(); + + undoTextView.setText(LocaleController.getString("PremiumMore", R.string.PremiumMore)); + layoutParams.leftMargin = AndroidUtilities.dp(58); + layoutParams.rightMargin = (int) Math.ceil(undoTextView.getPaint().measureText(undoTextView.getText().toString())) + AndroidUtilities.dp(26); + layoutParams.topMargin = layoutParams.bottomMargin = AndroidUtilities.dp(6); + layoutParams.height = TableLayout.LayoutParams.WRAP_CONTENT; + + avatarImageView.setVisibility(GONE); + subinfoTextView.setVisibility(GONE); + undoTextView.setVisibility(VISIBLE); + undoButton.setVisibility(VISIBLE); + undoImageView.setVisibility(GONE); + } else if (currentAction == ACTION_HINT_SWIPE_TO_REPLY) { + infoTextView.setVisibility(VISIBLE); + infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + infoTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + infoTextView.setText(LocaleController.getString("SwipeToReplyHint", R.string.SwipeToReplyHint)); + leftImageView.setVisibility(VISIBLE); + leftImageView.setAnimation(R.raw.hint_swipe_reply, (int) (36 * 1.8f), (int) (36 * 1.8f)); + leftImageView.setProgress(0); + leftImageView.playAnimation(); + + subinfoTextView.setVisibility(VISIBLE); + subinfoTextView.setText(LocaleController.getString("SwipeToReplyHintMessage", R.string.SwipeToReplyHintMessage)); + + layoutParams.leftMargin = AndroidUtilities.dp(58); + layoutParams.rightMargin = (int) Math.ceil(undoTextView.getPaint().measureText(undoTextView.getText().toString())) + AndroidUtilities.dp(26); + layoutParams.topMargin = AndroidUtilities.dp(6); + layoutParams.height = TableLayout.LayoutParams.WRAP_CONTENT; + + avatarImageView.setVisibility(GONE); + undoButton.setVisibility(GONE); } else if (currentAction == ACTION_ARCHIVE || currentAction == ACTION_ARCHIVE_FEW) { if (action == ACTION_ARCHIVE) { infoTextView.setText(LocaleController.getString("ChatArchived", R.string.ChatArchived)); @@ -1398,7 +1446,7 @@ public class UndoView extends FrameLayout { } width -= AndroidUtilities.dp(16); measureChildWithMargins(infoTextView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 0, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0); - undoViewHeight = infoTextView.getMeasuredHeight() + AndroidUtilities.dp(currentAction == ACTION_DICE_INFO || currentAction == ACTION_DICE_NO_SEND_INFO || currentAction == ACTION_TEXT_INFO ? 14 : 28); + undoViewHeight = infoTextView.getMeasuredHeight() + AndroidUtilities.dp(currentAction == ACTION_DICE_INFO || currentAction == ACTION_DICE_NO_SEND_INFO || currentAction == ACTION_TEXT_INFO || currentAction == ACTION_PREMIUM_TRANSCRIPTION || currentAction == ACTION_PREMIUM_ALL_FOLDER ? 14 : 28); if (currentAction == ACTION_TEXT_INFO) { undoViewHeight = Math.max(undoViewHeight, AndroidUtilities.dp(52)); } else if (currentAction == ACTION_PROXIMITY_REMOVED) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/UnreadCounterTextView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/UnreadCounterTextView.java new file mode 100644 index 000000000..40e23811c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/UnreadCounterTextView.java @@ -0,0 +1,301 @@ +package org.telegram.ui.Components; + + +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.view.MotionEvent; +import android.view.View; + +import androidx.core.graphics.ColorUtils; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.ui.ActionBar.Theme; + +public class UnreadCounterTextView extends View { + + private int currentCounter; + private String currentCounterString; + private int textWidth; + private TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + private RectF rect = new RectF(); + private int circleWidth; + private int rippleColor; + + private Drawable icon; + private StaticLayout textLayout; + private Drawable iconOut; + private StaticLayout textLayoutOut; + private int layoutTextWidth; + private TextPaint layoutPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + + Drawable selectableBackground; + + ValueAnimator replaceAnimator; + float replaceProgress = 1f; + boolean animatedFromBottom; + int textColor; + int panelBackgroundColor; + int counterColor; + CharSequence lastText; + + public UnreadCounterTextView(Context context) { + super(context); + textPaint.setTextSize(AndroidUtilities.dp(13)); + textPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + + layoutPaint.setTextSize(AndroidUtilities.dp(15)); + layoutPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + } + + public void setText(CharSequence text, boolean animatedFromBottom) { + if (lastText == text) { + return; + } + lastText = text; + this.animatedFromBottom = animatedFromBottom; + textLayoutOut = textLayout; + iconOut = icon; + layoutPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + layoutTextWidth = (int) Math.ceil(layoutPaint.measureText(text, 0, text.length())); + icon = null; + textLayout = new StaticLayout(text, layoutPaint, layoutTextWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true); + setContentDescription(text); + invalidate(); + + if (textLayoutOut != null || iconOut != null) { + if (replaceAnimator != null) { + replaceAnimator.cancel(); + } + replaceProgress = 0; + replaceAnimator = ValueAnimator.ofFloat(0,1f); + replaceAnimator.addUpdateListener(animation -> { + replaceProgress = (float) animation.getAnimatedValue(); + invalidate(); + }); + replaceAnimator.setDuration(150); + replaceAnimator.start(); + } + } + + public void setText(CharSequence text) { + layoutPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + layoutTextWidth = (int) Math.ceil(layoutPaint.measureText(text, 0, text.length())); + icon = null; + textLayout = new StaticLayout(text, layoutPaint, layoutTextWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true); + setContentDescription(text); + invalidate(); + } + + public void setTextInfo(CharSequence text) { + layoutPaint.setTypeface(null); + layoutTextWidth = (int) Math.ceil(layoutPaint.measureText(text, 0, text.length())); + icon = null; + textLayout = new StaticLayout(text, layoutPaint, layoutTextWidth + 1, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true); + setContentDescription(text); + invalidate(); + } + + public void setTextInfo(Drawable icon, CharSequence text) { + layoutPaint.setTypeface(null); + layoutTextWidth = (int) Math.ceil(layoutPaint.measureText(text, 0, text.length())); + this.icon = icon; + textLayout = new StaticLayout(text, layoutPaint, layoutTextWidth + 1, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true); + setContentDescription(text); + invalidate(); + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + if (selectableBackground != null) { + selectableBackground.setState(getDrawableState()); + } + } + + @Override + public boolean verifyDrawable(Drawable drawable) { + if (selectableBackground != null) { + return selectableBackground == drawable || super.verifyDrawable(drawable); + } + return super.verifyDrawable(drawable); + } + + @Override + public void jumpDrawablesToCurrentState() { + super.jumpDrawablesToCurrentState(); + if (selectableBackground != null) { + selectableBackground.jumpToCurrentState(); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (textLayout != null) { + int lineWidth = (int) Math.ceil(textLayout.getLineWidth(0)); + int contentWidth; + if (getMeasuredWidth() == ((View)getParent()).getMeasuredWidth()) { + contentWidth = getMeasuredWidth() - AndroidUtilities.dp(96); + } else { + if (isTouchFullWidth()) { + contentWidth = getMeasuredWidth(); + } else { + contentWidth = lineWidth + (circleWidth > 0 ? circleWidth + AndroidUtilities.dp(8) : 0); + contentWidth += AndroidUtilities.dp(48); + } + } + int x = (getMeasuredWidth() - contentWidth) / 2; + rect.set( + x, getMeasuredHeight() / 2f - contentWidth / 2f, + x + contentWidth, getMeasuredHeight() / 2f + contentWidth / 2f + ); + if (!rect.contains(event.getX(), event.getY())) { + setPressed(false); + return false; + } + } + } + return super.onTouchEvent(event); + } + + protected Theme.ResourcesProvider getResourceProvider() { + return null; + } + + protected boolean isTouchFullWidth() { + return false; + } + + protected void updateCounter() { + } + + protected float getTopOffset() { + return 0; + } + + public void setCounter(int newCount) { + if (currentCounter != newCount) { + currentCounter = newCount; + if (currentCounter == 0) { + currentCounterString = null; + circleWidth = 0; + } else { + currentCounterString = AndroidUtilities.formatWholeNumber(currentCounter, 0); + textWidth = (int) Math.ceil(textPaint.measureText(currentCounterString)); + int newWidth = Math.max(AndroidUtilities.dp(20), AndroidUtilities.dp(12) + textWidth); + if (circleWidth != newWidth) { + circleWidth = newWidth; + } + } + invalidate(); + } + } + + @Override + protected void onDraw(Canvas canvas) { + Layout layout = textLayout; + int color = Theme.getColor(isEnabled() ? Theme.key_chat_fieldOverlayText : Theme.key_windowBackgroundWhiteGrayText, getResourceProvider()); + if (textColor != color) { + layoutPaint.setColor(textColor = color); + } + color = Theme.getColor(Theme.key_chat_messagePanelBackground, getResourceProvider()); + if (panelBackgroundColor != color) { + textPaint.setColor(panelBackgroundColor = color); + } + color = Theme.getColor(Theme.key_chat_goDownButtonCounterBackground, getResourceProvider()); + if (counterColor != color) { + paint.setColor(counterColor = color); + } + + if (getParent() != null) { + int contentWidth = getMeasuredWidth(); + int x = (getMeasuredWidth() - contentWidth) / 2; + if (rippleColor != Theme.getColor(Theme.key_chat_fieldOverlayText, getResourceProvider()) || selectableBackground == null) { + selectableBackground = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(60), 0, ColorUtils.setAlphaComponent(rippleColor = Theme.getColor(Theme.key_chat_fieldOverlayText, getResourceProvider()), 26)); + selectableBackground.setCallback(this); + } + int start = (getLeft() + x) <= 0 ? x - AndroidUtilities.dp(20) : x; + int end = x + contentWidth > ((View) getParent()).getMeasuredWidth() ? x + contentWidth + AndroidUtilities.dp(20) : x + contentWidth; + selectableBackground.setBounds( + start, getMeasuredHeight() / 2 - contentWidth / 2, + end, getMeasuredHeight() / 2 + contentWidth / 2 + ); + selectableBackground.draw(canvas); + } + if (textLayout != null) { + canvas.save(); + if (replaceProgress != 1f && textLayoutOut != null) { + int oldAlpha = layoutPaint.getAlpha(); + + canvas.save(); + canvas.translate((getMeasuredWidth() - textLayoutOut.getWidth()) / 2 - circleWidth / 2, (getMeasuredHeight() - textLayout.getHeight()) / 2 + getTopOffset()); + canvas.translate(+(iconOut != null ? iconOut.getIntrinsicWidth() / 2 + AndroidUtilities.dp(3) : 0), (animatedFromBottom ? -1f : 1f) * AndroidUtilities.dp(18) * replaceProgress); + if (iconOut != null) { + iconOut.setBounds( + -iconOut.getIntrinsicWidth() - AndroidUtilities.dp(6), + (textLayout.getHeight() - iconOut.getIntrinsicHeight()) / 2 + AndroidUtilities.dp(1), + -AndroidUtilities.dp(6), + (textLayout.getHeight() + iconOut.getIntrinsicHeight()) / 2 + AndroidUtilities.dp(1) + ); + iconOut.setAlpha((int) (oldAlpha * (1f - replaceProgress))); + iconOut.draw(canvas); + } + layoutPaint.setAlpha((int) (oldAlpha * (1f - replaceProgress))); + textLayoutOut.draw(canvas); + canvas.restore(); + + canvas.save(); + canvas.translate((getMeasuredWidth() - layoutTextWidth) / 2 - circleWidth / 2, (getMeasuredHeight() - textLayout.getHeight()) / 2 + getTopOffset()); + canvas.translate(+(icon != null ? icon.getIntrinsicWidth() / 2 + AndroidUtilities.dp(3) : 0), (animatedFromBottom ? 1f : -1f) * AndroidUtilities.dp(18) * (1f - replaceProgress)); + if (icon != null) { + icon.setBounds( + -icon.getIntrinsicWidth() - AndroidUtilities.dp(6), + (textLayout.getHeight() - icon.getIntrinsicHeight()) / 2 + AndroidUtilities.dp(1), + -AndroidUtilities.dp(6), + (textLayout.getHeight() + icon.getIntrinsicHeight()) / 2 + AndroidUtilities.dp(1) + ); + icon.setAlpha((int) (oldAlpha * (replaceProgress))); + icon.draw(canvas); + } + layoutPaint.setAlpha((int) (oldAlpha * (replaceProgress))); + textLayout.draw(canvas); + canvas.restore(); + + layoutPaint.setAlpha(oldAlpha); + } else { + canvas.translate((getMeasuredWidth() - layoutTextWidth) / 2 - circleWidth / 2 + (icon != null ? icon.getIntrinsicWidth() / 2 + AndroidUtilities.dp(3) : 0), (getMeasuredHeight() - textLayout.getHeight()) / 2 + getTopOffset()); + if (icon != null) { + icon.setBounds( + -icon.getIntrinsicWidth()-AndroidUtilities.dp(6), + (textLayout.getHeight() - icon.getIntrinsicHeight()) / 2 + AndroidUtilities.dp(1), + -AndroidUtilities.dp(6), + (textLayout.getHeight() + icon.getIntrinsicHeight()) / 2 + AndroidUtilities.dp(1) + ); + icon.setAlpha(255); + icon.draw(canvas); + } + textLayout.draw(canvas); + } + + canvas.restore(); + } + + if (currentCounterString != null) { + if (layout != null) { + int lineWidth = (int) Math.ceil(layout.getLineWidth(0)); + int x = (getMeasuredWidth() - lineWidth) / 2 + lineWidth - circleWidth / 2 + AndroidUtilities.dp(6); + rect.set(x, getMeasuredHeight() / 2 - AndroidUtilities.dp(10), x + circleWidth, getMeasuredHeight() / 2 + AndroidUtilities.dp(10)); + canvas.drawRoundRect(rect, AndroidUtilities.dp(10), AndroidUtilities.dp(10), paint); + canvas.drawText(currentCounterString, rect.centerX() - textWidth / 2.0f, rect.top + AndroidUtilities.dp(14.5f), textPaint); + } + } + } +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/UsersAlertBase.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/UsersAlertBase.java index 0c72a6a68..11160a149 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/UsersAlertBase.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/UsersAlertBase.java @@ -182,7 +182,7 @@ public class UsersAlertBase extends BottomSheet { setColorProgress(0.0f); listView.setEmptyView(emptyView); - listView.setAnimateEmptyView(true, 0); + listView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); } @Override @@ -299,7 +299,7 @@ public class UsersAlertBase extends BottomSheet { if (TextUtils.isEmpty(text) && listView != null && listView.getAdapter() != listViewAdapter) { listView.setAnimateEmptyView(false, 0); listView.setAdapter(listViewAdapter); - listView.setAnimateEmptyView(true, 0); + listView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); if (oldItemsCount == 0) { showItemsAnimated(0); } @@ -360,6 +360,7 @@ public class UsersAlertBase extends BottomSheet { backgroundColor = AndroidUtilities.getOffsetColor(Theme.getColor(keyInviteMembersBackground), Theme.getColor(keyListViewBackground), progress, 1.0f); shadowDrawable.setColorFilter(new PorterDuffColorFilter(backgroundColor, PorterDuff.Mode.MULTIPLY)); frameLayout.setBackgroundColor(backgroundColor); + fixNavigationBar(backgroundColor); navBarColor = backgroundColor; listView.setGlowColor(backgroundColor); @@ -628,13 +629,24 @@ public class UsersAlertBase extends BottomSheet { canvas.drawRoundRect(rect, AndroidUtilities.dp(2), AndroidUtilities.dp(2), Theme.dialogs_onlineCirclePaint); if (statusBarHeight > 0) { - int finalColor = Color.argb(0xff, (int) (Color.red(backgroundColor) * 0.8f), (int) (Color.green(backgroundColor) * 0.8f), (int) (Color.blue(backgroundColor) * 0.8f)); - Theme.dialogs_onlineCirclePaint.setColor(finalColor); + Theme.dialogs_onlineCirclePaint.setColor(backgroundColor); canvas.drawRect(backgroundPaddingLeft, AndroidUtilities.statusBarHeight - statusBarHeight - getTranslationY(), getMeasuredWidth() - backgroundPaddingLeft, AndroidUtilities.statusBarHeight - getTranslationY(), Theme.dialogs_onlineCirclePaint); } + updateLightStatusBar(statusBarHeight > AndroidUtilities.statusBarHeight / 2); canvas.restore(); } + private Boolean statusBarOpen; + private void updateLightStatusBar(boolean open) { + if (statusBarOpen != null && statusBarOpen == open) { + return; + } + boolean openBgLight = AndroidUtilities.computePerceivedBrightness(getThemedColor(Theme.key_dialogBackground)) > .721f; + boolean closedBgLight = AndroidUtilities.computePerceivedBrightness(Theme.blendOver(getThemedColor(Theme.key_actionBarDefault), 0x33000000)) > .721f; + boolean isLight = (statusBarOpen = open) ? openBgLight : closedBgLight; + AndroidUtilities.setLightStatusBar(getWindow(), isLight); + } + @Override protected void dispatchDraw(Canvas canvas) { canvas.save(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java index a47569917..70ff32864 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java @@ -442,7 +442,6 @@ public class VideoPlayer implements ExoPlayer.EventListener, SimpleExoPlayer.Vid audioUpdateHandler.removeCallbacksAndMessages(null); audioVisualizerDelegate.onVisualizerUpdate(false, true, null); } - } public void setPlaybackSpeed(float speed) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/VoIPToggleButton.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/VoIPToggleButton.java index d87163a98..7cf8598bf 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/VoIPToggleButton.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/VoIPToggleButton.java @@ -448,8 +448,8 @@ public class VoIPToggleButton extends FrameLayout { } public void shakeView() { - AndroidUtilities.shakeView(textView[0], 2, 0); - AndroidUtilities.shakeView(textView[1], 2, 0); + AndroidUtilities.shakeView(textView[0]); + AndroidUtilities.shakeView(textView[1]); } public void showText(boolean show, boolean animated) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java index 5026cfe99..fa00a282a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java @@ -22,24 +22,23 @@ import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; -import org.telegram.messenger.AndroidUtilities; import org.telegram.PhoneFormat.PhoneFormat; +import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; -import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocaleController; -import org.telegram.messenger.UserObject; -import org.telegram.tgnet.TLRPC; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; +import org.telegram.messenger.UserObject; +import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.CheckBoxCell; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; -import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.LayoutHelper; @@ -63,6 +62,9 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent private boolean needAddException; private String phone; + private String firstNameFromCard; + private String lastNameFromCard; + private ContactAddActivityDelegate delegate; private final static int done_button = 1; @@ -90,6 +92,8 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent getNotificationCenter().addObserver(this, NotificationCenter.updateInterfaces); user_id = getArguments().getLong("user_id", 0); phone = getArguments().getString("phone"); + firstNameFromCard = getArguments().getString("first_name_card"); + lastNameFromCard = getArguments().getString("last_name_card"); addContact = getArguments().getBoolean("addContact", false); needAddException = MessagesController.getNotificationsSettings(currentAccount).getBoolean("dialog_bar_exception" + user_id, false); TLRPC.User user = null; @@ -218,6 +222,7 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent focused = hasFocus; } }); + firstNameField.setText(firstNameFromCard); lastNameField = new EditTextBoldCursor(context) { @Override @@ -248,9 +253,10 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent } return false; }); + lastNameField.setText(lastNameFromCard); TLRPC.User user = getMessagesController().getUser(user_id); - if (user != null) { + if (user != null && firstNameFromCard == null && lastNameFromCard == null) { if (user.phone == null) { if (phone != null) { user.phone = PhoneFormat.stripExceptNumbers(phone); @@ -266,7 +272,7 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); infoTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); if (addContact) { - if (!needAddException || TextUtils.isEmpty(user.phone)) { + if (!needAddException || TextUtils.isEmpty(getPhone())) { linearLayout.addView(infoTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 24, 18, 24, 0)); } @@ -295,11 +301,11 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent if (user == null) { return; } - if (TextUtils.isEmpty(user.phone)) { + if (TextUtils.isEmpty(getPhone())) { nameTextView.setText(LocaleController.getString("MobileHidden", R.string.MobileHidden)); infoTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("MobileHiddenExceptionInfo", R.string.MobileHiddenExceptionInfo, UserObject.getFirstName(user)))); } else { - nameTextView.setText(PhoneFormat.getInstance().format("+" + user.phone)); + nameTextView.setText(PhoneFormat.getInstance().format("+" + getPhone())); if (needAddException) { infoTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("MobileVisibleInfo", R.string.MobileVisibleInfo, UserObject.getFirstName(user)))); } @@ -308,6 +314,11 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent avatarImage.setForUserOrChat(user, avatarDrawable = new AvatarDrawable(user)); } + private String getPhone() { + TLRPC.User user = getMessagesController().getUser(user_id); + return user != null && !TextUtils.isEmpty(user.phone) ? user.phone : phone; + } + public void didReceivedNotification(int id, int account, Object... args) { if (id == NotificationCenter.updateInterfaces) { int mask = (Integer) args[0]; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java index a7dc58dec..912644102 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java @@ -214,7 +214,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter } @Override - protected void onTransitionAnimationProgress(boolean isOpen, float progress) { + public void onTransitionAnimationProgress(boolean isOpen, float progress) { super.onTransitionAnimationProgress(isOpen, progress); if (fragmentView != null) { fragmentView.invalidate(); @@ -338,7 +338,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter inviteViaLink = ChatObject.canUserDoAdminAction(chat, ChatObject.ACTION_INVITE) ? 1 : 0; } else if (channelId != 0) { TLRPC.Chat chat = getMessagesController().getChat(channelId); - inviteViaLink = ChatObject.canUserDoAdminAction(chat, ChatObject.ACTION_INVITE) && TextUtils.isEmpty(chat.username) ? 2 : 0; + inviteViaLink = ChatObject.canUserDoAdminAction(chat, ChatObject.ACTION_INVITE) && !ChatObject.isPublic(chat) ? 2 : 0; } else { inviteViaLink = 0; } @@ -410,7 +410,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setEmptyView(emptyView); - listView.setAnimateEmptyView(true, 0); + listView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); listView.setOnItemClickListener((view, position) -> { if (listView.getAdapter() == searchListViewAdapter) { @@ -998,10 +998,10 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter } @Override - protected AnimatorSet onCustomTransitionAnimation(boolean isOpen, Runnable callback) { + public AnimatorSet onCustomTransitionAnimation(boolean isOpen, Runnable callback) { ValueAnimator valueAnimator = isOpen ? ValueAnimator.ofFloat(1f, 0) : ValueAnimator.ofFloat(0, 1f); ViewGroup parent = (ViewGroup) fragmentView.getParent(); - BaseFragment previousFragment = parentLayout.fragmentsStack.size() > 1 ? parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2) : null; + BaseFragment previousFragment = parentLayout.getFragmentStack().size() > 1 ? parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 2) : null; DialogsActivity dialogsActivity = null; if (previousFragment instanceof DialogsActivity) { dialogsActivity = (DialogsActivity) previousFragment; @@ -1025,7 +1025,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter }); if (floatingButtonContainer != null) { ((ViewGroup) fragmentView).removeView(floatingButtonContainer); - ((FrameLayout) parent.getParent()).addView(floatingButtonContainer); + parentLayout.getOverlayContainerView().addView(floatingButtonContainer); } valueAnimator.setDuration(150); valueAnimator.setInterpolator(new DecelerateInterpolator(1.5f)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ContactsWidgetConfigActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ContactsWidgetConfigActivity.java index 127320b18..981dd27a3 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ContactsWidgetConfigActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ContactsWidgetConfigActivity.java @@ -34,11 +34,11 @@ public class ContactsWidgetConfigActivity extends ExternalActionActivity { }); if (AndroidUtilities.isTablet()) { - if (layersActionBarLayout.fragmentsStack.isEmpty()) { + if (layersActionBarLayout.getFragmentStack().isEmpty()) { layersActionBarLayout.addFragmentToStack(fragment); } } else { - if (actionBarLayout.fragmentsStack.isEmpty()) { + if (actionBarLayout.getFragmentStack().isEmpty()) { actionBarLayout.addFragmentToStack(fragment); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CreateTopicEmptyView.java b/TMessagesProj/src/main/java/org/telegram/ui/CreateTopicEmptyView.java new file mode 100644 index 000000000..4a3255169 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/CreateTopicEmptyView.java @@ -0,0 +1,93 @@ +package org.telegram.ui; + +import android.content.Context; +import android.graphics.Paint; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.DocumentObject; +import org.telegram.messenger.ImageLocation; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MediaDataController; +import org.telegram.messenger.R; +import org.telegram.messenger.SvgHelper; +import org.telegram.messenger.UserConfig; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.LayoutHelper; + +public class CreateTopicEmptyView extends LinearLayout { + + private final Theme.ResourcesProvider resourcesProvider; + BackupImageView backupImageView; + + + public CreateTopicEmptyView(Context context, FrameLayout parent, Theme.ResourcesProvider resourcesProvider) { + super(context); + this.resourcesProvider = resourcesProvider; + + setBackground(Theme.createServiceDrawable(AndroidUtilities.dp(18), this, parent, getThemedPaint(Theme.key_paint_chatActionBackground))); + setPadding(AndroidUtilities.dp(16), AndroidUtilities.dp(12), AndroidUtilities.dp(16), AndroidUtilities.dp(12)); + setOrientation(LinearLayout.VERTICAL); + + backupImageView = new BackupImageView(context); + + TextView headerTextView = new TextView(context); + headerTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + headerTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + headerTextView.setTextColor(getThemedColor(Theme.key_chat_serviceText)); + headerTextView.setGravity(Gravity.CENTER_HORIZONTAL); + headerTextView.setMaxWidth(AndroidUtilities.dp(210)); + + headerTextView.setText(LocaleController.getString(R.string.AlmostDone)); + + + TextView description = new TextView(context); + description.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + description.setTextColor(getThemedColor(Theme.key_chat_serviceText)); + description.setGravity(Gravity.CENTER_HORIZONTAL); + description.setMaxWidth(AndroidUtilities.dp(160)); + + description.setText(LocaleController.getString(R.string.TopicEmptyViewDescription)); + + addView(backupImageView, LayoutHelper.createLinear(58, 58, Gravity.CENTER_HORIZONTAL, 0, 8, 0, 8)); + addView(headerTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 0, 0, 2, 0)); + addView(description, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL)); + + //TODO topics optimize + setSticker(); + } + + + private int getThemedColor(String key) { + Integer color = resourcesProvider != null ? resourcesProvider.getColor(key) : null; + return color != null ? color : Theme.getColor(key); + } + + private Paint getThemedPaint(String paintKey) { + Paint paint = resourcesProvider != null ? resourcesProvider.getPaint(paintKey) : null; + return paint != null ? paint : Theme.getThemePaint(paintKey); + } + + private void setSticker() { + String imageFilter = null; + TLRPC.Document document = null; + TLRPC.TL_messages_stickerSet set = null; + document = MediaDataController.getInstance(UserConfig.selectedAccount).getEmojiAnimatedSticker("\uD83E\uDD73"); + + if (document != null) { + SvgHelper.SvgDrawable svgThumb = DocumentObject.getSvgThumb(document.thumbs, Theme.key_emptyListPlaceholder, 0.2f); + if (svgThumb != null) { + svgThumb.overrideWidthAndHeight(512, 512); + } + + ImageLocation imageLocation = ImageLocation.getForDocument(document); + backupImageView.setImage(imageLocation, imageFilter, "tgs", svgThumb, set); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DataSettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DataSettingsActivity.java index b76c35fee..7f3ab706f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DataSettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DataSettingsActivity.java @@ -101,6 +101,8 @@ public class DataSettingsActivity extends BaseFragment { private int rowCount; + private boolean updateVoipUseLessData; + @Override public boolean onFragmentCreate() { super.onFragmentCreate(); @@ -354,6 +356,7 @@ public class DataSettingsActivity extends BaseFragment { } if (val != -1) { preferences.edit().putInt("VoipDataSaving", val).commit(); + updateVoipUseLessData = true; } if (listAdapter != null) { listAdapter.notifyItemChanged(position); @@ -510,7 +513,8 @@ public class DataSettingsActivity extends BaseFragment { value = LocaleController.getString("UseLessDataAlways", R.string.UseLessDataAlways); break; } - textCell.setTextAndValue(LocaleController.getString("VoipUseLessData", R.string.VoipUseLessData), value, true); + textCell.setTextAndValue(LocaleController.getString("VoipUseLessData", R.string.VoipUseLessData), value, updateVoipUseLessData, true); + updateVoipUseLessData = false; } else if (position == dataUsageRow) { textCell.setText(LocaleController.getString("NetworkUsage", R.string.NetworkUsage), storageNumRow != -1); } else if (position == storageNumRow) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DefaultThemesPreviewCell.java b/TMessagesProj/src/main/java/org/telegram/ui/DefaultThemesPreviewCell.java index 0f71d31ec..b8a172b4b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DefaultThemesPreviewCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DefaultThemesPreviewCell.java @@ -9,7 +9,6 @@ import android.content.Context; import android.content.SharedPreferences; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; -import android.graphics.drawable.RippleDrawable; import android.text.TextUtils; import android.view.Gravity; import android.view.View; @@ -34,11 +33,11 @@ import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.DrawerProfileCell; import org.telegram.ui.Cells.TextCell; import org.telegram.ui.Components.ChatThemeBottomSheet; -import org.telegram.ui.Components.ThemeSmallPreviewView; import org.telegram.ui.Components.FlickerLoadingView; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RLottieDrawable; import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.ThemeSmallPreviewView; import java.util.ArrayList; @@ -146,7 +145,7 @@ public class DefaultThemesPreviewCell extends LinearLayout { recyclerView.setEmptyView(progressView); - recyclerView.setAnimateEmptyView(true, 0); + recyclerView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); if (currentType == ThemeActivity.THEME_TYPE_BASIC) { darkThemeDrawable = new RLottieDrawable(R.raw.sun_outline, "" + R.raw.sun_outline, AndroidUtilities.dp(28), AndroidUtilities.dp(28), true, null); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Delegates/ChatActivityMemberRequestsDelegate.java b/TMessagesProj/src/main/java/org/telegram/ui/Delegates/ChatActivityMemberRequestsDelegate.java index 29f4f7e36..42eee14e8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Delegates/ChatActivityMemberRequestsDelegate.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Delegates/ChatActivityMemberRequestsDelegate.java @@ -3,6 +3,7 @@ package org.telegram.ui.Delegates; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.graphics.Canvas; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.os.Build; @@ -68,7 +69,9 @@ public class ChatActivityMemberRequestsDelegate { View pendingRequestsSelector = new View(fragment.getParentActivity()); pendingRequestsSelector.setBackground(Theme.getSelectorDrawable(false)); - pendingRequestsSelector.setOnClickListener((v) -> showBottomSheet()); + pendingRequestsSelector.setOnClickListener((v) -> { + showBottomSheet(); + }); root.addView(pendingRequestsSelector, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 2)); LinearLayout requestsDataLayout = new LinearLayout(fragment.getParentActivity()); @@ -223,6 +226,9 @@ public class ChatActivityMemberRequestsDelegate { if (!appear) { root.setVisibility(View.GONE); } + if (callback != null) { + callback.onEnterOffsetChanged(); + } } }); pendingRequestsAnimator.setDuration(200); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Delegates/MemberRequestsDelegate.java b/TMessagesProj/src/main/java/org/telegram/ui/Delegates/MemberRequestsDelegate.java index 50a900637..d3c765b00 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Delegates/MemberRequestsDelegate.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Delegates/MemberRequestsDelegate.java @@ -865,7 +865,7 @@ public class MemberRequestsDelegate implements MemberRequestCell.OnClickListener canvas.scale(1f / factor, 1f / factor); canvas.save(); - ((LaunchActivity) fragment.getParentActivity()).getActionBarLayout().draw(canvas); + ((LaunchActivity) fragment.getParentActivity()).getActionBarLayout().getView().draw(canvas); canvas.drawColor(ColorUtils.setAlphaComponent(Color.BLACK, (int) (255 * 0.3f))); Dialog dialog = fragment.getVisibleDialog(); if (dialog != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DialogOrContactPickerActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DialogOrContactPickerActivity.java index 804147f39..11301655a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DialogOrContactPickerActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DialogOrContactPickerActivity.java @@ -20,6 +20,9 @@ import android.widget.EditText; import android.widget.FrameLayout; import android.widget.TextView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ContactsController; import org.telegram.messenger.DialogObject; @@ -41,9 +44,6 @@ import org.telegram.ui.Components.ScrollSlidingTextTabStrip; import java.util.ArrayList; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - public class DialogOrContactPickerActivity extends BaseFragment { private static class ViewPage extends FrameLayout { @@ -93,7 +93,7 @@ public class DialogOrContactPickerActivity extends BaseFragment { if (dids.isEmpty()) { return; } - long did = dids.get(0); + long did = dids.get(0).dialogId; if (!DialogObject.isUserDialog(did)) { return; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java index 9a3e9e819..af964ae63 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java @@ -71,6 +71,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; @@ -97,6 +98,7 @@ import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaDataController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; +import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.NotificationsController; import org.telegram.messenger.R; @@ -118,6 +120,7 @@ import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BackDrawable; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.MenuDrawable; import org.telegram.ui.ActionBar.SimpleTextView; import org.telegram.ui.ActionBar.Theme; @@ -161,6 +164,8 @@ import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.FilterTabsView; import org.telegram.ui.Components.FiltersListBottomSheet; import org.telegram.ui.Components.FlickerLoadingView; +import org.telegram.ui.Components.FloatingDebug.FloatingDebugController; +import org.telegram.ui.Components.FloatingDebug.FloatingDebugProvider; import org.telegram.ui.Components.FragmentContextView; import org.telegram.ui.Components.JoinGroupAlert; import org.telegram.ui.Components.LayoutHelper; @@ -168,6 +173,7 @@ import org.telegram.ui.Components.MediaActionDrawable; import org.telegram.ui.Components.NumberTextView; import org.telegram.ui.Components.PacmanAnimation; import org.telegram.ui.Components.Premium.LimitReachedBottomSheet; +import org.telegram.ui.Components.Premium.PremiumFeatureBottomSheet; import org.telegram.ui.Components.ProxyDrawable; import org.telegram.ui.Components.PullForegroundDrawable; import org.telegram.ui.Components.RLottieDrawable; @@ -187,8 +193,10 @@ import org.telegram.ui.Components.ViewPagerFixed; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; -public class DialogsActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { +public class DialogsActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, FloatingDebugProvider { public final static int DIALOGS_TYPE_START_ATTACH_BOT = 14; private boolean canShowFilterTabsView; @@ -303,6 +311,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. private float additionalFloatingTranslation2; private float floatingButtonTranslation; private float floatingButtonHideProgress; + private float floatingButtonPanOffset; private AnimatorSet searchAnimator; private Animator tabsAlphaAnimator; @@ -362,6 +371,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. private boolean searching; private boolean searchWas; private boolean onlySelect; + private boolean canSelectTopics; private String searchString; private String initialSearchString; private long openedDialogId; @@ -394,6 +404,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. private int canClearCacheCount; private int canReportSpamCount; private int canUnarchiveCount; + private int forumCount; private boolean canDeletePsaSelected; private int topPadding; @@ -434,6 +445,8 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. return t * t * t * t * t + 1.0F; }; + private Bulletin topBulletin; + private int animationIndex = -1; private boolean searchIsShowed; private boolean searchWasFullyShowed; @@ -792,6 +805,9 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } if (!onlySelect) { actionBar.setTranslationY(0); + if (topBulletin != null) { + topBulletin.updatePosition(); + } } searchViewPager.setTranslationY(0); } @@ -1505,15 +1521,15 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. canReadCount = dialog.unread_count > 0 || dialog.unread_mark ? 1 : 0; performSelectedDialogsAction(selectedDialogs, read, true); } else if (SharedConfig.getChatSwipeAction(currentAccount) == SwipeGestureSettingsView.SWIPE_GESTURE_MUTE) { - if (!getMessagesController().isDialogMuted(dialogId)) { - NotificationsController.getInstance(UserConfig.selectedAccount).setDialogNotificationsSettings(dialogId, NotificationsController.SETTING_MUTE_FOREVER); + if (!getMessagesController().isDialogMuted(dialogId, 0)) { + NotificationsController.getInstance(UserConfig.selectedAccount).setDialogNotificationsSettings(dialogId, 0, NotificationsController.SETTING_MUTE_FOREVER); if (BulletinFactory.canShowBulletin(DialogsActivity.this)) { BulletinFactory.createMuteBulletin(DialogsActivity.this, NotificationsController.SETTING_MUTE_FOREVER).show(); } } else { ArrayList selectedDialogs = new ArrayList<>(); selectedDialogs.add(dialogId); - canMuteCount = MessagesController.getInstance(currentAccount).isDialogMuted(dialogId) ? 0 : 1; + canMuteCount = MessagesController.getInstance(currentAccount).isDialogMuted(dialogId, 0) ? 0 : 1; canUnmuteCount = canMuteCount > 0 ? 0 : 1; performSelectedDialogsAction(selectedDialogs, mute, true); } @@ -1885,7 +1901,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } public interface DialogsActivityDelegate { - void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param); + void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param); } public DialogsActivity(Bundle args) { @@ -1898,6 +1914,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. if (getArguments() != null) { onlySelect = arguments.getBoolean("onlySelect", false); + canSelectTopics = arguments.getBoolean("canSelectTopics", false); cantSendToChannels = arguments.getBoolean("cantSendToChannels", false); initialDialogsType = arguments.getInt("dialogsType", 0); selectAlertString = arguments.getString("selectAlertString"); @@ -2379,6 +2396,17 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. fragmentView.invalidate(); } } + + @Override + protected void onDefaultTabMoved() { + if (!getMessagesController().premiumLocked) { + topBulletin = BulletinFactory.of(DialogsActivity.this).createSimpleBulletin(R.raw.filter_reorder, AndroidUtilities.replaceTags(LocaleController.formatString("LimitReachedReorderFolder", R.string.LimitReachedReorderFolder, LocaleController.getString(R.string.FilterAllChats))), LocaleController.getString("PremiumMore", R.string.PremiumMore), Bulletin.DURATION_PROLONG, () -> { + showDialog(new PremiumFeatureBottomSheet(DialogsActivity.this, PremiumPreviewFragment.PREMIUM_FEATURE_ADVANCED_CHAT_MANAGEMENT, false)); + filterTabsView.setIsEditing(false); + showDoneItem(false); + }).show(true); + } + } }; filterTabsView.setVisibility(View.GONE); @@ -2756,7 +2784,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. viewPage.listView = new DialogsRecyclerView(context, viewPage); viewPage.listView.setAccessibilityEnabled(false); - viewPage.listView.setAnimateEmptyView(true, 0); + viewPage.listView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); viewPage.listView.setClipToPadding(false); viewPage.listView.setPivotY(0); viewPage.dialogsItemAnimator = new DialogsItemAnimator(viewPage.listView) { @@ -3003,8 +3031,8 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. @Override public void didFinishChatCreation(GroupCreateFinalActivity fragment, long chatId) { - ArrayList arrayList = new ArrayList<>(); - arrayList.add(-chatId); + ArrayList arrayList = new ArrayList<>(); + arrayList.add(MessagesStorage.TopicKey.of(-chatId, 0)); DialogsActivityDelegate dialogsActivityDelegate = delegate; if (closeFragment) { removeSelfFromStack(); @@ -3181,6 +3209,44 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. FileLog.e(e); } } + + @Override + public void notifyItemChanged(int position) { + if (dialogsListFrozen) { + notifyDataSetChanged(); + return; + } + super.notifyItemChanged(position); + } + + + @Override + public void notifyItemRemoved(int position) { + if (dialogsListFrozen) { + notifyDataSetChanged(); + return; + } + super.notifyItemRemoved(position); + } + + @Override + public void notifyItemInserted(int position) { + if (dialogsListFrozen) { + notifyDataSetChanged(); + return; + } + super.notifyItemInserted(position); + } + + + @Override + public void notifyItemMoved(int fromPosition, int toPosition) { + if (dialogsListFrozen) { + notifyDataSetChanged(); + return; + } + super.notifyItemMoved(fromPosition, toPosition); + } }; viewPage.dialogsAdapter.setForceShowEmptyCell(afterSignup); if (AndroidUtilities.isTablet() && openedDialogId != 0) { @@ -3255,7 +3321,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. updateSelectedCount(); actionBar.closeSearchField(); } else { - didSelectResult(did, true, false); + didSelectResult(did, 0,true, false); } } else { Bundle args = new Bundle(); @@ -3312,13 +3378,13 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. if (searchViewPager.dialogsSearchAdapter.isSearchWas() && searchViewPager.dialogsSearchAdapter.isRecentSearchDisplayed()) { builder.setTitle(LocaleController.getString("ClearSearchAlertPartialTitle", R.string.ClearSearchAlertPartialTitle)); builder.setMessage(LocaleController.formatPluralString("ClearSearchAlertPartial", searchViewPager.dialogsSearchAdapter.getRecentResultsCount())); - builder.setPositiveButton(LocaleController.getString("Clear", R.string.Clear).toUpperCase(), (dialogInterface, i) -> { + builder.setPositiveButton(LocaleController.getString("Clear", R.string.Clear), (dialogInterface, i) -> { searchViewPager.dialogsSearchAdapter.clearRecentSearch(); }); } else { builder.setTitle(LocaleController.getString("ClearSearchAlertTitle", R.string.ClearSearchAlertTitle)); builder.setMessage(LocaleController.getString("ClearSearchAlert", R.string.ClearSearchAlert)); - builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton).toUpperCase(), (dialogInterface, i) -> { + builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton), (dialogInterface, i) -> { if (searchViewPager.dialogsSearchAdapter.isRecentSearchDisplayed()) { searchViewPager.dialogsSearchAdapter.clearRecentSearch(); } else { @@ -3399,7 +3465,11 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. if (delegate == null || selectedDialogs.isEmpty()) { return; } - delegate.didSelectDialogs(DialogsActivity.this, selectedDialogs, null, false); + ArrayList topicKeys = new ArrayList<>(); + for (int i = 0; i < selectedDialogs.size(); i++) { + topicKeys.add(MessagesStorage.TopicKey.of(selectedDialogs.get(i), 0)); + } + delegate.didSelectDialogs(DialogsActivity.this, topicKeys, null, false); } else { if (floatingButton.getVisibility() != View.VISIBLE) { return; @@ -3522,7 +3592,11 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. if (delegate == null || selectedDialogs.isEmpty()) { return; } - delegate.didSelectDialogs(DialogsActivity.this, selectedDialogs, message, false); + ArrayList topicKeys = new ArrayList<>(); + for (int i = 0; i < selectedDialogs.size(); i++) { + topicKeys.add(MessagesStorage.TopicKey.of(selectedDialogs.get(i), 0)); + } + delegate.didSelectDialogs(DialogsActivity.this, topicKeys, message, false); } @Override @@ -3711,7 +3785,11 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. if (delegate == null || selectedDialogs.isEmpty()) { return; } - delegate.didSelectDialogs(DialogsActivity.this, selectedDialogs, commentView.getFieldText(), false); + ArrayList topicKeys = new ArrayList<>(); + for (int i = 0; i < selectedDialogs.size(); i++) { + topicKeys.add(MessagesStorage.TopicKey.of(selectedDialogs.get(i), 0)); + } + delegate.didSelectDialogs(DialogsActivity.this, topicKeys, commentView.getFieldText(), false); }); writeButtonBackground.setOnLongClickListener(v -> { if (isNextButton) { @@ -4550,6 +4628,9 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } if (!onlySelect) { actionBar.setTranslationY(0); + if (topBulletin != null) { + topBulletin.updatePosition(); + } } searchViewPager.setTranslationY(0); } else { @@ -4558,6 +4639,9 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } if (!onlySelect) { actionBar.setTranslationY(y); + if (topBulletin != null) { + topBulletin.updatePosition(); + } } searchViewPager.setTranslationY(y); } @@ -4674,7 +4758,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. showNextSupportedSuggestion(); Bulletin.addDelegate(this, new Bulletin.Delegate() { @Override - public void onOffsetChange(float offset) { + public void onBottomOffsetChange(float offset) { if (undoView[0] != null && undoView[0].getVisibility() == View.VISIBLE) { return; } @@ -4693,6 +4777,11 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. undoView[0].hide(true, 2); } } + + @Override + public int getTopOffset(int tag) { + return (actionBar != null ? actionBar.getMeasuredHeight() + (int) actionBar.getTranslationY() : 0) + (filterTabsView != null && filterTabsView.getVisibility() == View.VISIBLE ? filterTabsView.getMeasuredHeight() : 0) + (fragmentContextView != null && fragmentContextView.isCallTypeVisible() ? AndroidUtilities.dp(fragmentContextView.getStyleHeight()) : 0); + } }); if (searchIsShowed) { AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid); @@ -4764,7 +4853,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } @Override - protected void onBecomeFullyHidden() { + public void onBecomeFullyHidden() { if (closeSearchFieldOnHide) { if (actionBar != null) { actionBar.closeSearchField(); @@ -4792,7 +4881,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } @Override - protected void setInPreviewMode(boolean value) { + public void setInPreviewMode(boolean value) { super.setInPreviewMode(value); if (!value && avatarContainer != null) { actionBar.setBackground(null); @@ -4812,6 +4901,9 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } public boolean addOrRemoveSelectedDialog(long did, View cell) { + if (onlySelect && getMessagesController().isForum(did)) { + return false; + } if (selectedDialogs.contains(did)) { selectedDialogs.remove(did); if (cell instanceof DialogCell) { @@ -5406,7 +5498,17 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } updateSelectedCount(); } else { - didSelectResult(dialogId, true, false); + if (canSelectTopics && getMessagesController().isForum(dialogId)) { + Bundle bundle = new Bundle(); + bundle.putLong("chat_id", -dialogId); + bundle.putBoolean("for_select", true); + bundle.putBoolean("forward_to", true); + TopicsFragment topicsFragment = new TopicsFragment(bundle); + topicsFragment.setForwardFromDialogFragment(DialogsActivity.this); + presentFragment(topicsFragment); + } else { + didSelectResult(dialogId, 0,true, false); + } } } else { Bundle args = new Bundle(); @@ -5439,14 +5541,24 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. args.putInt("dialog_filter_id", filterId); if (AndroidUtilities.isTablet()) { if (openedDialogId == dialogId && adapter != searchViewPager.dialogsSearchAdapter) { + if (getParentActivity() instanceof LaunchActivity) { + LaunchActivity launchActivity = (LaunchActivity) getParentActivity(); + List rightFragments = launchActivity.getRightActionBarLayout().getFragmentStack(); + if (!rightFragments.isEmpty()) { + if (rightFragments.size() == 1 && rightFragments.get(rightFragments.size() - 1) instanceof ChatActivity) { + ((ChatActivity) rightFragments.get(rightFragments.size() - 1)).onPageDownClicked(); + } else if (rightFragments.size() == 2) { + launchActivity.getRightActionBarLayout().closeLastFragment(); + } else if (getParentActivity() instanceof LaunchActivity) { + BaseFragment first = rightFragments.get(0); + rightFragments.clear(); + rightFragments.add(first); + launchActivity.getRightActionBarLayout().rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + } + } + } return; } - if (viewPages != null) { - for (int a = 0; a < viewPages.length; a++) { - viewPages[a].dialogsAdapter.setOpenedDialogId(openedDialogId = dialogId); - } - } - updateVisibleRows(MessagesController.UPDATE_MASK_SELECT_DIALOG); } if (searchViewPager.actionModeShowing()) { searchViewPager.hideActionMode(); @@ -5459,14 +5571,19 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } else { slowedReloadAfterDialogClick = true; if (getMessagesController().checkCanOpenChat(args, DialogsActivity.this)) { - ChatActivity chatActivity = new ChatActivity(args); - if (adapter instanceof DialogsAdapter && DialogObject.isUserDialog(dialogId) && (getMessagesController().dialogs_dict.get(dialogId) == null)) { - TLRPC.Document sticker = getMediaDataController().getGreetingsSticker(); - if (sticker != null) { - chatActivity.setPreloadedSticker(sticker, true); + TLRPC.Chat chat = getMessagesController().getChat(-dialogId); + if (chat != null && chat.forum) { + presentFragment(new TopicsFragment(args)); + } else { + ChatActivity chatActivity = new ChatActivity(args); + if (adapter instanceof DialogsAdapter && DialogObject.isUserDialog(dialogId) && (getMessagesController().dialogs_dict.get(dialogId) == null)) { + TLRPC.Document sticker = getMediaDataController().getGreetingsSticker(); + if (sticker != null) { + chatActivity.setPreloadedSticker(sticker, true); + } } - } - presentFragment(chatActivity); + presentFragment(chatActivity); + }; } } } @@ -5490,7 +5607,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. if (getParentActivity() == null) { return false; } - if (!actionBar.isActionModeShowed() && !AndroidUtilities.isTablet() && !onlySelect && view instanceof DialogCell) { + if (!actionBar.isActionModeShowed() && !AndroidUtilities.isTablet() && !onlySelect && view instanceof DialogCell && !getMessagesController().isForum(((DialogCell)view).getDialogId())) { DialogCell cell = (DialogCell) view; if (cell.isPointInsideAvatar(x, y)) { return showChatPreview(cell); @@ -5522,7 +5639,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } else { return false; } - builder.setPositiveButton(LocaleController.getString("ClearSearchRemove", R.string.ClearSearchRemove).toUpperCase(), (dialogInterface, i) -> searchViewPager.dialogsSearchAdapter.removeRecentSearch(did)); + builder.setPositiveButton(LocaleController.getString("ClearSearchRemove", R.string.ClearSearchRemove), (dialogInterface, i) -> searchViewPager.dialogsSearchAdapter.removeRecentSearch(did)); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); AlertDialog alertDialog = builder.create(); showDialog(alertDialog); @@ -5792,8 +5909,8 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(dialogId); boolean containsFilter; final MessagesController.DialogFilter filter = ( - (containsFilter = (viewPages[0].dialogsType == 7 || viewPages[0].dialogsType == 8) && (!actionBar.isActionModeShowed() || actionBar.isActionModeShowed(null))) ? - getMessagesController().selectedDialogFilter[viewPages[0].dialogsType == 8 ? 1 : 0] : null + (containsFilter = (viewPages[0].dialogsType == 7 || viewPages[0].dialogsType == 8) && (!actionBar.isActionModeShowed() || actionBar.isActionModeShowed(null))) ? + getMessagesController().selectedDialogFilter[viewPages[0].dialogsType == 8 ? 1 : 0] : null ); if (!isDialogPinned(dialog)) { int pinnedCount = 0; @@ -5897,18 +6014,18 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. if (!DialogObject.isUserDialog(dialogId) || !UserObject.isUserSelf(getMessagesController().getUser(dialogId))) { ActionBarMenuSubItem muteItem = new ActionBarMenuSubItem(getParentActivity(), false, false); - if (!getMessagesController().isDialogMuted(dialogId)) { + if (!getMessagesController().isDialogMuted(dialogId, 0)) { muteItem.setTextAndIcon(LocaleController.getString("Mute", R.string.Mute), R.drawable.msg_mute); } else { muteItem.setTextAndIcon(LocaleController.getString("Unmute", R.string.Unmute), R.drawable.msg_unmute); } muteItem.setMinimumWidth(160); muteItem.setOnClickListener(e -> { - boolean isMuted = getMessagesController().isDialogMuted(dialogId); + boolean isMuted = getMessagesController().isDialogMuted(dialogId, 0); if (!isMuted) { - getNotificationsController().setDialogNotificationsSettings(dialogId, NotificationsController.SETTING_MUTE_FOREVER); + getNotificationsController().setDialogNotificationsSettings(dialogId, 0, NotificationsController.SETTING_MUTE_FOREVER); } else { - getNotificationsController().setDialogNotificationsSettings(dialogId, NotificationsController.SETTING_MUTE_UNMUTE); + getNotificationsController().setDialogNotificationsSettings(dialogId, 0, NotificationsController.SETTING_MUTE_UNMUTE); } BulletinFactory.createMuteBulletin(this, !isMuted, null).show(); finishPreviewFragment(); @@ -5932,7 +6049,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. getNotificationCenter().postNotificationName(NotificationCenter.closeChats); } prepareBlurBitmap(); - parentLayout.highlightActionButtons = true; + parentLayout.setHighlightActionButtons(true); if (AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y) { presentFragmentAsPreview(chatActivity[0] = new ChatActivity(args)); } else { @@ -5950,7 +6067,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } private void updateFloatingButtonOffset() { - floatingButtonContainer.setTranslationY(floatingButtonTranslation - Math.max(additionalFloatingTranslation, additionalFloatingTranslation2) * (1f - floatingButtonHideProgress)); + floatingButtonContainer.setTranslationY(floatingButtonTranslation - floatingButtonPanOffset - Math.max(additionalFloatingTranslation, additionalFloatingTranslation2) * (1f - floatingButtonHideProgress)); } private boolean hasHiddenArchive() { @@ -5983,6 +6100,9 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. scrimView.getLocationInWindow(scrimViewLocation); } actionBar.setTranslationY(value); + if (topBulletin != null) { + topBulletin.updatePosition(); + } if (filterTabsView != null) { filterTabsView.setTranslationY(value); } @@ -6016,7 +6136,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } @Override - protected void onTransitionAnimationProgress(boolean isOpen, float progress) { + public void onTransitionAnimationProgress(boolean isOpen, float progress) { if (blurredView != null && blurredView.getVisibility() == View.VISIBLE) { if (isOpen) { blurredView.setAlpha(1.0f - progress); @@ -6027,7 +6147,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen && blurredView != null && blurredView.getVisibility() == View.VISIBLE) { blurredView.setVisibility(View.GONE); blurredView.setBackground(null); @@ -6130,6 +6250,9 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } private boolean isDialogPinned(TLRPC.Dialog dialog) { + if (dialog == null) { + return false; + } MessagesController.DialogFilter filter; boolean containsFilter = (viewPages[0].dialogsType == 7 || viewPages[0].dialogsType == 8) && (!actionBar.isActionModeShowed() || actionBar.isActionModeShowed(null)); if (containsFilter) { @@ -6389,7 +6512,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } else { AlertsCreator.createClearOrDeleteDialogAlert(DialogsActivity.this, action == clear, chat, user, DialogObject.isEncryptedDialog(dialog.id), action == delete, (param) -> { hideActionMode(false); - if (action == clear && ChatObject.isChannel(chat) && (!chat.megagroup || !TextUtils.isEmpty(chat.username))) { + if (action == clear && ChatObject.isChannel(chat) && (!chat.megagroup || ChatObject.isPublic(chat))) { getMessagesController().deleteDialog(selectedDialog, 2, param); } else { if (action == delete && folderId != 0 && getDialogsArray(currentAccount, viewPages[0].dialogsType, folderId, false).size() == 1) { @@ -6448,19 +6571,19 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } } else if (action == mute) { if (count == 1 && canMuteCount == 1) { - showDialog(AlertsCreator.createMuteAlert(this, selectedDialog, null), dialog12 -> hideActionMode(true)); + showDialog(AlertsCreator.createMuteAlert(this, selectedDialog, 0,null), dialog12 -> hideActionMode(true)); return; } else { if (canUnmuteCount != 0) { - if (!getMessagesController().isDialogMuted(selectedDialog)) { + if (!getMessagesController().isDialogMuted(selectedDialog, 0)) { continue; } - getNotificationsController().setDialogNotificationsSettings(selectedDialog, NotificationsController.SETTING_MUTE_UNMUTE); + getNotificationsController().setDialogNotificationsSettings(selectedDialog, 0, NotificationsController.SETTING_MUTE_UNMUTE); } else { - if (getMessagesController().isDialogMuted(selectedDialog)) { + if (getMessagesController().isDialogMuted(selectedDialog, 0)) { continue; } - getNotificationsController().setDialogNotificationsSettings(selectedDialog, NotificationsController.SETTING_MUTE_FOREVER); + getNotificationsController().setDialogNotificationsSettings(selectedDialog, 0, NotificationsController.SETTING_MUTE_FOREVER); } } } @@ -6512,7 +6635,11 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } } } - getMessagesController().markMentionsAsRead(did); + if (getMessagesController().isForum(did)) { + getMessagesController().markAllTopicsAsRead(did); + } + + getMessagesController().markMentionsAsRead(did, 0); getMessagesController().markDialogAsRead(did, dialog.top_message, dialog.top_message, dialog.last_message_date, false, 0, 0, true, 0); if (selectedDialogIndex >= 0) { @@ -6669,6 +6796,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. canMuteCount = 0; canPinCount = 0; canReadCount = 0; + forumCount = 0; canClearCacheCount = 0; int cantBlockCount = 0; canReportSpamCount = 0; @@ -6687,7 +6815,11 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. long selectedDialog = dialog.id; boolean pinned = isDialogPinned(dialog); boolean hasUnread = dialog.unread_count != 0 || dialog.unread_mark; - if (getMessagesController().isDialogMuted(selectedDialog)) { + + if (getMessagesController().isForum(selectedDialog)) { + forumCount++; + } + if (getMessagesController().isDialogMuted(selectedDialog, 0)) { canUnmuteCount++; } else { canMuteCount++; @@ -6732,7 +6864,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. canPinCount++; } if (chat != null && chat.megagroup) { - if (TextUtils.isEmpty(chat.username)) { + if (!ChatObject.isPublic(chat)) { canClearHistoryCount++; } else { canClearCacheCount++; @@ -6878,8 +7010,14 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } if (canReadCount != 0) { readItem.setTextAndIcon(LocaleController.getString("MarkAsRead", R.string.MarkAsRead), R.drawable.msg_markread); + readItem.setVisibility(View.VISIBLE); } else { - readItem.setTextAndIcon(LocaleController.getString("MarkAsUnread", R.string.MarkAsUnread), R.drawable.msg_markunread); + if (forumCount == 0) { + readItem.setTextAndIcon(LocaleController.getString("MarkAsUnread", R.string.MarkAsUnread), R.drawable.msg_markunread); + readItem.setVisibility(View.VISIBLE); + } else { + readItem.setVisibility(View.GONE); + } } if (canPinCount != 0) { pinItem.setIcon(R.drawable.msg_pin); @@ -7130,7 +7268,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. private boolean isNextButton = false; private AnimatorSet commentViewAnimator; - + private void updateSelectedCount() { if (commentView != null) { if (selectedDialogs.isEmpty()) { @@ -7360,6 +7498,11 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. checkListLoad(viewPage); } + public void setPanTranslationOffset(float y) { + floatingButtonPanOffset = y; + updateFloatingButtonOffset(); + } + @SuppressWarnings("unchecked") @Override public void didReceivedNotification(int id, int account, Object... args) { @@ -7740,7 +7883,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. for (int i = 0; i < count; ++i) { TLRPC.Dialog dialog = messagesController.dialogsCanAddUsers.get(i); if (allowChannels && ChatObject.isChannelAndNotMegaGroup(-dialog.id, currentAccount) || - allowGroups && (ChatObject.isMegagroup(currentAccount, -dialog.id) || !ChatObject.isChannel(-dialog.id, currentAccount))) { + allowGroups && (ChatObject.isMegagroup(currentAccount, -dialog.id) || !ChatObject.isChannel(-dialog.id, currentAccount))) { if (first) { dialogs.add(new DialogsHeader(DialogsHeader.HEADER_TYPE_GROUPS)); first = false; @@ -8074,6 +8217,10 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. return delegate == null && searchString == null; } + public boolean isArchive() { + return folderId == 1; + } + public void setInitialSearchType(int type) { this.initialSearchType = type; } @@ -8110,7 +8257,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. return true; } - private void didSelectResult(final long dialogId, boolean useAlert, final boolean param) { + public void didSelectResult(final long dialogId, int topicId, boolean useAlert, final boolean param) { if (!checkCanWrite(dialogId)) { return; } @@ -8149,8 +8296,8 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. TLRPC.TL_messages_checkedHistoryImportPeer res = (TLRPC.TL_messages_checkedHistoryImportPeer) response; AlertsCreator.createImportDialogAlert(this, arguments.getString("importTitle"), res.confirm_text, user, chat, () -> { setDialogsListFrozen(true); - ArrayList dids = new ArrayList<>(); - dids.add(dialogId); + ArrayList dids = new ArrayList<>(); + dids.add(MessagesStorage.TopicKey.of(dialogId, 0)); delegate.didSelectDialogs(DialogsActivity.this, dids, null, param); }); } else { @@ -8211,13 +8358,13 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } builder.setTitle(title); builder.setMessage(AndroidUtilities.replaceTags(message)); - builder.setPositiveButton(buttonText, (dialogInterface, i) -> didSelectResult(dialogId, false, false)); + builder.setPositiveButton(buttonText, (dialogInterface, i) -> didSelectResult(dialogId, 0,false, false)); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showDialog(builder.create()); } else { if (delegate != null) { - ArrayList dids = new ArrayList<>(); - dids.add(dialogId); + ArrayList dids = new ArrayList<>(); + dids.add(MessagesStorage.TopicKey.of(dialogId, topicId)); delegate.didSelectDialogs(DialogsActivity.this, dids, null, param); if (resetDelegate) { delegate = null; @@ -8282,7 +8429,11 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. if (delegate == null || selectedDialogs.isEmpty()) { return; } - delegate.didSelectDialogs(DialogsActivity.this, selectedDialogs, commentView.getFieldText(), false); + ArrayList topicKeys = new ArrayList<>(); + for (int i = 0; i < selectedDialogs.size(); i++) { + topicKeys.add(MessagesStorage.TopicKey.of(selectedDialogs.get(i), 0)); + } + delegate.didSelectDialogs(DialogsActivity.this, topicKeys, commentView.getFieldText(), false); }); layout.addView(sendPopupLayout2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); @@ -8865,7 +9016,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } @Override - protected void prepareFragmentToSlide(boolean topFragment, boolean beginSlide) { + public void prepareFragmentToSlide(boolean topFragment, boolean beginSlide) { if (!topFragment && beginSlide) { isSlideBackTransition = true; setFragmentIsSliding(true); @@ -8926,7 +9077,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } @Override - protected void onSlideProgress(boolean isOpen, float progress) { + public void onSlideProgress(boolean isOpen, float progress) { if (SharedConfig.getDevicePerformanceClass() == SharedConfig.PERFORMANCE_CLASS_LOW) { return; } @@ -9004,4 +9155,15 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } return ColorUtils.calculateLuminance(color) > 0.7f; } + + @Override + public List onGetDebugItems() { + return Arrays.asList( + new FloatingDebugController.DebugItem(LocaleController.getString(R.string.ClearLocalDatabase), () -> { + getMessagesStorage().clearLocalDatabase(); + Toast.makeText(getContext(), LocaleController.getString(R.string.DebugClearLocalDatabaseSuccess), Toast.LENGTH_SHORT).show(); + }), + new FloatingDebugController.DebugItem(LocaleController.getString(R.string.DebugClearSendMessageAsPeers), () -> getMessagesController().clearSendAsPeers()) + ); + } } \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/EditWidgetActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/EditWidgetActivity.java index f68e3032d..d4a2d18cf 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/EditWidgetActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/EditWidgetActivity.java @@ -55,6 +55,7 @@ import org.telegram.messenger.DialogObject; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessageObject; +import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.R; import org.telegram.messenger.SharedConfig; import org.telegram.messenger.UserObject; @@ -331,7 +332,8 @@ public class EditWidgetActivity extends BaseFragment { FileLog.e(e); } - MessageObject message = getMessagesController().dialogMessage.get(dialog.id); + ArrayList messages = getMessagesController().dialogMessage.get(dialog.id); + MessageObject message = messages != null && messages.size() > 0 ? messages.get(0) : null; if (message != null) { TLRPC.User fromUser = null; TLRPC.Chat fromChat = null; @@ -398,7 +400,7 @@ public class EditWidgetActivity extends BaseFragment { } else { innerMessage = String.format("\uD83C\uDFAE %s", message.messageOwner.media.game.title); } - } else if (message.type == 14) { + } else if (message.type == MessageObject.TYPE_MUSIC) { if (Build.VERSION.SDK_INT >= 18) { innerMessage = String.format("\uD83C\uDFA7 \u2068%s - %s\u2069", message.getMusicAuthor(), message.getMusicTitle()); } else { @@ -455,7 +457,7 @@ public class EditWidgetActivity extends BaseFragment { messageString = "\uD83D\uDCCA " + mediaPoll.poll.question; } else if (message.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { messageString = "\uD83C\uDFAE " + message.messageOwner.media.game.title; - } else if (message.type == 14) { + } else if (message.type == MessageObject.TYPE_MUSIC) { messageString = String.format("\uD83C\uDFA7 %s - %s", message.getMusicAuthor(), message.getMusicTitle()); } else { messageString = message.messageText; @@ -482,7 +484,7 @@ public class EditWidgetActivity extends BaseFragment { if (dialog.unread_count > 0) { ((TextView) cells[a].findViewById(R.id.shortcut_widget_item_badge)).setText(String.format("%d", dialog.unread_count)); cells[a].findViewById(R.id.shortcut_widget_item_badge).setVisibility(VISIBLE); - if (getMessagesController().isDialogMuted(dialog.id)) { + if (getMessagesController().isDialogMuted(dialog.id, 0)) { cells[a].findViewById(R.id.shortcut_widget_item_badge).setBackgroundResource(R.drawable.widget_counter_muted); } else { cells[a].findViewById(R.id.shortcut_widget_item_badge).setBackgroundResource(R.drawable.widget_counter); @@ -800,7 +802,12 @@ public class EditWidgetActivity extends BaseFragment { if (getParentActivity() == null) { return; } - getMessagesStorage().putWidgetDialogs(currentWidgetId, selectedDialogs); + + ArrayList topicKeys = new ArrayList<>(); + for (int i = 0; i < selectedDialogs.size(); i++) { + topicKeys.add(MessagesStorage.TopicKey.of(selectedDialogs.get(i), 0)); + } + getMessagesStorage().putWidgetDialogs(currentWidgetId, topicKeys); SharedPreferences preferences = getParentActivity().getSharedPreferences("shortcut_widget", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ExternalActionActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ExternalActionActivity.java index 79852f66e..1c3069fe5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ExternalActionActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ExternalActionActivity.java @@ -36,10 +36,10 @@ import org.telegram.messenger.SharedConfig; import org.telegram.messenger.UserConfig; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; -import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.DrawerLayoutContainer; +import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.LayoutHelper; @@ -48,15 +48,15 @@ import org.telegram.ui.Components.SizeNotifierFrameLayout; import java.util.ArrayList; -public class ExternalActionActivity extends Activity implements ActionBarLayout.ActionBarLayoutDelegate { +public class ExternalActionActivity extends Activity implements INavigationLayout.INavigationLayoutDelegate { private boolean finished; private static ArrayList mainFragmentsStack = new ArrayList<>(); private static ArrayList layerFragmentsStack = new ArrayList<>(); private PasscodeView passcodeView; - protected ActionBarLayout actionBarLayout; - protected ActionBarLayout layersActionBarLayout; + protected INavigationLayout actionBarLayout; + protected INavigationLayout layersActionBarLayout; protected SizeNotifierFrameLayout backgroundTablet; protected DrawerLayoutContainer drawerLayoutContainer; @@ -93,7 +93,7 @@ public class ExternalActionActivity extends Activity implements ActionBarLayout. Theme.createDialogsResources(this); Theme.createChatResources(this, false); - actionBarLayout = new ActionBarLayout(this); + actionBarLayout = INavigationLayout.newLayout(this); drawerLayoutContainer = new DrawerLayoutContainer(this); drawerLayoutContainer.setAllowOpenDrawer(false, false); @@ -119,26 +119,26 @@ public class ExternalActionActivity extends Activity implements ActionBarLayout. backgroundTablet.setBackgroundImage(Theme.getCachedWallpaper(), Theme.isWallpaperMotion()); launchLayout.addView(backgroundTablet, LayoutHelper.createRelative(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - launchLayout.addView(actionBarLayout, LayoutHelper.createRelative(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + launchLayout.addView(actionBarLayout.getView(), LayoutHelper.createRelative(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); FrameLayout shadowTablet = new FrameLayout(this); shadowTablet.setBackgroundColor(0x7F000000); launchLayout.addView(shadowTablet, LayoutHelper.createRelative(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); shadowTablet.setOnTouchListener((v, event) -> { - if (!actionBarLayout.fragmentsStack.isEmpty() && event.getAction() == MotionEvent.ACTION_UP) { + if (!actionBarLayout.getFragmentStack().isEmpty() && event.getAction() == MotionEvent.ACTION_UP) { float x = event.getX(); float y = event.getY(); int[] location = new int[2]; - layersActionBarLayout.getLocationOnScreen(location); + layersActionBarLayout.getView().getLocationOnScreen(location); int viewX = location[0]; int viewY = location[1]; - if (layersActionBarLayout.checkTransitionAnimation() || x > viewX && x < viewX + layersActionBarLayout.getWidth() && y > viewY && y < viewY + layersActionBarLayout.getHeight()) { + if (layersActionBarLayout.checkTransitionAnimation() || x > viewX && x < viewX + layersActionBarLayout.getView().getWidth() && y > viewY && y < viewY + layersActionBarLayout.getView().getHeight()) { return false; } else { - if (!layersActionBarLayout.fragmentsStack.isEmpty()) { - for (int a = 0; a < layersActionBarLayout.fragmentsStack.size() - 1; a++) { - layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.fragmentsStack.get(0)); + if (!layersActionBarLayout.getFragmentStack().isEmpty()) { + for (int a = 0; a < layersActionBarLayout.getFragmentStack().size() - 1; a++) { + layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.getFragmentStack().get(0)); a--; } layersActionBarLayout.closeLastFragment(true); @@ -153,13 +153,13 @@ public class ExternalActionActivity extends Activity implements ActionBarLayout. }); - layersActionBarLayout = new ActionBarLayout(this); + layersActionBarLayout = INavigationLayout.newLayout(this); layersActionBarLayout.setRemoveActionBarExtraHeight(true); layersActionBarLayout.setBackgroundView(shadowTablet); layersActionBarLayout.setUseAlphaAnimations(true); - layersActionBarLayout.setBackgroundResource(R.drawable.boxshadow); - launchLayout.addView(layersActionBarLayout, LayoutHelper.createRelative(530, (AndroidUtilities.isSmallTablet() ? 528 : 700))); - layersActionBarLayout.init(layerFragmentsStack); + layersActionBarLayout.getView().setBackgroundResource(R.drawable.boxshadow); + launchLayout.addView(layersActionBarLayout.getView(), LayoutHelper.createRelative(530, (AndroidUtilities.isSmallTablet() ? 528 : 700))); + layersActionBarLayout.setFragmentStack(layerFragmentsStack); layersActionBarLayout.setDelegate(this); layersActionBarLayout.setDrawerLayoutContainer(drawerLayoutContainer); } else { @@ -176,14 +176,14 @@ public class ExternalActionActivity extends Activity implements ActionBarLayout. backgroundTablet.setBackgroundImage(Theme.getCachedWallpaper(), Theme.isWallpaperMotion()); launchLayout.addView(backgroundTablet, LayoutHelper.createRelative(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - launchLayout.addView(actionBarLayout, LayoutHelper.createRelative(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + launchLayout.addView(actionBarLayout.getView(), LayoutHelper.createRelative(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); } // drawerLayoutContainer.setDrawerLayout(listView); drawerLayoutContainer.setParentActionBarLayout(actionBarLayout); actionBarLayout.setDrawerLayoutContainer(drawerLayoutContainer); - actionBarLayout.init(mainFragmentsStack); + actionBarLayout.setFragmentStack(mainFragmentsStack); actionBarLayout.setDelegate(this); passcodeView = new PasscodeView(this); @@ -388,11 +388,11 @@ public class ExternalActionActivity extends Activity implements ActionBarLayout. }, ConnectionsManager.RequestFlagFailOnServerErrors | ConnectionsManager.RequestFlagWithoutLogin); } else { if (AndroidUtilities.isTablet()) { - if (layersActionBarLayout.fragmentsStack.isEmpty()) { + if (layersActionBarLayout.getFragmentStack().isEmpty()) { layersActionBarLayout.addFragmentToStack(new CacheControlActivity()); } } else { - if (actionBarLayout.fragmentsStack.isEmpty()) { + if (actionBarLayout.getFragmentStack().isEmpty()) { actionBarLayout.addFragmentToStack(new CacheControlActivity()); } } @@ -452,11 +452,11 @@ public class ExternalActionActivity extends Activity implements ActionBarLayout. public void needLayout() { if (AndroidUtilities.isTablet()) { - RelativeLayout.LayoutParams relativeLayoutParams = (RelativeLayout.LayoutParams) layersActionBarLayout.getLayoutParams(); + RelativeLayout.LayoutParams relativeLayoutParams = (RelativeLayout.LayoutParams) layersActionBarLayout.getView().getLayoutParams(); relativeLayoutParams.leftMargin = (AndroidUtilities.displaySize.x - relativeLayoutParams.width) / 2; int y = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); relativeLayoutParams.topMargin = y + (AndroidUtilities.displaySize.y - relativeLayoutParams.height - y) / 2; - layersActionBarLayout.setLayoutParams(relativeLayoutParams); + layersActionBarLayout.getView().setLayoutParams(relativeLayoutParams); if (!AndroidUtilities.isSmallTablet() || getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { @@ -465,22 +465,22 @@ public class ExternalActionActivity extends Activity implements ActionBarLayout. leftWidth = AndroidUtilities.dp(320); } - relativeLayoutParams = (RelativeLayout.LayoutParams) actionBarLayout.getLayoutParams(); + relativeLayoutParams = (RelativeLayout.LayoutParams) actionBarLayout.getView().getLayoutParams(); relativeLayoutParams.width = leftWidth; relativeLayoutParams.height = LayoutHelper.MATCH_PARENT; - actionBarLayout.setLayoutParams(relativeLayoutParams); + actionBarLayout.getView().setLayoutParams(relativeLayoutParams); - if (AndroidUtilities.isSmallTablet() && actionBarLayout.fragmentsStack.size() == 2) { - BaseFragment chatFragment = actionBarLayout.fragmentsStack.get(1); + if (AndroidUtilities.isSmallTablet() && actionBarLayout.getFragmentStack().size() == 2) { + BaseFragment chatFragment = actionBarLayout.getFragmentStack().get(1); chatFragment.onPause(); - actionBarLayout.fragmentsStack.remove(1); + actionBarLayout.getFragmentStack().remove(1); actionBarLayout.showLastFragment(); } } else { - relativeLayoutParams = (RelativeLayout.LayoutParams) actionBarLayout.getLayoutParams(); + relativeLayoutParams = (RelativeLayout.LayoutParams) actionBarLayout.getView().getLayoutParams(); relativeLayoutParams.width = LayoutHelper.MATCH_PARENT; relativeLayoutParams.height = LayoutHelper.MATCH_PARENT; - actionBarLayout.setLayoutParams(relativeLayoutParams); + actionBarLayout.getView().setLayoutParams(relativeLayoutParams); } } } @@ -492,12 +492,12 @@ public class ExternalActionActivity extends Activity implements ActionBarLayout. if (actionBarLayout == null) { return; } - actionBarLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + actionBarLayout.getView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { needLayout(); if (actionBarLayout != null) { - actionBarLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this); + actionBarLayout.getView().getViewTreeObserver().removeOnGlobalLayoutListener(this); } } }); @@ -614,7 +614,7 @@ public class ExternalActionActivity extends Activity implements ActionBarLayout. } else if (drawerLayoutContainer.isDrawerOpened()) { drawerLayoutContainer.closeDrawer(false); } else if (AndroidUtilities.isTablet()) { - if (layersActionBarLayout.getVisibility() == View.VISIBLE) { + if (layersActionBarLayout.getView().getVisibility() == View.VISIBLE) { layersActionBarLayout.onBackPressed(); } else { actionBarLayout.onBackPressed(); @@ -634,29 +634,29 @@ public class ExternalActionActivity extends Activity implements ActionBarLayout. } @Override - public boolean needPresentFragment(BaseFragment fragment, boolean removeLast, boolean forceWithoutAnimation, ActionBarLayout layout) { + public boolean needPresentFragment(BaseFragment fragment, boolean removeLast, boolean forceWithoutAnimation, INavigationLayout layout) { return true; } @Override - public boolean needAddFragmentToStack(BaseFragment fragment, ActionBarLayout layout) { + public boolean needAddFragmentToStack(BaseFragment fragment, INavigationLayout layout) { return true; } @Override - public boolean needCloseLastFragment(ActionBarLayout layout) { + public boolean needCloseLastFragment(INavigationLayout layout) { if (AndroidUtilities.isTablet()) { - if (layout == actionBarLayout && layout.fragmentsStack.size() <= 1) { + if (layout == actionBarLayout && layout.getFragmentStack().size() <= 1) { onFinish(); finish(); return false; - } else if (layout == layersActionBarLayout && actionBarLayout.fragmentsStack.isEmpty() && layersActionBarLayout.fragmentsStack.size() == 1) { + } else if (layout == layersActionBarLayout && actionBarLayout.getFragmentStack().isEmpty() && layersActionBarLayout.getFragmentStack().size() == 1) { onFinish(); finish(); return false; } } else { - if (layout.fragmentsStack.size() <= 1) { + if (layout.getFragmentStack().size() <= 1) { onFinish(); finish(); return false; @@ -666,7 +666,7 @@ public class ExternalActionActivity extends Activity implements ActionBarLayout. } @Override - public void onRebuildAllFragments(ActionBarLayout layout, boolean last) { + public void onRebuildAllFragments(INavigationLayout layout, boolean last) { if (AndroidUtilities.isTablet()) { if (layout == layersActionBarLayout) { actionBarLayout.rebuildAllFragmentViews(last, last); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/FeedWidgetConfigActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/FeedWidgetConfigActivity.java index c6272347c..3556a038c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/FeedWidgetConfigActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/FeedWidgetConfigActivity.java @@ -37,7 +37,7 @@ public class FeedWidgetConfigActivity extends ExternalActionActivity { SharedPreferences preferences = FeedWidgetConfigActivity.this.getSharedPreferences("shortcut_widget", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.putInt("account" + creatingAppWidgetId, fragment1.getCurrentAccount()); - editor.putLong("dialogId" + creatingAppWidgetId, dids.get(0)); + editor.putLong("dialogId" + creatingAppWidgetId, dids.get(0).dialogId); editor.commit(); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(FeedWidgetConfigActivity.this); @@ -50,11 +50,11 @@ public class FeedWidgetConfigActivity extends ExternalActionActivity { }); if (AndroidUtilities.isTablet()) { - if (layersActionBarLayout.fragmentsStack.isEmpty()) { + if (layersActionBarLayout.getFragmentStack().isEmpty()) { layersActionBarLayout.addFragmentToStack(fragment); } } else { - if (actionBarLayout.fragmentsStack.isEmpty()) { + if (actionBarLayout.getFragmentStack().isEmpty()) { actionBarLayout.addFragmentToStack(fragment); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/FilterCreateActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/FilterCreateActivity.java index 1543d8273..fe2f81a9b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/FilterCreateActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/FilterCreateActivity.java @@ -957,7 +957,7 @@ public class FilterCreateActivity extends BaseFragment { String status; if (chat.participants_count != 0) { status = LocaleController.formatPluralString("Members", chat.participants_count); - } else if (TextUtils.isEmpty(chat.username)) { + } else if (!ChatObject.isPublic(chat)) { if (ChatObject.isChannel(chat) && !chat.megagroup) { status = LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/FilterUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/FilterUsersActivity.java index 7aaa3bc34..5b1453a06 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/FilterUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/FilterUsersActivity.java @@ -49,6 +49,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; import org.telegram.messenger.DialogObject; import org.telegram.messenger.LocaleController; @@ -1086,7 +1087,7 @@ public class FilterUsersActivity extends BaseFragment implements NotificationCen if (object instanceof TLRPC.User) { objectUserName = ((TLRPC.User) object).username; } else { - objectUserName = ((TLRPC.Chat) object).username; + objectUserName = ChatObject.getPublicUsername((TLRPC.Chat) object); } if (position < localCount) { name = searchResultNames.get(position); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/FilteredSearchView.java b/TMessagesProj/src/main/java/org/telegram/ui/FilteredSearchView.java index 6cedb5d24..6aeb7b6b5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/FilteredSearchView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/FilteredSearchView.java @@ -943,7 +943,7 @@ public class FilteredSearchView extends FrameLayout implements NotificationCente cell.useSeparator = (position != getItemCount() - 1); MessageObject messageObject = messages.get(position); boolean animated = cell.getMessage() != null && cell.getMessage().getId() == messageObject.getId(); - cell.setDialog(messageObject.getDialogId(), messageObject, messageObject.messageOwner.date, false); + cell.setDialog(messageObject.getDialogId(), messageObject, messageObject.messageOwner.date, false, false); if (uiCallback.actionModeShowing()) { messageHashIdTmp.set(messageObject.getId(), messageObject.getDialogId()); cell.setChecked(uiCallback.isSelected(messageHashIdTmp), animated); @@ -982,7 +982,7 @@ public class FilteredSearchView extends FrameLayout implements NotificationCente } if (currentSearchFilter.filterType == FiltersView.FILTER_TYPE_MEDIA) { PhotoViewer.getInstance().setParentActivity(parentFragment); - PhotoViewer.getInstance().openPhoto(messages, index, 0, 0, provider); + PhotoViewer.getInstance().openPhoto(messages, index, 0, 0, 0, provider); photoViewerClassGuid = PhotoViewer.getInstance().getClassGuid(); } else if (currentSearchFilter.filterType == FiltersView.FILTER_TYPE_MUSIC || currentSearchFilter.filterType == FiltersView.FILTER_TYPE_VOICE) { if (view instanceof SharedAudioCell) { @@ -1000,11 +1000,11 @@ public class FilteredSearchView extends FrameLayout implements NotificationCente ArrayList documents = new ArrayList<>(); documents.add(message); PhotoViewer.getInstance().setParentActivity(parentFragment); - PhotoViewer.getInstance().openPhoto(documents, 0, 0, 0, provider); + PhotoViewer.getInstance().openPhoto(documents, 0, 0, 0, 0, provider); photoViewerClassGuid = PhotoViewer.getInstance().getClassGuid(); } else { PhotoViewer.getInstance().setParentActivity(parentFragment); - PhotoViewer.getInstance().openPhoto(messages, index, 0, 0, provider); + PhotoViewer.getInstance().openPhoto(messages, index, 0, 0, 0, provider); photoViewerClassGuid = PhotoViewer.getInstance().getClassGuid(); } return; @@ -1536,7 +1536,7 @@ public class FilteredSearchView extends FrameLayout implements NotificationCente DialogCell cell = ((DialogCell) holder.itemView); MessageObject messageObject = messages.get(position); - cell.setDialog(messageObject.getDialogId(), messageObject, messageObject.messageOwner.date, false); + cell.setDialog(messageObject.getDialogId(), messageObject, messageObject.messageOwner.date, false, false); cell.useSeparator = position != getItemCount() - 1; boolean animated = cell.getMessage() != null && cell.getMessage().getId() == messageObject.getId(); cell.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/FiltersSetupActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/FiltersSetupActivity.java index 006d21684..23462c166 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/FiltersSetupActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/FiltersSetupActivity.java @@ -34,6 +34,7 @@ import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; @@ -44,6 +45,8 @@ import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.HeaderCell; import org.telegram.ui.Cells.ShadowSectionCell; +import org.telegram.ui.Components.Bulletin; +import org.telegram.ui.Components.BulletinFactory; import org.telegram.ui.Components.CombinedDrawable; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.Premium.LimitReachedBottomSheet; @@ -435,12 +438,7 @@ public class FiltersSetupActivity extends BaseFragment implements NotificationCe rowCount = 0; filterHelpRow = rowCount++; int count = getMessagesController().dialogFilters.size(); - if (!getUserConfig().isPremium()) { - count--; - showAllChats = false; - } else { - showAllChats = true; - } + showAllChats = true; if (!suggestedFilters.isEmpty() && count < 10) { recommendedHeaderRow = rowCount++; recommendedStartRow = rowCount; @@ -910,6 +908,26 @@ public class FiltersSetupActivity extends BaseFragment implements NotificationCe orderChanged = true; notifyItemMoved(fromIndex, toIndex); } + + public void moveElementToStart(int index) { + int idx1 = index; + int count = filtersEndRow - filtersStartRow; + if (!showAllChats) { + idx1++; + count++; + } + + if (idx1 < 0 || idx1 >= count) { + return; + } + ArrayList filters = getMessagesController().dialogFilters; + filters.add(0, filters.remove(index)); + for (int i = 0; i <= index; ++i) { + filters.get(i).order = i; + } + orderChanged = true; + notifyItemMoved(filtersStartRow + index, filtersStartRow); + } } public class TouchHelperCallback extends ItemTouchHelper.Callback { @@ -921,7 +939,7 @@ public class FiltersSetupActivity extends BaseFragment implements NotificationCe @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { - boolean canMove = getUserConfig().isPremium() || !((viewHolder.itemView instanceof FilterCell) && ((FilterCell) viewHolder.itemView).currentFilter.isDefault()); + boolean canMove = getUserConfig().isPremium() || !((viewHolder.itemView instanceof FilterCell) && ((FilterCell) viewHolder.itemView).currentFilter.isDefault()) || true; if (viewHolder.getItemViewType() != 2 || !canMove) { return makeMovementFlags(0, 0); } @@ -930,7 +948,7 @@ public class FiltersSetupActivity extends BaseFragment implements NotificationCe @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) { - boolean canMove = getUserConfig().isPremium() || !((target.itemView instanceof FilterCell) && ((FilterCell) target.itemView).currentFilter.isDefault()); + boolean canMove = getUserConfig().isPremium() || !((target.itemView instanceof FilterCell) && ((FilterCell) target.itemView).currentFilter.isDefault()) || true; if (source.getItemViewType() != target.getItemViewType() || !canMove) { return false; } @@ -943,11 +961,29 @@ public class FiltersSetupActivity extends BaseFragment implements NotificationCe super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } + private void resetDefaultPosition() { + if (UserConfig.getInstance(UserConfig.selectedAccount).isPremium()) { + return; + } + ArrayList filters = getMessagesController().dialogFilters; + for (int i = 0; i < filters.size(); ++i) { + if (filters.get(i).isDefault() && i != 0) { + adapter.moveElementToStart(i); + listView.scrollToPosition(0); + onDefaultTabMoved(); + break; + } + } + } + @Override public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { listView.cancelClickRunnables(false); viewHolder.itemView.setPressed(true); + } else { + AndroidUtilities.cancelRunOnUIThread(this::resetDefaultPosition); + AndroidUtilities.runOnUIThread(this::resetDefaultPosition, 320); } super.onSelectedChanged(viewHolder, actionState); } @@ -964,6 +1000,12 @@ public class FiltersSetupActivity extends BaseFragment implements NotificationCe } } + protected void onDefaultTabMoved() { + BulletinFactory.of(this).createSimpleBulletin(R.raw.filter_reorder, AndroidUtilities.replaceTags(LocaleController.formatString("LimitReachedReorderFolder", R.string.LimitReachedReorderFolder, LocaleController.getString(R.string.FilterAllChats))), LocaleController.getString("PremiumMore", R.string.PremiumMore), Bulletin.DURATION_PROLONG, () -> { + presentFragment(new PremiumPreviewFragment("folders")); + }).show(); + } + @Override public ArrayList getThemeDescriptions() { ArrayList themeDescriptions = new ArrayList<>(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GroupCallActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GroupCallActivity.java index 6b457eb8b..28cea1a4c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GroupCallActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GroupCallActivity.java @@ -43,7 +43,6 @@ import android.text.InputType; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.TextWatcher; -import android.util.Log; import android.util.Property; import android.util.TypedValue; import android.view.Gravity; @@ -1034,7 +1033,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter canvas.save(); canvas.translate(0, -AndroidUtilities.statusBarHeight); - parentActivity.getActionBarLayout().draw(canvas); + parentActivity.getActionBarLayout().getView().draw(canvas); canvas.drawColor(ColorUtils.setAlphaComponent(Color.BLACK, (int) (255 * 0.3f))); canvas.restore(); canvas.save(); @@ -1521,7 +1520,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter if (newChat != null) { currentChat = newChat; } - if (ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_INVITE) || (!ChatObject.isChannel(currentChat) || currentChat.megagroup) && (!TextUtils.isEmpty(currentChat.username) || ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_INVITE)) || ChatObject.isChannel(currentChat) && !currentChat.megagroup && !TextUtils.isEmpty(currentChat.username)) { + if (ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_INVITE) || (!ChatObject.isChannel(currentChat) || currentChat.megagroup) && (ChatObject.isPublic(currentChat) || ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_INVITE)) || ChatObject.isChannel(currentChat) && !currentChat.megagroup && ChatObject.isPublic(currentChat)) { inviteItem.setVisibility(View.VISIBLE); } else { inviteItem.setVisibility(View.GONE); @@ -1652,7 +1651,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter protected void makeFocusable(BottomSheet bottomSheet, AlertDialog alertDialog, EditTextBoldCursor editText, boolean showKeyboard) { if (!enterEventSent) { - BaseFragment fragment = parentActivity.getActionBarLayout().fragmentsStack.get(parentActivity.getActionBarLayout().fragmentsStack.size() - 1); + BaseFragment fragment = parentActivity.getActionBarLayout().getFragmentStack().get(parentActivity.getActionBarLayout().getFragmentStack().size() - 1); if (fragment instanceof ChatActivity) { boolean keyboardVisible = ((ChatActivity) fragment).needEnterText(); enterEventSent = true; @@ -1710,7 +1709,9 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter groupCallInstance = new GroupCallActivity(activity, account, call, chat, null, hasFewPeers, scheduledHash); } groupCallInstance.parentActivity = activity; - groupCallInstance.show(); + AndroidUtilities.runOnUIThread(() -> { + groupCallInstance.show(); + }); } private GroupCallActivity(Context context, AccountInstance account, ChatObject.Call groupCall, TLRPC.Chat chat, TLRPC.InputPeer schedulePeer, boolean scheduleHasFewPeers, String scheduledHash) { @@ -1762,7 +1763,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter } }; setOnDismissListener(dialog -> { - BaseFragment fragment = parentActivity.getActionBarLayout().fragmentsStack.get(parentActivity.getActionBarLayout().fragmentsStack.size() - 1); + BaseFragment fragment = parentActivity.getActionBarLayout().getFragmentStack().get(parentActivity.getActionBarLayout().getFragmentStack().size() - 1); if (anyEnterEventSent) { if (fragment instanceof ChatActivity) { ((ChatActivity) fragment).onEditTextDialogClose(true, true); @@ -1885,7 +1886,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter builder.setMessage(LocaleController.getString("VoipGroupStopRecordingText", R.string.VoipGroupStopRecordingText)); } builder.setPositiveButton(LocaleController.getString("Stop", R.string.Stop), (dialogInterface, i) -> { - call.toggleRecord(null, 0); + call.toggleRecord(null, ChatObject.Call.RECORD_TYPE_AUDIO); getUndoView().showWithAction(0, video ? UndoView.ACTION_VOIP_VIDEO_RECORDING_FINISHED : UndoView.ACTION_VOIP_RECORDING_FINISHED, null); }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); @@ -2047,7 +2048,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter if (s.length() > 40) { ignoreTextChange = true; s.delete(40, s.length()); - AndroidUtilities.shakeView(editText, 2, 0); + AndroidUtilities.shakeView(editText); editText.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); ignoreTextChange = false; } @@ -3355,7 +3356,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter parentActivity.presentFragment(new ProfileActivity(args)); dismiss(); } else if (position == listAdapter.addMemberRow) { - if (ChatObject.isChannel(currentChat) && !currentChat.megagroup && !TextUtils.isEmpty(currentChat.username)) { + if (ChatObject.isChannel(currentChat) && !currentChat.megagroup && ChatObject.isPublic(currentChat)) { getLink(false); return; } @@ -4269,7 +4270,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter return; } playingHandAnimation = true; - AndroidUtilities.shakeView(muteLabel[0], 2, 0); + AndroidUtilities.shakeView(muteLabel[0]); v.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); int num = Utilities.random.nextInt(100); int endFrame; @@ -5880,11 +5881,11 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter private void getLink(boolean copy) { TLRPC.Chat newChat = accountInstance.getMessagesController().getChat(currentChat.id); - if (newChat != null && TextUtils.isEmpty(newChat.username)) { + if (newChat != null && !ChatObject.isPublic(newChat)) { TLRPC.ChatFull chatFull = accountInstance.getMessagesController().getChatFull(currentChat.id); - String url; - if (!TextUtils.isEmpty(currentChat.username)) { - url = accountInstance.getMessagesController().linkPrefix + "/" + currentChat.username; + String url, username; + if (!TextUtils.isEmpty(username = ChatObject.getPublicUsername(currentChat))) { + url = accountInstance.getMessagesController().linkPrefix + "/" + username; } else { url = chatFull != null && chatFull.exported_invite != null ? chatFull.exported_invite.link : null; } @@ -5930,8 +5931,8 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter if (!copy && ChatObject.canManageCalls(currentChat) && !call.call.join_muted) { invites[0] = null; } - if (invites[0] == null && invites[1] == null && !TextUtils.isEmpty(currentChat.username)) { - openShareAlert(true, null, accountInstance.getMessagesController().linkPrefix + "/" + currentChat.username, copy); + if (invites[0] == null && invites[1] == null && ChatObject.isPublic(currentChat)) { + openShareAlert(true, null, accountInstance.getMessagesController().linkPrefix + "/" + ChatObject.getPublicUsername(currentChat), copy); } else { openShareAlert(false, invites[0], invites[1], copy); } @@ -5952,7 +5953,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter } else { boolean keyboardIsOpen = false; if (parentActivity != null) { - BaseFragment fragment = parentActivity.getActionBarLayout().fragmentsStack.get(parentActivity.getActionBarLayout().fragmentsStack.size() - 1); + BaseFragment fragment = parentActivity.getActionBarLayout().getFragmentStack().get(parentActivity.getActionBarLayout().getFragmentStack().size() - 1); if (fragment instanceof ChatActivity) { keyboardIsOpen = ((ChatActivity) fragment).needEnterText(); anyEnterEventSent = true; @@ -5976,7 +5977,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter } shareAlert = new ShareAlert(getContext(), null, null, message, urlMuted, false, urlUnmuted, urlMuted, false, true) { @Override - protected void onSend(LongSparseArray dids, int count) { + protected void onSend(LongSparseArray dids, int count, TLRPC.TL_forumTopic topic) { if (dids.size() == 1) { getUndoView().showWithAction(dids.valueAt(0).id, UndoView.ACTION_VOIP_INVITE_LINK_SENT, count); } else { @@ -6047,7 +6048,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter if (shouldAdd && "USER_NOT_PARTICIPANT".equals(error.text)) { processSelectedOption(null, id, 3); } else { - BaseFragment fragment = parentActivity.getActionBarLayout().fragmentsStack.get(parentActivity.getActionBarLayout().fragmentsStack.size() - 1); + BaseFragment fragment = parentActivity.getActionBarLayout().getFragmentStack().get(parentActivity.getActionBarLayout().getFragmentStack().size() - 1); AlertsCreator.processError(currentAccount, error, fragment, req); } }); @@ -6411,7 +6412,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter VoIPService service = VoIPService.getSharedInstance(); if (service == null || isRtmpStream()) { soundButton.setData(R.drawable.msg_voiceshare, Color.WHITE, 0, 0.3f, true, LocaleController.getString("VoipChatShare", R.string.VoipChatShare), false, animated); - soundButton.setEnabled(!TextUtils.isEmpty(currentChat.username) || ChatObject.hasAdminRights(currentChat) && ChatObject.canAddUsers(currentChat), false); + soundButton.setEnabled(ChatObject.isPublic(currentChat) || ChatObject.hasAdminRights(currentChat) && ChatObject.canAddUsers(currentChat), false); soundButton.setChecked(true, false); return; } else { @@ -7050,7 +7051,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter } else if (object instanceof TLRPC.User) { TLRPC.User user = (TLRPC.User) object; builder.setPositiveButton(LocaleController.getString("VoipGroupAdd", R.string.VoipGroupAdd), (dialogInterface, i) -> { - BaseFragment fragment = parentActivity.getActionBarLayout().fragmentsStack.get(parentActivity.getActionBarLayout().fragmentsStack.size() - 1); + BaseFragment fragment = parentActivity.getActionBarLayout().getFragmentStack().get(parentActivity.getActionBarLayout().getFragmentStack().size() - 1); accountInstance.getMessagesController().addUserToChat(currentChat.id, user, 0, null, fragment, () -> inviteUserToCall(peerId, false)); }); } @@ -7076,7 +7077,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter dismiss(); } else if (option == 8) { parentActivity.switchToAccount(currentAccount, true); - BaseFragment fragment = parentActivity.getActionBarLayout().fragmentsStack.get(parentActivity.getActionBarLayout().fragmentsStack.size() - 1); + BaseFragment fragment = parentActivity.getActionBarLayout().getFragmentStack().get(parentActivity.getActionBarLayout().getFragmentStack().size() - 1); if (fragment instanceof ChatActivity) { if (((ChatActivity) fragment).getDialogId() == peerId) { dismiss(); @@ -7868,7 +7869,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter } if (!isRtmpStream() && ((!ChatObject.isChannel(currentChat) || currentChat.megagroup) && ChatObject.canWriteToChat(currentChat) || - ChatObject.isChannel(currentChat) && !currentChat.megagroup && !TextUtils.isEmpty(currentChat.username))) { + ChatObject.isChannel(currentChat) && !currentChat.megagroup && ChatObject.isPublic(currentChat))) { addMemberRow = rowsCount++; } else { addMemberRow = -1; @@ -7960,7 +7961,7 @@ public class GroupCallActivity extends BottomSheet implements NotificationCenter GroupCallTextCell textCell = (GroupCallTextCell) holder.itemView; int color = AndroidUtilities.getOffsetColor(Theme.getColor(Theme.key_voipgroup_lastSeenTextUnscrolled), Theme.getColor(Theme.key_voipgroup_lastSeenText), actionBar.getTag() != null ? 1.0f : 0.0f, 1.0f); textCell.setColors(color, color); - if (ChatObject.isChannel(currentChat) && !currentChat.megagroup && !TextUtils.isEmpty(currentChat.username)) { + if (ChatObject.isChannel(currentChat) && !currentChat.megagroup && ChatObject.isPublic(currentChat)) { textCell.setTextAndIcon(LocaleController.getString("VoipGroupShareLink", R.string.VoipGroupShareLink), R.drawable.msg_link, false); } else { textCell.setTextAndIcon(LocaleController.getString("VoipGroupInviteMember", R.string.VoipGroupInviteMember), R.drawable.msg_contact_add, false); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java index bfdb49ca2..2a7cd8f20 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java @@ -771,7 +771,7 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen } } }); - listView.setAnimateEmptyView(true, 0); + listView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); floatingButton = new ImageView(context); floatingButton.setScaleType(ImageView.ScaleType.CENTER); @@ -1270,7 +1270,7 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen inviteViaLink = ChatObject.canUserDoAdminAction(chat, ChatObject.ACTION_INVITE) ? 1 : 0; } else if (channelId != 0) { TLRPC.Chat chat = getMessagesController().getChat(channelId); - inviteViaLink = ChatObject.canUserDoAdminAction(chat, ChatObject.ACTION_INVITE) && TextUtils.isEmpty(chat.username) ? 2 : 0; + inviteViaLink = ChatObject.canUserDoAdminAction(chat, ChatObject.ACTION_INVITE) && !ChatObject.isPublic(chat) ? 2 : 0; } else { inviteViaLink = 0; } @@ -1354,7 +1354,7 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen if (object instanceof TLRPC.User) { objectUserName = ((TLRPC.User) object).username; } else { - objectUserName = ((TLRPC.Chat) object).username; + objectUserName = ChatObject.getPublicUsername((TLRPC.Chat) object); } if (position < localCount) { name = searchResultNames.get(position); @@ -1515,7 +1515,7 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen } else { TLRPC.Chat chat = (TLRPC.Chat) object; name = chat.title; - username = chat.username; + username = ChatObject.getPublicUsername(chat); } String tName = LocaleController.getInstance().getTranslitString(name); if (name.equals(tName)) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java index a63725485..f467d6917 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java @@ -595,7 +595,7 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati if (v != null) { v.vibrate(200); } - AndroidUtilities.shakeView(editText, 2, 0); + AndroidUtilities.shakeView(editText); return; } donePressed = true; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/IdenticonActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/IdenticonActivity.java index bf20f0448..7c3994d9e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/IdenticonActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/IdenticonActivity.java @@ -311,7 +311,7 @@ public class IdenticonActivity extends BaseFragment implements NotificationCente } @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen && !backward && emojiText != null) { emojiTextView.setText(Emoji.replaceEmoji(emojiText, emojiTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(32), false)); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java index eaaf271f9..b0142e33f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java @@ -527,7 +527,7 @@ public class IntroActivity extends BaseFragment implements NotificationCenter.No } @Override - protected AnimatorSet onCustomTransitionAnimation(boolean isOpen, Runnable callback) { + public AnimatorSet onCustomTransitionAnimation(boolean isOpen, Runnable callback) { if (isOnLogout) { AnimatorSet set = new AnimatorSet().setDuration(50); set.playTogether(ValueAnimator.ofFloat()); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/KeyboardHideHelper.java b/TMessagesProj/src/main/java/org/telegram/ui/KeyboardHideHelper.java index 8a4cfae65..7ef566644 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/KeyboardHideHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/KeyboardHideHelper.java @@ -147,7 +147,7 @@ public class KeyboardHideHelper { va.setDuration(200); va.start(); if (end && startedAtBottom && ca != null) { - ca.scrollToLastMessage(true); + ca.scrollToLastMessage(true, false); } startedOutsideView = false; return true; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LNavigation/LNavigation.java b/TMessagesProj/src/main/java/org/telegram/ui/LNavigation/LNavigation.java new file mode 100644 index 000000000..d471a211b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/LNavigation/LNavigation.java @@ -0,0 +1,2258 @@ +package org.telegram.ui.LNavigation; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.HapticFeedbackConstants; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.graphics.ColorUtils; +import androidx.core.math.MathUtils; +import androidx.core.view.GestureDetectorCompat; +import androidx.dynamicanimation.animation.FloatValueHolder; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; +import androidx.viewpager.widget.ViewPager; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ImageLoader; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.R; +import org.telegram.messenger.Utilities; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarMenuSubItem; +import org.telegram.ui.ActionBar.ActionBarPopupWindow; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.DrawerLayoutContainer; +import org.telegram.ui.ActionBar.INavigationLayout; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.CheckBoxCell; +import org.telegram.ui.Components.BackButtonMenu; +import org.telegram.ui.Components.FloatingDebug.FloatingDebugController; +import org.telegram.ui.Components.FloatingDebug.FloatingDebugProvider; +import org.telegram.ui.Components.GroupCallPip; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.SeekBarView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +public class LNavigation extends FrameLayout implements INavigationLayout, FloatingDebugProvider { + private final static boolean ALLOW_OPEN_STIFFNESS_CONTROL = false; + private static float SPRING_STIFFNESS = 1000f; + private static float SPRING_DAMPING_RATIO = 1f; + private final static float SPRING_STIFFNESS_PREVIEW = 650f; + private final static float SPRING_STIFFNESS_PREVIEW_OUT = 800f; + private final static float SPRING_STIFFNESS_PREVIEW_EXPAND = 750f; + private final static float SPRING_MULTIPLIER = 1000f; + private List pulledDialogs = new ArrayList<>(); + + /** + * Temp rect to calculate if it's ignored view + */ + private Rect ignoreRect = new Rect(); + + /** + * Temp path for clipping + */ + private Path path = new Path(); + + /** + * Darker paint + */ + private Paint dimmPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + /** + * Flag if we should remove extra height for action bar + */ + private boolean removeActionBarExtraHeight; + + /** + * Current fragment stack + */ + private List fragmentStack = new ArrayList<>(); + + /** + * Unmodifiable fragment stack for {@link LNavigation#getFragmentStack()} + */ + private List unmodifiableFragmentStack = Collections.unmodifiableList(fragmentStack); + + /** + * Delegate for this view + */ + private INavigationLayoutDelegate delegate; + + /** + * A listener when fragment stack is being changed + */ + private Runnable onFragmentStackChangedListener; + + /** + * Drawer layout container (For the swipe-back-to-drawer feature) + */ + private DrawerLayoutContainer drawerLayoutContainer; + + /** + * Currently running spring animation + */ + private SpringAnimation currentSpringAnimation; + + /** + * Overlay layout for containers like shared ActionBar + */ + private FrameLayout overlayLayout; + + /** + * Current swipe progress + */ + private float swipeProgress; + + /** + * Start scroll offset + */ + private float startScroll; + + /** + * Header shadow drawable + */ + private Drawable headerShadowDrawable; + + /** + * Front view shadow drawable + */ + private Drawable layerShadowDrawable; + + /** + * Gesture detector for scroll + */ + private GestureDetectorCompat gestureDetector; + + /** + * If there's currently scroll in progress + */ + private boolean isSwipeInProgress; + + /** + * If swipe back should be disallowed + */ + private boolean isSwipeDisallowed; + + /** + * If set, should be canceled if trying to open another fragment + */ + private Runnable delayedPresentAnimation; + + /** + * If navigation is used in bubble mode + */ + private boolean isInBubbleMode; + + /** + * If device is currently showing action mode over our ActionBar + */ + private boolean isInActionMode; + + /** + * If menu buttons in preview should be highlighted + */ + private boolean highlightActionButtons = false; + + /** + * Custom animation in progress + */ + private AnimatorSet customAnimation; + + /** + * Preview fragment's menu + */ + private ActionBarPopupWindow.ActionBarPopupWindowLayout previewMenu; + + /** + * A blurred snapshot of background fragment + */ + private Bitmap blurredBackFragmentForPreview; + + /** + * Snapshot of a small preview fragment + */ + private Bitmap previewFragmentSnapshot; + + /** + * Bounds of small preview fragment + */ + private Rect previewFragmentRect = new Rect(); + + /** + * Preview expand progress + */ + private float previewExpandProgress; + + /** + * Paint for blurred snapshot + */ + private Paint blurPaint = new Paint(Paint.DITHER_FLAG | Paint.ANTI_ALIAS_FLAG); + + /** + * View that captured current touch input + */ + private View touchCapturedView; + + /** + * Flag if layout was portrait + */ + private boolean wasPortrait; + + /** + * Callback after preview fragment is opened + */ + private Runnable previewOpenCallback; + + /** + * Flag if navigation view should disappear when last fragment closes + */ + private boolean useAlphaAnimations; + + /** + * Background view for tablets + */ + private View backgroundView; + + /** + * Flag that indicates that user can press button of the preview menu + */ + private boolean allowToPressByHover; + + /** + * Flag if menu hover should be allowed (Only first time opening preview) + */ + private boolean isFirstHoverAllowed; + + // TODO: Split theme logic to another component + private ValueAnimator themeAnimator; + private StartColorsProvider startColorsProvider = new StartColorsProvider(); + private Theme.MessageDrawable messageDrawableOutStart; + private Theme.MessageDrawable messageDrawableOutMediaStart; + private ThemeAnimationSettings.onAnimationProgress animationProgressListener; + private ArrayList themeAnimatorDelegate = new ArrayList<>(); + private ArrayList presentingFragmentDescriptions; + + private float themeAnimationValue; + private ArrayList> themeAnimatorDescriptions = new ArrayList<>(); + private ArrayList animateStartColors = new ArrayList<>(); + private ArrayList animateEndColors = new ArrayList<>(); + + private int fromBackgroundColor; + + private LinearLayout stiffnessControl; + private CheckBoxCell openChatCheckbox; + + private String titleOverlayTitle; + private int titleOverlayTitleId; + private Runnable titleOverlayAction; + + public LNavigation(@NonNull Context context) { + this(context, null); + } + + public LNavigation(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + + overlayLayout = new FrameLayout(context); + addView(overlayLayout); + + headerShadowDrawable = getResources().getDrawable(R.drawable.header_shadow).mutate(); + layerShadowDrawable = getResources().getDrawable(R.drawable.layer_shadow).mutate(); + + dimmPaint.setColor(0x7a000000); + setWillNotDraw(false); + + int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + gestureDetector = new GestureDetectorCompat(context, new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDown(MotionEvent e) { + return true; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + if (highlightActionButtons && !allowToPressByHover && isFirstHoverAllowed && isInPreviewMode() && (Math.abs(distanceX) >= touchSlop || Math.abs(distanceY) >= touchSlop) && !isSwipeInProgress && previewMenu != null) { + allowToPressByHover = true; + } + + if (allowToPressByHover && previewMenu != null && (previewMenu.getSwipeBack() == null || previewMenu.getSwipeBack().isForegroundOpen())) { + for (int i = 0; i < previewMenu.getItemsCount(); ++i) { + ActionBarMenuSubItem button = (ActionBarMenuSubItem) previewMenu.getItemAt(i); + if (button != null) { + Drawable ripple = button.getBackground(); + button.getGlobalVisibleRect(AndroidUtilities.rectTmp2); + boolean shouldBeEnabled = AndroidUtilities.rectTmp2.contains((int) e2.getX(), (int) e2.getY()), enabled = ripple.getState().length == 2; + if (shouldBeEnabled != enabled) { + ripple.setState(shouldBeEnabled ? new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled} : new int[]{}); + if (shouldBeEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + try { + button.performHapticFeedback(HapticFeedbackConstants.TEXT_HANDLE_MOVE, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + } catch (Exception ignore) {} + } + } + } + } + } + + if (!isSwipeInProgress && !isSwipeDisallowed) { + if (Math.abs(distanceX) >= Math.abs(distanceY) * 1.5f && distanceX <= -touchSlop && !isIgnoredView(getForegroundView(), e2, ignoreRect) && + getLastFragment() != null && getLastFragment().canBeginSlide() && getLastFragment().isSwipeBackEnabled(e2) && fragmentStack.size() >= 2 && !isInActionMode && + !isInPreviewMode()) { + isSwipeInProgress = true; + + startScroll = swipeProgress - MathUtils.clamp((e2.getX() - e1.getX()) / getWidth(), 0, 1); + + if (getParentActivity().getCurrentFocus() != null) { + AndroidUtilities.hideKeyboard(getParentActivity().getCurrentFocus()); + } + + if (getBackgroundView() != null) { + getBackgroundView().setVisibility(VISIBLE); + } + getLastFragment().prepareFragmentToSlide(true, true); + getLastFragment().onBeginSlide(); + BaseFragment bgFragment = getBackgroundFragment(); + if (bgFragment != null) { + bgFragment.setPaused(false); + bgFragment.prepareFragmentToSlide(false, true); + bgFragment.onBeginSlide(); + } + + MotionEvent e = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0); + for (int i = 0; i < getChildCount(); i++) { + getChildAt(i).dispatchTouchEvent(e); + } + e.recycle(); + } else { + isSwipeDisallowed = true; + } + } + + if (isSwipeInProgress) { + swipeProgress = MathUtils.clamp(startScroll + (e2.getX() - e1.getX()) / getWidth(), 0, 1); + invalidateTranslation(); + } + return isSwipeInProgress; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + if (isSwipeInProgress) { + if (velocityX >= 800) { + closeLastFragment(true, false, velocityX / 15f); + clearTouchFlags(); + return true; + } + } + return false; + } + }); + gestureDetector.setIsLongpressEnabled(false); + + stiffnessControl = new LinearLayout(context); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + stiffnessControl.setElevation(AndroidUtilities.dp(12)); + } + stiffnessControl.setOrientation(LinearLayout.VERTICAL); + stiffnessControl.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + + TextView titleView = new TextView(context); + titleView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + titleView.setGravity(Gravity.CENTER); + titleView.setText(String.format(Locale.ROOT, "Stiffness: %f", SPRING_STIFFNESS)); + stiffnessControl.addView(titleView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36)); + + SeekBarView seekBarView = new SeekBarView(context); + seekBarView.setReportChanges(true); + seekBarView.setDelegate(new SeekBarView.SeekBarViewDelegate() { + @Override + public void onSeekBarDrag(boolean stop, float progress) { + titleView.setText(String.format(Locale.ROOT, "Stiffness: %f", 500f + progress * 1000f)); + if (stop) { + SPRING_STIFFNESS = 500f + progress * 1000f; + } + } + + @Override + public void onSeekBarPressed(boolean pressed) { + + } + }); + seekBarView.setProgress((SPRING_STIFFNESS - 500f) / 1000f); + stiffnessControl.addView(seekBarView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 38)); + + TextView dampingTitle = new TextView(context); + dampingTitle.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + dampingTitle.setGravity(Gravity.CENTER); + dampingTitle.setText(String.format(Locale.ROOT, "Damping ratio: %f", SPRING_DAMPING_RATIO)); + stiffnessControl.addView(dampingTitle, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36)); + + seekBarView = new SeekBarView(context); + seekBarView.setReportChanges(true); + seekBarView.setDelegate(new SeekBarView.SeekBarViewDelegate() { + @Override + public void onSeekBarDrag(boolean stop, float progress) { + dampingTitle.setText(String.format(Locale.ROOT, "Damping ratio: %f", 0.2f + progress * 0.8f)); + if (stop) { + SPRING_DAMPING_RATIO = 0.2f + progress * 0.8f; + } + } + + @Override + public void onSeekBarPressed(boolean pressed) { + + } + }); + seekBarView.setProgress((SPRING_DAMPING_RATIO - 0.2f) / 0.8f); + stiffnessControl.addView(seekBarView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 38)); + + openChatCheckbox = new CheckBoxCell(context, 1); + openChatCheckbox.setText("Show chat open measurement", null, false, false); + openChatCheckbox.setOnClickListener(v -> openChatCheckbox.setChecked(!openChatCheckbox.isChecked(), true)); + stiffnessControl.addView(openChatCheckbox, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36)); + + stiffnessControl.setVisibility(GONE); + overlayLayout.addView(stiffnessControl, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM)); + } + + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + if (getChildCount() >= 3) { + throw new IllegalStateException("LNavigation must have no more than 3 child views!"); + } + + super.addView(child, index, params); + } + + public boolean doShowOpenChat() { + return openChatCheckbox.isChecked(); + } + + public LinearLayout getStiffnessControl() { + return stiffnessControl; + } + + @Override + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + super.requestDisallowInterceptTouchEvent(disallowIntercept); + + if (disallowIntercept && isSwipeInProgress) { + isSwipeInProgress = false; + onReleaseTouch(); + } + isSwipeDisallowed = disallowIntercept; + } + + private void animateReset() { + BaseFragment fragment = getLastFragment(); + BaseFragment bgFragment = getBackgroundFragment(); + if (fragment == null) { + return; + } + + fragment.onTransitionAnimationStart(true, true); + if (bgFragment != null) { + bgFragment.onTransitionAnimationStart(false, true); + } + + FloatValueHolder valueHolder = new FloatValueHolder(swipeProgress * SPRING_MULTIPLIER); + currentSpringAnimation = new SpringAnimation(valueHolder) + .setSpring(new SpringForce(0f) + .setStiffness(SPRING_STIFFNESS) + .setDampingRatio(SPRING_DAMPING_RATIO)); + currentSpringAnimation.addUpdateListener((animation, value, velocity) -> { + swipeProgress = value / SPRING_MULTIPLIER; + invalidateTranslation(); + fragment.onTransitionAnimationProgress(true, 1f - swipeProgress); + if (bgFragment != null) { + bgFragment.onTransitionAnimationProgress(false, 1f - swipeProgress); + } + }); + Runnable onEnd = ()->{ + fragment.onTransitionAnimationEnd(true, true); + fragment.prepareFragmentToSlide(true, false); + if (bgFragment != null) { + bgFragment.onTransitionAnimationEnd(false, true); + } + + swipeProgress = 0f; + invalidateTranslation(); + if (getBackgroundView() != null) { + getBackgroundView().setVisibility(GONE); + } + + fragment.onBecomeFullyVisible(); + if (bgFragment != null) { + bgFragment.setPaused(true); + bgFragment.onBecomeFullyHidden(); + bgFragment.prepareFragmentToSlide(false, false); + } + + currentSpringAnimation = null; + }; + currentSpringAnimation.addEndListener((animation, canceled, value, velocity) -> { + if (animation == currentSpringAnimation) { + onEnd.run(); + } + }); + if (swipeProgress != 0f) { + currentSpringAnimation.start(); + } else { + onEnd.run(); + } + } + + private boolean processTouchEvent(MotionEvent ev) { + int act = ev.getActionMasked(); + if (isTransitionAnimationInProgress()) { + return true; + } + + if (!gestureDetector.onTouchEvent(ev)) { + switch (act) { + case MotionEvent.ACTION_DOWN: + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (isFirstHoverAllowed && !allowToPressByHover) { + clearTouchFlags(); + } else if (allowToPressByHover && previewMenu != null) { + for (int i = 0; i < previewMenu.getItemsCount(); ++i) { + ActionBarMenuSubItem button = (ActionBarMenuSubItem) previewMenu.getItemAt(i); + if (button != null) { + button.getGlobalVisibleRect(AndroidUtilities.rectTmp2); + boolean shouldBeEnabled = AndroidUtilities.rectTmp2.contains((int) ev.getX(), (int) ev.getY()); + if (shouldBeEnabled) { + button.performClick(); + } + } + } + + clearTouchFlags(); + } else if (isSwipeInProgress) { + clearTouchFlags(); + onReleaseTouch(); + } else if (isSwipeDisallowed) { + clearTouchFlags(); + } + return false; + } + } + return isSwipeInProgress; + } + + private void onReleaseTouch() { + if (swipeProgress < 0.5f) { + animateReset(); + } else { + closeLastFragment(true, false); + } + } + + private void clearTouchFlags() { + isSwipeDisallowed = false; + isSwipeInProgress = false; + allowToPressByHover = false; + isFirstHoverAllowed = false; + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + processTouchEvent(event); + + return true; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (processTouchEvent(ev) && touchCapturedView == null) { + return true; + } + + if (getChildCount() < 1) { + return false; + } + + if (getForegroundView() != null) { + View capturedView = touchCapturedView; + View fg = getForegroundView(); + ev.offsetLocation(-getPaddingLeft(), -getPaddingTop()); + boolean overlay = overlayLayout.dispatchTouchEvent(ev) || capturedView == overlayLayout; + if (overlay) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + touchCapturedView = overlayLayout; + + MotionEvent e = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0); + for (int i = 0; i < getChildCount() - 1; i++) { + getChildAt(i).dispatchTouchEvent(e); + } + e.recycle(); + } + } + if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) { + touchCapturedView = null; + } + if (overlay) { + return true; + } + if (capturedView != null) { + return capturedView.dispatchTouchEvent(ev) || ev.getActionMasked() == MotionEvent.ACTION_DOWN; + } + + boolean foreground = fg.dispatchTouchEvent(ev); + if (foreground) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + touchCapturedView = fg; + } + } + return foreground || ev.getActionMasked() == MotionEvent.ACTION_DOWN; + } + return super.dispatchTouchEvent(ev); + } + + @Override + public boolean hasIntegratedBlurInPreview() { + return true; + } + + @Override + public boolean presentFragment(NavigationParams params) { + BaseFragment fragment = params.fragment; + if (!params.isFromDelay && (fragment == null || checkTransitionAnimation() || delegate != null && params.checkPresentFromDelegate && + !delegate.needPresentFragment(this, params) || !fragment.onFragmentCreate() || delayedPresentAnimation != null)) { + return false; + } + + if (getParentActivity().getCurrentFocus() != null) { + AndroidUtilities.hideKeyboard(getParentActivity().getCurrentFocus()); + } + + if (!params.isFromDelay) { + fragment.setInPreviewMode(params.preview); + if (previewMenu != null) { + if (previewMenu.getParent() != null) { + ((ViewGroup) previewMenu.getParent()).removeView(previewMenu); + } + } + previewMenu = params.menuView; + fragment.setInMenuMode(previewMenu != null); + fragment.setParentLayout(this); + } + boolean animate = params.preview || MessagesController.getGlobalMainSettings().getBoolean("view_animations", true) && + !params.noAnimation && (useAlphaAnimations || fragmentStack.size() >= 1); + + BaseFragment prevFragment = params.isFromDelay ? getBackgroundFragment() : getLastFragment(); + Runnable onFragmentOpened = ()->{ + if (params.removeLast && prevFragment != null) { + removeFragmentFromStack(prevFragment); + } + }; + if (animate) { + if (!params.isFromDelay) { + if (params.preview) { + View bgView = getForegroundView(); + float scaleFactor = 8; + int w = (int) (bgView.getMeasuredWidth() / scaleFactor); + int h = (int) (bgView.getMeasuredHeight() / scaleFactor); + Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + canvas.scale(1.0f / scaleFactor, 1.0f / scaleFactor); + canvas.drawColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + bgView.draw(canvas); + Utilities.stackBlurBitmap(bitmap, Math.max(8, Math.max(w, h) / 150)); + blurredBackFragmentForPreview = bitmap; + + if (getParent() != null) { + getParent().requestDisallowInterceptTouchEvent(true); + } + isFirstHoverAllowed = true; + } + + FragmentHolderView holderView = onCreateHolderView(fragment); + if (params.preview) { + MarginLayoutParams layoutParams = (MarginLayoutParams) holderView.getLayoutParams(); + layoutParams.leftMargin = layoutParams.topMargin = layoutParams.rightMargin = layoutParams.bottomMargin = AndroidUtilities.dp(8); + + if (previewMenu != null) { + previewMenu.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec((int) (getHeight() * 0.5f), MeasureSpec.AT_MOST)); + layoutParams = (MarginLayoutParams) fragment.getFragmentView().getLayoutParams(); + layoutParams.bottomMargin += AndroidUtilities.dp(8) + previewMenu.getMeasuredHeight(); + + if (LocaleController.isRTL) { + previewMenu.setTranslationX(getWidth() - previewMenu.getMeasuredWidth() - AndroidUtilities.dp(8)); + } else { + previewMenu.setTranslationX(-AndroidUtilities.dp(8)); + } + previewMenu.setTranslationY(getHeight() - AndroidUtilities.dp(24) - previewMenu.getMeasuredHeight()); + holderView.addView(previewMenu, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), 0, 0, 0, 8)); + } else { + layoutParams.topMargin += AndroidUtilities.dp(52); + } + + holderView.setOnClickListener(v -> finishPreviewFragment()); + } + addView(holderView, getChildCount() - 1); + fragmentStack.add(fragment); + notifyFragmentStackChanged(); + fragment.setPaused(false); + + swipeProgress = 1f; + invalidateTranslation(); + } + + if (fragment.needDelayOpenAnimation() && !params.delayDone) { + AndroidUtilities.runOnUIThread(delayedPresentAnimation = ()->{ + delayedPresentAnimation = null; + + params.isFromDelay = true; + params.delayDone = true; + presentFragment(params); + }, 200); + return true; + } + + fragment.onTransitionAnimationStart(true, false); + if (prevFragment != null) { + prevFragment.onTransitionAnimationStart(false, true); + } + + customAnimation = fragment.onCustomTransitionAnimation(true, ()-> { + customAnimation = null; + fragment.onTransitionAnimationEnd(true, false); + + swipeProgress = 0f; + invalidateTranslation(); + if (getBackgroundView() != null) { + getBackgroundView().setVisibility(GONE); + } + + fragment.onBecomeFullyVisible(); + if (prevFragment != null) { + prevFragment.onTransitionAnimationEnd(false, true); + prevFragment.onBecomeFullyHidden(); + } + onFragmentOpened.run(); + + }); + if (customAnimation != null) { + getForegroundView().setTranslationX(0); + return true; + } + + FloatValueHolder valueHolder = new FloatValueHolder(SPRING_MULTIPLIER); + currentSpringAnimation = new SpringAnimation(valueHolder) + .setSpring(new SpringForce(0f) + .setStiffness(params.preview ? SPRING_STIFFNESS_PREVIEW : SPRING_STIFFNESS) + .setDampingRatio(params.preview ? 0.6f : SPRING_DAMPING_RATIO)); + currentSpringAnimation.addUpdateListener((animation, value, velocity) -> { + swipeProgress = value / SPRING_MULTIPLIER; + invalidateTranslation(); + fragment.onTransitionAnimationProgress(true, 1f - swipeProgress); + }); + currentSpringAnimation.addEndListener((animation, canceled, value, velocity) -> { + if (animation == currentSpringAnimation) { + fragment.onTransitionAnimationEnd(true, false); + + swipeProgress = 0f; + invalidateTranslation(); + if (!params.preview && getBackgroundView() != null) { + getBackgroundView().setVisibility(GONE); + } + + fragment.onBecomeFullyVisible(); + if (prevFragment != null) { + prevFragment.onTransitionAnimationEnd(false, true); + prevFragment.onBecomeFullyHidden(); + prevFragment.setPaused(true); + } + onFragmentOpened.run(); + + currentSpringAnimation = null; + + if (params.preview && previewOpenCallback != null) { + previewOpenCallback.run(); + } + previewOpenCallback = null; + } + }); + currentSpringAnimation.start(); + } else if (!params.preview) { + if (fragment.needDelayOpenAnimation() && !params.delayDone && params.needDelayWithoutAnimation) { + AndroidUtilities.runOnUIThread(delayedPresentAnimation = ()->{ + delayedPresentAnimation = null; + + params.isFromDelay = true; + params.delayDone = true; + presentFragment(params); + }, 200); + return true; + } + addFragmentToStack(fragment, -1, true); + onFragmentOpened.run(); + } + + return true; + } + + /** + * Invalidates current fragment and action bar translation + */ + private void invalidateTranslation() { + if (useAlphaAnimations && fragmentStack.size() == 1) { + backgroundView.setAlpha(1f - swipeProgress); + setAlpha(1f - swipeProgress); + return; + } + + FragmentHolderView bgView = getBackgroundView(); + FragmentHolderView fgView = getForegroundView(); + + boolean preview = isInPreviewMode(); + + float widthNoPaddings = getWidth() - getPaddingLeft() - getPaddingRight(); + float heightNoPadding = getHeight() - getPaddingTop() - getPaddingBottom(); + if (preview) { + if (bgView != null) { + bgView.setTranslationX(0); + bgView.invalidate(); + } + if (fgView != null) { + fgView.setPivotX(widthNoPaddings / 2f); + fgView.setPivotY(heightNoPadding / 2f); + + fgView.setTranslationX(0); + fgView.setTranslationY(0); + + float scale = 0.5f + (1f - swipeProgress) * 0.5f; + fgView.setScaleX(scale); + fgView.setScaleY(scale); + fgView.setAlpha(1f - Math.max(swipeProgress, 0f)); + + fgView.invalidate(); + } + } else { + if (bgView != null) { + bgView.setTranslationX(-(1f - swipeProgress) * 0.35f * widthNoPaddings); + } + if (fgView != null) { + fgView.setTranslationX(swipeProgress * widthNoPaddings); + } + } + invalidate(); + + try { + if (bgView != null && fgView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getParentActivity().getWindow().setNavigationBarColor(ColorUtils.blendARGB(fgView.fragment.getNavigationBarColor(), bgView.fragment.getNavigationBarColor(), swipeProgress)); + } + } catch (Exception ignore) {} + + if (getBackgroundFragment() != null) { + getBackgroundFragment().onSlideProgress(false, swipeProgress); + } + } + + @Override + public List onGetDebugItems() { + List items = new ArrayList<>(); + BaseFragment fragment = getLastFragment(); + if (fragment != null) { + if (fragment instanceof FloatingDebugProvider) { + items.addAll(((FloatingDebugProvider) fragment).onGetDebugItems()); + } + observeDebugItemsFromView(items, fragment.getFragmentView()); + } + if (ALLOW_OPEN_STIFFNESS_CONTROL) { + items.add(new FloatingDebugController.DebugItem(LocaleController.getString(R.string.DebugAltNavigationToggleControls), () -> getStiffnessControl().setVisibility(getStiffnessControl().getVisibility() == VISIBLE ? GONE : VISIBLE))); + } + return items; + } + + private void observeDebugItemsFromView(List items, View v) { + if (v instanceof FloatingDebugProvider) { + items.addAll(((FloatingDebugProvider) v).onGetDebugItems()); + } + if (v instanceof ViewGroup) { + ViewGroup vg = (ViewGroup) v; + for (int i = 0; i < vg.getChildCount(); i++) { + observeDebugItemsFromView(items, vg.getChildAt(i)); + } + } + } + + + private FragmentHolderView getForegroundView() { + if (getChildCount() >= 2) { + return (FragmentHolderView) getChildAt(getChildCount() >= 3 ? 1 : 0); + } + return null; + } + + private FragmentHolderView getBackgroundView() { + if (getChildCount() >= 3) { + return (FragmentHolderView) getChildAt(0); + } + return null; + } + + @Override + public boolean checkTransitionAnimation() { + return isTransitionAnimationInProgress(); + } + + @Override + public boolean addFragmentToStack(BaseFragment fragment, int position) { + return addFragmentToStack(fragment, position, false); + } + + public boolean addFragmentToStack(BaseFragment fragment, int position, boolean fromPresent) { + if (!fromPresent && (delegate != null && !delegate.needAddFragmentToStack(fragment, this) || !fragment.onFragmentCreate())) { + return false; + } + fragment.setParentLayout(this); + if (position == -1 || position >= fragmentStack.size()) { + BaseFragment lastFragment = getLastFragment(); + if (lastFragment != null) { + lastFragment.setPaused(true); + lastFragment.onTransitionAnimationStart(false, true); + lastFragment.onTransitionAnimationEnd(false, true); + lastFragment.onBecomeFullyHidden(); + } + + fragmentStack.add(fragment); + notifyFragmentStackChanged(); + + FragmentHolderView holderView = onCreateHolderView(fragment); + addView(holderView, getChildCount() - 1); + + fragment.setPaused(false); + fragment.onTransitionAnimationStart(true, false); + fragment.onTransitionAnimationEnd(true, false); + fragment.onBecomeFullyVisible(); + + if (getBackgroundView() != null) { + getBackgroundView().setVisibility(GONE); + } + getForegroundView().setVisibility(VISIBLE); + } else { + fragmentStack.add(position, fragment); + notifyFragmentStackChanged(); + + if (position == fragmentStack.size() - 2) { + FragmentHolderView holderView = onCreateHolderView(fragment); + addView(holderView, getChildCount() - 2); + getBackgroundView().setVisibility(GONE); + getForegroundView().setVisibility(VISIBLE); + } + } + invalidateTranslation(); + return true; + } + + private FragmentHolderView onCreateHolderView(BaseFragment fragment) { + FragmentHolderView holderView; + if (getChildCount() >= 3) { + holderView = getBackgroundView(); + } else { + holderView = new FragmentHolderView(getContext()); + } + holderView.setFragment(fragment); + if (holderView.getParent() != null) { + holderView.setVisibility(VISIBLE); + removeView(holderView); + } + holderView.setOnClickListener(null); + resetViewProperties(holderView); + resetViewProperties(fragment.getFragmentView()); + if (fragment.getActionBar() != null) { + fragment.getActionBar().setTitleOverlayText(titleOverlayTitle, titleOverlayTitleId, titleOverlayAction); + } + return holderView; + } + + private void resetViewProperties(View v) { + if (v == null) { + return; + } + + v.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + v.setAlpha(1f); + v.setPivotX(0); + v.setPivotY(0); + v.setScaleX(1); + v.setScaleY(1); + v.setTranslationX(0); + v.setTranslationY(0); + } + + /** + * Called to notify ImageLoader and listeners about fragments stack changed + */ + private void notifyFragmentStackChanged() { + if (onFragmentStackChangedListener != null) { + onFragmentStackChangedListener.run(); + } + if (useAlphaAnimations) { + if (fragmentStack.isEmpty()) { + setVisibility(GONE); + backgroundView.setVisibility(GONE); + } else { + setVisibility(VISIBLE); + backgroundView.setVisibility(VISIBLE); + } + if (drawerLayoutContainer != null) { + drawerLayoutContainer.setAllowOpenDrawer(fragmentStack.isEmpty(), false); + } + } + ImageLoader.getInstance().onFragmentStackChanged(); + } + + @Override + public void removeFragmentFromStack(BaseFragment fragment) { + int i = fragmentStack.indexOf(fragment); + if (i == -1) { + return; + } + + int wasSize = fragmentStack.size(); + + fragment.setRemovingFromStack(true); + fragment.onFragmentDestroy(); + fragment.setParentLayout(null); + fragmentStack.remove(i); + notifyFragmentStackChanged(); + + if (i == wasSize - 1) { + BaseFragment newLastFragment = getLastFragment(); + if (newLastFragment != null) { + newLastFragment.setPaused(false); + newLastFragment.onBecomeFullyVisible(); + } + + FragmentHolderView holderView = getForegroundView(); + if (holderView != null) { + removeView(holderView); + resetViewProperties(holderView); + } + + if (getForegroundView() != null) { + getForegroundView().setVisibility(VISIBLE); + } + + if (fragmentStack.size() >= 2) { + BaseFragment bgFragment = getBackgroundFragment(); + bgFragment.setParentLayout(this); + if (holderView != null) { + holderView.setFragment(bgFragment); + } else { + holderView = onCreateHolderView(bgFragment); + } + bgFragment.onBecomeFullyHidden(); + holderView.setVisibility(GONE); + addView(holderView, getChildCount() - 2); + } + } else if (i == wasSize - 2) { + FragmentHolderView holderView = getBackgroundView(); + if (holderView != null) { + removeView(holderView); + resetViewProperties(holderView); + } + + if (fragmentStack.size() >= 2) { + BaseFragment bgFragment = getBackgroundFragment(); + bgFragment.setParentLayout(this); + if (holderView != null) { + holderView.setFragment(bgFragment); + } else { + holderView = onCreateHolderView(bgFragment); + } + bgFragment.onBecomeFullyHidden(); + holderView.setVisibility(GONE); + addView(holderView, getChildCount() - 2); + } + } + + invalidateTranslation(); + } + + @Override + public List getFragmentStack() { + return unmodifiableFragmentStack; + } + + @Override + public void setFragmentStack(List fragmentStack) { + this.fragmentStack = fragmentStack; + unmodifiableFragmentStack = Collections.unmodifiableList(fragmentStack); + } + + @Override + public void showLastFragment() { + rebuildFragments(REBUILD_FLAG_REBUILD_LAST); + } + + @Override + public void rebuildFragments(int flags) { + if (currentSpringAnimation != null && currentSpringAnimation.isRunning()) { + currentSpringAnimation.addEndListener((animation, canceled, value, velocity) -> AndroidUtilities.runOnUIThread(()-> rebuildFragments(flags))); + return; + } else if (customAnimation != null && customAnimation.isRunning()) { + customAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + AndroidUtilities.runOnUIThread(()-> rebuildFragments(flags)); + } + }); + return; + } + if (fragmentStack.isEmpty()) { + while (getChildCount() > 1) { + removeViewAt(0); + } + return; + } + + boolean rebuildLast = (flags & REBUILD_FLAG_REBUILD_LAST) != 0; + boolean rebuildBeforeLast = (flags & REBUILD_FLAG_REBUILD_ONLY_LAST) == 0 || rebuildLast && (getBackgroundView() != null && getBackgroundView().fragment != getBackgroundFragment() || getForegroundView() != null && getForegroundView().fragment == getLastFragment()); + + if (rebuildBeforeLast) { + if (getChildCount() >= 3) { + View child = getChildAt(0); + if (child instanceof FragmentHolderView) { + ((FragmentHolderView) child).fragment.setPaused(true); + } + removeViewAt(0); + } + } + if (rebuildLast) { + if (getChildCount() >= 2) { + View child = getChildAt(0); + if (child instanceof FragmentHolderView) { + ((FragmentHolderView) child).fragment.setPaused(true); + } + removeViewAt(0); + } + } + for (int i = rebuildBeforeLast ? 0 : fragmentStack.size() - 1; i < fragmentStack.size() - (rebuildLast ? 0 : 1); i++) { + BaseFragment fragment = fragmentStack.get(i); + fragment.clearViews(); + fragment.setParentLayout(this); + FragmentHolderView holderView = new FragmentHolderView(getContext()); + holderView.setFragment(fragment); + + if (i >= fragmentStack.size() - 2) { + addView(holderView, getChildCount() - 1); + } + } + if (delegate != null) { + delegate.onRebuildAllFragments(this, rebuildLast); + } + if (getLastFragment() != null) { + getLastFragment().setPaused(false); + } + } + + @Override + public void setDelegate(INavigationLayoutDelegate delegate) { + this.delegate = delegate; + } + + @Override + public void draw(Canvas canvas) { + if (useAlphaAnimations) { + canvas.save(); + path.rewind(); + AndroidUtilities.rectTmp.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); + path.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(12), AndroidUtilities.dp(12), Path.Direction.CW); + canvas.clipPath(path); + } + super.draw(canvas); + + if (!isInPreviewMode() && !(useAlphaAnimations && fragmentStack.size() <= 1) && (isSwipeInProgress() || isTransitionAnimationInProgress()) && swipeProgress != 0) { + int widthNoPaddings = getWidth() - getPaddingLeft() - getPaddingRight(); + dimmPaint.setAlpha((int) (0x7a * (1f - swipeProgress))); + canvas.drawRect(getPaddingLeft(), getPaddingTop(), widthNoPaddings * swipeProgress + getPaddingLeft(), getHeight() - getPaddingBottom(), dimmPaint); + + layerShadowDrawable.setAlpha((int) (0xFF * (1f - swipeProgress))); + layerShadowDrawable.setBounds((int) (widthNoPaddings * swipeProgress - layerShadowDrawable.getIntrinsicWidth()) + getPaddingLeft(), getPaddingTop(), (int) (widthNoPaddings * swipeProgress) + getPaddingLeft(), getHeight() - getPaddingBottom()); + layerShadowDrawable.draw(canvas); + } + if (useAlphaAnimations) { + canvas.restore(); + } + + if (previewFragmentSnapshot != null) { + canvas.save(); + path.rewind(); + AndroidUtilities.rectTmp.set(previewFragmentRect.left * (1f - previewExpandProgress), previewFragmentRect.top * (1f - previewExpandProgress), AndroidUtilities.lerp(previewFragmentRect.right, getWidth(), previewExpandProgress), AndroidUtilities.lerp(previewFragmentRect.bottom, getHeight(), previewExpandProgress)); + path.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(8), AndroidUtilities.dp(8), Path.Direction.CW); + canvas.clipPath(path); + + canvas.translate(previewFragmentRect.left * (1f - previewExpandProgress), previewFragmentRect.top * (1f - previewExpandProgress)); + canvas.scale(AndroidUtilities.lerp(1f, (float) getWidth() / previewFragmentRect.width(), previewExpandProgress), AndroidUtilities.lerp(1f, (float) getHeight() / previewFragmentRect.height(), previewExpandProgress)); + blurPaint.setAlpha((int) (0xFF * (1f - Math.min(1f, previewExpandProgress)))); + canvas.drawBitmap(previewFragmentSnapshot, 0, 0, blurPaint); + canvas.restore(); + } + } + + @Override + public void resumeDelayedFragmentAnimation() { + if (delayedPresentAnimation != null) { + AndroidUtilities.cancelRunOnUIThread(delayedPresentAnimation); + delayedPresentAnimation.run(); + } + } + + @Override + public void setUseAlphaAnimations(boolean useAlphaAnimations) { + this.useAlphaAnimations = useAlphaAnimations; + } + + @Override + public void setBackgroundView(View backgroundView) { + this.backgroundView = backgroundView; + } + + @Override + public void closeLastFragment(boolean animated, boolean forceNoAnimation) { + closeLastFragment(animated, forceNoAnimation, 0); + } + + public void closeLastFragment(boolean animated, boolean forceNoAnimation, float velocityX) { + if (fragmentStack.isEmpty() || checkTransitionAnimation() || delegate != null && !delegate.needCloseLastFragment(this)) { + return; + } + + boolean animate = animated && !forceNoAnimation && MessagesController.getGlobalMainSettings().getBoolean("view_animations", true) && (useAlphaAnimations || fragmentStack.size() >= 2); + if (animate) { + AndroidUtilities.hideKeyboard(this); + + BaseFragment lastFragment = getLastFragment(); + + BaseFragment newLastFragment = getBackgroundFragment(); + + if (getBackgroundView() != null) { + getBackgroundView().setVisibility(VISIBLE); + } + + lastFragment.onTransitionAnimationStart(false, true); + if (newLastFragment != null) { + newLastFragment.setPaused(false); + newLastFragment.onTransitionAnimationStart(true, false); + } + + if (swipeProgress == 0) { + customAnimation = lastFragment.onCustomTransitionAnimation(false, () -> { + lastFragment.onTransitionAnimationEnd(false, true); + onCloseAnimationEnd(lastFragment, newLastFragment); + + customAnimation = null; + }); + if (customAnimation != null) { + getForegroundView().setTranslationX(0); + if (getBackgroundView() != null) { + getBackgroundView().setTranslationX(0); + } + return; + } + } + + FloatValueHolder valueHolder = new FloatValueHolder(swipeProgress * SPRING_MULTIPLIER); + currentSpringAnimation = new SpringAnimation(valueHolder) + .setSpring(new SpringForce(SPRING_MULTIPLIER) + .setStiffness(isInPreviewMode() ? SPRING_STIFFNESS_PREVIEW_OUT : SPRING_STIFFNESS) + .setDampingRatio(SPRING_DAMPING_RATIO)); + if (velocityX != 0) { + currentSpringAnimation.setStartVelocity(velocityX); + } + currentSpringAnimation.addUpdateListener((animation, value, velocity) -> { + swipeProgress = value / SPRING_MULTIPLIER; + invalidateTranslation(); + lastFragment.onTransitionAnimationProgress(false, swipeProgress); + + if (newLastFragment != null) { + lastFragment.onTransitionAnimationProgress(true, swipeProgress); + } + }); + currentSpringAnimation.addEndListener((animation, canceled, value, velocity) -> { + if (animation == currentSpringAnimation) { + onCloseAnimationEnd(lastFragment, newLastFragment); + + currentSpringAnimation = null; + } + }); + currentSpringAnimation.start(); + } else { + swipeProgress = 0f; + removeFragmentFromStack(getLastFragment()); + } + } + + private void onCloseAnimationEnd(BaseFragment lastFragment, BaseFragment newLastFragment) { + lastFragment.setPaused(true); + lastFragment.setRemovingFromStack(true); + lastFragment.onTransitionAnimationEnd(false, true); + lastFragment.prepareFragmentToSlide(true, false); + lastFragment.onBecomeFullyHidden(); + lastFragment.onFragmentDestroy(); + lastFragment.setParentLayout(null); + fragmentStack.remove(lastFragment); + notifyFragmentStackChanged(); + + FragmentHolderView holderView = getForegroundView(); + holderView.setFragment(null); + removeView(holderView); + resetViewProperties(holderView); + + if (newLastFragment != null) { + newLastFragment.prepareFragmentToSlide(false, false); + newLastFragment.onTransitionAnimationEnd(true, true); + newLastFragment.onBecomeFullyVisible(); + } + + if (fragmentStack.size() >= 2) { + BaseFragment prevFragment = getBackgroundFragment(); + prevFragment.setParentLayout(this); + + holderView.setFragment(prevFragment); + holderView.setVisibility(GONE); + addView(holderView, getChildCount() - 2); + } + swipeProgress = 0f; + invalidateTranslation(); + + previewMenu = null; + if (blurredBackFragmentForPreview != null) { + blurredBackFragmentForPreview.recycle(); + blurredBackFragmentForPreview = null; + } + previewOpenCallback = null; + } + + @Override + public DrawerLayoutContainer getDrawerLayoutContainer() { + return drawerLayoutContainer; + } + + @Override + public void setDrawerLayoutContainer(DrawerLayoutContainer drawerLayoutContainer) { + this.drawerLayoutContainer = drawerLayoutContainer; + } + + @Override + public void setRemoveActionBarExtraHeight(boolean removeExtraHeight) { + this.removeActionBarExtraHeight = removeExtraHeight; + } + + private ActionBar getCurrentActionBar() { + return getLastFragment() != null ? getLastFragment().getActionBar() : null; + } + + @Override + public void setTitleOverlayText(String title, int titleId, Runnable action) { + titleOverlayTitle = title; + titleOverlayTitleId = titleId; + titleOverlayAction = action; + for (BaseFragment fragment : fragmentStack) { + if (fragment.getActionBar() != null) { + fragment.getActionBar().setTitleOverlayText(title, titleId, action); + } + } + } + + private void addStartDescriptions(ArrayList descriptions) { + if (descriptions == null) { + return; + } + themeAnimatorDescriptions.add(descriptions); + int[] startColors = new int[descriptions.size()]; + animateStartColors.add(startColors); + for (int a = 0, N = descriptions.size(); a < N; a++) { + ThemeDescription description = descriptions.get(a); + startColors[a] = description.getSetColor(); + ThemeDescription.ThemeDescriptionDelegate delegate = description.setDelegateDisabled(); + if (delegate != null && !themeAnimatorDelegate.contains(delegate)) { + themeAnimatorDelegate.add(delegate); + } + } + } + + private void addEndDescriptions(ArrayList descriptions) { + if (descriptions == null) { + return; + } + int[] endColors = new int[descriptions.size()]; + animateEndColors.add(endColors); + for (int a = 0, N = descriptions.size(); a < N; a++) { + endColors[a] = descriptions.get(a).getSetColor(); + } + } + + @Override + public void animateThemedValues(ThemeAnimationSettings settings, Runnable onDone) { + if (themeAnimator != null) { + themeAnimator.cancel(); + themeAnimator = null; + } + int fragmentCount = settings.onlyTopFragment ? 1 : fragmentStack.size(); + Runnable next = () -> { + boolean startAnimation = false; + for (int i = 0; i < fragmentCount; i++) { + BaseFragment fragment; + if (i == 0) { + fragment = getLastFragment(); + } else { + if (!isInPreviewMode() && !isPreviewOpenAnimationInProgress() || fragmentStack.size() <= 1) { + continue; + } + fragment = fragmentStack.get(fragmentStack.size() - 2); + } + if (fragment != null) { + startAnimation = true; + if (settings.resourcesProvider != null) { + if (messageDrawableOutStart == null) { + messageDrawableOutStart = new Theme.MessageDrawable(Theme.MessageDrawable.TYPE_TEXT, true, false, startColorsProvider); + messageDrawableOutStart.isCrossfadeBackground = true; + messageDrawableOutMediaStart = new Theme.MessageDrawable(Theme.MessageDrawable.TYPE_MEDIA, true, false, startColorsProvider); + messageDrawableOutMediaStart.isCrossfadeBackground = true; + } + startColorsProvider.saveColors(settings.resourcesProvider); + } + ArrayList descriptions = fragment.getThemeDescriptions(); + addStartDescriptions(descriptions); + if (fragment.getVisibleDialog() instanceof BottomSheet) { + BottomSheet sheet = (BottomSheet) fragment.getVisibleDialog(); + addStartDescriptions(sheet.getThemeDescriptions()); + } else if (fragment.getVisibleDialog() instanceof AlertDialog) { + AlertDialog dialog = (AlertDialog) fragment.getVisibleDialog(); + addStartDescriptions(dialog.getThemeDescriptions()); + } + if (i == 0) { + if (settings.afterStartDescriptionsAddedRunnable != null) { + settings.afterStartDescriptionsAddedRunnable.run(); + } + } + addEndDescriptions(descriptions); + if (fragment.getVisibleDialog() instanceof BottomSheet) { + addEndDescriptions(((BottomSheet) fragment.getVisibleDialog()).getThemeDescriptions()); + } else if (fragment.getVisibleDialog() instanceof AlertDialog) { + addEndDescriptions(((AlertDialog) fragment.getVisibleDialog()).getThemeDescriptions()); + } + } + } + if (startAnimation) { + if (!settings.onlyTopFragment) { + int count = fragmentStack.size() - (isInPreviewMode() || isPreviewOpenAnimationInProgress() ? 2 : 1); + boolean needRebuild = false; + for (int i = 0; i < count; i++) { + BaseFragment fragment = fragmentStack.get(i); + fragment.clearViews(); + fragment.setParentLayout(this); + + if (i == fragmentStack.size() - 1) { + if (getForegroundView() != null) { + getForegroundView().setFragment(fragment); + } else { + needRebuild = true; + } + } else if (i == fragmentStack.size() - 2) { + if (getBackgroundView() != null) { + getBackgroundView().setFragment(fragment); + } else { + needRebuild = true; + } + } + } + if (needRebuild) { + rebuildFragments(REBUILD_FLAG_REBUILD_LAST); + } + } + if (settings.instant) { + setThemeAnimationValue(1.0f); + themeAnimatorDescriptions.clear(); + animateStartColors.clear(); + animateEndColors.clear(); + themeAnimatorDelegate.clear(); + presentingFragmentDescriptions = null; + if (settings.afterAnimationRunnable != null) { + settings.afterAnimationRunnable.run(); + } + if (onDone != null) { + onDone.run(); + } + return; + } + Theme.setAnimatingColor(true); + if (settings.beforeAnimationRunnable != null) { + settings.beforeAnimationRunnable.run(); + } + animationProgressListener = settings.animationProgress; + if (animationProgressListener != null) { + animationProgressListener.setProgress(0); + } + fromBackgroundColor = getBackground() instanceof ColorDrawable ? ((ColorDrawable) getBackground()).getColor() : 0; + themeAnimator = ValueAnimator.ofFloat(0, 1).setDuration(settings.duration); + themeAnimator.addUpdateListener(animation -> setThemeAnimationValue((float) animation.getAnimatedValue())); + themeAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(themeAnimator)) { + themeAnimatorDescriptions.clear(); + animateStartColors.clear(); + animateEndColors.clear(); + themeAnimatorDelegate.clear(); + Theme.setAnimatingColor(false); + presentingFragmentDescriptions = null; + themeAnimator = null; + if (settings.afterAnimationRunnable != null) { + settings.afterAnimationRunnable.run(); + } + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (animation.equals(themeAnimator)) { + themeAnimatorDescriptions.clear(); + animateStartColors.clear(); + animateEndColors.clear(); + themeAnimatorDelegate.clear(); + Theme.setAnimatingColor(false); + presentingFragmentDescriptions = null; + themeAnimator = null; + if (settings.afterAnimationRunnable != null) { + settings.afterAnimationRunnable.run(); + } + } + } + }); + themeAnimator.start(); + } + if (onDone != null) { + onDone.run(); + } + }; + if (fragmentCount >= 1 && settings.applyTheme) { + if (settings.accentId != -1 && settings.theme != null) { + settings.theme.setCurrentAccentId(settings.accentId); + Theme.saveThemeAccents(settings.theme, true, false, true, false); + } + if (onDone == null) { + Theme.applyTheme(settings.theme, settings.nightTheme); + next.run(); + } else { + Theme.applyThemeInBackground(settings.theme, settings.nightTheme, () -> AndroidUtilities.runOnUIThread(next)); + } + } else { + next.run(); + } + } + + private void setThemeAnimationValue(float value) { + themeAnimationValue = value; + for (int j = 0, N = themeAnimatorDescriptions.size(); j < N; j++) { + ArrayList descriptions = themeAnimatorDescriptions.get(j); + int[] startColors = animateStartColors.get(j); + int[] endColors = animateEndColors.get(j); + int rE, gE, bE, aE, rS, gS, bS, aS, a, r, g, b; + for (int i = 0, N2 = descriptions.size(); i < N2; i++) { + rE = Color.red(endColors[i]); + gE = Color.green(endColors[i]); + bE = Color.blue(endColors[i]); + aE = Color.alpha(endColors[i]); + + rS = Color.red(startColors[i]); + gS = Color.green(startColors[i]); + bS = Color.blue(startColors[i]); + aS = Color.alpha(startColors[i]); + + a = Math.min(255, (int) (aS + (aE - aS) * value)); + r = Math.min(255, (int) (rS + (rE - rS) * value)); + g = Math.min(255, (int) (gS + (gE - gS) * value)); + b = Math.min(255, (int) (bS + (bE - bS) * value)); + int color = Color.argb(a, r, g, b); + ThemeDescription description = descriptions.get(i); + description.setAnimatedColor(color); + description.setColor(color, false, false); + } + } + for (int j = 0, N = themeAnimatorDelegate.size(); j < N; j++) { + ThemeDescription.ThemeDescriptionDelegate delegate = themeAnimatorDelegate.get(j); + if (delegate != null) { + delegate.didSetColor(); + delegate.onAnimationProgress(value); + } + } + if (presentingFragmentDescriptions != null) { + for (int i = 0, N = presentingFragmentDescriptions.size(); i < N; i++) { + ThemeDescription description = presentingFragmentDescriptions.get(i); + String key = description.getCurrentKey(); + description.setColor(Theme.getColor(key), false, false); + } + } + if (animationProgressListener != null) { + animationProgressListener.setProgress(value); + } + if (delegate != null) { + delegate.onThemeProgress(value); + } + } + + @Override + public float getThemeAnimationValue() { + return themeAnimationValue; + } + + @Override + public void setFragmentStackChangedListener(Runnable onFragmentStackChanged) { + this.onFragmentStackChangedListener = onFragmentStackChanged; + } + + @Override + public boolean isTransitionAnimationInProgress() { + return currentSpringAnimation != null || customAnimation != null; + } + + @Override + public boolean isInPassivePreviewMode() { + return (isInPreviewMode() && previewMenu == null) || isTransitionAnimationInProgress(); + } + + @Override + public void setInBubbleMode(boolean bubbleMode) { + this.isInBubbleMode = bubbleMode; + } + + @Override + public boolean isInBubbleMode() { + return isInBubbleMode; + } + + @Override + public boolean isInPreviewMode() { + return getLastFragment() != null && getLastFragment().isInPreviewMode() || blurredBackFragmentForPreview != null; + } + + @Override + public boolean isPreviewOpenAnimationInProgress() { + return isInPreviewMode() && isTransitionAnimationInProgress(); + } + + @Override + public void movePreviewFragment(float dy) { + if (!isInPreviewMode() || previewMenu != null || isTransitionAnimationInProgress() || getForegroundView() == null) { + return; + } + float currentTranslation = getForegroundView().getTranslationY(); + float nextTranslation = -dy; + if (nextTranslation > 0) { + nextTranslation = 0; + } else if (nextTranslation < -AndroidUtilities.dp(60)) { + nextTranslation = 0; + expandPreviewFragment(); + } + if (currentTranslation != nextTranslation) { + getForegroundView().setTranslationY(nextTranslation); + invalidate(); + } + } + + @Override + public void expandPreviewFragment() { + if (!isInPreviewMode() || isTransitionAnimationInProgress() || fragmentStack.isEmpty()) { + return; + } + + try { + performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); + } catch (Exception ignored) {} + + BaseFragment fragment = getLastFragment(); + View bgView = getBackgroundView(); + View fgView = getForegroundView(); + View fragmentView = fragment.getFragmentView(); + previewFragmentRect.set(fragmentView.getLeft(), fragmentView.getTop(), fragmentView.getRight(), fragmentView.getBottom()); + previewFragmentSnapshot = AndroidUtilities.snapshotView(fgView); + + resetViewProperties(fgView); + resetViewProperties(fragment.getFragmentView()); + fragment.setInPreviewMode(false); + swipeProgress = 0f; + invalidateTranslation(); + + float fromMenuY; + if (previewMenu != null) { + fromMenuY = previewMenu.getTranslationY(); + } else { + fromMenuY = 0; + } + + FloatValueHolder valueHolder = new FloatValueHolder(0); + currentSpringAnimation = new SpringAnimation(valueHolder) + .setSpring(new SpringForce(SPRING_MULTIPLIER) + .setStiffness(SPRING_STIFFNESS_PREVIEW_EXPAND) + .setDampingRatio(0.6f)); + currentSpringAnimation.addUpdateListener((animation, value, velocity) -> { + previewExpandProgress = value / SPRING_MULTIPLIER; + bgView.invalidate(); + + fgView.setPivotX(previewFragmentRect.centerX()); + fgView.setPivotY(previewFragmentRect.centerY()); + fgView.setScaleX(AndroidUtilities.lerp(previewFragmentRect.width() / (float) fgView.getWidth(), 1f, previewExpandProgress)); + fgView.setScaleY(AndroidUtilities.lerp(previewFragmentRect.height() / (float) fgView.getHeight(), 1f, previewExpandProgress)); + fgView.invalidate(); + + if (previewMenu != null) { + previewMenu.setTranslationY(AndroidUtilities.lerp(fromMenuY, getHeight(), previewExpandProgress)); + } + + invalidate(); + }); + currentSpringAnimation.addEndListener((animation, canceled, value, velocity) -> { + if (animation == currentSpringAnimation) { + currentSpringAnimation = null; + fragment.onPreviewOpenAnimationEnd(); + + previewFragmentSnapshot.recycle(); + previewFragmentSnapshot = null; + + if (blurredBackFragmentForPreview != null) { + blurredBackFragmentForPreview.recycle(); + blurredBackFragmentForPreview = null; + } + + if (previewMenu != null && previewMenu.getParent() != null) { + ((ViewGroup) previewMenu.getParent()).removeView(previewMenu); + } + previewMenu = null; + previewOpenCallback = null; + previewExpandProgress = 0; + + if (getBackgroundView() != null) { + getBackgroundView().setVisibility(GONE); + } + } + }); + currentSpringAnimation.start(); + } + + @Override + public void finishPreviewFragment() { + if (isInPreviewMode()) { + Runnable callback = () -> { + if (delayedPresentAnimation != null) { + AndroidUtilities.cancelRunOnUIThread(delayedPresentAnimation); + delayedPresentAnimation = null; + } + + closeLastFragment(); + }; + if (!isTransitionAnimationInProgress()) { + callback.run(); + } else { + previewOpenCallback = callback; + } + } + } + + @Override + public void setFragmentPanTranslationOffset(int offset) { + FragmentHolderView holderView = getForegroundView(); + if (holderView != null) { + holderView.setFragmentPanTranslationOffset(offset); + } + } + + @Override + public ViewGroup getOverlayContainerView() { + return overlayLayout; + } + + @Override + public void setHighlightActionButtons(boolean highlightActionButtons) { + this.highlightActionButtons = highlightActionButtons; + } + + @Override + public float getCurrentPreviewFragmentAlpha() { + return isInPreviewMode() ? getForegroundView().getAlpha() : 0f; + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + int index = indexOfChild(child); + if (drawerLayoutContainer != null && drawerLayoutContainer.isDrawCurrentPreviewFragmentAbove() && isInPreviewMode() && index == 1) { + drawerLayoutContainer.invalidate(); + return false; + } + + boolean clipBackground = getChildCount() >= 3 && index == 0 && customAnimation == null && !isInPreviewMode(); + if (clipBackground) { + canvas.save(); + AndroidUtilities.rectTmp.set(getPaddingLeft(), getPaddingTop(), getPaddingLeft() + (getWidth() - getPaddingLeft() - getPaddingRight()) * swipeProgress, getHeight() - getPaddingBottom()); + canvas.clipRect(AndroidUtilities.rectTmp); + } + if (index == 1 && isInPreviewMode()) { + drawPreviewDrawables(canvas, (ViewGroup) child); + } + boolean draw = super.drawChild(canvas, child, drawingTime); + if (index == 0 && isInPreviewMode() && blurredBackFragmentForPreview != null) { + canvas.save(); + + if (previewFragmentSnapshot != null) { + blurPaint.setAlpha((int) (0xFF * (1f - Math.min(previewExpandProgress, 1f)))); + } else { + blurPaint.setAlpha((int) (0xFF * (1f - Math.max(swipeProgress, 0f)))); + } + + canvas.scale(child.getWidth() / (float)blurredBackFragmentForPreview.getWidth(), child.getHeight() / (float)blurredBackFragmentForPreview.getHeight()); + canvas.drawBitmap(blurredBackFragmentForPreview, 0, 0, blurPaint); + canvas.restore(); + } + if (clipBackground) { + canvas.restore(); + } + return draw; + } + + @Override + public void drawCurrentPreviewFragment(Canvas canvas, Drawable foregroundDrawable) { + if (isInPreviewMode()) { + FragmentHolderView v = getForegroundView(); + drawPreviewDrawables(canvas, v); + if (v.getAlpha() < 1f) { + canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(), (int) (v.getAlpha() * 255), Canvas.ALL_SAVE_FLAG); + } else { + canvas.save(); + } + canvas.concat(v.getMatrix()); + MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams(); + canvas.translate(params.leftMargin, params.topMargin); + path.rewind(); + AndroidUtilities.rectTmp.set(0, previewExpandProgress != 0 ? 0 : AndroidUtilities.statusBarHeight, v.getWidth(), v.getHeight()); + path.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(8), AndroidUtilities.dp(8), Path.Direction.CW); + canvas.clipPath(path); + v.draw(canvas); + if (foregroundDrawable != null) { + View child = v.getChildAt(0); + if (child != null) { + MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + Rect rect = new Rect(); + child.getLocalVisibleRect(rect); + rect.offset(lp.leftMargin, lp.topMargin); + rect.top += Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight - 1 : 0; + foregroundDrawable.setAlpha((int) (v.getAlpha() * 255)); + foregroundDrawable.setBounds(rect); + foregroundDrawable.draw(canvas); + } + } + canvas.restore(); + } + } + + private void drawPreviewDrawables(Canvas canvas, ViewGroup containerView) { + View view = containerView.getChildAt(0); + if (view != null) { + MarginLayoutParams params = (MarginLayoutParams) containerView.getLayoutParams(); + + float alpha = 1f - Math.max(swipeProgress, 0); + if (previewFragmentSnapshot != null) { + alpha = 1f - Math.min(previewExpandProgress, 1f); + } + canvas.drawColor(Color.argb((int)(0x2e * alpha), 0, 0, 0)); + if (previewMenu == null) { + int width = AndroidUtilities.dp(32), height = width / 2; + int x = (getMeasuredWidth() - width) / 2; + int y = (int) (params.topMargin + containerView.getTranslationY() - AndroidUtilities.dp(12 + (Build.VERSION.SDK_INT < 21 ? 20 : 0))); + Theme.moveUpDrawable.setAlpha((int) (alpha * 0xFF)); + Theme.moveUpDrawable.setBounds(x, y, x + width, y + height); + Theme.moveUpDrawable.draw(canvas); + } + } + } + + @Override + public void drawHeaderShadow(Canvas canvas, int alpha, int y) { + if (headerShadowDrawable != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (headerShadowDrawable.getAlpha() != alpha) { + headerShadowDrawable.setAlpha(alpha); + } + } else { + headerShadowDrawable.setAlpha(alpha); + } + headerShadowDrawable.setBounds(0, y, getMeasuredWidth(), y + headerShadowDrawable.getIntrinsicHeight()); + headerShadowDrawable.draw(canvas); + } + } + + @Override + public boolean isSwipeInProgress() { + return isSwipeInProgress; + } + + @Override + public void onPause() { + BaseFragment fragment = getLastFragment(); + if (fragment != null) { + fragment.setPaused(true); + } + } + + @Override + public void onResume() { + BaseFragment fragment = getLastFragment(); + if (fragment != null) { + fragment.setPaused(false); + } + } + + @Override + public void onUserLeaveHint() { + BaseFragment fragment = getLastFragment(); + if (fragment != null) { + fragment.onUserLeaveHint(); + } + } + + @Override + public void onLowMemory() { + for (BaseFragment fragment : fragmentStack) { + fragment.onLowMemory(); + } + } + + @Override + public void onBackPressed() { + if (isSwipeInProgress() || checkTransitionAnimation() || fragmentStack.isEmpty()) { + return; + } + if (GroupCallPip.onBackPressed()) { + return; + } + if (getCurrentActionBar() != null && !getCurrentActionBar().isActionModeShowed() && getCurrentActionBar().isSearchFieldVisible()) { + getCurrentActionBar().closeSearchField(); + return; + } + BaseFragment lastFragment = getLastFragment(); + if (lastFragment.onBackPressed()) { + closeLastFragment(true); + } + } + + @Override + public boolean extendActionMode(Menu menu) { + BaseFragment lastFragment = getLastFragment(); + return lastFragment != null && lastFragment.extendActionMode(menu); + } + + @Override + public void onActionModeStarted(Object mode) { + if (getCurrentActionBar() != null) { + getCurrentActionBar().setVisibility(GONE); + } + isInActionMode = true; + } + + @Override + public void onActionModeFinished(Object mode) { + if (getCurrentActionBar() != null) { + getCurrentActionBar().setVisibility(VISIBLE); + } + isInActionMode = false; + } + + @Override + public void startActivityForResult(Intent intent, int requestCode) { + Activity parentActivity = getParentActivity(); + if (parentActivity == null) { + return; + } + // Maybe reset current animation? + + if (intent != null) { + parentActivity.startActivityForResult(intent, requestCode); + } + } + + @Override + public Theme.MessageDrawable getMessageDrawableOutStart() { + return messageDrawableOutStart; + } + + @Override + public Theme.MessageDrawable getMessageDrawableOutMediaStart() { + return messageDrawableOutMediaStart; + } + + @Override + public List getPulledDialogs() { + return pulledDialogs; + } + + @Override + public void setPulledDialogs(List pulledDialogs) { + this.pulledDialogs = pulledDialogs; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_MENU && !checkTransitionAnimation() && !isSwipeInProgress() && getCurrentActionBar() != null) { + getCurrentActionBar().onMenuButtonPressed(); + } + return super.onKeyUp(keyCode, event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + boolean isPortrait = height > width; + if (wasPortrait != isPortrait && isInPreviewMode()) { + finishPreviewFragment(); + } + wasPortrait = isPortrait; + } + + private final class FragmentHolderView extends FrameLayout { + private BaseFragment fragment; + private int fragmentPanTranslationOffset; + private Paint backgroundPaint = new Paint(); + private int backgroundColor; + + public FragmentHolderView(@NonNull Context context) { + super(context); + setWillNotDraw(false); + } + + public void invalidateBackgroundColor() { + if (fragment == null || fragment.hasOwnBackground()) { + setBackground(null); + } else { + setBackgroundColor(fragment.getThemedColor(Theme.key_windowBackgroundWhite)); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + int actionBarHeight = 0; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child instanceof ActionBar) { + child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + actionBarHeight = child.getMeasuredHeight(); + } + } + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (!(child instanceof ActionBar)) { + if (child.getFitsSystemWindows()) { + measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); + } else { + measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, actionBarHeight); + } + } + } + setMeasuredDimension(width, height); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int actionBarHeight = 0; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child instanceof ActionBar) { + child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight()); + actionBarHeight = child.getMeasuredHeight(); + } + } + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (!(child instanceof ActionBar)) { + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) child.getLayoutParams(); + if (child.getFitsSystemWindows()) { + child.layout(layoutParams.leftMargin, layoutParams.topMargin, layoutParams.leftMargin + child.getMeasuredWidth(), layoutParams.topMargin + child.getMeasuredHeight()); + } else { + child.layout(layoutParams.leftMargin, layoutParams.topMargin + actionBarHeight, layoutParams.leftMargin + child.getMeasuredWidth(), layoutParams.topMargin + actionBarHeight + child.getMeasuredHeight()); + } + } + } + } + + public void setFragmentPanTranslationOffset(int fragmentPanTranslationOffset) { + this.fragmentPanTranslationOffset = fragmentPanTranslationOffset; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (fragmentPanTranslationOffset != 0) { + int color = Theme.getColor(Theme.key_windowBackgroundWhite); + if (backgroundColor != color) { + backgroundPaint.setColor(backgroundColor = Theme.getColor(Theme.key_windowBackgroundWhite)); + } + canvas.drawRect(0, getMeasuredHeight() - fragmentPanTranslationOffset - 3, getMeasuredWidth(), getMeasuredHeight(), backgroundPaint); + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + fragment.drawOverlay(canvas, this); + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + if (child instanceof ActionBar) { + return super.drawChild(canvas, child, drawingTime); + } else { + int actionBarHeight = 0; + int actionBarY = 0; + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View view = getChildAt(i); + if (view == child) { + continue; + } + if (view instanceof ActionBar && view.getVisibility() == VISIBLE) { + if (((ActionBar) view).getCastShadows()) { + actionBarHeight = (int) (view.getMeasuredHeight() * view.getScaleY()); + actionBarY = (int) view.getY(); + } + break; + } + } + + boolean clipRoundForeground = indexOfChild(child) == 0 && fragment.isInPreviewMode(); + if (clipRoundForeground) { + canvas.save(); + path.rewind(); + AndroidUtilities.rectTmp.set(child.getLeft(), child.getTop() + AndroidUtilities.statusBarHeight, child.getRight(), child.getBottom()); + path.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(8), AndroidUtilities.dp(8), Path.Direction.CW); + canvas.clipPath(path); + } + boolean result = super.drawChild(canvas, child, drawingTime); + if (clipRoundForeground) { + canvas.restore(); + } + if (actionBarHeight != 0 && headerShadowDrawable != null) { + headerShadowDrawable.setBounds(0, actionBarY + actionBarHeight, getMeasuredWidth(), actionBarY + actionBarHeight + headerShadowDrawable.getIntrinsicHeight()); + headerShadowDrawable.draw(canvas); + } + return result; + } + } + + public void setFragment(BaseFragment fragment) { + this.fragment = fragment; + fragmentPanTranslationOffset = 0; + invalidate(); + + removeAllViews(); + + if (fragment == null) { + invalidateBackgroundColor(); + return; + } + + View v = fragment.getFragmentView(); + if (v == null) { + v = fragment.createView(getContext()); + fragment.setFragmentView(v); + } + if (v != null && v.getParent() instanceof ViewGroup) { + ((ViewGroup) v.getParent()).removeView(v); + } + addView(v); + + if (removeActionBarExtraHeight) { + fragment.getActionBar().setOccupyStatusBar(false); + } + if (fragment.getActionBar() != null && fragment.getActionBar().shouldAddToContainer()) { + ViewGroup parent = (ViewGroup) fragment.getActionBar().getParent(); + if (parent != null) { + parent.removeView(fragment.getActionBar()); + } + addView(fragment.getActionBar()); + } + + invalidateBackgroundColor(); + } + } + + private boolean isIgnoredView(ViewGroup root, MotionEvent e, Rect rect) { + if (root == null) return false; + for (int i = 0; i < root.getChildCount(); i++) { + View ch = root.getChildAt(i); + if (isIgnoredView0(ch, e, rect)) { + return true; + } + + if (ch instanceof ViewGroup) { + if (isIgnoredView((ViewGroup) ch, e, rect)) { + return true; + } + } + } + return isIgnoredView0(root, e, rect); + } + + private boolean isIgnoredView0(View v, MotionEvent e, Rect rect) { + v.getGlobalVisibleRect(rect); + if (v.getVisibility() != View.VISIBLE || !rect.contains((int)e.getX(), (int)e.getY())) { + return false; + } + + if (v instanceof ViewPager) { + ViewPager vp = (ViewPager) v; + return vp.getCurrentItem() != 0; + } + + return v.canScrollHorizontally(-1) || v instanceof SeekBarView; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java index 59b8c95fa..5295e6387 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java @@ -18,6 +18,7 @@ import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityManager; +import android.app.Dialog; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -29,6 +30,7 @@ import android.graphics.Canvas; import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.Point; import android.graphics.Shader; import android.location.LocationManager; @@ -44,6 +46,7 @@ import android.provider.ContactsContract; import android.provider.Settings; import android.text.TextUtils; import android.util.Base64; +import android.util.SparseArray; import android.util.SparseIntArray; import android.util.TypedValue; import android.view.ActionMode; @@ -65,6 +68,7 @@ import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.arch.core.util.Function; import androidx.core.app.ActivityCompat; import androidx.core.content.pm.ShortcutInfoCompat; @@ -83,6 +87,7 @@ import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AccountInstance; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.BotWebViewVibrationEffect; import org.telegram.messenger.BuildVars; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; @@ -105,6 +110,7 @@ import org.telegram.messenger.PushListenerController; import org.telegram.messenger.R; import org.telegram.messenger.SendMessagesHelper; import org.telegram.messenger.SharedConfig; +import org.telegram.messenger.TopicsController; import org.telegram.messenger.UserConfig; import org.telegram.messenger.UserObject; import org.telegram.messenger.Utilities; @@ -115,10 +121,10 @@ import org.telegram.messenger.voip.VoIPService; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; -import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.DrawerLayoutContainer; +import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.SimpleTextView; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Adapters.DrawerLayoutAdapter; @@ -140,6 +146,8 @@ import org.telegram.ui.Components.Easings; import org.telegram.ui.Components.EmbedBottomSheet; import org.telegram.ui.Components.EmojiPacksAlert; import org.telegram.ui.Components.FireworksOverlay; +import org.telegram.ui.Components.FloatingDebug.FloatingDebugController; +import org.telegram.ui.Components.Forum.ForumUtilities; import org.telegram.ui.Components.GroupCallPip; import org.telegram.ui.Components.JoinGroupAlert; import org.telegram.ui.Components.LayoutHelper; @@ -161,7 +169,6 @@ import org.telegram.ui.Components.TermsOfServiceView; import org.telegram.ui.Components.ThemeEditorView; import org.telegram.ui.Components.UndoView; import org.telegram.ui.Components.UpdateAppAlertDialog; -import org.telegram.ui.Components.VerticalPositionAutoAnimator; import org.telegram.ui.Components.voip.VoIPHelper; import org.webrtc.voiceengine.WebRtcAudioTrack; @@ -176,11 +183,12 @@ import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class LaunchActivity extends BasePermissionsActivity implements ActionBarLayout.ActionBarLayoutDelegate, NotificationCenter.NotificationCenterDelegate, DialogsActivity.DialogsActivityDelegate { +public class LaunchActivity extends BasePermissionsActivity implements INavigationLayout.INavigationLayoutDelegate, NotificationCenter.NotificationCenterDelegate, DialogsActivity.DialogsActivityDelegate { public final static Pattern PREFIX_T_ME_PATTERN = Pattern.compile("^(?:http(?:s|)://|)([A-z0-9-]+?)\\.t\\.me"); public static boolean isResumed; @@ -215,9 +223,9 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar private ImageView themeSwitchImageView; private View themeSwitchSunView; private RLottieDrawable themeSwitchSunDrawable; - private ActionBarLayout actionBarLayout; - private ActionBarLayout layersActionBarLayout; - private ActionBarLayout rightActionBarLayout; + private INavigationLayout actionBarLayout; + private INavigationLayout layersActionBarLayout; + private INavigationLayout rightActionBarLayout; private RelativeLayout launchLayout; private FrameLayout shadowTablet; private FrameLayout shadowTabletSide; @@ -336,19 +344,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar SharedConfig.lastPauseTime = (int) (SystemClock.elapsedRealtime() / 1000); } AndroidUtilities.fillStatusBarHeight(this); - actionBarLayout = new ActionBarLayout(this) { - @Override - public void setThemeAnimationValue(float value) { - super.setThemeAnimationValue(value); - if (ArticleViewer.hasInstance() && ArticleViewer.getInstance().isVisible()) { - ArticleViewer.getInstance().updateThemeColors(value); - } - drawerLayoutContainer.setBehindKeyboardColor(Theme.getColor(Theme.key_windowBackgroundWhite)); - if (PhotoViewer.hasInstance()) { - PhotoViewer.getInstance().updateColors(); - } - } - }; + actionBarLayout = INavigationLayout.newLayout(this); frameLayout = new FrameLayout(this) { @Override @@ -416,7 +412,23 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar frameLayout.addView(themeSwitchSunView, LayoutHelper.createFrame(48, 48)); themeSwitchSunView.setVisibility(View.GONE); } - frameLayout.addView(fireworksOverlay = new FireworksOverlay(this)); + frameLayout.addView(fireworksOverlay = new FireworksOverlay(this) { + { + setVisibility(GONE); + } + + @Override + public void start() { + setVisibility(VISIBLE); + super.start(); + } + + @Override + protected void onStop() { + super.onStop(); + setVisibility(GONE); + } + }); setupActionBarLayout(); sideMenuContainer = new FrameLayout(this); sideMenu = new RecyclerListView(this) { @@ -479,8 +491,8 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar presentFragment(new LoginActivity(availableAccount)); drawerLayoutContainer.closeDrawer(false); } else if (!UserConfig.hasPremiumOnAccounts()) { - if (actionBarLayout.fragmentsStack.size() > 0) { - BaseFragment fragment = actionBarLayout.fragmentsStack.get(0); + if (actionBarLayout.getFragmentStack().size() > 0) { + BaseFragment fragment = actionBarLayout.getFragmentStack().get(0); LimitReachedBottomSheet limitReachedBottomSheet = new LimitReachedBottomSheet(fragment, this, TYPE_ACCOUNTS, currentAccount); fragment.showDialog(limitReachedBottomSheet); limitReachedBottomSheet.onShowPremiumScreenRunnable = () -> drawerLayoutContainer.closeDrawer(false); @@ -650,19 +662,21 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } else { final BaseFragment fragment = new DialogsActivity(null) { @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { super.onTransitionAnimationEnd(isOpen, backward); if (!isOpen && backward) { // closed drawerLayoutContainer.setDrawCurrentPreviewFragmentAbove(false); + actionBarLayout.getView().invalidate(); } } @Override - protected void onPreviewOpenAnimationEnd() { + public void onPreviewOpenAnimationEnd() { super.onPreviewOpenAnimationEnd(); drawerLayoutContainer.setAllowOpenDrawer(false, false); drawerLayoutContainer.setDrawCurrentPreviewFragmentAbove(false); switchToAccount(accountNumber, true); + actionBarLayout.getView().invalidate(); } }; fragment.setCurrentAccount(accountNumber); @@ -675,7 +689,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar }); drawerLayoutContainer.setParentActionBarLayout(actionBarLayout); actionBarLayout.setDrawerLayoutContainer(drawerLayoutContainer); - actionBarLayout.init(mainFragmentsStack); + actionBarLayout.setFragmentStack(mainFragmentsStack); actionBarLayout.setFragmentStackChangedListener(() -> { checkSystemBarColors(true, false); }); @@ -702,7 +716,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.appUpdateAvailable); NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.requestPermissions); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.currentUserPremiumStatusChanged); - if (actionBarLayout.fragmentsStack.isEmpty()) { + if (actionBarLayout.getFragmentStack().isEmpty()) { if (!UserConfig.getInstance(currentAccount).isClientActivated()) { actionBarLayout.addFragmentToStack(getClientNotActivatedFragment()); drawerLayoutContainer.setAllowOpenDrawer(false, false); @@ -771,18 +785,18 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar FileLog.e(e); } } else { - BaseFragment fragment = actionBarLayout.fragmentsStack.get(0); + BaseFragment fragment = actionBarLayout.getFragmentStack().get(0); if (fragment instanceof DialogsActivity) { ((DialogsActivity) fragment).setSideMenu(sideMenu); } boolean allowOpen = true; if (AndroidUtilities.isTablet()) { - allowOpen = actionBarLayout.fragmentsStack.size() <= 1 && layersActionBarLayout.fragmentsStack.isEmpty(); - if (layersActionBarLayout.fragmentsStack.size() == 1 && (layersActionBarLayout.fragmentsStack.get(0) instanceof LoginActivity || layersActionBarLayout.fragmentsStack.get(0) instanceof IntroActivity)) { + allowOpen = actionBarLayout.getFragmentStack().size() <= 1 && layersActionBarLayout.getFragmentStack().isEmpty(); + if (layersActionBarLayout.getFragmentStack().size() == 1 && (layersActionBarLayout.getFragmentStack().get(0) instanceof LoginActivity || layersActionBarLayout.getFragmentStack().get(0) instanceof IntroActivity)) { allowOpen = false; } } - if (actionBarLayout.fragmentsStack.size() == 1 && (actionBarLayout.fragmentsStack.get(0) instanceof LoginActivity || actionBarLayout.fragmentsStack.get(0) instanceof IntroActivity)) { + if (actionBarLayout.getFragmentStack().size() == 1 && (actionBarLayout.getFragmentStack().get(0) instanceof LoginActivity || actionBarLayout.getFragmentStack().get(0) instanceof IntroActivity)) { allowOpen = false; } drawerLayoutContainer.setAllowOpenDrawer(allowOpen, false); @@ -843,8 +857,19 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } } + @Override + public void onThemeProgress(float progress) { + if (ArticleViewer.hasInstance() && ArticleViewer.getInstance().isVisible()) { + ArticleViewer.getInstance().updateThemeColors(progress); + } + drawerLayoutContainer.setBehindKeyboardColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + if (PhotoViewer.hasInstance()) { + PhotoViewer.getInstance().updateColors(); + } + } + private void setupActionBarLayout() { - int i = drawerLayoutContainer.indexOfChild(launchLayout) != -1 ? drawerLayoutContainer.indexOfChild(launchLayout) : drawerLayoutContainer.indexOfChild(actionBarLayout); + int i = drawerLayoutContainer.indexOfChild(launchLayout) != -1 ? drawerLayoutContainer.indexOfChild(launchLayout) : drawerLayoutContainer.indexOfChild(actionBarLayout.getView()); if (i != -1) { drawerLayoutContainer.removeViewAt(i); } @@ -852,7 +877,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); launchLayout = new RelativeLayout(this) { - + private Path path = new Path(); private boolean inLayout; @Override @@ -876,16 +901,16 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (leftWidth < AndroidUtilities.dp(320)) { leftWidth = AndroidUtilities.dp(320); } - actionBarLayout.measure(MeasureSpec.makeMeasureSpec(leftWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + actionBarLayout.getView().measure(MeasureSpec.makeMeasureSpec(leftWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); shadowTabletSide.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(1), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); - rightActionBarLayout.measure(MeasureSpec.makeMeasureSpec(width - leftWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + rightActionBarLayout.getView().measure(MeasureSpec.makeMeasureSpec(width - leftWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); } else { tabletFullSize = true; - actionBarLayout.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + actionBarLayout.getView().measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); } backgroundTablet.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); shadowTablet.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); - layersActionBarLayout.measure(MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(530), width), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(528), height), MeasureSpec.EXACTLY)); + layersActionBarLayout.getView().measure(MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(530), width - AndroidUtilities.dp(16)), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height - AndroidUtilities.statusBarHeight - AndroidUtilities.dp(16), MeasureSpec.EXACTLY)); inLayout = false; } @@ -901,14 +926,14 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar leftWidth = AndroidUtilities.dp(320); } shadowTabletSide.layout(leftWidth, 0, leftWidth + shadowTabletSide.getMeasuredWidth(), shadowTabletSide.getMeasuredHeight()); - actionBarLayout.layout(0, 0, actionBarLayout.getMeasuredWidth(), actionBarLayout.getMeasuredHeight()); - rightActionBarLayout.layout(leftWidth, 0, leftWidth + rightActionBarLayout.getMeasuredWidth(), rightActionBarLayout.getMeasuredHeight()); + actionBarLayout.getView().layout(0, 0, actionBarLayout.getView().getMeasuredWidth(), actionBarLayout.getView().getMeasuredHeight()); + rightActionBarLayout.getView().layout(leftWidth, 0, leftWidth + rightActionBarLayout.getView().getMeasuredWidth(), rightActionBarLayout.getView().getMeasuredHeight()); } else { - actionBarLayout.layout(0, 0, actionBarLayout.getMeasuredWidth(), actionBarLayout.getMeasuredHeight()); + actionBarLayout.getView().layout(0, 0, actionBarLayout.getView().getMeasuredWidth(), actionBarLayout.getView().getMeasuredHeight()); } - int x = (width - layersActionBarLayout.getMeasuredWidth()) / 2; - int y = (height - layersActionBarLayout.getMeasuredHeight()) / 2; - layersActionBarLayout.layout(x, y, x + layersActionBarLayout.getMeasuredWidth(), y + layersActionBarLayout.getMeasuredHeight()); + int x = (width - layersActionBarLayout.getView().getMeasuredWidth()) / 2; + int y = (height - layersActionBarLayout.getView().getMeasuredHeight() + AndroidUtilities.statusBarHeight) / 2; + layersActionBarLayout.getView().layout(x, y, x + layersActionBarLayout.getView().getMeasuredWidth(), y + layersActionBarLayout.getView().getMeasuredHeight()); backgroundTablet.layout(0, 0, backgroundTablet.getMeasuredWidth(), backgroundTablet.getMeasuredHeight()); shadowTablet.layout(0, 0, shadowTablet.getMeasuredWidth(), shadowTablet.getMeasuredHeight()); } @@ -929,16 +954,16 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar backgroundTablet.setBackgroundImage(Theme.getCachedWallpaper(), Theme.isWallpaperMotion()); launchLayout.addView(backgroundTablet, LayoutHelper.createRelative(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - ViewGroup parent = (ViewGroup) actionBarLayout.getParent(); + ViewGroup parent = (ViewGroup) actionBarLayout.getView().getParent(); if (parent != null) { - parent.removeView(actionBarLayout); + parent.removeView(actionBarLayout.getView()); } - launchLayout.addView(actionBarLayout); + launchLayout.addView(actionBarLayout.getView()); - rightActionBarLayout = new ActionBarLayout(this); - rightActionBarLayout.init(rightFragmentsStack); + rightActionBarLayout = INavigationLayout.newLayout(this); + rightActionBarLayout.setFragmentStack(rightFragmentsStack); rightActionBarLayout.setDelegate(this); - launchLayout.addView(rightActionBarLayout); + launchLayout.addView(rightActionBarLayout.getView()); shadowTabletSide = new FrameLayout(this); shadowTabletSide.setBackgroundColor(0x40295274); @@ -949,20 +974,20 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar shadowTablet.setBackgroundColor(0x7f000000); launchLayout.addView(shadowTablet); shadowTablet.setOnTouchListener((v, event) -> { - if (!actionBarLayout.fragmentsStack.isEmpty() && event.getAction() == MotionEvent.ACTION_UP) { + if (!actionBarLayout.getFragmentStack().isEmpty() && event.getAction() == MotionEvent.ACTION_UP) { float x = event.getX(); float y = event.getY(); int[] location = new int[2]; - layersActionBarLayout.getLocationOnScreen(location); + layersActionBarLayout.getView().getLocationOnScreen(location); int viewX = location[0]; int viewY = location[1]; - if (layersActionBarLayout.checkTransitionAnimation() || x > viewX && x < viewX + layersActionBarLayout.getWidth() && y > viewY && y < viewY + layersActionBarLayout.getHeight()) { + if (layersActionBarLayout.checkTransitionAnimation() || x > viewX && x < viewX + layersActionBarLayout.getView().getWidth() && y > viewY && y < viewY + layersActionBarLayout.getView().getHeight()) { return false; } else { - if (!layersActionBarLayout.fragmentsStack.isEmpty()) { - for (int a = 0; a < layersActionBarLayout.fragmentsStack.size() - 1; a++) { - layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.fragmentsStack.get(0)); + if (!layersActionBarLayout.getFragmentStack().isEmpty()) { + for (int a = 0; a < layersActionBarLayout.getFragmentStack().size() - 1; a++) { + layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.getFragmentStack().get(0)); a--; } layersActionBarLayout.closeLastFragment(true); @@ -977,30 +1002,32 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar }); - layersActionBarLayout = new ActionBarLayout(this); + layersActionBarLayout = INavigationLayout.newLayout(this); layersActionBarLayout.setRemoveActionBarExtraHeight(true); layersActionBarLayout.setBackgroundView(shadowTablet); layersActionBarLayout.setUseAlphaAnimations(true); - layersActionBarLayout.setBackgroundResource(R.drawable.boxshadow); - layersActionBarLayout.init(layerFragmentsStack); + layersActionBarLayout.setFragmentStack(layerFragmentsStack); layersActionBarLayout.setDelegate(this); layersActionBarLayout.setDrawerLayoutContainer(drawerLayoutContainer); - layersActionBarLayout.setVisibility(layerFragmentsStack.isEmpty() ? View.GONE : View.VISIBLE); - VerticalPositionAutoAnimator.attach(layersActionBarLayout); - launchLayout.addView(layersActionBarLayout); + + View layersView = layersActionBarLayout.getView(); + layersView.setBackgroundResource(R.drawable.popup_fixed_alert3); + layersView.setVisibility(layerFragmentsStack.isEmpty() ? View.GONE : View.VISIBLE); + launchLayout.addView(layersView); } else { - ViewGroup parent = (ViewGroup) actionBarLayout.getParent(); + ViewGroup parent = (ViewGroup) actionBarLayout.getView().getParent(); if (parent != null) { - parent.removeView(actionBarLayout); + parent.removeView(actionBarLayout.getView()); } - actionBarLayout.init(mainFragmentsStack); + actionBarLayout.setFragmentStack(mainFragmentsStack); if (i != -1) { - drawerLayoutContainer.addView(actionBarLayout, i, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + drawerLayoutContainer.addView(actionBarLayout.getView(), i, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } else { - drawerLayoutContainer.addView(actionBarLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + drawerLayoutContainer.addView(actionBarLayout.getView(), new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } } + FloatingDebugController.setActive(this, SharedConfig.isFloatingDebugActive, false); } public void addOnUserLeaveHintListener(Runnable callback) { @@ -1044,14 +1071,21 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar yoff = -(profileCell.getHeight() - AndroidUtilities.rectTmp2.centerY()) - AndroidUtilities.dp(16); xoff = AndroidUtilities.rectTmp2.centerX(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - getWindow() != null && - getWindow().getDecorView() != null && - getWindow().getDecorView().getRootWindowInsets() != null + getWindow() != null && + getWindow().getDecorView() != null && + getWindow().getDecorView().getRootWindowInsets() != null ) { xoff -= getWindow().getDecorView().getRootWindowInsets().getStableInsetLeft(); } } SelectAnimatedEmojiDialog popupLayout = new SelectAnimatedEmojiDialog(fragment, this, true, xoff, SelectAnimatedEmojiDialog.TYPE_EMOJI_STATUS, null) { + @Override + public void onSettings() { + if (drawerLayoutContainer != null) { + drawerLayoutContainer.closeDrawer(); + } + } + @Override protected void onEmojiSelected(View emojiView, Long documentId, TLRPC.Document document, Integer until) { TLRPC.TL_account_updateEmojiStatus req = new TLRPC.TL_account_updateEmojiStatus(); @@ -1190,6 +1224,10 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } } + public FrameLayout getMainContainerFrameLayout() { + return frameLayout; + } + public void switchToAccount(int account, boolean removeAll) { switchToAccount(account, removeAll, obj -> new DialogsActivity(null)); } @@ -1209,12 +1247,12 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar rightActionBarLayout.removeAllFragments(); if (!tabletFullSize) { shadowTabletSide.setVisibility(View.VISIBLE); - if (rightActionBarLayout.fragmentsStack.isEmpty()) { + if (rightActionBarLayout.getFragmentStack().isEmpty()) { backgroundTablet.setVisibility(View.VISIBLE); } - rightActionBarLayout.setVisibility(View.GONE); + rightActionBarLayout.getView().setVisibility(View.GONE); } - layersActionBarLayout.setVisibility(View.GONE); + layersActionBarLayout.getView().setVisibility(View.GONE); } if (removeAll) { actionBarLayout.removeAllFragments(); @@ -1225,10 +1263,10 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar dialogsActivity.setSideMenu(sideMenu); actionBarLayout.addFragmentToStack(dialogsActivity, 0); drawerLayoutContainer.setAllowOpenDrawer(true, false); - actionBarLayout.showLastFragment(); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); if (AndroidUtilities.isTablet()) { - layersActionBarLayout.showLastFragment(); - rightActionBarLayout.showLastFragment(); + layersActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); } if (!ApplicationLoader.mainInterfacePaused) { ConnectionsManager.getInstance(currentAccount).setAppPaused(false, false); @@ -1305,6 +1343,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.stickersImportComplete); NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.newSuggestionsAvailable); NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.currentUserPremiumStatusChanged); + NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.chatSwithcedToForum); } currentAccount = UserConfig.selectedAccount; NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.appDidLogout); @@ -1324,54 +1363,55 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.newSuggestionsAvailable); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.currentUserShowLimitReachedDialog); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.currentUserPremiumStatusChanged); + NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatSwithcedToForum); } private void checkLayout() { - if (!AndroidUtilities.isTablet() || rightActionBarLayout == null) { + if (!AndroidUtilities.isTablet() || rightActionBarLayout == null || AndroidUtilities.getWasTablet() != null && AndroidUtilities.getWasTablet() != AndroidUtilities.isTabletForce()) { return; } if (!AndroidUtilities.isInMultiwindow && (!AndroidUtilities.isSmallTablet() || getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)) { tabletFullSize = false; - if (actionBarLayout.fragmentsStack.size() >= 2) { - for (int a = 1; a < actionBarLayout.fragmentsStack.size(); a++) { - BaseFragment chatFragment = actionBarLayout.fragmentsStack.get(a); + if (actionBarLayout.getFragmentStack().size() >= 2) { + for (int a = 1; a < actionBarLayout.getFragmentStack().size(); a++) { + BaseFragment chatFragment = actionBarLayout.getFragmentStack().get(a); if (chatFragment instanceof ChatActivity) { ((ChatActivity) chatFragment).setIgnoreAttachOnPause(true); } chatFragment.onPause(); - actionBarLayout.fragmentsStack.remove(a); - rightActionBarLayout.fragmentsStack.add(chatFragment); + actionBarLayout.removeFragmentFromStack(a); + rightActionBarLayout.addFragmentToStack(chatFragment); a--; } if (passcodeView == null || passcodeView.getVisibility() != View.VISIBLE) { - actionBarLayout.showLastFragment(); - rightActionBarLayout.showLastFragment(); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); } } - rightActionBarLayout.setVisibility(rightActionBarLayout.fragmentsStack.isEmpty() ? View.GONE : View.VISIBLE); - backgroundTablet.setVisibility(rightActionBarLayout.fragmentsStack.isEmpty() ? View.VISIBLE : View.GONE); - shadowTabletSide.setVisibility(!actionBarLayout.fragmentsStack.isEmpty() ? View.VISIBLE : View.GONE); + rightActionBarLayout.getView().setVisibility(rightActionBarLayout.getFragmentStack().isEmpty() ? View.GONE : View.VISIBLE); + backgroundTablet.setVisibility(rightActionBarLayout.getFragmentStack().isEmpty() ? View.VISIBLE : View.GONE); + shadowTabletSide.setVisibility(!actionBarLayout.getFragmentStack().isEmpty() ? View.VISIBLE : View.GONE); } else { tabletFullSize = true; - if (!rightActionBarLayout.fragmentsStack.isEmpty()) { - for (int a = 0; a < rightActionBarLayout.fragmentsStack.size(); a++) { - BaseFragment chatFragment = rightActionBarLayout.fragmentsStack.get(a); + if (!rightActionBarLayout.getFragmentStack().isEmpty()) { + for (int a = 0; a < rightActionBarLayout.getFragmentStack().size(); a++) { + BaseFragment chatFragment = rightActionBarLayout.getFragmentStack().get(a); if (chatFragment instanceof ChatActivity) { ((ChatActivity) chatFragment).setIgnoreAttachOnPause(true); } chatFragment.onPause(); - rightActionBarLayout.fragmentsStack.remove(a); - actionBarLayout.fragmentsStack.add(chatFragment); + rightActionBarLayout.removeFragmentFromStack(a); + actionBarLayout.addFragmentToStack(chatFragment); a--; } if (passcodeView == null || passcodeView.getVisibility() != View.VISIBLE) { - actionBarLayout.showLastFragment(); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); } } shadowTabletSide.setVisibility(View.GONE); - rightActionBarLayout.setVisibility(View.GONE); - backgroundTablet.setVisibility(!actionBarLayout.fragmentsStack.isEmpty() ? View.GONE : View.VISIBLE); + rightActionBarLayout.getView().setVisibility(View.GONE); + backgroundTablet.setVisibility(!actionBarLayout.getFragmentStack().isEmpty() ? View.GONE : View.VISIBLE); } } @@ -1456,12 +1496,14 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar MediaController.getInstance().cleanupPlayer(true, true); } passcodeView.onShow(fingerprint, animated, x, y, () -> { - actionBarLayout.setVisibility(View.INVISIBLE); + actionBarLayout.getView().setVisibility(View.INVISIBLE); if (AndroidUtilities.isTablet()) { - if (layersActionBarLayout.getVisibility() == View.VISIBLE) { - layersActionBarLayout.setVisibility(View.INVISIBLE); + if (layersActionBarLayout != null && layersActionBarLayout.getView() != null && layersActionBarLayout.getView().getVisibility() == View.VISIBLE) { + layersActionBarLayout.getView().setVisibility(View.INVISIBLE); + } + if (rightActionBarLayout != null && rightActionBarLayout.getView() != null) { + rightActionBarLayout.getView().setVisibility(View.INVISIBLE); } - rightActionBarLayout.setVisibility(View.INVISIBLE); } if (onShow != null) { onShow.run(); @@ -1476,15 +1518,15 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar passcodeSaveIntent = null; } drawerLayoutContainer.setAllowOpenDrawer(true, false); - actionBarLayout.setVisibility(View.VISIBLE); - actionBarLayout.showLastFragment(); + actionBarLayout.getView().setVisibility(View.VISIBLE); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); if (AndroidUtilities.isTablet()) { - layersActionBarLayout.showLastFragment(); - rightActionBarLayout.showLastFragment(); - if (layersActionBarLayout.getVisibility() == View.INVISIBLE) { - layersActionBarLayout.setVisibility(View.VISIBLE); + layersActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + if (layersActionBarLayout.getView().getVisibility() == View.INVISIBLE) { + layersActionBarLayout.getView().setVisibility(View.VISIBLE); } - rightActionBarLayout.setVisibility(View.VISIBLE); + rightActionBarLayout.getView().setVisibility(View.VISIBLE); } }); } @@ -1492,10 +1534,10 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar @SuppressLint("Range") private boolean handleIntent(Intent intent, boolean isNew, boolean restore, boolean fromPassword) { if (AndroidUtilities.handleProxyIntent(this, intent)) { - actionBarLayout.showLastFragment(); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); if (AndroidUtilities.isTablet()) { - layersActionBarLayout.showLastFragment(); - rightActionBarLayout.showLastFragment(); + layersActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); } return true; } @@ -1522,6 +1564,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar boolean pushOpened = false; long push_user_id = 0; long push_chat_id = 0; + int push_topic_id = 0; int push_enc_id = 0; int push_msg_id = 0; int open_settings = 0; @@ -2024,6 +2067,12 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (threadId == 0) { threadId = null; } + if (threadId == null) { + threadId = Utilities.parseInt(data.getQueryParameter("topic")); + if (threadId == 0) { + threadId = null; + } + } } } else if (path.length() >= 1) { ArrayList segments = new ArrayList<>(data.getPathSegments()); @@ -2056,6 +2105,12 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (threadId == 0) { threadId = null; } + if (threadId == null) { + threadId = Utilities.parseInt(data.getQueryParameter("topic")); + if (threadId == 0) { + threadId = null; + } + } commentId = Utilities.parseInt(data.getQueryParameter("comment")); if (commentId == 0) { commentId = null; @@ -2070,8 +2125,8 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (url.startsWith("tg:premium_offer") || url.startsWith("tg://premium_offer")) { String finalUrl = url; AndroidUtilities.runOnUIThread(() -> { - if (!actionBarLayout.fragmentsStack.isEmpty()) { - BaseFragment fragment = actionBarLayout.fragmentsStack.get(0); + if (!actionBarLayout.getFragmentStack().isEmpty()) { + BaseFragment fragment = actionBarLayout.getFragmentStack().get(0); Uri uri = Uri.parse(finalUrl); fragment.presentFragment(new PremiumPreviewFragment(uri.getQueryParameter("ref"))); }}); @@ -2117,6 +2172,12 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (threadId == 0) { threadId = null; } + if (threadId == null) { + threadId = Utilities.parseInt(data.getQueryParameter("topic")); + if (threadId == 0) { + threadId = null; + } + } commentId = Utilities.parseInt(data.getQueryParameter("comment")); if (commentId == 0) { commentId = null; @@ -2140,6 +2201,12 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (threadId == 0) { threadId = null; } + if (threadId == null) { + threadId = Utilities.parseInt(data.getQueryParameter("topic")); + if (threadId == 0) { + threadId = null; + } + } commentId = Utilities.parseInt(data.getQueryParameter("comment")); if (commentId == 0) { commentId = null; @@ -2483,6 +2550,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar long userId = intent.getLongExtra("userId", 0); int encId = intent.getIntExtra("encId", 0); int widgetId = intent.getIntExtra("appWidgetId", 0); + int topicId = intent.getIntExtra("topicId", 0); if (widgetId != 0) { open_settings = 6; open_widget_edit = widgetId; @@ -2494,6 +2562,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (chatId != 0) { NotificationCenter.getInstance(intentAccount[0]).postNotificationName(NotificationCenter.closeChats); push_chat_id = chatId; + push_topic_id = topicId; } else if (userId != 0) { NotificationCenter.getInstance(intentAccount[0]).postNotificationName(NotificationCenter.closeChats); push_user_id = userId; @@ -2548,7 +2617,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } if (mainFragmentsStack.isEmpty() || MessagesController.getInstance(intentAccount[0]).checkCanOpenChat(args, mainFragmentsStack.get(mainFragmentsStack.size() - 1))) { ChatActivity fragment = new ChatActivity(args); - if (actionBarLayout.presentFragment(fragment, false, true, true, false)) { + if (actionBarLayout.presentFragment(new INavigationLayout.NavigationParams(fragment).setNoAnimation(true))) { pushOpened = true; drawerLayoutContainer.closeDrawer(); } @@ -2562,7 +2631,18 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } if (mainFragmentsStack.isEmpty() || MessagesController.getInstance(intentAccount[0]).checkCanOpenChat(args, mainFragmentsStack.get(mainFragmentsStack.size() - 1))) { ChatActivity fragment = new ChatActivity(args); - if (actionBarLayout.presentFragment(fragment, false, true, true, false)) { + + if (push_topic_id > 0) { + TLRPC.TL_forumTopic topic = MessagesController.getInstance(currentAccount).getTopicsController().findTopic(push_chat_id, push_topic_id); + if (topic != null) { + TLRPC.Message message = topic.topicStartMessage; + ArrayList messageObjects = new ArrayList<>(); + TLRPC.Chat chatLocal = MessagesController.getInstance(currentAccount).getChat(push_chat_id); + messageObjects.add(new MessageObject(currentAccount, message, false, false)); + fragment.setThreadMessages(messageObjects, chatLocal, topic.id, topic.read_inbox_max_id, topic.read_outbox_max_id, topic); + } + } + if (actionBarLayout.presentFragment(new INavigationLayout.NavigationParams(fragment).setNoAnimation(true))) { pushOpened = true; drawerLayoutContainer.closeDrawer(); } @@ -2571,7 +2651,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar Bundle args = new Bundle(); args.putInt("enc_id", push_enc_id); ChatActivity fragment = new ChatActivity(args); - if (actionBarLayout.presentFragment(fragment, false, true, true, false)) { + if (actionBarLayout.presentFragment(new INavigationLayout.NavigationParams(fragment).setNoAnimation(true))) { pushOpened = true; drawerLayoutContainer.closeDrawer(); } @@ -2579,9 +2659,9 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (!AndroidUtilities.isTablet()) { actionBarLayout.removeAllFragments(); } else { - if (!layersActionBarLayout.fragmentsStack.isEmpty()) { - for (int a = 0; a < layersActionBarLayout.fragmentsStack.size() - 1; a++) { - layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.fragmentsStack.get(0)); + if (!layersActionBarLayout.getFragmentStack().isEmpty()) { + for (int a = 0; a < layersActionBarLayout.getFragmentStack().size() - 1; a++) { + layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.getFragmentStack().get(0)); a--; } layersActionBarLayout.closeLastFragment(false); @@ -2590,14 +2670,14 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar pushOpened = false; isNew = false; } else if (showPlayer) { - if (!actionBarLayout.fragmentsStack.isEmpty()) { - BaseFragment fragment = actionBarLayout.fragmentsStack.get(0); + if (!actionBarLayout.getFragmentStack().isEmpty()) { + BaseFragment fragment = actionBarLayout.getFragmentStack().get(0); fragment.showDialog(new AudioPlayerAlert(this, null)); } pushOpened = false; } else if (showLocations) { - if (!actionBarLayout.fragmentsStack.isEmpty()) { - BaseFragment fragment = actionBarLayout.fragmentsStack.get(0); + if (!actionBarLayout.getFragmentStack().isEmpty()) { + BaseFragment fragment = actionBarLayout.getFragmentStack().get(0); fragment.showDialog(new SharingLocationsAlert(this, info -> { intentAccount[0] = info.messageObject.currentAccount; switchToAccount(intentAccount[0], true); @@ -2614,8 +2694,8 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar runImportRequest(exportingChatUri, documentsUrisArray); } else if (importingStickers != null) { AndroidUtilities.runOnUIThread(() -> { - if (!actionBarLayout.fragmentsStack.isEmpty()) { - BaseFragment fragment = actionBarLayout.fragmentsStack.get(0); + if (!actionBarLayout.getFragmentStack().isEmpty()) { + BaseFragment fragment = actionBarLayout.getFragmentStack().get(0); fragment.showDialog(new StickersAlert(this, importingStickersSoftware, importingStickers, importingStickersEmoji, null)); } }); @@ -2628,8 +2708,8 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar openDialogsToSend(false); pushOpened = true; } else { - ArrayList dids = new ArrayList<>(); - dids.add(dialogId); + ArrayList dids = new ArrayList<>(); + dids.add(MessagesStorage.TopicKey.of(dialogId, 0)); didSelectDialogs(null, dids, null, false); } } else if (open_settings != 0) { @@ -2655,13 +2735,13 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } boolean closePreviousFinal = closePrevious; if (open_settings == 6) { - actionBarLayout.presentFragment(fragment, false, true, true, false); + actionBarLayout.presentFragment(new INavigationLayout.NavigationParams(fragment).setNoAnimation(true)); } else { AndroidUtilities.runOnUIThread(() -> presentFragment(fragment, closePreviousFinal, false)); } if (AndroidUtilities.isTablet()) { - actionBarLayout.showLastFragment(); - rightActionBarLayout.showLastFragment(); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); drawerLayoutContainer.setAllowOpenDrawer(false, false); } else { drawerLayoutContainer.setAllowOpenDrawer(true, false); @@ -2670,10 +2750,10 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } else if (open_new_dialog != 0) { Bundle args = new Bundle(); args.putBoolean("destroyAfterSelect", true); - actionBarLayout.presentFragment(new ContactsActivity(args), false, true, true, false); + actionBarLayout.presentFragment(new INavigationLayout.NavigationParams(new ContactsActivity(args)).setNoAnimation(true)); if (AndroidUtilities.isTablet()) { - actionBarLayout.showLastFragment(); - rightActionBarLayout.showLastFragment(); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); drawerLayoutContainer.setAllowOpenDrawer(false, false); } else { drawerLayoutContainer.setAllowOpenDrawer(true, false); @@ -2692,10 +2772,10 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar final TLRPC.UserFull userFull = MessagesController.getInstance(currentAccount).getUserFull(user.id); VoIPHelper.startCall(user, videoCall, userFull != null && userFull.video_calls_available, LaunchActivity.this, userFull, AccountInstance.getInstance(intentAccount[0])); }); - actionBarLayout.presentFragment(contactsFragment, actionBarLayout.getLastFragment() instanceof ContactsActivity, true, true, false); + actionBarLayout.presentFragment(new INavigationLayout.NavigationParams(contactsFragment).setRemoveLast(actionBarLayout.getLastFragment() instanceof ContactsActivity)); if (AndroidUtilities.isTablet()) { - actionBarLayout.showLastFragment(); - rightActionBarLayout.showLastFragment(); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); drawerLayoutContainer.setAllowOpenDrawer(false, false); } else { drawerLayoutContainer.setAllowOpenDrawer(true, false); @@ -2720,10 +2800,10 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } })); }); - actionBarLayout.presentFragment(fragment, false, true, true, false); + actionBarLayout.presentFragment(new INavigationLayout.NavigationParams(fragment).setNoAnimation(true)); if (AndroidUtilities.isTablet()) { - actionBarLayout.showLastFragment(); - rightActionBarLayout.showLastFragment(); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); drawerLayoutContainer.setAllowOpenDrawer(false, false); } else { drawerLayoutContainer.setAllowOpenDrawer(true, false); @@ -2738,10 +2818,10 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (newContactPhone != null) { fragment.setInitialPhoneNumber(PhoneFormat.stripExceptNumbers(newContactPhone, true), false); } - actionBarLayout.presentFragment(fragment, false, true, true, false); + actionBarLayout.presentFragment(new INavigationLayout.NavigationParams(fragment).setNoAnimation(true)); if (AndroidUtilities.isTablet()) { - actionBarLayout.showLastFragment(); - rightActionBarLayout.showLastFragment(); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); drawerLayoutContainer.setAllowOpenDrawer(false, false); } else { drawerLayoutContainer.setAllowOpenDrawer(true, false); @@ -2775,10 +2855,10 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar pushOpened = true; } } else if (showCallLog) { - actionBarLayout.presentFragment(new CallLogActivity(), false, true, true, false); + actionBarLayout.presentFragment(new INavigationLayout.NavigationParams(new CallLogActivity()).setNoAnimation(true)); if (AndroidUtilities.isTablet()) { - actionBarLayout.showLastFragment(); - rightActionBarLayout.showLastFragment(); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); drawerLayoutContainer.setAllowOpenDrawer(false, false); } else { drawerLayoutContainer.setAllowOpenDrawer(true, false); @@ -2789,12 +2869,12 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (!pushOpened && !isNew) { if (AndroidUtilities.isTablet()) { if (!UserConfig.getInstance(currentAccount).isClientActivated()) { - if (layersActionBarLayout.fragmentsStack.isEmpty()) { + if (layersActionBarLayout.getFragmentStack().isEmpty()) { layersActionBarLayout.addFragmentToStack(getClientNotActivatedFragment()); drawerLayoutContainer.setAllowOpenDrawer(false, false); } } else { - if (actionBarLayout.fragmentsStack.isEmpty()) { + if (actionBarLayout.getFragmentStack().isEmpty()) { DialogsActivity dialogsActivity = new DialogsActivity(null); dialogsActivity.setSideMenu(sideMenu); if (searchQuery != null) { @@ -2805,7 +2885,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } } } else { - if (actionBarLayout.fragmentsStack.isEmpty()) { + if (actionBarLayout.getFragmentStack().isEmpty()) { if (!UserConfig.getInstance(currentAccount).isClientActivated()) { actionBarLayout.addFragmentToStack(getClientNotActivatedFragment()); drawerLayoutContainer.setAllowOpenDrawer(false, false); @@ -2820,10 +2900,10 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } } } - actionBarLayout.showLastFragment(); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); if (AndroidUtilities.isTablet()) { - layersActionBarLayout.showLastFragment(); - rightActionBarLayout.showLastFragment(); + layersActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); } } if (isVoipIntent) { @@ -2905,9 +2985,9 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar fragment.setDelegate(this); boolean removeLast; if (AndroidUtilities.isTablet()) { - removeLast = layersActionBarLayout.fragmentsStack.size() > 0 && layersActionBarLayout.fragmentsStack.get(layersActionBarLayout.fragmentsStack.size() - 1) instanceof DialogsActivity; + removeLast = layersActionBarLayout.getFragmentStack().size() > 0 && layersActionBarLayout.getFragmentStack().get(layersActionBarLayout.getFragmentStack().size() - 1) instanceof DialogsActivity; } else { - removeLast = actionBarLayout.fragmentsStack.size() > 1 && actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1) instanceof DialogsActivity; + removeLast = actionBarLayout.getFragmentStack().size() > 1 && actionBarLayout.getFragmentStack().get(actionBarLayout.getFragmentStack().size() - 1) instanceof DialogsActivity; } actionBarLayout.presentFragment(fragment, removeLast, !animated, true, false); if (SecretMediaViewer.hasInstance() && SecretMediaViewer.getInstance().isVisible()) { @@ -2924,8 +3004,8 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (!animated) { drawerLayoutContainer.setAllowOpenDrawer(false, false); if (AndroidUtilities.isTablet()) { - actionBarLayout.showLastFragment(); - rightActionBarLayout.showLastFragment(); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); } else { drawerLayoutContainer.setAllowOpenDrawer(true, false); } @@ -2950,18 +3030,52 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar arrayList.add(new MessageObject(UserConfig.selectedAccount, res.messages.get(a), true, true)); } if (!arrayList.isEmpty()) { - Bundle args = new Bundle(); - args.putLong("chat_id", -arrayList.get(0).getDialogId()); - args.putInt("message_id", Math.max(1, messageId)); - ChatActivity chatActivity = new ChatActivity(args); - chatActivity.setThreadMessages(arrayList, chat, req.msg_id, res.read_inbox_max_id, res.read_outbox_max_id); - if (commentId != null) { - chatActivity.setHighlightMessageId(commentId); - } else if (threadId != null) { - chatActivity.setHighlightMessageId(messageId); + if (chat.forum) { + TLRPC.TL_channels_getForumTopicsByID getForumTopicsByID = new TLRPC.TL_channels_getForumTopicsByID(); + getForumTopicsByID.channel = MessagesController.getInstance(currentAccount).getInputChannel(chat.id); + getForumTopicsByID.topics.add(threadId); + ConnectionsManager.getInstance(currentAccount).sendRequest(getForumTopicsByID, (response2, error2) -> AndroidUtilities.runOnUIThread(() -> { + if (error2 == null) { + TLRPC.TL_messages_forumTopics topics = (TLRPC.TL_messages_forumTopics) response2; + SparseArray messagesMap = new SparseArray<>(); + for (int i = 0; i < topics.messages.size(); i++) { + messagesMap.put(topics.messages.get(i).id, topics.messages.get(i)); + } + MessagesController.getInstance(currentAccount).putUsers(topics.users, false); + MessagesController.getInstance(currentAccount).putChats(topics.chats, false); + + MessagesController.getInstance(currentAccount).getTopicsController().processTopics(chat.id, topics.topics, messagesMap, false, TopicsController.LOAD_TYPE_LOAD_UNKNOWN, -1); + } + TLRPC.TL_forumTopic topic = MessagesController.getInstance(currentAccount).getTopicsController().findTopic(chat.id, threadId); + if (topic != null) { + Bundle args = new Bundle(); + args.putLong("chat_id", -arrayList.get(0).getDialogId()); + args.putInt("message_id", Math.max(1, messageId)); + ChatActivity chatActivity = new ChatActivity(args); + chatActivity.setThreadMessages(arrayList, chat, req.msg_id, res.read_inbox_max_id, res.read_outbox_max_id, topic); + if (commentId != null) { + chatActivity.setHighlightMessageId(commentId); + } else if (threadId != null) { + chatActivity.setHighlightMessageId(messageId); + } + presentFragment(chatActivity); + } + })); + chatOpened = true; + } else { + Bundle args = new Bundle(); + args.putLong("chat_id", -arrayList.get(0).getDialogId()); + args.putInt("message_id", Math.max(1, messageId)); + ChatActivity chatActivity = new ChatActivity(args); + chatActivity.setThreadMessages(arrayList, chat, req.msg_id, res.read_inbox_max_id, res.read_outbox_max_id, null); + if (commentId != null) { + chatActivity.setHighlightMessageId(commentId); + } else if (threadId != null) { + chatActivity.setHighlightMessageId(messageId); + } + presentFragment(chatActivity); + chatOpened = true; } - presentFragment(chatActivity); - chatOpened = true; } } if (!chatOpened) { @@ -3066,8 +3180,8 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar drawerLayoutContainer.setAllowOpenDrawer(false, false); if (AndroidUtilities.isTablet()) { - actionBarLayout.showLastFragment(); - rightActionBarLayout.showLastFragment(); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); } else { drawerLayoutContainer.setAllowOpenDrawer(true, false); } @@ -3076,9 +3190,9 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar fragment.setDelegate(this); boolean removeLast; if (AndroidUtilities.isTablet()) { - removeLast = layersActionBarLayout.fragmentsStack.size() > 0 && layersActionBarLayout.fragmentsStack.get(layersActionBarLayout.fragmentsStack.size() - 1) instanceof DialogsActivity; + removeLast = layersActionBarLayout.getFragmentStack().size() > 0 && layersActionBarLayout.getFragmentStack().get(layersActionBarLayout.getFragmentStack().size() - 1) instanceof DialogsActivity; } else { - removeLast = actionBarLayout.fragmentsStack.size() > 1 && actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1) instanceof DialogsActivity; + removeLast = actionBarLayout.getFragmentStack().size() > 1 && actionBarLayout.getFragmentStack().get(actionBarLayout.getFragmentStack().size() - 1) instanceof DialogsActivity; } actionBarLayout.presentFragment(fragment, removeLast, false, true, false); } else { @@ -3268,7 +3382,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar dialogsActivity = new DialogsActivity(args); dialogsActivity.setDelegate((fragment, dids, message1, param) -> { - long did = dids.get(0); + long did = dids.get(0).dialogId; Bundle args1 = new Bundle(); args1.putBoolean("scrollToTopOnResume", true); @@ -3356,7 +3470,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar args.putString("selectAlertStringGroup", LocaleController.getString("SendGameToGroupText", R.string.SendGameToGroupText)); DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate((fragment1, dids, message1, param) -> { - long did = dids.get(0); + long did = dids.get(0).dialogId; TLRPC.TL_inputMediaGame inputMediaGame = new TLRPC.TL_inputMediaGame(); inputMediaGame.id = new TLRPC.TL_inputGameShortName(); inputMediaGame.id.short_name = game; @@ -3379,9 +3493,9 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar }); boolean removeLast; if (AndroidUtilities.isTablet()) { - removeLast = layersActionBarLayout.fragmentsStack.size() > 0 && layersActionBarLayout.fragmentsStack.get(layersActionBarLayout.fragmentsStack.size() - 1) instanceof DialogsActivity; + removeLast = layersActionBarLayout.getFragmentStack().size() > 0 && layersActionBarLayout.getFragmentStack().get(layersActionBarLayout.getFragmentStack().size() - 1) instanceof DialogsActivity; } else { - removeLast = actionBarLayout.fragmentsStack.size() > 1 && actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1) instanceof DialogsActivity; + removeLast = actionBarLayout.getFragmentStack().size() > 1 && actionBarLayout.getFragmentStack().get(actionBarLayout.getFragmentStack().size() - 1) instanceof DialogsActivity; } actionBarLayout.presentFragment(fragment, removeLast, true, true, false); if (SecretMediaViewer.hasInstance() && SecretMediaViewer.getInstance().isVisible()) { @@ -3396,8 +3510,8 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } drawerLayoutContainer.setAllowOpenDrawer(false, false); if (AndroidUtilities.isTablet()) { - actionBarLayout.showLastFragment(); - rightActionBarLayout.showLastFragment(); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); } else { drawerLayoutContainer.setAllowOpenDrawer(true, false); } @@ -3424,7 +3538,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar // args.putString("addToGroupAlertString", LocaleController.formatString("AddToTheGroupAlertText", R.string.AddToTheGroupAlertText, UserObject.getUserName(user), "%1$s")); DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate((fragment12, dids, message1, param) -> { - long did = dids.get(0); + long did = dids.get(0).dialogId; TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-did); if (chat != null && (chat.creator || chat.admin_rights != null && chat.admin_rights.add_admins)) { @@ -3549,6 +3663,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar }); presentFragment(fragment); } else { + long dialog_id; boolean isBot = false; Bundle args = new Bundle(); @@ -3590,63 +3705,106 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (isBot && lastFragment instanceof ChatActivity && ((ChatActivity) lastFragment).getDialogId() == dialog_id) { ((ChatActivity) lastFragment).setBotUser(botUser); } else { - MessagesController.getInstance(intentAccount).ensureMessagesLoaded(dialog_id, messageId == null ? 0 : messageId, new MessagesController.MessagesLoadedCallback() { - @Override - public void onMessagesLoaded(boolean fromCache) { + TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-dialog_id); + if (chat != null && chat.forum) { + Integer topicId = threadId; + if (topicId == null) { + topicId = messageId; + } + if (topicId != null && topicId != 0) { + openForumFromLink(-dialog_id, topicId, messageId, () -> { + try { + progressDialog.dismiss(); + } catch (Exception e) { + FileLog.e(e); + } + }); + } else { + Bundle bundle = new Bundle(); + bundle.putLong("chat_id", -dialog_id); + presentFragment(new TopicsFragment(bundle)); try { progressDialog.dismiss(); } catch (Exception e) { FileLog.e(e); } - if (!LaunchActivity.this.isFinishing()) { - BaseFragment voipLastFragment; - if (livestream == null || !(lastFragment instanceof ChatActivity) || ((ChatActivity) lastFragment).getDialogId() != dialog_id) { - ChatActivity fragment = new ChatActivity(args); - actionBarLayout.presentFragment(fragment); - voipLastFragment = fragment; - } else { - voipLastFragment = lastFragment; + } + } else { + MessagesController.getInstance(intentAccount).ensureMessagesLoaded(dialog_id, messageId == null ? 0 : messageId, new MessagesController.MessagesLoadedCallback() { + @Override + public void onMessagesLoaded(boolean fromCache) { + try { + progressDialog.dismiss(); + } catch (Exception e) { + FileLog.e(e); } + if (!LaunchActivity.this.isFinishing()) { + BaseFragment voipLastFragment; + if (livestream == null || !(lastFragment instanceof ChatActivity) || ((ChatActivity) lastFragment).getDialogId() != dialog_id) { + if (lastFragment instanceof ChatActivity && ((ChatActivity) lastFragment).getDialogId() == dialog_id) { + ChatActivity chatActivity = (ChatActivity) lastFragment; + ViewGroup v = chatActivity.getChatListView(); + AndroidUtilities.shakeViewSpring(v, 5); + BotWebViewVibrationEffect.APP_ERROR.vibrate(); - AndroidUtilities.runOnUIThread(() -> { - if (livestream != null) { - AccountInstance accountInstance = AccountInstance.getInstance(currentAccount); - ChatObject.Call cachedCall = accountInstance.getMessagesController().getGroupCall(-dialog_id, false); - if (cachedCall != null) { - VoIPHelper.startCall(accountInstance.getMessagesController().getChat(-dialog_id), accountInstance.getMessagesController().getInputPeer(dialog_id), null, false, cachedCall == null || !cachedCall.call.rtmp_stream, LaunchActivity.this, voipLastFragment, accountInstance); + v = chatActivity.getChatActivityEnterView(); + for (int i = 0; i < v.getChildCount(); i++) { + AndroidUtilities.shakeViewSpring(v.getChildAt(i), 5); + } + v = chatActivity.getActionBar(); + for (int i = 0; i < v.getChildCount(); i++) { + AndroidUtilities.shakeViewSpring(v.getChildAt(i), 5); + } + voipLastFragment = lastFragment; } else { - TLRPC.ChatFull chatFull = accountInstance.getMessagesController().getChatFull(-dialog_id); - if (chatFull != null) { - if (chatFull.call == null) { - if (voipLastFragment.getParentActivity() != null) { - BulletinFactory.of(voipLastFragment).createSimpleBulletin(R.raw.linkbroken, LocaleController.getString("InviteExpired", R.string.InviteExpired)).show(); + ChatActivity fragment = new ChatActivity(args); + actionBarLayout.presentFragment(fragment); + voipLastFragment = fragment; + } + } else { + voipLastFragment = lastFragment; + } + + AndroidUtilities.runOnUIThread(() -> { + if (livestream != null) { + AccountInstance accountInstance = AccountInstance.getInstance(currentAccount); + ChatObject.Call cachedCall = accountInstance.getMessagesController().getGroupCall(-dialog_id, false); + if (cachedCall != null) { + VoIPHelper.startCall(accountInstance.getMessagesController().getChat(-dialog_id), accountInstance.getMessagesController().getInputPeer(dialog_id), null, false, cachedCall == null || !cachedCall.call.rtmp_stream, LaunchActivity.this, voipLastFragment, accountInstance); + } else { + TLRPC.ChatFull chatFull = accountInstance.getMessagesController().getChatFull(-dialog_id); + if (chatFull != null) { + if (chatFull.call == null) { + if (voipLastFragment.getParentActivity() != null) { + BulletinFactory.of(voipLastFragment).createSimpleBulletin(R.raw.linkbroken, LocaleController.getString("InviteExpired", R.string.InviteExpired)).show(); + } + } else { + accountInstance.getMessagesController().getGroupCall(-dialog_id, true, () -> AndroidUtilities.runOnUIThread(() -> { + ChatObject.Call call = accountInstance.getMessagesController().getGroupCall(-dialog_id, false); + VoIPHelper.startCall(accountInstance.getMessagesController().getChat(-dialog_id), accountInstance.getMessagesController().getInputPeer(dialog_id), null, false, call == null || !call.call.rtmp_stream, LaunchActivity.this, voipLastFragment, accountInstance); + })); } - } else { - accountInstance.getMessagesController().getGroupCall(-dialog_id, true, () -> AndroidUtilities.runOnUIThread(() -> { - ChatObject.Call call = accountInstance.getMessagesController().getGroupCall(-dialog_id, false); - VoIPHelper.startCall(accountInstance.getMessagesController().getChat(-dialog_id), accountInstance.getMessagesController().getInputPeer(dialog_id), null, false, call == null || !call.call.rtmp_stream, LaunchActivity.this, voipLastFragment, accountInstance); - })); } } } - } - }, 150); + }, 150); + } } - } - @Override - public void onError() { - if (!LaunchActivity.this.isFinishing()) { - BaseFragment fragment = mainFragmentsStack.get(mainFragmentsStack.size() - 1); - AlertsCreator.showSimpleAlert(fragment, LocaleController.getString("JoinToGroupErrorNotExist", R.string.JoinToGroupErrorNotExist)); + @Override + public void onError() { + if (!LaunchActivity.this.isFinishing()) { + BaseFragment fragment = mainFragmentsStack.get(mainFragmentsStack.size() - 1); + AlertsCreator.showSimpleAlert(fragment, LocaleController.getString("JoinToGroupErrorNotExist", R.string.JoinToGroupErrorNotExist)); + } + try { + progressDialog.dismiss(); + } catch (Exception e) { + FileLog.e(e); + } } - try { - progressDialog.dismiss(); - } catch (Exception e) { - FileLog.e(e); - } - } - }); + }); + } hideProgressDialog = false; } } @@ -3655,6 +3813,9 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar try { if (!mainFragmentsStack.isEmpty()) { BaseFragment fragment = mainFragmentsStack.get(mainFragmentsStack.size() - 1); + if (fragment instanceof ChatActivity) { + ((ChatActivity) fragment).shakeContent(); + } if (error != null && error.text != null && error.text.startsWith("FLOOD_WAIT")) { BulletinFactory.of(fragment).createErrorBulletin(LocaleController.getString("FloodWait", R.string.FloodWait)).show(); } else if (AndroidUtilities.isNumeric(username)) { @@ -3686,7 +3847,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar boolean hideProgressDialog = true; if (error == null && actionBarLayout != null) { TLRPC.ChatInvite invite = (TLRPC.ChatInvite) response; - if (invite.chat != null && (!ChatObject.isLeftFromChat(invite.chat) || !invite.chat.kicked && (!TextUtils.isEmpty(invite.chat.username) || invite instanceof TLRPC.TL_chatInvitePeek || invite.chat.has_geo))) { + if (invite.chat != null && (!ChatObject.isLeftFromChat(invite.chat) || !invite.chat.kicked && (ChatObject.isPublic(invite.chat) || invite instanceof TLRPC.TL_chatInvitePeek || invite.chat.has_geo))) { MessagesController.getInstance(intentAccount).putChat(invite.chat, false); ArrayList chats = new ArrayList<>(); chats.add(invite.chat); @@ -3696,39 +3857,45 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (mainFragmentsStack.isEmpty() || MessagesController.getInstance(intentAccount).checkCanOpenChat(args, mainFragmentsStack.get(mainFragmentsStack.size() - 1))) { boolean[] canceled = new boolean[1]; progressDialog.setOnCancelListener(dialog -> canceled[0] = true); + if (invite.chat.forum) { + Bundle bundle = new Bundle(); + bundle.putLong("chat_id", invite.chat.id); + presentFragment(new TopicsFragment(bundle)); + } else { + MessagesController.getInstance(intentAccount).ensureMessagesLoaded(-invite.chat.id, 0, new MessagesController.MessagesLoadedCallback() { + @Override + public void onMessagesLoaded(boolean fromCache) { + try { + progressDialog.dismiss(); + } catch (Exception e) { + FileLog.e(e); + } + if (canceled[0]) { + return; + } + ChatActivity fragment = new ChatActivity(args); + if (invite instanceof TLRPC.TL_chatInvitePeek) { + fragment.setChatInvite(invite); + } + actionBarLayout.presentFragment(fragment); + } - MessagesController.getInstance(intentAccount).ensureMessagesLoaded(-invite.chat.id, 0, new MessagesController.MessagesLoadedCallback() { - @Override - public void onMessagesLoaded(boolean fromCache) { - try { - progressDialog.dismiss(); - } catch (Exception e) { - FileLog.e(e); + @Override + public void onError() { + if (!LaunchActivity.this.isFinishing()) { + BaseFragment fragment = mainFragmentsStack.get(mainFragmentsStack.size() - 1); + AlertsCreator.showSimpleAlert(fragment, LocaleController.getString("JoinToGroupErrorNotExist", R.string.JoinToGroupErrorNotExist)); + } + try { + progressDialog.dismiss(); + } catch (Exception e) { + FileLog.e(e); + } } - if (canceled[0]) { - return; - } - ChatActivity fragment = new ChatActivity(args); - if (invite instanceof TLRPC.TL_chatInvitePeek) { - fragment.setChatInvite(invite); - } - actionBarLayout.presentFragment(fragment); - } + }); + hideProgressDialog = false; + } - @Override - public void onError() { - if (!LaunchActivity.this.isFinishing()) { - BaseFragment fragment = mainFragmentsStack.get(mainFragmentsStack.size() - 1); - AlertsCreator.showSimpleAlert(fragment, LocaleController.getString("JoinToGroupErrorNotExist", R.string.JoinToGroupErrorNotExist)); - } - try { - progressDialog.dismiss(); - } catch (Exception e) { - FileLog.e(e); - } - } - }); - hideProgressDialog = false; } } else { BaseFragment fragment = mainFragmentsStack.get(mainFragmentsStack.size() - 1); @@ -3849,7 +4016,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar args.putInt("dialogsType", 3); DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate((fragment13, dids, m, param) -> { - long did = dids.get(0); + long did = dids.get(0).dialogId; Bundle args13 = new Bundle(); args13.putBoolean("scrollToTopOnResume", true); args13.putBoolean("hasUrl", hasUrl); @@ -4097,7 +4264,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } catch (Exception e) { FileLog.e(e); } - showAlertDialog(AlertsCreator.createSimpleAlert(LaunchActivity.this, LocaleController.getString("LinkNotFound", R.string.LinkNotFound))); + showAlertDialog(AlertsCreator.createNoAccessAlert(LaunchActivity.this, LocaleController.getString(R.string.DialogNotAvailable), LocaleController.getString(R.string.LinkNotFound), null)); } })); } @@ -4105,38 +4272,50 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar Bundle args = new Bundle(); args.putLong("chat_id", channelId); args.putInt("message_id", messageId); - BaseFragment lastFragment = !mainFragmentsStack.isEmpty() ? mainFragmentsStack.get(mainFragmentsStack.size() - 1) : null; - if (lastFragment == null || MessagesController.getInstance(intentAccount).checkCanOpenChat(args, lastFragment)) { - AndroidUtilities.runOnUIThread(() -> { - if (!actionBarLayout.presentFragment(new ChatActivity(args))) { - TLRPC.TL_channels_getChannels req = new TLRPC.TL_channels_getChannels(); - TLRPC.TL_inputChannel inputChannel = new TLRPC.TL_inputChannel(); - inputChannel.channel_id = channelId; - req.id.add(inputChannel); - requestId[0] = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { - try { - progressDialog.dismiss(); - } catch (Exception e) { - FileLog.e(e); - } - boolean notFound = true; - if (response instanceof TLRPC.TL_messages_chats) { - TLRPC.TL_messages_chats res = (TLRPC.TL_messages_chats) response; - if (!res.chats.isEmpty()) { - notFound = false; - MessagesController.getInstance(currentAccount).putChats(res.chats, false); - TLRPC.Chat chat = res.chats.get(0); - if (lastFragment == null || MessagesController.getInstance(intentAccount).checkCanOpenChat(args, lastFragment)) { - actionBarLayout.presentFragment(new ChatActivity(args)); + TLRPC.Chat chatLocal = MessagesController.getInstance(currentAccount).getChat(channelId); + if (chatLocal != null && chatLocal.forum) { + openForumFromLink(-channelId, messageId, null, null); + } else { + BaseFragment lastFragment = !mainFragmentsStack.isEmpty() ? mainFragmentsStack.get(mainFragmentsStack.size() - 1) : null; + if (lastFragment == null || MessagesController.getInstance(intentAccount).checkCanOpenChat(args, lastFragment)) { + AndroidUtilities.runOnUIThread(() -> { + if (!actionBarLayout.presentFragment(new ChatActivity(args))) { + TLRPC.TL_channels_getChannels req = new TLRPC.TL_channels_getChannels(); + TLRPC.TL_inputChannel inputChannel = new TLRPC.TL_inputChannel(); + inputChannel.channel_id = channelId; + req.id.add(inputChannel); + requestId[0] = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + try { + progressDialog.dismiss(); + } catch (Exception e) { + FileLog.e(e); + } + boolean notFound = true; + if (response instanceof TLRPC.TL_messages_chats) { + TLRPC.TL_messages_chats res = (TLRPC.TL_messages_chats) response; + if (!res.chats.isEmpty()) { + notFound = false; + MessagesController.getInstance(currentAccount).putChats(res.chats, false); + TLRPC.Chat chat = res.chats.get(0); + if (chat != null && chat.forum) { + if (threadId != null) { + openForumFromLink(-channelId, threadId, messageId, null); + } else { + openForumFromLink(-channelId, messageId, null, null); + } + } + if (lastFragment == null || MessagesController.getInstance(intentAccount).checkCanOpenChat(args, lastFragment)) { + actionBarLayout.presentFragment(new ChatActivity(args)); + } } } - } - if (notFound) { - showAlertDialog(AlertsCreator.createSimpleAlert(LaunchActivity.this, LocaleController.getString("LinkNotFound", R.string.LinkNotFound))); - } - })); - } - }); + if (notFound) { + showAlertDialog(AlertsCreator.createNoAccessAlert(LaunchActivity.this, LocaleController.getString(R.string.DialogNotAvailable), LocaleController.getString(R.string.LinkNotFound), null)); + } + })); + } + }); + } } } } @@ -4157,6 +4336,49 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } } + private void openForumFromLink(long dialogId, int topicId, Integer messageId, Runnable onOpened) { + TLRPC.TL_channels_getForumTopicsByID getForumTopicsByID = new TLRPC.TL_channels_getForumTopicsByID(); + getForumTopicsByID.channel = MessagesController.getInstance(currentAccount).getInputChannel(-dialogId); + getForumTopicsByID.topics.add(topicId); + ConnectionsManager.getInstance(currentAccount).sendRequest(getForumTopicsByID, (response2, error2) -> AndroidUtilities.runOnUIThread(() -> { + if (error2 == null) { + TLRPC.TL_messages_forumTopics topics = (TLRPC.TL_messages_forumTopics) response2; + SparseArray messagesMap = new SparseArray<>(); + for (int i = 0; i < topics.messages.size(); i++) { + messagesMap.put(topics.messages.get(i).id, topics.messages.get(i)); + } + MessagesController.getInstance(currentAccount).putUsers(topics.users, false); + MessagesController.getInstance(currentAccount).putChats(topics.chats, false); + + MessagesController.getInstance(currentAccount).getTopicsController().processTopics(-dialogId, topics.topics, messagesMap, false, TopicsController.LOAD_TYPE_LOAD_UNKNOWN, -1); + } + TLRPC.TL_forumTopic topic = MessagesController.getInstance(currentAccount).getTopicsController().findTopic(-dialogId, topicId); + if (topic != null) { + TLRPC.Chat chatLocal = MessagesController.getInstance(currentAccount).getChat(-dialogId); + Bundle args2 = new Bundle(); + args2.putLong("chat_id", -dialogId); + if (messageId != null && !messageId.equals(topicId)) { + args2.putLong("message_id", messageId); + } + args2.putInt("unread_count", topic.unread_count); + args2.putBoolean("historyPreloaded", false); + ChatActivity chatActivity = new ChatActivity(args2); + TLRPC.Message message2 = topic.topicStartMessage; + ArrayList messageObjects = new ArrayList<>(); + messageObjects.add(new MessageObject(currentAccount, message2, false, false)); + chatActivity.setThreadMessages(messageObjects, chatLocal, topic.id, topic.read_inbox_max_id, topic.read_outbox_max_id, topic); + presentFragment(chatActivity); + } else { + Bundle bundle = new Bundle(); + bundle.putLong("chat_id", -dialogId); + presentFragment(new TopicsFragment(bundle)); + } + if (onOpened != null) { + onOpened.run(); + } + })); + } + private List findContacts(String userName, String userPhone, boolean allowSelf) { final MessagesController messagesController = MessagesController.getInstance(currentAccount); final ContactsController contactsController = ContactsController.getInstance(currentAccount); @@ -4524,14 +4746,14 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } @Override - public void didSelectDialogs(DialogsActivity dialogsFragment, ArrayList dids, CharSequence message, boolean param) { + public void didSelectDialogs(DialogsActivity dialogsFragment, ArrayList dids, CharSequence message, boolean param) { final int account = dialogsFragment != null ? dialogsFragment.getCurrentAccount() : currentAccount; if (exportingChatUri != null) { Uri uri = exportingChatUri; ArrayList documentsUris = documentsUrisArray != null ? new ArrayList<>(documentsUrisArray) : null; final AlertDialog progressDialog = new AlertDialog(this, 3); - SendMessagesHelper.getInstance(account).prepareImportHistory(dids.get(0), exportingChatUri, documentsUrisArray, (result) -> { + SendMessagesHelper.getInstance(account).prepareImportHistory(dids.get(0).dialogId, exportingChatUri, documentsUrisArray, (result) -> { if (result != 0) { Bundle args = new Bundle(); args.putBoolean("scrollToTopOnResume", true); @@ -4569,7 +4791,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar boolean notify = dialogsFragment == null || dialogsFragment.notify; final ChatActivity fragment; if (dids.size() <= 1) { - final long did = dids.get(0); + final long did = dids.get(0).dialogId; Bundle args = new Bundle(); args.putBoolean("scrollToTopOnResume", true); @@ -4612,7 +4834,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } for (int i = 0; i < dids.size(); i++) { - final long did = dids.get(i); + final long did = dids.get(i).dialogId; if (AlertsCreator.checkSlowMode(this, currentAccount, did, attachesCount > 1)) { return; } @@ -4626,7 +4848,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } AccountInstance accountInstance = AccountInstance.getInstance(UserConfig.selectedAccount); for (int i = 0; i < dids.size(); i++) { - long did = dids.get(i); + long did = dids.get(i).dialogId; SendMessagesHelper.getInstance(account).sendMessage(user, did, null, null, null, null, notify2, scheduleDate); if (!TextUtils.isEmpty(message)) { SendMessagesHelper.prepareSendingText(accountInstance, message.toString(), did, notify, 0); @@ -4637,7 +4859,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } else { String captionToSend = null; for (int i = 0; i < dids.size(); i++) { - final long did = dids.get(i); + final long did = dids.get(i).dialogId; AccountInstance accountInstance = AccountInstance.getInstance(UserConfig.selectedAccount); boolean photosEditorOpened = false, videoEditorOpened = false; @@ -4753,6 +4975,10 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.requestPermissions); } + public void presentFragment(INavigationLayout.NavigationParams params) { + actionBarLayout.presentFragment(params); + } + public void presentFragment(BaseFragment fragment) { actionBarLayout.presentFragment(fragment); } @@ -4761,15 +4987,15 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar return actionBarLayout.presentFragment(fragment, removeLast, forceWithoutAnimation, true, false); } - public ActionBarLayout getActionBarLayout() { + public INavigationLayout getActionBarLayout() { return actionBarLayout; } - public ActionBarLayout getLayersActionBarLayout() { + public INavigationLayout getLayersActionBarLayout() { return layersActionBarLayout; } - public ActionBarLayout getRightActionBarLayout() { + public INavigationLayout getRightActionBarLayout() { return rightActionBarLayout; } @@ -4812,17 +5038,17 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (editorView != null) { editorView.onActivityResult(requestCode, resultCode, data); } - if (actionBarLayout.fragmentsStack.size() != 0) { - BaseFragment fragment = actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1); + if (actionBarLayout.getFragmentStack().size() != 0) { + BaseFragment fragment = actionBarLayout.getFragmentStack().get(actionBarLayout.getFragmentStack().size() - 1); fragment.onActivityResultFragment(requestCode, resultCode, data); } if (AndroidUtilities.isTablet()) { - if (rightActionBarLayout.fragmentsStack.size() != 0) { - BaseFragment fragment = rightActionBarLayout.fragmentsStack.get(rightActionBarLayout.fragmentsStack.size() - 1); + if (rightActionBarLayout.getFragmentStack().size() != 0) { + BaseFragment fragment = rightActionBarLayout.getFragmentStack().get(rightActionBarLayout.getFragmentStack().size() - 1); fragment.onActivityResultFragment(requestCode, resultCode, data); } - if (layersActionBarLayout.fragmentsStack.size() != 0) { - BaseFragment fragment = layersActionBarLayout.fragmentsStack.get(layersActionBarLayout.fragmentsStack.size() - 1); + if (layersActionBarLayout.getFragmentStack().size() != 0) { + BaseFragment fragment = layersActionBarLayout.getFragmentStack().get(layersActionBarLayout.getFragmentStack().size() - 1); fragment.onActivityResultFragment(requestCode, resultCode, data); } } @@ -4835,17 +5061,17 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (!checkPermissionsResult(requestCode, permissions, grantResults)) return; - if (actionBarLayout.fragmentsStack.size() != 0) { - BaseFragment fragment = actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1); + if (actionBarLayout.getFragmentStack().size() != 0) { + BaseFragment fragment = actionBarLayout.getFragmentStack().get(actionBarLayout.getFragmentStack().size() - 1); fragment.onRequestPermissionsResultFragment(requestCode, permissions, grantResults); } if (AndroidUtilities.isTablet()) { - if (rightActionBarLayout.fragmentsStack.size() != 0) { - BaseFragment fragment = rightActionBarLayout.fragmentsStack.get(rightActionBarLayout.fragmentsStack.size() - 1); + if (rightActionBarLayout.getFragmentStack().size() != 0) { + BaseFragment fragment = rightActionBarLayout.getFragmentStack().get(rightActionBarLayout.getFragmentStack().size() - 1); fragment.onRequestPermissionsResultFragment(requestCode, permissions, grantResults); } - if (layersActionBarLayout.fragmentsStack.size() != 0) { - BaseFragment fragment = layersActionBarLayout.fragmentsStack.get(layersActionBarLayout.fragmentsStack.size() - 1); + if (layersActionBarLayout.getFragmentStack().size() != 0) { + BaseFragment fragment = layersActionBarLayout.getFragmentStack().get(layersActionBarLayout.getFragmentStack().size() - 1); fragment.onRequestPermissionsResultFragment(requestCode, permissions, grantResults); } } @@ -4860,6 +5086,21 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } } + @Override + public void onRestoreInstanceState(@Nullable Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + + if (actionBarLayout != null) { + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + } + if (rightActionBarLayout != null) { + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + } + if (layersActionBarLayout != null) { + layersActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + } + } + @Override protected void onPause() { super.onPause(); @@ -4941,7 +5182,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } PipRoundVideoView pipRoundVideoView = PipRoundVideoView.getInstance(); MediaController.getInstance().setBaseActivity(this, false); - MediaController.getInstance().setFeedbackView(actionBarLayout, false); + MediaController.getInstance().setFeedbackView(actionBarLayout.getView(), false); if (pipRoundVideoView != null) { pipRoundVideoView.close(false); } @@ -4972,6 +5213,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } super.onDestroy(); onFinish(); + FloatingDebugController.onDestroy(); } @Override @@ -4996,14 +5238,14 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar checkWasMutedByAdmin(true); //FileLog.d("UI resume time = " + (SystemClock.elapsedRealtime() - ApplicationLoader.startTime)); NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.startAllHeavyOperations, 4096); - MediaController.getInstance().setFeedbackView(actionBarLayout, true); + MediaController.getInstance().setFeedbackView(actionBarLayout.getView(), true); ApplicationLoader.mainInterfacePaused = false; showLanguageAlert(false); Utilities.stageQueue.postRunnable(() -> { ApplicationLoader.mainInterfacePausedStageQueue = false; ApplicationLoader.mainInterfacePausedStageQueueTime = System.currentTimeMillis(); }); - checkFreeDiscSpace(); + checkFreeDiscSpace(0); MediaController.checkGallery(); onPasscodeResume(); if (passcodeView == null || passcodeView.getVisibility() != View.VISIBLE) { @@ -5069,13 +5311,13 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar mainFragmentsStack.addAll(layerFragmentsStack); rightFragmentsStack.clear(); layerFragmentsStack.clear(); - } else if (rightFragmentsStack.isEmpty()) { + } else { List fragments = new ArrayList<>(mainFragmentsStack); mainFragmentsStack.clear(); rightFragmentsStack.clear(); layerFragmentsStack.clear(); for (BaseFragment fragment : fragments) { - if (fragment instanceof DialogsActivity && ((DialogsActivity) fragment).isMainDialogList()) { + if (fragment instanceof DialogsActivity && ((DialogsActivity) fragment).isMainDialogList() && !((DialogsActivity) fragment).isArchive()) { mainFragmentsStack.add(fragment); } else if (fragment instanceof ChatActivity && !((ChatActivity) fragment).isInScheduleMode()) { rightFragmentsStack.add(fragment); @@ -5086,21 +5328,13 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar layerFragmentsStack.add(fragment); } } - } else { - for (BaseFragment fragment : rightFragmentsStack) { - if (fragment instanceof ChatActivity && !((ChatActivity) fragment).isInScheduleMode()) { - if (dialogId == 0) { - dialogId = ((ChatActivity) fragment).getDialogId(); - } - } - } } setupActionBarLayout(); - actionBarLayout.showLastFragment(); + actionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); if (AndroidUtilities.isTablet()) { - rightActionBarLayout.showLastFragment(); - layersActionBarLayout.showLastFragment(); + rightActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); + layersActionBarLayout.rebuildFragments(INavigationLayout.REBUILD_FLAG_REBUILD_LAST); for (BaseFragment fragment : mainFragmentsStack) { if (fragment instanceof DialogsActivity && ((DialogsActivity) fragment).isMainDialogList()) { @@ -5179,6 +5413,13 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + if (fragment != null) { + Map colorsReplacement = new HashMap<>(); + colorsReplacement.put("info1.**", fragment.getThemedColor(Theme.key_dialogTopBackground)); + colorsReplacement.put("info2.**", fragment.getThemedColor(Theme.key_dialogTopBackground)); + builder.setTopAnimation(R.raw.not_available, AlertsCreator.NEW_DENY_DIALOG_TOP_ICON_SIZE, false, fragment.getThemedColor(Theme.key_dialogTopBackground), colorsReplacement); + builder.setTopAnimationIsNew(true); + } if (reason != 2 && reason != 3) { builder.setNegativeButton(LocaleController.getString("MoreInfo", R.string.MoreInfo), (dialogInterface, i) -> { if (!mainFragmentsStack.isEmpty()) { @@ -5201,6 +5442,9 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (type.startsWith("AUTH_KEY_DROP_")) { builder.setPositiveButton(LocaleController.getString("Cancel", R.string.Cancel), null); builder.setNegativeButton(LocaleController.getString("LogOut", R.string.LogOut), (dialog, which) -> MessagesController.getInstance(currentAccount).performLogout(2)); + } else if (type.startsWith("PREMIUM_")) { + builder.setTitle(LocaleController.getString(R.string.TelegramPremium)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); } else { builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); } @@ -5282,14 +5526,14 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar ArticleViewer.getInstance().setParentActivity(this, mainFragmentsStack.get(mainFragmentsStack.size() - 1)); ArticleViewer.getInstance().open((TLRPC.TL_webPage) args[0], (String) args[1]); } else if (id == NotificationCenter.hasNewContactsToImport) { - if (actionBarLayout == null || actionBarLayout.fragmentsStack.isEmpty()) { + if (actionBarLayout == null || actionBarLayout.getFragmentStack().isEmpty()) { return; } final int type = (Integer) args[0]; final HashMap contactHashMap = (HashMap) args[1]; final boolean first = (Boolean) args[2]; final boolean schedule = (Boolean) args[3]; - BaseFragment fragment = actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1); + BaseFragment fragment = actionBarLayout.getFragmentStack().get(actionBarLayout.getFragmentStack().size() - 1); AlertDialog.Builder builder = new AlertDialog.Builder(LaunchActivity.this); builder.setTopAnimation(R.raw.permission_request_contacts, AlertsCreator.PERMISSIONS_REQUEST_TOP_ICON_SIZE, false, Theme.getColor(Theme.key_dialogTopBackground)); @@ -5661,6 +5905,9 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar requsetPermissionsPointer ); } + } else if (id == NotificationCenter.chatSwithcedToForum) { + long chatId = (long) args[0]; + ForumUtilities.switchAllFragmentsInStackToForum(chatId, actionBarLayout); } } @@ -5714,7 +5961,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } if (!mainFragmentsStack.isEmpty()) { TLRPC.Chat chat = voIPService.getChat(); - BaseFragment fragment = actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1); + BaseFragment fragment = actionBarLayout.getFragmentStack().get(actionBarLayout.getFragmentStack().size() - 1); if (fragment instanceof ChatActivity) { ChatActivity chatActivity = (ChatActivity) fragment; if (chatActivity.getDialogId() == -chat.id) { @@ -5766,10 +6013,14 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar loadingTheme = null; } - private void checkFreeDiscSpace() { + private boolean checkFreeDiscSpaceShown; + private long alreadyShownFreeDiscSpaceAlertForced; + private static LaunchActivity staticInstanceForAlerts; + private void checkFreeDiscSpace(final int force) { + staticInstanceForAlerts = this; SharedConfig.checkKeepMedia(); SharedConfig.checkLogsToDelete(); - if (Build.VERSION.SDK_INT >= 26) { + if (Build.VERSION.SDK_INT >= 26 && force == 0 || checkFreeDiscSpaceShown) { return; } Utilities.globalQueue.postRunnable(() -> { @@ -5778,7 +6029,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } try { SharedPreferences preferences = MessagesController.getGlobalMainSettings(); - if (Math.abs(preferences.getLong("last_space_check", 0) - System.currentTimeMillis()) >= 3 * 24 * 3600 * 1000) { + if (force == 2 || force == 1 && Math.abs(alreadyShownFreeDiscSpaceAlertForced - System.currentTimeMillis()) > 1000 * 60 * 4 || Math.abs(preferences.getLong("last_space_check", 0) - System.currentTimeMillis()) >= 3 * 24 * 3600 * 1000) { File path = FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE); if (path == null) { return; @@ -5790,11 +6041,22 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } else { freeSpace = statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong(); } - if (freeSpace < 1024 * 1024 * 100) { + if (force > 0 || freeSpace < 1024 * 1024 * 50) { + if (force > 0) { + alreadyShownFreeDiscSpaceAlertForced = System.currentTimeMillis(); + } preferences.edit().putLong("last_space_check", System.currentTimeMillis()).commit(); AndroidUtilities.runOnUIThread(() -> { + if (checkFreeDiscSpaceShown) { + return; + } try { - AlertsCreator.createFreeSpaceDialog(LaunchActivity.this).show(); + Dialog dialog = AlertsCreator.createFreeSpaceDialog(LaunchActivity.this); + dialog.setOnDismissListener(di -> { + checkFreeDiscSpaceShown = false; + }); + checkFreeDiscSpaceShown = true; + dialog.show(); } catch (Throwable ignore) { } @@ -5806,6 +6068,11 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } }, 2000); } + public static void checkFreeDiscSpaceStatic(final int force) { + if (staticInstanceForAlerts != null) { + staticInstanceForAlerts.checkFreeDiscSpace(force); + } + } private void showLanguageAlertInternal(LocaleController.LocaleInfo systemInfo, LocaleController.LocaleInfo englishInfo, String systemLang) { try { @@ -6107,16 +6374,16 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar super.onSaveInstanceState(outState); BaseFragment lastFragment = null; if (AndroidUtilities.isTablet()) { - if (layersActionBarLayout != null && !layersActionBarLayout.fragmentsStack.isEmpty()) { - lastFragment = layersActionBarLayout.fragmentsStack.get(layersActionBarLayout.fragmentsStack.size() - 1); - } else if (rightActionBarLayout != null && !rightActionBarLayout.fragmentsStack.isEmpty()) { - lastFragment = rightActionBarLayout.fragmentsStack.get(rightActionBarLayout.fragmentsStack.size() - 1); - } else if (!actionBarLayout.fragmentsStack.isEmpty()) { - lastFragment = actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1); + if (layersActionBarLayout != null && !layersActionBarLayout.getFragmentStack().isEmpty()) { + lastFragment = layersActionBarLayout.getFragmentStack().get(layersActionBarLayout.getFragmentStack().size() - 1); + } else if (rightActionBarLayout != null && !rightActionBarLayout.getFragmentStack().isEmpty()) { + lastFragment = rightActionBarLayout.getFragmentStack().get(rightActionBarLayout.getFragmentStack().size() - 1); + } else if (!actionBarLayout.getFragmentStack().isEmpty()) { + lastFragment = actionBarLayout.getFragmentStack().get(actionBarLayout.getFragmentStack().size() - 1); } } else { - if (!actionBarLayout.fragmentsStack.isEmpty()) { - lastFragment = actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1); + if (!actionBarLayout.getFragmentStack().isEmpty()) { + lastFragment = actionBarLayout.getFragmentStack().get(actionBarLayout.getFragmentStack().size() - 1); } } @@ -6151,6 +6418,9 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar @Override public void onBackPressed() { + if (FloatingDebugController.onBackPressed()) { + return; + } if (passcodeView != null && passcodeView.getVisibility() == View.VISIBLE) { finish(); return; @@ -6166,11 +6436,11 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } else if (drawerLayoutContainer.isDrawerOpened()) { drawerLayoutContainer.closeDrawer(false); } else if (AndroidUtilities.isTablet()) { - if (layersActionBarLayout.getVisibility() == View.VISIBLE) { + if (layersActionBarLayout.getView().getVisibility() == View.VISIBLE) { layersActionBarLayout.onBackPressed(); } else { - if (rightActionBarLayout.getVisibility() == View.VISIBLE && !rightActionBarLayout.fragmentsStack.isEmpty()) { - BaseFragment lastFragment = rightActionBarLayout.fragmentsStack.get(rightActionBarLayout.fragmentsStack.size() - 1); + if (rightActionBarLayout.getView().getVisibility() == View.VISIBLE && !rightActionBarLayout.getFragmentStack().isEmpty()) { + BaseFragment lastFragment = rightActionBarLayout.getFragmentStack().get(rightActionBarLayout.getFragmentStack().size() - 1); if (lastFragment.onBackPressed()) { lastFragment.finishFragment(); } @@ -6303,15 +6573,15 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar return super.onKeyUp(keyCode, event); } if (AndroidUtilities.isTablet()) { - if (layersActionBarLayout.getVisibility() == View.VISIBLE && !layersActionBarLayout.fragmentsStack.isEmpty()) { - layersActionBarLayout.onKeyUp(keyCode, event); - } else if (rightActionBarLayout.getVisibility() == View.VISIBLE && !rightActionBarLayout.fragmentsStack.isEmpty()) { - rightActionBarLayout.onKeyUp(keyCode, event); + if (layersActionBarLayout.getView().getVisibility() == View.VISIBLE && !layersActionBarLayout.getFragmentStack().isEmpty()) { + layersActionBarLayout.getView().onKeyUp(keyCode, event); + } else if (rightActionBarLayout.getView().getVisibility() == View.VISIBLE && !rightActionBarLayout.getFragmentStack().isEmpty()) { + rightActionBarLayout.getView().onKeyUp(keyCode, event); } else { - actionBarLayout.onKeyUp(keyCode, event); + actionBarLayout.getView().onKeyUp(keyCode, event); } } else { - if (actionBarLayout.fragmentsStack.size() == 1) { + if (actionBarLayout.getFragmentStack().size() == 1) { if (!drawerLayoutContainer.isDrawerOpened()) { if (getCurrentFocus() != null) { AndroidUtilities.hideKeyboard(getCurrentFocus()); @@ -6321,7 +6591,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar drawerLayoutContainer.closeDrawer(false); } } else { - actionBarLayout.onKeyUp(keyCode, event); + actionBarLayout.getView().onKeyUp(keyCode, event); } } } @@ -6329,23 +6599,27 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } @Override - public boolean needPresentFragment(BaseFragment fragment, boolean removeLast, boolean forceWithoutAnimation, ActionBarLayout layout) { + public boolean needPresentFragment(INavigationLayout layout, INavigationLayout.NavigationParams params) { + BaseFragment fragment = params.fragment; + boolean removeLast = params.removeLast; + boolean forceWithoutAnimation = params.noAnimation; + if (ArticleViewer.hasInstance() && ArticleViewer.getInstance().isVisible()) { ArticleViewer.getInstance().close(false, true); } if (AndroidUtilities.isTablet()) { - drawerLayoutContainer.setAllowOpenDrawer(!(fragment instanceof LoginActivity || fragment instanceof IntroActivity || fragment instanceof CountrySelectActivity) && layersActionBarLayout.getVisibility() != View.VISIBLE, true); + drawerLayoutContainer.setAllowOpenDrawer(!(fragment instanceof LoginActivity || fragment instanceof IntroActivity || fragment instanceof CountrySelectActivity) && layersActionBarLayout.getView().getVisibility() != View.VISIBLE, true); if (fragment instanceof DialogsActivity) { DialogsActivity dialogsActivity = (DialogsActivity) fragment; if (dialogsActivity.isMainDialogList() && layout != actionBarLayout) { actionBarLayout.removeAllFragments(); - actionBarLayout.presentFragment(fragment, removeLast, forceWithoutAnimation, false, false); + actionBarLayout.presentFragment(params.setRemoveLast(removeLast).setNoAnimation(forceWithoutAnimation).setCheckPresentFromDelegate(false)); layersActionBarLayout.removeAllFragments(); - layersActionBarLayout.setVisibility(View.GONE); + layersActionBarLayout.getView().setVisibility(View.GONE); drawerLayoutContainer.setAllowOpenDrawer(true, false); if (!tabletFullSize) { shadowTabletSide.setVisibility(View.VISIBLE); - if (rightActionBarLayout.fragmentsStack.isEmpty()) { + if (rightActionBarLayout.getFragmentStack().isEmpty()) { backgroundTablet.setVisibility(View.VISIBLE); } } @@ -6354,63 +6628,72 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } if (fragment instanceof ChatActivity && !((ChatActivity) fragment).isInScheduleMode()) { if (!tabletFullSize && layout == rightActionBarLayout || tabletFullSize && layout == actionBarLayout) { - boolean result = !(tabletFullSize && layout == actionBarLayout && actionBarLayout.fragmentsStack.size() == 1); - if (!layersActionBarLayout.fragmentsStack.isEmpty()) { - for (int a = 0; a < layersActionBarLayout.fragmentsStack.size() - 1; a++) { - layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.fragmentsStack.get(0)); + boolean result = !(tabletFullSize && layout == actionBarLayout && actionBarLayout.getFragmentStack().size() == 1); + if (!layersActionBarLayout.getFragmentStack().isEmpty()) { + for (int a = 0; a < layersActionBarLayout.getFragmentStack().size() - 1; a++) { + layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.getFragmentStack().get(0)); a--; } layersActionBarLayout.closeLastFragment(!forceWithoutAnimation); } if (!result) { - actionBarLayout.presentFragment(fragment, false, forceWithoutAnimation, false, false); + actionBarLayout.presentFragment(params.setNoAnimation(forceWithoutAnimation).setCheckPresentFromDelegate(false)); } return result; } else if (!tabletFullSize && layout != rightActionBarLayout) { - rightActionBarLayout.setVisibility(View.VISIBLE); + rightActionBarLayout.getView().setVisibility(View.VISIBLE); backgroundTablet.setVisibility(View.GONE); rightActionBarLayout.removeAllFragments(); - rightActionBarLayout.presentFragment(fragment, removeLast, true, false, false); - if (!layersActionBarLayout.fragmentsStack.isEmpty()) { - for (int a = 0; a < layersActionBarLayout.fragmentsStack.size() - 1; a++) { - layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.fragmentsStack.get(0)); + rightActionBarLayout.presentFragment(params.setNoAnimation(true).setRemoveLast(removeLast).setCheckPresentFromDelegate(false)); + if (!layersActionBarLayout.getFragmentStack().isEmpty()) { + for (int a = 0; a < layersActionBarLayout.getFragmentStack().size() - 1; a++) { + layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.getFragmentStack().get(0)); a--; } layersActionBarLayout.closeLastFragment(!forceWithoutAnimation); } return false; } else if (tabletFullSize && layout != actionBarLayout) { - actionBarLayout.presentFragment(fragment, actionBarLayout.fragmentsStack.size() > 1, forceWithoutAnimation, false, false); - if (!layersActionBarLayout.fragmentsStack.isEmpty()) { - for (int a = 0; a < layersActionBarLayout.fragmentsStack.size() - 1; a++) { - layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.fragmentsStack.get(0)); + actionBarLayout.presentFragment(params.setRemoveLast(actionBarLayout.getFragmentStack().size() > 1).setNoAnimation(forceWithoutAnimation).setCheckPresentFromDelegate(false)); + if (!layersActionBarLayout.getFragmentStack().isEmpty()) { + for (int a = 0; a < layersActionBarLayout.getFragmentStack().size() - 1; a++) { + layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.getFragmentStack().get(0)); a--; } layersActionBarLayout.closeLastFragment(!forceWithoutAnimation); } return false; } else { - if (!layersActionBarLayout.fragmentsStack.isEmpty()) { - for (int a = 0; a < layersActionBarLayout.fragmentsStack.size() - 1; a++) { - layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.fragmentsStack.get(0)); + if (!layersActionBarLayout.getFragmentStack().isEmpty()) { + for (int a = 0; a < layersActionBarLayout.getFragmentStack().size() - 1; a++) { + layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.getFragmentStack().get(0)); a--; } layersActionBarLayout.closeLastFragment(!forceWithoutAnimation); } - actionBarLayout.presentFragment(fragment, actionBarLayout.fragmentsStack.size() > 1, forceWithoutAnimation, false, false); + actionBarLayout.presentFragment(params.setRemoveLast(actionBarLayout.getFragmentStack().size() > 1).setNoAnimation(forceWithoutAnimation).setCheckPresentFromDelegate(false)); return false; } } else if (layout != layersActionBarLayout) { - layersActionBarLayout.setVisibility(View.VISIBLE); + layersActionBarLayout.getView().setVisibility(View.VISIBLE); drawerLayoutContainer.setAllowOpenDrawer(false, true); - if (fragment instanceof LoginActivity) { + + int account = -1; + for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { + if (UserConfig.getInstance(a).isClientActivated()) { + account = a; + break; + } + } + + if (fragment instanceof LoginActivity && account == -1) { backgroundTablet.setVisibility(View.VISIBLE); shadowTabletSide.setVisibility(View.GONE); shadowTablet.setBackgroundColor(0x00000000); } else { shadowTablet.setBackgroundColor(0x7f000000); } - layersActionBarLayout.presentFragment(fragment, removeLast, forceWithoutAnimation, false, false); + layersActionBarLayout.presentFragment(params.setRemoveLast(removeLast).setNoAnimation(forceWithoutAnimation).setCheckPresentFromDelegate(false)); return false; } } else { @@ -6430,20 +6713,20 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } @Override - public boolean needAddFragmentToStack(BaseFragment fragment, ActionBarLayout layout) { + public boolean needAddFragmentToStack(BaseFragment fragment, INavigationLayout layout) { if (AndroidUtilities.isTablet()) { - drawerLayoutContainer.setAllowOpenDrawer(!(fragment instanceof LoginActivity || fragment instanceof IntroActivity || fragment instanceof CountrySelectActivity) && layersActionBarLayout.getVisibility() != View.VISIBLE, true); + drawerLayoutContainer.setAllowOpenDrawer(!(fragment instanceof LoginActivity || fragment instanceof IntroActivity || fragment instanceof CountrySelectActivity) && layersActionBarLayout.getView().getVisibility() != View.VISIBLE, true); if (fragment instanceof DialogsActivity) { DialogsActivity dialogsActivity = (DialogsActivity) fragment; if (dialogsActivity.isMainDialogList() && layout != actionBarLayout) { actionBarLayout.removeAllFragments(); actionBarLayout.addFragmentToStack(fragment); layersActionBarLayout.removeAllFragments(); - layersActionBarLayout.setVisibility(View.GONE); + layersActionBarLayout.getView().setVisibility(View.GONE); drawerLayoutContainer.setAllowOpenDrawer(true, false); if (!tabletFullSize) { shadowTabletSide.setVisibility(View.VISIBLE); - if (rightActionBarLayout.fragmentsStack.isEmpty()) { + if (rightActionBarLayout.getFragmentStack().isEmpty()) { backgroundTablet.setVisibility(View.VISIBLE); } } @@ -6451,13 +6734,13 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } } else if (fragment instanceof ChatActivity && !((ChatActivity) fragment).isInScheduleMode()) { if (!tabletFullSize && layout != rightActionBarLayout) { - rightActionBarLayout.setVisibility(View.VISIBLE); + rightActionBarLayout.getView().setVisibility(View.VISIBLE); backgroundTablet.setVisibility(View.GONE); rightActionBarLayout.removeAllFragments(); rightActionBarLayout.addFragmentToStack(fragment); - if (!layersActionBarLayout.fragmentsStack.isEmpty()) { - for (int a = 0; a < layersActionBarLayout.fragmentsStack.size() - 1; a++) { - layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.fragmentsStack.get(0)); + if (!layersActionBarLayout.getFragmentStack().isEmpty()) { + for (int a = 0; a < layersActionBarLayout.getFragmentStack().size() - 1; a++) { + layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.getFragmentStack().get(0)); a--; } layersActionBarLayout.closeLastFragment(true); @@ -6465,9 +6748,9 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar return false; } else if (tabletFullSize && layout != actionBarLayout) { actionBarLayout.addFragmentToStack(fragment); - if (!layersActionBarLayout.fragmentsStack.isEmpty()) { - for (int a = 0; a < layersActionBarLayout.fragmentsStack.size() - 1; a++) { - layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.fragmentsStack.get(0)); + if (!layersActionBarLayout.getFragmentStack().isEmpty()) { + for (int a = 0; a < layersActionBarLayout.getFragmentStack().size() - 1; a++) { + layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.getFragmentStack().get(0)); a--; } layersActionBarLayout.closeLastFragment(true); @@ -6475,9 +6758,18 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar return false; } } else if (layout != layersActionBarLayout) { - layersActionBarLayout.setVisibility(View.VISIBLE); + layersActionBarLayout.getView().setVisibility(View.VISIBLE); drawerLayoutContainer.setAllowOpenDrawer(false, true); - if (fragment instanceof LoginActivity) { + + int account = -1; + for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { + if (UserConfig.getInstance(a).isClientActivated()) { + account = a; + break; + } + } + + if (fragment instanceof LoginActivity && account == -1) { backgroundTablet.setVisibility(View.VISIBLE); shadowTabletSide.setVisibility(View.GONE); shadowTablet.setBackgroundColor(0x00000000); @@ -6504,9 +6796,9 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } @Override - public boolean needCloseLastFragment(ActionBarLayout layout) { + public boolean needCloseLastFragment(INavigationLayout layout) { if (AndroidUtilities.isTablet()) { - if (layout == actionBarLayout && layout.fragmentsStack.size() <= 1) { + if (layout == actionBarLayout && layout.getFragmentStack().size() <= 1) { onFinish(); finish(); return false; @@ -6514,18 +6806,18 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar if (!tabletFullSize) { backgroundTablet.setVisibility(View.VISIBLE); } - } else if (layout == layersActionBarLayout && actionBarLayout.fragmentsStack.isEmpty() && layersActionBarLayout.fragmentsStack.size() == 1) { + } else if (layout == layersActionBarLayout && actionBarLayout.getFragmentStack().isEmpty() && layersActionBarLayout.getFragmentStack().size() == 1) { onFinish(); finish(); return false; } } else { - if (layout.fragmentsStack.size() <= 1) { + if (layout.getFragmentStack().size() <= 1) { onFinish(); finish(); return false; } - if (layout.fragmentsStack.size() >= 2 && !(layout.fragmentsStack.get(0) instanceof LoginActivity)) { + if (layout.getFragmentStack().size() >= 2 && !(layout.getFragmentStack().get(0) instanceof LoginActivity)) { drawerLayoutContainer.setAllowOpenDrawer(true, false); } } @@ -6541,7 +6833,7 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } @Override - public void onRebuildAllFragments(ActionBarLayout layout, boolean last) { + public void onRebuildAllFragments(INavigationLayout layout, boolean last) { if (AndroidUtilities.isTablet()) { if (layout == layersActionBarLayout) { rightActionBarLayout.rebuildAllFragmentViews(last, last); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LinkEditActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LinkEditActivity.java index 4cff8d255..a63a69069 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LinkEditActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LinkEditActivity.java @@ -282,35 +282,42 @@ public class LinkEditActivity extends BaseFragment { buttonTextView.setText(LocaleController.getString("SaveLink", R.string.SaveLink)); } - approveCell = new TextCheckCell(context) { - @Override - protected void onDraw(Canvas canvas) { - canvas.save(); - canvas.clipRect(0, 0, getWidth(), getHeight()); - super.onDraw(canvas); - canvas.restore(); - } - }; - approveCell.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundUnchecked)); - approveCell.setColors(Theme.key_windowBackgroundCheckText, Theme.key_switchTrackBlue, Theme.key_switchTrackBlueChecked, Theme.key_switchTrackBlueThumb, Theme.key_switchTrackBlueThumbChecked); - approveCell.setDrawCheckRipple(true); - approveCell.setHeight(56); - approveCell.setTag(Theme.key_windowBackgroundUnchecked); - approveCell.setTextAndCheck(LocaleController.getString("ApproveNewMembers", R.string.ApproveNewMembers), false, false); - approveCell.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - approveCell.setOnClickListener(view -> { - TextCheckCell cell = (TextCheckCell) view; - boolean newIsChecked = !cell.isChecked(); - cell.setBackgroundColorAnimated(newIsChecked, Theme.getColor(newIsChecked ? Theme.key_windowBackgroundChecked : Theme.key_windowBackgroundUnchecked)); - cell.setChecked(newIsChecked); - setUsesVisible(!newIsChecked); - firstLayout = true; - }); - linearLayout.addView(approveCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 56)); + TLRPC.Chat chatLocal = getMessagesController().getChat(chatId); + boolean hasApproveCell = false; + if (chatLocal == null || chatLocal.username == null) { + hasApproveCell = true; + approveCell = new TextCheckCell(context) { + @Override + protected void onDraw(Canvas canvas) { + canvas.save(); + canvas.clipRect(0, 0, getWidth(), getHeight()); + super.onDraw(canvas); + canvas.restore(); + } + }; + approveCell.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundUnchecked)); + approveCell.setColors(Theme.key_windowBackgroundCheckText, Theme.key_switchTrackBlue, Theme.key_switchTrackBlueChecked, Theme.key_switchTrackBlueThumb, Theme.key_switchTrackBlueThumbChecked); + approveCell.setDrawCheckRipple(true); + approveCell.setHeight(56); + approveCell.setTag(Theme.key_windowBackgroundUnchecked); + approveCell.setTextAndCheck(LocaleController.getString("ApproveNewMembers", R.string.ApproveNewMembers), false, false); + approveCell.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + approveCell.setOnClickListener(view -> { + TextCheckCell cell = (TextCheckCell) view; + boolean newIsChecked = !cell.isChecked(); + cell.setBackgroundColorAnimated(newIsChecked, Theme.getColor(newIsChecked ? Theme.key_windowBackgroundChecked : Theme.key_windowBackgroundUnchecked)); + cell.setChecked(newIsChecked); + setUsesVisible(!newIsChecked); + firstLayout = true; + }); + linearLayout.addView(approveCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 56)); + } TextInfoPrivacyCell hintCell = new TextInfoPrivacyCell(context); hintCell.setBackground(Theme.getThemedDrawable(context, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); - hintCell.setText(LocaleController.getString("ApproveNewMembersDescription", R.string.ApproveNewMembersDescription)); + if (hasApproveCell) { + hintCell.setText(LocaleController.getString("ApproveNewMembersDescription", R.string.ApproveNewMembersDescription)); + } linearLayout.addView(hintCell); timeHeaderCell = new HeaderCell(context); @@ -517,7 +524,7 @@ public class LinkEditActivity extends BaseFragment { int timeIndex = timeChooseView.getSelectedIndex(); if (timeIndex < dispalyedDates.size() && dispalyedDates.get(timeIndex) < 0) { - AndroidUtilities.shakeView(timeEditText, 2, 0); + AndroidUtilities.shakeView(timeEditText); Vibrator vibrator = (Vibrator) timeEditText.getContext().getSystemService(Context.VIBRATOR_SERVICE); if (vibrator != null) { vibrator.vibrate(200); @@ -552,7 +559,7 @@ public class LinkEditActivity extends BaseFragment { req.usage_limit = 0; } - req.request_needed = approveCell.isChecked(); + req.request_needed = approveCell != null && approveCell.isChecked(); if (req.request_needed) { req.usage_limit = 0; } @@ -620,9 +627,9 @@ public class LinkEditActivity extends BaseFragment { } } - if (inviteToEdit.request_needed != approveCell.isChecked()) { + if (inviteToEdit.request_needed != (approveCell != null && approveCell.isChecked())) { req.flags |= 8; - req.request_needed = approveCell.isChecked(); + req.request_needed = (approveCell != null && approveCell.isChecked()); if (req.request_needed) { req.flags |= 2; req.usage_limit = 0; @@ -775,8 +782,10 @@ public class LinkEditActivity extends BaseFragment { chooseUses(invite.usage_limit); usesEditText.setText(Integer.toString(invite.usage_limit)); } - approveCell.setBackgroundColor(Theme.getColor(invite.request_needed ? Theme.key_windowBackgroundChecked : Theme.key_windowBackgroundUnchecked)); - approveCell.setChecked(invite.request_needed); + if (approveCell != null) { + approveCell.setBackgroundColor(Theme.getColor(invite.request_needed ? Theme.key_windowBackgroundChecked : Theme.key_windowBackgroundUnchecked)); + approveCell.setChecked(invite.request_needed); + } setUsesVisible(!invite.request_needed); if (!TextUtils.isEmpty(invite.title)) { SpannableStringBuilder builder = new SpannableStringBuilder(invite.title); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java index 545c50f62..e2a27cbf2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java @@ -2538,7 +2538,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter } @Override - protected void onBecomeFullyHidden() { + public void onBecomeFullyHidden() { if (undoView[0] != null) { undoView[0].hide(true, 0); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java index b4b1d94e0..bc31e10d4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java @@ -6082,7 +6082,7 @@ public class LoginActivity extends BaseFragment { try { codeField[num].performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } catch (Exception ignore) {} - AndroidUtilities.shakeView(codeField[num], 2, 0); + AndroidUtilities.shakeView(codeField[num]); } @Override @@ -6882,8 +6882,12 @@ public class LoginActivity extends BaseFragment { } @Override - protected AnimatorSet onCustomTransitionAnimation(boolean isOpen, Runnable callback) { + public AnimatorSet onCustomTransitionAnimation(boolean isOpen, Runnable callback) { if (isOpen && introView != null) { + if (fragmentView.getParent() instanceof View) { + ((View) fragmentView.getParent()).setTranslationX(0); + } + TransformableLoginButtonView transformButton = new TransformableLoginButtonView(fragmentView.getContext()); transformButton.setButtonText(startMessagingButton.getPaint(), startMessagingButton.getText().toString()); @@ -6901,9 +6905,9 @@ public class LoginActivity extends BaseFragment { transformButton.setTranslationX(fromX); transformButton.setTranslationY(fromY); - int toX = getParentLayout().getWidth() - floatingButtonIcon.getLayoutParams().width - ((ViewGroup.MarginLayoutParams)floatingButtonContainer.getLayoutParams()).rightMargin - getParentLayout().getPaddingLeft() - getParentLayout().getPaddingRight(), - toY = getParentLayout().getHeight() - floatingButtonIcon.getLayoutParams().height - ((ViewGroup.MarginLayoutParams)floatingButtonContainer.getLayoutParams()).bottomMargin - - (isCustomKeyboardVisible() ? AndroidUtilities.dp(CustomPhoneKeyboardView.KEYBOARD_HEIGHT_DP) : 0) - getParentLayout().getPaddingTop() - getParentLayout().getPaddingBottom(); + int toX = getParentLayout().getView().getWidth() - floatingButtonIcon.getLayoutParams().width - ((ViewGroup.MarginLayoutParams)floatingButtonContainer.getLayoutParams()).rightMargin - getParentLayout().getView().getPaddingLeft() - getParentLayout().getView().getPaddingRight(), + toY = getParentLayout().getView().getHeight() - floatingButtonIcon.getLayoutParams().height - ((ViewGroup.MarginLayoutParams)floatingButtonContainer.getLayoutParams()).bottomMargin - + (isCustomKeyboardVisible() ? AndroidUtilities.dp(CustomPhoneKeyboardView.KEYBOARD_HEIGHT_DP) : 0) - getParentLayout().getView().getPaddingTop() - getParentLayout().getView().getPaddingBottom(); ValueAnimator animator = ValueAnimator.ofFloat(0, 1); animator.addListener(new AnimatorListenerAdapter() { @@ -7210,6 +7214,7 @@ public class LoginActivity extends BaseFragment { Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); canvas.scale(1.0f / scaleFactor, 1.0f / scaleFactor); + canvas.drawColor(Theme.getColor(Theme.key_windowBackgroundWhite)); fragmentView.draw(canvas); Utilities.stackBlurBitmap(bitmap, Math.max(8, Math.max(w, h) / 150)); blurredView.setBackground(new BitmapDrawable(getContext().getResources(), bitmap)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ManageLinksActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ManageLinksActivity.java index 72524c5fe..d45ac1951 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ManageLinksActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ManageLinksActivity.java @@ -661,7 +661,7 @@ public class ManageLinksActivity extends BaseFragment { info = chatFull; this.invite = (TLRPC.TL_chatInviteExported) invite; - isPublic = !TextUtils.isEmpty(currentChat.username); + isPublic = ChatObject.isPublic(currentChat); loadLinks(true); } @@ -816,7 +816,7 @@ public class ManageLinksActivity extends BaseFragment { linkActionView.setCanEdit(adminId == getAccountInstance().getUserConfig().clientUserId); if (isPublic && adminId == getAccountInstance().getUserConfig().clientUserId) { if (info != null) { - linkActionView.setLink("https://t.me/" + currentChat.username); + linkActionView.setLink("https://t.me/" + ChatObject.getPublicUsername(currentChat)); linkActionView.setUsers(0, null); linkActionView.hideRevokeOption(true); } @@ -1683,7 +1683,7 @@ public class ManageLinksActivity extends BaseFragment { int animationIndex = -1; @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { super.onTransitionAnimationEnd(isOpen, backward); if (isOpen) { isOpened = true; @@ -1695,7 +1695,7 @@ public class ManageLinksActivity extends BaseFragment { } @Override - protected void onTransitionAnimationStart(boolean isOpen, boolean backward) { + public void onTransitionAnimationStart(boolean isOpen, boolean backward) { super.onTransitionAnimationStart(isOpen, backward); animationIndex = NotificationCenter.getInstance(currentAccount).setAnimationInProgress(animationIndex, null); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/MessageSeenView.java b/TMessagesProj/src/main/java/org/telegram/ui/MessageSeenView.java index 0b8e2ab92..42dfe1c92 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/MessageSeenView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/MessageSeenView.java @@ -30,6 +30,7 @@ import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.SimpleTextView; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.AvatarsDarawable; @@ -48,7 +49,7 @@ public class MessageSeenView extends FrameLayout { ArrayList peerIds = new ArrayList<>(); public ArrayList users = new ArrayList<>(); AvatarsImageView avatarsImageView; - TextView titleView; + SimpleTextView titleView; ImageView iconView; int currentAccount; boolean isVoice; @@ -65,17 +66,12 @@ public class MessageSeenView extends FrameLayout { flickerLoadingView.setIsSingleCell(false); addView(flickerLoadingView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT)); - titleView = new TextView(context) { - @Override - public void setText(CharSequence text, BufferType type) { - super.setText(text, type); - } - }; - titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - titleView.setLines(1); - titleView.setEllipsize(TextUtils.TruncateAt.END); + titleView = new SimpleTextView(context); + titleView.setTextSize(16); + titleView.setEllipsizeByGradient(true); + titleView.setRightPadding(AndroidUtilities.dp(62)); - addView(titleView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.CENTER_VERTICAL, 40, 0, 62, 0)); + addView(titleView, LayoutHelper.createFrame(0, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.CENTER_VERTICAL, 40, 0, 0, 0)); avatarsImageView = new AvatarsImageView(context, false); avatarsImageView.setStyle(AvatarsDarawable.STYLE_MESSAGE_SEEN); @@ -195,17 +191,22 @@ public class MessageSeenView extends FrameLayout { if (parent != null && parent.getWidth() > 0) { widthMeasureSpec = MeasureSpec.makeMeasureSpec(parent.getWidth(), MeasureSpec.EXACTLY); } - if (flickerLoadingView.getVisibility() == View.VISIBLE) { - ignoreLayout = true; + ignoreLayout = true; + boolean measureFlicker = flickerLoadingView.getVisibility() == View.VISIBLE; + titleView.setVisibility(View.GONE); + if (measureFlicker) { flickerLoadingView.setVisibility(View.GONE); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (measureFlicker) { flickerLoadingView.getLayoutParams().width = getMeasuredWidth(); flickerLoadingView.setVisibility(View.VISIBLE); - ignoreLayout = false; - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } else { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + titleView.setVisibility(View.VISIBLE); + titleView.getLayoutParams().width = getMeasuredWidth() - AndroidUtilities.dp(40); + ignoreLayout = false; + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); } private void updateView() { @@ -225,12 +226,7 @@ public class MessageSeenView extends FrameLayout { avatarsImageView.setTranslationX(0); } - int newRightMargin = AndroidUtilities.dp(users.size() == 0 ? 8 : 62); - MarginLayoutParams titleViewMargins = (MarginLayoutParams) titleView.getLayoutParams(); - if (titleViewMargins.rightMargin != newRightMargin) { - titleViewMargins.rightMargin = newRightMargin; - titleView.setLayoutParams(titleViewMargins); - } + titleView.setRightPadding(AndroidUtilities.dp(8 + 24 + Math.min(2, users.size() - 1) * 12 + 6)); avatarsImageView.commitTransition(false); if (peerIds.size() == 1 && users.get(0) != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/MessageStatisticActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/MessageStatisticActivity.java index 9a062decf..138c8737a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/MessageStatisticActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/MessageStatisticActivity.java @@ -8,6 +8,9 @@ package org.telegram.ui; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.content.Context; @@ -27,6 +30,11 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import androidx.collection.ArraySet; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.SimpleItemAnimator; + import org.json.JSONException; import org.json.JSONObject; import org.telegram.messenger.AndroidUtilities; @@ -73,14 +81,6 @@ import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; -import androidx.collection.ArraySet; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.SimpleItemAnimator; - -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; - public class MessageStatisticActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { private TLRPC.ChatFull chat; @@ -444,8 +444,8 @@ public class MessageStatisticActivity extends BaseFragment implements Notificati actionBar.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); avatarContainer.setOnClickListener(view -> { - if (getParentLayout().fragmentsStack.size() > 1) { - BaseFragment previousFragemnt = getParentLayout().fragmentsStack.get(getParentLayout().fragmentsStack.size() - 2); + if (getParentLayout().getFragmentStack().size() > 1) { + BaseFragment previousFragemnt = getParentLayout().getFragmentStack().get(getParentLayout().getFragmentStack().size() - 2); if (previousFragemnt instanceof ChatActivity && ((ChatActivity) previousFragemnt).getCurrentChat().id == chatId) { finishFragment(); return; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java index 6fc1f988f..5e13cd952 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java @@ -117,7 +117,7 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt if (v != null) { v.vibrate(200); } - AndroidUtilities.shakeView(firstNameField, 2, 0); + AndroidUtilities.shakeView(firstNameField); return; } if (codeField.length() == 0) { @@ -125,7 +125,7 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt if (v != null) { v.vibrate(200); } - AndroidUtilities.shakeView(codeField, 2, 0); + AndroidUtilities.shakeView(codeField); return; } if (phoneField.length() == 0) { @@ -133,7 +133,7 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt if (v != null) { v.vibrate(200); } - AndroidUtilities.shakeView(phoneField, 2, 0); + AndroidUtilities.shakeView(phoneField); return; } donePressed = true; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsCustomSettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsCustomSettingsActivity.java index c58a91638..4fadc14d1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsCustomSettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsCustomSettingsActivity.java @@ -110,6 +110,8 @@ public class NotificationsCustomSettingsActivity extends BaseFragment implements private ArrayList exceptions; private HashMap exceptionsDict = new HashMap<>(); + int topicId = 0; + public NotificationsCustomSettingsActivity(int type, ArrayList notificationExceptions) { this(type, notificationExceptions, false); } @@ -275,9 +277,10 @@ public class NotificationsCustomSettingsActivity extends BaseFragment implements ChatNotificationsPopupWrapper chatNotificationsPopupWrapper = new ChatNotificationsPopupWrapper(context, currentAccount, null, true, true, new ChatNotificationsPopupWrapper.Callback() { @Override public void toggleSound() { + String key = NotificationsController.getSharedPrefKey(did, topicId); SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); - boolean enabled = !preferences.getBoolean("sound_enabled_" + did, true); - preferences.edit().putBoolean("sound_enabled_" + did, enabled).apply(); + boolean enabled = !preferences.getBoolean("sound_enabled_" + key, true); + preferences.edit().putBoolean("sound_enabled_" + key, enabled).apply(); if (BulletinFactory.canShowBulletin(NotificationsCustomSettingsActivity.this)) { BulletinFactory.createSoundEnabledBulletin(NotificationsCustomSettingsActivity.this, enabled ? NotificationsController.SETTING_SOUND_ON : NotificationsController.SETTING_SOUND_OFF, getResourceProvider()).show(); } @@ -286,14 +289,14 @@ public class NotificationsCustomSettingsActivity extends BaseFragment implements @Override public void muteFor(int timeInSeconds) { if (timeInSeconds == 0) { - if (getMessagesController().isDialogMuted(did)) { + if (getMessagesController().isDialogMuted(did, topicId)) { toggleMute(); } if (BulletinFactory.canShowBulletin(NotificationsCustomSettingsActivity.this)) { BulletinFactory.createMuteBulletin(NotificationsCustomSettingsActivity.this, NotificationsController.SETTING_MUTE_UNMUTE, timeInSeconds, getResourceProvider()).show(); } } else { - getNotificationsController().muteUntil(did, timeInSeconds); + getNotificationsController().muteUntil(did, topicId, timeInSeconds); if (BulletinFactory.canShowBulletin(NotificationsCustomSettingsActivity.this)) { BulletinFactory.createMuteBulletin(NotificationsCustomSettingsActivity.this, NotificationsController.SETTING_MUTE_CUSTOM, timeInSeconds, getResourceProvider()).show(); } @@ -322,14 +325,14 @@ public class NotificationsCustomSettingsActivity extends BaseFragment implements @Override public void toggleMute() { - boolean muted = getMessagesController().isDialogMuted(did); - getNotificationsController().muteDialog(did, !muted); - BulletinFactory.createMuteBulletin(NotificationsCustomSettingsActivity.this, getMessagesController().isDialogMuted(did), null).show(); + boolean muted = getMessagesController().isDialogMuted(did, topicId); + getNotificationsController().muteDialog(did, topicId, !muted); + BulletinFactory.createMuteBulletin(NotificationsCustomSettingsActivity.this, getMessagesController().isDialogMuted(did, topicId), null).show(); update(); } private void update() { - if (getMessagesController().isDialogMuted(did) != defaultEnabled) { + if (getMessagesController().isDialogMuted(did, topicId) != defaultEnabled) { setDefault(); } else { setNotDefault(); @@ -384,7 +387,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment implements actionBar.closeSearchField(); } }, getResourceProvider()); - chatNotificationsPopupWrapper.update(did); + chatNotificationsPopupWrapper.update(did, topicId, null); chatNotificationsPopupWrapper.showAsOptions(NotificationsCustomSettingsActivity.this, view, x, y); return; } @@ -402,7 +405,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment implements DialogsActivity activity = new DialogsActivity(args); activity.setDelegate((fragment, dids, message, param) -> { Bundle args2 = new Bundle(); - args2.putLong("dialog_id", dids.get(0)); + args2.putLong("dialog_id", dids.get(0).dialogId); args2.putBoolean("exception", true); ProfileNotificationsActivity profileNotificationsActivity = new ProfileNotificationsActivity(args2, getResourceProvider()); profileNotificationsActivity.setDelegate(exception -> { @@ -431,7 +434,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment implements editor.commit(); for (int a = 0, N = exceptions.size(); a < N; a++) { NotificationsSettingsActivity.NotificationException exception = exceptions.get(a); - getNotificationsController().updateServerNotificationsSettings(exception.did, false); + getNotificationsController().updateServerNotificationsSettings(exception.did, topicId, false); } exceptions.clear(); @@ -459,7 +462,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment implements } checkRowsEnabled(); } else { - AlertsCreator.showCustomNotificationsDialog(NotificationsCustomSettingsActivity.this, 0, currentType, exceptions, currentAccount, param -> { + AlertsCreator.showCustomNotificationsDialog(NotificationsCustomSettingsActivity.this, 0, 0, currentType, exceptions, currentAccount, param -> { int offUntil; SharedPreferences preferences = getNotificationsSettings(); if (currentType == NotificationsController.TYPE_PRIVATE) { @@ -551,7 +554,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment implements if (!view.isEnabled()) { return; } - showDialog(AlertsCreator.createColorSelectDialog(getParentActivity(), 0, currentType, () -> { + showDialog(AlertsCreator.createColorSelectDialog(getParentActivity(), 0, 0, currentType, () -> { RecyclerView.ViewHolder holder = listView.findViewHolderForAdapterPosition(position); if (holder != null) { adapter.onBindViewHolder(holder, position); @@ -579,7 +582,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment implements } else { key = "vibrate_channel"; } - showDialog(AlertsCreator.createVibrationSelectDialog(getParentActivity(), 0, key, () -> { + showDialog(AlertsCreator.createVibrationSelectDialog(getParentActivity(), 0, 0, key, () -> { RecyclerView.ViewHolder holder = listView.findViewHolderForAdapterPosition(position); if (holder != null) { adapter.onBindViewHolder(holder, position); @@ -589,7 +592,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment implements if (!view.isEnabled()) { return; } - showDialog(AlertsCreator.createPrioritySelectDialog(getParentActivity(), 0, currentType, () -> { + showDialog(AlertsCreator.createPrioritySelectDialog(getParentActivity(), 0, 0, currentType, () -> { RecyclerView.ViewHolder holder = listView.findViewHolderForAdapterPosition(position); if (holder != null) { adapter.onBindViewHolder(holder, position); @@ -1046,7 +1049,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment implements continue; } names[0] = chat.title; - names[1] = chat.username; + names[1] = ChatObject.getPublicUsername(chat); object = chat; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java index 86d61b3d6..96b7931b5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java @@ -124,6 +124,10 @@ public class NotificationsSettingsActivity extends BaseFragment implements Notif private int resetNotificationsSectionRow; private int rowCount = 0; + private boolean updateVibrate; + private boolean updateRingtone; + private boolean updateRepeatNotifications; + @Override public boolean onFragmentCreate() { MessagesController.getInstance(currentAccount).loadSignUpNotificationsSettings(); @@ -210,6 +214,10 @@ public class NotificationsSettingsActivity extends BaseFragment implements Notif String key = entry.getKey(); if (key.startsWith("notify2_")) { key = key.replace("notify2_", ""); + if (key.contains("_")) { + //it's topic + continue; + } long did = Utilities.parseLong(key); if (did != 0 && did != selfId) { @@ -586,7 +594,10 @@ public class NotificationsSettingsActivity extends BaseFragment implements Notif if (position == callsVibrateRow) { key = "vibrate_calls"; } - showDialog(AlertsCreator.createVibrationSelectDialog(getParentActivity(), 0, key, () -> adapter.notifyItemChanged(position))); + showDialog(AlertsCreator.createVibrationSelectDialog(getParentActivity(), 0, 0, key, () -> { + updateVibrate = true; + adapter.notifyItemChanged(position); + })); } else if (position == repeatRow) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("RepeatNotifications", R.string.RepeatNotifications)); @@ -615,6 +626,7 @@ public class NotificationsSettingsActivity extends BaseFragment implements Notif } SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); preferences.edit().putInt("repeat_messages", minutes).commit(); + updateRepeatNotifications = true; adapter.notifyItemChanged(position); }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); @@ -664,6 +676,7 @@ public class NotificationsSettingsActivity extends BaseFragment implements Notif editor.putString("CallsRingtone", "NoSound"); editor.putString("CallsRingtonePath", "NoSound"); } + updateRingtone = true; } editor.commit(); adapter.notifyItemChanged(requestCode); @@ -912,20 +925,22 @@ public class NotificationsSettingsActivity extends BaseFragment implements Notif if (value.equals("NoSound")) { value = LocaleController.getString("NoSound", R.string.NoSound); } - textCell.setTextAndValue(LocaleController.getString("VoipSettingsRingtone", R.string.VoipSettingsRingtone), value, false); + textCell.setTextAndValue(LocaleController.getString("VoipSettingsRingtone", R.string.VoipSettingsRingtone), value, updateRingtone, false); + updateRingtone = false; } else if (position == callsVibrateRow) { int value = preferences.getInt("vibrate_calls", 0); if (value == 0) { - textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("VibrationDefault", R.string.VibrationDefault), true); + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("VibrationDefault", R.string.VibrationDefault), updateVibrate, true); } else if (value == 1) { - textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("Short", R.string.Short), true); + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("Short", R.string.Short), updateVibrate, true); } else if (value == 2) { - textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("VibrationDisabled", R.string.VibrationDisabled), true); + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("VibrationDisabled", R.string.VibrationDisabled), updateVibrate, true); } else if (value == 3) { - textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("Long", R.string.Long), true); + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("Long", R.string.Long), updateVibrate, true); } else if (value == 4) { - textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("OnlyIfSilent", R.string.OnlyIfSilent), true); + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("OnlyIfSilent", R.string.OnlyIfSilent), updateVibrate, true); } + updateVibrate = false; } else if (position == repeatRow) { int minutes = preferences.getInt("repeat_messages", 60); String value; @@ -936,7 +951,8 @@ public class NotificationsSettingsActivity extends BaseFragment implements Notif } else { value = LocaleController.formatPluralString("Hours", minutes / 60); } - textCell.setTextAndValue(LocaleController.getString("RepeatNotifications", R.string.RepeatNotifications), value, false); + textCell.setTextAndValue(LocaleController.getString("RepeatNotifications", R.string.RepeatNotifications), value, updateRepeatNotifications, false); + updateRepeatNotifications = false; } break; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSoundActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSoundActivity.java index 961f54ad3..1e83071ca 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSoundActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSoundActivity.java @@ -59,6 +59,7 @@ import org.telegram.ui.Components.ChatAttachAlertDocumentLayout; import org.telegram.ui.Components.ChatAvatarContainer; import org.telegram.ui.Components.CheckBox2; import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.Forum.ForumUtilities; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.NumberTextView; import org.telegram.ui.Components.RadioButton; @@ -114,6 +115,8 @@ public class NotificationsSoundActivity extends BaseFragment implements ChatAtta private final int tonesStreamType = AudioManager.STREAM_ALARM; + int topicId = 0; + public NotificationsSoundActivity(Bundle args) { this(args, null); } @@ -127,13 +130,15 @@ public class NotificationsSoundActivity extends BaseFragment implements ChatAtta public boolean onFragmentCreate() { if (getArguments() != null) { dialogId = getArguments().getLong("dialog_id", 0); + topicId = getArguments().getInt("topic_id", 0); currentType = getArguments().getInt("type", -1); } String prefPath; String prefDocId; if (dialogId != 0) { - prefDocId = "sound_document_id_" + dialogId; - prefPath = "sound_path_" + dialogId; + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); + prefDocId = "sound_document_id_" + key; + prefPath = "sound_path_" + key; } else { if (currentType == NotificationsController.TYPE_PRIVATE) { prefPath = "GlobalSoundPath"; @@ -291,9 +296,15 @@ public class NotificationsSoundActivity extends BaseFragment implements ChatAtta avatarContainer.setOccupyStatusBar(!AndroidUtilities.isTablet()); actionBar.addView(avatarContainer, 0, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, !inPreviewMode ? 56 : 0, 0, 40, 0)); if (dialogId < 0) { - TLRPC.Chat chatLocal = getMessagesController().getChat(-dialogId); - avatarContainer.setChatAvatar(chatLocal); - avatarContainer.setTitle(chatLocal.title); + if (topicId != 0) { + TLRPC.TL_forumTopic forumTopic = getMessagesController().getTopicsController().findTopic(-dialogId, topicId); + ForumUtilities.setTopicIcon(avatarContainer.getAvatarImageView(), forumTopic); + avatarContainer.setTitle(forumTopic.title); + } else { + TLRPC.Chat chatLocal = getMessagesController().getChat(-dialogId); + avatarContainer.setChatAvatar(chatLocal); + avatarContainer.setTitle(chatLocal.title); + } } else { TLRPC.User user = getMessagesController().getUser(dialogId); if (user != null) { @@ -830,10 +841,10 @@ public class NotificationsSoundActivity extends BaseFragment implements ChatAtta String prefDocId; if (dialogId != 0) { - prefName = "sound_" + dialogId; - prefPath = "sound_path_" + dialogId; - prefDocId = "sound_document_id_" + dialogId; - editor.putBoolean("sound_enabled_" + dialogId, true); + prefName = "sound_" + NotificationsController.getSharedPrefKey(dialogId, topicId); + prefPath = "sound_path_" + NotificationsController.getSharedPrefKey(dialogId, topicId); + prefDocId = "sound_document_id_" + NotificationsController.getSharedPrefKey(dialogId, topicId); + editor.putBoolean("sound_enabled_" + NotificationsController.getSharedPrefKey(dialogId, topicId), true); } else { if (currentType == NotificationsController.TYPE_PRIVATE) { prefName = "GlobalSound"; @@ -872,7 +883,7 @@ public class NotificationsSoundActivity extends BaseFragment implements ChatAtta editor.apply(); if (dialogId != 0) { - getNotificationsController().updateServerNotificationsSettings(dialogId); + getNotificationsController().updateServerNotificationsSettings(dialogId, topicId); } else { getNotificationsController().updateServerNotificationsSettings(currentType); getNotificationCenter().postNotificationName(NotificationCenter.notificationsSettingsUpdated); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PassportActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PassportActivity.java index 1c2fabb35..b4141ab73 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PassportActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PassportActivity.java @@ -2233,7 +2233,7 @@ public class PassportActivity extends BaseFragment implements NotificationCenter if (v != null) { v.vibrate(200); } - AndroidUtilities.shakeView(getViewByType(requiredType), 2, 0); + AndroidUtilities.shakeView(getViewByType(requiredType)); return; } String key = getNameForType(requiredType.type); @@ -2243,7 +2243,7 @@ public class PassportActivity extends BaseFragment implements NotificationCenter if (v != null) { v.vibrate(200); } - AndroidUtilities.shakeView(getViewByType(requiredType), 2, 0); + AndroidUtilities.shakeView(getViewByType(requiredType)); return; } valuesToSend.add(new ValueToSend(value, requiredType.selfie_required, requiredType.translation_required)); @@ -3579,7 +3579,7 @@ public class PassportActivity extends BaseFragment implements NotificationCenter if (v != null) { v.vibrate(200); } - AndroidUtilities.shakeView(field, 2, 0); + AndroidUtilities.shakeView(field); scrollToField(field); } @@ -6262,7 +6262,7 @@ public class PassportActivity extends BaseFragment implements NotificationCenter if (clear) { inputFields[FIELD_PASSWORD].setText(""); } - AndroidUtilities.shakeView(inputFields[FIELD_PASSWORD], 2, 0); + AndroidUtilities.shakeView(inputFields[FIELD_PASSWORD]); } private void startPhoneVerification(boolean checkPermissions, final String phone, Runnable finishRunnable, ErrorRunnable errorRunnable, final PassportActivityDelegate delegate) { @@ -7772,7 +7772,7 @@ public class PassportActivity extends BaseFragment implements NotificationCenter code = getCode(); } if (TextUtils.isEmpty(code)) { - AndroidUtilities.shakeView(codeFieldContainer, 2, 0); + AndroidUtilities.shakeView(codeFieldContainer); return; } nextPressed = true; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PaymentFormActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PaymentFormActivity.java index 899fc6454..f124727cb 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PaymentFormActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PaymentFormActivity.java @@ -109,12 +109,12 @@ import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; -import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.EditTextSettingsCell; @@ -3063,16 +3063,16 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } public int getOtherSameFragmentDiff() { - if (parentLayout == null || parentLayout.fragmentsStack == null) { + if (parentLayout == null || parentLayout.getFragmentStack() == null) { return 0; } - int cur = parentLayout.fragmentsStack.indexOf(this); + int cur = parentLayout.getFragmentStack().indexOf(this); if (cur == -1) { - cur = parentLayout.fragmentsStack.size(); + cur = parentLayout.getFragmentStack().size(); } int i = cur; - for (int a = 0; a < parentLayout.fragmentsStack.size(); a++) { - BaseFragment fragment = parentLayout.fragmentsStack.get(a); + for (int a = 0; a < parentLayout.getFragmentStack().size(); a++) { + BaseFragment fragment = parentLayout.getFragmentStack().get(a); if (fragment instanceof PaymentFormActivity) { i = a; break; @@ -3124,7 +3124,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } @Override - protected void onBecomeFullyVisible() { + public void onBecomeFullyVisible() { super.onBecomeFullyVisible(); if (currentStep == STEP_CHECKOUT) { @@ -3136,7 +3136,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen && !backward) { if (webView != null) { if (currentStep != STEP_CHECKOUT) { @@ -3372,10 +3372,10 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen } } - private boolean onCheckoutSuccess(ActionBarLayout parentLayout, Activity parentActivity) { + private boolean onCheckoutSuccess(INavigationLayout parentLayout, Activity parentActivity) { if (botUser.username != null && botUser.username.equalsIgnoreCase(getMessagesController().premiumBotUsername) && invoiceSlug == null || invoiceSlug != null && getMessagesController().premiumInvoiceSlug != null && Objects.equals(invoiceSlug, getMessagesController().premiumInvoiceSlug)) { if (parentLayout != null) { - for (BaseFragment fragment : new ArrayList<>(parentLayout.fragmentsStack)) { + for (BaseFragment fragment : new ArrayList<>(parentLayout.getFragmentStack())) { if (fragment instanceof ChatActivity || fragment instanceof PremiumPreviewFragment) { fragment.removeSelfFromStack(); } @@ -4030,7 +4030,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen doneItem.getContentView().setVisibility(View.INVISIBLE); } - ActionBarLayout parentLayout = getParentLayout(); + INavigationLayout parentLayout = getParentLayout(); Activity parentActivity = getParentActivity(); getMessagesController().newMessageCallback = message -> { if (MessageObject.getPeerId(message.peer_id) == botUser.id && message.action instanceof TLRPC.TL_messageActionPaymentSent) { @@ -4089,7 +4089,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen try { view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } catch (Exception ignored) {} - AndroidUtilities.shakeView(view, 2, 0); + AndroidUtilities.shakeView(view); } private void setDonePressed(boolean value) { @@ -4123,7 +4123,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen try { inputFields[FIELD_SAVEDPASSWORD].performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } catch (Exception ignored) {} - AndroidUtilities.shakeView(inputFields[FIELD_SAVEDPASSWORD], 2, 0); + AndroidUtilities.shakeView(inputFields[FIELD_SAVEDPASSWORD]); return; } final String password = inputFields[FIELD_SAVEDPASSWORD].getText().toString(); @@ -4168,7 +4168,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen try { inputFields[FIELD_SAVEDPASSWORD].performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } catch (Exception ignored) {} - AndroidUtilities.shakeView(inputFields[FIELD_SAVEDPASSWORD], 2, 0); + AndroidUtilities.shakeView(inputFields[FIELD_SAVEDPASSWORD]); inputFields[FIELD_SAVEDPASSWORD].setText(""); } else { AlertsCreator.processError(currentAccount, error1, PaymentFormActivity.this, req1); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PeopleNearbyActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PeopleNearbyActivity.java index 74af6ac94..ea07f3af1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PeopleNearbyActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PeopleNearbyActivity.java @@ -701,7 +701,7 @@ public class PeopleNearbyActivity extends BaseFragment implements NotificationCe } @Override - protected void onBecomeFullyHidden() { + public void onBecomeFullyHidden() { super.onBecomeFullyHidden(); if (undoView != null) { undoView.hide(true, 0); @@ -728,7 +728,7 @@ public class PeopleNearbyActivity extends BaseFragment implements NotificationCe } @Override - protected void onBecomeFullyVisible() { + public void onBecomeFullyVisible() { super.onBecomeFullyVisible(); groupCreateActivity = null; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java index 82adcb41c..4cbae925d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java @@ -799,7 +799,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("ClearSearchAlertTitle", R.string.ClearSearchAlertTitle)); builder.setMessage(LocaleController.getString("ClearSearchAlert", R.string.ClearSearchAlert)); - builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton).toUpperCase(), (dialogInterface, i) -> { + builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton), (dialogInterface, i) -> { if (searchDelegate != null) { searchDelegate.shouldClearRecentSearch(); } else { @@ -900,7 +900,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen public void onStartStopSelection(boolean start) { alertOnlyOnce = start ? 1 : 0; if (start) { - parentLayout.requestDisallowInterceptTouchEvent(true); + parentLayout.getView().requestDisallowInterceptTouchEvent(true); } listView.hideSelector(true); } @@ -1188,7 +1188,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen allowIndices = (selectedAlbum != null || type == 0 || type == 1) && allowOrder; listView.setEmptyView(emptyView); - listView.setAnimateEmptyView(true, 0); + listView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); updatePhotosButton(0); return fragmentView; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java index 524a2981c..4dfbb0865 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java @@ -695,8 +695,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (DialogObject.isChatDialog(dialogId)) { TLRPC.Chat currentChat = MessagesController.getInstance(currentAccount).getChat(-dialogId); - if (currentChat != null && currentChat.username != null) { - url1 = "https://t.me/" + currentChat.username + "/" + messageId + "?t=" + finalTimestamp; + String username = ChatObject.getPublicUsername(currentChat); + if (username != null) { + url1 = "https://t.me/" + username + "/" + messageId + "?t=" + finalTimestamp; } } else { TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(dialogId); @@ -1158,6 +1159,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private boolean canEditAvatar; private boolean isEvent; private int sharedMediaType; + private int topicId; private long currentDialogId; private long mergeDialogId; private int totalImagesCount; @@ -3360,17 +3362,18 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } else if (id == NotificationCenter.mediaCountDidLoad) { long uid = (Long) args[0]; - if (uid == currentDialogId || uid == mergeDialogId) { + int topicId = (Integer) args[1]; + if (this.topicId == topicId && (uid == currentDialogId || uid == mergeDialogId)) { if (currentMessageObject == null || MediaDataController.getMediaType(currentMessageObject.messageOwner) == sharedMediaType) { if (uid == currentDialogId) { - totalImagesCount = (Integer) args[1]; + totalImagesCount = (Integer) args[2]; } else { - totalImagesCountMerge = (Integer) args[1]; + totalImagesCountMerge = (Integer) args[2]; } if (needSearchImageInArr && isFirstLoading) { isFirstLoading = false; loadingMoreImages = true; - MediaDataController.getInstance(currentAccount).loadMedia(currentDialogId, 20, 0, 0, sharedMediaType, 1, classGuid, 0); + MediaDataController.getInstance(currentAccount).loadMedia(currentDialogId, 20, 0, 0, sharedMediaType, topicId, 1, classGuid, 0); } else if (!imagesArr.isEmpty()) { if (opennedFromMedia) { actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, startOffset + currentIndex + 1, totalImagesCount + totalImagesCountMerge)); @@ -3465,7 +3468,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (!endReached[loadIndex]) { loadingMoreImages = true; - MediaDataController.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 40, loadFromMaxId, 0, sharedMediaType, 1, classGuid, 0); + MediaDataController.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 40, loadFromMaxId, 0, sharedMediaType, topicId, 1, classGuid, 0); } } } else { @@ -4046,30 +4049,115 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat return; } - File f = null; - final boolean isVideo; - if (currentMessageObject != null) { - if (MessageObject.getMedia(currentMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaWebPage && MessageObject.getMedia(currentMessageObject.messageOwner).webpage != null && MessageObject.getMedia(currentMessageObject.messageOwner).webpage.document == null) { - TLObject fileLocation = getFileLocation(currentIndex, null); - f = FileLoader.getInstance(currentAccount).getPathToAttach(fileLocation, true); - } else { - f = FileLoader.getInstance(currentAccount).getPathToMessage(currentMessageObject.messageOwner); - } - isVideo = currentMessageObject.isVideo(); - } else if (currentFileLocationVideo != null) { - f = FileLoader.getInstance(currentAccount).getPathToAttach(getFileLocation(currentFileLocationVideo), getFileLocationExt(currentFileLocationVideo), avatarsDialogId != 0 || isEvent); - isVideo = false; - } else if (pageBlocksAdapter != null) { - f = pageBlocksAdapter.getFile(currentIndex); - isVideo = pageBlocksAdapter.isVideo(currentIndex); + ArrayList msgs = new ArrayList<>(1); + MessageObject.GroupedMessages group = parentChatActivity != null ? parentChatActivity.getGroup(currentMessageObject.getGroupId()) : null; + if (group != null) { + msgs.addAll(group.messages); } else { - isVideo = false; + msgs.add(currentMessageObject); } - if (f != null && f.exists()) { - MediaController.saveFile(f.toString(), parentActivity, isVideo ? 1 : 0, null, null, () -> BulletinFactory.createSaveToGalleryBulletin(containerView, isVideo, 0xf9222222, 0xffffffff).show()); + if (msgs.size() <= 1) { + File f = null; + final boolean isVideo; + if (currentMessageObject != null) { + if (MessageObject.getMedia(currentMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaWebPage && MessageObject.getMedia(currentMessageObject.messageOwner).webpage != null && MessageObject.getMedia(currentMessageObject.messageOwner).webpage.document == null) { + TLObject fileLocation = getFileLocation(currentIndex, null); + f = FileLoader.getInstance(currentAccount).getPathToAttach(fileLocation, true); + } else { + f = FileLoader.getInstance(currentAccount).getPathToMessage(currentMessageObject.messageOwner); + } + isVideo = currentMessageObject.isVideo(); + } else if (currentFileLocationVideo != null) { + f = FileLoader.getInstance(currentAccount).getPathToAttach(getFileLocation(currentFileLocationVideo), getFileLocationExt(currentFileLocationVideo), avatarsDialogId != 0 || isEvent); + isVideo = false; + } else if (pageBlocksAdapter != null) { + f = pageBlocksAdapter.getFile(currentIndex); + isVideo = pageBlocksAdapter.isVideo(currentIndex); + } else { + isVideo = false; + } + + if (f != null && f.exists()) { + MediaController.saveFile(f.toString(), parentActivity, isVideo ? 1 : 0, null, null, () -> BulletinFactory.createSaveToGalleryBulletin(containerView, isVideo, 0xf9222222, 0xffffffff).show()); + } else { + showDownloadAlert(); + } } else { - showDownloadAlert(); + boolean isVideo_ = false; + for (int i = 0; i < msgs.size() && !isVideo_; ++i) { + if (msgs.get(i).isVideo()) { + isVideo_ = true; + } + } + final boolean isVideo = isVideo_; + AlertDialog dialog = new AlertDialog.Builder(parentActivity, resourcesProvider) + .setTitle(LocaleController.getString("SaveGroupMedia", R.string.SaveGroupMedia)) + .setMessage(LocaleController.getString("SaveGroupMediaMessage", R.string.SaveGroupMediaMessage)) + .setDialogButtonColorKey(Theme.key_voipgroup_listeningText) + .setNegativeButton((!isVideo ? LocaleController.getString("ThisPhoto", R.string.ThisPhoto) : LocaleController.getString("ThisMedia", R.string.ThisMedia)), (di, a) -> { + if (currentMessageObject == null) { + return; + } + + File f; + if (MessageObject.getMedia(currentMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaWebPage && MessageObject.getMedia(currentMessageObject.messageOwner).webpage != null && MessageObject.getMedia(currentMessageObject.messageOwner).webpage.document == null) { + TLObject fileLocation = getFileLocation(currentIndex, null); + f = FileLoader.getInstance(currentAccount).getPathToAttach(fileLocation, true); + } else { + f = FileLoader.getInstance(currentAccount).getPathToMessage(currentMessageObject.messageOwner); + } + boolean isThisVideo = currentMessageObject.isVideo(); + + if (f != null && f.exists()) { + MediaController.saveFile(f.toString(), parentActivity, isThisVideo ? 1 : 0, null, null, () -> BulletinFactory.createSaveToGalleryBulletin(containerView, isThisVideo, 0xf9222222, 0xffffffff).show()); + } else { + showDownloadAlert(); + } + }) + .setPositiveButton(!isVideo ? LocaleController.formatPluralString("AllNPhotos", msgs.size()) : LocaleController.formatPluralString("AllNMedia", msgs.size()), (di, a) -> { + final int[] count = new int[1]; + final int[] done = new int[1]; + Runnable bulletin = () -> { + done[0]++; + if (done[0] == count[0]) { + BulletinFactory.createSaveToGalleryBulletin(containerView, count[0], isVideo, 0xf9222222, 0xffffffff).show(); + } + }; + for (int i = 0; i < msgs.size(); ++i) { + MessageObject msg = msgs.get(i); + if (msg == null) { + continue; + } + + File f; + if (MessageObject.getMedia(msg.messageOwner) instanceof TLRPC.TL_messageMediaWebPage && MessageObject.getMedia(msg.messageOwner).webpage != null && MessageObject.getMedia(msg.messageOwner).webpage.document == null) { + f = FileLoader.getInstance(currentAccount).getPathToAttach(getFileLocation(currentIndex, null), true); + } else { + f = FileLoader.getInstance(currentAccount).getPathToMessage(msg.messageOwner); + } + boolean isThisVideo = msg.isVideo(); + + if (f != null && f.exists()) { + count[0]++; + MediaController.saveFile(f.toString(), parentActivity, isThisVideo ? 1 : 0, null, null, () -> AndroidUtilities.runOnUIThread(bulletin)); + } + } + }) + .setNeutralButton(LocaleController.getString("Cancel", R.string.Cancel), (di, a) -> { + di.dismiss(); + }).create(); + dialog.setBackgroundColor(getThemedColor(Theme.key_voipgroup_dialogBackground)); + dialog.show(); + View neutralButton = dialog.getButton(Dialog.BUTTON_NEUTRAL); + if (neutralButton instanceof TextView) { + ((TextView) neutralButton).setTextColor(getThemedColor(Theme.key_dialogTextRed)); + neutralButton.setBackground(Theme.getRoundRectSelectorDrawable(getThemedColor(Theme.key_dialogTextRed))); + if (dialog.getButtonsLayout() instanceof LinearLayout && ((LinearLayout) dialog.getButtonsLayout()).getOrientation() == LinearLayout.VERTICAL) { + neutralButton.bringToFront(); + } + } + dialog.setTextColor(getThemedColor(Theme.key_voipgroup_actionBarItems)); } } else if (id == gallery_menu_showall) { if (currentDialogId != 0) { @@ -4128,53 +4216,51 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } ((LaunchActivity) parentActivity).switchToAccount(currentMessageObject.currentAccount, true); - if (isChannel) { - ArrayList msgs = new ArrayList<>(1); - MessageObject.GroupedMessages group = parentChatActivity != null ? parentChatActivity.getGroup(currentMessageObject.getGroupId()) : null; - if (group != null) { - msgs.addAll(group.messages); - } else { - msgs.add(currentMessageObject); + ArrayList msgs = new ArrayList<>(1); + MessageObject.GroupedMessages group = parentChatActivity != null ? parentChatActivity.getGroup(currentMessageObject.getGroupId()) : null; + if (group != null) { + msgs.addAll(group.messages); + } else { + msgs.add(currentMessageObject); + } + + if (isChannel && msgs.size() <= 1) { + showShareAlert(msgs); + } else if (msgs.size() > 1) { + boolean photos = true; + for (int i = 0; i < msgs.size(); ++i) { + if (!msgs.get(i).isPhoto() || msgs.get(i).isVideo()) { + photos = false; + break; + } } - if (msgs.size() <= 1) { - showShareAlert(msgs); - } else { - boolean photos = true; - for (int i = 0; i < msgs.size(); ++i) { - if (!msgs.get(i).isPhoto() || msgs.get(i).isVideo()) { - photos = false; - break; - } + AlertDialog dialog = new AlertDialog.Builder(parentActivity, resourcesProvider) + .setTitle(LocaleController.getString("ForwardGroupMedia", R.string.ForwardGroupMedia)) + .setMessage(LocaleController.getString("ForwardGroupMediaMessage", R.string.ForwardGroupMediaMessage)) + .setDialogButtonColorKey(Theme.key_voipgroup_listeningText) + .setNegativeButton((photos ? LocaleController.getString("ThisPhoto", R.string.ThisPhoto) : LocaleController.getString("ThisMedia", R.string.ThisMedia)), (di, a) -> { + ArrayList singleMessage = new ArrayList<>(1); + singleMessage.add(currentMessageObject); + showShareAlert(singleMessage); + }) + .setPositiveButton(photos ? LocaleController.formatPluralString("AllNPhotos", msgs.size()) : LocaleController.formatPluralString("AllNMedia", msgs.size()), (di, a) -> { + showShareAlert(msgs); + }) + .setNeutralButton(LocaleController.getString("Cancel", R.string.Cancel), (di, a) -> { + di.dismiss(); + }).create(); + dialog.setBackgroundColor(getThemedColor(Theme.key_voipgroup_dialogBackground)); + dialog.show(); + View neutralButton = dialog.getButton(Dialog.BUTTON_NEUTRAL); + if (neutralButton instanceof TextView) { + ((TextView) neutralButton).setTextColor(getThemedColor(Theme.key_dialogTextRed)); + neutralButton.setBackground(Theme.getRoundRectSelectorDrawable(getThemedColor(Theme.key_dialogTextRed))); + if (dialog.getButtonsLayout() instanceof LinearLayout && ((LinearLayout) dialog.getButtonsLayout()).getOrientation() == LinearLayout.VERTICAL) { + neutralButton.bringToFront(); } - - AlertDialog dialog = new AlertDialog.Builder(parentActivity, resourcesProvider) - .setTitle(LocaleController.getString("ForwardGroupMedia", R.string.ForwardGroupMedia)) - .setMessage(LocaleController.getString("ForwardGroupMediaMessage", R.string.ForwardGroupMediaMessage)) - .setDialogButtonColorKey(Theme.key_voipgroup_listeningText) - .setNegativeButton((photos ? LocaleController.getString("ThisPhoto", R.string.ThisPhoto) : LocaleController.getString("ThisMedia", R.string.ThisMedia)), (di, a) -> { - ArrayList singleMessage = new ArrayList<>(1); - singleMessage.add(currentMessageObject); - showShareAlert(singleMessage); - }) - .setPositiveButton(photos ? LocaleController.formatPluralString("AllNPhotos", msgs.size()) : LocaleController.formatPluralString("AllNMedia", msgs.size()), (di, a) -> { - showShareAlert(msgs); - }) - .setNeutralButton(LocaleController.getString("Cancel", R.string.Cancel), (di, a) -> { - di.dismiss(); - }).create(); - dialog.setBackgroundColor(getThemedColor(Theme.key_voipgroup_dialogBackground)); - dialog.show(); - View neutralButton = dialog.getButton(Dialog.BUTTON_NEUTRAL); - if (neutralButton instanceof TextView) { - ((TextView) neutralButton).setTextColor(getThemedColor(Theme.key_dialogTextRed)); - neutralButton.setBackground(Theme.getRoundRectSelectorDrawable(getThemedColor(Theme.key_dialogTextRed))); - if (dialog.getButtonsLayout() instanceof LinearLayout && ((LinearLayout) dialog.getButtonsLayout()).getOrientation() == LinearLayout.VERTICAL) { - neutralButton.bringToFront(); - } - } - dialog.setTextColor(getThemedColor(Theme.key_voipgroup_actionBarItems)); } + dialog.setTextColor(getThemedColor(Theme.key_voipgroup_actionBarItems)); } else { Bundle args = new Bundle(); args.putBoolean("onlySelect", true); @@ -4184,9 +4270,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat fmessages.add(currentMessageObject); final ChatActivity parentChatActivityFinal = parentChatActivity; fragment.setDelegate((fragment1, dids, message, param) -> { - if (dids.size() > 1 || dids.get(0) == UserConfig.getInstance(currentAccount).getClientUserId() || message != null) { + if (dids.size() > 1 || dids.get(0).dialogId == UserConfig.getInstance(currentAccount).getClientUserId() || message != null) { for (int a = 0; a < dids.size(); a++) { - long did = dids.get(a); + long did = dids.get(a).dialogId; if (message != null) { SendMessagesHelper.getInstance(currentAccount).sendMessage(message.toString(), did, null, null, null, true, null, null, null, true, 0, null, false); } @@ -4195,13 +4281,13 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat fragment1.finishFragment(); if (parentChatActivityFinal != null) { if (dids.size() == 1) { - parentChatActivityFinal.getUndoView().showWithAction(dids.get(0), UndoView.ACTION_FWD_MESSAGES, fmessages.size()); + parentChatActivityFinal.getUndoView().showWithAction(dids.get(0).dialogId, UndoView.ACTION_FWD_MESSAGES, fmessages.size()); } else { parentChatActivityFinal.getUndoView().showWithAction(0, UndoView.ACTION_FWD_MESSAGES, fmessages.size(), dids.size(), null, null); } } } else { - long did = dids.get(0); + long did = dids.get(0).dialogId; Bundle args1 = new Bundle(); args1.putBoolean("scrollToTopOnResume", true); if (DialogObject.isEncryptedDialog(did)) { @@ -5285,7 +5371,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat pickerViewSendButton.setContentDescription(LocaleController.getString("Send", R.string.Send)); pickerViewSendButton.setOnClickListener(v -> { if (captionEditText.getCaptionLimitOffset() < 0) { - AndroidUtilities.shakeView(captionLimitView, 2, 0); + AndroidUtilities.shakeView(captionLimitView); Vibrator vibrator = (Vibrator) captionLimitView.getContext().getSystemService(Context.VIBRATOR_SERVICE); if (vibrator != null) { vibrator.vibrate(200); @@ -6139,6 +6225,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (mentionListViewVisible && getVisibility() == VISIBLE && mentionListAnimation == null) { setTranslationY(h - oldh); + mentionListView.setTranslationY(MathUtils.clamp(mentionListView.getTranslationY(), Math.min(h - oldh, 0), Math.max(h - oldh, 0))); mentionListAnimation = new SpringAnimation(this, DynamicAnimation.TRANSLATION_Y) .setMinValue(Math.min(h - oldh, 0)) .setMaxValue(Math.max(h - oldh, 0)) @@ -6198,6 +6285,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat mentionListView.setVisibility(View.VISIBLE); mentionListViewVisible = true; mentionListView.setTranslationY(AndroidUtilities.dp(height)); + mentionListView.setTranslationY(MathUtils.clamp(mentionListView.getTranslationY(), 0, AndroidUtilities.dp(height))); mentionListAnimation = new SpringAnimation(mentionListView, DynamicAnimation.TRANSLATION_Y) .setMinValue(0) .setMaxValue(AndroidUtilities.dp(height)) @@ -6225,6 +6313,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } if (allowMentions) { mentionListViewVisible = false; + mentionListView.setTranslationY(MathUtils.clamp(mentionListView.getTranslationY(), 0, mentionListView.getMeasuredHeight())); mentionListAnimation = new SpringAnimation(mentionListView, DynamicAnimation.TRANSLATION_Y) .setMinValue(0) .setMaxValue(mentionListView.getMeasuredHeight()) @@ -6302,7 +6391,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity, resourcesProvider); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setMessage(LocaleController.getString("ClearSearch", R.string.ClearSearch)); - builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton).toUpperCase(), (dialogInterface, i) -> mentionsAdapter.clearRecentHashtags()); + builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton), (dialogInterface, i) -> mentionsAdapter.clearRecentHashtags()); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showAlertDialog(builder); return true; @@ -6403,8 +6492,10 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (videoEditedInfo != null) { long sizeToCheck = (long) (videoEditedInfo.estimatedSize * 0.9f); if ((sizeToCheck > FileLoader.DEFAULT_MAX_FILE_SIZE && !UserConfig.getInstance(currentAccount).isPremium()) || sizeToCheck > FileLoader.DEFAULT_MAX_FILE_SIZE_PREMIUM) { - LimitReachedBottomSheet limitReachedBottomSheet = new LimitReachedBottomSheet(parentAlert.getBaseFragment(), parentAlert.getContainer().getContext(), LimitReachedBottomSheet.TYPE_LARGE_FILE, UserConfig.selectedAccount); - limitReachedBottomSheet.show(); + if (parentAlert != null) { + LimitReachedBottomSheet limitReachedBottomSheet = new LimitReachedBottomSheet(parentAlert.getBaseFragment(), parentAlert.getContainer().getContext(), LimitReachedBottomSheet.TYPE_LARGE_FILE, UserConfig.selectedAccount); + limitReachedBottomSheet.show(); + } return; } } @@ -6433,7 +6524,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat final boolean finalOpenKeyboardOnShareAlertClose = openKeyboardOnShareAlertClose; ShareAlert alert = new ShareAlert(parentActivity, parentChatActivity, messages, null, null, false, null, null, false, true, null) { @Override - protected void onSend(LongSparseArray dids, int count) { + protected void onSend(LongSparseArray dids, int count, TLRPC.TL_forumTopic topic) { AndroidUtilities.runOnUIThread(() -> { BulletinFactory.createForwardedBulletin(parentActivity, photoContainerView, dids.size(), dids.size() == 1 ? dids.valueAt(0).id : 0, count, 0xf9222222, 0xffffffff).show(); }, 250); @@ -6847,8 +6938,12 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat pipAnimationInProgress = false; switchToInlineRunnable.run(); AndroidUtilities.runOnUIThread(()->{ - videoTextureView.setOutlineProvider(null); - textureImageView.setOutlineProvider(null); + if (videoTextureView != null) { + videoTextureView.setOutlineProvider(null); + } + if (textureImageView != null) { + textureImageView.setOutlineProvider(null); + } if (firstFrameView != null) { firstFrameView.setOutlineProvider(null); } @@ -7724,6 +7819,48 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (photoViewerWebView != null && photoViewerWebView.isControllable() && !playWhenReady) { toggleActionBar(true, true); } + if (photoViewerWebView != null && photoViewerWebView.isControllable() && playbackState == ExoPlayer.STATE_READY && getVideoDuration() >= 10000 && shouldSavePositionForCurrentVideo == null && shouldSavePositionForCurrentVideoShortTerm == null) { + if (currentMessageObject != null) { + final long duration = getVideoDuration() / 1000L; + final String name = currentMessageObject.messageOwner != null && currentMessageObject.messageOwner.media != null && currentMessageObject.messageOwner.media.webpage != null ? currentMessageObject.messageOwner.media.webpage.url : null; + if (!TextUtils.isEmpty(name)) { + if (duration >= 10 * 60) { + if (currentMessageObject.forceSeekTo < 0) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("media_saved_pos", Activity.MODE_PRIVATE); + float pos = preferences.getFloat(name, -1); + if (pos > 0 && pos < 0.999f) { + currentMessageObject.forceSeekTo = pos; + videoPlayerSeekbar.setProgress(pos); + } + } + shouldSavePositionForCurrentVideo = name; + } else if (duration >= 10) { + SavedVideoPosition videoPosition = null; + for (int i = savedVideoPositions.size() - 1; i >= 0; i--) { + final SavedVideoPosition item = savedVideoPositions.valueAt(i); + if (item.timestamp < SystemClock.elapsedRealtime() - 5 * 1000) { + savedVideoPositions.removeAt(i); + } else if (videoPosition == null && savedVideoPositions.keyAt(i).equals(name)) { + videoPosition = item; + } + } + if (currentMessageObject.forceSeekTo < 0 && videoPosition != null) { + float pos = videoPosition.position; + if (pos > 0 && pos < 0.999f) { + currentMessageObject.forceSeekTo = pos; + videoPlayerSeekbar.setProgress(pos); + } + } + shouldSavePositionForCurrentVideoShortTerm = name; + } + } + } + + if (currentMessageObject != null && currentMessageObject.forceSeekTo >= 0) { + seekVideoOrWebToProgress(currentMessageObject.forceSeekTo); + currentMessageObject.forceSeekTo = -1; + } + } if (isStreaming) { if (playbackState == ExoPlayer.STATE_BUFFERING && skipFirstBufferingProgress) { if (playWhenReady) { @@ -8413,6 +8550,13 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } else { playerWasPlaying = false; } + if (photoViewerWebView != null) { + AndroidUtilities.cancelRunOnUIThread(hideActionBarRunnable); + if (shouldSavePositionForCurrentVideoShortTerm != null) { + float progress = getCurrentVideoPosition() / (float) getVideoDuration(); + savedVideoPositions.put(shouldSavePositionForCurrentVideoShortTerm, new SavedVideoPosition(progress, SystemClock.elapsedRealtime())); + } + } if (orientationEventListener != null) { orientationEventListener.disable(); @@ -10965,9 +11109,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (currentAnimation == null && !isEvent) { if (currentDialogId != 0 && totalImagesCount == 0 && currentMessageObject != null && !currentMessageObject.scheduled) { if (MediaDataController.getMediaType(currentMessageObject.messageOwner) == sharedMediaType) { - MediaDataController.getInstance(currentAccount).getMediaCount(currentDialogId, sharedMediaType, classGuid, true); + MediaDataController.getInstance(currentAccount).getMediaCount(currentDialogId, topicId, sharedMediaType, classGuid, true); if (mergeDialogId != 0) { - MediaDataController.getInstance(currentAccount).getMediaCount(mergeDialogId, sharedMediaType, classGuid, true); + MediaDataController.getInstance(currentAccount).getMediaCount(mergeDialogId, topicId, sharedMediaType, classGuid, true); } } } else if (avatarsDialogId != 0) { @@ -11186,7 +11330,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } if (!placeProvider.loadMore()) { - MediaDataController.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 40, loadFromMaxId, 0, sharedMediaType, 1, classGuid, 0); + MediaDataController.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 40, loadFromMaxId, 0, sharedMediaType, topicId,1, classGuid, 0); loadingMoreImages = true; } } @@ -11195,7 +11339,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat int loadFromMinId = imagesArr.get(0).getId(); int loadIndex = 0; if (!placeProvider.loadMore()) { - MediaDataController.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 40, 0, loadFromMinId, sharedMediaType, 1, classGuid, 0); + MediaDataController.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 40, 0, loadFromMinId, sharedMediaType, topicId, 1, classGuid, 0); loadingMoreImages = true; } } @@ -11219,7 +11363,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } - MediaDataController.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 80, loadFromMaxId, 0, sharedMediaType, 1, classGuid, 0); + MediaDataController.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 80, loadFromMaxId, 0, sharedMediaType, topicId, 1, classGuid, 0); loadingMoreImages = true; } actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, (totalImagesCount + totalImagesCountMerge - imagesArr.size()) + switchingToIndex + 1, totalImagesCount + totalImagesCountMerge)); @@ -12828,40 +12972,40 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } - public boolean openPhoto(final MessageObject messageObject, ChatActivity chatActivity, long dialogId, long mergeDialogId, final PhotoViewerProvider provider) { - return openPhoto(messageObject, null, null, null, null, null, null, 0, provider, chatActivity, dialogId, mergeDialogId, true, null, null); + public boolean openPhoto(final MessageObject messageObject, ChatActivity chatActivity, long dialogId, long mergeDialogId, int topicId, final PhotoViewerProvider provider) { + return openPhoto(messageObject, null, null, null, null, null, null, 0, provider, chatActivity, dialogId, mergeDialogId, topicId, true, null, null); } - public boolean openPhoto(final MessageObject messageObject, int embedSeekTime, ChatActivity chatActivity, long dialogId, long mergeDialogId, final PhotoViewerProvider provider) { - return openPhoto(messageObject, null, null, null, null, null, null, 0, provider, chatActivity, dialogId, mergeDialogId, true, null, embedSeekTime); + public boolean openPhoto(final MessageObject messageObject, int embedSeekTime, ChatActivity chatActivity, long dialogId, long mergeDialogId, int topicId, final PhotoViewerProvider provider) { + return openPhoto(messageObject, null, null, null, null, null, null, 0, provider, chatActivity, dialogId, mergeDialogId, topicId, true, null, embedSeekTime); } - public boolean openPhoto(final MessageObject messageObject, long dialogId, long mergeDialogId, final PhotoViewerProvider provider, boolean fullScreenVideo) { - return openPhoto(messageObject, null, null, null, null, null, null, 0, provider, null, dialogId, mergeDialogId, fullScreenVideo, null, null); + public boolean openPhoto(final MessageObject messageObject, long dialogId, long mergeDialogId, int topicId, final PhotoViewerProvider provider, boolean fullScreenVideo) { + return openPhoto(messageObject, null, null, null, null, null, null, 0, provider, null, dialogId, mergeDialogId, topicId, fullScreenVideo, null, null); } public boolean openPhoto(final TLRPC.FileLocation fileLocation, final PhotoViewerProvider provider) { - return openPhoto(null, fileLocation, null, null, null, null, null, 0, provider, null, 0, 0, true, null, null); + return openPhoto(null, fileLocation, null, null, null, null, null, 0, provider, null, 0, 0, 0, true, null, null); } public boolean openPhotoWithVideo(final TLRPC.FileLocation fileLocation, ImageLocation videoLocation, final PhotoViewerProvider provider) { - return openPhoto(null, fileLocation, null, videoLocation, null, null, null, 0, provider, null, 0, 0, true, null, null); + return openPhoto(null, fileLocation, null, videoLocation, null, null, null, 0, provider, null, 0, 0, 0, true, null, null); } public boolean openPhoto(final TLRPC.FileLocation fileLocation, final ImageLocation imageLocation, final PhotoViewerProvider provider) { - return openPhoto(null, fileLocation, imageLocation, null, null, null, null, 0, provider, null, 0, 0, true, null, null); + return openPhoto(null, fileLocation, imageLocation, null, null, null, null, 0, provider, null, 0, 0, 0, true, null, null); } - public boolean openPhoto(final ArrayList messages, final int index, long dialogId, long mergeDialogId, final PhotoViewerProvider provider) { - return openPhoto(messages.get(index), null, null, null, messages, null, null, index, provider, null, dialogId, mergeDialogId, true, null, null); + public boolean openPhoto(final ArrayList messages, final int index, long dialogId, long mergeDialogId, int topicId, final PhotoViewerProvider provider) { + return openPhoto(messages.get(index), null, null, null, messages, null, null, index, provider, null, dialogId, mergeDialogId, topicId, true, null, null); } public boolean openPhoto(final ArrayList documents, final int index, final PhotoViewerProvider provider) { - return openPhoto(null, null, null, null, null, documents, null, index, provider, null, 0, 0, true, null, null); + return openPhoto(null, null, null, null, null, documents, null, index, provider, null, 0, 0, 0, true, null, null); } public boolean openPhoto(int index, PageBlocksAdapter pageBlocksAdapter, PhotoViewerProvider provider) { - return openPhoto(null, null, null, null, null, null, null, index, provider, null, 0, 0, true, pageBlocksAdapter, null); + return openPhoto(null, null, null, null, null, null, null, index, provider, null, 0, 0, 0, true, pageBlocksAdapter, null); } public boolean openPhotoForSelect(final ArrayList photos, final int index, int type, boolean documentsPicker, final PhotoViewerProvider provider, ChatActivity chatActivity) { @@ -12916,7 +13060,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat return true; } sendPhotoType = type; - return openPhoto(null, null, null, null, null, null, photos, index, provider, chatActivity, 0, 0, true, null, null); + return openPhoto(null, null, null, null, null, null, photos, index, provider, chatActivity, 0, 0, 0, true, null, null); } private void openCurrentPhotoInPaintModeForSelect() { @@ -13124,7 +13268,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat padImageForHorizontalInsets = true; } - public boolean openPhoto(final MessageObject messageObject, final TLRPC.FileLocation fileLocation, final ImageLocation imageLocation, final ImageLocation videoLocation, final ArrayList messages, final ArrayList documents, final ArrayList photos, final int index, final PhotoViewerProvider provider, ChatActivity chatActivity, long dialogId, long mDialogId, boolean fullScreenVideo, PageBlocksAdapter pageBlocksAdapter, Integer embedSeekTime) { + public boolean openPhoto(final MessageObject messageObject, final TLRPC.FileLocation fileLocation, final ImageLocation imageLocation, final ImageLocation videoLocation, final ArrayList messages, final ArrayList documents, final ArrayList photos, final int index, final PhotoViewerProvider provider, ChatActivity chatActivity, long dialogId, long mDialogId, int topicId, boolean fullScreenVideo, PageBlocksAdapter pageBlocksAdapter, Integer embedSeekTime) { if (parentActivity == null || isVisible || provider == null && checkAnimation() || messageObject == null && fileLocation == null && messages == null && photos == null && documents == null && imageLocation == null && pageBlocksAdapter == null) { return false; } @@ -13194,6 +13338,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat placeProvider = provider; mergeDialogId = mDialogId; currentDialogId = dialogId; + this.topicId = topicId; selectedPhotosAdapter.notifyDataSetChanged(); this.pageBlocksAdapter = pageBlocksAdapter; @@ -13633,6 +13778,14 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } videoPlayerSeekbar.clearTimestamps(); updateVideoPlayerTime(); + + shouldSavePositionForCurrentVideo = null; + shouldSavePositionForCurrentVideoShortTerm = null; + lastSaveTime = 0; + seekToProgressPending = seekToProgressPending2; + videoPlayerSeekbar.setProgress(0); + videoTimelineView.setProgress(0); + videoPlayerSeekbar.setBufferedProgress(0); } private void makeFocusable() { @@ -14858,13 +15011,17 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (clippingImageProgress != 0) { progress = progress + (1f - progress) * clippingImageProgress; } - float scale = 1f + (1f - MathUtils.clamp(progress, 0, 1)) * ZOOM_SCALE; + float scale = 1f + (1f - Utilities.clamp(progress, 1, 0)) * ZOOM_SCALE; View view = parentFragment.getFragmentView(); + view.setPivotX(view.getWidth() / 2f); + view.setPivotY(view.getHeight() / 2f); view.setScaleX(scale); view.setScaleY(scale); if (parentAlert != null) { view = parentAlert.getContainer(); + view.setPivotX(view.getWidth() / 2f); + view.setPivotY(view.getHeight() / 2f); view.setScaleX(scale); view.setScaleY(scale); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java index 995e334e6..5d6002ee6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java @@ -857,7 +857,7 @@ public class PopupNotificationActivity extends Activity implements NotificationC } ViewGroup view; MessageObject messageObject = popupMessages.get(num); - if ((messageObject.type == 1 || messageObject.type == 4) && !messageObject.isSecretMedia()) { + if ((messageObject.type == MessageObject.TYPE_PHOTO || messageObject.type == MessageObject.TYPE_GEO) && !messageObject.isSecretMedia()) { if (imageViews.size() > 0) { view = imageViews.get(0); imageViews.remove(0); @@ -889,13 +889,13 @@ public class PopupNotificationActivity extends Activity implements NotificationC BackupImageView imageView = view.findViewWithTag(311); imageView.setAspectFit(true); - if (messageObject.type == 1) { + if (messageObject.type == MessageObject.TYPE_PHOTO) { TLRPC.PhotoSize currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 100); boolean photoSet = false; if (currentPhotoObject != null) { boolean photoExist = true; - if (messageObject.type == 1) { + if (messageObject.type == MessageObject.TYPE_PHOTO) { File cacheFile = FileLoader.getInstance(UserConfig.selectedAccount).getPathToMessage(messageObject.messageOwner); if (!cacheFile.exists()) { photoExist = false; @@ -922,7 +922,7 @@ public class PopupNotificationActivity extends Activity implements NotificationC imageView.setVisibility(View.VISIBLE); messageText.setVisibility(View.GONE); } - } else if (messageObject.type == 4) { + } else if (messageObject.type == MessageObject.TYPE_GEO) { messageText.setVisibility(View.GONE); messageText.setText(messageObject.messageText); imageView.setVisibility(View.VISIBLE); @@ -937,7 +937,7 @@ public class PopupNotificationActivity extends Activity implements NotificationC imageView.setImage(currentUrl, null, null); } } - } else if (messageObject.type == 2) { + } else if (messageObject.type == MessageObject.TYPE_VOICE) { PopupAudioView cell; if (audioViews.size() > 0) { view = audioViews.get(0); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PremiumPreviewFragment.java b/TMessagesProj/src/main/java/org/telegram/ui/PremiumPreviewFragment.java index 1c46890bb..58eb91a60 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PremiumPreviewFragment.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PremiumPreviewFragment.java @@ -49,6 +49,7 @@ import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.BillingController; import org.telegram.messenger.BuildVars; +import org.telegram.messenger.FileLoader; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaDataController; import org.telegram.messenger.MessagesController; @@ -845,6 +846,12 @@ public class PremiumPreviewFragment extends BaseFragment implements Notification NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.currentUserPremiumStatusChanged); getNotificationCenter().addObserver(this, NotificationCenter.premiumPromoUpdated); + if (getMediaDataController().getPremiumPromo() != null) { + for (TLRPC.Document document : getMediaDataController().getPremiumPromo().videos) { + FileLoader.getInstance(currentAccount).loadFile(document, null, FileLoader.PRIORITY_HIGH, 0); + } + } + return super.onFragmentCreate(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java index c9389b5d7..d70346126 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java @@ -107,6 +107,8 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio private int secretDetailRow; private int rowCount; + private boolean deleteAccountUpdate; + private boolean secretMapUpdate; private boolean currentSync; private boolean newSync; private boolean currentSuggest; @@ -213,6 +215,7 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio }); listView.setVerticalScrollBarEnabled(false); listView.setLayoutAnimation(null); + listView.setItemAnimator(null); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setAdapter(listAdapter); listView.setOnItemClickListener((view, position) -> { @@ -287,6 +290,7 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio FileLog.e(e); } if (response instanceof TLRPC.TL_boolTrue) { + deleteAccountUpdate = true; getContactsController().setDeleteAccountTTL(req.ttl.days); listAdapter.notifyDataSetChanged(); } @@ -447,7 +451,10 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio ((TextCheckCell) view).setChecked(newSync); } } else if (position == secretMapRow) { - AlertsCreator.showSecretLocationAlert(getParentActivity(), currentAccount, () -> listAdapter.notifyDataSetChanged(), false, null); + AlertsCreator.showSecretLocationAlert(getParentActivity(), currentAccount, () -> { + listAdapter.notifyDataSetChanged(); + secretMapUpdate = true; + }, false, null); } else if (position == paymentsClearRow) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("PrivacyPaymentsClearAlertTitle", R.string.PrivacyPaymentsClearAlertTitle)); @@ -978,7 +985,8 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio value = LocaleController.formatPluralString("Days", ttl); } } - textCell.setTextAndValue(LocaleController.getString("DeleteAccountIfAwayFor3", R.string.DeleteAccountIfAwayFor3), value, false); + textCell.setTextAndValue(LocaleController.getString("DeleteAccountIfAwayFor3", R.string.DeleteAccountIfAwayFor3), value, deleteAccountUpdate, false); + deleteAccountUpdate = false; } else if (position == paymentsClearRow) { textCell.setText(LocaleController.getString("PrivacyPaymentsClear", R.string.PrivacyPaymentsClear), true); } else if (position == secretMapRow) { @@ -997,7 +1005,8 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio value = LocaleController.getString("MapPreviewProviderYandex", R.string.MapPreviewProviderYandex); break; } - textCell.setTextAndValue(LocaleController.getString("MapPreviewProvider", R.string.MapPreviewProvider), value, true); + textCell.setTextAndValue(LocaleController.getString("MapPreviewProvider", R.string.MapPreviewProvider), value, secretMapUpdate, true); + secretMapUpdate = false; } else if (position == contactsDeleteRow) { textCell.setText(LocaleController.getString("SyncContactsDelete", R.string.SyncContactsDelete), true); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java index 9f6962196..5fbee2a54 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java @@ -19,6 +19,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import org.telegram.PhoneFormat.PhoneFormat; +import org.telegram.messenger.ChatObject; import org.telegram.messenger.DialogObject; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; @@ -440,7 +441,7 @@ public class PrivacyUsersActivity extends BaseFragment implements NotificationCe subtitle = LocaleController.formatPluralString("Members", chat.participants_count); } else if (chat.has_geo) { subtitle = LocaleController.getString("MegaLocation", R.string.MegaLocation); - } else if (TextUtils.isEmpty(chat.username)) { + } else if (!ChatObject.isPublic(chat)) { subtitle = LocaleController.getString("MegaPrivate", R.string.MegaPrivate); } else { subtitle = LocaleController.getString("MegaPublic", R.string.MegaPublic); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java index 609f3fbfc..2619e485a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java @@ -30,6 +30,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; @@ -43,10 +44,12 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.SystemClock; +import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextPaint; import android.text.TextUtils; +import android.text.style.ClickableSpan; import android.text.style.ForegroundColorSpan; import android.util.Property; import android.util.SparseIntArray; @@ -110,6 +113,7 @@ import org.telegram.messenger.MediaController; import org.telegram.messenger.MediaDataController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; +import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.NotificationsController; import org.telegram.messenger.R; @@ -146,6 +150,7 @@ import org.telegram.ui.Cells.SettingsSearchCell; import org.telegram.ui.Cells.SettingsSuggestionCell; import org.telegram.ui.Cells.ShadowSectionCell; import org.telegram.ui.Cells.TextCell; +import org.telegram.ui.Cells.TextCheckCell; import org.telegram.ui.Cells.TextDetailCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.UserCell; @@ -164,6 +169,9 @@ import org.telegram.ui.Components.ChatNotificationsPopupWrapper; import org.telegram.ui.Components.CombinedDrawable; import org.telegram.ui.Components.CrossfadeDrawable; import org.telegram.ui.Components.CubicBezierInterpolator; +import org.telegram.ui.Components.EmojiPacksAlert; +import org.telegram.ui.Components.FloatingDebug.FloatingDebugController; +import org.telegram.ui.Components.Forum.ForumUtilities; import org.telegram.ui.Components.FragmentContextView; import org.telegram.ui.Components.HintView; import org.telegram.ui.Components.IdenticonDrawable; @@ -205,6 +213,7 @@ import java.util.List; import java.util.Locale; import java.util.Set; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -303,6 +312,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private TopView topView; private long userId; private long chatId; + private int topicId; private long dialogId; private boolean creatingChat; private boolean userBlocked; @@ -311,6 +321,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private boolean expandPhoto; private boolean needSendMessage; private boolean hasVoiceChatItem; + private boolean isTopic; private boolean scrolling; @@ -456,11 +467,13 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private int usernameRow; private int notificationsDividerRow; private int notificationsRow; + private int notificationsSimpleRow; private int infoSectionRow; private int sendMessageRow; private int reportRow; private int reportReactionRow; private int reportDividerRow; + private int addToContactsRow; private int addToGroupButtonRow; private int addToGroupInfoRow; private int premiumRow; @@ -503,8 +516,15 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private int reportReactionMessageId = 0; private long reportReactionFromDialogId = 0; + private boolean showAddToContacts; + private String vcardPhone; + private String vcardFirstName; + private String vcardLastName; + ChatActivity previousTransitionFragment; + HashSet notificationsExceptionTopics = new HashSet<>(); + private final Property HEADER_SHADOW = new AnimationProperties.FloatProperty("headerShadow") { @Override public void setValue(ProfileActivity object, float value) { @@ -574,6 +594,11 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. }; private boolean fragmentOpened; private NestedFrameLayout contentView; + private float titleAnimationsYDiff; + + public int getTopicId() { + return topicId; + } public static class AvatarImageView extends BackupImageView { @@ -662,6 +687,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. @Override protected void onDraw(Canvas canvas) { + ImageReceiver imageReceiver = animatedEmojiDrawable != null ? animatedEmojiDrawable.getImageReceiver() : this.imageReceiver; if (foregroundAlpha < 1f) { imageReceiver.setImageCoords(0, 0, getMeasuredWidth(), getMeasuredHeight()); imageReceiver.draw(canvas); @@ -1398,9 +1424,15 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. public boolean onFragmentCreate() { userId = arguments.getLong("user_id", 0); chatId = arguments.getLong("chat_id", 0); + topicId = arguments.getInt("topic_id", 0); + isTopic = topicId != 0; banFromGroup = arguments.getLong("ban_chat_id", 0); reportReactionMessageId = arguments.getInt("report_reaction_message_id", 0); reportReactionFromDialogId = arguments.getLong("report_reaction_from_dialog_id", 0); + showAddToContacts = arguments.getBoolean("show_add_to_contacts"); + vcardPhone = PhoneFormat.stripExceptNumbers(arguments.getString("vcard_phone")); + vcardFirstName = arguments.getString("vcard_first_name"); + vcardLastName = arguments.getString("vcard_last_name"); reportSpam = arguments.getBoolean("reportSpam", false); if (!expandPhoto) { expandPhoto = arguments.getBoolean("expandPhoto", false); @@ -1486,6 +1518,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else if (chatInfo == null) { chatInfo = getMessagesStorage().loadChatInfo(chatId, false, null, false, false); } + + updateExceptions(); } else { return false; } @@ -1497,6 +1531,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. getNotificationCenter().addObserver(this, NotificationCenter.updateInterfaces); getNotificationCenter().addObserver(this, NotificationCenter.didReceiveNewMessages); getNotificationCenter().addObserver(this, NotificationCenter.closeChats); + getNotificationCenter().addObserver(this, NotificationCenter.topicsDidLoaded); NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.emojiLoaded); updateRowsIds(); if (listAdapter != null) { @@ -1519,10 +1554,29 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. }); } } - return true; } + private void updateExceptions() { + if (!isTopic && currentChat != null && currentChat.forum) { + getNotificationsController().loadTopicsNotificationsExceptions(-chatId, (topics) -> { + ArrayList arrayList = new ArrayList<>(topics); + for (int i = 0; i < arrayList.size(); i++) { + if (getMessagesController().getTopicsController().findTopic(chatId, arrayList.get(i)) == null) { + arrayList.remove(i); + i--; + } + } + notificationsExceptionTopics.clear(); + notificationsExceptionTopics.addAll(arrayList); + + if (notificationsRow >= 0 && listAdapter != null) { + listAdapter.notifyItemChanged(notificationsRow); + } + }); + } + } + @Override public void onFragmentDestroy() { super.onFragmentDestroy(); @@ -1539,6 +1593,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. getNotificationCenter().removeObserver(this, NotificationCenter.updateInterfaces); getNotificationCenter().removeObserver(this, NotificationCenter.closeChats); getNotificationCenter().removeObserver(this, NotificationCenter.didReceiveNewMessages); + getNotificationCenter().removeObserver(this, NotificationCenter.topicsDidLoaded); NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.emojiLoaded); if (avatarsViewPager != null) { avatarsViewPager.onDestroy(); @@ -1740,11 +1795,18 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else if (id == leave_group) { leaveChatPressed(); } else if (id == edit_channel) { - Bundle args = new Bundle(); - args.putLong("chat_id", chatId); - ChatEditActivity fragment = new ChatEditActivity(args); - fragment.setInfo(chatInfo); - presentFragment(fragment); + if (isTopic) { + Bundle args = new Bundle(); + args.putLong("chat_id", chatId); + TopicCreateFragment fragment = TopicCreateFragment.create(chatId, topicId); + presentFragment(fragment); + } else { + Bundle args = new Bundle(); + args.putLong("chat_id", chatId); + ChatEditActivity fragment = new ChatEditActivity(args); + fragment.setInfo(chatInfo); + presentFragment(fragment); + } } else if (id == invite_to_group) { final TLRPC.User user = getMessagesController().getUser(userId); if (user == null) { @@ -1758,7 +1820,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. // args.putString("addToGroupAlertString", LocaleController.formatString("AddToTheGroupAlertText", R.string.AddToTheGroupAlertText, UserObject.getUserName(user), "%1$s")); DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate((fragment1, dids, message, param) -> { - long did = dids.get(0); + long did = dids.get(0).dialogId; TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-did); if (chat != null && (chat.creator || chat.admin_rights != null && chat.admin_rights.add_admins)) { @@ -2379,8 +2441,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. canvas.drawRect(0, top + extraHeight + searchTransitionOffset, getMeasuredWidth(), top + getMeasuredHeight(), whitePaint); } super.dispatchDraw(canvas); - if (profileTransitionInProgress && parentLayout.fragmentsStack.size() > 1) { - BaseFragment fragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2); + if (profileTransitionInProgress && parentLayout.getFragmentStack().size() > 1) { + BaseFragment fragment = parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 2); if (fragment instanceof ChatActivity) { ChatActivity chatActivity = (ChatActivity) fragment; FragmentContextView fragmentContextView = chatActivity.getFragmentContextView(); @@ -2753,7 +2815,24 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. return; } listView.stopScroll(); - if (position == reportReactionRow) { + if (position == notificationsSimpleRow) { + boolean muted = getMessagesController().isDialogMuted(did, topicId); + getNotificationsController().muteDialog(did, topicId, !muted); + BulletinFactory.createMuteBulletin(ProfileActivity.this, !muted, null).show(); + updateExceptions(); + if (notificationsSimpleRow >= 0) { + listAdapter.notifyItemChanged(notificationsSimpleRow); + } + } else if (position == addToContactsRow) { + TLRPC.User user = getMessagesController().getUser(userId); + Bundle args = new Bundle(); + args.putLong("user_id", user.id); + args.putBoolean("addContact", true); + args.putString("phone", vcardPhone); + args.putString("first_name_card", vcardFirstName); + args.putString("last_name_card", vcardLastName); + presentFragment(new ContactAddActivity(args, resourcesProvider)); + } else if (position == reportReactionRow) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity(), resourcesProvider); builder.setTitle(LocaleController.getString("ReportReaction", R.string.ReportReaction)); builder.setMessage(LocaleController.getString("ReportAlertReaction", R.string.ReportAlertReaction)); @@ -2820,44 +2899,50 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. boolean defaultEnabled = getNotificationsController().isGlobalNotificationsEnabled(did); + String key = NotificationsController.getSharedPrefKey(did, topicId); if (checked) { SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); SharedPreferences.Editor editor = preferences.edit(); if (defaultEnabled) { - editor.remove("notify2_" + did); + editor.remove("notify2_" + key); } else { - editor.putInt("notify2_" + did, 0); + editor.putInt("notify2_" + key, 0); } - getMessagesStorage().setDialogFlags(did, 0); - editor.commit(); - TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(did); - if (dialog != null) { - dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + if (topicId == 0) { + getMessagesStorage().setDialogFlags(did, 0); + TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(did); + if (dialog != null) { + dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + } } + editor.apply(); } else { int untilTime = Integer.MAX_VALUE; SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); SharedPreferences.Editor editor = preferences.edit(); long flags; if (!defaultEnabled) { - editor.remove("notify2_" + did); + editor.remove("notify2_" + key); flags = 0; } else { - editor.putInt("notify2_" + did, 2); + editor.putInt("notify2_" + key, 2); flags = 1; } getNotificationsController().removeNotificationsForDialog(did); - getMessagesStorage().setDialogFlags(did, flags); - editor.commit(); - TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(did); - if (dialog != null) { - dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); - if (defaultEnabled) { - dialog.notify_settings.mute_until = untilTime; + if (topicId == 0) { + getMessagesStorage().setDialogFlags(did, flags); + TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(did); + if (dialog != null) { + dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + if (defaultEnabled) { + dialog.notify_settings.mute_until = untilTime; + } } } + editor.apply(); } - getNotificationsController().updateServerNotificationsSettings(did); + updateExceptions(); + getNotificationsController().updateServerNotificationsSettings(did, topicId); checkCell.setChecked(checked); RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.findViewHolderForPosition(notificationsRow); if (holder != null) { @@ -2869,8 +2954,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. @Override public void toggleSound() { SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); - boolean enabled = !preferences.getBoolean("sound_enabled_" + did, true); - preferences.edit().putBoolean("sound_enabled_" + did, enabled).apply(); + boolean enabled = !preferences.getBoolean("sound_enabled_" + NotificationsController.getSharedPrefKey(did, topicId), true); + preferences.edit().putBoolean("sound_enabled_" + NotificationsController.getSharedPrefKey(did, topicId), enabled).apply(); if (BulletinFactory.canShowBulletin(ProfileActivity.this)) { BulletinFactory.createSoundEnabledBulletin(ProfileActivity.this, enabled ? NotificationsController.SETTING_SOUND_ON : NotificationsController.SETTING_SOUND_OFF, getResourceProvider()).show(); } @@ -2879,17 +2964,18 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. @Override public void muteFor(int timeInSeconds) { if (timeInSeconds == 0) { - if (getMessagesController().isDialogMuted(did)) { + if (getMessagesController().isDialogMuted(did, topicId)) { toggleMute(); } if (BulletinFactory.canShowBulletin(ProfileActivity.this)) { BulletinFactory.createMuteBulletin(ProfileActivity.this, NotificationsController.SETTING_MUTE_UNMUTE, timeInSeconds, getResourceProvider()).show(); } } else { - getNotificationsController().muteUntil(did, timeInSeconds); + getNotificationsController().muteUntil(did, topicId, timeInSeconds); if (BulletinFactory.canShowBulletin(ProfileActivity.this)) { BulletinFactory.createMuteBulletin(ProfileActivity.this, NotificationsController.SETTING_MUTE_CUSTOM, timeInSeconds, getResourceProvider()).show(); } + updateExceptions(); if (notificationsRow >= 0) { listAdapter.notifyItemChanged(notificationsRow); } @@ -2901,21 +2987,37 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (did != 0) { Bundle args = new Bundle(); args.putLong("dialog_id", did); + args.putInt("topic_id", topicId); presentFragment(new ProfileNotificationsActivity(args, resourcesProvider)); } } @Override public void toggleMute() { - boolean muted = getMessagesController().isDialogMuted(did); - getNotificationsController().muteDialog(did, !muted); + boolean muted = getMessagesController().isDialogMuted(did, topicId); + getNotificationsController().muteDialog(did, topicId, !muted); BulletinFactory.createMuteBulletin(ProfileActivity.this, !muted, null).show(); + updateExceptions(); if (notificationsRow >= 0) { listAdapter.notifyItemChanged(notificationsRow); } } + + @Override + public void openExceptions() { + Bundle bundle = new Bundle(); + bundle.putLong("dialog_id", did); + TopicsNotifySettingsFragments notifySettings = new TopicsNotifySettingsFragments(bundle); + notifySettings.setExceptions(notificationsExceptionTopics); + presentFragment(notifySettings); + } }, getResourceProvider()); - chatNotificationsPopupWrapper.update(did); + chatNotificationsPopupWrapper.update(did, topicId, notificationsExceptionTopics); + if (AndroidUtilities.isTablet()) { + View v = parentLayout.getView(); + x += v.getX() + v.getPaddingLeft(); + y += v.getY() + v.getPaddingTop(); + } chatNotificationsPopupWrapper.showAsOptions(ProfileActivity.this, view, x, y); } else if (position == unblockRow) { getMessagesController().unblockPeer(userId); @@ -2943,7 +3045,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else if (position == addMemberRow) { openAddMember(); } else if (position == usernameRow) { - processOnClickOrPress(position, view); + processOnClickOrPress(position, view, x, y); } else if (position == locationRow) { if (chatInfo.location instanceof TLRPC.TL_channelLocation) { LocationActivity fragment = new LocationActivity(LocationActivity.LOCATION_TYPE_GROUP_VIEW); @@ -3033,7 +3135,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else if (position == premiumRow) { presentFragment(new PremiumPreviewFragment("settings")); } else { - processOnClickOrPress(position, view); + processOnClickOrPress(position, view, x, y); } }); @@ -3071,6 +3173,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. BuildVars.DEBUG_PRIVATE_VERSION ? LocaleController.getString(R.string.DebugMenuClearWebViewCache) : null, Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ? LocaleController.getString(SharedConfig.debugWebView ? R.string.DebugMenuDisableWebViewDebug : R.string.DebugMenuEnableWebViewDebug) : null, (AndroidUtilities.isTabletInternal() && BuildVars.DEBUG_PRIVATE_VERSION) ? (SharedConfig.forceDisableTabletMode ? "Enable tablet mode" : "Disable tablet mode") : null, + LocaleController.getString(SharedConfig.useLNavigation ? R.string.AltNavigationDisable : R.string.AltNavigationEnable), + BuildVars.DEBUG_PRIVATE_VERSION ? LocaleController.getString(SharedConfig.isFloatingDebugActive ? R.string.FloatingDebugDisable : R.string.FloatingDebugEnable) : null, }; builder.setItems(items, (dialog, which) -> { @@ -3160,7 +3264,12 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. activity.finishAffinity(); // Finishes all activities. activity.startActivity(intent); // Start the launch activity System.exit(0); - + } else if (which == 21) { + SharedConfig.useLNavigation = !SharedConfig.useLNavigation; + SharedConfig.saveConfig(); + getParentActivity().recreate(); + } else if (which == 22) { + FloatingDebugController.setActive((LaunchActivity) getParentActivity(), !FloatingDebugController.isActive()); } }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); @@ -3182,7 +3291,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } return onMemberClick(participant, true); } else { - return processOnClickOrPress(position, view); + return processOnClickOrPress(position, view, view.getWidth() / 2f, (int) (view.getHeight() * .75f)); } } }); @@ -3245,7 +3354,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity(), resourcesProvider); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setMessage(LocaleController.getString("ClearSearch", R.string.ClearSearch)); - builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton).toUpperCase(), (dialogInterface, i) -> searchAdapter.clearRecent()); + builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton), (dialogInterface, i) -> searchAdapter.clearRecent()); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showDialog(builder.create()); return true; @@ -3258,7 +3367,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } } }); - searchListView.setAnimateEmptyView(true, 1); + searchListView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA_SCALE); emptyView = new StickerEmptyView(context, null, 1); emptyView.setAnimateLayoutChange(true); @@ -3367,9 +3476,17 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. info.setVisibleToUser(false); } } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + if (animatedEmojiDrawable != null) { + animatedEmojiDrawable.getImageReceiver().startAnimation(); + } + } }; avatarImage.getImageReceiver().setAllowDecodeSingleFrame(true); - avatarImage.setRoundRadius(AndroidUtilities.dp(21)); + avatarImage.setRoundRadius(getSmallAvatarRoundRadius()); avatarImage.setPivotX(0); avatarImage.setPivotY(0); avatarContainer.addView(avatarImage, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); @@ -3377,6 +3494,40 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (avatarBig != null) { return; } + if (isTopic) { + ArrayList topics = getMessagesController().getTopicsController().getTopics(chatId); + if (topics != null) { + TLRPC.TL_forumTopic currentTopic = null; + for (int i = 0; currentTopic == null && i < topics.size(); ++i) { + TLRPC.TL_forumTopic topic = topics.get(i); + if (topic != null && topic.id == topicId) { + currentTopic = topic; + } + } + if (currentTopic != null && currentTopic.icon_emoji_id != 0) { + long documentId = currentTopic.icon_emoji_id; + TLRPC.Document document = AnimatedEmojiDrawable.findDocument(currentAccount, documentId); + if (document == null) { + return; + } + TLRPC.InputStickerSet inputStickerSet = MessageObject.getInputStickerSet(document); + if (inputStickerSet == null) { + return; + } + TLRPC.TL_messages_stickerSet set = MediaDataController.getInstance(currentAccount).getStickerSet(inputStickerSet, false); + if (set == null || set.set == null) { + return; + } + BulletinFactory.of(ProfileActivity.this).createEmojiBulletin(document, AndroidUtilities.replaceTags(LocaleController.formatString("TopicContainsEmojiPackSingle", R.string.TopicContainsEmojiPackSingle, set.set.title)), LocaleController.getString("ViewAction", R.string.ViewAction), () -> { + ArrayList inputSets = new ArrayList<>(1); + inputSets.add(inputStickerSet); + EmojiPacksAlert alert = new EmojiPacksAlert(ProfileActivity.this, getParentActivity(), resourcesProvider, inputSets); + showDialog(alert); + }).show(); + } + } + return; + } if (!AndroidUtilities.isTablet() && !isInLandscapeMode && avatarImage.getImageReceiver().hasNotThumb() && !AndroidUtilities.isAccessibilityScreenReaderEnabled()) { openingAvatar = true; allowPullingDown = true; @@ -3401,7 +3552,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. openAvatar(); }); avatarImage.setOnLongClickListener(v -> { - if (avatarBig != null) { + if (avatarBig != null || isTopic) { return false; } openAvatar(); @@ -3444,7 +3595,9 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } overlaysView = new OverlaysView(context); avatarsViewPager = new ProfileGalleryView(context, userId != 0 ? userId : -chatId, actionBar, listView, avatarImage, getClassGuid(), overlaysView); - avatarsViewPager.setChatInfo(chatInfo); + if (!isTopic) { + avatarsViewPager.setChatInfo(chatInfo); + } avatarContainer2.addView(avatarsViewPager); avatarContainer2.addView(overlaysView); avatarImage.setAvatarsViewPager(avatarsViewPager); @@ -3516,7 +3669,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. onlineTextView[a].setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); } onlineTextView[a].setFocusable(a == 0); - avatarContainer2.addView(onlineTextView[a], LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 118, 0, a == 0 ? rightMargin - 12 : 8, 0)); + avatarContainer2.addView(onlineTextView[a], LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 118, 0, a == 0 ? rightMargin - (hasTitleExpanded ? 10 : 0) : 8, 0)); } avatarContainer2.addView(animatedStatusView); @@ -3624,7 +3777,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. avatarContainer.setScaleY(avatarScale); avatarContainer.setTranslationX(AndroidUtilities.lerp(avatarX, 0f, value)); avatarContainer.setTranslationY(AndroidUtilities.lerp((float) Math.ceil(avatarY), 0f, value)); - avatarImage.setRoundRadius((int) AndroidUtilities.lerp(AndroidUtilities.dpf2(21f), 0f, value)); + avatarImage.setRoundRadius((int) AndroidUtilities.lerp(getSmallAvatarRoundRadius(), 0f, value)); if (searchItem != null) { searchItem.setAlpha(1.0f - value); searchItem.setScaleY(1.0f - value); @@ -3816,6 +3969,16 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. return fragmentView; } + private int getSmallAvatarRoundRadius() { + if (chatId != 0) { + TLRPC.Chat chatLocal = getMessagesController().getChat(chatId); + if (chatLocal != null && chatLocal.forum) { + return AndroidUtilities.dp(16); + } + } + return AndroidUtilities.dp(21); + } + private void updateTtlIcon() { if (ttlIconView == null) { return; @@ -4023,7 +4186,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. setAvatarCell.getImageView().playAnimation(); } } else { - if (playProfileAnimation != 0 && parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2) instanceof ChatActivity) { + if (playProfileAnimation != 0 && parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 2) instanceof ChatActivity) { finishFragment(); } else { TLRPC.User user = getMessagesController().getUser(userId); @@ -4195,7 +4358,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. boolean[] needShowBulletin = new boolean[1]; ChatRightsEditActivity fragment = new ChatRightsEditActivity(user.id, chatId, adminRights, currentChat.default_banned_rights, bannedRights, rank, action, true, false, null) { @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (!isOpen && backward && needShowBulletin[0] && BulletinFactory.canShowBulletin(ProfileActivity.this)) { BulletinFactory.createPromoteToAdminBulletin(ProfileActivity.this, user.first_name).show(); } @@ -4279,7 +4442,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. presentFragment(fragment); } - private boolean processOnClickOrPress(final int position, final View view) { + private boolean processOnClickOrPress(final int position, final View view, final float x, final float y) { if (position == usernameRow || position == setUsernameRow) { final String username; if (userId != 0) { @@ -4290,7 +4453,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. username = user.username; } else if (chatId != 0) { final TLRPC.Chat chat = getMessagesController().getChat(chatId); - if (chat == null || chat.username == null) { + if (chat == null || !ChatObject.isPublic(chat)) { return false; } username = chat.username; @@ -4298,10 +4461,11 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. return false; } if (userId == 0) { - String link = "https://" + MessagesController.getInstance(UserConfig.selectedAccount).linkPrefix + "/" + username; + TLRPC.Chat chat = getMessagesController().getChat(chatId); + String link = "https://" + getLink(ChatObject.getPublicUsername(chat), topicId); showDialog(new ShareAlert(getParentActivity(), null, link, false, link, false) { @Override - protected void onSend(LongSparseArray dids, int count) { + protected void onSend(LongSparseArray dids, int count, TLRPC.TL_forumTopic topic) { AndroidUtilities.runOnUIThread(() -> { BulletinFactory.createInviteSentBulletin(getParentActivity(), contentView, dids.size(), dids.size() == 1 ? dids.valueAt(0).id : 0, count, getThemedColor(Theme.key_undo_background), getThemedColor(Theme.key_undo_infoColor)).show(); }, 250); @@ -4325,7 +4489,6 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. return false; } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity(), resourcesProvider); ArrayList items = new ArrayList<>(); ArrayList actions = new ArrayList<>(); List icons = new ArrayList<>(); @@ -4348,41 +4511,85 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. items.add(LocaleController.getString("Copy", R.string.Copy)); actions.add(PHONE_OPTION_COPY); - int[] iconsArr = new int[icons.size()]; - for (int i = 0; i < iconsArr.length; i++) { - iconsArr[i] = icons.get(i); - } - builder.setItems(items.toArray(new CharSequence[0]), iconsArr, (dialogInterface, i) -> { - i = actions.get(i); - switch (i) { - case PHONE_OPTION_CALL: - try { - Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:+" + user.phone)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - getParentActivity().startActivityForResult(intent, 500); - } catch (Exception e) { - FileLog.e(e); - } - break; - case PHONE_OPTION_COPY: - try { - android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = android.content.ClipData.newPlainText("label", "+" + user.phone); - clipboard.setPrimaryClip(clip); - if (AndroidUtilities.shouldShowClipboardToast()) { - BulletinFactory.of(this).createCopyBulletin(LocaleController.getString("PhoneCopied", R.string.PhoneCopied)).show(); - } - } catch (Exception e) { - FileLog.e(e); - } - break; - case PHONE_OPTION_TELEGRAM_CALL: - case PHONE_OPTION_TELEGRAM_VIDEO_CALL: - VoIPHelper.startCall(user, i == PHONE_OPTION_TELEGRAM_VIDEO_CALL, userInfo != null && userInfo.video_calls_available, getParentActivity(), userInfo, getAccountInstance()); - break; + AtomicReference popupWindowRef = new AtomicReference<>(); + ActionBarPopupWindow.ActionBarPopupWindowLayout popupLayout = new ActionBarPopupWindow.ActionBarPopupWindowLayout(getContext(), R.drawable.popup_fixed_alert, resourcesProvider) { + Path path = new Path(); + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + canvas.save(); + path.rewind(); + AndroidUtilities.rectTmp.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom()); + path.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(6), AndroidUtilities.dp(6), Path.Direction.CW); + canvas.clipPath(path); + boolean draw = super.drawChild(canvas, child, drawingTime); + canvas.restore(); + return draw; } - }); - showDialog(builder.create()); + }; + popupLayout.setFitItems(true); + + for (int i = 0; i < icons.size(); i++) { + int action = actions.get(i); + ActionBarMenuItem.addItem(popupLayout, icons.get(i), items.get(i), false, resourcesProvider).setOnClickListener(v -> { + popupWindowRef.get().dismiss(); + switch (action) { + case PHONE_OPTION_CALL: + try { + Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:+" + user.phone)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getParentActivity().startActivityForResult(intent, 500); + } catch (Exception e) { + FileLog.e(e); + } + break; + case PHONE_OPTION_COPY: + try { + android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); + android.content.ClipData clip = android.content.ClipData.newPlainText("label", "+" + user.phone); + clipboard.setPrimaryClip(clip); + if (AndroidUtilities.shouldShowClipboardToast()) { + BulletinFactory.of(this).createCopyBulletin(LocaleController.getString("PhoneCopied", R.string.PhoneCopied)).show(); + } + } catch (Exception e) { + FileLog.e(e); + } + break; + case PHONE_OPTION_TELEGRAM_CALL: + case PHONE_OPTION_TELEGRAM_VIDEO_CALL: + VoIPHelper.startCall(user, action == PHONE_OPTION_TELEGRAM_VIDEO_CALL, userInfo != null && userInfo.video_calls_available, getParentActivity(), userInfo, getAccountInstance()); + break; + } + }); + } + + ActionBarPopupWindow popupWindow = new ActionBarPopupWindow(popupLayout, LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT); + popupWindow.setPauseNotifications(true); + popupWindow.setDismissAnimationDuration(220); + popupWindow.setOutsideTouchable(true); + popupWindow.setClippingEnabled(true); + popupWindow.setAnimationStyle(R.style.PopupContextAnimation); + popupWindow.setFocusable(true); + popupLayout.measure(View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(1000), View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(1000), View.MeasureSpec.AT_MOST)); + popupWindow.setInputMethodMode(ActionBarPopupWindow.INPUT_METHOD_NOT_NEEDED); + popupWindow.getContentView().setFocusableInTouchMode(true); + popupWindowRef.set(popupWindow); + + float px = x, py = y; + View v = view; + while (v != getFragmentView()) { + px += v.getX(); + py += v.getY(); + v = (View) v.getParent(); + } + if (AndroidUtilities.isTablet()) { + View pv = parentLayout.getView(); + px += pv.getX() + pv.getPaddingLeft(); + py += pv.getY() + pv.getPaddingTop(); + } + px -= popupLayout.getMeasuredWidth() / 2f; + popupWindow.showAtLocation(getFragmentView(), 0, (int) px, (int) py); + popupWindow.dimBehind(); return true; } else if (position == channelInfoRow || position == userInfoRow || position == locationRow || position == bioRow) { if (position == bioRow && (userInfo == null || TextUtils.isEmpty(userInfo.about))) { @@ -4413,30 +4620,81 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (getParentActivity() == null) { return; } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity(), resourcesProvider); - builder.setItems(withTranslate[0] ? new CharSequence[]{LocaleController.getString("Copy", R.string.Copy), LocaleController.getString("TranslateMessage", R.string.TranslateMessage)} : new CharSequence[]{LocaleController.getString("Copy", R.string.Copy)}, withTranslate[0] ? new int[] {R.drawable.msg_copy, R.drawable.msg_translate} : new int[] {R.drawable.msg_copy}, (dialogInterface, i) -> { - try { - if (i == 0) { - AndroidUtilities.addToClipboard(finalText); - if (position == bioRow) { - BulletinFactory.of(this).createCopyBulletin(LocaleController.getString("BioCopied", R.string.BioCopied)).show(); - } else { - BulletinFactory.of(this).createCopyBulletin(LocaleController.getString("TextCopied", R.string.TextCopied)).show(); - } - } else if (i == 1) { - TranslateAlert.showAlert(fragmentView.getContext(), this, fromLanguage[0], toLang, finalText, false, span -> { - if (span != null) { - openUrl(span.getURL()); - return true; - } - return false; - }, null); - } - } catch (Exception e) { - FileLog.e(e); + CharSequence[] items = withTranslate[0] ? new CharSequence[]{LocaleController.getString("Copy", R.string.Copy), LocaleController.getString("TranslateMessage", R.string.TranslateMessage)} : new CharSequence[]{LocaleController.getString("Copy", R.string.Copy)}; + int[] icons = withTranslate[0] ? new int[] {R.drawable.msg_copy, R.drawable.msg_translate} : new int[] {R.drawable.msg_copy}; + + AtomicReference popupWindowRef = new AtomicReference<>(); + ActionBarPopupWindow.ActionBarPopupWindowLayout popupLayout = new ActionBarPopupWindow.ActionBarPopupWindowLayout(getContext(), R.drawable.popup_fixed_alert, resourcesProvider) { + Path path = new Path(); + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + canvas.save(); + path.rewind(); + AndroidUtilities.rectTmp.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom()); + path.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(6), AndroidUtilities.dp(6), Path.Direction.CW); + canvas.clipPath(path); + boolean draw = super.drawChild(canvas, child, drawingTime); + canvas.restore(); + return draw; } - }); - showDialog(builder.create()); + }; + popupLayout.setFitItems(true); + + for (int i = 0; i < icons.length; i++) { + int j = i; + ActionBarMenuItem.addItem(popupLayout, icons[i], items[i], false, resourcesProvider).setOnClickListener(v -> { + popupWindowRef.get().dismiss(); + try { + if (j == 0) { + AndroidUtilities.addToClipboard(finalText); + if (position == bioRow) { + BulletinFactory.of(this).createCopyBulletin(LocaleController.getString("BioCopied", R.string.BioCopied)).show(); + } else { + BulletinFactory.of(this).createCopyBulletin(LocaleController.getString("TextCopied", R.string.TextCopied)).show(); + } + } else if (j == 1) { + TranslateAlert.showAlert(fragmentView.getContext(), this, fromLanguage[0], toLang, finalText, false, span -> { + if (span != null) { + openUrl(span.getURL()); + return true; + } + return false; + }, null); + } + } catch (Exception e) { + FileLog.e(e); + } + }); + } + + ActionBarPopupWindow popupWindow = new ActionBarPopupWindow(popupLayout, LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT); + popupWindow.setPauseNotifications(true); + popupWindow.setDismissAnimationDuration(220); + popupWindow.setOutsideTouchable(true); + popupWindow.setClippingEnabled(true); + popupWindow.setAnimationStyle(R.style.PopupContextAnimation); + popupWindow.setFocusable(true); + popupLayout.measure(View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(1000), View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(1000), View.MeasureSpec.AT_MOST)); + popupWindow.setInputMethodMode(ActionBarPopupWindow.INPUT_METHOD_NOT_NEEDED); + popupWindow.getContentView().setFocusableInTouchMode(true); + popupWindowRef.set(popupWindow); + + float px = x, py = y; + View v = view; + while (v != getFragmentView()) { + px += v.getX(); + py += v.getY(); + v = (View) v.getParent(); + } + if (AndroidUtilities.isTablet()) { + View pv = parentLayout.getView(); + px += pv.getX() + pv.getPaddingLeft(); + py += pv.getY() + pv.getPaddingTop(); + } + px -= popupLayout.getMeasuredWidth() / 2f; + popupWindow.showAtLocation(getFragmentView(), 0, (int) px, (int) py); + popupWindow.dimBehind(); }; if (withTranslate[0]) { if (LanguageDetector.hasSupport()) { @@ -4444,7 +4702,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. fromLanguage[0] = fromLang; withTranslate[0] = fromLang != null && (!fromLang.equals(toLang) || fromLang.equals("und")) && ( translateButtonEnabled && !RestrictedLanguagesSelectActivity.getRestrictedLanguages().contains(fromLang) || - (currentChat != null && (currentChat.has_link || currentChat.username != null)) && ("uk".equals(fromLang) || "ru".equals(fromLang))); + (currentChat != null && (currentChat.has_link || ChatObject.isPublic(currentChat))) && ("uk".equals(fromLang) || "ru".equals(fromLang))); showMenu.run(); }, (error) -> { FileLog.e("mlkit: failed to detect language in selection", error); @@ -4456,7 +4714,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else { showMenu.run(); } - return !(view instanceof AboutLinkCell); + return true; } return false; } @@ -4483,7 +4741,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. req.filter = new TLRPC.TL_channelParticipantsRecent(); req.offset = reload ? 0 : participantsMap.size(); req.limit = 200; - int reqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + int reqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> getNotificationCenter().doOnIdle(() -> { if (error == null) { TLRPC.TL_channels_channelParticipants res = (TLRPC.TL_channels_channelParticipants) response; getMessagesController().putUsers(res.users, false); @@ -4513,8 +4771,9 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } } loadingUsers = false; + saveScrollPosition(); updateListAnimated(true); - }, delay)); + }), delay)); getConnectionsManager().bindRequestToGuid(reqId, classGuid); } @@ -4714,9 +4973,15 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. currentParticipants.add(chatInfo.participants.participants.get(i).user_id); } } - for (int a = 0, N = users.size(); a < N; a++) { + int N = users.size(); + int[] finished = new int[1]; + for (int a = 0; a < N; a++) { TLRPC.User user = users.get(a); - getMessagesController().addUserToChat(chatId, user, fwdCount, null, ProfileActivity.this, null); + getMessagesController().addUserToChat(chatId, user, fwdCount, null, ProfileActivity.this, () -> { + if (++finished[0] == N) { + BulletinFactory.of(ProfileActivity.this).createUsersAddedBulletin(users, currentChat).show(); + } + }); if (!currentParticipants.contains(user.id)) { if (chatInfo.participants == null) { chatInfo.participants = new TLRPC.TL_chatParticipants(); @@ -4781,7 +5046,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } setMediaHeaderVisible(mediaHeaderVisible); - if (extraHeight != newOffset) { + if (extraHeight != newOffset && !transitionAnimationInProress) { extraHeight = newOffset; topView.invalidate(); if (playProfileAnimation != 0) { @@ -5063,7 +5328,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. avatarScale = AndroidUtilities.lerp(1.0f, (42f + 42f + 18f) / 42f, animationProgress); - avatarImage.setRoundRadius((int) AndroidUtilities.lerp(AndroidUtilities.dpf2(21f), 0f, animationProgress)); + avatarImage.setRoundRadius((int) AndroidUtilities.lerp(getSmallAvatarRoundRadius(), 0f, animationProgress)); avatarContainer.setTranslationX(AndroidUtilities.lerp(avX, 0, animationProgress)); avatarContainer.setTranslationY(AndroidUtilities.lerp((float) Math.ceil(avY), 0f, animationProgress)); float extra = (avatarContainer.getMeasuredWidth() - AndroidUtilities.dp(42)) * avatarScale; @@ -5108,7 +5373,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. timeItem.setTranslationY(avatarContainer.getY() + AndroidUtilities.dp(15) + extra); } nameX = -21 * AndroidUtilities.density * diff; - nameY = (float) Math.floor(avatarY) + AndroidUtilities.dp(1.3f) + AndroidUtilities.dp(7) * diff; + nameY = (float) Math.floor(avatarY) + AndroidUtilities.dp(1.3f) + AndroidUtilities.dp(7) * diff + titleAnimationsYDiff * (1f - animationProgress); onlineX = -21 * AndroidUtilities.density * diff; onlineY = (float) Math.floor(avatarY) + AndroidUtilities.dp(24) + (float) Math.floor(11 * AndroidUtilities.density) * diff; for (int a = 0; a < nameTextView.length; a++) { @@ -5427,10 +5692,10 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. chatInfo = chatFull; if (mergeDialogId == 0 && chatInfo.migrated_from_chat_id != 0) { mergeDialogId = -chatInfo.migrated_from_chat_id; - getMediaDataController().getMediaCount(mergeDialogId, MediaDataController.MEDIA_PHOTOVIDEO, classGuid, true); + getMediaDataController().getMediaCount(mergeDialogId, topicId, MediaDataController.MEDIA_PHOTOVIDEO, classGuid, true); } fetchUsersFromChannelInfo(); - if (avatarsViewPager != null) { + if (avatarsViewPager != null && !isTopic) { avatarsViewPager.setChatInfo(chatInfo); } updateListAnimated(true); @@ -5458,7 +5723,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (uid == userId) { userInfo = (TLRPC.UserFull) args[1]; if (imageUpdater != null) { - if (!TextUtils.equals(userInfo.about, currentBio)) { + if (listAdapter != null && !TextUtils.equals(userInfo.about, currentBio)) { listAdapter.notifyItemChanged(bioRow); } } else { @@ -5515,6 +5780,10 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (prevRow1 != passwordSuggestionRow || prevRow2 != phoneSuggestionRow) { listAdapter.notifyDataSetChanged(); } + } else if (id == NotificationCenter.topicsDidLoaded) { + if (isTopic) { + updateProfileData(false); + } } } @@ -5661,7 +5930,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } @Override - protected void onBecomeFullyHidden() { + public void onBecomeFullyHidden() { if (undoView != null) { undoView.hide(true, 0); } @@ -5689,7 +5958,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. public boolean isFragmentOpened; @Override - protected void onTransitionAnimationStart(boolean isOpen, boolean backward) { + public void onTransitionAnimationStart(boolean isOpen, boolean backward) { isFragmentOpened = isOpen; if ((!isOpen && backward || isOpen && !backward) && playProfileAnimation != 0 && allowProfileAnimation && !isPulledDown) { openAnimationInProgress = true; @@ -5708,7 +5977,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen) { if (!backward) { if (playProfileAnimation != 0 && allowProfileAnimation) { @@ -5814,13 +6083,13 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. boolean profileTransitionInProgress; @Override - protected AnimatorSet onCustomTransitionAnimation(final boolean isOpen, final Runnable callback) { + public AnimatorSet onCustomTransitionAnimation(final boolean isOpen, final Runnable callback) { if (playProfileAnimation != 0 && allowProfileAnimation && !isPulledDown && !disableProfileAnimation) { if (timeItem != null) { timeItem.setAlpha(1.0f); } - if (parentLayout != null && parentLayout.fragmentsStack.size() >= 2) { - BaseFragment fragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2); + if (parentLayout != null && parentLayout.getFragmentStack().size() >= 2) { + BaseFragment fragment = parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 2); if (fragment instanceof ChatActivity) { previousTransitionFragment = (ChatActivity) fragment; } @@ -5909,7 +6178,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } boolean onlineTextCrosafade = false; - BaseFragment previousFragment = parentLayout.fragmentsStack.size() > 1 ? parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2) : null; + BaseFragment previousFragment = parentLayout.getFragmentStack().size() > 1 ? parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 2) : null; if (previousFragment instanceof ChatActivity) { ChatAvatarContainer avatarContainer = ((ChatActivity) previousFragment).getAvatarContainer(); if (avatarContainer.getSubtitleTextView().getLeftDrawable() != null || avatarContainer.statusMadeShorter[0]) { @@ -5966,7 +6235,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. animators.add(ObjectAnimator.ofFloat(ttlIconView, View.ALPHA, ttlIconView.getAlpha(), 0.0f)); } boolean crossfadeOnlineText = false; - BaseFragment previousFragment = parentLayout.fragmentsStack.size() > 1 ? parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2) : null; + BaseFragment previousFragment = parentLayout.getFragmentStack().size() > 1 ? parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 2) : null; if (previousFragment instanceof ChatActivity) { ChatAvatarContainer avatarContainer = ((ChatActivity) previousFragment).getAvatarContainer(); if (avatarContainer.getSubtitleTextView().getLeftDrawable() != null || avatarContainer.statusMadeShorter[0]) { @@ -6099,12 +6368,12 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. chatInfo = value; if (chatInfo != null && chatInfo.migrated_from_chat_id != 0 && mergeDialogId == 0) { mergeDialogId = -chatInfo.migrated_from_chat_id; - getMediaDataController().getMediaCounts(mergeDialogId, classGuid); + getMediaDataController().getMediaCounts(mergeDialogId, topicId, classGuid); } if (sharedMediaLayout != null) { sharedMediaLayout.setChatInfo(chatInfo); } - if (avatarsViewPager != null) { + if (avatarsViewPager != null && !isTopic) { avatarsViewPager.setChatInfo(chatInfo); } fetchUsersFromChannelInfo(); @@ -6199,6 +6468,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. sendMessageRow = -1; reportRow = -1; reportReactionRow = -1; + addToContactsRow = -1; emptyRow = -1; infoHeaderRow = -1; phoneRow = -1; @@ -6227,6 +6497,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. blockedUsersRow = -1; membersSectionRow = -1; sharedMediaRow = -1; + notificationsSimpleRow = -1; unblockRow = -1; joinRow = -1; @@ -6308,7 +6579,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. versionRow = rowCount++; } else { boolean hasInfo = userInfo != null && !TextUtils.isEmpty(userInfo.about) || user != null && !TextUtils.isEmpty(user.username); - boolean hasPhone = user != null && !TextUtils.isEmpty(user.phone); + boolean hasPhone = user != null && (!TextUtils.isEmpty(user.phone) || !TextUtils.isEmpty(vcardPhone)); infoHeaderRow = rowCount++; if (!isBot && (hasPhone || !hasInfo)) { @@ -6351,6 +6622,11 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. reportDividerRow = rowCount++; } + if (showAddToContacts) { + addToContactsRow = rowCount++; + reportDividerRow = rowCount++; + } + if (hasMedia || userInfo != null && userInfo.common_chats_count != 0) { sharedMediaRow = rowCount++; } else if (lastSectionRow == -1 && needSendMessage) { @@ -6359,8 +6635,18 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. lastSectionRow = rowCount++; } } + } else if (isTopic) { + infoHeaderRow = rowCount++; + if (ChatObject.isPublic(currentChat)) { + usernameRow = rowCount++; + } + notificationsSimpleRow = rowCount++; + infoSectionRow = rowCount++; + if (hasMedia) { + sharedMediaRow = rowCount++; + } } else if (chatId != 0) { - if (chatInfo != null && (!TextUtils.isEmpty(chatInfo.about) || chatInfo.location instanceof TLRPC.TL_channelLocation) || !TextUtils.isEmpty(currentChat.username)) { + if (chatInfo != null && (!TextUtils.isEmpty(chatInfo.about) || chatInfo.location instanceof TLRPC.TL_channelLocation) || ChatObject.isPublic(currentChat)) { if (LocaleController.isRTL && ChatObject.isChannel(currentChat) && chatInfo != null && !currentChat.megagroup && chatInfo.linked_chat_id != 0) { emptyRow = rowCount++; } @@ -6373,7 +6659,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. locationRow = rowCount++; } } - if (!TextUtils.isEmpty(currentChat.username)) { + if (ChatObject.isPublic(currentChat)) { usernameRow = rowCount++; } } @@ -6399,7 +6685,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } if (ChatObject.isChannel(currentChat)) { - if (chatInfo != null && currentChat.megagroup && chatInfo.participants != null && !chatInfo.participants.participants.isEmpty()) { + if (!isTopic && chatInfo != null && currentChat.megagroup && chatInfo.participants != null && !chatInfo.participants.participants.isEmpty()) { if (!ChatObject.isNotInChat(currentChat) && ChatObject.canAddUsers(currentChat) && chatInfo.participants_count < getMessagesController().maxMegagroupCount) { addMemberRow = rowCount++; } @@ -6431,6 +6717,10 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. sharedMediaLayout.setChatUsers(sortedUsers, chatInfo); } } + } else { + if (sharedMediaLayout != null) { + sharedMediaLayout.updateAdapters(); + } } if (lastSectionRow == -1 && currentChat.left && !currentChat.kicked) { @@ -6438,7 +6728,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. lastSectionRow = rowCount++; } } else if (chatInfo != null) { - if (!(chatInfo.participants instanceof TLRPC.TL_chatParticipantsForbidden)) { + if (!isTopic && chatInfo.participants != null && !(chatInfo.participants instanceof TLRPC.TL_chatParticipantsForbidden)) { if (ChatObject.canAddUsers(currentChat) || currentChat.default_banned_rights == null || !currentChat.default_banned_rights.invite_users) { addMemberRow = rowCount++; } @@ -6466,6 +6756,10 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. sharedMediaLayout.setChatUsers(sortedUsers, chatInfo); } } + } else { + if (sharedMediaLayout != null) { + sharedMediaLayout.updateAdapters(); + } } } @@ -6517,10 +6811,9 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private Drawable getEmojiStatusDrawable(TLRPC.EmojiStatus emojiStatus, boolean switchable, boolean animated, int a) { if (emojiStatusDrawable[a] == null) { - emojiStatusDrawable[a] = new AnimatedEmojiDrawable.SwapAnimatedEmojiDrawable(nameTextView[1], AndroidUtilities.dp(24), a == 0 ? AnimatedEmojiDrawable.CACHE_TYPE_EMOJI_STATUS : AnimatedEmojiDrawable.CACHE_TYPE_KEYBOARD); + emojiStatusDrawable[a] = new AnimatedEmojiDrawable.SwapAnimatedEmojiDrawable(nameTextView[a], AndroidUtilities.dp(24), a == 0 ? AnimatedEmojiDrawable.CACHE_TYPE_EMOJI_STATUS : AnimatedEmojiDrawable.CACHE_TYPE_KEYBOARD); } - final boolean reportSpam = MessagesController.getNotificationsSettings(currentAccount).getBoolean("dialog_bar_report" + dialogId, false); - if (emojiStatus instanceof TLRPC.TL_emojiStatus && !reportSpam) { + if (emojiStatus instanceof TLRPC.TL_emojiStatus) { emojiStatusDrawable[a].set(((TLRPC.TL_emojiStatus) emojiStatus).document_id, animated); } else if (emojiStatus instanceof TLRPC.TL_emojiStatusUntil && ((TLRPC.TL_emojiStatusUntil) emojiStatus).until > (int) (System.currentTimeMillis() / 1000) && !reportSpam) { emojiStatusDrawable[a].set(((TLRPC.TL_emojiStatusUntil) emojiStatus).document_id, animated); @@ -6578,6 +6871,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. onlineTextOverride = null; } + TLRPC.TL_forumTopic topic = null; + if (userId != 0) { TLRPC.User user = getMessagesController().getUser(userId); if (user == null) { @@ -6673,7 +6968,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. rightIconIsPremium = true; rightIcon = getEmojiStatusDrawable(null, false, false, a); nameTextViewRightDrawableContentDescription = LocaleController.getString("AccDescrPremium", R.string.AccDescrPremium); - } else if (getMessagesController().isDialogMuted(dialogId != 0 ? dialogId : userId)) { + } else if (getMessagesController().isDialogMuted(dialogId != 0 ? dialogId : userId, topicId)) { rightIcon = getThemedDrawable(Theme.key_drawable_muteIconDrawable); nameTextViewRightDrawableContentDescription = LocaleController.getString("NotificationsMuted", R.string.NotificationsMuted); } else { @@ -6773,21 +7068,27 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. chat = currentChat; } + if (isTopic) { + topic = getMessagesController().getTopicsController().findTopic(chatId, topicId); + } + String statusString; String profileStatusString; if (ChatObject.isChannel(chat)) { - if (chatInfo == null || !currentChat.megagroup && (chatInfo.participants_count == 0 || ChatObject.hasAdminRights(currentChat) || chatInfo.can_view_participants)) { + if (!isTopic && (chatInfo == null || !currentChat.megagroup && (chatInfo.participants_count == 0 || ChatObject.hasAdminRights(currentChat) || chatInfo.can_view_participants))) { if (currentChat.megagroup) { statusString = profileStatusString = LocaleController.getString("Loading", R.string.Loading).toLowerCase(); } else { - if ((chat.flags & TLRPC.CHAT_FLAG_IS_PUBLIC) != 0) { + if (ChatObject.isPublic(chat)) { statusString = profileStatusString = LocaleController.getString("ChannelPublic", R.string.ChannelPublic).toLowerCase(); } else { statusString = profileStatusString = LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate).toLowerCase(); } } } else { - if (currentChat.megagroup) { + if (isTopic) { + statusString = profileStatusString = LocaleController.formatString("TopicProfileStatus", R.string.TopicProfileStatus, chat.title); + } else if (currentChat.megagroup) { if (onlineCount > 1 && chatInfo.participants_count != 0) { statusString = String.format("%s, %s", LocaleController.formatPluralString("Members", chatInfo.participants_count), LocaleController.formatPluralString("OnlineCount", Math.min(onlineCount, chatInfo.participants_count))); profileStatusString = String.format("%s, %s", LocaleController.formatPluralStringComma("Members", chatInfo.participants_count), LocaleController.formatPluralStringComma("OnlineCount", Math.min(onlineCount, chatInfo.participants_count))); @@ -6795,7 +7096,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (chatInfo.participants_count == 0) { if (chat.has_geo) { statusString = profileStatusString = LocaleController.getString("MegaLocation", R.string.MegaLocation).toLowerCase(); - } else if (!TextUtils.isEmpty(chat.username)) { + } else if (ChatObject.isPublic(chat)) { statusString = profileStatusString = LocaleController.getString("MegaPublic", R.string.MegaPublic).toLowerCase(); } else { statusString = profileStatusString = LocaleController.getString("MegaPrivate", R.string.MegaPrivate).toLowerCase(); @@ -6824,7 +7125,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. statusString = profileStatusString = LocaleController.getString("YouLeft", R.string.YouLeft); } else { int count = chat.participants_count; - if (chatInfo != null) { + if (chatInfo != null && chatInfo.participants != null) { count = chatInfo.participants.participants.size(); } if (count != 0 && onlineCount > 1) { @@ -6840,7 +7141,16 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (nameTextView[a] == null) { continue; } - if (chat.title != null) { + if (isTopic) { + CharSequence title = topic.title; + try { + title = Emoji.replaceEmoji(title, nameTextView[a].getPaint().getFontMetricsInt(), AndroidUtilities.dp(24), false); + } catch (Exception ignore) { + } + if (nameTextView[a].setText(title)) { + changed = true; + } + } else if (chat.title != null) { CharSequence title = chat.title; try { title = Emoji.replaceEmoji(title, nameTextView[a].getPaint().getFontMetricsInt(), AndroidUtilities.dp(24), false); @@ -6868,7 +7178,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. nameTextView[a].setRightDrawable(getScamDrawable(chat.scam ? 0 : 1)); } else if (chat.verified) { nameTextView[a].setRightDrawable(getVerifiedCrossfadeDrawable()); - } else if (getMessagesController().isDialogMuted(-chatId)) { + } else if (getMessagesController().isDialogMuted(-chatId, topicId)) { nameTextView[a].setRightDrawable(getThemedDrawable(Theme.key_drawable_muteIconDrawable)); } else { nameTextView[a].setRightDrawable(null); @@ -6877,7 +7187,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (a == 0 && onlineTextOverride != null) { onlineTextView[a].setText(onlineTextOverride); } else { - if (currentChat.megagroup && chatInfo != null && onlineCount > 0) { + if ((currentChat.megagroup && chatInfo != null && onlineCount > 0) || isTopic) { onlineTextView[a].setText(a == 0 ? statusString : profileStatusString); } else if (a == 0 && ChatObject.isChannel(currentChat) && chatInfo != null && chatInfo.participants_count != 0 && (currentChat.megagroup || currentChat.broadcast)) { int[] result = new int[1]; @@ -6886,7 +7196,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (chatInfo.participants_count == 0) { if (chat.has_geo) { onlineTextView[a].setText(LocaleController.getString("MegaLocation", R.string.MegaLocation).toLowerCase()); - } else if (!TextUtils.isEmpty(chat.username)) { + } else if (ChatObject.isPublic(chat)) { onlineTextView[a].setText(LocaleController.getString("MegaPublic", R.string.MegaPublic).toLowerCase()); } else { onlineTextView[a].setText(LocaleController.getString("MegaPrivate", R.string.MegaPrivate).toLowerCase()); @@ -6907,13 +7217,25 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } TLRPC.FileLocation photoBig = null; - if (chat.photo != null) { + if (chat.photo != null && !isTopic) { photoBig = chat.photo.photo_big; } - avatarDrawable.setInfo(chat); - final ImageLocation imageLocation = ImageLocation.getForUserOrChat(chat, ImageLocation.TYPE_BIG); - final ImageLocation thumbLocation = ImageLocation.getForUserOrChat(chat, ImageLocation.TYPE_SMALL); - final ImageLocation videoLocation = avatarsViewPager.getCurrentVideoLocation(thumbLocation, imageLocation); + + final ImageLocation imageLocation; + final ImageLocation thumbLocation; + final ImageLocation videoLocation; + if (isTopic) { + imageLocation = null; + thumbLocation = null; + videoLocation = null; + ForumUtilities.setTopicIcon(avatarImage, topic, true); + } else { + avatarDrawable.setInfo(chat); + imageLocation = ImageLocation.getForUserOrChat(chat, ImageLocation.TYPE_BIG); + thumbLocation = ImageLocation.getForUserOrChat(chat, ImageLocation.TYPE_SMALL); + videoLocation = avatarsViewPager.getCurrentVideoLocation(thumbLocation, imageLocation); + } + boolean initied = avatarsViewPager.initIfEmpty(imageLocation, thumbLocation, reload); if ((imageLocation == null || initied) && isPulledDown) { final View view = layoutManager.findViewByPosition(0); @@ -6927,7 +7249,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else { filter = null; } - if (avatarBig == null) { + if (avatarBig == null && !isTopic) { avatarImage.setImage(videoLocation, filter, thumbLocation, "50_50", avatarDrawable, chat); } if (imageLocation != null && (prevLoadedImageLocation == null || imageLocation.photoId != prevLoadedImageLocation.photoId)) { @@ -7018,19 +7340,25 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else if (chatId != 0) { TLRPC.Chat chat = getMessagesController().getChat(chatId); hasVoiceChatItem = false; - if (ChatObject.canUserDoAdminAction(chat, ChatObject.ACTION_DELETE_MESSAGES)) { + if (topicId == 0 && ChatObject.canUserDoAdminAction(chat, ChatObject.ACTION_DELETE_MESSAGES)) { createAutoDeleteItem(context); } if (ChatObject.isChannel(chat)) { - if (ChatObject.hasAdminRights(chat) || chat.megagroup && ChatObject.canChangeChatInfo(chat)) { - editItemVisible = true; + if (isTopic) { + if ((ChatObject.hasAdminRights(chat) || chat.megagroup) && ChatObject.canManageTopic(currentAccount, chat, topicId)) { + editItemVisible = true; + } + } else { + if ((ChatObject.hasAdminRights(chat) || chat.megagroup) && ChatObject.canChangeChatInfo(chat)) { + editItemVisible = true; + } } if (chatInfo != null) { if (ChatObject.canManageCalls(chat) && chatInfo.call == null) { otherItem.addSubItem(call_item, R.drawable.msg_voicechat, chat.megagroup && !chat.gigagroup ? LocaleController.getString("StartVoipChat", R.string.StartVoipChat) : LocaleController.getString("StartVoipChannel", R.string.StartVoipChannel)); hasVoiceChatItem = true; } - if (chatInfo.can_view_stats) { + if (chatInfo.can_view_stats && topicId == 0) { otherItem.addSubItem(statistics, R.drawable.msg_stats, LocaleController.getString("Statistics", R.string.Statistics)); } ChatObject.Call call = getMessagesController().getGroupCall(chatId, false); @@ -7043,7 +7371,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. otherItem.addSubItem(leave_group, R.drawable.msg_leave, LocaleController.getString("LeaveMegaMenu", R.string.LeaveMegaMenu)); } } else { - if (!TextUtils.isEmpty(chat.username)) { + if (ChatObject.isPublic(chat)) { otherItem.addSubItem(share, R.drawable.msg_share, LocaleController.getString("BotShare", R.string.BotShare)); } if (chatInfo != null && chatInfo.linked_chat_id != 0) { @@ -7071,7 +7399,9 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } otherItem.addSubItem(leave_group, R.drawable.msg_leave, LocaleController.getString("DeleteAndExit", R.string.DeleteAndExit)); } - otherItem.addSubItem(add_shortcut, R.drawable.msg_home, LocaleController.getString("AddShortcut", R.string.AddShortcut)); + if (topicId == 0) { + otherItem.addSubItem(add_shortcut, R.drawable.msg_home, LocaleController.getString("AddShortcut", R.string.AddShortcut)); + } } if (imageUpdater != null) { @@ -7216,8 +7546,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } @Override - public void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param) { - long did = dids.get(0); + public void didSelectDialogs(DialogsActivity fragment, ArrayList dids, CharSequence message, boolean param) { + long did = dids.get(0).dialogId; Bundle args = new Bundle(); args.putBoolean("scrollToTopOnResume", true); if (DialogObject.isEncryptedDialog(did)) { @@ -7756,7 +8086,9 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. VIEW_TYPE_VERSION = 14, VIEW_TYPE_SUGGESTION = 15, VIEW_TYPE_ADDTOGROUP_INFO = 17, - VIEW_TYPE_PREMIUM_TEXT_CELL = 18; + VIEW_TYPE_PREMIUM_TEXT_CELL = 18, + VIEW_TYPE_TEXT_DETAIL_MULTILINE = 19, + VIEW_TYPE_NOTIFICATIONS_CHECK_SIMPLE = 20; private Context mContext; @@ -7772,13 +8104,13 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. view = new HeaderCell(mContext, 23, resourcesProvider); break; } - case VIEW_TYPE_TEXT_DETAIL: { - final TextDetailCell textDetailCell = new TextDetailCell(mContext, resourcesProvider); + case VIEW_TYPE_TEXT_DETAIL_MULTILINE: + case VIEW_TYPE_TEXT_DETAIL: + final TextDetailCell textDetailCell = new TextDetailCell(mContext, resourcesProvider, viewType == VIEW_TYPE_TEXT_DETAIL_MULTILINE); textDetailCell.setContentDescriptionValueFirst(true); textDetailCell.setImageClickListener(ProfileActivity.this::onTextDetailCellImageClicked); view = textDetailCell; break; - } case VIEW_TYPE_ABOUT_LINK: { view = aboutLinkCell = new AboutLinkCell(mContext, ProfileActivity.this, resourcesProvider) { @Override @@ -7811,6 +8143,10 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. view = new NotificationsCheckCell(mContext, 23, 70, false, resourcesProvider); break; } + case VIEW_TYPE_NOTIFICATIONS_CHECK_SIMPLE: { + view = new TextCheckCell(mContext, resourcesProvider); + break; + } case VIEW_TYPE_SHADOW: { view = new ShadowSectionCell(mContext, resourcesProvider); break; @@ -7979,6 +8315,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. headerCell.setText(LocaleController.getString("SettingsDebug", R.string.SettingsDebug)); } break; + case VIEW_TYPE_TEXT_DETAIL_MULTILINE: case VIEW_TYPE_TEXT_DETAIL: TextDetailCell detailCell = (TextDetailCell) holder.itemView; if (position == usernameRow) { @@ -7991,26 +8328,59 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (position == phoneRow) { String text; final TLRPC.User user = getMessagesController().getUser(userId); - if (!TextUtils.isEmpty(user.phone)) { + if (user != null && !TextUtils.isEmpty(vcardPhone)) { + text = PhoneFormat.getInstance().format("+" + vcardPhone); + } else if (!TextUtils.isEmpty(user.phone)) { text = PhoneFormat.getInstance().format("+" + user.phone); } else { text = LocaleController.getString("PhoneHidden", R.string.PhoneHidden); } detailCell.setTextAndValue(text, LocaleController.getString("PhoneMobile", R.string.PhoneMobile), false); } else if (position == usernameRow) { - String text; + String text, username = null; + CharSequence value; + boolean isUser = false; + ArrayList usernames = new ArrayList<>(); if (userId != 0) { + isUser = true; final TLRPC.User user = getMessagesController().getUser(userId); - if (user != null && !TextUtils.isEmpty(user.username)) { - text = "@" + user.username; - } else { - text = "-"; + if (user != null) { + usernames.addAll(user.usernames); } - detailCell.setTextAndValue(text, LocaleController.getString("Username", R.string.Username), false); + if (user != null && !TextUtils.isEmpty(user.username)) { + username = user.username; + } + usernames = user == null ? new ArrayList<>() : new ArrayList<>(user.usernames); + value = LocaleController.getString("Username", R.string.Username); } else if (currentChat != null) { TLRPC.Chat chat = getMessagesController().getChat(chatId); - detailCell.setTextAndValue(getMessagesController().linkPrefix + "/" + chat.username, LocaleController.getString("InviteLink", R.string.InviteLink), false); + username = chat.username; + usernames.addAll(chat.usernames); + value = LocaleController.getString("InviteLink", R.string.InviteLink); + } else { + text = ""; + value = ""; + usernames = new ArrayList<>(); } + if (TextUtils.isEmpty(username) && usernames != null) { + for (int i = 0; i < usernames.size(); ++i) { + TLRPC.TL_username u = usernames.get(i); + if (u != null && u.active && !TextUtils.isEmpty(u.username)) { + username = u.username; + break; + } + } + } + if (isUser) { + if (username != null) { + text = "@" + username; + } else { + text = "—"; + } + } else { + text = getLink(username, topicId); + } + detailCell.setTextAndValue(text, alsoUsernamesString(username, usernames, value), false); } else if (position == locationRow) { if (chatInfo != null && chatInfo.location instanceof TLRPC.TL_channelLocation) { TLRPC.TL_channelLocation location = (TLRPC.TL_channelLocation) chatInfo.location; @@ -8028,13 +8398,30 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. detailCell.setContentDescriptionValueFirst(false); } else if (position == setUsernameRow) { TLRPC.User user = UserConfig.getInstance(currentAccount).getCurrentUser(); - String value; - if (user != null && !TextUtils.isEmpty(user.username)) { - value = "@" + user.username; + String text = ""; + CharSequence value = LocaleController.getString("Username", R.string.Username); + String username = null; + if (user != null && user.usernames.size() > 0) { + for (int i = 0; i < user.usernames.size(); ++i) { + TLRPC.TL_username u = user.usernames.get(i); + if (u != null && u.active && !TextUtils.isEmpty(u.username)) { + username = u.username; + break; + } + } + if (username == null) { + username = user.username; + } + text = "@" + username; + value = alsoUsernamesString(username, user.usernames, value); } else { - value = LocaleController.getString("UsernameEmpty", R.string.UsernameEmpty); + if (user != null && !TextUtils.isEmpty(user.username)) { + text = "@" + (username = user.username); + } else { + text = LocaleController.getString("UsernameEmpty", R.string.UsernameEmpty); + } } - detailCell.setTextAndValue(value, LocaleController.getString("Username", R.string.Username), true); + detailCell.setTextAndValue(text, value, true); detailCell.setContentDescriptionValueFirst(true); } detailCell.setTag(position); @@ -8063,15 +8450,6 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } aboutLinkCell.setMoreButtonDisabled(true); } - if (position == bioRow) { - aboutLinkCell.setOnClickListener(e -> { - if (userInfo != null) { - presentFragment(new ChangeBioActivity()); - } - }); - } else { - aboutLinkCell.setOnClickListener(e -> processOnClickOrPress(position, aboutLinkCell)); - } break; case VIEW_TYPE_PREMIUM_TEXT_CELL: case VIEW_TYPE_TEXT: @@ -8086,7 +8464,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else { value = LocaleController.formatTTLString(encryptedChat.ttl); } - textCell.setTextAndValue(LocaleController.getString("MessageLifetime", R.string.MessageLifetime), value, false); + textCell.setTextAndValue(LocaleController.getString("MessageLifetime", R.string.MessageLifetime), value, false,false); } else if (position == unblockRow) { textCell.setText(LocaleController.getString("Unblock", R.string.Unblock), false); textCell.setColors(null, Theme.key_windowBackgroundWhiteRedText5); @@ -8137,6 +8515,9 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. textCell.setTextAndIcon(LocaleController.getString("AddMember", R.string.AddMember), R.drawable.msg_contact_add, membersSectionRow == -1); } else if (position == sendMessageRow) { textCell.setText(LocaleController.getString("SendMessageLocation", R.string.SendMessageLocation), true); + } else if (position == addToContactsRow) { + textCell.setText(LocaleController.getString(R.string.AddToContacts), false); + textCell.setColors(null, Theme.key_windowBackgroundWhiteBlackText); } else if (position == reportReactionRow) { TLRPC.Chat chat = getMessagesController().getChat(-reportReactionFromDialogId); if (chat != null && ChatObject.canBlockUsers(chat)) { @@ -8210,12 +8591,12 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else { did = -chatId; } - + String key = NotificationsController.getSharedPrefKey(did, topicId); boolean enabled = false; - boolean custom = preferences.getBoolean("custom_" + did, false); - boolean hasOverride = preferences.contains("notify2_" + did); - int value = preferences.getInt("notify2_" + did, 0); - int delta = preferences.getInt("notifyuntil_" + did, 0); + boolean custom = preferences.getBoolean("custom_" + key, false); + boolean hasOverride = preferences.contains("notify2_" + key); + int value = preferences.getInt("notify2_" + key, 0); + int delta = preferences.getInt("notifyuntil_" + key, 0); String val; if (value == 3 && delta != Integer.MAX_VALUE) { delta -= getConnectionsManager().getCurrentTime(); @@ -8254,6 +8635,9 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (val == null) { val = LocaleController.getString("NotificationsOff", R.string.NotificationsOff); } + if (notificationsExceptionTopics != null && !notificationsExceptionTopics.isEmpty()) { + val = String.format(Locale.US, LocaleController.getPluralString("NotificationTopicExceptionsDesctription", notificationsExceptionTopics.size()), val, notificationsExceptionTopics.size()); + } checkCell.setAnimationsEnabled(fragmentOpened); checkCell.setTextAndValueAndCheck(LocaleController.getString("Notifications", R.string.Notifications), val, enabled, false); } @@ -8321,6 +8705,81 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. addToGroupInfo.setBackground(Theme.getThemedDrawable(mContext, R.drawable.greydivider, getThemedColor(Theme.key_windowBackgroundGrayShadow))); addToGroupInfo.setText(LocaleController.getString("BotAddToGroupOrChannelInfo", R.string.BotAddToGroupOrChannelInfo)); break; + case VIEW_TYPE_NOTIFICATIONS_CHECK_SIMPLE: + TextCheckCell textCheckCell = (TextCheckCell) holder.itemView; + textCheckCell.setTextAndCheck(LocaleController.getString("Notifications", R.string.Notifications), !getMessagesController().isDialogMuted(getDialogId(), topicId), false); + break; + } + } + + private CharSequence alsoUsernamesString(String originalUsername, ArrayList alsoUsernames, CharSequence fallback) { + if (alsoUsernames == null) { + return fallback; + } + alsoUsernames = new ArrayList<>(alsoUsernames); + for (int i = 0; i < alsoUsernames.size(); ++i) { + if ( + !alsoUsernames.get(i).active || + originalUsername != null && originalUsername.equals(alsoUsernames.get(i).username) + ) { + alsoUsernames.remove(i--); + } + } + if (alsoUsernames.size() > 0) { + SpannableStringBuilder usernames = new SpannableStringBuilder(); + for (int i = 0; i < alsoUsernames.size(); ++i) { + final String usernameRaw = alsoUsernames.get(i).username; + SpannableString username = new SpannableString("@" + usernameRaw); + username.setSpan(new ClickableSpan() { + @Override + public void onClick(@NonNull View view) { + String urlFinal = getMessagesController().linkPrefix + "/" + usernameRaw; + if (currentChat == null || !currentChat.noforwards) { + AndroidUtilities.addToClipboard(urlFinal); + undoView.showWithAction(0, UndoView.ACTION_USERNAME_COPIED, null); + } +// BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity(), false, resourcesProvider); +// String formattedUrl = urlFinal; +// try { +// formattedUrl = URLDecoder.decode(urlFinal.replaceAll("\\+", "%2b"), "UTF-8"); +// } catch (Exception e) { +// FileLog.e(e); +// } +// builder.setTitle(formattedUrl); +// builder.setTitleMultipleLines(true); +// boolean noforwards = currentChat != null && currentChat.noforwards; +// builder.setItems(noforwards ? new CharSequence[] {LocaleController.getString("Open", R.string.Open)} : new CharSequence[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy)}, (dialog, which) -> { +// if (which == 0) { +// Browser.openUrl(getParentActivity(), urlFinal); +// } else if (which == 1) { +// AndroidUtilities.addToClipboard(urlFinal); +// undoView.showWithAction(0, UndoView.ACTION_LINK_COPIED, null); +// } +// }); +// showDialog(builder.create()); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + ds.setUnderlineText(false); + } + }, 0, username.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + username.setSpan(new ForegroundColorSpan(getThemedColor(Theme.key_chat_messageLinkIn)), 0, username.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + usernames.append(username); + if (i < alsoUsernames.size() - 1) { + usernames.append(", "); + } + } + String string = LocaleController.getString("UsernameAlso", R.string.UsernameAlso); + SpannableStringBuilder finalString = new SpannableStringBuilder(string); + final String toFind = "%1$s"; + int index = string.indexOf(toFind); + if (index >= 0) { + finalString.replace(index, index + toFind.length(), usernames); + } + return finalString; + } else { + return fallback; } } @@ -8368,9 +8827,10 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (position == infoHeaderRow || position == membersHeaderRow || position == settingsSectionRow2 || position == numberSectionRow || position == helpHeaderRow || position == debugHeaderRow) { return VIEW_TYPE_HEADER; - } else if (position == phoneRow || position == usernameRow || position == locationRow || - position == numberRow || position == setUsernameRow) { + } else if (position == phoneRow || position == locationRow || position == numberRow) { return VIEW_TYPE_TEXT_DETAIL; + } else if (position == usernameRow || position == setUsernameRow) { + return VIEW_TYPE_TEXT_DETAIL_MULTILINE; } else if (position == userInfoRow || position == channelInfoRow || position == bioRow) { return VIEW_TYPE_ABOUT_LINK; } else if (position == settingsTimerRow || position == settingsKeyRow || position == reportRow || position == reportReactionRow || @@ -8380,13 +8840,16 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. position == languageRow || position == dataRow || position == chatRow || position == questionRow || position == devicesRow || position == filtersRow || position == stickersRow || position == faqRow || position == policyRow || position == sendLogsRow || position == sendLastLogsRow || - position == clearLogsRow || position == switchBackendRow || position == setAvatarRow || position == addToGroupButtonRow) { + position == clearLogsRow || position == switchBackendRow || position == setAvatarRow || position == addToGroupButtonRow || + position == addToContactsRow) { return VIEW_TYPE_TEXT; } else if (position == notificationsDividerRow) { return VIEW_TYPE_DIVIDER; } else if (position == notificationsRow) { return VIEW_TYPE_NOTIFICATIONS_CHECK; - } else if (position == infoSectionRow || position == lastSectionRow || position == membersSectionRow || + } else if (position == notificationsSimpleRow) { + return VIEW_TYPE_NOTIFICATIONS_CHECK_SIMPLE; + }else if (position == infoSectionRow || position == lastSectionRow || position == membersSectionRow || position == secretSettingsSectionRow || position == settingsSectionRow || position == devicesSectionRow || position == helpSectionCell || position == setAvatarSectionRow || position == passwordSuggestionSectionRow || position == phoneSuggestionSectionRow || position == premiumSectionsRow || position == reportDividerRow) { @@ -9006,8 +9469,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. fragment.setSearchString(url); presentFragment(fragment); } else if (url.startsWith("/")) { - if (parentLayout.fragmentsStack.size() > 1) { - BaseFragment previousFragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2); + if (parentLayout.getFragmentStack().size() > 1) { + BaseFragment previousFragment = parentLayout.getFragmentStack().get(parentLayout.getFragmentStack().size() - 2); if (previousFragment instanceof ChatActivity) { finishFragment(); ((ChatActivity) previousFragment).chatActivityEnterView.setCommand(null, url, false, false); @@ -9299,7 +9762,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } @Override - protected void onBecomeFullyVisible() { + public void onBecomeFullyVisible() { super.onBecomeFullyVisible(); try { @@ -9442,6 +9905,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. put(++pointer, sendMessageRow, sparseIntArray); put(++pointer, reportRow, sparseIntArray); put(++pointer, reportReactionRow, sparseIntArray); + put(++pointer, addToContactsRow, sparseIntArray); put(++pointer, settingsTimerRow, sparseIntArray); put(++pointer, settingsKeyRow, sparseIntArray); put(++pointer, secretSettingsSectionRow, sparseIntArray); @@ -9458,6 +9922,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. put(++pointer, addToGroupInfoRow, sparseIntArray); put(++pointer, joinRow, sparseIntArray); put(++pointer, lastSectionRow, sparseIntArray); + put(++pointer, notificationsSimpleRow, sparseIntArray); } private void put(int id, int position, SparseIntArray sparseIntArray) { @@ -9482,4 +9947,12 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } return ColorUtils.calculateLuminance(color) > 0.7f; } + + public String getLink(String username, int topicId) { + String link = getMessagesController().linkPrefix + "/" + username; + if (topicId != 0) { + link += "/" + topicId; + } + return link; + } } \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java index f854a711c..4fafb3120 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java @@ -63,6 +63,7 @@ import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Cells.UserCell2; import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.ChatAvatarContainer; +import org.telegram.ui.Components.Forum.ForumUtilities; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RecyclerListView; @@ -76,6 +77,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi private Theme.ResourcesProvider resourcesProvider; private long dialogId; + private int topicId; private boolean addingException; @@ -127,6 +129,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi super(args); this.resourcesProvider = resourcesProvider; dialogId = args.getLong("dialog_id"); + topicId = args.getInt("topic_id"); addingException = args.getBoolean("exception", false); } @@ -142,7 +145,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi avatarSectionRow = -1; } generalRow = rowCount++; - if (addingException) { + if (addingException || topicId != 0) { enableRow = rowCount++; } else { enableRow = -1; @@ -214,8 +217,9 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi } else { SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); - boolean hasOverride = preferences.contains("notify2_" + dialogId); - int value = preferences.getInt("notify2_" + dialogId, 0); + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); + boolean hasOverride = preferences.contains("notify2_" + key); + int value = preferences.getInt("notify2_" + key, 0); if (value == 0) { if (hasOverride) { notificationsEnabled = true; @@ -239,7 +243,8 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi public void onFragmentDestroy() { super.onFragmentDestroy(); if (!needReset) { - MessagesController.getNotificationsSettings(currentAccount).edit().putBoolean("custom_" + dialogId, true).apply(); + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); + MessagesController.getNotificationsSettings(currentAccount).edit().putBoolean("custom_" + key, true).apply(); } NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.notificationsSettingsUpdated); } @@ -254,45 +259,52 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_avatar_actionBarSelectorBlue, resourcesProvider), false); actionBar.setItemsColor(Theme.getColor(Theme.key_actionBarDefaultIcon, resourcesProvider), false); actionBar.setBackButtonImage(R.drawable.ic_ab_back); + + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override public void onItemClick(int id) { if (id == -1) { if (!addingException && notificationsEnabled) { SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); - preferences.edit().putInt("notify2_" + dialogId, 0).apply(); + preferences.edit().putInt("notify2_" + key, 0).apply(); } } else if (id == done_button) { SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); SharedPreferences.Editor editor = preferences.edit(); - editor.putBoolean("custom_" + dialogId, true); + editor.putBoolean("custom_" + key, true); TLRPC.Dialog dialog = MessagesController.getInstance(currentAccount).dialogs_dict.get(dialogId); if (notificationsEnabled) { - editor.putInt("notify2_" + dialogId, 0); - MessagesStorage.getInstance(currentAccount).setDialogFlags(dialogId, 0); - if (dialog != null) { - dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + editor.putInt("notify2_" + key, 0); + if (topicId == 0) { + MessagesStorage.getInstance(currentAccount).setDialogFlags(dialogId, 0); + if (dialog != null) { + dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + } } } else { - editor.putInt("notify2_" + dialogId, 2); - NotificationsController.getInstance(currentAccount).removeNotificationsForDialog(dialogId); - MessagesStorage.getInstance(currentAccount).setDialogFlags(dialogId, 1); - if (dialog != null) { - dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); - dialog.notify_settings.mute_until = Integer.MAX_VALUE; + editor.putInt("notify2_" + key, 2); + if (topicId == 0) { + NotificationsController.getInstance(currentAccount).removeNotificationsForDialog(dialogId); + MessagesStorage.getInstance(currentAccount).setDialogFlags(dialogId, 1); + if (dialog != null) { + dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + dialog.notify_settings.mute_until = Integer.MAX_VALUE; + } } } editor.apply(); - NotificationsController.getInstance(currentAccount).updateServerNotificationsSettings(dialogId); + NotificationsController.getInstance(currentAccount).updateServerNotificationsSettings(dialogId, topicId); if (delegate != null) { NotificationsSettingsActivity.NotificationException exception = new NotificationsSettingsActivity.NotificationException(); exception.did = dialogId; exception.hasCustom = true; - exception.notify = preferences.getInt("notify2_" + dialogId, 0); + exception.notify = preferences.getInt("notify2_" + key, 0); if (exception.notify != 0) { - exception.muteUntil = preferences.getInt("notifyuntil_" + dialogId, 0); + exception.muteUntil = preferences.getInt("notifyuntil_" + key, 0); } delegate.didCreateNewException(exception); } @@ -307,9 +319,15 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi actionBar.addView(avatarContainer, 0, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, !inPreviewMode ? 56 : 0, 0, 40, 0)); actionBar.setAllowOverlayTitle(false); if (dialogId < 0) { - TLRPC.Chat chatLocal = getMessagesController().getChat(-dialogId); - avatarContainer.setChatAvatar(chatLocal); - avatarContainer.setTitle(chatLocal.title); + if (topicId != 0) { + TLRPC.TL_forumTopic forumTopic = getMessagesController().getTopicsController().findTopic(-dialogId, topicId); + ForumUtilities.setTopicIcon(avatarContainer.getAvatarImageView(), forumTopic); + avatarContainer.setTitle(forumTopic.title); + } else { + TLRPC.Chat chatLocal = getMessagesController().getChat(-dialogId); + avatarContainer.setChatAvatar(chatLocal); + avatarContainer.setTitle(chatLocal.title); + } } else { TLRPC.User user = getMessagesController().getUser(dialogId); if (user != null) { @@ -340,6 +358,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi return false; } }); + listView.setOnItemClickListener((view, position) -> { if (!view.isEnabled()) { return; @@ -351,7 +370,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi .setPositiveButton(LocaleController.getString(R.string.Reset), (d, w) -> { needReset = true; - MessagesController.getNotificationsSettings(currentAccount).edit().putBoolean("custom_" + dialogId, false).remove("notify2_" + dialogId).apply(); + MessagesController.getNotificationsSettings(currentAccount).edit().putBoolean("custom_" + key, false).remove("notify2_" + key).apply(); finishFragment(); if (delegate != null) { delegate.didRemoveException(dialogId); @@ -367,6 +386,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi } else if (position == soundRow) { Bundle bundle = new Bundle(); bundle.putLong("dialog_id", dialogId); + bundle.putInt("topic_id", topicId); presentFragment(new NotificationsSoundActivity(bundle, resourcesProvider)); } else if (position == ringtoneRow) { try { @@ -384,7 +404,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi defaultPath = defaultUri.getPath(); } - String path = preferences.getString("ringtone_path_" + dialogId, defaultPath); + String path = preferences.getString("ringtone_path_" + key, defaultPath); if (path != null && !path.equals("NoSound")) { if (path.equals(defaultPath)) { currentSound = defaultUri; @@ -399,7 +419,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi FileLog.e(e); } } else if (position == vibrateRow) { - showDialog(AlertsCreator.createVibrationSelectDialog(getParentActivity(), dialogId, false, false, () -> { + showDialog(AlertsCreator.createVibrationSelectDialog(getParentActivity(), dialogId, topicId, false, false, () -> { if (adapter != null) { adapter.notifyItemChanged(vibrateRow); } @@ -411,16 +431,16 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi checkRowsEnabled(); } else if (position == previewRow) { TextCheckCell checkCell = (TextCheckCell) view; - MessagesController.getNotificationsSettings(currentAccount).edit().putBoolean("content_preview_" + dialogId, !checkCell.isChecked()).apply(); + MessagesController.getNotificationsSettings(currentAccount).edit().putBoolean("content_preview_" + key, !checkCell.isChecked()).apply(); checkCell.setChecked(!checkCell.isChecked()); } else if (position == callsVibrateRow) { - showDialog(AlertsCreator.createVibrationSelectDialog(getParentActivity(), dialogId, "calls_vibrate_" + dialogId, () -> { + showDialog(AlertsCreator.createVibrationSelectDialog(getParentActivity(), dialogId, topicId, "calls_vibrate_" + key, () -> { if (adapter != null) { adapter.notifyItemChanged(callsVibrateRow); } }, resourcesProvider)); } else if (position == priorityRow) { - showDialog(AlertsCreator.createPrioritySelectDialog(getParentActivity(), dialogId, -1, () -> { + showDialog(AlertsCreator.createPrioritySelectDialog(getParentActivity(), dialogId, topicId, -1, () -> { if (adapter != null) { adapter.notifyItemChanged(priorityRow); } @@ -430,15 +450,15 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi return; } SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); - int notifyMaxCount = preferences.getInt("smart_max_count_" + dialogId, 2); - int notifyDelay = preferences.getInt("smart_delay_" + dialogId, 3 * 60); + int notifyMaxCount = preferences.getInt("smart_max_count_" + key, 2); + int notifyDelay = preferences.getInt("smart_delay_" + key, 3 * 60); if (notifyMaxCount == 0) { notifyMaxCount = 2; } AlertsCreator.createSoundFrequencyPickerDialog(getParentActivity(), notifyMaxCount, notifyDelay, (time, minute) -> { MessagesController.getNotificationsSettings(currentAccount).edit() - .putInt("smart_max_count_" + dialogId, time) - .putInt("smart_delay_" + dialogId, minute) + .putInt("smart_max_count_" + key, time) + .putInt("smart_delay_" + key, minute) .apply(); if (adapter != null) { adapter.notifyItemChanged(smartRow); @@ -448,20 +468,20 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi if (getParentActivity() == null) { return; } - showDialog(AlertsCreator.createColorSelectDialog(getParentActivity(), dialogId, -1, () -> { + showDialog(AlertsCreator.createColorSelectDialog(getParentActivity(), dialogId, topicId, -1, () -> { if (adapter != null) { adapter.notifyItemChanged(colorRow); } }, resourcesProvider)); } else if (position == popupEnabledRow) { - MessagesController.getNotificationsSettings(currentAccount).edit().putInt("popup_" + dialogId, 1).apply(); + MessagesController.getNotificationsSettings(currentAccount).edit().putInt("popup_" + key, 1).apply(); ((RadioCell) view).setChecked(true, true); view = listView.findViewWithTag(2); if (view != null) { ((RadioCell) view).setChecked(false, true); } } else if (position == popupDisabledRow) { - MessagesController.getNotificationsSettings(currentAccount).edit().putInt("popup_" + dialogId, 2).apply(); + MessagesController.getNotificationsSettings(currentAccount).edit().putInt("popup_" + key, 2).apply(); ((RadioCell) view).setChecked(true, true); view = listView.findViewWithTag(1); if (view != null) { @@ -504,22 +524,23 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); SharedPreferences.Editor editor = preferences.edit(); + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); if (requestCode == 12) { if (name != null) { - editor.putString("sound_" + dialogId, name); - editor.putString("sound_path_" + dialogId, ringtone.toString()); + editor.putString("sound_" + key, name); + editor.putString("sound_path_" + key, ringtone.toString()); } else { - editor.putString("sound_" + dialogId, "NoSound"); - editor.putString("sound_path_" + dialogId, "NoSound"); + editor.putString("sound_" + key, "NoSound"); + editor.putString("sound_path_" + key, "NoSound"); } - getNotificationsController().deleteNotificationChannel(dialogId); + getNotificationsController().deleteNotificationChannel(dialogId, topicId); } else if (requestCode == 13) { if (name != null) { - editor.putString("ringtone_" + dialogId, name); - editor.putString("ringtone_path_" + dialogId, ringtone.toString()); + editor.putString("ringtone_" + key, name); + editor.putString("ringtone_path_" + key, ringtone.toString()); } else { - editor.putString("ringtone_" + dialogId, "NoSound"); - editor.putString("ringtone_path_" + dialogId, "NoSound"); + editor.putString("ringtone_" + key, "NoSound"); + editor.putString("ringtone_path_" + key, "NoSound"); } } editor.apply(); @@ -713,6 +734,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi } case VIEW_TYPE_TEXT_SETTINGS: { TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); if (position == customResetRow) { textCell.setText(LocaleController.getString(R.string.ResetCustomNotifications), false); @@ -720,8 +742,8 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi } else { textCell.setTextColor(getThemedColor(Theme.key_windowBackgroundWhiteBlackText)); if (position == soundRow) { - String value = preferences.getString("sound_" + dialogId, LocaleController.getString("SoundDefault", R.string.SoundDefault)); - long documentId = preferences.getLong("sound_document_id_" + dialogId, 0); + String value = preferences.getString("sound_" + key, LocaleController.getString("SoundDefault", R.string.SoundDefault)); + long documentId = preferences.getLong("sound_document_id_" + key, 0); if (documentId != 0) { TLRPC.Document document = getMediaDataController().ringtoneDataStore.getDocument(documentId); if (document == null) { @@ -736,13 +758,13 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi } textCell.setTextAndValue(LocaleController.getString("Sound", R.string.Sound), value, true); } else if (position == ringtoneRow) { - String value = preferences.getString("ringtone_" + dialogId, LocaleController.getString("DefaultRingtone", R.string.DefaultRingtone)); + String value = preferences.getString("ringtone_" + key, LocaleController.getString("DefaultRingtone", R.string.DefaultRingtone)); if (value.equals("NoSound")) { value = LocaleController.getString("NoSound", R.string.NoSound); } textCell.setTextAndValue(LocaleController.getString("VoipSettingsRingtone", R.string.VoipSettingsRingtone), value, false); } else if (position == vibrateRow) { - int value = preferences.getInt("vibrate_" + dialogId, 0); + int value = preferences.getInt("vibrate_" + key, 0); if (value == 0 || value == 4) { textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("VibrationDefault", R.string.VibrationDefault), smartRow != -1 || priorityRow != -1); } else if (value == 1) { @@ -753,7 +775,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("Long", R.string.Long), smartRow != -1 || priorityRow != -1); } } else if (position == priorityRow) { - int value = preferences.getInt("priority_" + dialogId, 3); + int value = preferences.getInt("priority_" + key, 3); if (value == 0) { textCell.setTextAndValue(LocaleController.getString("NotificationsImportance", R.string.NotificationsImportance), LocaleController.getString("NotificationsPriorityHigh", R.string.NotificationsPriorityHigh), false); } else if (value == 1 || value == 2) { @@ -766,8 +788,8 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi textCell.setTextAndValue(LocaleController.getString("NotificationsImportance", R.string.NotificationsImportance), LocaleController.getString("NotificationsPriorityMedium", R.string.NotificationsPriorityMedium), false); } } else if (position == smartRow) { - int notifyMaxCount = preferences.getInt("smart_max_count_" + dialogId, 2); - int notifyDelay = preferences.getInt("smart_delay_" + dialogId, 3 * 60); + int notifyMaxCount = preferences.getInt("smart_max_count_" + key, 2); + int notifyDelay = preferences.getInt("smart_delay_" + key, 3 * 60); if (notifyMaxCount == 0) { textCell.setTextAndValue(LocaleController.getString("SmartNotifications", R.string.SmartNotifications), LocaleController.getString("SmartNotificationsDisabled", R.string.SmartNotificationsDisabled), priorityRow != -1); } else { @@ -775,7 +797,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi textCell.setTextAndValue(LocaleController.getString("SmartNotifications", R.string.SmartNotifications), LocaleController.formatString("SmartNotificationsInfo", R.string.SmartNotificationsInfo, notifyMaxCount, minutes), priorityRow != -1); } } else if (position == callsVibrateRow) { - int value = preferences.getInt("calls_vibrate_" + dialogId, 0); + int value = preferences.getInt("calls_vibrate_" + key, 0); if (value == 0 || value == 4) { textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("VibrationDefault", R.string.VibrationDefault), true); } else if (value == 1) { @@ -812,10 +834,11 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi } case VIEW_TYPE_TEXT_COLOR: { TextColorCell textCell = (TextColorCell) holder.itemView; + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); int color; - if (preferences.contains("color_" + dialogId)) { - color = preferences.getInt("color_" + dialogId, 0xff0000ff); + if (preferences.contains("color_" + key)) { + color = preferences.getInt("color_" + key, 0xff0000ff); } else { if (DialogObject.isChatDialog(dialogId)) { color = preferences.getInt("GroupLed", 0xff0000ff); @@ -835,7 +858,8 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi case VIEW_TYPE_RADIO: { RadioCell radioCell = (RadioCell) holder.itemView; SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); - int popup = preferences.getInt("popup_" + dialogId, 0); + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); + int popup = preferences.getInt("popup_" + key, 0); if (popup == 0) { popup = preferences.getInt(DialogObject.isChatDialog(dialogId) ? "popupGroup" : "popupAll", 0); if (popup != 0) { @@ -870,7 +894,8 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi if (position == enableRow) { checkCell.setTextAndCheck(LocaleController.getString("Notifications", R.string.Notifications), notificationsEnabled, true); } else if (position == previewRow) { - checkCell.setTextAndCheck(LocaleController.getString("MessagePreview", R.string.MessagePreview), preferences.getBoolean("content_preview_" + dialogId, true), true); + String key = NotificationsController.getSharedPrefKey(dialogId, topicId); + checkCell.setTextAndCheck(LocaleController.getString("MessagePreview", R.string.MessagePreview), preferences.getBoolean("content_preview_" + key, true), true); } break; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProxySettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProxySettingsActivity.java index d8b77088c..96dd95b85 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProxySettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProxySettingsActivity.java @@ -755,7 +755,7 @@ public class ProxySettingsActivity extends BaseFragment { } @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen && !backward && addingNewProxy) { inputFields[FIELD_IP].requestFocus(); AndroidUtilities.showKeyboard(inputFields[FIELD_IP]); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/QrActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/QrActivity.java index 84265699a..82543f59f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/QrActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/QrActivity.java @@ -57,6 +57,7 @@ import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.ChatObject; import org.telegram.messenger.ChatThemeController; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocaleController; @@ -278,7 +279,7 @@ public class QrActivity extends BaseFragment { } else if (chatId != 0) { TLRPC.Chat chat = getMessagesController().getChat(chatId); if (chat != null) { - username = chat.username; + username = ChatObject.getPublicUsername(chat); avatarDrawable = new AvatarDrawable(chat); imageLocationSmall = ImageLocation.getForChat(chat, ImageLocation.TYPE_SMALL); imageLocation = ImageLocation.getForChat(chat, ImageLocation.TYPE_BIG); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ReadAllMentionsMenu.java b/TMessagesProj/src/main/java/org/telegram/ui/ReadAllMentionsMenu.java index 2bbb5f79d..e8ff4b4e0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ReadAllMentionsMenu.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ReadAllMentionsMenu.java @@ -11,6 +11,7 @@ import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; import org.telegram.ui.ActionBar.ActionBarMenuSubItem; import org.telegram.ui.ActionBar.ActionBarPopupWindow; +import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; @@ -19,7 +20,7 @@ public class ReadAllMentionsMenu { public final static int TYPE_REACTIONS = 0; public final static int TYPE_MENTIONS = 1; - public static ActionBarPopupWindow show(int type, Activity activity, FrameLayout contentView, View mentionButton, Theme.ResourcesProvider resourcesProvider, Runnable onRead) { + public static ActionBarPopupWindow show(int type, Activity activity, INavigationLayout navigationLayout, FrameLayout contentView, View mentionButton, Theme.ResourcesProvider resourcesProvider, Runnable onRead) { ActionBarPopupWindow.ActionBarPopupWindowLayout popupWindowLayout = new ActionBarPopupWindow.ActionBarPopupWindowLayout(activity); popupWindowLayout.setMinimumWidth(AndroidUtilities.dp(200)); @@ -47,6 +48,11 @@ public class ReadAllMentionsMenu { float x = mentionButton.getX() + mentionButton.getWidth() - popupWindowLayout.getMeasuredWidth() + AndroidUtilities.dp(8); float y = mentionButton.getY() - popupWindowLayout.getMeasuredHeight(); + if (AndroidUtilities.isTablet()) { + View v = navigationLayout.getView(); + x += v.getX() + v.getPaddingLeft(); + y += v.getY() + v.getPaddingTop(); + } scrimPopupWindow.showAtLocation(contentView, Gravity.LEFT | Gravity.TOP, (int) x, (int) y); return scrimPopupWindow; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/RestrictedLanguagesSelectActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/RestrictedLanguagesSelectActivity.java index 15b832c42..a74266424 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/RestrictedLanguagesSelectActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/RestrictedLanguagesSelectActivity.java @@ -226,7 +226,7 @@ public class RestrictedLanguagesSelectActivity extends BaseFragment implements N LocaleController.LocaleInfo currentLocaleInfo = LocaleController.getInstance().getCurrentLocaleInfo(); String langCode = localeInfo.pluralLangCode; if (langCode != null && langCode.equals(currentLocaleInfo.pluralLangCode)) { - AndroidUtilities.shakeView(((TextCheckbox2Cell) view).checkbox, 2, 0); + AndroidUtilities.shakeView(((TextCheckbox2Cell) view).checkbox); return; } boolean value = selectedLanguages.contains(langCode); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SelectAnimatedEmojiDialog.java b/TMessagesProj/src/main/java/org/telegram/ui/SelectAnimatedEmojiDialog.java index e79886061..204e41584 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SelectAnimatedEmojiDialog.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SelectAnimatedEmojiDialog.java @@ -95,7 +95,6 @@ import org.telegram.ui.Components.CloseProgressDrawable2; import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.DrawingInBackgroundThreadDrawable; import org.telegram.ui.Components.EditTextCaption; -import org.telegram.ui.Components.EditTextEmoji; import org.telegram.ui.Components.EmojiPacksAlert; import org.telegram.ui.Components.EmojiTabsStrip; import org.telegram.ui.Components.EmojiView; @@ -108,7 +107,6 @@ import org.telegram.ui.Components.Reactions.ReactionsLayoutInBubble; import org.telegram.ui.Components.Reactions.ReactionsUtils; import org.telegram.ui.Components.RecyclerAnimationScrollHelper; import org.telegram.ui.Components.RecyclerListView; -import org.telegram.ui.Components.SizeNotifierFrameLayout; import java.lang.reflect.Field; import java.util.ArrayList; @@ -124,6 +122,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati public final static int TYPE_EMOJI_STATUS = 0; public final static int TYPE_REACTIONS = 1; public final static int TYPE_SET_DEFAULT_REACTION = 2; + public static final int TYPE_TOPIC_ICON = 3; private final int RECENT_MAX_LINES = 5; private final int EXPAND_MAX_LINES = 3; @@ -136,6 +135,8 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati private int recentReactionsSectionRow; private int popularSectionRow; private int longtapHintRow; + private int defaultTopicIconRow; + private int topicEmojiHeaderRow; private EmojiPackExpand recentExpandButton; @@ -146,6 +147,9 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati HashSet selectedDocumentIds = new HashSet(); public Paint selectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG); public Paint selectorAccentPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Drawable forumIconDrawable; + private ImageViewEmoji forumIconImage; + private boolean animationsEnabled; public void putAnimatedEmojiToCache(AnimatedEmojiDrawable animatedEmojiDrawable) { emojiGridView.animatedEmojiDrawables.put(animatedEmojiDrawable.getDocumentId(), animatedEmojiDrawable); @@ -335,6 +339,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati private ArrayList topReactions = new ArrayList<>(); private ArrayList recentReactions = new ArrayList<>(); private ArrayList defaultStatuses = new ArrayList<>(); + private ArrayList frozenEmojiPacks = new ArrayList<>(); private ArrayList packs = new ArrayList<>(); private boolean includeEmpty = false; private boolean includeHint = false; @@ -355,6 +360,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati private BaseFragment baseFragment; private int topMarginDp; + DefaultItemAnimator emojiItemAnimator; public SelectAnimatedEmojiDialog(BaseFragment baseFragment, Context context, boolean includeEmpty, Theme.ResourcesProvider resourcesProvider) { this(baseFragment, context, includeEmpty, null, TYPE_EMOJI_STATUS, resourcesProvider); @@ -468,7 +474,14 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati addView(bubble2View, LayoutHelper.createFrame(17, 9, Gravity.TOP | Gravity.LEFT, bubbleX / AndroidUtilities.density + (bubbleRight ? -25 : 10), 6 + 8 - 9 + topMarginDp, 0, 0)); } - emojiTabs = new EmojiTabsStrip(context, null, false, true, null) { + boolean showSettings = baseFragment != null && type != TYPE_TOPIC_ICON; + emojiTabs = new EmojiTabsStrip(context, null, false, true, type, showSettings ? () -> { + onSettings(); + baseFragment.presentFragment(new StickersActivity(MediaDataController.TYPE_EMOJIPACKS, frozenEmojiPacks)); + if (dismiss != null) { + dismiss.run(); + } + } : null) { @Override protected boolean onTabClick(int index) { if (smoothScrolling) { @@ -540,7 +553,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati super.onScrollStateChanged(state); } }; - DefaultItemAnimator emojiItemAnimator = new DefaultItemAnimator() { + emojiItemAnimator = new DefaultItemAnimator() { @Override protected float animateByScale(View view) { return (view instanceof EmojiPackExpand ? .6f : 0f); @@ -575,7 +588,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { - return (positionToSection.indexOfKey(position) >= 0 || positionToButton.indexOfKey(position) >= 0 || position == recentReactionsSectionRow || position == popularSectionRow || position == longtapHintRow || position == searchRow) ? layoutManager.getSpanCount() : 1; + return (positionToSection.indexOfKey(position) >= 0 || positionToButton.indexOfKey(position) >= 0 || position == recentReactionsSectionRow || position == popularSectionRow || position == longtapHintRow || position == searchRow || position == topicEmojiHeaderRow) ? layoutManager.getSpanCount() : 1; } }); @@ -587,15 +600,26 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati }; gridViewContainer.addView(emojiGridView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL, 0, 0, 0, 0)); - emojiSearchGridView = new EmojiListView(context); + emojiSearchGridView = new EmojiListView(context) { + @Override + public void onScrolled(int dx, int dy) { + super.onScrolled(dx, dy); + checkScroll(); + } + }; if (emojiSearchGridView.getItemAnimator() != null) { emojiSearchGridView.getItemAnimator().setDurations(180); emojiSearchGridView.getItemAnimator().setMoveInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); } TextView emptyViewText = new TextView(context); - emptyViewText.setText(type == TYPE_EMOJI_STATUS ? - LocaleController.getString("NoEmojiFound", R.string.NoEmojiFound) : - LocaleController.getString("NoReactionsFound", R.string.NoReactionsFound)); + if (type == TYPE_EMOJI_STATUS) { + emptyViewText.setText(LocaleController.getString("NoEmojiFound", R.string.NoEmojiFound)); + } else if (type == TYPE_REACTIONS || type == TYPE_SET_DEFAULT_REACTION) { + emptyViewText.setText(LocaleController.getString("NoReactionsFound", R.string.NoReactionsFound)); + } else { + emptyViewText.setText(LocaleController.getString("NoIconsFound", R.string.NoIconsFound)); + } + emptyViewText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); emptyViewText.setTextColor(Theme.getColor(Theme.key_chat_emojiPanelEmptyText, resourcesProvider)); emojiSearchEmptyViewImageView = new BackupImageView(context); @@ -747,7 +771,6 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati RecyclerListView.OnItemClickListener onItemClick = (view, position) -> { if (view instanceof ImageViewEmoji) { ImageViewEmoji viewEmoji = (ImageViewEmoji) view; -// viewEmoji.notDraw = true; if (viewEmoji.isDefaultReaction) { incrementHintUse(); onReactionClick(viewEmoji, viewEmoji.reaction); @@ -813,17 +836,21 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati contentViewForeground.setBackgroundColor(0xff000000); contentView.addView(contentViewForeground, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - preload(currentAccount); + preload(type, currentAccount); bigReactionImageReceiver.setLayerNum(7); - updateRows(false); + updateRows(true, false); + } + + protected void onSettings() { + } public void setExpireDateHint(int date) { includeHint = true; hintExpireDate = date; - updateRows(false); + updateRows(true, false); } private void setBigReactionAnimatedEmoji(AnimatedEmojiDrawable animatedEmojiDrawable) { @@ -846,10 +873,10 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati AlertDialog.Builder builder = new AlertDialog.Builder(getContext(), null); builder.setTitle(LocaleController.getString("ClearRecentEmojiStatusesTitle", R.string.ClearRecentEmojiStatusesTitle)); builder.setMessage(LocaleController.getString("ClearRecentEmojiStatusesText", R.string.ClearRecentEmojiStatusesText)); - builder.setPositiveButton(LocaleController.getString("Clear", R.string.Clear).toUpperCase(), (dialogInterface, i) -> { + builder.setPositiveButton(LocaleController.getString("Clear", R.string.Clear), (dialogInterface, i) -> { ConnectionsManager.getInstance(currentAccount).sendRequest(new TLRPC.TL_account_clearRecentEmojiStatuses(), null); MediaDataController.getInstance(currentAccount).clearRecentEmojiStatuses(); - updateRows(true); + updateRows(false, true); }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); builder.setDimEnabled(false); @@ -1086,12 +1113,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati private boolean bottomGradientShown = false; private void checkScroll() { -// final boolean top = emojiGridView.canScrollVertically(-1); -// if (top != topGradientShown) { -// topGradientShown = top; -// topGradientView.animate().alpha(top ? 1f : 0f).setDuration(200).start(); -// } - final boolean bottom = emojiGridView.canScrollVertically(1); + final boolean bottom = (gridSearch ? emojiSearchGridView : emojiGridView).canScrollVertically(1); if (bottom != bottomGradientShown) { bottomGradientShown = bottom; bottomGradientView.animate().alpha(bottom ? 1f : 0f).setDuration(200).start(); @@ -1176,6 +1198,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati .setInterpolator(CubicBezierInterpolator.DEFAULT) .setDuration(160) .start(); + checkScroll(); } private ArrayList emptyViewEmojis = new ArrayList(4); { @@ -1537,7 +1560,9 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati } imageView.imageReceiver.setParentView(emojiSearchGridView); imageView.reaction = currentReaction; - imageView.setViewSelected(selectedReactions.contains(currentReaction)); + imageView.setViewSelected(selectedReactions.contains(currentReaction), false); + imageView.notDraw = false; + imageView.invalidate(); if (currentReaction.emojicon != null) { imageView.isDefaultReaction = true; TLRPC.TL_availableReaction reaction = MediaDataController.getInstance(currentAccount).getReactionsMap().get(currentReaction.emojicon); @@ -1597,7 +1622,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati } imageView.setDrawable(drawable); } - imageView.setViewSelected(selected); + imageView.setViewSelected(selected, false); } } @@ -1610,6 +1635,9 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati } public void updateRows(boolean diff) { + if (!isAttached) { + diff = false; + } ArrayList prevRowHashCodes = new ArrayList<>(rowHashCodes); count = 0; @@ -1665,6 +1693,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati public int VIEW_TYPE_BUTTON = 5; public int VIEW_TYPE_HINT = 6; public int VIEW_TYPE_SEARCH = 7; + public int VIEW_TYPE_TOPIC_ICON = 8; @Override public boolean isEnabled(RecyclerView.ViewHolder holder) { @@ -1672,7 +1701,8 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati return ( viewType == VIEW_TYPE_IMAGE || viewType == VIEW_TYPE_REACTION || - viewType == VIEW_TYPE_EMOJI + viewType == VIEW_TYPE_EMOJI || + viewType == VIEW_TYPE_TOPIC_ICON ); } @@ -1684,8 +1714,22 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati view = new HeaderView(getContext()); } else if (viewType == VIEW_TYPE_IMAGE) { view = new ImageView(getContext()); - } else if (viewType == VIEW_TYPE_EMOJI || viewType == VIEW_TYPE_REACTION) { - view = new ImageViewEmoji(getContext()); + } else if (viewType == VIEW_TYPE_EMOJI || viewType == VIEW_TYPE_REACTION || viewType == VIEW_TYPE_TOPIC_ICON) { + ImageViewEmoji imageView = new ImageViewEmoji(getContext()); + if (viewType == VIEW_TYPE_TOPIC_ICON) { + imageView.isStaticIcon = true; + imageView.imageReceiverToDraw = imageView.imageReceiver = new ImageReceiver(imageView) { + @Override + public boolean draw(Canvas canvas) { + return super.draw(canvas); + } + }; + + imageView.imageReceiver.setImageBitmap(forumIconDrawable); + forumIconImage = imageView; + imageView.setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8)); + } + view = imageView; } else if (viewType == VIEW_TYPE_EXPAND) { view = new EmojiPackExpand(getContext(), null); } else if (viewType == VIEW_TYPE_BUTTON) { @@ -1698,7 +1742,9 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati } }; textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); - if (type == TYPE_EMOJI_STATUS) { + if (type == TYPE_TOPIC_ICON) { + textView.setText(LocaleController.getString("SelectTopicIconHint", R.string.SelectTopicIconHint)); + } else if (type == TYPE_EMOJI_STATUS) { textView.setText(LocaleController.getString("EmojiLongtapHint", R.string.EmojiLongtapHint)); } else { textView.setText(LocaleController.getString("ReactionsLongtapHint", R.string.ReactionsLongtapHint)); @@ -1731,8 +1777,10 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati return VIEW_TYPE_BUTTON; } else if (position == longtapHintRow) { return VIEW_TYPE_HINT; - } else if (positionToSection.indexOfKey(position) >= 0 || position == recentReactionsSectionRow || position == popularSectionRow) { + } else if (positionToSection.indexOfKey(position) >= 0 || position == recentReactionsSectionRow || position == popularSectionRow || position == topicEmojiHeaderRow) { return VIEW_TYPE_HEADER; + } if (position == defaultTopicIconRow) { + return VIEW_TYPE_TOPIC_ICON; } else { return VIEW_TYPE_EMOJI; } @@ -1741,6 +1789,12 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { int viewType = holder.getItemViewType(); + if (viewType == VIEW_TYPE_TOPIC_ICON) { + ImageViewEmoji imageView = (ImageViewEmoji) holder.itemView; + imageView.position = position; + imageView.selected = selectedDocumentIds.contains(0L); + return; + } if (showAnimator == null || !showAnimator.isRunning()) { holder.itemView.setScaleX(1); holder.itemView.setScaleY(1); @@ -1752,6 +1806,11 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati } } else if (viewType == VIEW_TYPE_HEADER) { HeaderView header = (HeaderView) holder.itemView; + if (position == topicEmojiHeaderRow) { + header.setText(LocaleController.getString("SelectTopicIconHint", R.string.SelectTopicIconHint), false); + header.closeIcon.setVisibility(View.GONE); + return; + } if (position == recentReactionsSectionRow) { header.setText(LocaleController.getString("RecentlyUsed", R.string.RecentlyUsed), false); header.closeIcon.setVisibility(View.VISIBLE); @@ -1791,7 +1850,8 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati imageView.imageReceiver.onAttachedToWindow(); } imageView.reaction = currentReaction; - imageView.setViewSelected(selectedReactions.contains(currentReaction)); + imageView.setViewSelected(selectedReactions.contains(currentReaction), false); + imageView.notDraw = false; if (currentReaction.emojicon != null) { imageView.isDefaultReaction = true; TLRPC.TL_availableReaction reaction = MediaDataController.getInstance(currentAccount).getReactionsMap().get(currentReaction.emojicon); @@ -1878,7 +1938,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati EmojiPacksAlert.installSet(null, pack.set, false); installedEmojiSets.add(pack.set.id); - updateRows(true); + updateRows(true, true); }); } } @@ -1891,7 +1951,12 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati imageView.setPadding(AndroidUtilities.dp(1), AndroidUtilities.dp(1), AndroidUtilities.dp(1), AndroidUtilities.dp(1)); final int recentmaxlen = layoutManager.getSpanCount() * RECENT_MAX_LINES; final int maxlen = layoutManager.getSpanCount() * EXPAND_MAX_LINES; - int recentSize = recent.size() > recentmaxlen && !recentExpanded ? recentmaxlen : recent.size() + (includeEmpty ? 1 : 0); + int recentSize; + if (type == TYPE_TOPIC_ICON) { + recentSize = recent.size(); + } else { + recentSize = recent.size() > recentmaxlen && !recentExpanded ? recentmaxlen : recent.size() + (includeEmpty ? 1 : 0); + } boolean selected = false; imageView.setDrawable(null); if (includeEmpty && position == (searchRow != -1 ? 1 : 0) + (includeHint ? 1 : 0)) { @@ -1939,7 +2004,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati } else { imageView.setDrawable(null); } - imageView.setViewSelected(selected); + imageView.setViewSelected(selected, false); } } @@ -2205,6 +2270,8 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati private float pressedProgress; public float skewAlpha; public int skewIndex; + public boolean isStaticIcon; + private float selectedProgress; final AnimatedEmojiSpan.InvalidateHolder invalidateHolder = new AnimatedEmojiSpan.InvalidateHolder() { @Override public void invalidate() { @@ -2272,19 +2339,32 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati } } - public void setViewSelected(boolean selected) { + public void setViewSelected(boolean selected, boolean animated) { if (this.selected != selected) { this.selected = selected; + if (!animated) { + selectedProgress = selected ? 1f : 0f; + } } } - public void drawSelected(Canvas canvas) { - if (selected && !notDraw) { + public void drawSelected(Canvas canvas, View view) { + if ((selected || selectedProgress > 0) && !notDraw) { + if (selected && selectedProgress < 1f) { + selectedProgress += 16/ 300f; + view.invalidate(); + } + if (!selected && selectedProgress > 0) { + selectedProgress -= 16/ 300f; + view.invalidate(); + } + selectedProgress = Utilities.clamp(selectedProgress, 1f, 0f); + AndroidUtilities.rectTmp.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); AndroidUtilities.rectTmp.inset(AndroidUtilities.dp(1), AndroidUtilities.dp(1)); Paint paint = empty || drawable instanceof AnimatedEmojiDrawable && ((AnimatedEmojiDrawable) drawable).canOverrideColor() ? selectorAccentPaint : selectorPaint; int wasAlpha = paint.getAlpha(); - paint.setAlpha((int) (wasAlpha * getAlpha())); + paint.setAlpha((int) (wasAlpha * getAlpha() * selectedProgress)); canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(4), AndroidUtilities.dp(4), paint); paint.setAlpha(wasAlpha); } @@ -2367,6 +2447,21 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati } + public void preload(int type, int account) { + if (MediaDataController.getInstance(account) == null) { + return; + } + MediaDataController.getInstance(account).checkStickers(MediaDataController.TYPE_EMOJIPACKS); + if (type == TYPE_REACTIONS || type == TYPE_SET_DEFAULT_REACTION) { + MediaDataController.getInstance(account).checkReactions(); + } else if (type == TYPE_EMOJI_STATUS) { + MediaDataController.getInstance(account).fetchEmojiStatuses(0, true); + } else if (type == TYPE_TOPIC_ICON) { + MediaDataController.getInstance(account).checkDefaultTopicIcons(); + } + MediaDataController.getInstance(account).getStickerSet(new TLRPC.TL_inputStickerSetEmojiDefaultStatuses(), false); + } + public static void preload(int account) { if (MediaDataController.getInstance(account) == null) { return; @@ -2375,16 +2470,23 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati MediaDataController.getInstance(account).fetchEmojiStatuses(0, true); MediaDataController.getInstance(account).checkReactions(); MediaDataController.getInstance(account).getStickerSet(new TLRPC.TL_inputStickerSetEmojiDefaultStatuses(), false); + MediaDataController.getInstance(account).checkDefaultTopicIcons(); } private boolean defaultSetLoading = false; - private void updateRows(boolean diff) { + private void updateRows(boolean updateEmojipacks, boolean diff) { + if (!animationsEnabled) { + diff = false; + } MediaDataController mediaDataController = MediaDataController.getInstance(UserConfig.selectedAccount); if (mediaDataController == null) { return; } - ArrayList installedEmojipacks = new ArrayList<>(mediaDataController.getStickerSets(MediaDataController.TYPE_EMOJIPACKS)); + if (updateEmojipacks || frozenEmojiPacks == null) { + frozenEmojiPacks = new ArrayList<>(mediaDataController.getStickerSets(MediaDataController.TYPE_EMOJIPACKS)); + } + ArrayList installedEmojipacks = frozenEmojiPacks; ArrayList featuredEmojiPacks = new ArrayList<>(mediaDataController.getFeaturedEmojiSets()); ArrayList prevRowHashCodes = new ArrayList<>(rowHashCodes); @@ -2394,6 +2496,8 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati recentReactionsEndRow = -1; popularSectionRow = -1; longtapHintRow = -1; + defaultTopicIconRow = -1; + topicEmojiHeaderRow = -1; recent.clear(); defaultStatuses.clear(); topReactions.clear(); @@ -2407,11 +2511,48 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati if (!installedEmojipacks.isEmpty()) { searchRow = totalCount++; + rowHashCodes.add(9); } else { searchRow = -1; } - if (includeHint && type != TYPE_SET_DEFAULT_REACTION) { + if (type == TYPE_TOPIC_ICON) { + topicEmojiHeaderRow = totalCount++; + rowHashCodes.add(12); + defaultTopicIconRow = totalCount++; + rowHashCodes.add(7); + + String packName = UserConfig.getInstance(currentAccount).defaultTopicIcons; + TLRPC.TL_messages_stickerSet defaultSet = null; + if (packName != null) { + defaultSet = MediaDataController.getInstance(currentAccount).getStickerSetByName(packName); + if (defaultSet == null) { + defaultSet = MediaDataController.getInstance(currentAccount).getStickerSetByEmojiOrName(packName); + } + } + + if (defaultSet == null) { + defaultSetLoading = true; + } else { + if (includeEmpty) { + totalCount++; + rowHashCodes.add(2); + } + if (defaultSet.documents != null && !defaultSet.documents.isEmpty()) { + for (int i = 0; i < defaultSet.documents.size(); ++i) { + recent.add(new AnimatedEmojiSpan(defaultSet.documents.get(i), null)); + } + } + + for (int i = 0; i < recent.size(); ++i) { + rowHashCodes.add(Objects.hash(43223, recent.get(i).getDocumentId())); + totalCount++; + } + } + + } + + if (includeHint && type != TYPE_SET_DEFAULT_REACTION && type != TYPE_TOPIC_ICON) { longtapHintRow = totalCount++; rowHashCodes.add(6); } @@ -2637,30 +2778,32 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati }); if (diff) { - DiffUtil.calculateDiff(new DiffUtil.Callback() { - @Override - public int getOldListSize() { - return prevRowHashCodes.size(); - } - - @Override - public int getNewListSize() { - return rowHashCodes.size(); - } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - return prevRowHashCodes.get(oldItemPosition).equals(rowHashCodes.get(newItemPosition)); - } - - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - return true; - } - }, false).dispatchUpdatesTo(adapter); + emojiGridView.setItemAnimator(emojiItemAnimator); } else { - adapter.notifyDataSetChanged(); + emojiGridView.setItemAnimator(null); } + DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return prevRowHashCodes.size(); + } + + @Override + public int getNewListSize() { + return rowHashCodes.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + return prevRowHashCodes.get(oldItemPosition).equals(rowHashCodes.get(newItemPosition)); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + return true; + } + }, false).dispatchUpdatesTo(adapter); + if (!emojiGridView.scrolledByUserOnce) { emojiGridView.scrollToPosition(1); } @@ -2709,7 +2852,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati count = toCount - fromCount; } - updateRows(true); + updateRows(false, true); if (from != null && count != null) { animateExpandFromButton = expandButton; @@ -2735,7 +2878,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (drawBackground) { + if (drawBackground && type != TYPE_TOPIC_ICON) { super.onMeasure( MeasureSpec.makeMeasureSpec((int) Math.min(AndroidUtilities.dp(340 - 16), AndroidUtilities.displaySize.x * .95f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) Math.min(AndroidUtilities.dp(410 - 16 - 64), AndroidUtilities.displaySize.y * .75f), MeasureSpec.AT_MOST) @@ -2853,7 +2996,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati canvas.save(); canvas.translate(imageViewEmoji.getX(), imageViewEmoji.getY()); - imageViewEmoji.drawSelected(canvas); + imageViewEmoji.drawSelected(canvas, this); canvas.restore(); if (imageViewEmoji.getBackground() != null) { @@ -2998,7 +3141,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati boolean animatedExpandIn = animateExpandStartTime > 0 && (SystemClock.elapsedRealtime() - animateExpandStartTime) < animateExpandDuration(); for (int i = 0; i < imageViewEmojis.size(); i++) { ImageViewEmoji img = imageViewEmojis.get(i); - if (img.pressedProgress != 0 || img.backAnimator != null || img.getTranslationX() != 0 || img.getTranslationY() != 0 || img.getAlpha() != 1 || (animatedExpandIn && img.position > animateExpandFromPosition && img.position < animateExpandToPosition)) { + if (img.pressedProgress != 0 || img.backAnimator != null || img.getTranslationX() != 0 || img.getTranslationY() != 0 || img.getAlpha() != 1 || (animatedExpandIn && img.position > animateExpandFromPosition && img.position < animateExpandToPosition) || img.isStaticIcon) { drawInUi = true; break; } @@ -3110,7 +3253,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati alpha = imageView.getAlpha(); } - if (!imageView.isDefaultReaction) { + if (!imageView.isDefaultReaction && !imageView.isStaticIcon) { AnimatedEmojiSpan span = imageView.span; if (span == null) { continue; @@ -3150,7 +3293,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati int w = imageView.getWidth() - imageView.getPaddingLeft() - imageView.getPaddingRight(); int h = imageView.getHeight() - imageView.getPaddingTop() - imageView.getPaddingBottom(); AndroidUtilities.rectTmp2.set(imageView.getPaddingLeft(), imageView.getPaddingTop(), imageView.getWidth() - imageView.getPaddingRight(), imageView.getHeight() - imageView.getPaddingBottom()); - if (imageView.selected) { + if (imageView.selected && type != TYPE_TOPIC_ICON) { AndroidUtilities.rectTmp2.set( (int) Math.round(AndroidUtilities.rectTmp2.centerX() - AndroidUtilities.rectTmp2.width() / 2f * 0.86f), (int) Math.round(AndroidUtilities.rectTmp2.centerY() - AndroidUtilities.rectTmp2.height() / 2f * 0.86f), @@ -3204,7 +3347,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati float scale = imageView.getScaleX(); if (imageView.pressedProgress != 0 || imageView.selected) { - scale *= 0.8f + 0.2f * (1f - (imageView.selected ? 0.7f : imageView.pressedProgress)); + scale *= 0.8f + 0.2f * (1f - ((imageView.selected && type != TYPE_TOPIC_ICON) ? 0.7f : imageView.pressedProgress)); } boolean animatedExpandIn = animateExpandStartTime > 0 && (SystemClock.elapsedRealtime() - animateExpandStartTime) < animateExpandDuration(); boolean animatedExpandInLocal = animatedExpandIn && animateExpandFromPosition >= 0 && animateExpandToPosition >= 0 && animateExpandStartTime > 0; @@ -3233,7 +3376,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati drawable = getPremiumStar(); drawable.setBounds(AndroidUtilities.rectTmp2); drawable.setAlpha(255); - } else if (!imageView.isDefaultReaction) { + } else if (!imageView.isDefaultReaction && !imageView.isStaticIcon) { AnimatedEmojiSpan span = imageView.span; if (span == null || imageView.notDraw) { continue; @@ -3291,7 +3434,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati if (imageView.premiumLockIconView != null) { } - } else if (imageView.isDefaultReaction && imageView.imageReceiver != null) { + } else if ((imageView.isDefaultReaction || imageView.isStaticIcon) && imageView.imageReceiver != null) { imageView.imageReceiver.setAlpha(alpha); imageView.imageReceiver.draw(canvas); } @@ -3357,15 +3500,15 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati public void didReceivedNotification(int id, int account, Object... args) { if (id == NotificationCenter.stickersDidLoad) { if (((int) args[0]) == MediaDataController.TYPE_EMOJIPACKS) { - updateRows(true); + updateRows(true, true); } } else if (id == NotificationCenter.featuredEmojiDidLoad) { - updateRows(true); + updateRows(false, true); } else if (id == NotificationCenter.recentEmojiStatusesUpdate) { - updateRows(true); + updateRows(false, true); } else if (id == NotificationCenter.groupStickersDidLoad) { if (defaultSetLoading) { - updateRows(true); + updateRows(true, true); defaultSetLoading = false; } } @@ -3403,34 +3546,41 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati hideAnimator.cancel(); hideAnimator = null; } - showAnimator = ValueAnimator.ofFloat(0, 1); - showAnimator.addUpdateListener(anm -> { - final float t = (float) anm.getAnimatedValue(); - updateShow(t); - }); - showAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - checkScroll(); - updateShow(1); - for (int i = 0; i < emojiGridView.getChildCount(); ++i) { - View child = emojiGridView.getChildAt(i); - child.setScaleX(1); - child.setScaleY(1); - } - for (int i = 0; i < emojiTabs.contentView.getChildCount(); ++i) { - View child = emojiTabs.contentView.getChildAt(i); - child.setScaleX(1); - child.setScaleY(1); - } - emojiTabs.contentView.invalidate(); + boolean animated = type != TYPE_TOPIC_ICON; - emojiGridView.updateEmojiDrawables(); - } - }); - updateShow(0); - showAnimator.setDuration(showDuration); - showAnimator.start(); + if (animated) { + showAnimator = ValueAnimator.ofFloat(0, 1); + showAnimator.addUpdateListener(anm -> { + final float t = (float) anm.getAnimatedValue(); + updateShow(t); + }); + showAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + checkScroll(); + updateShow(1); + for (int i = 0; i < emojiGridView.getChildCount(); ++i) { + View child = emojiGridView.getChildAt(i); + child.setScaleX(1); + child.setScaleY(1); + } + for (int i = 0; i < emojiTabs.contentView.getChildCount(); ++i) { + View child = emojiTabs.contentView.getChildAt(i); + child.setScaleX(1); + child.setScaleY(1); + } + emojiTabs.contentView.invalidate(); + + emojiGridView.updateEmojiDrawables(); + } + }); + updateShow(0); + showAnimator.setDuration(showDuration); + showAnimator.start(); + } else { + checkScroll(); + updateShow(1); + } } private class SearchBox extends FrameLayout { @@ -3496,7 +3646,14 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati input.setBackground(null); input.setPadding(0, 0, AndroidUtilities.dp(4), 0); input.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - input.setHint(type == TYPE_EMOJI_STATUS ? LocaleController.getString(R.string.SearchEmojiHint) : LocaleController.getString(R.string.SearchReactionsHint)); + if (type == TYPE_EMOJI_STATUS) { + input.setHint(LocaleController.getString(R.string.SearchEmojiHint)); + } else if (type == TYPE_REACTIONS || type == TYPE_SET_DEFAULT_REACTION) { + input.setHint(LocaleController.getString(R.string.SearchReactionsHint)); + } else { + input.setHint(LocaleController.getString(R.string.SearchIconsHint)); + } + input.setHintTextColor(Theme.getColor(Theme.key_chat_emojiSearchIcon, resourcesProvider)); input.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); input.setImeOptions(EditorInfo.IME_ACTION_SEARCH | EditorInfo.IME_FLAG_NO_EXTRACT_UI); @@ -3505,6 +3662,8 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati input.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); input.setCursorWidth(1.5f); input.setMaxLines(1); + input.setSingleLine(true); + input.setLines(1); box.addView(input, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL, 36, -1, 32, 0)); clear = new ImageView(context); @@ -3586,16 +3745,17 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati final float px = emojiTabsShadow.getPivotX(), py = 0; final float fullr = (float) Math.sqrt(Math.max( - px * px + Math.pow(contentView.getHeight(), 2), - Math.pow(contentView.getWidth() - px, 2) + Math.pow(contentView.getHeight(), 2) + px * px + Math.pow(contentView.getHeight(), 2), + Math.pow(contentView.getWidth() - px, 2) + Math.pow(contentView.getHeight(), 2) )); for (int i = 0; i < emojiTabs.contentView.getChildCount(); ++i) { View child = emojiTabs.contentView.getChildAt(i); float ccx = child.getLeft() + child.getWidth() / 2f, ccy = child.getTop() + child.getHeight() / 2f; float distance = (float) Math.sqrt((ccx - px) * (ccx - px) + ccy * ccy * .4f); float scale = AndroidUtilities.cascade(containeritemst, distance, fullr, child.getHeight() * 1.75f); - if (Float.isNaN(scale)) + if (Float.isNaN(scale)) { scale = 0; + } child.setScaleX(scale); child.setScaleY(scale); } @@ -3661,7 +3821,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati public void setRecentReactions(List reactions) { recentReactionsToSet = reactions; - updateRows(true); + updateRows(false, true); } public void resetBackgroundBitmaps() { @@ -3692,6 +3852,19 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati public void setSelected(Long documentId) { selectedDocumentIds.clear(); selectedDocumentIds.add(documentId); + if (emojiGridView != null) { + for (int i = 0; i < emojiGridView.getChildCount(); i++) { + if (emojiGridView.getChildAt(i) instanceof ImageViewEmoji) { + ImageViewEmoji imageViewEmoji = (ImageViewEmoji) emojiGridView.getChildAt(i); + if (imageViewEmoji.span != null) { + imageViewEmoji.setViewSelected(selectedDocumentIds.contains(imageViewEmoji.span.getDocumentId()), true); + } else { + imageViewEmoji.setViewSelected(selectedDocumentIds.contains(0L), true); + } + } + } + emojiGridView.invalidate(); + } } public void setScrimDrawable(AnimatedEmojiDrawable.SwapAnimatedEmojiDrawable scrimDrawable, View drawableParent) { @@ -3726,7 +3899,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati float pressedViewScale = 1 + 2 * pressedProgress; canvas.save(); - canvas.translate(emojiGridView.getX() + selectedReactionView.getX(), emojiGridView.getY() + selectedReactionView.getY()); + canvas.translate(emojiGridView.getX() + selectedReactionView.getX(), gridViewContainer.getY() + emojiGridView.getY() + selectedReactionView.getY()); paint.setColor(Theme.getColor(Theme.key_actionBarDefaultSubmenuBackground, resourcesProvider)); canvas.drawRect(0, 0, selectedReactionView.getMeasuredWidth(), selectedReactionView.getMeasuredHeight(), paint); canvas.scale(pressedViewScale, pressedViewScale, selectedReactionView.getMeasuredWidth() / 2f, selectedReactionView.getMeasuredHeight()); @@ -4306,4 +4479,20 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati dismissed = true; } } + + public void setForumIconDrawable(Drawable drawable) { + forumIconDrawable = drawable; + if (forumIconImage != null) { + forumIconImage.imageReceiver.setImageBitmap(forumIconDrawable); + } + } + + @Override + public void setPressed(boolean pressed) { + return; + } + + void setAnimationsEnabled(boolean aniationsEnabled) { + this.animationsEnabled = aniationsEnabled; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java index 5bfee8131..c16ae456e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java @@ -18,7 +18,6 @@ import android.os.Build; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.util.Base64; -import android.util.Log; import android.util.TypedValue; import android.view.Gravity; import android.view.View; @@ -26,7 +25,6 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; @@ -72,7 +70,6 @@ import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.FlickerLoadingView; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.LinkSpanDrawable; -import org.telegram.ui.Components.RecyclerItemsEnterAnimator; import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.Components.URLSpanNoUnderline; import org.telegram.ui.Components.UndoView; @@ -178,7 +175,7 @@ public class SessionsActivity extends BaseFragment implements NotificationCenter }); listView.setVerticalScrollBarEnabled(false); listView.setEmptyView(emptyView); - listView.setAnimateEmptyView(true, 0); + listView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setAdapter(listAdapter); DefaultItemAnimator itemAnimator = new DefaultItemAnimator(); @@ -512,7 +509,7 @@ public class SessionsActivity extends BaseFragment implements NotificationCenter } @Override - protected void onBecomeFullyHidden() { + public void onBecomeFullyHidden() { if (undoView != null) { undoView.hide(true, 0); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/StatisticActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/StatisticActivity.java index 9e4311976..569d698eb 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/StatisticActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/StatisticActivity.java @@ -212,7 +212,7 @@ public class StatisticActivity extends BaseFragment implements NotificationCente if (recentPostsAll.size() > 0) { int lastPostId = recentPostsAll.get(0).counters.msg_id; int count = recentPostsAll.size(); - getMessagesStorage().getMessages(-chat.id, 0, false, count, lastPostId, 0, 0, classGuid, 0, false, 0, 0, true); + getMessagesStorage().getMessages(-chat.id, 0, false, count, lastPostId, 0, 0, classGuid, 0, false, 0, 0, true, false); } AndroidUtilities.runOnUIThread(() -> { @@ -1878,7 +1878,7 @@ public class StatisticActivity extends BaseFragment implements NotificationCente for (int i = 0; i < messages.size(); i++) { messageObjects.add(new MessageObject(currentAccount, messages.get(i), false, true)); } - getMessagesStorage().putMessages(messages, false, true, true, 0, false); + getMessagesStorage().putMessages(messages, false, true, true, 0, false, 0); } AndroidUtilities.runOnUIThread(() -> { @@ -2656,7 +2656,7 @@ public class StatisticActivity extends BaseFragment implements NotificationCente boolean[] needShowBulletin = new boolean[1]; ChatRightsEditActivity newFragment = new ChatRightsEditActivity(user.id, chat.id, finalCurrentParticipant.channelParticipant.admin_rights, null, finalCurrentParticipant.channelParticipant.banned_rights, finalCurrentParticipant.channelParticipant.rank, ChatRightsEditActivity.TYPE_ADMIN, true, finalIsAdmin, null) { @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (!isOpen && backward && needShowBulletin[0] && BulletinFactory.canShowBulletin(fragment)) { BulletinFactory.createPromoteToAdminBulletin(fragment, user.first_name).show(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java index 368e090e2..07214d477 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java @@ -137,6 +137,8 @@ public class StickersActivity extends BaseFragment implements NotificationCenter private int stickersShadowRow; private int rowCount; + private boolean updateSuggestStickers; + private boolean isListeningForFeaturedUpdate; ArrayList frozenEmojiPacks; @@ -429,6 +431,7 @@ public class StickersActivity extends BaseFragment implements NotificationCenter cell.setOnClickListener(v -> { Integer which = (Integer) v.getTag(); SharedConfig.setSuggestStickers(which); + updateSuggestStickers = true; listAdapter.notifyItemChanged(suggestRow); builder.getDismissRunnable().run(); }); @@ -1099,9 +1102,9 @@ public class StickersActivity extends BaseFragment implements NotificationCenter if (currentType == MediaDataController.TYPE_IMAGE) { settingsCell.setTextAndValueAndIcon(LocaleController.getString(R.string.ArchivedStickers), value, R.drawable.msg_archived_stickers, true); } else if (currentType == MediaDataController.TYPE_EMOJIPACKS) { - settingsCell.setTextAndValue(LocaleController.getString("ArchivedEmojiPacks", R.string.ArchivedEmojiPacks), value, true); + settingsCell.setTextAndValue(LocaleController.getString("ArchivedEmojiPacks", R.string.ArchivedEmojiPacks), value, false, true); } else { - settingsCell.setTextAndValue(LocaleController.getString("ArchivedMasks", R.string.ArchivedMasks), value, true); + settingsCell.setTextAndValue(LocaleController.getString("ArchivedMasks", R.string.ArchivedMasks), value, false, true); } } else if (position == masksRow) { int type = MediaDataController.TYPE_MASK; @@ -1128,7 +1131,8 @@ public class StickersActivity extends BaseFragment implements NotificationCenter value = LocaleController.getString("SuggestStickersNone", R.string.SuggestStickersNone); break; } - settingsCell.setTextAndValue(LocaleController.getString("SuggestStickers", R.string.SuggestStickers), value, true); + settingsCell.setTextAndValue(LocaleController.getString("SuggestStickers", R.string.SuggestStickers), value, updateSuggestStickers, true); + updateSuggestStickers = false; } } break; @@ -1441,7 +1445,7 @@ public class StickersActivity extends BaseFragment implements NotificationCenter if (!SharedConfig.stickersReorderingHintUsed && currentType != MediaDataController.TYPE_EMOJIPACKS) { SharedConfig.setStickersReorderingHintUsed(true); String stickersReorderHint = LocaleController.getString("StickersReorderHint", R.string.StickersReorderHint); - Bulletin.make(parentLayout, new ReorderingBulletinLayout(mContext, stickersReorderHint, null), ReorderingHintDrawable.DURATION * 2 + 250).show(); + Bulletin.make(parentLayout.getLastFragment(), new ReorderingBulletinLayout(mContext, stickersReorderHint, null), ReorderingHintDrawable.DURATION * 2 + 250).show(); } } } else if (actionModeShowed) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/TextMessageEnterTransition.java b/TMessagesProj/src/main/java/org/telegram/ui/TextMessageEnterTransition.java index 6109e9d18..b876eefb4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/TextMessageEnterTransition.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/TextMessageEnterTransition.java @@ -385,6 +385,7 @@ public class TextMessageEnterTransition implements MessageEnterTransitionContain NotificationCenter.getInstance(currentAccount).onAnimationFinish(animationIndex); container.removeTransition(TextMessageEnterTransition.this); messageView.setEnterTransitionInProgress(false); + messageView.getTransitionParams().lastDrawingBackgroundRect.set(messageView.getBackgroundDrawableLeft(), messageView.getBackgroundDrawableTop(), messageView.getBackgroundDrawableRight(), messageView.getBackgroundDrawableBottom()); chatActivityEnterView.setTextTransitionIsRunning(false); chatActivityEnterView.getEditField().setAlpha(1f); chatActivity.getReplyNameTextView().setAlpha(1f); @@ -530,6 +531,9 @@ public class TextMessageEnterTransition implements MessageEnterTransitionContain chatActivity.getReplyNameTextView().setAlpha(0f); chatActivity.getReplyObjectTextView().setAlpha(0f); + float replyHeight = AndroidUtilities.lerp(AndroidUtilities.dp(35), AndroidUtilities.dp(7) + Theme.chat_replyNamePaint.getTextSize() + Theme.chat_replyTextPaint.getTextSize(), progressX); + int offset = (int) Math.min(AndroidUtilities.dp(10), (replyHeight - AndroidUtilities.dp(35)) / 1.5f + AndroidUtilities.dp(10)); + float fromReplayX = replyFromStartX - container.getX(); float fromReplayY = replyFromStartY - container.getY(); float toReplayX = messageViewX + messageView.replyStartX; @@ -538,7 +542,7 @@ public class TextMessageEnterTransition implements MessageEnterTransitionContain int replyMessageColor; int replyOwnerMessageColor; int replyLineColor; - if (currentMessageObject.hasValidReplyMessageObject() && (currentMessageObject.replyMessageObject.type == 0 || !TextUtils.isEmpty(currentMessageObject.replyMessageObject.caption)) && !(currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame || currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice)) { + if (currentMessageObject.hasValidReplyMessageObject() && (currentMessageObject.replyMessageObject.type == MessageObject.TYPE_TEXT || !TextUtils.isEmpty(currentMessageObject.replyMessageObject.caption)) && !(currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame || currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice)) { replyMessageColor = getThemedColor(Theme.key_chat_outReplyMessageText); } else { replyMessageColor = getThemedColor(Theme.key_chat_outReplyMediaMessageText); @@ -562,18 +566,18 @@ public class TextMessageEnterTransition implements MessageEnterTransitionContain float replyY = (fromReplayY + AndroidUtilities.dp(12) * progress) * (1f - progress) + toReplayY * progress; Theme.chat_replyLinePaint.setColor(ColorUtils.setAlphaComponent(replyLineColor, (int) (Color.alpha(replyLineColor) * progressX))); - canvas.drawRect(replyX, replyY, replyX + AndroidUtilities.dp(2), replyY + AndroidUtilities.dp(35), Theme.chat_replyLinePaint); + canvas.drawRect(replyX, replyY, replyX + AndroidUtilities.dp(2), replyY + AndroidUtilities.lerp(AndroidUtilities.dp(35), messageView.replyHeight, progressX), Theme.chat_replyLinePaint); canvas.save(); - canvas.translate(AndroidUtilities.dp(10) * progressX, 0); + canvas.translate(offset * progressX, 0); if (messageView.needReplyImage) { canvas.save(); - messageView.replyImageReceiver.setImageCoords(replyX, replyY, AndroidUtilities.dp(35), AndroidUtilities.dp(35)); + messageView.replyImageReceiver.setImageCoords(replyX, replyY, replyHeight, replyHeight); messageView.replyImageReceiver.draw(canvas); canvas.translate(replyX, replyY); canvas.restore(); - canvas.translate(AndroidUtilities.dp(44), 0); + canvas.translate(offset - AndroidUtilities.dp(1) + replyHeight, 0); } float replyToMessageX = toReplayX - replyMessageDx; @@ -591,7 +595,7 @@ public class TextMessageEnterTransition implements MessageEnterTransitionContain if (messageView.replyTextLayout != null) { canvas.save(); - canvas.translate(replyMessageX, replyY + AndroidUtilities.dp(19)); + canvas.translate(replyMessageX, replyY + AndroidUtilities.lerp(AndroidUtilities.dp(19), Theme.chat_replyNamePaint.getTextSize() + AndroidUtilities.dp(5), progressX)); canvas.save(); SpoilerEffect.clipOutCanvas(canvas, messageView.replySpoilers); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ThemeActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ThemeActivity.java index b32b97440..a84b0c8f6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ThemeActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ThemeActivity.java @@ -198,6 +198,9 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No private int previousUpdatedType; private boolean previousByLocation; + private boolean updateRecordViaSco; + private boolean updateDistance; + private GpsLocationListener gpsLocationListener = new GpsLocationListener(); private GpsLocationListener networkLocationListener = new GpsLocationListener(); @@ -443,11 +446,16 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No private boolean setFontSize(int size) { if (size != SharedConfig.fontSize) { SharedConfig.fontSize = size; - SharedPreferences preferences = MessagesController.getGlobalMainSettings(); + SharedConfig.fontSizeIsDefault = false; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + if (preferences == null) { + return false; + } SharedPreferences.Editor editor = preferences.edit(); editor.putInt("fons_size", SharedConfig.fontSize); editor.commit(); - Theme.chat_msgTextPaint.setTextSize(AndroidUtilities.dp(SharedConfig.fontSize)); + + Theme.createCommonMessageResources(); RecyclerView.ViewHolder holder = listView.findViewHolderForAdapterPosition(textSizeRow); if (holder != null && holder.itemView instanceof TextSizeCell) { @@ -1023,6 +1031,7 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No LocaleController.getString("DistanceUnitsMiles", R.string.DistanceUnitsMiles) }, (dialog, which) -> { SharedConfig.setDistanceSystemType(which); + updateDistance = true; RecyclerView.ViewHolder holder = listView.findViewHolderForAdapterPosition(distanceRow); if (holder != null) { listAdapter.onBindViewHolder(holder, distanceRow); @@ -1048,6 +1057,7 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No textView.setOnClickListener(v -> { SharedConfig.recordViaSco = false; SharedConfig.saveConfig(); + updateRecordViaSco = true; dialogRef.get().dismiss(); RecyclerView.ViewHolder holder = listView.findViewHolderForAdapterPosition(bluetoothScoRow); @@ -1064,6 +1074,7 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No scoLinearLayout.setOnClickListener(v -> { SharedConfig.recordViaSco = true; SharedConfig.saveConfig(); + updateRecordViaSco = true; dialogRef.get().dismiss(); RecyclerView.ViewHolder holder = listView.findViewHolderForAdapterPosition(bluetoothScoRow); @@ -1263,7 +1274,7 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No } @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen) { AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid); AndroidUtilities.setAdjustResizeToNothing(getParentActivity(), classGuid); @@ -2124,9 +2135,11 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No } else { value = LocaleController.getString("DistanceUnitsMiles", R.string.DistanceUnitsMiles); } - cell.setTextAndValue(LocaleController.getString("DistanceUnits", R.string.DistanceUnits), value, false); + cell.setTextAndValue(LocaleController.getString("DistanceUnits", R.string.DistanceUnits), value, updateDistance, false); + updateDistance = false; } else if (position == bluetoothScoRow) { - cell.setTextAndValue(LocaleController.getString(R.string.MicrophoneForVoiceMessages), LocaleController.getString(SharedConfig.recordViaSco ? R.string.MicrophoneForVoiceMessagesSco : R.string.MicrophoneForVoiceMessagesBuiltIn), true); + cell.setTextAndValue(LocaleController.getString(R.string.MicrophoneForVoiceMessages), LocaleController.getString(SharedConfig.recordViaSco ? R.string.MicrophoneForVoiceMessagesSco : R.string.MicrophoneForVoiceMessagesBuiltIn), updateRecordViaSco, true); + updateRecordViaSco = false; } break; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ThemePreviewActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ThemePreviewActivity.java index 6cf34d3af..23c5c5a5c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ThemePreviewActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ThemePreviewActivity.java @@ -640,7 +640,7 @@ public class ThemePreviewActivity extends BaseFragment implements DownloadContro } showDialog(new ShareAlert(getParentActivity(), null, link, false, link, false) { @Override - protected void onSend(LongSparseArray dids, int count) { + protected void onSend(LongSparseArray dids, int count, TLRPC.TL_forumTopic topic) { if (dids.size() == 1) { undoView.showWithAction(dids.valueAt(0).id, UndoView.ACTION_SHARE_BACKGROUND, count); } else { @@ -2560,7 +2560,7 @@ public class ThemePreviewActivity extends BaseFragment implements DownloadContro } @Override - protected void onTransitionAnimationStart(boolean isOpen, boolean backward) { + public void onTransitionAnimationStart(boolean isOpen, boolean backward) { super.onTransitionAnimationStart(isOpen, backward); if (!isOpen) { if (screenType == SCREEN_TYPE_CHANGE_BACKGROUND) { @@ -3846,7 +3846,7 @@ public class ThemePreviewActivity extends BaseFragment implements DownloadContro customDialog.date = date; customDialog.verified = false; customDialog.isMedia = false; - customDialog.sent = true; + customDialog.sent = DialogCell.SENT_STATE_READ; dialogs.add(customDialog); customDialog = new DialogCell.CustomDialog(); @@ -3860,7 +3860,7 @@ public class ThemePreviewActivity extends BaseFragment implements DownloadContro customDialog.date = date - 60 * 60; customDialog.verified = false; customDialog.isMedia = false; - customDialog.sent = false; + customDialog.sent = DialogCell.SENT_STATE_NOTHING; dialogs.add(customDialog); customDialog = new DialogCell.CustomDialog(); @@ -3874,7 +3874,7 @@ public class ThemePreviewActivity extends BaseFragment implements DownloadContro customDialog.date = date - 60 * 60 * 2; customDialog.verified = false; customDialog.isMedia = true; - customDialog.sent = false; + customDialog.sent = DialogCell.SENT_STATE_NOTHING; dialogs.add(customDialog); customDialog = new DialogCell.CustomDialog(); @@ -3888,7 +3888,7 @@ public class ThemePreviewActivity extends BaseFragment implements DownloadContro customDialog.date = date - 60 * 60 * 3; customDialog.verified = false; customDialog.isMedia = false; - customDialog.sent = false; + customDialog.sent = DialogCell.SENT_STATE_NOTHING; dialogs.add(customDialog); customDialog = new DialogCell.CustomDialog(); @@ -3902,7 +3902,7 @@ public class ThemePreviewActivity extends BaseFragment implements DownloadContro customDialog.date = date - 60 * 60 * 4; customDialog.verified = false; customDialog.isMedia = false; - customDialog.sent = true; + customDialog.sent = DialogCell.SENT_STATE_READ; dialogs.add(customDialog); customDialog = new DialogCell.CustomDialog(); @@ -3916,7 +3916,7 @@ public class ThemePreviewActivity extends BaseFragment implements DownloadContro customDialog.date = date - 60 * 60 * 5; customDialog.verified = false; customDialog.isMedia = false; - customDialog.sent = false; + customDialog.sent = DialogCell.SENT_STATE_NOTHING; dialogs.add(customDialog); customDialog = new DialogCell.CustomDialog(); @@ -3930,7 +3930,7 @@ public class ThemePreviewActivity extends BaseFragment implements DownloadContro customDialog.date = date - 60 * 60 * 6; customDialog.verified = true; customDialog.isMedia = false; - customDialog.sent = false; + customDialog.sent = DialogCell.SENT_STATE_NOTHING; dialogs.add(customDialog); customDialog = new DialogCell.CustomDialog(); @@ -3944,7 +3944,7 @@ public class ThemePreviewActivity extends BaseFragment implements DownloadContro customDialog.date = date - 60 * 60 * 7; customDialog.verified = true; customDialog.isMedia = false; - customDialog.sent = false; + customDialog.sent = DialogCell.SENT_STATE_NOTHING; dialogs.add(customDialog); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/TooManyCommunitiesActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/TooManyCommunitiesActivity.java index 2ad9cdfe7..0914ffd5b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/TooManyCommunitiesActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/TooManyCommunitiesActivity.java @@ -603,7 +603,7 @@ public class TooManyCommunitiesActivity extends BaseFragment { TLRPC.Chat chat = inactiveChats.get(a); boolean found = false; for (int i = 0; i < 2; i++) { - String name = i == 0 ? chat.title : chat.username; + String name = i == 0 ? chat.title : ChatObject.getPublicUsername(chat); if (name == null) { continue; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/TopicCreateFragment.java b/TMessagesProj/src/main/java/org/telegram/ui/TopicCreateFragment.java new file mode 100644 index 000000000..ba8470d3d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/TopicCreateFragment.java @@ -0,0 +1,527 @@ +package org.telegram.ui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Canvas; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Vibrator; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.Gravity; +import android.view.View; +import android.view.animation.OvershootInterpolator; +import android.view.inputmethod.EditorInfo; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MediaDataController; +import org.telegram.messenger.MessageObject; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; +import org.telegram.messenger.Utilities; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Cells.HeaderCell; +import org.telegram.ui.Components.AnimatedEmojiDrawable; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.BulletinFactory; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.EditTextBoldCursor; +import org.telegram.ui.Components.Forum.ForumBubbleDrawable; +import org.telegram.ui.Components.Forum.ForumUtilities; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.LetterDrawable; +import org.telegram.ui.Components.Premium.PremiumFeatureBottomSheet; +import org.telegram.ui.Components.ReplaceableIconDrawable; +import org.telegram.ui.Components.SizeNotifierFrameLayout; + +import java.util.ArrayList; + +public class TopicCreateFragment extends BaseFragment { + + private final static int CREATE_ID = 1; + private final static int EDIT_ID = 2; + long chatId; + long selectedEmojiDocumentId; + int topicId; + + EditTextBoldCursor editTextBoldCursor; + SelectAnimatedEmojiDialog selectAnimatedEmojiDialog; + BackupImageView[] backupImageView = new BackupImageView[2]; + + String firstSymbol = ""; + boolean created; + Drawable defaultIconDrawable; + ReplaceableIconDrawable replaceableIconDrawable; + TLRPC.TL_forumTopic topicForEdit; + ForumBubbleDrawable forumBubbleDrawable; + + int iconColor; + + public static TopicCreateFragment create(long chatId, int topicId) { + Bundle bundle = new Bundle(); + bundle.putLong("chat_id", chatId); + bundle.putInt("topic_id", topicId); + return new TopicCreateFragment(bundle); + } + + private TopicCreateFragment(Bundle bundle) { + super(bundle); + } + + @Override + public boolean onFragmentCreate() { + chatId = arguments.getLong("chat_id"); + topicId = arguments.getInt("topic_id", 0); + if (topicId != 0) { + topicForEdit = getMessagesController().getTopicsController().findTopic(chatId, topicId); + if (topicForEdit == null) { + return false; + } + iconColor = topicForEdit.icon_color; + } else { + iconColor = ForumBubbleDrawable.serverSupportedColor[Math.abs(Utilities.random.nextInt() % ForumBubbleDrawable.serverSupportedColor.length)]; + } + return super.onFragmentCreate(); + } + + @Override + public View createView(Context context) { + if (topicForEdit != null) { + actionBar.setTitle(LocaleController.getString("NewTopic", R.string.EditTopic)); + } else { + actionBar.setTitle(LocaleController.getString("NewTopic", R.string.NewTopic)); + } + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + return; + } + if (id == CREATE_ID) { + String topicName = editTextBoldCursor.getText() == null ? null : editTextBoldCursor.getText().toString(); + if (TextUtils.isEmpty(topicName)) { + Vibrator v = (Vibrator) getParentActivity().getSystemService(Context.VIBRATOR_SERVICE); + if (v != null) { + v.vibrate(200); + } + AndroidUtilities.shakeView(editTextBoldCursor); + return; + } + + if (created) { + return; + } + + final AlertDialog progressDialog = new AlertDialog(getParentActivity(), 3); + progressDialog.showDelayed(500); + created = true; + + TLRPC.TL_channels_createForumTopic reqSend = new TLRPC.TL_channels_createForumTopic(); + + reqSend.channel = getMessagesController().getInputChannel(chatId); + reqSend.title = topicName; + if (selectedEmojiDocumentId != 0) { + reqSend.icon_emoji_id = selectedEmojiDocumentId; + reqSend.flags |= 8; + } + long randomId = Utilities.random.nextLong(); + reqSend.random_id = randomId; + reqSend.icon_color = iconColor; + reqSend.flags |= 1; + + ConnectionsManager.getInstance(currentAccount).sendRequest(reqSend, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + if (response != null) { + TLRPC.Updates updates = (TLRPC.Updates) response; + for (int i = 0; i < updates.updates.size(); i++) { + if (updates.updates.get(i) instanceof TLRPC.TL_updateMessageID) { + TLRPC.TL_updateMessageID updateMessageID = (TLRPC.TL_updateMessageID) updates.updates.get(i); + Bundle args = new Bundle(); + args.putLong("chat_id", chatId); + args.putInt("message_id", 1); + args.putInt("unread_count", 0); + args.putBoolean("historyPreloaded", false); + ChatActivity chatActivity = new ChatActivity(args); + TLRPC.TL_messageActionTopicCreate actionMessage = new TLRPC.TL_messageActionTopicCreate(); + actionMessage.title = topicName; + TLRPC.TL_messageService message = new TLRPC.TL_messageService(); + message.action = actionMessage; + message.peer_id = getMessagesController().getPeer(-chatId); + message.dialog_id = -chatId; + message.id = updateMessageID.id; + message.date = (int) (System.currentTimeMillis() / 1000); + + ArrayList messageObjects = new ArrayList<>(); + messageObjects.add(new MessageObject(currentAccount, message, false, false)); + TLRPC.Chat chatLocal = getMessagesController().getChat(chatId); + TLRPC.TL_forumTopic forumTopic = new TLRPC.TL_forumTopic(); + forumTopic.id = updateMessageID.id; + if (selectedEmojiDocumentId != 0) { + forumTopic.icon_emoji_id = selectedEmojiDocumentId; + forumTopic.flags |= 1; + } + forumTopic.my = true; + forumTopic.flags |= 2; + forumTopic.topicStartMessage = message; + forumTopic.title = topicName; + forumTopic.top_message = message.id; + forumTopic.topMessage = message; + forumTopic.from_id = getMessagesController().getPeer(getUserConfig().clientUserId); + forumTopic.notify_settings = new TLRPC.TL_peerNotifySettings(); + forumTopic.icon_color = iconColor; + + chatActivity.setThreadMessages(messageObjects, chatLocal, message.id, 1, 1, forumTopic); + chatActivity.justCreatedTopic = true; + getMessagesController().getTopicsController().onTopicCreated(-chatId, forumTopic, true); + presentFragment(chatActivity); + } + } + } + progressDialog.dismiss(); + })); + } else if (id == EDIT_ID) { + + String topicName = editTextBoldCursor.getText() == null ? null : editTextBoldCursor.getText().toString(); + if (TextUtils.isEmpty(topicName)) { + Vibrator v = (Vibrator) getParentActivity().getSystemService(Context.VIBRATOR_SERVICE); + if (v != null) { + v.vibrate(200); + } + AndroidUtilities.shakeView(editTextBoldCursor); + return; + } + if (!topicForEdit.title.equals(topicName) || topicForEdit.icon_emoji_id != selectedEmojiDocumentId) { + TLRPC.TL_channels_editForumTopic editForumRequest = new TLRPC.TL_channels_editForumTopic(); + editForumRequest.channel = getMessagesController().getInputChannel(chatId); + editForumRequest.topic_id = topicForEdit.id; + if (!topicForEdit.title.equals(topicName)) { + editForumRequest.title = topicName; + editForumRequest.flags |= 1; + } + if (topicForEdit.icon_emoji_id != editForumRequest.icon_emoji_id) { + editForumRequest.icon_emoji_id = selectedEmojiDocumentId; + editForumRequest.flags |= 2; + } + ConnectionsManager.getInstance(currentAccount).sendRequest(editForumRequest, (response, error) -> { + + }); + } + + + topicForEdit.icon_emoji_id = selectedEmojiDocumentId; + if (selectedEmojiDocumentId != 0) { + topicForEdit.flags |= 1; + } else { + topicForEdit.flags &= ~1; + } + topicForEdit.title = topicName; + getMessagesController().getTopicsController().onTopicEdited(-chatId, topicForEdit); + finishFragment(); + } + } + }); + if (topicForEdit == null) { + actionBar.createMenu().addItem(CREATE_ID, LocaleController.getString("Create", R.string.Create).toUpperCase()); + } else { + actionBar.createMenu().addItem(EDIT_ID, R.drawable.ic_ab_done); + } + + FrameLayout contentView = new SizeNotifierFrameLayout(context) { + boolean keyboardWasShown; + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + measureKeyboardHeight(); + if (getKeyboardHeight() == 0 && !keyboardWasShown) { + SharedPreferences sharedPreferences = MessagesController.getGlobalEmojiSettings(); + keyboardHeight = sharedPreferences.getInt("kbd_height", AndroidUtilities.dp(200)); + setPadding(0, 0, 0, keyboardHeight); + } else { + keyboardWasShown = true; + setPadding(0, 0, 0, 0); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + }; + fragmentView = contentView; + + LinearLayout linearLayout = new LinearLayout(context); + linearLayout.setOrientation(LinearLayout.VERTICAL); + contentView.addView(linearLayout); + + HeaderCell headerCell = new HeaderCell(context); + headerCell.setText(LocaleController.getString("CreateTopicTitle", R.string.CreateTopicTitle)); + + FrameLayout editTextContainer = new FrameLayout(context); + + editTextBoldCursor = new EditTextBoldCursor(context); + editTextBoldCursor.setHintText(LocaleController.getString("EnterTopicName", R.string.EnterTopicName)); + editTextBoldCursor.setHintColor(getThemedColor(Theme.key_chat_messagePanelHint)); + editTextBoldCursor.setTextColor(getThemedColor(Theme.key_chat_messagePanelText)); + editTextBoldCursor.setPadding(AndroidUtilities.dp(0), editTextBoldCursor.getPaddingTop(), AndroidUtilities.dp(0), editTextBoldCursor.getPaddingBottom()); + editTextBoldCursor.setBackgroundDrawable(null); + editTextBoldCursor.setSingleLine(true); + editTextBoldCursor.setInputType(editTextBoldCursor.getInputType() | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES); + editTextContainer.addView(editTextBoldCursor, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0, 51, 4, 21, 4)); + + editTextBoldCursor.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + String trimmedString = s.toString().trim(); + String oldFirstSymbol = firstSymbol; + if (trimmedString.length() > 0) { + firstSymbol = trimmedString.substring(0, 1).toUpperCase(); + } else { + firstSymbol = ""; + } + if (!oldFirstSymbol.equals(firstSymbol)) { + LetterDrawable letterDrawable = new LetterDrawable(null, LetterDrawable.STYLE_TOPIC_DRAWABLE); + letterDrawable.setTitle(firstSymbol); + replaceableIconDrawable.setIcon(letterDrawable, true); + + } + } + }); + FrameLayout iconContainer = new FrameLayout(context) { + + ValueAnimator backAnimator; + boolean pressed; + float pressedProgress; + + @Override + protected void dispatchDraw(Canvas canvas) { + float s = 0.8f + 0.2f * (1f - pressedProgress); + canvas.save(); + canvas.scale(s, s, getMeasuredHeight() / 2f, getMeasuredWidth() / 2f); + super.dispatchDraw(canvas); + canvas.restore(); + updatePressedProgress(); + } + + @Override + public void setPressed(boolean pressed) { + super.setPressed(pressed); + if (this.pressed != pressed) { + this.pressed = pressed; + invalidate(); + if (pressed) { + if (backAnimator != null) { + backAnimator.removeAllListeners(); + backAnimator.cancel(); + } + } + if (!pressed && pressedProgress != 0) { + backAnimator = ValueAnimator.ofFloat(pressedProgress, 0); + backAnimator.addUpdateListener(animation -> { + pressedProgress = (float) animation.getAnimatedValue(); + invalidate(); + }); + backAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + backAnimator = null; + } + }); + backAnimator.setInterpolator(new OvershootInterpolator(5.0f)); + backAnimator.setDuration(350); + backAnimator.start(); + } + } + } + + public void updatePressedProgress() { + if (isPressed() && pressedProgress != 1f) { + pressedProgress = Utilities.clamp(pressedProgress + 16f / 100f, 1f, 0); + invalidate(); + } + } + }; + iconContainer.setOnClickListener(v -> { + if (selectedEmojiDocumentId == 0 && topicForEdit == null) { + iconColor = forumBubbleDrawable.moveNexColor(); + } + }); + for (int i = 0; i < 2; i++) { + backupImageView[i] = new BackupImageView(context); + iconContainer.addView(backupImageView[i], LayoutHelper.createFrame(28, 28, Gravity.CENTER)); + } + editTextContainer.addView(iconContainer, LayoutHelper.createFrame(40, 40, Gravity.CENTER_VERTICAL, 10, 0, 0, 0)); + linearLayout.addView(headerCell); + linearLayout.addView(editTextContainer); + + + FrameLayout emojiContainer = new FrameLayout(context); + Drawable shadowDrawable = Theme.getThemedDrawable(context, R.drawable.greydivider_top, Theme.getColor(Theme.key_windowBackgroundGrayShadow)); + Drawable background = new ColorDrawable(Theme.getColor(Theme.key_windowBackgroundGray)); + CombinedDrawable combinedDrawable = new CombinedDrawable(background, shadowDrawable, 0, 0); + combinedDrawable.setFullsize(true); + emojiContainer.setBackgroundDrawable(combinedDrawable); + emojiContainer.setClipChildren(false); + + + selectAnimatedEmojiDialog = new SelectAnimatedEmojiDialog(this, getContext(), false, null, SelectAnimatedEmojiDialog.TYPE_TOPIC_ICON, null) { + + private boolean firstLayout = true; + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (firstLayout) { + firstLayout = false; + selectAnimatedEmojiDialog.onShow(null); + } + } + + protected void onEmojiSelected(View view, Long documentId, TLRPC.Document document, Integer until) { + boolean setIsFree = false; + if (!TextUtils.isEmpty(UserConfig.getInstance(currentAccount).defaultTopicIcons)) { + TLRPC.TL_messages_stickerSet stickerSet = getMediaDataController().getStickerSetByEmojiOrName(UserConfig.getInstance(currentAccount).defaultTopicIcons); + long stickerSetId = stickerSet == null ? 0 : stickerSet.set.id; + long documentSetId = MediaDataController.getStickerSetId(document); + setIsFree = stickerSetId == documentSetId; + } + + selectEmoji(documentId, setIsFree); + } + }; + + selectAnimatedEmojiDialog.setAnimationsEnabled(fragmentBeginToShow); + selectAnimatedEmojiDialog.setClipChildren(false); + emojiContainer.addView(selectAnimatedEmojiDialog, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0, 12, 12, 12, 12)); + + linearLayout.addView(emojiContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + Drawable drawable = ForumUtilities.createTopicDrawable("", iconColor); + CombinedDrawable topicCombinedDrawable = (CombinedDrawable) drawable; + forumBubbleDrawable = (ForumBubbleDrawable) topicCombinedDrawable.getBackgroundDrawable(); + + + replaceableIconDrawable = new ReplaceableIconDrawable(context); + CombinedDrawable combinedDrawable2 = new CombinedDrawable(drawable, replaceableIconDrawable, 0, 0); + combinedDrawable2.setFullsize(true); + + selectAnimatedEmojiDialog.setForumIconDrawable(combinedDrawable2); + + defaultIconDrawable = combinedDrawable2; + + replaceableIconDrawable.addView(backupImageView[0]); + replaceableIconDrawable.addView(backupImageView[1]); + backupImageView[0].setImageDrawable(defaultIconDrawable); + AndroidUtilities.updateViewVisibilityAnimated(backupImageView[0], true, 1, false); + AndroidUtilities.updateViewVisibilityAnimated(backupImageView[1], false, 1, false); + + forumBubbleDrawable.addParent(backupImageView[0]); + forumBubbleDrawable.addParent(backupImageView[1]); + if (topicForEdit != null) { + editTextBoldCursor.setText(topicForEdit.title); + selectEmoji(topicForEdit.icon_emoji_id, true); + } else { + selectEmoji(0L, true); + } + + return fragmentView; + } + + private void selectEmoji(Long documentId, boolean free) { + long docId = documentId == null ? 0L : documentId; + selectAnimatedEmojiDialog.setSelected(docId); + if (selectedEmojiDocumentId == docId) { + return; + } + + if (!free && docId != 0 && !getUserConfig().isPremium()) { + TLRPC.Document emoji = AnimatedEmojiDrawable.findDocument(currentAccount, documentId); + if (emoji != null) { + BulletinFactory.of(this) + .createEmojiBulletin( + emoji, + AndroidUtilities.replaceTags(LocaleController.getString("UnlockPremiumEmojiHint", R.string.UnlockPremiumEmojiHint)), + LocaleController.getString("PremiumMore", R.string.PremiumMore), + () -> { + new PremiumFeatureBottomSheet(this, PremiumPreviewFragment.PREMIUM_FEATURE_ANIMATED_EMOJI, false).show(); + } + ).show(); + } + return; + } + + selectedEmojiDocumentId = docId; + + if (docId != 0) { + AnimatedEmojiDrawable animatedEmojiDrawable = new AnimatedEmojiDrawable(AnimatedEmojiDrawable.CACHE_TYPE_FORUM_TOPIC, currentAccount, docId); + backupImageView[1].setAnimatedEmojiDrawable(animatedEmojiDrawable); + backupImageView[1].setImageDrawable(null); + } else { + LetterDrawable letterDrawable = new LetterDrawable(null, LetterDrawable.STYLE_TOPIC_DRAWABLE); + letterDrawable.setTitle(firstSymbol); + replaceableIconDrawable.setIcon(letterDrawable, false); + backupImageView[1].setImageDrawable(defaultIconDrawable); + backupImageView[1].setAnimatedEmojiDrawable(null); + } + + BackupImageView tmp = backupImageView[0]; + backupImageView[0] = backupImageView[1]; + backupImageView[1] = tmp; + + AndroidUtilities.updateViewVisibilityAnimated(backupImageView[0], true, 0.5f, true); + AndroidUtilities.updateViewVisibilityAnimated(backupImageView[1], false, 0.5f, true); + } + + int animationIndex = 0; + + @Override + public void onTransitionAnimationStart(boolean isOpen, boolean backward) { + super.onTransitionAnimationStart(isOpen, backward); + if (isOpen) { + animationIndex = getNotificationCenter().setAnimationInProgress(animationIndex, null); + } + } + + @Override + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + super.onTransitionAnimationEnd(isOpen, backward); + if (!isOpen && created) { + removeSelfFromStack(); + } + + getNotificationCenter().onAnimationFinish(animationIndex); + selectAnimatedEmojiDialog.setAnimationsEnabled(fragmentBeginToShow); + } + + @Override + public void onResume() { + super.onResume(); + editTextBoldCursor.requestFocus(); + AndroidUtilities.showKeyboard(editTextBoldCursor); + AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid); + } + + public void showKeyboard() { + editTextBoldCursor.requestFocus(); + AndroidUtilities.showKeyboard(editTextBoldCursor); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/TopicsFragment.java b/TMessagesProj/src/main/java/org/telegram/ui/TopicsFragment.java new file mode 100644 index 000000000..2e156250f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/TopicsFragment.java @@ -0,0 +1,2338 @@ +package org.telegram.ui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.StateListAnimator; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Outline; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Bundle; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.HapticFeedbackConstants; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewOutlineProvider; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.collection.LongSparseArray; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.ColorUtils; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ChatObject; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessageObject; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.MessagesStorage; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.NotificationsController; +import org.telegram.messenger.R; +import org.telegram.messenger.SharedConfig; +import org.telegram.messenger.TopicsController; +import org.telegram.messenger.Utilities; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.ActionBarMenuSubItem; +import org.telegram.ui.ActionBar.ActionBarPopupWindow; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BackDrawable; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Adapters.FiltersView; +import org.telegram.ui.Cells.DialogCell; +import org.telegram.ui.Cells.GraySectionCell; +import org.telegram.ui.Cells.TopicSearchCell; +import org.telegram.ui.Components.AlertsCreator; +import org.telegram.ui.Components.AnimatedEmojiDrawable; +import org.telegram.ui.Components.Bulletin; +import org.telegram.ui.Components.BulletinFactory; +import org.telegram.ui.Components.ChatAvatarContainer; +import org.telegram.ui.Components.ChatNotificationsPopupWrapper; +import org.telegram.ui.Components.ColoredImageSpan; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.CubicBezierInterpolator; +import org.telegram.ui.Components.EditTextBoldCursor; +import org.telegram.ui.Components.FlickerLoadingView; +import org.telegram.ui.Components.Forum.ForumUtilities; +import org.telegram.ui.Components.FragmentContextView; +import org.telegram.ui.Components.InviteMembersBottomSheet; +import org.telegram.ui.Components.JoinGroupAlert; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.NumberTextView; +import org.telegram.ui.Components.RLottieImageView; +import org.telegram.ui.Components.RadialProgressView; +import org.telegram.ui.Components.RecyclerItemsEnterAnimator; +import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.SizeNotifierFrameLayout; +import org.telegram.ui.Components.StickerEmptyView; +import org.telegram.ui.Components.UnreadCounterTextView; +import org.telegram.ui.Delegates.ChatActivityMemberRequestsDelegate; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Objects; + +public class TopicsFragment extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, FragmentContextView.ChatActivityInterface { + + final long chatId; + ArrayList forumTopics = new ArrayList<>(); + + SizeNotifierFrameLayout contentView; + ChatAvatarContainer avatarContainer; + ChatActivity.ThemeDelegate themeDelegate; + FrameLayout floatingButtonContainer; + Adapter adapter = new Adapter(); + private final TopicsController topicsController; + OnTopicSelectedListener onTopicSelectedListener; + + private float floatingButtonTranslation; + private float floatingButtonHideProgress; + + private boolean floatingHidden = false; + private final AccelerateDecelerateInterpolator floatingInterpolator = new AccelerateDecelerateInterpolator(); + + LinearLayoutManager layoutManager; + + private static final int toggle_id = 1; + private static final int add_member_id = 2; + private static final int create_topic_id = 3; + private static final int pin_id = 4; + private static final int unpin_id = 5; + private static final int mute_id = 6; + private static final int delete_id = 7; + private static final int read_id = 8; + private static final int close_topic_id = 9; + private static final int restart_topic_id = 10; + private static final int delete_chat_id = 11; + + private boolean removeFragmentOnTransitionEnd; + TLRPC.ChatFull chatFull; + boolean canShowCreateTopic; + private UnreadCounterTextView bottomOverlayChatText; + private RecyclerListView recyclerListView; + private ActionBarMenuSubItem createTopicSubmenu; + private ActionBarMenuSubItem addMemberSubMenu; + private ActionBarMenuSubItem deleteChatSubmenu; + private boolean bottomPannelVisible = true; + private float searchAnimationProgress = 0f; + private float searchAnimation2Progress = 0f; + + HashSet selectedTopics = new HashSet<>(); + private NumberTextView selectedDialogsCountTextView; + private ActionBarMenuItem pinItem; + private ActionBarMenuItem unpinItem; + private ActionBarMenuItem muteItem; + private ActionBarMenuItem deleteItem; + private ActionBarMenuSubItem readItem; + private ActionBarMenuSubItem closeTopic; + private ActionBarMenuSubItem restartTopic; + ActionBarMenuItem otherItem; + private RadialProgressView bottomOverlayProgress; + private FrameLayout bottomOverlayContainer; + private ActionBarMenuItem searchItem; + private ActionBarMenuItem other; + private SearchContainer searchContainer; + private boolean searching, searchingNotEmpty; + private boolean opnendForSelect; + private boolean openedForForward; + HashSet excludeTopics; + private boolean mute = false; + + private boolean scrollToTop; + private boolean endReached; + StickerEmptyView topicsEmptyView; + + FragmentContextView fragmentContextView; + private ChatObject.Call groupCall; + private DefaultItemAnimator itemAnimator; + private boolean loadingTopics; + RecyclerItemsEnterAnimator itemsEnterAnimator; + DialogsActivity dialogsActivity; + + private boolean updateAnimated; + private long lastAnimatedDuration; + + private int transitionAnimationIndex; + private int transitionAnimationGlobalIndex; + private View blurredView; + + private boolean joinRequested; + private ChatActivityMemberRequestsDelegate pendingRequestsDelegate; + + float slideFragmentProgress = 1f; + boolean isSlideBackTransition; + boolean isDrawerTransition; + ValueAnimator slideBackTransitionAnimator; + + private FrameLayout topView; + + public TopicsFragment(Bundle bundle) { + super(bundle); + chatId = arguments.getLong("chat_id", 0); + opnendForSelect = arguments.getBoolean("for_select", false); + openedForForward = arguments.getBoolean("forward_to", false); + topicsController = getMessagesController().getTopicsController(); + } + + public static void prepareToSwitchAnimation(ChatActivity chatActivity) { + boolean needCreateTopicsFragment = false; + if (chatActivity.getParentLayout().getFragmentStack().size() <= 1) { + needCreateTopicsFragment = true; + } else { + BaseFragment previousFragment = chatActivity.getParentLayout().getFragmentStack().get(chatActivity.getParentLayout().getFragmentStack().size() - 2); + if (previousFragment instanceof TopicsFragment) { + TopicsFragment topicsFragment = (TopicsFragment) previousFragment; + if (topicsFragment.chatId != -chatActivity.getDialogId()) { + needCreateTopicsFragment = true; + } + } else { + needCreateTopicsFragment = true; + } + } + if (needCreateTopicsFragment) { + Bundle bundle = new Bundle(); + bundle.putLong("chat_id", -chatActivity.getDialogId()); + TopicsFragment topicsFragment = new TopicsFragment(bundle); + chatActivity.getParentLayout().addFragmentToStack(topicsFragment, chatActivity.getParentLayout().getFragmentStack().size() - 1); + } + chatActivity.setSwitchFromTopics(true); + chatActivity.finishFragment(); + } + + @Override + public View createView(Context context) { + fragmentView = contentView = new SizeNotifierFrameLayout(context) { + { + setWillNotDraw(false); + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + + getParentLayout().drawHeaderShadow(canvas, (int) (actionBar.getY() + actionBar.getHeight() * actionBar.getScaleY())); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + int actionBarHeight = 0; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child instanceof ActionBar) { + child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + actionBarHeight = child.getMeasuredHeight(); + } + } + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (!(child instanceof ActionBar)) { + if (child.getFitsSystemWindows()) { + measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); + } else { + measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, actionBarHeight); + } + } + } + setMeasuredDimension(width, height); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + final int count = getChildCount(); + + final int parentLeft = getPaddingLeft(); + final int parentRight = right - left - getPaddingRight(); + + final int parentTop = getPaddingTop(); + final int parentBottom = bottom - top - getPaddingBottom(); + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + final int width = child.getMeasuredWidth(); + final int height = child.getMeasuredHeight(); + + int childLeft; + int childTop; + + int gravity = lp.gravity; + if (gravity == -1) { + gravity = Gravity.NO_GRAVITY; + } + + boolean forceLeftGravity = false; + final int layoutDirection; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { + layoutDirection = getLayoutDirection(); + } else { + layoutDirection = 0; + } + final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); + final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; + + switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + case Gravity.CENTER_HORIZONTAL: + childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + + lp.leftMargin - lp.rightMargin; + break; + case Gravity.RIGHT: + if (!forceLeftGravity) { + childLeft = parentRight - width - lp.rightMargin; + break; + } + case Gravity.LEFT: + default: + childLeft = parentLeft + lp.leftMargin; + } + + switch (verticalGravity) { + case Gravity.CENTER_VERTICAL: + childTop = parentTop + (parentBottom - parentTop - height) / 2 + + lp.topMargin - lp.bottomMargin; + break; + case Gravity.BOTTOM: + childTop = parentBottom - height - lp.bottomMargin; + break; + case Gravity.TOP: + default: + childTop = parentTop + lp.topMargin; + if (child == topView) { + topView.setPadding(0, actionBar.getTop() + actionBar.getMeasuredHeight(), 0, 0); + } else if (!(child instanceof ActionBar)) { + childTop += actionBar.getTop() + actionBar.getMeasuredHeight(); + } + } + + child.layout(childLeft, childTop, childLeft + width, childTop + height); + } + } + } + + @Override + protected void drawList(Canvas blurCanvas, boolean top) { + for (int i = 0; i < recyclerListView.getChildCount(); i++) { + View child = recyclerListView.getChildAt(i); + if (child.getY() < AndroidUtilities.dp(100) && child.getVisibility() == View.VISIBLE) { + int restore = blurCanvas.save(); + blurCanvas.translate(recyclerListView.getX() + child.getX(), getY() + recyclerListView.getY() + child.getY()); + child.draw(blurCanvas); + blurCanvas.restoreToCount(restore); + } + } + } + }; + contentView.needBlur = true; + + actionBar.setAddToContainer(false); + actionBar.setCastShadows(false); + actionBar.setClipContent(true); + + actionBar.setBackButtonDrawable(new BackDrawable(false)); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + if (selectedTopics.size() > 0) { + clearSelectedTopics(); + return; + } + finishFragment(); + return; + } + switch (id) { + case toggle_id: + switchToChat(false); + break; + case add_member_id: + if (chatFull != null && chatFull.participants != null) { + LongSparseArray users = new LongSparseArray<>(); + for (int a = 0; a < chatFull.participants.participants.size(); a++) { + users.put(chatFull.participants.participants.get(a).user_id, null); + } + long chatId = chatFull.id; + InviteMembersBottomSheet bottomSheet = new InviteMembersBottomSheet(context, currentAccount, users, chatFull.id, TopicsFragment.this, themeDelegate) { + @Override + protected boolean canGenerateLink() { + TLRPC.Chat chat = getMessagesController().getChat(chatId); + return chat != null && ChatObject.canUserDoAdminAction(chat, ChatObject.ACTION_INVITE); + } + }; + bottomSheet.setDelegate((users1, fwdCount) -> { + int N = users1.size(); + int[] finished = new int[1]; + for (int a = 0; a < N; a++) { + TLRPC.User user = users1.get(a); + getMessagesController().addUserToChat(chatId, user, fwdCount, null, TopicsFragment.this, () -> { + if (++finished[0] == N) { + BulletinFactory.of(TopicsFragment.this).createUsersAddedBulletin(users1, getMessagesController().getChat(chatId)).show(); + } + }); + } + }); + bottomSheet.show(); + } + break; + case create_topic_id: + TopicCreateFragment fragment = TopicCreateFragment.create(chatId, 0); + presentFragment(fragment); + AndroidUtilities.runOnUIThread(() -> { + fragment.showKeyboard(); + }, 200); + break; + case delete_chat_id: + TLRPC.Chat chatLocal = getMessagesController().getChat(chatId); + AlertsCreator.createClearOrDeleteDialogAlert(TopicsFragment.this, false, chatLocal, null, false, true, false, (param) -> { + getMessagesController().deleteDialog(-chatId, 0); + finishFragment(); + }, themeDelegate); + break; + case delete_id: + deleteTopics(selectedTopics, () -> { + clearSelectedTopics(); + }); + break; + case pin_id: + case unpin_id: + if (selectedTopics.size() > 0) { + scrollToTop = true; + updateAnimated = true; + topicsController.pinTopic(chatId, selectedTopics.iterator().next(), id == pin_id); + } + clearSelectedTopics(); + break; + case mute_id: + Iterator iterator = selectedTopics.iterator(); + while (iterator.hasNext()) { + int topicId = iterator.next(); + getNotificationsController().muteDialog(-chatId, topicId, mute); + } + clearSelectedTopics(); + break; + case restart_topic_id: + case close_topic_id: + updateAnimated = true; + ArrayList list = new ArrayList<>(selectedTopics); + for (int i = 0; i < list.size(); ++i) { + topicsController.toggleCloseTopic(chatId, list.get(i), id == close_topic_id); + } + clearSelectedTopics(); + break; + case read_id: + list = new ArrayList<>(selectedTopics); + for (int i = 0; i < list.size(); ++i) { + TLRPC.TL_forumTopic topic = topicsController.findTopic(chatId, list.get(i)); + if (topic != null) { + getMessagesController().markMentionsAsRead(-chatId, topic.id); + getMessagesController().markDialogAsRead(-chatId, topic.top_message, 0, topic.topMessage.date, false, topic.id, 0, true, 0); + getMessagesStorage().updateRepliesMaxReadId(chatId, topic.id, topic.top_message, 0, true); + } + } + clearSelectedTopics(); + break; + } + super.onItemClick(id); + } + }); + + + actionBar.setOnClickListener(v -> { + Bundle args = new Bundle(); + args.putLong("chat_id", chatId); + ProfileActivity fragment = new ProfileActivity(args, avatarContainer.getSharedMediaPreloader()); +// fragment.setChatInfo(parentFragment.getCurrentChatInfo()); +// fragment.setPlayProfileAnimation(byAvatar ? 2 : 1); + presentFragment(fragment); + }); + + ActionBarMenu menu = actionBar.createMenu(); + + searchItem = menu.addItem(0, R.drawable.ic_ab_search).setIsSearchField(true).setActionBarMenuItemSearchListener(new ActionBarMenuItem.ActionBarMenuItemSearchListener() { + @Override + public void onSearchExpand() { + animateToSearchView(true); + searchContainer.setSearchString(""); + searchContainer.setAlpha(0); + searchContainer.emptyView.showProgress(true, false); + } + + + @Override + public void onSearchCollapse() { + animateToSearchView(false); + } + + @Override + public void onTextChanged(EditText editText) { + String text = editText.getText().toString(); + searchContainer.setSearchString(text); + } + + @Override + public void onSearchFilterCleared(FiltersView.MediaFilterData filterData) { + + } + }); + searchItem.setSearchPaddingStart(56); + searchItem.setSearchFieldHint(LocaleController.getString("Search", R.string.Search)); + other = menu.addItem(0, R.drawable.ic_ab_other, themeDelegate); + other.addSubItem(toggle_id, R.drawable.msg_discussion, LocaleController.getString("TopicViewAsMessages", R.string.TopicViewAsMessages)); + addMemberSubMenu = other.addSubItem(add_member_id, R.drawable.msg_addcontact, LocaleController.getString("AddMember", R.string.AddMember)); + createTopicSubmenu = other.addSubItem(create_topic_id, R.drawable.msg_topic_create, LocaleController.getString("CreateTopic", R.string.CreateTopic)); + deleteChatSubmenu = other.addSubItem(delete_chat_id, R.drawable.msg_leave, LocaleController.getString("LeaveMegaMenu", R.string.LeaveMegaMenu), themeDelegate); + + avatarContainer = new ChatAvatarContainer(context, this, false); + avatarContainer.getAvatarImageView().setRoundRadius(AndroidUtilities.dp(16)); + avatarContainer.setOccupyStatusBar(!AndroidUtilities.isTablet()); + actionBar.addView(avatarContainer, 0, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, !inPreviewMode ? 56 : 0, 0, 86, 0)); + + recyclerListView = new RecyclerListView(context) { + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + checkForLoadMore(); + } + }; + DefaultItemAnimator defaultItemAnimator = new DefaultItemAnimator(); + defaultItemAnimator.setSupportsChangeAnimations(false); + defaultItemAnimator.setDelayAnimations(false); + recyclerListView.setItemAnimator(itemAnimator = defaultItemAnimator); + recyclerListView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + checkForLoadMore(); + } + }); + recyclerListView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); + recyclerListView.setItemsEnterAnimator(itemsEnterAnimator = new RecyclerItemsEnterAnimator(recyclerListView, true)); + recyclerListView.setOnItemClickListener((view, position) -> { + if (opnendForSelect) { + TLRPC.TL_forumTopic topic = forumTopics.get(position); + if (onTopicSelectedListener != null) { + onTopicSelectedListener.onTopicSelected(topic); + } + if (dialogsActivity != null) { + dialogsActivity.didSelectResult(-chatId, topic.id, true, false); + } + removeFragmentOnTransitionEnd = true; + return; + } + if (selectedTopics.size() > 0) { + toggleSelection(view); + return; + } + TLRPC.TL_forumTopic topic = forumTopics.get(position); + ForumUtilities.openTopic(TopicsFragment.this, chatId, topic, 0); + }); + recyclerListView.setOnItemLongClickListener((view, position, x, y) -> { + if (opnendForSelect) { + return false; + } + if (!actionBar.isActionModeShowed() && !AndroidUtilities.isTablet() && view instanceof TopicDialogCell) { + TopicDialogCell cell = (TopicDialogCell) view; + if (cell.isPointInsideAvatar(x, y)) { + return showChatPreview(cell); + } + } + toggleSelection(view); + view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + return true; + }); + recyclerListView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + contentView.invalidateBlur(); + } + }); + recyclerListView.setLayoutManager(layoutManager = new LinearLayoutManager(context)); + recyclerListView.setAdapter(adapter); + recyclerListView.setClipToPadding(false); + recyclerListView.addOnScrollListener(new RecyclerView.OnScrollListener() { + + int prevPosition; + int prevTop; + + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + int firstVisibleItem = layoutManager.findFirstVisibleItemPosition(); + if (firstVisibleItem != RecyclerView.NO_POSITION) { + RecyclerView.ViewHolder holder = recyclerView.findViewHolderForAdapterPosition(firstVisibleItem); + + int firstViewTop = 0; + if (holder != null) { + firstViewTop = holder.itemView.getTop(); + } + boolean goingDown; + boolean changed = true; + if (prevPosition == firstVisibleItem) { + final int topDelta = prevTop - firstViewTop; + goingDown = firstViewTop < prevTop; + changed = Math.abs(topDelta) > 1; + } else { + goingDown = firstVisibleItem > prevPosition; + } + + hideFloatingButton(goingDown || !canShowCreateTopic, true); + } + } + }); + + contentView.addView(recyclerListView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + ((ViewGroup.MarginLayoutParams) recyclerListView.getLayoutParams()).topMargin = -AndroidUtilities.dp(100); + floatingButtonContainer = new FrameLayout(getContext()); + floatingButtonContainer.setVisibility(View.VISIBLE); + contentView.addView(floatingButtonContainer, LayoutHelper.createFrame((Build.VERSION.SDK_INT >= 21 ? 56 : 60), (Build.VERSION.SDK_INT >= 21 ? 56 : 60), (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 14)); + floatingButtonContainer.setOnClickListener(v -> { + presentFragment(TopicCreateFragment.create(chatId, 0)); + }); + + Drawable drawable = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(56), Theme.getColor(Theme.key_chats_actionBackground), Theme.getColor(Theme.key_chats_actionPressedBackground)); + if (Build.VERSION.SDK_INT < 21) { + Drawable shadowDrawable = ContextCompat.getDrawable(getParentActivity(), R.drawable.floating_shadow).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(0xff000000, PorterDuff.Mode.MULTIPLY)); + CombinedDrawable combinedDrawable = new CombinedDrawable(shadowDrawable, drawable, 0, 0); + combinedDrawable.setIconSize(AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + drawable = combinedDrawable; + } else { + StateListAnimator animator = new StateListAnimator(); + animator.addState(new int[]{android.R.attr.state_pressed}, ObjectAnimator.ofFloat(floatingButtonContainer, View.TRANSLATION_Z, AndroidUtilities.dp(2), AndroidUtilities.dp(4)).setDuration(200)); + animator.addState(new int[]{}, ObjectAnimator.ofFloat(floatingButtonContainer, View.TRANSLATION_Z, AndroidUtilities.dp(4), AndroidUtilities.dp(2)).setDuration(200)); + floatingButtonContainer.setStateListAnimator(animator); + floatingButtonContainer.setOutlineProvider(new ViewOutlineProvider() { + @SuppressLint("NewApi") + @Override + public void getOutline(View view, Outline outline) { + outline.setOval(0, 0, AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + } + }); + } + floatingButtonContainer.setBackground(drawable); + RLottieImageView floatingButton = new RLottieImageView(context); + floatingButton.setImageResource(R.drawable.ic_chatlist_add_2); + floatingButtonContainer.setContentDescription(LocaleController.getString("CreateTopic", R.string.CreateTopic)); + + floatingButtonContainer.addView(floatingButton, LayoutHelper.createFrame(24, 24, Gravity.CENTER)); + + + FlickerLoadingView flickerLoadingView = new FlickerLoadingView(context); + flickerLoadingView.setViewType(FlickerLoadingView.TOPIC_CELL_TYPE); + flickerLoadingView.setVisibility(View.GONE); + flickerLoadingView.showDate(true); + + EmptyViewContainer emptyViewContainer = new EmptyViewContainer(context); + emptyViewContainer.textView.setAlpha(0); + + topicsEmptyView = new StickerEmptyView(context, flickerLoadingView, StickerEmptyView.STICKER_TYPE_NO_CONTACTS) { + boolean showProgressInternal; + + @Override + public void showProgress(boolean show, boolean animated) { + super.showProgress(show, animated); + showProgressInternal = show; + if (animated) { + emptyViewContainer.textView.animate().alpha(show ? 0f : 1f).start(); + } else { + emptyViewContainer.textView.animate().cancel(); + emptyViewContainer.textView.setAlpha(show ? 0f : 1f); + } + } + }; + try { + topicsEmptyView.stickerView.getImageReceiver().setAutoRepeat(2); + } catch (Exception ignore) {} + topicsEmptyView.showProgress(loadingTopics, fragmentBeginToShow); + topicsEmptyView.title.setText(LocaleController.getString("NoTopics", R.string.NoTopics)); + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder("d"); + ColoredImageSpan coloredImageSpan = new ColoredImageSpan(R.drawable.ic_ab_other); + coloredImageSpan.setSize(AndroidUtilities.dp(16)); + spannableStringBuilder.setSpan(coloredImageSpan, 0, 1, 0); + topicsEmptyView.subtitle.setText( + AndroidUtilities.replaceCharSequence("%s", LocaleController.getString("NoTopicsDescription", R.string.NoTopicsDescription), spannableStringBuilder) + ); + + + emptyViewContainer.addView(flickerLoadingView); + emptyViewContainer.addView(topicsEmptyView); + contentView.addView(emptyViewContainer); + + recyclerListView.setEmptyView(emptyViewContainer); + + bottomOverlayContainer = new FrameLayout(context) { + @Override + protected void dispatchDraw(Canvas canvas) { + int bottom = Theme.chat_composeShadowDrawable.getIntrinsicHeight(); + Theme.chat_composeShadowDrawable.setBounds(0, 0, getMeasuredWidth(), bottom); + Theme.chat_composeShadowDrawable.draw(canvas); + super.dispatchDraw(canvas); + } + }; + bottomOverlayChatText = new UnreadCounterTextView(context); + bottomOverlayContainer.addView(bottomOverlayChatText); + contentView.addView(bottomOverlayContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 51, Gravity.BOTTOM)); + bottomOverlayChatText.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + joinToGroup(); + } + }); + + bottomOverlayProgress = new RadialProgressView(context, themeDelegate); + bottomOverlayProgress.setSize(AndroidUtilities.dp(22)); + bottomOverlayProgress.setProgressColor(getThemedColor(Theme.key_chat_fieldOverlayText)); + bottomOverlayProgress.setVisibility(View.INVISIBLE); + bottomOverlayContainer.addView(bottomOverlayProgress, LayoutHelper.createFrame(30, 30, Gravity.CENTER)); + + updateChatInfo(); + + bottomOverlayChatText.setBackground(Theme.createSelectorDrawable(ColorUtils.setAlphaComponent(getThemedColor(Theme.key_chat_fieldOverlayText), 26), Theme.RIPPLE_MASK_ALL)); + floatingButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chats_actionIcon), PorterDuff.Mode.MULTIPLY)); + bottomOverlayContainer.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + actionBar.setActionModeColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + actionBar.setBackgroundColor(Theme.getColor(Theme.key_actionBarDefault)); + + searchContainer = new SearchContainer(context); + searchContainer.setVisibility(View.GONE); + contentView.addView(searchContainer); + EditTextBoldCursor editText = searchItem.getSearchField(); + + searchContainer.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); +// editText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); +// editText.setHintTextColor(Theme.getColor(Theme.key_player_time)); +// editText.setCursorColor(Theme.getColor(Theme.key_chat_messagePanelCursor)); + + actionBar.setDrawBlurBackground(contentView); + + getMessagesStorage().loadChatInfo(chatId, true, null, true, false, 0); + + topView = new FrameLayout(context); + contentView.addView(topView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 200, Gravity.TOP)); + + TLRPC.Chat currentChat = getCurrentChat(); + if (currentChat != null) { + pendingRequestsDelegate = new ChatActivityMemberRequestsDelegate(this, currentChat, this::updateTopView); + pendingRequestsDelegate.setChatInfo(chatFull, false); + topView.addView(pendingRequestsDelegate.getView(), ViewGroup.LayoutParams.MATCH_PARENT, pendingRequestsDelegate.getViewHeight()); + } + + fragmentContextView = new FragmentContextView(context, this, false, themeDelegate) { + @Override + public void setTopPadding(float value) { + super.topPadding = value; + updateTopView(); + } + }; + topView.addView(fragmentContextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 38, Gravity.TOP | Gravity.LEFT)); + + FrameLayout.LayoutParams layoutParams = LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT); + if (inPreviewMode && Build.VERSION.SDK_INT >= 21) { + layoutParams.topMargin = AndroidUtilities.statusBarHeight; + } + contentView.addView(actionBar, layoutParams); + + checkForLoadMore(); + + blurredView = new View(context) { + @Override + public void setAlpha(float alpha) { + super.setAlpha(alpha); + if (fragmentView != null) { + fragmentView.invalidate(); + } + } + }; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + blurredView.setForeground(new ColorDrawable(ColorUtils.setAlphaComponent(getThemedColor(Theme.key_windowBackgroundWhite), 100))); + } + blurredView.setFocusable(false); + blurredView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + blurredView.setOnClickListener(e -> { + finishPreviewFragment(); + }); + blurredView.setFitsSystemWindows(true); + + bottomPannelVisible = true; + + updateChatInfo(); + + return fragmentView; + } + + public void switchToChat(boolean removeFragment) { + removeFragmentOnTransitionEnd = removeFragment; + + Bundle bundle = new Bundle(); + bundle.putLong("chat_id", chatId); + ChatActivity chatActivity = new ChatActivity(bundle); + chatActivity.setSwitchFromTopics(true); + presentFragment(chatActivity); + } + + private void updateTopView() { + float translation = 0; + if (fragmentContextView != null) { + translation += Math.max(0, fragmentContextView.getTopPadding()); + fragmentContextView.setTranslationY(translation); + } + View pendingRequestsView = pendingRequestsDelegate != null ? pendingRequestsDelegate.getView() : null; + if (pendingRequestsView != null) { + pendingRequestsView.setTranslationY(translation + pendingRequestsDelegate.getViewEnterOffset()); + translation += pendingRequestsDelegate.getViewEnterOffset() + pendingRequestsDelegate.getViewHeight(); + } + recyclerListView.setTranslationY(Math.max(0, translation)); + } + + + private void deleteTopics(HashSet selectedTopics, Runnable runnable) { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle(LocaleController.getPluralString("DeleteTopics", selectedTopics.size())); + ArrayList topicsToRemove = new ArrayList<>(selectedTopics); + if (selectedTopics.size() == 1) { + TLRPC.TL_forumTopic topic = topicsController.findTopic(chatId, topicsToRemove.get(0)); + builder.setMessage(LocaleController.formatString("DeleteSelectedTopic", R.string.DeleteSelectedTopic, topic.title)); + } else { + builder.setMessage(LocaleController.getString("DeleteSelectedTopics", R.string.DeleteSelectedTopics)); + } + builder.setPositiveButton(LocaleController.getString("Delete", R.string.Delete), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + excludeTopics = new HashSet<>(); + excludeTopics.addAll(selectedTopics); + updateTopicsList(true, false); + BulletinFactory.of(TopicsFragment.this).createUndoBulletin(LocaleController.getPluralString("TopicsDeleted", selectedTopics.size()), () -> { + excludeTopics = null; + updateTopicsList(true, false); + }, () -> { + topicsController.deleteTopics(chatId, topicsToRemove); + runnable.run(); + }).show(); + clearSelectedTopics(); + dialog.dismiss(); + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + AlertDialog alertDialog = builder.create(); + alertDialog.show(); + TextView button = (TextView) alertDialog.getButton(DialogInterface.BUTTON_POSITIVE); + if (button != null) { + button.setTextColor(Theme.getColor(Theme.key_dialogTextRed2)); + } + } + + private boolean showChatPreview(TopicDialogCell cell) { + cell.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + final ActionBarPopupWindow.ActionBarPopupWindowLayout[] previewMenu = new ActionBarPopupWindow.ActionBarPopupWindowLayout[1]; + int flags = ActionBarPopupWindow.ActionBarPopupWindowLayout.FLAG_USE_SWIPEBACK; + previewMenu[0] = new ActionBarPopupWindow.ActionBarPopupWindowLayout(getParentActivity(), R.drawable.popup_fixed_alert, getResourceProvider(), flags); + + TLRPC.TL_forumTopic topic = cell.forumTopic; + ChatNotificationsPopupWrapper chatNotificationsPopupWrapper = new ChatNotificationsPopupWrapper(getContext(), currentAccount, previewMenu[0].getSwipeBack(), false, false, new ChatNotificationsPopupWrapper.Callback() { + @Override + public void dismiss() { + finishPreviewFragment(); + } + + @Override + public void toggleSound() { + SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + boolean enabled = !preferences.getBoolean("sound_enabled_" + NotificationsController.getSharedPrefKey(-chatId, topic.id), true); + preferences.edit().putBoolean("sound_enabled_" + NotificationsController.getSharedPrefKey(-chatId, topic.id), enabled).apply(); + finishPreviewFragment(); + if (BulletinFactory.canShowBulletin(TopicsFragment.this)) { + BulletinFactory.createSoundEnabledBulletin(TopicsFragment.this, enabled ? NotificationsController.SETTING_SOUND_ON : NotificationsController.SETTING_SOUND_OFF, getResourceProvider()).show(); + } + + } + + @Override + public void muteFor(int timeInSeconds) { + finishPreviewFragment(); + if (timeInSeconds == 0) { + if (getMessagesController().isDialogMuted(-chatId, topic.id)) { + getNotificationsController().muteDialog(-chatId, topic.id, false); + } + if (BulletinFactory.canShowBulletin(TopicsFragment.this)) { + BulletinFactory.createMuteBulletin(TopicsFragment.this, NotificationsController.SETTING_MUTE_UNMUTE, timeInSeconds, getResourceProvider()).show(); + } + } else { + getNotificationsController().muteUntil(-chatId, topic.id, timeInSeconds); + if (BulletinFactory.canShowBulletin(TopicsFragment.this)) { + BulletinFactory.createMuteBulletin(TopicsFragment.this, NotificationsController.SETTING_MUTE_CUSTOM, timeInSeconds, getResourceProvider()).show(); + } + } + + } + + @Override + public void showCustomize() { + finishPreviewFragment(); + AndroidUtilities.runOnUIThread(() -> { + Bundle args = new Bundle(); + args.putLong("dialog_id", -chatId); + args.putInt("topic_id", topic.id); + presentFragment(new ProfileNotificationsActivity(args, themeDelegate)); + }, 500); + } + + @Override + public void toggleMute() { + finishPreviewFragment(); + boolean mute = !getMessagesController().isDialogMuted(-chatId, topic.id); + getNotificationsController().muteDialog(-chatId, topic.id, mute); + + if (BulletinFactory.canShowBulletin(TopicsFragment.this)) { + BulletinFactory.createMuteBulletin(TopicsFragment.this, mute ? NotificationsController.SETTING_MUTE_FOREVER : NotificationsController.SETTING_MUTE_UNMUTE, mute ? Integer.MAX_VALUE : 0, getResourceProvider()).show(); + } + } + }, getResourceProvider()); + + int muteForegroundIndex = previewMenu[0].addViewToSwipeBack(chatNotificationsPopupWrapper.windowLayout); + chatNotificationsPopupWrapper.type = ChatNotificationsPopupWrapper.TYPE_PREVIEW_MENU; + chatNotificationsPopupWrapper.update(-chatId, topic.id, null); + + if (ChatObject.canManageTopics(getCurrentChat())) { + ActionBarMenuSubItem pinItem = new ActionBarMenuSubItem(getParentActivity(), true, false); + if (topic.pinned) { + pinItem.setTextAndIcon(LocaleController.getString("DialogUnpin", R.string.DialogUnpin), R.drawable.msg_unpin); + } else { + pinItem.setTextAndIcon(LocaleController.getString("DialogPin", R.string.DialogPin), R.drawable.msg_pin); + } + pinItem.setMinimumWidth(160); + pinItem.setOnClickListener(e -> { + scrollToTop = true; + updateAnimated = true; + topicsController.pinTopic(chatId, topic.id, !topic.pinned); + finishPreviewFragment(); + }); + + previewMenu[0].addView(pinItem); + } + + ActionBarMenuSubItem muteItem = new ActionBarMenuSubItem(getParentActivity(), false, false); + if (getMessagesController().isDialogMuted(-chatId, topic.id)) { + muteItem.setTextAndIcon(LocaleController.getString("Unmute", R.string.Unmute), R.drawable.msg_mute); + } else { + muteItem.setTextAndIcon(LocaleController.getString("Mute", R.string.Mute), R.drawable.msg_unmute); + } + muteItem.setMinimumWidth(160); + muteItem.setOnClickListener(e -> { + if (getMessagesController().isDialogMuted(-chatId, topic.id)) { + getNotificationsController().muteDialog(-chatId, topic.id, false); + finishPreviewFragment(); + if (BulletinFactory.canShowBulletin(TopicsFragment.this)) { + BulletinFactory.createMuteBulletin(TopicsFragment.this, NotificationsController.SETTING_MUTE_UNMUTE, 0, getResourceProvider()).show(); + } + } else { + previewMenu[0].getSwipeBack().openForeground(muteForegroundIndex); + } + }); + previewMenu[0].addView(muteItem); + + if (ChatObject.canManageTopic(currentAccount, getCurrentChat(), topic)) { + ActionBarMenuSubItem closeItem = new ActionBarMenuSubItem(getParentActivity(), false, false); + if (topic.closed) { + closeItem.setTextAndIcon(LocaleController.getString("RestartTopic", R.string.RestartTopic), R.drawable.msg_topic_restart); + } else { + closeItem.setTextAndIcon(LocaleController.getString("CloseTopic", R.string.CloseTopic), R.drawable.msg_topic_close); + } + closeItem.setMinimumWidth(160); + closeItem.setOnClickListener(e -> { + updateAnimated = true; + topicsController.toggleCloseTopic(chatId, topic.id, !topic.closed); + finishPreviewFragment(); + }); + previewMenu[0].addView(closeItem); + } + + if (ChatObject.canManageTopics(getCurrentChat())) { + ActionBarMenuSubItem deleteItem = new ActionBarMenuSubItem(getParentActivity(), false, true); + deleteItem.setTextAndIcon(LocaleController.getString("DeleteTopics_one", R.string.DeleteTopics_one), R.drawable.msg_delete); + deleteItem.setIconColor(getThemedColor(Theme.key_dialogRedIcon)); + deleteItem.setTextColor(getThemedColor(Theme.key_dialogTextRed)); + deleteItem.setMinimumWidth(160); + deleteItem.setOnClickListener(e -> { + HashSet hashSet = new HashSet(); + hashSet.add(topic.id); + deleteTopics(hashSet, () -> { + finishPreviewFragment(); + }); + }); + previewMenu[0].addView(deleteItem); + } + + prepareBlurBitmap(); + Bundle bundle = new Bundle(); + bundle.putLong("chat_id", chatId); + ChatActivity chatActivity = new ChatActivity(bundle); + ForumUtilities.applyTopic(chatActivity, MessagesStorage.TopicKey.of(-chatId, cell.forumTopic.id)); + presentFragmentAsPreviewWithMenu(chatActivity, previewMenu[0]); + return false; + } + + private void checkLoading() { + loadingTopics = topicsController.isLoading(chatId); + if (topicsEmptyView != null && forumTopics.size() == 0) { + topicsEmptyView.showProgress(loadingTopics, fragmentBeginToShow); + } + updateCreateTopicButton(true); + } + + ValueAnimator searchAnimator; + ValueAnimator searchAnimator2; + boolean animateSearchWithScale; + + private void animateToSearchView(boolean showSearch) { + searching = showSearch; + if (searchAnimator != null) { + searchAnimator.removeAllListeners(); + searchAnimator.cancel(); + } + searchAnimator = ValueAnimator.ofFloat(searchAnimationProgress, showSearch ? 1f : 0); + AndroidUtilities.updateViewVisibilityAnimated(searchContainer, false, 1f, true); + animateSearchWithScale = !showSearch && searchContainer.getVisibility() == View.VISIBLE && searchContainer.getAlpha() == 1f; + searchAnimator.addUpdateListener(animation -> updateSearchProgress((Float) animation.getAnimatedValue())); + if (!showSearch) { + other.setVisibility(View.VISIBLE); + } else { + searchContainer.setVisibility(View.VISIBLE); + AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid); + updateCreateTopicButton(false); + } + searchAnimator.addListener(new AnimatorListenerAdapter() { + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + updateSearchProgress(showSearch ? 1f : 0); + if (showSearch) { + other.setVisibility(View.GONE); + } else { + AndroidUtilities.setAdjustResizeToNothing(getParentActivity(), classGuid); + searchContainer.setVisibility(View.GONE); + updateCreateTopicButton(true); + } + } + }); + searchAnimator.setDuration(200); + searchAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT); + searchAnimator.start(); + + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.needCheckSystemBarColors, true); + } + + private void updateCreateTopicButton(boolean animated) { + if (createTopicSubmenu == null) { + return; + } + TLRPC.Chat chatLocal = getMessagesController().getChat(chatId); + canShowCreateTopic = !ChatObject.isNotInChat(getMessagesController().getChat(chatId)) && ChatObject.canCreateTopic(chatLocal) && !searching && !opnendForSelect && !loadingTopics; + createTopicSubmenu.setVisibility(canShowCreateTopic ? View.VISIBLE : View.GONE); + hideFloatingButton(!canShowCreateTopic, animated); + } + + private void updateSearchProgress(float value) { + searchAnimationProgress = value; + + avatarContainer.getTitleTextView().setAlpha(1f - value); + avatarContainer.getSubtitleTextView().setAlpha(1f - value); + + if (animateSearchWithScale) { + float scale = 0.98f + 0.02f * (1f - searchAnimationProgress); + recyclerListView.setScaleX(scale); + recyclerListView.setScaleY(scale); + } + } + + private ArrayList getSelectedTopics() { + ArrayList topics = new ArrayList<>(); + Iterator iterator = selectedTopics.iterator(); + while (iterator.hasNext()) { + int topicId = iterator.next(); + TLRPC.TL_forumTopic topic = topicsController.findTopic(chatId, topicId); + if (topic != null) { + topics.add(topic); + } + } + return topics; + } + + private void joinToGroup() { + getMessagesController().addUserToChat(chatId, getUserConfig().getCurrentUser(), 0, null, this, false, () -> { + joinRequested = false; + updateChatInfo(true); + }, e -> { + if (e != null && "INVITE_REQUEST_SENT".equals(e.text)) { + SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + preferences.edit().putLong("dialog_join_requested_time_" + -chatId, System.currentTimeMillis()).commit(); + JoinGroupAlert.showBulletin(getContext(), this, ChatObject.isChannelAndNotMegaGroup(getCurrentChat())); + updateChatInfo(true); + return false; + } + return true; + }); + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.closeSearchByActiveAction); + updateChatInfo(); + } + + private void clearSelectedTopics() { + selectedTopics.clear(); + actionBar.hideActionMode(); + AndroidUtilities.updateVisibleRows(recyclerListView); + } + + private void toggleSelection(View view) { + if (view instanceof TopicDialogCell) { + TopicDialogCell cell = (TopicDialogCell) view; + int id = cell.forumTopic.id; + if (!selectedTopics.remove(id)) { + selectedTopics.add(id); + } + cell.setChecked(selectedTopics.contains(id), true); + + TLRPC.Chat currentChat = getMessagesController().getChat(chatId); + + if (selectedTopics.size() > 0) { + chekActionMode(); + actionBar.showActionMode(true); + Iterator iterator = selectedTopics.iterator(); + int unreadCount = 0, readCount = 0; + int canPinCount = 0, canUnpinCount = 0; + int canMuteCount = 0, canUnmuteCount = 0; + while (iterator.hasNext()) { + int topicId = iterator.next(); + TLRPC.TL_forumTopic topic = topicsController.findTopic(chatId, topicId); + if (topic != null) { + if (topic.unread_count != 0) { + unreadCount++; + } else { + readCount++; + } + if (ChatObject.canManageTopic(currentAccount, currentChat, topic)) { + if (topic.pinned) { + canUnpinCount++; + } else { + canPinCount++; + } + } + } + if (getMessagesController().isDialogMuted(-chatId, topicId)) { + canUnmuteCount++; + } else { + canMuteCount++; + } + } + + if (unreadCount > 0) { + readItem.setVisibility(View.VISIBLE); + readItem.setTextAndIcon(LocaleController.getString("MarkAsRead", R.string.MarkAsRead), R.drawable.msg_markread); + } else { + readItem.setVisibility(View.GONE); + } + if (canUnmuteCount != 0) { + mute = false; + muteItem.setIcon(R.drawable.msg_unmute); + muteItem.setContentDescription(LocaleController.getString("ChatsUnmute", R.string.ChatsUnmute)); + } else { + mute = true; + muteItem.setIcon(R.drawable.msg_mute); + muteItem.setContentDescription(LocaleController.getString("ChatsMute", R.string.ChatsMute)); + } + + pinItem.setVisibility(canPinCount == 1 && canUnpinCount == 0 ? View.VISIBLE : View.GONE); + unpinItem.setVisibility(canUnpinCount == 1 && canPinCount == 0 ? View.VISIBLE : View.GONE); + } else { + actionBar.hideActionMode(); + } + selectedDialogsCountTextView.setNumber(selectedTopics.size(), true); + + int canPin = 0; + int canDeleteCount = 0; + int closedTopicsCount = 0; + int openTopicsCount = 0; + Iterator iterator = selectedTopics.iterator(); + while (iterator.hasNext()) { + int topicId = iterator.next(); + TLRPC.TL_forumTopic topic = topicsController.findTopic(chatId, topicId); + if (topic != null) { + if (ChatObject.canManageTopics(currentChat)) { + canDeleteCount++; + } + if (ChatObject.canManageTopic(currentAccount, currentChat, topic)) { + if (topic.closed) { + closedTopicsCount++; + } else { + openTopicsCount++; + } + } + } + } + closeTopic.setVisibility(closedTopicsCount == 0 && openTopicsCount > 0 ? View.VISIBLE : View.GONE); + closeTopic.setText(openTopicsCount > 1 ? LocaleController.getString("CloseTopics", R.string.CloseTopics) : LocaleController.getString("CloseTopic", R.string.CloseTopic)); + restartTopic.setVisibility(openTopicsCount == 0 && closedTopicsCount > 0 ? View.VISIBLE : View.GONE); + restartTopic.setText(closedTopicsCount > 1 ? LocaleController.getString("RestartTopics", R.string.RestartTopics) : LocaleController.getString("RestartTopic", R.string.RestartTopic)); + deleteItem.setVisibility(canDeleteCount == selectedTopics.size() ? View.VISIBLE : View.GONE); + + otherItem.checkHideMenuItem(); + } + } + + private void chekActionMode() { + if (actionBar.actionModeIsExist(null)) { + return; + } + final ActionBarMenu actionMode = actionBar.createActionMode(false, null); + + selectedDialogsCountTextView = new NumberTextView(actionMode.getContext()); + selectedDialogsCountTextView.setTextSize(18); + selectedDialogsCountTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + selectedDialogsCountTextView.setTextColor(Theme.getColor(Theme.key_actionBarActionModeDefaultIcon)); + actionMode.addView(selectedDialogsCountTextView, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, 1.0f, 72, 0, 0, 0)); + selectedDialogsCountTextView.setOnTouchListener((v, event) -> true); + + pinItem = actionMode.addItemWithWidth(pin_id, R.drawable.msg_pin, AndroidUtilities.dp(54)); + unpinItem = actionMode.addItemWithWidth(unpin_id, R.drawable.msg_unpin, AndroidUtilities.dp(54)); + muteItem = actionMode.addItemWithWidth(mute_id, R.drawable.msg_mute, AndroidUtilities.dp(54)); + deleteItem = actionMode.addItemWithWidth(delete_id, R.drawable.msg_delete, AndroidUtilities.dp(54), LocaleController.getString("Delete", R.string.Delete)); + + otherItem = actionMode.addItemWithWidth(0, R.drawable.ic_ab_other, AndroidUtilities.dp(54), LocaleController.getString("AccDescrMoreOptions", R.string.AccDescrMoreOptions)); + readItem = otherItem.addSubItem(read_id, R.drawable.msg_markread, LocaleController.getString("MarkAsRead", R.string.MarkAsRead)); + closeTopic = otherItem.addSubItem(close_topic_id, R.drawable.msg_topic_close, LocaleController.getString("CloseTopic", R.string.CloseTopic)); + restartTopic = otherItem.addSubItem(restart_topic_id, R.drawable.msg_topic_restart, LocaleController.getString("RestartTopic", R.string.RestartTopic)); + } + + private void updateChatInfo() { + updateChatInfo(false); + } + + private void updateChatInfo(boolean forceAnimate) { + if (fragmentView == null) { + return; + } + TLRPC.Chat chatLocal = getMessagesController().getChat(chatId); + + avatarContainer.setChatAvatar(chatLocal); + if (!opnendForSelect) { + if (chatLocal != null) { + avatarContainer.setTitle(chatLocal.title); + } + updateSubtitle(); + } else { + if (openedForForward) { + avatarContainer.setTitle(LocaleController.getString("ForwardTo", R.string.ForwardTo)); + } else { + avatarContainer.setTitle(LocaleController.getString("SelectTopic", R.string.SelectTopic)); + } + searchItem.setVisibility(View.GONE); + if (avatarContainer != null && avatarContainer.getLayoutParams() != null) { + ((ViewGroup.MarginLayoutParams) avatarContainer.getLayoutParams()).rightMargin = AndroidUtilities.dp(searchItem.getVisibility() == View.VISIBLE ? 86 : 40); + } + avatarContainer.updateSubtitle(); + avatarContainer.getSubtitleTextView().setVisibility(View.GONE); + } + boolean animated = fragmentBeginToShow || forceAnimate; + boolean bottomPannelVisibleLocal; + long requestedTime = MessagesController.getNotificationsSettings(currentAccount).getLong("dialog_join_requested_time_" + -chatId, -1); + if (chatLocal != null && ChatObject.isNotInChat(chatLocal) && (requestedTime > 0 && System.currentTimeMillis() - requestedTime < 1000 * 60 * 2)) { + bottomPannelVisibleLocal = true; + recyclerListView.setPadding(0, AndroidUtilities.dp(100), 0, AndroidUtilities.dp(51)); + + bottomOverlayChatText.setText(LocaleController.getString("ChannelJoinRequestSent", R.string.ChannelJoinRequestSent), animated); + bottomOverlayChatText.setEnabled(false); + AndroidUtilities.updateViewVisibilityAnimated(bottomOverlayProgress, false, 0.5f, animated); + AndroidUtilities.updateViewVisibilityAnimated(bottomOverlayChatText, true, 0.5f, animated); + } else if (chatLocal != null && !opnendForSelect && (ChatObject.isNotInChat(chatLocal) || getMessagesController().isJoiningChannel(chatLocal.id))) { + bottomPannelVisibleLocal = true; + + recyclerListView.setPadding(0, AndroidUtilities.dp(100), 0, AndroidUtilities.dp(51)); + boolean showProgress = false; + if (getMessagesController().isJoiningChannel(chatLocal.id)) { + showProgress = true; + } else { + if (chatLocal.join_request) { + bottomOverlayChatText.setText(LocaleController.getString("ChannelJoinRequest", R.string.ChannelJoinRequest)); + } else { + bottomOverlayChatText.setText(LocaleController.getString("ChannelJoin", R.string.ChannelJoin)); + } + bottomOverlayChatText.setClickable(true); + bottomOverlayChatText.setEnabled(true); + } + + AndroidUtilities.updateViewVisibilityAnimated(bottomOverlayProgress, showProgress, 0.5f, animated); + AndroidUtilities.updateViewVisibilityAnimated(bottomOverlayChatText, !showProgress, 0.5f, animated); + } else { + bottomPannelVisibleLocal = false; + recyclerListView.setPadding(0, AndroidUtilities.dp(100), 0, 0); + } + + if (bottomPannelVisible != bottomPannelVisibleLocal) { + bottomPannelVisible = bottomPannelVisibleLocal; + bottomOverlayContainer.animate().setListener(null).cancel(); + if (!animated) { + bottomOverlayContainer.setVisibility(bottomPannelVisibleLocal ? View.VISIBLE : View.GONE); + bottomOverlayContainer.setTranslationY(bottomPannelVisibleLocal ? 0 : AndroidUtilities.dp(53)); + } else { + bottomOverlayContainer.animate().translationY(bottomPannelVisibleLocal ? 0 : AndroidUtilities.dp(53)).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (!bottomPannelVisibleLocal) { + bottomOverlayContainer.setVisibility(View.GONE); + } + } + }); + } + } + + other.setVisibility(opnendForSelect ? View.GONE : View.VISIBLE); + addMemberSubMenu.setVisibility(ChatObject.canAddUsers(chatLocal) ? View.VISIBLE : View.GONE); + + deleteChatSubmenu.setVisibility(chatLocal != null && !chatLocal.creator && !ChatObject.isNotInChat(chatLocal) ? View.VISIBLE : View.GONE); + updateCreateTopicButton(true); + groupCall = getMessagesController().getGroupCall(chatId, true); + } + + private void updateSubtitle() { + TLRPC.ChatFull chatFull = getMessagesController().getChatFull(chatId); + if (this.chatFull != null && this.chatFull.participants != null) { + chatFull.participants = this.chatFull.participants; + } + this.chatFull = chatFull; + String newSubtitle; + if (chatFull != null) { + newSubtitle = LocaleController.formatPluralString("Members", chatFull.participants_count); + } else { + newSubtitle = LocaleController.getString("Loading", R.string.Loading).toLowerCase(); + } + + avatarContainer.setSubtitle(newSubtitle); + } + + @Override + protected ActionBar createActionBar(Context context) { + return super.createActionBar(context); + } + + private static HashSet settingsPreloaded = new HashSet<>(); + + @Override + public boolean onFragmentCreate() { + getMessagesController().loadFullChat(chatId, 0, true); + NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatInfoDidLoad); + NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.topicsDidLoaded); + NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.updateInterfaces); + NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.dialogsNeedReload); + NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.groupCallUpdated); + NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.notificationsSettingsUpdated); + NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatSwithcedToForum); + + updateTopicsList(false, false); + SelectAnimatedEmojiDialog.preload(currentAccount); + + TLRPC.Chat chatLocal = getMessagesController().getChat(chatId); + if (ChatObject.isChannel(chatLocal)) { + getMessagesController().startShortPoll(chatLocal, classGuid, false); + } + //TODO remove when server start send in get diff + if (!settingsPreloaded.contains(chatId)) { + settingsPreloaded.add(chatId); + TLRPC.TL_account_getNotifyExceptions exceptionsReq = new TLRPC.TL_account_getNotifyExceptions(); + exceptionsReq.peer = new TLRPC.TL_inputNotifyPeer(); + exceptionsReq.flags |= 1; + ((TLRPC.TL_inputNotifyPeer) exceptionsReq.peer).peer = getMessagesController().getInputPeer(-chatId); + getConnectionsManager().sendRequest(exceptionsReq, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + if (response instanceof TLRPC.TL_updates) { + TLRPC.TL_updates updates = (TLRPC.TL_updates) response; + getMessagesController().processUpdates(updates, false); + } + })); + } + return true; + } + + @Override + public void onFragmentDestroy() { + getNotificationCenter().onAnimationFinish(transitionAnimationIndex); + NotificationCenter.getGlobalInstance().onAnimationFinish(transitionAnimationGlobalIndex); + + NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.chatInfoDidLoad); + NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.topicsDidLoaded); + NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.updateInterfaces); + NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.dialogsNeedReload); + NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.groupCallUpdated); + NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.notificationsSettingsUpdated); + NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.chatSwithcedToForum); + + TLRPC.Chat chatLocal = getMessagesController().getChat(chatId); + if (ChatObject.isChannel(chatLocal)) { + getMessagesController().startShortPoll(chatLocal, classGuid, true); + } + super.onFragmentDestroy(); + } + + + private void updateTopicsList(boolean animated, boolean enalbeEnterAnimation) { + if (!animated && (updateAnimated || itemAnimator != null && (System.currentTimeMillis() - lastAnimatedDuration) < itemAnimator.getMoveDuration())) { + animated = true; + } + if (animated) { + lastAnimatedDuration = System.currentTimeMillis(); + } + updateAnimated = false; + ArrayList topics = topicsController.getTopics(chatId); + if (topics != null) { + int oldCount = forumTopics.size(); + forumTopics.clear(); + for (int i = 0; i < topics.size(); i++) { + if (excludeTopics != null && excludeTopics.contains(topics.get(i).id)) { + continue; + } + forumTopics.add(topics.get(i)); + } + if (forumTopics.size() == 1 && forumTopics.get(0).id == 1) { + forumTopics.clear(); + } + + for (int i = 0; i < forumTopics.size(); ++i) { + if (forumTopics.get(i).pinned) { + forumTopics.add(0, forumTopics.remove(i)); + break; + } + } + if (recyclerListView != null && recyclerListView.getItemAnimator() != (animated ? itemAnimator : null)) { + recyclerListView.setItemAnimator(animated ? itemAnimator : null); + } + + if (adapter != null) { + adapter.notifyDataSetChanged(true); + } + + int newCount = forumTopics.size(); + if (fragmentBeginToShow && enalbeEnterAnimation && newCount > oldCount) { + itemsEnterAnimator.showItemsAnimated(oldCount + 1); + } + + if (scrollToTop && layoutManager != null) { + layoutManager.scrollToPosition(0); + scrollToTop = false; + } + } + + checkLoading(); + } + + @Override + public void didReceivedNotification(int id, int account, Object... args) { + if (id == NotificationCenter.chatInfoDidLoad) { + TLRPC.ChatFull chatFull = (TLRPC.ChatFull) args[0]; + if (chatFull.participants != null && this.chatFull != null) { + this.chatFull.participants = chatFull.participants; + } + if (chatFull.id == chatId) { + updateChatInfo(); + if (pendingRequestsDelegate != null) { + pendingRequestsDelegate.setChatInfo(chatFull, true); + } + } + } else if (id == NotificationCenter.topicsDidLoaded) { + Long chatId = (Long) args[0]; + if (this.chatId == chatId) { + updateTopicsList(false, true); + if (args.length > 1 && (Boolean) args[1]) { + checkForLoadMore(); + } + } + } else if (id == NotificationCenter.updateInterfaces) { + int mask = (Integer) args[0]; + if (mask == MessagesController.UPDATE_MASK_CHAT) { + updateChatInfo(); + } + if ((mask & MessagesController.UPDATE_MASK_SELECT_DIALOG) > 0) { + updateTopicsList(false, false); + } + } else if (id == NotificationCenter.dialogsNeedReload) { + updateTopicsList(false, false); + } else if (id == NotificationCenter.groupCallUpdated) { + Long chatId = (Long) args[0]; + if (this.chatId == chatId) { + groupCall = getMessagesController().getGroupCall(chatId, false); + if (fragmentContextView != null) { + fragmentContextView.checkCall(true); + } + } + } else if (id == NotificationCenter.notificationsSettingsUpdated) { + updateTopicsList(false, false); + } else if (id == NotificationCenter.chatSwithcedToForum) { + + } + } + + private void checkForLoadMore() { + if (topicsController.endIsReached(chatId) || layoutManager == null) { + return; + } + int lastPosition = layoutManager.findLastVisibleItemPosition(); + if (forumTopics.isEmpty() || lastPosition >= adapter.getItemCount() - 5) { + topicsController.loadTopics(chatId); + } + checkLoading(); + } + + public void setExcludeTopics(HashSet exceptionsTopics) { + this.excludeTopics = exceptionsTopics; + } + + @Override + public ChatObject.Call getGroupCall() { + return groupCall != null && groupCall.call instanceof TLRPC.TL_groupCall ? groupCall : null; + } + + @Override + public TLRPC.Chat getCurrentChat() { + return getMessagesController().getChat(chatId); + } + + @Override + public long getDialogId() { + return -chatId; + } + + public void setForwardFromDialogFragment(DialogsActivity dialogsActivity) { + this.dialogsActivity = dialogsActivity; + } + + private class Adapter extends RecyclerListView.SelectionAdapter { + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + TopicDialogCell dialogCell = new TopicDialogCell(null, parent.getContext(), true, false); + return new RecyclerListView.Holder(dialogCell); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + TLRPC.TL_forumTopic topic = forumTopics.get(position); + TLRPC.TL_forumTopic nextTopic = null; + if (position + 1 < forumTopics.size()) { + nextTopic = forumTopics.get(position + 1); + } + TopicDialogCell dialogCell = (TopicDialogCell) holder.itemView; + + TLRPC.Message tlMessage = topic.topMessage; + int oldId = dialogCell.forumTopic == null ? 0 : dialogCell.forumTopic.id; + int newId = topic.id; + boolean animated = oldId == newId && dialogCell.position == position; + if (tlMessage != null) { + MessageObject messageObject = new MessageObject(currentAccount, tlMessage, false, false); + + dialogCell.setForumTopic(topic, -chatId, messageObject, animated); + dialogCell.drawDivider = position != forumTopics.size() - 1; + dialogCell.fullSeparator = topic.pinned && (nextTopic == null || !nextTopic.pinned); + dialogCell.setPinForced(topic.pinned); + dialogCell.position = position; + } + + dialogCell.setTopicIcon(topic); + + dialogCell.setChecked(selectedTopics.contains(newId), animated); + } + + @Override + public int getItemCount() { + return forumTopics.size(); + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; + } + + private ArrayList hashes = new ArrayList<>(); + + @Override + public void notifyDataSetChanged() { + hashes.clear(); + for (int i = 0; i < forumTopics.size(); ++i) { + hashes.add(forumTopics.get(i).id); + } + super.notifyDataSetChanged(); + } + + public void notifyDataSetChanged(boolean diff) { + final ArrayList oldHashes = new ArrayList<>(hashes); + hashes.clear(); + for (int i = 0; i < forumTopics.size(); ++i) { + hashes.add(forumTopics.get(i).id); + } + + if (diff) { + DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return oldHashes.size(); + } + + @Override + public int getNewListSize() { + return hashes.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + return Objects.equals(hashes.get(newItemPosition), oldHashes.get(oldItemPosition)); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + return false; + } + }).dispatchUpdatesTo(this); + } else { + super.notifyDataSetChanged(); + } + } + } + + private class TopicDialogCell extends DialogCell { + + public boolean drawDivider; + public int position = -1; + + public TopicDialogCell(DialogsActivity fragment, Context context, boolean needCheck, boolean forceThreeLines) { + super(fragment, context, needCheck, forceThreeLines); + drawAvatar = false; + messagePaddingStart = 50; + chekBoxPaddingTop = 24; + heightDefault = 64; + heightThreeLines = 76; + } + + private AnimatedEmojiDrawable animatedEmojiDrawable; + private Drawable forumIcon; + boolean attached; + private boolean closed; + + @Override + protected void onDraw(Canvas canvas) { + canvas.save(); + canvas.drawColor(getThemedColor(Theme.key_windowBackgroundWhite)); + canvas.translate(0, translateY = -AndroidUtilities.dp(4)); + super.onDraw(canvas); + canvas.restore(); + if (drawDivider) { + int left = fullSeparator ? 0 : AndroidUtilities.dp(messagePaddingStart); + if (LocaleController.isRTL) { + canvas.drawLine(0, getMeasuredHeight() - 1, getMeasuredWidth() - left, getMeasuredHeight() - 1, Theme.dividerPaint); + } else { + canvas.drawLine(left, getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, Theme.dividerPaint); + } + } + if (animatedEmojiDrawable != null || forumIcon != null) { + int padding = AndroidUtilities.dp(10); + int paddingTop = AndroidUtilities.dp(10); + int size = AndroidUtilities.dp(28); + if (animatedEmojiDrawable != null) { + if (LocaleController.isRTL) { + animatedEmojiDrawable.setBounds(getWidth() - padding - size, paddingTop, getWidth() - padding, paddingTop + size); + } else { + animatedEmojiDrawable.setBounds(padding, paddingTop, padding + size, paddingTop + size); + } + animatedEmojiDrawable.draw(canvas); + } else { + if (LocaleController.isRTL) { + forumIcon.setBounds(getWidth() - padding - size, paddingTop, getWidth() - padding, paddingTop + size); + } else { + forumIcon.setBounds(padding, paddingTop, padding + size, paddingTop + size); + } + forumIcon.draw(canvas); + } + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + attached = true; + if (animatedEmojiDrawable != null) { + animatedEmojiDrawable.addView(this); + } + + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + attached = false; + if (animatedEmojiDrawable != null) { + animatedEmojiDrawable.removeView(this); + } + + } + + public void setAnimatedEmojiDrawable(AnimatedEmojiDrawable animatedEmojiDrawable) { + if (this.animatedEmojiDrawable == animatedEmojiDrawable) { + return; + } + if (this.animatedEmojiDrawable != null && attached) { + this.animatedEmojiDrawable.removeView(this); + } + this.animatedEmojiDrawable = animatedEmojiDrawable; + if (animatedEmojiDrawable != null && attached) { + animatedEmojiDrawable.addView(this); + } + } + + public void setForumIcon(Drawable drawable) { + forumIcon = drawable; + } + + public void setTopicIcon(TLRPC.TL_forumTopic topic) { + closed = topic != null && topic.closed; + if (topic != null && topic.icon_emoji_id != 0) { + setForumIcon(null); + setAnimatedEmojiDrawable(new AnimatedEmojiDrawable(AnimatedEmojiDrawable.CACHE_TYPE_FORUM_TOPIC, currentAccount, topic.icon_emoji_id)); + } else { + setAnimatedEmojiDrawable(null); + setForumIcon(ForumUtilities.createTopicDrawable(topic)); + } + buildLayout(); + } + + @Override + protected boolean drawLock2() { + return closed; + } + } + + private void hideFloatingButton(boolean hide, boolean animated) { + if (floatingHidden == hide) { + return; + } + floatingHidden = hide; + boolean animatedLocal = fragmentBeginToShow && animated; + if (animatedLocal) { + AnimatorSet animatorSet = new AnimatorSet(); + ValueAnimator valueAnimator = ValueAnimator.ofFloat(floatingButtonHideProgress, floatingHidden ? 1f : 0f); + valueAnimator.addUpdateListener(animation -> { + floatingButtonHideProgress = (float) animation.getAnimatedValue(); + floatingButtonTranslation = AndroidUtilities.dp(100) * floatingButtonHideProgress; + updateFloatingButtonOffset(); + }); + animatorSet.playTogether(valueAnimator); + animatorSet.setDuration(300); + animatorSet.setInterpolator(floatingInterpolator); + animatorSet.start(); + } else { + floatingButtonHideProgress = floatingHidden ? 1f : 0f; + floatingButtonTranslation = AndroidUtilities.dp(100) * floatingButtonHideProgress; + updateFloatingButtonOffset(); + } + floatingButtonContainer.setClickable(!hide); + } + + private void updateFloatingButtonOffset() { + floatingButtonContainer.setTranslationY(floatingButtonTranslation); + } + + @Override + public void onBecomeFullyHidden() { + if (actionBar != null) { + actionBar.closeSearchField(); + } + } + + private class EmptyViewContainer extends FrameLayout { + + TextView textView; + + public EmptyViewContainer(Context context) { + super(context); + textView = new TextView(context); + SpannableStringBuilder spannableStringBuilder; + if (LocaleController.isRTL) { + spannableStringBuilder = new SpannableStringBuilder(" "); + spannableStringBuilder.setSpan(new ColoredImageSpan(R.drawable.attach_arrow_left), 0, 1, 0); + spannableStringBuilder.append(LocaleController.getString("TapToCreateTopicHint", R.string.TapToCreateTopicHint)); + } else { + spannableStringBuilder = new SpannableStringBuilder(LocaleController.getString("TapToCreateTopicHint", R.string.TapToCreateTopicHint)); + spannableStringBuilder.append(" "); + spannableStringBuilder.setSpan(new ColoredImageSpan(R.drawable.attach_arrow_right), spannableStringBuilder.length() - 1, spannableStringBuilder.length(), 0); + } + textView.setText(spannableStringBuilder); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setLayerType(LAYER_TYPE_HARDWARE, null); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText, getResourceProvider())); + addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 86, 0, 86, 32)); + } + + float progress; + boolean increment; + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + if (increment) { + progress += 16 / 1200f; + if (progress > 1) { + increment = false; + progress = 1; + } + } else { + progress -= 16 / 1200f; + if (progress < 0) { + increment = true; + progress = 0; + } + } + textView.setTranslationX(CubicBezierInterpolator.DEFAULT.getInterpolation(progress) * AndroidUtilities.dp(8) * (LocaleController.isRTL ? -1 : 1)); + invalidate(); + } + } + + @Override + public boolean isLightStatusBar() { + if (searchingNotEmpty) { + int color = Theme.getColor(Theme.key_windowBackgroundWhite); + return ColorUtils.calculateLuminance(color) > 0.7f; + } else { + return super.isLightStatusBar(); + } + } + + private class SearchContainer extends FrameLayout { + + RecyclerListView recyclerView; + LinearLayoutManager layoutManager; + SearchAdapter searchAdapter; + Runnable searchRunnable; + String searchString = "empty"; + + ArrayList searchResultTopics = new ArrayList<>(); + ArrayList searchResultMessages = new ArrayList<>(); + + int topicsHeaderRow; + int topicsStartRow; + int topicsEndRow; + + int messagesHeaderRow; + int messagesStartRow; + int messagesEndRow; + + int rowCount; + + boolean isLoading; + boolean canLoadMore; + + FlickerLoadingView flickerLoadingView; + StickerEmptyView emptyView; + RecyclerItemsEnterAnimator itemsEnterAnimator; + boolean messagesIsLoading; + + public SearchContainer(@NonNull Context context) { + super(context); + recyclerView = new RecyclerListView(context); + recyclerView.setAdapter(searchAdapter = new SearchAdapter()); + recyclerView.setLayoutManager(layoutManager = new LinearLayoutManager(context)); + recyclerView.setOnItemClickListener((view, position) -> { + if (view instanceof TopicSearchCell) { + TopicSearchCell cell = (TopicSearchCell) view; + ForumUtilities.openTopic(TopicsFragment.this, chatId, cell.getTopic(), 0); + } else if (view instanceof TopicDialogCell) { + TopicDialogCell cell = (TopicDialogCell) view; + ForumUtilities.openTopic(TopicsFragment.this, chatId, cell.forumTopic, cell.getMessageId()); + } + }); + recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + if (canLoadMore) { + int lastPosition = layoutManager.findLastVisibleItemPosition(); + if (lastPosition + 5 >= rowCount) { + loadMessages(searchString); + } + } + + if (searching && (dx != 0 || dy != 0)) { + AndroidUtilities.hideKeyboard(searchItem.getSearchField()); + } + } + }); + + flickerLoadingView = new FlickerLoadingView(context); + flickerLoadingView.setViewType(FlickerLoadingView.DIALOG_CELL_TYPE); + flickerLoadingView.showDate(false); + flickerLoadingView.setUseHeaderOffset(true); + + emptyView = new StickerEmptyView(context, flickerLoadingView, StickerEmptyView.STICKER_TYPE_SEARCH); + emptyView.title.setText(LocaleController.getString("NoResult", R.string.NoResult)); + emptyView.subtitle.setVisibility(View.GONE); + emptyView.setVisibility(View.GONE); + emptyView.addView(flickerLoadingView, 0); + emptyView.setAnimateLayoutChange(true); + + recyclerView.setEmptyView(emptyView); + recyclerView.setAnimateEmptyView(true, RecyclerListView.EMPTY_VIEW_ANIMATION_TYPE_ALPHA); + addView(emptyView); + addView(recyclerView); + updateRows(); + + itemsEnterAnimator = new RecyclerItemsEnterAnimator(recyclerView, true); + recyclerView.setItemsEnterAnimator(itemsEnterAnimator); + } + + public void setSearchString(String searchString) { + if (this.searchString.equals(searchString)) { + return; + } + this.searchString = searchString; + if (searchRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(searchRunnable); + searchRunnable = null; + } + + AndroidUtilities.updateViewVisibilityAnimated(searchContainer, !TextUtils.isEmpty(searchString), 1f, true); + + messagesIsLoading = false; + canLoadMore = false; + searchResultTopics.clear(); + searchResultMessages.clear(); + updateRows(); + if (TextUtils.isEmpty(searchString)) { + isLoading = false; + // emptyView.showProgress(true, true); + return; + } else { + updateRows(); + } + + isLoading = true; + emptyView.showProgress(isLoading, true); + + + searchRunnable = () -> { + String searchTrimmed = searchString.trim().toLowerCase(); + ArrayList topics = new ArrayList<>(); + for (int i = 0; i < forumTopics.size(); i++) { + if (forumTopics.get(i).title.toLowerCase().contains(searchTrimmed)) { + topics.add(forumTopics.get(i)); + forumTopics.get(i).searchQuery = searchTrimmed; + } + } + + searchResultTopics.clear(); + searchResultTopics.addAll(topics); + updateRows(); + + if (!searchResultTopics.isEmpty()) { + isLoading = false; + // emptyView.showProgress(isLoading, true); + itemsEnterAnimator.showItemsAnimated(0); + } + + loadMessages(searchString); + }; + AndroidUtilities.runOnUIThread(searchRunnable, 200); + } + + private void loadMessages(String searchString) { + if (messagesIsLoading) { + return; + } + TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); + req.peer = getMessagesController().getInputPeer(-chatId); + req.filter = new TLRPC.TL_inputMessagesFilterEmpty(); + req.limit = 20; + req.q = searchString; + if (!searchResultMessages.isEmpty()) { + req.offset_id = searchResultMessages.get(searchResultMessages.size() - 1).getId(); + } + messagesIsLoading = true; +// if (query.equals(lastMessagesSearchString) && !searchResultMessages.isEmpty()) { +// MessageObject lastMessage = searchResultMessages.get(searchResultMessages.size() - 1); +// req.offset_id = lastMessage.getId(); +// req.offset_rate = nextSearchRate; +// long id = MessageObject.getPeerId(lastMessage.messageOwner.peer_id); +// req.offset_peer = MessagesController.getInstance(currentAccount).getInputPeer(id); +// } else { +// req.offset_rate = 0; +// req.offset_id = 0; +// req.offset_peer = new TLRPC.TL_inputPeerEmpty(); +// } + ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + if (searchString.equals(this.searchString)) { + int oldRowCount = rowCount; + messagesIsLoading = false; + isLoading = false; + if (response instanceof TLRPC.messages_Messages) { + TLRPC.messages_Messages messages = (TLRPC.messages_Messages) response; + + for (int i = 0; i < messages.messages.size(); i++) { + TLRPC.Message message = messages.messages.get(i); + MessageObject messageObject = new MessageObject(currentAccount, message, false, false); + messageObject.setQuery(searchString); + searchResultMessages.add(messageObject); + } + updateRows(); + canLoadMore = searchResultMessages.size() < messages.count && !messages.messages.isEmpty(); + } else { + canLoadMore = false; + } + + if (rowCount == 0) { + emptyView.showProgress(isLoading, true); + } + itemsEnterAnimator.showItemsAnimated(oldRowCount); + } + })); + } + + private void updateRows() { + topicsHeaderRow = -1; + topicsStartRow = -1; + topicsEndRow = -1; + messagesHeaderRow = -1; + messagesStartRow = -1; + messagesEndRow = -1; + + rowCount = 0; + + if (!searchResultTopics.isEmpty()) { + topicsHeaderRow = rowCount++; + topicsStartRow = rowCount; + rowCount += searchResultTopics.size(); + topicsEndRow = rowCount; + } + + if (!searchResultMessages.isEmpty()) { + messagesHeaderRow = rowCount++; + messagesStartRow = rowCount; + rowCount += searchResultMessages.size(); + messagesEndRow = rowCount; + } + + searchAdapter.notifyDataSetChanged(); + } + + private class SearchAdapter extends RecyclerListView.SelectionAdapter { + + private final static int VIEW_TYPE_HEADER = 1; + private final static int VIEW_TYPE_TOPIC = 2; + private final static int VIEW_TYPE_MESSAGE = 3; + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case VIEW_TYPE_HEADER: + view = new GraySectionCell(parent.getContext()); + break; + case VIEW_TYPE_TOPIC: + view = new TopicSearchCell(parent.getContext()); + break; + case VIEW_TYPE_MESSAGE: + view = new TopicDialogCell(null, parent.getContext(), false, true); + break; + default: + throw new RuntimeException("unsupported view type"); + } + + + view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + if (getItemViewType(position) == VIEW_TYPE_HEADER) { + GraySectionCell headerCell = (GraySectionCell) holder.itemView; + if (position == topicsHeaderRow) { + headerCell.setText(LocaleController.getString("Topics", R.string.Topics)); + } + if (position == messagesHeaderRow) { + headerCell.setText(LocaleController.getString("SearchMessages", R.string.SearchMessages)); + } + } + if (getItemViewType(position) == VIEW_TYPE_TOPIC) { + TLRPC.TL_forumTopic topic = searchResultTopics.get(position - topicsStartRow); + TopicSearchCell topicSearchCell = (TopicSearchCell) holder.itemView; + topicSearchCell.setTopic(topic); + topicSearchCell.drawDivider = position != topicsEndRow - 1; + } + if (getItemViewType(position) == VIEW_TYPE_MESSAGE) { + MessageObject message = searchResultMessages.get(position - messagesStartRow); + TopicDialogCell dialogCell = (TopicDialogCell) holder.itemView; + dialogCell.drawDivider = position != messagesEndRow - 1; + int topicId = MessageObject.getTopicId(message.messageOwner); + if (topicId == 0) { + topicId = 1; + } + TLRPC.TL_forumTopic topic = topicsController.findTopic(chatId, topicId); + if (topic == null) { + FileLog.d("cant find topic " + topicId); + } else { + dialogCell.setForumTopic(topic, message.getDialogId(), message, false); + dialogCell.setPinForced(topic.pinned); + dialogCell.setTopicIcon(topic); + } + } + } + + @Override + public int getItemViewType(int position) { + if (position == messagesHeaderRow || position == topicsHeaderRow) { + return VIEW_TYPE_HEADER; + } + if (position >= topicsStartRow && position < topicsEndRow) { + return VIEW_TYPE_TOPIC; + } + if (position >= messagesStartRow && position < messagesEndRow) { + return VIEW_TYPE_MESSAGE; + } + return 0; + } + + @Override + public int getItemCount() { + if (isLoading) { + return 0; + } + return rowCount; + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getItemViewType() == VIEW_TYPE_MESSAGE || holder.getItemViewType() == VIEW_TYPE_TOPIC; + } + } + } + + public void setOnTopicSelectedListener(OnTopicSelectedListener listener) { + onTopicSelectedListener = listener; + } + + public interface OnTopicSelectedListener { + void onTopicSelected(TLRPC.TL_forumTopic topic); + } + + @Override + public void onResume() { + super.onResume(); + getMessagesController().getTopicsController().onTopicFragmentResume(chatId); + Bulletin.addDelegate(this, new Bulletin.Delegate() { + @Override + public int getBottomOffset(int tag) { + return bottomOverlayChatText != null && bottomOverlayChatText.getVisibility() == View.VISIBLE ? bottomOverlayChatText.getMeasuredHeight() : 0; + } + }); + } + + @Override + public void onPause() { + super.onPause(); + getMessagesController().getTopicsController().onTopicFragmentPause(chatId); + Bulletin.removeDelegate(this); + } + + @Override + public void prepareFragmentToSlide(boolean topFragment, boolean beginSlide) { + if (!topFragment && beginSlide) { + isSlideBackTransition = true; + setFragmentIsSliding(true); + } else { + slideBackTransitionAnimator = null; + isSlideBackTransition = false; + setFragmentIsSliding(false); + setSlideTransitionProgress(1f); + } + } + + private void setFragmentIsSliding(boolean sliding) { + if (SharedConfig.getDevicePerformanceClass() == SharedConfig.PERFORMANCE_CLASS_LOW) { + return; + } + ViewGroup v = contentView; + if (v != null) { + if (sliding) { + v.setLayerType(View.LAYER_TYPE_HARDWARE, null); + v.setClipChildren(false); + v.setClipToPadding(false); + } else { + v.setLayerType(View.LAYER_TYPE_NONE, null); + v.setClipChildren(true); + v.setClipToPadding(true); + } + } + contentView.requestLayout(); + actionBar.requestLayout(); + } + + @Override + public void onSlideProgress(boolean isOpen, float progress) { + if (SharedConfig.getDevicePerformanceClass() == SharedConfig.PERFORMANCE_CLASS_LOW) { + return; + } + if (isSlideBackTransition && slideBackTransitionAnimator == null) { + setSlideTransitionProgress(progress); + } + } + + private void setSlideTransitionProgress(float progress) { + if (SharedConfig.getDevicePerformanceClass() == SharedConfig.PERFORMANCE_CLASS_LOW) { + return; + } + slideFragmentProgress = progress; + if (fragmentView != null) { + fragmentView.invalidate(); + } + + View v = recyclerListView; + if (v != null) { + float s = 1f - 0.05f * (1f - slideFragmentProgress); + v.setPivotX(0); + v.setPivotY(0); + v.setScaleX(s); + v.setScaleY(s); + + topView.setPivotX(0); + topView.setPivotY(0); + topView.setScaleX(s); + topView.setScaleY(s); + + actionBar.setPivotX(0); + actionBar.setPivotY(0); + actionBar.setScaleX(s); + actionBar.setScaleY(s); + } + } + + @Override + public void onTransitionAnimationStart(boolean isOpen, boolean backward) { + super.onTransitionAnimationStart(isOpen, backward); + + transitionAnimationIndex = getNotificationCenter().setAnimationInProgress(transitionAnimationIndex, new int[] {NotificationCenter.topicsDidLoaded}); + transitionAnimationGlobalIndex = NotificationCenter.getGlobalInstance().setAnimationInProgress(transitionAnimationGlobalIndex, new int[0]); + } + + @Override + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + super.onTransitionAnimationEnd(isOpen, backward); + if (isOpen && blurredView != null) { + if (blurredView.getParent() != null) { + ((ViewGroup) blurredView.getParent()).removeView(blurredView); + } + blurredView.setBackground(null); + } + + getNotificationCenter().onAnimationFinish(transitionAnimationIndex); + NotificationCenter.getGlobalInstance().onAnimationFinish(transitionAnimationGlobalIndex); + + if (!isOpen && (opnendForSelect || removeFragmentOnTransitionEnd)) { + removeSelfFromStack(); + if (dialogsActivity != null) { + dialogsActivity.removeSelfFromStack(); + } + } + } + + @Override + public void drawOverlay(Canvas canvas, View parent) { + canvas.save(); + canvas.translate(contentView.getX(), contentView.getY()); + if (fragmentContextView != null && fragmentContextView.isCallStyle()) { + float alpha = 1f;//(blurredView != null && blurredView.getVisibility() == View.VISIBLE) ? 1f - blurredView.getAlpha() : 1f; + if (alpha > 0) { + if (alpha == 1f) { + canvas.save(); + } else { + canvas.saveLayerAlpha(fragmentContextView.getX(), topView.getY() + fragmentContextView.getY() - AndroidUtilities.dp(30), fragmentContextView.getX() + fragmentContextView.getMeasuredWidth(), topView.getY() + fragmentContextView.getY() + fragmentContextView.getMeasuredHeight(), (int) (255 * alpha), Canvas.ALL_SAVE_FLAG); + } + canvas.translate(fragmentContextView.getX(), topView.getY() + fragmentContextView.getY()); + fragmentContextView.setDrawOverlay(true); + fragmentContextView.draw(canvas); + fragmentContextView.setDrawOverlay(false); + canvas.restore(); + } + parent.invalidate(); + } + canvas.restore(); + } + + private void prepareBlurBitmap() { + if (blurredView == null || parentLayout == null || SharedConfig.useLNavigation) { + return; + } + int w = (int) (fragmentView.getMeasuredWidth() / 6.0f); + int h = (int) (fragmentView.getMeasuredHeight() / 6.0f); + Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + canvas.scale(1.0f / 6.0f, 1.0f / 6.0f); + parentLayout.getView().draw(canvas); + Utilities.stackBlurBitmap(bitmap, Math.max(7, Math.max(w, h) / 180)); + blurredView.setBackground(new BitmapDrawable(bitmap)); + blurredView.setAlpha(0.0f); + if (blurredView.getParent() != null) { + ((ViewGroup) blurredView.getParent()).removeView(blurredView); + } + parentLayout.getOverlayContainerView().addView(blurredView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + } + + @Override + public boolean onBackPressed() { + if (!selectedTopics.isEmpty()) { + clearSelectedTopics(); + return false; + } + return super.onBackPressed(); + } + + @Override + public void onTransitionAnimationProgress(boolean isOpen, float progress) { + if (blurredView != null && blurredView.getVisibility() == View.VISIBLE) { + if (isOpen) { + blurredView.setAlpha(1.0f - progress); + } else { + blurredView.setAlpha(progress); + } + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/TopicsNotifySettingsFragments.java b/TMessagesProj/src/main/java/org/telegram/ui/TopicsNotifySettingsFragments.java new file mode 100644 index 000000000..7df9b62fd --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/TopicsNotifySettingsFragments.java @@ -0,0 +1,311 @@ +package org.telegram.ui; + +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BackDrawable; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Cells.ShadowSectionCell; +import org.telegram.ui.Cells.TextCell; +import org.telegram.ui.Cells.TopicExceptionCell; +import org.telegram.ui.Components.RecyclerListView; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; + +public class TopicsNotifySettingsFragments extends BaseFragment { + + + private final int VIEW_TYPE_ADD_EXCEPTION = 1; + private final int VIEW_TYPE_TOPIC = 2; + private final int VIEW_TYPE_DIVIDER = 3; + private final int VIEW_TYPE_DELETE_ALL = 4; + + Adapter adapter; + + RecyclerListView recyclerListView; + long dialogId; + + ArrayList items = new ArrayList<>(); + HashSet exceptionsTopics = new HashSet<>(); + + public TopicsNotifySettingsFragments(Bundle bundle) { + super(bundle); + } + + @Override + public View createView(Context context) { + FrameLayout frameLayout = new FrameLayout(context); + fragmentView = frameLayout; + + actionBar.setBackButtonDrawable(new BackDrawable(false)); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + return; + } + } + }); + actionBar.setTitle(LocaleController.getString(R.string.NotificationsExceptions)); + recyclerListView = new RecyclerListView(context); + DefaultItemAnimator defaultItemAnimator = new DefaultItemAnimator(); + defaultItemAnimator.setDelayAnimations(false); + defaultItemAnimator.setSupportsChangeAnimations(false); + recyclerListView.setItemAnimator(defaultItemAnimator); + recyclerListView.setLayoutManager(new LinearLayoutManager(context)); + recyclerListView.setAdapter(adapter = new Adapter()); + recyclerListView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (items.get(position).viewType == VIEW_TYPE_ADD_EXCEPTION) { + Bundle bundle = new Bundle(); + bundle.putLong("chat_id", -dialogId); + bundle.putBoolean("for_select", true); + TopicsFragment topicsFragment = new TopicsFragment(bundle); + topicsFragment.setExcludeTopics(exceptionsTopics); + topicsFragment.setOnTopicSelectedListener((topic) -> { + Bundle bundle2 = new Bundle(); + bundle2.putLong("dialog_id", dialogId); + bundle2.putInt("topic_id", topic.id); + bundle2.putBoolean("exception", true); + ProfileNotificationsActivity fragment = new ProfileNotificationsActivity(bundle2); + fragment.setDelegate(exception -> { + exceptionsTopics.add(topic.id); + updateRows(); + }); + presentFragment(fragment); + }); + presentFragment(topicsFragment); + } + + if (items.get(position).viewType == VIEW_TYPE_TOPIC) { + TLRPC.TL_forumTopic topic = (TLRPC.TL_forumTopic) items.get(position).object; + Bundle bundle = new Bundle(); + bundle.putLong("dialog_id", dialogId); + bundle.putInt("topic_id", topic.id); + bundle.putBoolean("exception", false); + ProfileNotificationsActivity topicsFragment = new ProfileNotificationsActivity(bundle); + topicsFragment.setDelegate(new ProfileNotificationsActivity.ProfileNotificationsActivityDelegate() { + @Override + public void didCreateNewException(NotificationsSettingsActivity.NotificationException exception) { + + } + + @Override + public void didRemoveException(long dialog_id) { + removeException(topic.id); + AndroidUtilities.runOnUIThread(() -> { + exceptionsTopics.remove(topic.id); + updateRows(); + }, 300); + + } + }); + presentFragment(topicsFragment); + } + + if (items.get(position).viewType == VIEW_TYPE_DELETE_ALL) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("NotificationsDeleteAllExceptionTitle", R.string.NotificationsDeleteAllExceptionTitle)); + builder.setMessage(LocaleController.getString("NotificationsDeleteAllExceptionAlert", R.string.NotificationsDeleteAllExceptionAlert)); + builder.setPositiveButton(LocaleController.getString("Delete", R.string.Delete), (dialogInterface, i) -> { + Iterator iterator = exceptionsTopics.iterator(); + while (iterator.hasNext()) { + int topicId = iterator.next(); + removeException(topicId); + } + exceptionsTopics.clear(); + updateRows(); + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + AlertDialog alertDialog = builder.create(); + showDialog(alertDialog); + TextView button = (TextView) alertDialog.getButton(DialogInterface.BUTTON_POSITIVE); + if (button != null) { + button.setTextColor(Theme.getColor(Theme.key_dialogTextRed2)); + } + } + } + }); + frameLayout.addView(recyclerListView); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + return fragmentView; + } + + private void removeException(int topicId) { + getNotificationsController().getNotificationsSettingsFacade().clearPreference(dialogId, topicId); + TLRPC.TL_account_updateNotifySettings req = new TLRPC.TL_account_updateNotifySettings(); + req.settings = new TLRPC.TL_inputPeerNotifySettings(); + TLRPC.TL_inputNotifyForumTopic topicPeer = new TLRPC.TL_inputNotifyForumTopic(); + topicPeer.peer = getMessagesController().getInputPeer(dialogId); + topicPeer.top_msg_id = topicId; + req.peer = topicPeer; + getConnectionsManager().sendRequest(req, (response, error) -> { + }); + } + + @Override + public boolean onFragmentCreate() { + dialogId = arguments.getLong("dialog_id"); + updateRows(); + return super.onFragmentCreate(); + } + + private void updateRows() { + boolean animated = !isPaused && adapter != null; + ArrayList oldItems = null; + if (animated) { + oldItems = new ArrayList(); + oldItems.addAll(items); + } + + items.clear(); + items.add(new Item(VIEW_TYPE_ADD_EXCEPTION, null)); + ArrayList topics = getMessagesController().getTopicsController().getTopics(-dialogId); + boolean added = false; + if (topics != null) { + for (int i = 0; i < topics.size(); i++) { + if (exceptionsTopics.contains(topics.get(i).id)) { + added = true; + items.add(new Item(VIEW_TYPE_TOPIC, topics.get(i))); + } + } + } + if (added) { + items.add(new Item(VIEW_TYPE_DIVIDER, null)); + items.add(new Item(VIEW_TYPE_DELETE_ALL, null)); + } + items.add(new Item(VIEW_TYPE_DIVIDER, null)); + + if (adapter != null) { + if (!animated) { + adapter.notifyDataSetChanged(); + } else { + ArrayList finalOldItems = oldItems; + DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return finalOldItems.size(); + } + + @Override + public int getNewListSize() { + return items.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + return items.get(newItemPosition).compare(finalOldItems.get(oldItemPosition)); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + return false; + } + }).dispatchUpdatesTo(adapter); + } + } + } + + public void setExceptions(HashSet notificationsExceptionTopics) { + exceptionsTopics = notificationsExceptionTopics; + } + + private class Adapter extends RecyclerListView.SelectionAdapter { + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = null; + switch (viewType) { + case VIEW_TYPE_TOPIC: + view = new TopicExceptionCell(parent.getContext()); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case VIEW_TYPE_ADD_EXCEPTION: + TextCell textCell = new TextCell(parent.getContext()); + textCell.setTextAndIcon(LocaleController.getString("NotificationsAddAnException", R.string.NotificationsAddAnException), R.drawable.msg_contact_add, true); + textCell.setColors(Theme.key_windowBackgroundWhiteBlueIcon, Theme.key_windowBackgroundWhiteBlueButton); + view = textCell; + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case VIEW_TYPE_DIVIDER: + view = new ShadowSectionCell(parent.getContext()); + break; + case VIEW_TYPE_DELETE_ALL: + textCell = new TextCell(parent.getContext()); + textCell.setText(LocaleController.getString("NotificationsDeleteAllException", R.string.NotificationsDeleteAllException), false); + textCell.setColors(null, Theme.key_windowBackgroundWhiteRedText5); + view = textCell; + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + } + view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + if (items.get(position).viewType == VIEW_TYPE_TOPIC) { + TopicExceptionCell cell = (TopicExceptionCell) holder.itemView; + cell.setTopic(dialogId, (TLRPC.TL_forumTopic) items.get(position).object); + cell.drawDivider = !(position != items.size() - 1 && items.get(position +1).viewType != VIEW_TYPE_TOPIC); + } + } + + @Override + public int getItemCount() { + return items.size(); + } + + @Override + public int getItemViewType(int position) { + return items.get(position).viewType; + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getItemViewType() == VIEW_TYPE_ADD_EXCEPTION || holder.getItemViewType() == VIEW_TYPE_TOPIC || holder.getItemViewType() == VIEW_TYPE_DELETE_ALL; + } + } + + private class Item { + final int viewType; + final TLRPC.TL_forumTopic object; + + private Item(int viewType, TLRPC.TL_forumTopic object) { + this.viewType = viewType; + this.object = object; + } + + boolean compare(Item item) { + if (viewType != item.viewType) { + return false; + } + if (object != null && item.object != null && object.id == item.object.id) { + return true; + } + return false; + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java index d7f1a5c28..15e6b1853 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java @@ -194,7 +194,7 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific public View createView(Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setAllowOverlayTitle(false); - if (!passwordEntered) { + if (!passwordEntered || delegate != null) { actionBar.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); actionBar.setTitleColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); actionBar.setItemsColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText), false); @@ -427,13 +427,15 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific updateRows(); - if (passwordEntered) { + if (passwordEntered && delegate == null) { actionBar.setTitle(LocaleController.getString("TwoStepVerificationTitle", R.string.TwoStepVerificationTitle)); } else { actionBar.setTitle(null); } if (delegate != null) { - titleTextView.setText(LocaleController.getString("PleaseEnterCurrentPasswordTransfer", R.string.PleaseEnterCurrentPasswordTransfer)); + titleTextView.setText(LocaleController.getString(R.string.YourPassword)); + subtitleTextView.setText(LocaleController.getString(R.string.PleaseEnterCurrentPasswordTransfer)); + subtitleTextView.setVisibility(View.VISIBLE); } else { titleTextView.setText(LocaleController.getString(R.string.YourPassword)); subtitleTextView.setVisibility(View.VISIBLE); @@ -725,7 +727,7 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific } @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { super.onTransitionAnimationEnd(isOpen, backward); if (isOpen) { if (forgotPasswordOnShow) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationSetupActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationSetupActivity.java index f9faf03f3..84be6b38f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationSetupActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationSetupActivity.java @@ -258,7 +258,7 @@ public class TwoStepVerificationSetupActivity extends BaseFragment { @Override public void onItemClick(int id) { if (id == -1) { - if (otherwiseReloginDays >= 0 && parentLayout.fragmentsStack.size() == 1) { + if (otherwiseReloginDays >= 0 && parentLayout.getFragmentStack().size() == 1) { showSetForcePasswordAlert(); } else { finishFragment(); @@ -2137,7 +2137,7 @@ public class TwoStepVerificationSetupActivity extends BaseFragment { @Override public boolean isSwipeBackEnabled(MotionEvent event) { - if (otherwiseReloginDays >= 0 && parentLayout.fragmentsStack.size() == 1) { + if (otherwiseReloginDays >= 0 && parentLayout.getFragmentStack().size() == 1) { return false; } return super.isSwipeBackEnabled(event); @@ -2145,7 +2145,7 @@ public class TwoStepVerificationSetupActivity extends BaseFragment { @Override public boolean onBackPressed() { - if (otherwiseReloginDays >= 0 && parentLayout.fragmentsStack.size() == 1) { + if (otherwiseReloginDays >= 0 && parentLayout.getFragmentStack().size() == 1) { showSetForcePasswordAlert(); return false; } @@ -2155,7 +2155,7 @@ public class TwoStepVerificationSetupActivity extends BaseFragment { @Override public void finishFragment(boolean animated) { - for (BaseFragment fragment : getParentLayout().fragmentsStack) { + for (BaseFragment fragment : getParentLayout().getFragmentStack()) { if (fragment != this && fragment instanceof TwoStepVerificationSetupActivity) { ((TwoStepVerificationSetupActivity) fragment).floatingAutoAnimator.ignoreNextLayout(); } @@ -2181,7 +2181,7 @@ public class TwoStepVerificationSetupActivity extends BaseFragment { @Override public void finishFragment() { - if (otherwiseReloginDays >= 0 && parentLayout.fragmentsStack.size() == 1) { + if (otherwiseReloginDays >= 0 && parentLayout.getFragmentStack().size() == 1) { final Bundle args = new Bundle(); args.putBoolean("afterSignup", true); presentFragment(new DialogsActivity(args), true); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/WallpapersListActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/WallpapersListActivity.java index 38dcc512b..081ba9162 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/WallpapersListActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/WallpapersListActivity.java @@ -589,10 +589,10 @@ public class WallpapersListActivity extends BaseFragment implements Notification actionBar.hideActionMode(); actionBar.closeSearchField(); - if (dids.size() > 1 || dids.get(0) == UserConfig.getInstance(currentAccount).getClientUserId() || message != null) { + if (dids.size() > 1 || dids.get(0).dialogId == UserConfig.getInstance(currentAccount).getClientUserId() || message != null) { updateRowsSelection(); for (int a = 0; a < dids.size(); a++) { - long did = dids.get(a); + long did = dids.get(a).dialogId; if (message != null) { SendMessagesHelper.getInstance(currentAccount).sendMessage(message.toString(), did, null, null, null, true, null, null, null, true, 0, null, false); } @@ -602,7 +602,7 @@ public class WallpapersListActivity extends BaseFragment implements Notification } fragment1.finishFragment(); } else { - long did = dids.get(0); + long did = dids.get(0).dialogId; Bundle args1 = new Bundle(); args1.putBoolean("scrollToTopOnResume", true); if (DialogObject.isEncryptedDialog(did)) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java index 9a3de24c8..df4d3265a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java @@ -321,7 +321,7 @@ public class WebviewActivity extends BaseFragment { } @Override - protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen && !backward && webView != null) { webView.loadUrl(currentUrl); } diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_chatlist_add_2.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_chatlist_add_2.png new file mode 100644 index 0000000000000000000000000000000000000000..36f91c45ec10e45aa4289c15cd1ee7dd261737d7 GIT binary patch literal 1834 zcma)7dpr}28s6H>Hq1=fnE7lmR%n`Bzt5LjLK2Id59eD;HkZn!S)DJpG51@_U7t%Q zm(tvm`z4IT_%dY_CX`E5l-xS}anAYc{C?+;=Y5{%eV_N=_l-Mu)>;Ox1P1^BGB&3z zo%YyoA5h3%P4T)gut&fkCu?&6Z&3Nep0T{_YU5>Z4>+=?p#WfvHvqgJ*@My^002+{ z5CGa^;C`>*z<;KG1)%@x{owa;kqQ6+xocx-cK!x%*~6|>+3H}3pv^HqSF_R4fS@VP z@A?TcE78~4*0mvN*3Er-JSR#1E^`8GKmfyHW5rZ7NAn25hu&-6?Lfw|hXdg z70N#u#l;UB|L)9U2}n<`xA7Xk{b|hDq&N$|czK=g3dbNp^pn!HcOu4uJ9W_OVzJn^ zAU6glJ5Dh%X{vFrr6x<$2cCsSIrsN(%nXrAikm-L+)b}^tb%m@;-n%bS%WX@OONRK zwIy2#%&HEM249pZApBSg^`DNw^apL;%V9HzRX+OJm`C&UFE@YVj`&n zm6l7W1>1G_bY}g}L5y1B8_^I(O8Lxf|I>ZnEJCs$KZ_l!5vGjnaN5|uK3-9WUgoQg z>RHh-f`LG$pAV6lD0MEyoKeB?kTn4X!J5Pg&lEr9lGZV>49Wu5Ju=4D3B&|e-d~@=d>S%l@gQsH1_IDG<_(Shx(vS(% ziEEuY%LS)}I?H9dj=j4`;-p5w6~H8`%4^f%vJ@9;h1t4@eRLy_GreStMSOF&Os`X$g)yFny z*r?hTutD7w;4SZN`1sc{e?PMAZJHy~9bP?-dhWsqz*eNuX7a$e4mPE=-!CUeF_!?d z9b%`^*2iiBT<+z3S1e4Ts;yI1Rx1(TFM$hUisehB5mDKVEk=2cZ6}5o^A6*hJy(BI zS%xUzCMRnIu89ePEZ?DzEWwlSIiI8>wdd zetIkGYEPBeI^iY1q5JUE-c*SCz${ zED9~1=BEqXMLUou?z-DC9c+!k36JO*vzS0=Ffg@6K8mNPA%0BVXb-&89g#W62NI>M$oEszzWF*_-SL>A@ux@Ajb${8Ucl0H~K=8KflX2~3?y>s_1g zd@Dx8PM=awjZkK|s_HdlV^V2E7q}_01Fpaa+jFvANgG+yP3Ch$%v?=nTf?^3FtAK_C8jNI>(z&zp) zz*n>uqpWF;ETMlkxfO~Sotd7WoK@S~2265a7ux$aAaG%bqBrp2T`6@uUtRa08+vEs zOQ^tHZxMCVjjx`SbWe}PCM>-yEL3;Z4C?>Vgl3FANkyWxqUsDyWlz@f7o*c?EX9QJ zU3{#|B1;FRa3MUEdCTIx+3(h{x&HH_U^l#I$CTvG@fMcICIpCnmKgpR%ngLO%-bU_ z=m?t>Z$OIPT=bnoEn#zO7K$kC$40j4Eul`HnY8>4$INbO^i35{3BLsv&jm#l!nw zYjjsNj(fyC!j%+z<#DRyESoU7-)N4+X$Ll4nli{0sivJ+uG- literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/list_unmute.png b/TMessagesProj/src/main/res/drawable-hdpi/list_unmute.png new file mode 100644 index 0000000000000000000000000000000000000000..a6735ccfea3b45124a913440c6075eeb3556c56b GIT binary patch literal 314 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyy~STEL88gA~qMxhWJ#E%9`546zVQ zPLL>i@c;jRC5IaBj`fq?RxC*KQ?O$@lHf4S(P6HS!yd6DmKK%k3ph`-HQZp~Wm*-; z_UN|4(H{(U%`+BEN_2=~o$usSWYBzrQ&UBS-?(FfRnw;s^A~!K+MF5*ji>Y(FZDRA z=~`mPdSaTxqshlv)R?^${x!~#IbqDXf;+=QDU^q4CqssSPV4_e&y^x19tH>VOsQcz f^8JJ6qFFKwNuOu>GPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91B%lKT1ONa40RR91Bme*a04fgS`~Uz0>`6pHR9Fesmv3lJQ5?tj__z7D zA~ua^nSVv1{K*^b-6kdZ`$oL>Liro=av>Dn5t>qjk|LX^DG^Fpd9fMUShQtC8MDpf z^S$?+XZLyTpXWaI5a0UV^SkHwJKyuW=iGbFxdjFP7!rfcCz(u+L!%UqhMlk@kx2ZY z{4L~^5S;a319dme=n;<>Gve3XhIMA;*ef3cKUvetTV802f%|oA&5=sIp8R3*Km`d+3&hiEPfeQM( zq-=m*aOVpi=ILj>u^U;qnmzR?lSWz`*TKS6Wp<;7kgI6E3<$kU-9|VOR>Hf`1)X(6 zp~lu_miU^Sh}rV!eNcaQ3Z7{_?Zk+Q*Sw+Orjj$B}5dSzfcMopvl-h?{a zjGZ#)#yHy3wFd4GSj7`*pUP>EM^`Dkdzb7nmd&na0k%?{ELFrQqXUZtw zd777b?7v~!*>PsZ$&RP~#8l|`Fbd8N%Yv8+d~v@ry*F3T_Ktp5~oxdYjat!BPd zsCcZ()QOQKv^dF81a%Isru2chxL;Wt8XtTDrUD&}$U~?cjuPvvL&ohE1Ra7FE7F{K zyrF|3Q0FW`r;AB}@;Hj2_7$NEWa_nA}KT~q5d{3S~1Eanxwl3 zzGGTGOZT{U__P^Ef*8GzYv6t8#Z7KP+dC_@SDE0c-0N&2)Vqay#WxM>7+j03Q^6zn z0)B*Acl5s5A4MI3z3)dC_cib&pLmio9_Z*G6f|AhDzu8vfk&V{GhxPtzS?~-*3V^X zVKg1~LKW&ux1)}=%_zDG0SsT0!x5MDoG`}hM)#zA1i}PV#|5*J!{UPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91B%lKT1ONa40RR91Bme*a04fgS`~Uz0Oi4sRR9Fe^mQP4jQ542!GQ~wx zLH1fwrqmbw0U*i+3GU!(Q#)=X5jXE+&eFh zhknlQiPxpAH*APc)dU`c1uzTlg56%%q8I0S6n-x1k(OynTQfAP$z8Ej}2HZ1Di!f7-dspdnXN z)A*&Rc#SXmO^wcyX>~d+)_RQ2qJz-jD)7@XJ!&xSsd1xD#CiClzRRc|H7#jYNeAC! zoMwld(^s<3l5%k^ctSOgYu4Rv)fl zi%y6}#mzt`w9^#k@sEIhaNPpez=pRw>wLCK!EK<+vI5S7eL(gN=!B*?sX#=yAvSY> zco3Wb&Qx$zM@oK>_>v{Q<41vBX1_ot(~UU83G zK^v^#b-a-EevfC^S(NUzWx2Z#@v!%o3j>F1FTwZez8Jbz`HTDqEQwY>L!dSwk+v=pKVzlqLh}z{n zZ&Knvy*+(q-~Zb;Pd?qGlby>fl;K)?+%w}F%hfAJhhld$Ug7I9+{D;4ea4E^tcfgw zKKo|fPZraj{r}{#m8l*)Yv#W3e1H6e7YCnT*+TZ|^RM6NHROM`EpMCK`s|{EJnA!c zUG|7(VV=d?q+HS67}fYn-^P5Q;t5vo+yxJKGsIe|3fNcLPBFF2Zwp{wr%>9`WxgO* rz{lYgk8iHtjL8Nn7x?1R_I_ksQ244*`t+*FAU}J$`njxgN@xNA0+M?p literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_signed.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_signed.png new file mode 100644 index 0000000000000000000000000000000000000000..3a1c939813210e107039b67fa7530520e23ce40c GIT binary patch literal 763 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSWdSpS4N|DZ_xKM315>J}i(^Ox z=i8}!vx5Ug4t$;^Bp|$`iKEr*hS&2)3%X@xTgo&_v{}X3#oJ#jnc@24!IB$%U9wRc zP75@aMzsE6T*%VGv+<}BN79P0jeJX2&UpTQ&mCp+gKh4gK52a7`%`3hulD(zJLUFC z8$A|D{y3j@;Nt(syWVlWk$iDILcHW=;hZ_^_n4FjADVD_t<qhD@?lBbz}M-kH62Gj@4%0+s!e5t;&C+(=6w2cz%38tMUFtqto*e&d>a@?3mL< zk=kUg^%;(TOy11iQR8<%Nxo-ae3sM-&d;Mc>_9PqDiM%r!?mtq{Kd^hx zMdkp8=zR}swlW>vHhV+7%EKK~L(VS}+PI|0`E+fXxZQ?h-8gsZuhM2bfKNS<4sXf(m+-FlsjKp%5<{sea!<@}4vpQNg;9jRNep5>-b zSIZ{9kQBCAu6cidU-)48!Tn)L!r2-1tHjlJ-c7Y&-8A>@;ED@{a~ti=?$0&ZJvDw3DB*d!`njxgN@xNAmr6~! literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_topic_close.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_topic_close.png new file mode 100644 index 0000000000000000000000000000000000000000..f01297f99fc1319f0c215a919c6b2a74d99026be GIT binary patch literal 762 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSWdSpS4N|DZ_xKM315=8pi(^Ox z=i3?fMl6mZ$8;CjGPr6=dvcb|Jt)h<_duwa;b7V$-PRb5LZ%o86^ES+oX6fv|GhHX ztiEg;Pw>&zzqT#keZTsC_}}zh>Cd12TU*6ab@lGmy_vJlJ>JL@*8JS|Y4ZB()!bjd zal7O+vcxot3(UTCWqQNfYa$bCSo9o%gU@6{C2kFN+hli=@1Wd=f8kfoAJn}Na?+%f zc>{CX0iCB0Io=r0^}lvwhsznM9ZYHGZDbm@?n?Q@xv$GGc-^fVhwgB(luzsYB(p}` zD%?a%?MP6{vN>m61TV1Hr5w8=u2rGETZDVcVV|8_MV1-QQ5W;HoTs!8{BSTq-Ig;CEGXi;>zb| z1FlN%$TjXedtmNIU8}IUx3(=Vk$KXiAS=B)LHT-M{LX_wkc~gK6gm) z)wPR?ojpF~eAg8+qyu+Jov8c&OVFkCd6O7R>1!@&AZwDoqtUeqEPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91B%lKT1ONa40RR91Bme*a04fgS`~Uy~uSrBfR9Fe^mp^C}K@`SsV-zDw z{1ZYDR4l|oj9?)_LQoJ1Xr&-xKt(~Y5{%fG!aj|aMG8S|QrM`KqJ<~~Efj(vDqf+T zRwjjF$gRKU&0N-Gso*}K`)YAX@A0%@A|gF9d>ur&@HBjZ3~ zF3t?)s$EKXgUQ)Zp~OpRhT_fUq1?^Ztbum`bQ}H-+eht?y=MHHB*|xcPi=tfK6~#u z{)AUYCoSY7JOg~90cli(7*BykpC*MfNA5OQubHa4W&Ma%?UiTqGI)-u)p=71$^4d` zgLK{y4`&)$TSiYpei(?ckm!MY8XPlC*V(?wQUNBr3I0YYv0@S7E->4iA$^*csk-cW z9q)TqqyhQ0Y1o}_)C|oVk{$CsowyfM604fS(qH>kU&_>K_qf>uo@JBEb?q2pNxw1q zg(3s&{Z?u_i|R`LBbwipyqaoM(vUjbIYkucqb7 z*b73;NRRxic%O@!_*n$5fs0@>Xa%40F-8xP*wsq5zj#OAoA$IOLo;MeY|=y!pCK=Z z63&)InI%9}TcN^swrefL39z@2)GRD8++{T%wc$DCAVYMY05=>JJ#mx|umw|M&O1W4 z>@CVaJk?*8n6aloFQ)%_R)bcE3Em?hKaAVkm|B3_dYMzWatd4p>%e_9M+?nUjC$Hz zbIdahY)sj_Gs2j#EJo+>a~dip(Cz`RGINcqQV>pNNc&V$hmy=Q0KS;{aN7&Vc5&Di3zm P00000NkvXXu0mjfUFT+) literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_topic_restart.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_topic_restart.png new file mode 100644 index 0000000000000000000000000000000000000000..70df24b4a3176c6dcc48bc753d787b12fa70a2f0 GIT binary patch literal 991 zcmV<510ei~P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91B%lKT1ONa40RR91Bme*a04fgS`~Uz0Nl8ROR9Fe^n9FJvK@f&>;thgi z=`|?g0|YS<+=y;;BdDM&!Hv7@+_{@o5DXGga3>rEH|7CC5TYQcV3Icw6p0!{kStU% z=J@@Ss-e%M=O!c`(1QO|chz4Prl+Sn>FfJ1D-x=)ZMaaoC^F39K!O=VAFZygL zAEG{bE)zv$2vbJ-ihk<>=+EFnQJmZ1Xs5aqe>`C%&O#UN1bokoCp`!r*xc2920Oih zyvC;;?2_dD*&K#G`I5QJ7#ZNt8ge(bOW+G|dBfni`|}##oDmoDzI2YWAFLQ@JW4Vn zoOKP!g?&GGVmYLg?|Lj3_Hhvqadb$hk`0*Y)b<^v?MN)({vf5!oVN7_4hW1)xp2!Y}V_?t% zK1Bk<8ajMa+3Evy*zw-L-e^cOhY6ShM=bCo`i_l571j_ZmGB8IVfkftT_GBay(`)^ z-pmWe$5_Jmkz`&&HC21PknoSTJ#pK?UErc);U)-IpsOIh@@+3Rqy)H>eHfn_N!$Mr zcx`hjeT&X^RnJZE*ZwfpJvtt%GNLnSIUTM2yhKIMzpy7b^^Oh&L9TPSo`UxigmR0? zwyNjta#oAxB2zMnraj5@^>$2)=+Oss33}%b#jrCP+mt`0!v;P19 literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_topics.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_topics.png new file mode 100644 index 0000000000000000000000000000000000000000..18357687d6557fce623d33cd3af5c561c741990f GIT binary patch literal 449 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSWdSpS4N|DZ_xKM`nYO2kV@L$& z+iCuM2Ll9-P3<_sxn|89T@{&wiyo;6wHL5*mv3-Z>N{|ZF(R_V_(%AD_gOyiv&=4O zO`ZAVbDEWL{<8S&UtFxBsSc7SW-y-rylO$}wH|N#)0Uewe6MV{zJQN!!DEYaonJM7 z%+{z4sEYOcQ=QhXXIvMy=PWPr<_PaGUBk@NL1v%dB}~~Vf06mV zQE=mJbLNfQVF8wA*J}<=f2;hN`}HcGKcdXf;@RpR|8P{+oAAhMTcOVZUpAF1=b!Ya z@Kzg`EfK!E?QG?(a~p&18J%|dp5m5ZaOrKn>?hylCSR|v-<#rH3iR9$7G{3ib4z}1 S7InG;3O-L)KbLh*2~7Yt^Q4mi literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_viewintopic.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_viewintopic.png new file mode 100644 index 0000000000000000000000000000000000000000..d3ff44038946e2adb186ec2cd4dae3e189020685 GIT binary patch literal 1170 zcmV;D1a13?P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91B%lKT1ONa40RR91Bme*a04fgS`~Uz0{7FPXR9FekmrIDvQ5?tTdOxR3 zUJYR~NJx}N7NjhY1raN;uwgOfnb%TiQrL(PrC2HDHL@U&LKF)Nq@fW;Au{7NT%Yfq z^Be#3zxRLt=eo1-tIs{>{9fnWbI$*N&cCAKKa)(kd@hU@SOGy|{1IfU4XakccR8~X(kVN=Rg5#iB zGI>O0Y)E{E4*?p?Y0E(Q2{aJxp^}BT8aNLopQ%g>PK5ITi==!2LF-?v19Iq3ZUURF zchtuei^tpk(2 z#T-vMFa3jAiH5#Q7}t_5`R5SCNHU^4ho;K zputEE=cPs&oyKu_4k-%acPq1#&_SJX{@URF6O7`PGd@h|JJC#hNrS?FvFlUCJTEbd zR%hJh8A@!BBY05w_5^$JZc$g=^n=Pkxhqh`;t+MiPRiY)++r%u%HY%!5s4qvq12mf-p$?KH zw^SJYm7%j1knBr}4AB$67VK#FKz(Xb9Jb1c?*`8g!zGYB^9&_4hz%Z$(&E2T9Xgrm zb%k<`Stt|#l@}Cg5L41%6iUoR*QZ{j{=8=?y1Rf+G!D#OHu;@9LUoPjzse6&xTz9aop?z zwNb-@iVTVzb3tDlCNHSe6bbQ7IA3*<1+OC5s{Dxj7CzdKOW`I&GUBWg zdahG6bQq{$8%d!z`7-J{z3;$N_yFHQ`^`QSbp?*@kN329!9k9pEM=I`k=5w!R4>(4uin>H zuU_?{)YF6af{KHKgM)*EgM)*E!v@3iylwU|3w$2wHp*WjamOn;k8`%jb0<%p+>UKd zS+LJ}qsr&Di84coI`%w4HaJM>*pBh>@f}DLq)zUSjg8eoBJvrL<;89DJ_Kdaxu8su z*RE$foeKyQgc(ROo+UBC9Yfntn1vfwuccvRWTatuc(_q-H*zW^?wGtr=CRMk9kcWE z@j{`Hvd_yD`YO_Ubab?yZw3&9%a!AcSZOh|P`z6=fdHI}O9`8q77nDicEKOR{T9`PrjeAuvw5ewRS+OC2yFA@R zP#A{Kc64;K=(LTbLrW(~jzn$;(%jq}doG*Jc5zDi`2@_2 zXiAE^AcwocR<70n8^9KrC6aA0Ou=7N>b#SQ5MbwwZU^Vo+|tt0F*7ssv8`0CVs38k zGuQ;S(UL|GNU=sGWK>K<;1`tdoZBZRCPv}3Y=vqSn4}y#c5Dx9LpG{rh>9n|vm7*n zmw|zSGzDq7`T6;8Y-MU4H*enj8n(eS9=1~c5Vavh048##s2PzZ81Hqxp%my9M~@y2 z^$IaO0dYfAJP`!;4Ru%~qjj0wrAwFIx0P`4asK@IcVHvsPiEjx&G6(UPhrawL5!j| zWYFbw+I{QRt)FZK9DKkw+D1K!rn8FT3U9qcsNyn)anhMgrtiUn2a~n}4n9^_SEsa{ z`jj7a!)82{O@bjJi4728W=j~BC~z65g=LH<1#F{j)K9i*rT|a+i6~S|goz!rbfr?M zop$98VWiXPmlOqfWINb!T|^j1EvBs?V$iPGAq;J&om#rJw9u2lI5L)&OjOE)c*!*=!FdWcZPWsQIc-6I?kWdIu~f94brLAH!TA`vEr zpg<1OFcjLWWtUyCLm03TZd#h|ZwL_(?h$R&BODQBplwx9{!NRUmt%>f=MsrT#Q6wE zL>Xus706a?v@E`^9s$T6fo|68d_;LDzen`IS<~X*x`;47LWPMivnChN#e)L6LFG?h zjgO$$Zl0J3U^^agh&Td;53a1N{9`NN;A44t`A;R{2<6|zCZO}m_=qH!$eMzZ7KH7H ztmo$DCTs;9e9X_!|Ez6gB5PW$t~jzz0xrfn3D!&SEW)fcojrT@U0VSM9~UlM_z*TC zTY-p**P0^X(GxmVBmygmSW(O$IdWvb^An|b`0(MZw$IC%OeRmyuX*(q{$WFifWyvE z$Zn$RYuB!QYb#RgfK3$5m?)UbARrdsVZQeJbyRLm@gE1&t@v$M0m*hk)LZ5n?uY zuStjSSW7|RvUg!&;dfiPS^?S)8;~t}X~f|7ns#`}1Td0`AlObDOq)0Tl-~^t4Gj(Z zu3x|YtF7c#2imOLzYjJbTj0YM;~NkNtMaoYMluneH9RSL_f-{#C^)5l8XNabPEMX! zU0wapR(PvnZEbDo%9Sf$pv~0wJZymg4vT!S5k?S??8j*Wl8FGULa=RKCP0OlqR~Tg zC{k!_Y}|AF`0=-DQ$iiCgwwC(WW!*A&J?v*5HX{FB;s(ICtQ9TiH-$!Wa!}DyN z4q>R$^6I&B=RTR4nfdMR-MfFG5q86#cR=Um<>hI#LF0o%hYr1|{GkYr8S6=X`bO-; zI-De-g1Es(c_M631sq$L?<8nJ5Kam+hJNKct|8q+;KW8WDX-~te|>%Z0i7Pu=|M_) zAE`M;g6$;Xd|VbEJ1FZA66B|IoHuli^%C;B7U=#8ZZ6#(5P-I!josv!%||MDZN~Bo z)euoNOa+1r6JIFbXJFoJK3q{nLU7lh`wnT@4bhE33WAIfS&%(q+w8Mo$cwoK+ni5c z$K{!A&M)hd`FLB@gA}^L@Iy2N?M$cBXj2b*f%2ceq0I=|WW2DsB#`k<1=wa$#Ww*{ z=$^FV2tqKc0wH*Yff+?!H)H_s{Lp(pND+&eZ5BFb^4Z7az9=KujwzG(O=_QysRsh1 z_ha2ir@a&(1YecXlQ;Pxz5vchK;IHU!^U>}V@048fA|hkFk~?) z=FbC)zV46s3qZ3xF9qjuPFxepXPt{GC-Tn(F%tY8LHT-T=y9hRf|T_sFHK$oJQV^M zQI(0i0Ez{@MEOn~${TV-uR+S&DSG&1j`M#qkjmqnSBN=g|7Sy%Ev}bk$g%>RkcxkF zAT@fE6GmHtUq7gbVJONj&l0Sl6eu4O`I8ZOjQ07CGmYncoa6lBI?h{Cwv`6u$m@|t z{ryIiZ*HtghMRKdpEjZt=KWA=4{f5ckz$<-AdUl}JQ2DuO3D{v&fEIYb_faOJ2*Hv rI5=z^Joua{#J|KzoOo4=zDE5IOPNQJ_mdI+00000NkvXXu0mjf%$A3M literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_chatlist_add_2.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_chatlist_add_2.png new file mode 100644 index 0000000000000000000000000000000000000000..1d405790367415b34b18407e3070ee552200d10b GIT binary patch literal 1136 zcmV-$1dscPP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS;+DSw~RCodHn!RfjK@`9*ej+BK zU?mbe6+ciAQLykMQ4vJ{0l`B26D)14>;ypsJ3$)@Efhs!8mridf`!_MDT0NSF+v0r ze=oOL_I77>cJ^j-o833??sn$Aeecb0XLe`qw$ss~1eAahC`Vv%ad8xxKxU)w(Q=*| zdR7F5ueeM!w5#R11(x|3P4{5l*VB;i39HFon56R^qNLmM>TB|ps_S$VkYf}mAO++g z;85!bQa}y@4)v-WK|H;eSIFsgPIt8veVY2-W00+gKUsMfUvXo+6}g3cK<1D^l#9vE zl7Mug$`Y@**Chj|kICQ^Z4Z&+MAJ^HsUNS#ZR|DnZ=GVmIfwZqQa3o#iz)f2u*Ck2 zfPgbh@CS%dUtwZJU0=iS4f40>2?g!MpZFF3OT{+DZlK*2+0@}{Z88bAQ`6MXCfiKM za+6B8huVu>PCFiOeea;+8+W#VpCuugW_2kuByxzW|I;U-5F^M2C^IyD6068oAl*ei zBmWFBW&rp;+1@rl$-di;k0c?5094nflnsEZeTU1ySKl;1zWsOFHyu!Vos_P(9?;tq zfpk5k&Q_Z?f_fH|ba#2h{hQ;S6>=7Zxy81UHe1)fbx$%yo@mq5oh$C-Ifahp@#s;$ zV>^y)DCE2kBZ(2kl9;A4MPv1|!W22DZM=^@mpYYP^sUFL9hLKR^9hjFC%bC{B>zIT zv0S3lkAW2bLu*?o9>DM;VguN8c#r7dRTh&AMgS;2VQ|0c6mt4^1>HT-8%K~R7-sTU z@+;(w)LU8(8b^@8U=+!_G#3ydxq!5?SVvHj2BT>|_3McITp(}W*Xa#d(g|MVH={cY zlAFOjt3xHA1eAahPy$Lo2`B+2pahhF5>NvD68I0_@}+oXRI_6M0000c%a4Q+#JIu{9_O>}O08IKUCM sMvXz#it!`ALfMg346_vi43*6a8EVC>HpOvxfZW31>FVdQ&MBb@0MqqDR{#J2 literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_discuss.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_discuss.png new file mode 100644 index 0000000000000000000000000000000000000000..229c42f1c4bdab15d210ed44b794cd13c724e660 GIT binary patch literal 730 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfyu?w#WBQ# z_w3ZO-XVb!?R!`5oUpOQLpFe;%j3eDR<3n-G#a&CHyvfsTeQ|?wi%yC=Og%8-RCb{67T*lfj3+l}T{48IHUSFVc{v%WBmHjM{{$UN5 z%jGt`{g?AW-@0e9ZxPS>&CU;e4rs=lWqbL6{{Uyp&G1R(Hl_KUvKnc2?z(^VWoFne zpYYF2$8n?jyAorId%M0Lv6`{ulFtI?P4CwP&F5U6;j*yo-xm?rGY@YbU8z|zMWA-d z9NtXhgI0bm;YDxb|7eh`6yNvR7d2wzzq+h}QbY03kzS6@A(wsVqiR-!-Iym+zlpd85 zNhvO{4Dfn1QRK#92S*2iTWggP?6 z@DJyAU$9*+>G_I#`8;B+^9^{v3b4oSPn@P?$!2+e!e?GN0L-CiC#wQYIPw zA})vJJD$$}=W+i0^V0YIzbD_X-n)#$>chi%)#tv?sjmM%H$MFO{=CS$@)Mp{+TU`! z@IEE7EN=U@Aos^SX%A%+U z>NlHSyLC8D`XysD%Qk0c`vu!8<-YBg4c@wNnFPz9+#6o}Gn5zXZ8LA3{(Q17BcGvq zmdyn-%Oh%zo3F{;idj9+mtW_g`VWq}HTFhXX$j(QHgedSt@xv^w5#_SqeU0YOy!1w=I1?E35h6fmtSb{u#gG?k}9F_L1HC$U4tk+pa}Vp0zRm-nH$!XQth- ppJEdBwEWU-VVPO~zfF&Sz~wq2V#o5k^Eg1s!qe5yWt~$(69B606?*^x literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_mini_forumarrow.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_mini_forumarrow.png new file mode 100644 index 0000000000000000000000000000000000000000..dda06da60e6551e426596d1e4af3da88a7b86beb GIT binary patch literal 310 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&k#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz1&S-^~7gA@w${E7uq3p`yMLo`Hd zCv4)oeiu}JHt`iMg<6v`0I4qgGq?t9Qar>d7O`+sWr6#Nhm6uDjwg91vfWr%`uT~Z cHMGce+R;UUjMRF_f^W#*B`Db-Z!#4?r6vQUv~NI_TO5|YYtazHxB1( zo={fX>FRU+0q>H%M|x6Q9@gY!pV8aM@&3Xr55bpYEfHXWgB-Qf4e!yOTxsi{X+)nRgDg ze&r`96!wv$ixgK1*N)%Mv7dlPc!K>aOU>cO?=si)WQ@EjsovWBp!(qnz&DZ{$6*}2m z>Mk$8x94P4`n5olO-G%-UN)I~XfMYfmA=R87jE2ZP88l@vq|OMzS|0SP9C~;x94AI@+J=xt4Ism3;Z9L%Km*`)?s+y z`ck*w4|cs zssC$bv;3cwuG4SY!MHnT^2F2frdLkwvhxqv^<92x?}Vt&&rD}KOv-2%b4!mCpZt(n z+e6pX`>}0L(TS_!Up%bdPg`yD_MUR`)qvG`ZxZgf8OwSe+pXo)RH(mu!jh`kYW=^j zcdfg+hSS7F_r9pbKX&$uhdj10#5GO(@X>CjYul*?mgE0lgn!|e`^Thm>E+t<=UZQa O;>Od}&t;ucLK6T`y4J}6 literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_topic_create.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_topic_create.png new file mode 100644 index 0000000000000000000000000000000000000000..ec3a3ee56fd4d2ae7e44e693856424040812a3ee GIT binary patch literal 599 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgf?4YNMV~7Xu z(oshHXFum2xw^tzv2|2BjUKGhV3G|z(EtSFtL{`(yJqG-C}V(!OS7n*wNam znbX3eyQeWp^u4&jYx6?2n{)5>TE$86|CwX?9|$)a@0RBJyY%KZTTZsRT(z_D2Y&hU zab%y(>U?eTE;_MA*8gJSjf<%Qd7V2rf~KWD?NE>S-k4S9ZRGrS;cAYLoE)22It_Mk zYF(%(JhJ&5qnL}a%W@U|577yXHx$nIbzSMG5nqv}w4W);(Rx?5|HeDMrzZbUX|&U; z?$Pe7J2_oNFXYkz@g>U~GnKOxYwiD?JGpVOSyEw-W7(-f8#dLijOQPn2{k<19`T;p zyU?zVKkrPte&DYL8{ea=C8swj|1rv#xN-jJ9}~MSvwxZJBGi-3c0*jq#?I^PJE}gI zmW)^&ZCp+;mcO>EyxmCGj;Xe?&1}``msAV_p_x4`Zq67_sBk5Y&7rr z%gg7)mmP8DGX8hLMfl6fAo1INClW>7A93Y`&se|y!u4iZ1)XQF7&bB<*{{xaWc}?E z7u*gSYV5F6=-{(@*Od1`tI^n~aDVzb#znl&vkHn6SWK9Lcf0S%()#DV%lw)Lo9U;6 Q|H0Ak>FVdQ&MBb@087B*{r~^~ literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_topic_restart.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_topic_restart.png new file mode 100644 index 0000000000000000000000000000000000000000..4c42344d9c8155ad24a19cfed3b9c99ca4acdbf5 GIT binary patch literal 720 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfyv6##WBQ# zck9&MSs{)R4c}iJ^G@Aj(71#%A~GX;<&B)j(h)y+G+7fPgScPn=(;HVVB%`J)+F?S zw`W6G5|_S&P2GJXz1fuX=V9`{pZUhc&x)${{?|FX>Fe!nv5IB3NBd_l`?)pe z-5raryaOEH9-5~FUSF1-FMhoV$&++TSuKf zhE}kis+Ksg@}*Ah$@HG2bFWHbmis%{vtHV=yI_}az)OkANttTRJAUmF*pMu;Nm}_u z^b@H$z28>MW^^ZGe<{#{}ZDOAg9)IkB8AEDFPvpUf zy&1ng+8T2Iy^>@{}q;9Z(iP{u(WwGvEh6{J> z{QF}5Mb3`%Ti;K*zxCVRy8*S8msg)JeSYTIMUNseXN#ZmesV{qHJTl~{8aFpN-8M2 tzCGn}U8T+OZ&A>TC-uLU{<7DoXDC#iW$#|7b`6x8JYD@<);T3K0RXU|G>QNK literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_topics.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_topics.png new file mode 100644 index 0000000000000000000000000000000000000000..3b4d3fb87a5b8a2f7d771a55dd3a413f03858f19 GIT binary patch literal 363 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlfUUG;Qv4DsMi zPLN=2R%7}1|NnXBBc2XxyoB8udGi>P1a@$4W;n5q?NPmgqtL;+8In2%Y!mje9jR~p zVKnImr$V)_@G#IpT!-lq?*S13rXt>e zGm;)l84Y7(J?3x}$Yi+Ld&m|UU1j<-voS(kg^^Xv!AhjVbE|{M3icevD>AoIO?I#@ c?qTF+WJvaE(_^gguLOD2)78&qol`;+0MguIF#rGn literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_viewintopic.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_viewintopic.png new file mode 100644 index 0000000000000000000000000000000000000000..93b8d0ee37c55ecf0b02357012970cc3ebfa27d1 GIT binary patch literal 781 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfvMcn#WBQ# z_w3a3-l2{n$M0X=cw(8m0+VVZOls+6dZdrWp`<$KM_suk%{`ev3q5kLPH@RS`^i(nNt;Ok!~;CiwASiSMA^26Q+vyc5`$WOS`)Dy43pFMpG(;>DU z>_(#T?acvC%@g*C>K)>qu(#bTe)1>5HSK{D?zWn02D&o%vZOzh{?oEpbka`E&Z!1W zo46eV-6qVE4QSE4F?o{E9;Qt-S}$2o$TwQ)KZ$wt+hPAyxh;DdXIyEF`zX4mKva}( z=UIi|<_FJ}>wKBsEYVIc+9lHU__N-QhURlpEaHmmeT|rz*Rf8n`@#J>#^A8y_8Ut4 z7kyT?*7oWCm~Uwi|94-9pTYYN$@A=+?(;dnh*~-KW6}DL)}7`zn5Z`0p6Z?XPm|-4x^0{vGQkZ`V8YgyBTq&V&Ps@vPmZ`N7I-%r7<_F!y^jJDu{K=GMM?a(OmeW30HXSoA56 zSko^}IRaTtr7Gt<8s~CxF*$B&cU&G7)^J2L>Gl<`-39*+EMV$*ExFD~`NfBf^cMzs zEn*kyt>!jnUWop_AvJWq*(}q8`aAf~PH0z3xhZjS_5yZ}S^wFd2V~UM$cgqGRIOmB zPGt62%>1Qqxor9wllsQQDb61R-5v_^&NG@NdL}aLvCfpQ5qmy_7s`N=qNl5$%Q~lo FCID(~MUDUf literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/popup_fixed_alert3.9.png b/TMessagesProj/src/main/res/drawable-mdpi/popup_fixed_alert3.9.png new file mode 100644 index 0000000000000000000000000000000000000000..fa126294a9651a5a851cf94881a6b26eb473d84d GIT binary patch literal 1520 zcmVi(2V;Z}`3~i{$SFdR1|>}zx+onD1x2LPNpuliq)Q|Unxsw?C0$a|Cryw- zgE%P^ND-VOgdo9|5Qty%XZ$ny-gw3yk8O7C;|?$4lb&W~cV_m@n>Ro2y|viQg0t5P zi{0bqfZ5|VyNAHNASAjH1n1n+@=dAs-qJ`s*U9WpZ)f(Q`q%UNxAgvAClqtm%*>1} z%Qod@*=|*yoSY0G4al;{o6o4e@yu*rtEZl8g4KoR8Qkk8Z_b`%f|v?|LZC1xGBPp} zK^m25txS1I(c$4?RW{QIy_4_tdU`*48WEQk#fZec5ymE;l2oaRvL+lo`k4;ZcXTblPOj{zJ zCBW+&BuTP=V`Jk>?o(v5*>C7Hp|Q}IcpE{`g-D#w+A|!xqNwf$&d|`%$IPdI3_ipL z|0A-2y}iAX99t9UX@ek0Vp0;M5wt(VipAnz!C-Kh`4CvrS;ukSNA?tS1uC)@7pS&I z7miE|w>BXVzY2jMc~`azgL%+WC0c0&t&ITg_YPDlQYaMS=jZ3!%*ViZ-a?WGU9hS~ zRLnYdi$)^d+S=j}goW$tUGZ56Qvn*$@}=$4@UB1z0e2$nx(ep0L9nqjLePp>Boc{3 zEB<9Z2DJP!Zkc&lyR9^ z>WVFlF3_Weg@w@og`vkC=FSN_0z9PnHI3Vz8CktW_{IDjneuHAKNuMn;}?7k04 z7)wh_&u}c|c#i>==E?m0{8RAw4)~=dF+jYJkBmbOAA;Tny}P)$_%%+H zKlAzgKm3+J6GA+!tgL)HF){Ih%kS93CEqu?dCHd5Fra zZsyi*q z2@_eKo13d#7vyn`bn&W_Mh(h>irF|lNX>{GnFd)sPN@V}12$Q@-PKa_xtVu^QI}&v zDsiLm3QXvSvvkYgwnl5QU5-#u}9+S;kni&7=rf zhnUcmHx25wCi^l{C@(Jm-1|KDz5m?jIiK@6-?RO7&U3zT7G`*10SN&R2qa7}!ddQe z${wJ6yL#GiqHdSKBul&=sEUo4-F@J$UnaPlnu1R3+E5TU)(ZserR+jt7a$N16%69p zW$>Pl3i)?5h060EdoNK(M}V^1;sF7tYaI+;%rT}&8HmNmP4KA67#hF}I`BeS_dUh> zQZz2Ai^4xpuGEX5#w=kdZ(OIy8HGcJuz=e%J!l8+LuTeaT4|kMNhA`}mqry;$x7VO z`SDIU==bfsvN^oXx|8qJ$ZVBW7<*u+vTCX7OAyh-0Kj4YA+1Kv?(XhV#8X6!YO|9^ zfNjlrIN74M-K;&P=eXkFh{xIPrf3kr%#GDMY*S};?jnEJNL7qRaOnw!kPtzmxI=KV zzsA&Yg^zD4qv==0MxyOJqd;kHLrXGZ#^**c9=|Z%=713v@^F77)CjA)F{ub9@aO_) zDl*hRom?1!gos+e&TATD|HQ(;{PNJeY+0lil0pW=qRrW&DQ93fEJT1Q=fQ6z0hK|5 zdB;gXor*$iG@{pKszQkFI$Ul0m1LGpD%9j$8Vnq0XN#-F-np6a=2!AZH`4TFTg(tt zvi_jf_{G5+jCtpQR`&g$`HgY)_FoKT14aSr%M(OI(XoYKDceFPzcfwHLHHe#|#yWe&|zVzs^A7a7bs`t2M; zmQYxQ^g)e%D$>(lM`iIS$8|N^H`m%G)lNksvU!1(3hdqGw%?~Us=RM4;q(!dV!Pe0 znYP&gu)?iaygFF4YS*Vz1XRDskSfijQN&kQ+dZ63J`=E|Exfnoq-_4;civ~h^g*nF z7c>;tnBs>O%txKrf}B9umZjrSJ9oL1u=wd5fiF$d-V>fFoI`>l%M#J_d2Q0B@5ZoZ zyWnqbOAm@jk|qO_b=s}!S8NY~E6vaLxI>&09Dmc?&mMe#$pHwk(RRTfe#=XlOOroZ zt|Gts&N{SV8;3~nNh}gHB{{$5<+<*dPp@$fFI1!O3CEQTDk7o##E3afDU4 z;osTx%n$P99^dBSHM%}$Cw~d`!}WDSsOxM5no4WxUuwX|2xaDuFdhS;mOrF#lXnO zRB|M@X0Flsp1;|dbd2WBH3QL<92=rq&(vj-#9X9iuZJstLoc7X9NZ*v@yX7CO?>?|O%?UL)4A46gSOw-md8}%8gdqe zpGYQZHl%a*n;O`0?WVKp^~3IAv=nl|#3|_NY+SC?%hVs`QAmgKn-?S!^|_#S{g*!D z^wTA$WCkjv=7QXNt%_xp$1_>45K&(>6w4xNG{xPo^07!;mf7*!bF)t$OTv`&TU!7= z(@UeR_o9-reT0j@Kwi1|KhVV`@QUv+%iJ&O*`3iC=Oh>IZ=u$+4~fuH z3#UmVPH3ld$ps`UKA4i4cjN91?Fe5FQ}1r z7hcoiCCdy8seWPk4zauG`^8RH!&Ar$71pY;WZ*H6LBfBRMq~mV{F9*)+owmm;+`wl zDt0cJ+{WuAiH#)DpQC?P3i)!k(hZWJ)9S+%c}A-EQs?T&-aL5sW^#}fBKwuTNZYJ( zMW2@h2RjsHKOfVdFb=C#Ra=1t|3O+bZ-EZgn{IR>sh{&)td6og-S7v7Sfm54FpFVT zDW87xep9-)OoKSkAzf`;qqrgUlOkGQ5O=wk=VbDVj{qfVCPcZow0z5snH&a7DhwxA z@(nzy0kI^K^7l<#F|i!&W_74W)D>v+^sFrk2Zy0TMz;k+s98O7o;#>P*+q}1(qLD? zFbCszP}0-z6FN`5(>r~k<;UPPmbftjqI*QXZ0xleHGWpw<)C?|%@cNo(ejEt#s zELsK58^lQVJ>eQI5LMQ(6~bL!jg6T3XF6eM2AHs{i}xQv-YGdkG4q|e`d5MSvZOB& zFY5)dnlU-z!N-Qs;JA)?9w%mSAE77@Sc;@H`(mPnHQG{JvR;YY4rQ3on~o~86Po}W zo-$+K$PHP{TMbXUC(8W;=BJBWi0E{A#DFWOZNoH})=@wQACmTnG0k1OJE%11q?~pW z9xS*Sy2kpdMp1HolrwPyE4AIa;4My<^2-91n3okAlSglUV_X+llK?t)Z2rdq|K~~n ee?6^6W8q?R8ILbl_D}77Q3wWRxGFv8$bSKd`w8y= literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/list_unmute.png b/TMessagesProj/src/main/res/drawable-xhdpi/list_unmute.png new file mode 100644 index 0000000000000000000000000000000000000000..136be0e40e63a43a7f13d678b895ae9f182051e6 GIT binary patch literal 403 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlfU{q=Nl4DsN7 zI@OTxkO7Zt^bw{tJUkmh=QK(ys2yQoj%nfEU}Ugqv%#SQf;$+eH_Yw*pd%%+HE{C5 z*aMf=OCSGxzf8o`^WV81LuUh4*{{zO&mNHNc$>r`W#~DHIZ?cv@zK#L`S8CzLJfOZ z&q}6TEqQu(0mG9?#SDu-J%8vCz|f^TqjlvUNnhz@mpI>7U)XNQ`@U>}pC0Qjrd3mK zzl&v*4*t4d^+V8HU%zL4FQVEHe3x^uW6w6w;OLFq&TY6?RlI<)rmQEh(EZ6-|H~mA zX7_y8Dm-2OASA*=E>3NR8=^ah!;oUp>zwln$ VQkL`0L#Y-NAfB#%F6*2UngDL|is=9V literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_discuss.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_discuss.png new file mode 100644 index 0000000000000000000000000000000000000000..6c66533f8806313d11a04373fe093bc6a27f356e GIT binary patch literal 1451 zcmV;c1yuTpP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NH6-h)vRA>e5nOlfeRT#%Tjx#b~ zk(rmouz65Y6MaZ2QNasoRAz=K_>zRTS09u>kt~hg5-RnQ(09QS<(Ed{>6z zD7+3V1D8M^^d~_kr>U;2J3x{ek}LtAgCQXa+mO2{a}EA3=npilgqJ}W{e@yRcjQo* zL25b04+aN75H%wF7R&_oQAyCh3O)rpKr8Um69iuYU95bFVAH`h;HP~E^dCq?_-ZOH z1s8#z_9M_jkc|kk7_U;VWpX&r25mMXyrxQBnV}My z6So2A;#fLQr~EsC(?syVL=yjTqDnFtkwepJlgTUg7|?!_j4&Pm>>o^bVOBEl>{jF| zfbGE2&cu=N+G0K7KTGVKlOZ{Kf!)43X2gcaVLZ@M=4ex_Q-usDpbuzrh0tob*6trp z7#ubZH{qi;p7209X~;j<)FXCzXlRsUe8!7Hjh~@{nzY|IY79+{#X4t2dmZGr8-`|$ zS5SvGQcGh3y|DP#Sp5)aR0Sb5)d2e>u&$&G3`Uyb#YR3IqF2ygz$1{AP7)w;$3_3W zNpdo103RBIhwwd1jEn&NG-IC!yU~O%VM#E@B)RVlY?1cyxS2=VC(bCOZ9?82qDv#b z5aXnLj_MgqKXrOJ39TJ~H8v(imI0n2$V?<3fh^BP9r$ZX#O*i!npAJA9e@rSj&u-- zB?hq%BjH)lb&_bX?hrOWN$nK6ErCZ>-Mm~8zE(B4;-zZUj~zg>1Rf1`%yZQ0jfbVn z>evBXG1?)_Z#!C}sv2tt@Q5mO?(Q;xv!*mXd%tD+B`gEXvJYtQ_!-y>Sdzmhm}vT! z`2XquP$XS(a2YuIx~MJXFv?>em|7Y+SVF%4Tw$~uoK@!)@J95TQ_#*ad=RT;tu08A zP$pmh(irGU!SA3LYLve^zRNtvP*>y+r=*?Fz&eeM{3(n5G?>|-)~}1{KSuvMv2CtU z7xL^tInOiDpof-BAt^EJ3629t7hrmOj#X*MKgAW}wO>XSg3%10!>UM!3M#2X$I4Tt zo@V$yu_-ayYn=2>=!00N3K)=gv#CSuf<$RB90Bgi!z1y*hloTG%Qqt5T9@2w50n)J zCp})m_Z)i99QTrHgdRe>%YF3mXN#ZzZcI{!3t&FQ?v5y^$&}j%!S?bE$$P zbRv(=y?Tvk=^h>J0qF?mqbAyq>7qn?5&vCq$mDUX-T~=zRXW1C!A0`gPl6($D*(Ok zUjSYYvavF3v@}iz>w(>K=ysBxlj4_40^0+P%_DxH5$J>5UekZdl4LB5&a!Vi9Wot; zj)9v%&+HF?{``Z04o$gm70K~t=1W#UVv=_c9o)=n$F^> zyBKvrZ3)plf#&;4(Wj$vUdwLk7@6q zUkf&a(Lk$41JDsrs0Z$&pS~5hfWMh0XK3>$iti7SkUsx>1769$iVP7cs1E48Xb{~( zs1-s}swP94++4$^kiNJt1?`~G!)b22R+w(;z5?b7{0~>NX|zIhkHP=|002ovPDHLk FV1iWnghv1X literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_members_list.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_members_list.png new file mode 100644 index 0000000000000000000000000000000000000000..f11083410913146bdb662f5114a78820d6106c31 GIT binary patch literal 1382 zcmV-s1)2JZP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NG&`Cr=RA>e5nB9w2RT#!SrWAn^ zHkoBwV^F4jYC$D|MtT)qsJG3YqHgWuqN1qKs6U{(sGzIV?$oaOf+Wi7#?&O0oG1-H zNFjx!K%LX?*=MiqGwbYq_Hf)W6YLkBS?_wk_uB7XYn_=grJ-g8nicq8uRyEr>f7!1 zG-5skR)D!#{G*IJz}Kx->wG4gd;`TN;0P!q>4qj<Ouw5we$?iL zEH)QV>@j6HD@-(gwzU6PrK}b{kKcm`<#G6W}?C&b0ZiMES&q21 zo;Ka>k?*5HmyZ~1P{fQj=8B9#ejq3r^M{j~dC@&cTcxfNJArSTOjG*Mb0ATXf%dyD z%kg?pGQJk{zK!~J_FFKp@v!l3EAbOxv+2cN36_BeGoB$Q-GiRYXC213WX~1MT;{eN zJx}$FJ)sihgnTAAW%}!Lr7`G-z!9^HSzR6SwlP<|#kppD5`IRc_qV*>bt#X-5(^;q zB`^SP1)qUHhq|0ifRR4z6+kWdK?d>5LHEZu)^`M(K+37ys^R4zWE=(enA=4b#rtMh z#;&~RQ7=crRb@#{{$6QTjMOThnz6Izm^@$VT$6$vF978#qE8UctW1T`Q$P=*zCQ%E zfO{()Yce3$eOXSml)eYm5`Xk*wpJZE34SAS2EhREovkRmE_|oJuiS5ZXDPD%?_?CM z-@(s;{h(j^2}d20eg8i0dIJR033@ZQ2DtwBuwvNgKC%@o0CmtW1X)A;9~F%FdZY8i zXVhjq2}ZCfzOt)u@FGyp#iUa>y&5n+0Cb(Gn-ueY70jru_+UYTKalAoPiv8FN(g3y z??D-@ks-=C;6!AkHqZgK2vqgIVoc3!6UMmxjw8D^%e5B1j8yCFwD`<;dx512Jqon? z=>fzTpA&B*>$`vPy#l-goZii9nvPJpRYSaJvR4bItGZ4zqqpE^rAqsQYyg}$yB+ZU zjhOZLS;YWffj~L~u8t!hn+3FBPHXi_KQ>9{sbX*D*d%5r_Lc&(zC^@Y7{wkc3xu6_ z%Qp4wLNorz_?W%Z1jpG8ezy4~vBWw^fD;x(zlFj+jtO=fUl;2! zuj>)iYn)NJYgnNCw=lA^Ca;V2n4YU=d_8~;5t06H-|J}i1LQ*LQSTp*>3p0;HL6z8 zh(6AFK=<`WIj_CcZ#r^36wl5>ME8+?Gyhacp*@zpj(eJA!ux!b93t+;ajZo`w(Sk0NC+g3hi66e^k zv(fu)^&xe^A2)>UdJd$|`=D3w+e>e!-);^D(-X}vax^2wcQ`cMWPX06^d9d&!50%N mS>noCwRsAP+V^=qT9g0lezo-PGmgQa;PG_zb6Mw<&;$VVsF*Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFTuDShRA>e5ms^NUVHC&b%tcK~ zlMo`IOj90^OHmUdnU{%3^QJuT;>Bfn5X}Q#NJ2?2FAOOSQsYhJK{5}cJWXkg!Q?XT z8Y6T3)@iR}?Qb^rcJ?{5X|MXvx_x`C|NiDX-}mjZvQ*L;NM|6Ofpi86n1PIaHk-|k z!`}lY+jxoXSM)oO$z+~I?#m0D1xb*$u;k0b4ovE}N{3i8{s%D9OK88tD|OQuNN3=G zodMrLCNIe$umn}eE|lf@P7b0jsaH~gd~-~v*f;7dDg}A>Ad}gTL*#r}7)PN$)$lDL zX04tFE`marPzN;{SVnTM@d-U@2s8q@clCiQj2l2FTOghRJHQuUr5k5bqS-^R0$j4J z9s5SoU*L>GxC{R@81mwS=rM_W35ad-NUA!AQ6*&2vV2tLB<=-ZHU)fAhUhks-}$hg znB3eXzCf!X_!L`qpqyZj2D*e~SU#s?B5R7&gXdB$qUgrG*mYk?)$WXF+P2UL1& z?yqgCUDJvY^wyw;Jb%K}c!Zr_Ix-&rs>z=Usu4Lx!JVdP7*nnc!%kdc<=~|$u)&fc zJ{Ej6`PDWb#rB=V@*qVQqr#lTa%p=QbZMi;0&@3(h(RKbVMf7O4C4j_AVzSnoz+)#bv%X#^q1QS&`616QBHf>*X;cMHIt0{} zo2lJlG=2|bhE~BI1VSWU`5vaujDfcxxQ2sl~|bKkdT>|OPiH; z9NCfN<|(tO@&@A=NY1w~WacS5Paj14PI~-Vpbq4|$ihra$;Ln`1B7%Z$o&{g);w*L g&OkZ?|LzR@0ASGPGE6IwWB>pF07*qoM6N<$f?m$NbpQYW literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_topic_close.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_topic_close.png new file mode 100644 index 0000000000000000000000000000000000000000..6a467f15781897638b4638289fa07a4799eff6ba GIT binary patch literal 1179 zcmV;M1Z4Y(P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NG1xZ9fRA>e5nafTTQ4oe_1dMkA zveB&zG2lB0YE;~SVl+WF#+ZO_06qfv9xikzD!c(E-gb%@mBbxU6ET6!`291}oubk+ zeY)qyVk-Hk&!tZNb?%ob6uQ!FfreV3*ih+&!lhD4Hcx`nRY>b+6W&<7rY4Ukv9P;d_PwzV=4-U92OS@Ib}(|Ib9X%0X#2+31$ zDS>R8wAbJT_zI$NPcWVjX9!P#E8r;bvP!~j=E*Og(Qq8+&w->?e@pr#I21}ET`&dS z0Wabh4z(4ehrx^Bk7N2qdh^FB<+Cnq2Z`(f+b)@lY1byaSE?mYlIhb8w)H5hp)s+V9~v57}8Q{ zMP1zVmfy=-D8SJJURq+p*_K!eXp^!-N0Y2+W8ORM<8WzC*om#76u#M^tR2?Mj3;Sz z9P+!?0m5^^oG9Dlf!6ulOJy*rQxqq#`^36aNsdzJJg_pEtl9choX}=xWh9jqZJjD3 z`AZ;phfOt}Q9;~bJ938aLaGUrbli>PS!#h6E8xiU$QizCb?H#hxZ5=a?$<5QhD>~U zVEozflzv?wLZQzn=~UTi*)R?qq%ykg#o{Y)D$c0{bRi&BvT-euq>|`; z&{Z{AFm%;q0H7?Mla=G3Qg=NxX$)yT&=)VLL6>haK+A@-db@L#X=b^fAt@ zT)<7VBf;y2^@9l~2kEZ;bd)%ZKlUgwpX{|srGQ@9tW25gwgz$HhINE+`e9BUqNo?M zNOYvFyOgz}R&eT${62DmZ`g6VS^+)xS?R=u=ALzc@N95Hq1+2}jf+IryZ&GU4JwKY ze2>WaegffF%4#o68v_cq^HTrHao^0^e`hEE+b!EuX3NFs$*Y zKR7NYRQVrinsCcN*FYW31###|tecb$X3-xUM+kdaA>mF-{@}1oR76Xx-6x1PBwFY5 zEtS@>EMW+>*=fg48df9+iOSA`Y@1XTM?-XRU<53HHIVi=x`+X5YJf&b=N_g=I$z-|Bl002ovPDHLkV1kn64qN~L literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_topic_create.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_topic_create.png new file mode 100644 index 0000000000000000000000000000000000000000..355acb27b7ff83102e46a17a23b6ab2e399cc7cf GIT binary patch literal 989 zcmV<310wv1P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFM@d9MRA>e5nLTI}Q4q)X#E(G4 z!dQr=6j32q7z7JJ5JeFo8b!gzDqI;G3kwU4m7-OeSXfwyDMUmIt7nS%JnLXb+?jfUQHLz+6pK>JC6*?0z4KeQl*C#f>WRp`k3U}7aiAd&VC^wP2wz9BKVL_0KV?# zhw&hCb}4QP^SvPHJurixZ*kwgV_N}Qrui1%1fOH#ZOdei1KSbGF^%M*R(t`*qv5kA zoPT*=mz%vG_%22R-FM(T$1nBKOH}|#TmVU0pmenna2li{-&HdOSuhDa=6!7S3#EMM z{6}eb;1(Wt0y#pL9nRudSOfmrCne_Da%igRj}D;l!Jiy z*HljP*1K7h$*EnxmN!s&B`AXA*%h_YAFcxANj(axMP%v`^Rb6c^MP`b-KeXr>jd@# zNo=$3K)gB(q* literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_topic_restart.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_topic_restart.png new file mode 100644 index 0000000000000000000000000000000000000000..dfd71b6e52c4aa2656ffa11bcc5f1ab0154e1f67 GIT binary patch literal 1486 zcmV;<1u^=GP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NHI7vi7RA>e5na_(>RT#&ew8#<% zm7>~7NQMUH)9&OJZxyJX3tEcU=aJuotmcfxsQ zW=5QAz#Ro-^=pBPBO@a-%J^RiL2m@FfX~4+s1lXQW&&*dPa8o1*Mk?pInY&p#Bm>3 zwxErn!pFf`Ff922qFr4Y(P|D*$!aQj8$6JxY>K>(zz5(b5XL=fZ$8|K{s?#&+zetl zMaFLC$)&hp;55PC4N|@SCHXtRjk%S`7L0+fKrE_beJ)p)9fbFT-=ms-COr;Xy+yB3 zz6FQ4TrQK??zWwEh}83O@F&Py*_kwQgliOt(?yLK>`g$h8pOlqFnQNSF$|m(CpZFJ zG`UxGCdc`dv!NgEOqS_L9dRQjGhNGO5NWA&(JQX)dCFIS$G}TqYnd@Gh2K(e61cQm zV)4`_h zKiltBpM%d+#vyfIy@!=z?8gJG^R-JQ<(q-alqU-PI8SYaS%R&WSl=hk<~rU}R2UvR znjA5V=gUg+js*gH{(}Bc6JIFDR-2v6hEP`d#9F7yAb+AX?y%UD8W70Ie-vB*F4suB z337O~WBc2j2;7xV9CX|b@=m3p6=0lffJ`X61-u^!?fEVGCZkEJ*t}~#XOc!C6GM%= ztLCsLl+``4uWUf@!{&LQH|nDM!Tw;AdQ$$JIZUT)dU+VCw_LXN3TqS`8XF*Y?AC$P zUO}P1Tt!d$w#Z($y`IDy)9t;IIYmd}p$W%itt4B(Yv3OvK1h(MIAQz{{n0_j+4BV^OknCc33Z10|@*EaFoc7gHel6jd{k>v05536qY(U zZh&6nRb$Yr9#{x`EhL|U7bCJ`q_@!fzeV|;^cbeGRgBd_Wy9XcbYH#%Ty((OT^+@I z33l(9_+8>BX@x5#fi0|jnubvbfrKFrA(4}CEUq9bkXQbzf`!MNi22l-Eiz;d40>yE6q z6(sLCT{J+S`&`DGD#iFScQE}M^r}s&k`|w*jYI0*O5?Zy<7GhCxFEXT`O+7==&juf zJ_!yybx=DBmiHMqWZ*Lj&VwM|qvT&H#gD_T;9Wc$FZG4r2JG(xw>89>EXGfl`VWDN zA3KtM$xIj~odx|!(r}2G)bV;$r@ps#6Npv;|2iRVeYaWKjj`HGoFEQf!?^p0Ema4> zdT<%I=(|nroTv`9zeHRh{uZPAly+EcQohcUoD~h5?4EDcdk||p>JN?w6Fu-3d75x1 zz&W5(qo6u;B-W`?2ea@8$4%(5Op@^g2e#q=!QnO05G}F3Pmml)w9a>1Dm}0^Iu&ZO z(~g}QR%B<1eD{KOn^c=Xb8HDfo7V(51GEEIiOOVi0OTFa8UkoF2O79~#RRv8gvPue onVk=fmytQ!#oxsqSjZmu7b~NvyGu)C4gdfE07*qoM6N<$f+yIh^#A|> literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_topics.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_topics.png new file mode 100644 index 0000000000000000000000000000000000000000..50ab3a7886543d78c200fb81664dd381ee42d656 GIT binary patch literal 625 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyy~ySip>6gB0F2&*1?oyY1=X7?Q#I zcE(08W=Dax;#--E4~89V*cNtcMMrO0SL@o;4~lD&UV2B&-qVnjvqgYqa#WzaVrPcr zxqOwz-8W5jUdb*$bKv~DGv{*u{oQ=$T-?f(G_Dy;%NjHjxc->4KC*~o*S)}8YVbMz z8plne7Y$3Uer1xF!{B?tUoGv%d!z4-&mR6z$*uPIQley8J#Wt%=l)s8XYf7P-w<(h z{#5-u={?ntBHQx5JekPX(cfb_OIhKu>;`Gqi>A4!PRv%_z;i5^`KCqevb|02_8C%6p!M5S><_tZuk2f#h;-mSGQi^tDhU} zF)>^Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NHT}ebiRA>e5nOlffRTRhloN+?3 z&`L8YXo93GhBA>|gd$R@q!KIeMTyLyrydL;5iPqt6?V~w$}W^I6+|#TM1|=?76@I? zGA+|-(wszP5<#7Q|2o@Q-#L4KXMg*g=ph&U*=w)8)_<*Y&i?kH3}%tL13ky&ZWbiZE6JH1&&)rbWN46 zHmRcA;q3;xIaYSjY5z$ObR#%wn55qwRY?UWa%ftuF?pqK1$v*v3CTB0uB(21X>b50Mi0) zK{GL9z#ia4%eCJAVZq@G=S_P6w0iM`i5MYvuHib2enZOu&mb}L42RT5J#PP)7@U%G zelX}9F#wm|A68N?Lhlg`SmnHE_k+rBLpYu>oKoAq2x9U#Vv6@7aNMB`nY`?E zN-)A#=^}{l0Cp#^s!#?FR{$$XyTbx;W`MZrMZ9E+`?ipW!8?t^G4zoIr-}wRlP`kU zdrW7owLTk6jeQaq@nlY~mp?02Qy@P(^3z*LYk7UDoyY|8}n|zxyTd3J%21XNe zE-?e43$V^6Zdiq29=*4~8N=2j&0Rzf1%2OwAzzbj_<)@z`q*%y50NrighSQhT@GEu zLe~kj`>_w1u?f=c1spVkF)yzVjei~L4<#h3$$mi&$lp$(K0`3c=w6k_y_#v%%bs%f?=9lW%GV&dH$9CCS=%XgkU{@tI@d zv`RMDhheI5klIs5+Riw9$;B%V@z1HKOm-AJ6^ zi-LpZwUbs$S}{0afvk?=r@NxE*8e@u;4L}F`vP@Enc>oxUDBMl3E)_7>e69a^Za+t z4Cn*oYvcSAdh%LN>;l6jHR=cJ>QPI~%{h)I?07-Pd1fx)@q$yN0iH{MJ1Th-y>zv) z)cALT?|>6M%`w);tK+o6MbPR@=nk)Agpt^BAokF=iIKRqz{>n4hfs%z%fS&7Dc%1O z5hAH*C*MkwU+TuTC<)N^+MdY$N?(Vx|GJRpF<@mkozmSwp)i70SQhkg1*aMf@Q(%W z=W^awIX1THsZJCjHvgUF-rUL7M^8Kg5klA`EF@Rx?|jiN_&3;ef%O(u_1bq1ur ztI~wvZpW0hbA3#VHGtOp`QT!okG0{WrEvy$3fOlJ-BY6XMfq|GvE5GFJkl2$!yx(w z)Bjr~$u%gifa}~CHhm0j1qXok>_>rq{1bt;-jl)Ez$d#fE@!L$wr?=nMiIJ8_DJtF z8_bvvz+MM-m7<$0KN6{@*y@Jb5?bpJ-JdIE7X2Qu1bmYlb6tC$>TIproeaJRZU@PQ zdpAW8Zs8JRzXuNCyArGgR|BmY-9Vp!f=({npMLtUxF2k0nv{J);+7y3KOV%0{{DFu z*iXZRj5UW)P#4g-s89aDs1-s}s;!M#&1IgV3r!(yz7~TMK&QdNHXeJ-aSN`e<^OXB Z{sIk`@=EP-_kjQa002ovPDHLkV1oU8x`_Y) literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/popup_fixed_alert3.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/popup_fixed_alert3.9.png new file mode 100644 index 0000000000000000000000000000000000000000..3509bd3baf7e768489de94ed9ccebf693a3ec274 GIT binary patch literal 3809 zcmV<74j%D|P)A000iCNklFDST8Rx+l;)_B;h6j1kM|~m07{ZgW246H$qYwI^ z;FDHje9;(!iTWTR;e~`iGQr{l&`NEAq@=ZhmZ9XjzjfBycb#?5?LDWow|8WJ$;v)I z`|O{!zq9t*d!HZYnh)hyOd=v8A|fIpA|fIpA|fIpA|fIpYNA{&uY<^T)_LBvp#_d- zrlZryNRNZXEIRIz`3vfuZ+hqoWHgRd;<~eE&%Vanukp4> zDxMsd`1(4Dk7Ftg-H7(Os%zDD)_zDgt4wA;RToggCryuSp5wKy(^7E@6#yY?`*5sdCtJvM=dM{;G~5fn-XT znkYcfXO_O35*Z{26_o~YX@#|T*`Q@;XozJ0{{5|fzZKgEStyR2)5kcTJxAyHm`ziz zp>yU~2y3OO(@~O@Zz!Xy)Hg*i)=QPOG1i0dsUU)@6kd}+?i^AAE3a)}V4%&1B_-|s z{r&CRwr$&pZ3%GU`9BeJ;eQaW162bLUQ7SJWLM zB!O-q)>2e8dQ%XgmT8j4SN(Hne7WmX!;_->`uaNjcBhw3EM48*-6Yr+(fj(mjaN%- zJe!`4UrVye#uCb(C43FV>Fw?9gmB#qRv<4fe@nemuqw! zmA<4C&iS}o<$6hv^Vs4;NY7>uS0Pd-TcMJ4V@~-P>IO`85w&j2wSW{vOxk)e{jOqG zK>%s|EjMo5_-Jx+a`?%UCx2O7T>STo7cXYZuLw}rPd@qNW7K_mdiuxE1$4r?fsUXn zB7|cLftyNv2xTDv3d_qUL-7Y>RAwZyEtY_tKXHWGz;3nxUAPN;oPDVHtJLUuftz7& zZtnLZBO~v-#7cq?)NPlAShRuhfo+QBxr?0#Qh&C(*pv`%*>p# zm5;~<2;uP_bmx*T1G+4+e+3;w*EF&))?qnVJ`tQ^qxb}B2o^{HEAZgKgYQNQP#mxj zr`8Z^~Q+|C>QG@@)H3Go5ZzG0Com{fV%?pb-mFFygZ;ompGx_Jv}|J z4)7fU8C-gZxg5u_7!h2Wbz}v&uObp)RKNx89v&VZoS&b+YU&iROu(^(PN7?ubQMGf z*1gO(gzmarj10wyAPFOcNn#R^PvFv)VC(be&p%GpB;uuk8wgpmg;KoBYp{CpQ z^mGyACjy`{5K{!Rq)D(DvxPJ| zIDh{92k>-l!6Q}{7=fKSb?PmbOh9#U8^9{i9D|G`%K1nD6eEIDLq(S`umtn8Y11Z* zb+^sV&i=s`I%0J&Jw5&FjT<*^bBRyZ$qQN1MWn1k4&)<3xx%b<+CjvuAr?~5C7e5V z?n7JHh}Ff(lP3@OcIf5BEEz?hZHf*h^AUkjWevP^!h;1fqcvzerlzKT>UF=JM-j4u zNxnkC*~a2TfRo5xYP(nizuoVLi}!6oBUTsi6`*^xfwBs%?8wW5EqPF%2%MNmm^^X` z1L3u9y?pud30ugB#^B18D_=z$D61gd11(uoBi2WKA}G)V5>IQAlgM-6+b1U{e`X69 z(HPvjcklaX1DA{-Fyn|WTZWQy1J^?_BCyG!Zowt1iqtL4&CUJQ7BHeQ@V?pK&<5N^ zqfNw5DAGL;5%RJD&PNOU156-spVU_>m3A&HEZnjMjA#ttoB4Lw$xlI{t0q6etr8Jr zA_;vD`XE_Tz&LhSTw-23K=*BJZM*fH0=JQ#a;}H^L@4t(RIf7flrl0!-?ZBnE~2sU zMA*Ybz{DP{Kgh_ztrQV}a$;V{QREpcWVWTfy?uud_t=6(G!~u+1HK)0zQ|*}5Q{+19Po%?2MS>e7SUMvb{IfgI5Tok&o$OVVIs(g0h!T=!Am5<8pF52UbKZ7 zImk;gdDb0@5rNH(JcT4g*lP zff!Y5R?$TwtU1sILIhcS!cDu<7J(8{^9b@pQY1o?ptZGiA9vG2gy18TS8(_!j1r50 zDNK4a%|&OiDd5;gaEX-;>kTXFEHZ+#$o)@h)DWkb*ADzlLTc0?J_7uMa>dzS3iXMg z5`xpAM-4QyhzX@<&z?=#0!B0j^Yion^zAXgUBec6#2sx_v};%roRCXD&75rA@K(W!;s#zkgW-~bW!jE;`}z!or~F&G;g z`#IVGZNX2IcgP#lt7Q?wGBmF~V(pujp)Xy!^fg<+h{oXJ#fx8;WoSIEhPD9_)JHW| z?kQ#@q`rSZM8M}>_+f~hUIwGX*m%G-P#OmVV!-s$Lx({;*E*Uz~WD>Vkp=Id25Qi}gA-PW?KjHZC)VzgMd$FN3#k-TI*^cf<dTA!F4m@zi4VQJ4r-zJgOP z#PZ{7^oYAJ@S7VpYRfEl5HlptW8TNw=SFc_jURYT8kEvfo9V{*`{`cn1n?G_13EqTGS-14f zMK7K5*&O>qOIi7K*sp{9L{N}~={ZKnUr*8BSaeC>rO9yc%$YM^o|>Bav#o4IHedyg z969numyiJ60W1M@n)mWZ7Orpd#^XXlfCloAvYtbbyUBG9+6EyhD z%h2i5r@sP1L<><3Sb*ENZ~u}B@D6mwJ^?HNblJxwfR6R2a*ECySQI0I2p_8DFOX&6 ze-NM_gHwa??tn}BxP#t8ppx&7jEsD9baeDrj~+ex$K2f9zo3Y;YW{zLy5eR)-LGH2 z{*wzAE_}o5+V>I1 z#LLW=1#vU;%E;sMyp0g`{Vc>c{*X&_0^RJz(y|Zxv8u@H2wl)A|I|Z&hnczrh-#PR zkYV}YjHLiX1{qe$90Na9i^VCi4sVo7C9x2gbAS_c0Cxc#OTP_;46$NhIcT2+cMHx* zsL#=HeXR*o>6lQbRh6gN&lJg9T@&)i$RG8@T@QJSbuh?J8W907Jj60T7WP<&9qJQ$ zp*yheEiBU_6vq-K2nC|aCjC`bW-xu8*GmsS2EUDmKrnkC1^U1Nm)r@k81O0f;W2IA z24T=wLgZZ7mbhB&S~uT3MBw-P$8^WyDrocEDGWWXhL--XFIm-qp& zo){I#k2|zK0G$#E^ym4}7IIU1$!eR1feL}bsZa0)GBA@-8H_5#LcrYhaksIZi6PR@ z9x@)$hCoTOP5W|Q#iKZQ(L?stHVN;i(vs_KT#0AOp`~A6Bio3NZQf7C!9J%4p}KPY zsqwiB=ynwXbnwVPNI)OoW*rL=vi!Hk!DJrT$RQP!ok;y}r;@S|s4N5(hX^_72Uu)5 zQa|uQsE=upYxta;uZo9b=7(RYeDuEgL0GN)Q+ep0h1u(HT$L~U+{;ThJJ+~T!q2@N z6M*#xT~c@8D*9fV#$O87QBkagz&gOB2_a;t34v2dzrL&S3qAd7z0EoflqbL5gYY~C zZOfn2YNaXZ*<%u4Unl8j#mf?VEkY@qt5`*9bQUx`MDG6Z|0Z;^+5*@+zp03z`zHxC zF-&(sq~MCe@eyL+4uA+jARGDa5G93U>Ua?O!KOJbKk-~j^fmhDo|z)~+p_8WZJsJV z&P!eCuE$7Ae(fit0rkt^)ck-ULi1S!4O2L6!XQG56fsz!1QPqJ-;7`J1~H_PG_+zv zJlR+ORIExbLv)(@x~kCTC)dihKE@(*0jb7!HB5iK{icO2s1O37GI>G_5>bfw7<{}c z=`^(3a;y3g0!DXVJr*z_=_D-WdNR!Ax8 z{XOk-Xd+R3+15nMO4A&-jiC3loZ(vO1hM7Fv2AabuwCQ)ryEMCH7jWM86j?#^3|~{ zSOsUrlVg@dsq$o$&F%~89yfIf$jUb&A|fIpA|fIpqB&58qxuq(BoSq;r2LZN({TR- XV)(%EiZ^cg00000NkvXXu0mjfc!)5H literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_chatlist_add_2.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_chatlist_add_2.png new file mode 100644 index 0000000000000000000000000000000000000000..52bca09ffe98faab338f076c5aad885e43b0cf7a GIT binary patch literal 3776 zcmd5sV*A;JLWiG@o z%?|>Bge=aX?e>uM+n{`VYrKNi;vRs5?95F-^ua@OdmA*#$>ItY3p%prLqXuXo*>9? z%AQH@83@Ev2nO-&A^3N!5cpS?Rmk&i|2L5{uj>T@?LT6HKI0GqUd;C{cQqI9_VdbR zjY`*_&^v;}YeZp6JTu-DdHUe^y)c&Ny&qpI=~U!BBlc>3pSwFm1aeFP5tq`VCv9=o z`5ynfcMr;+MDgF&Ig-=itkES|CtbZO&Q)KZ%G2G-Gt>Z6v{F59{oA6Q^2ln zx$2t?fT6}9KsXWvQ2_lH7ju|VS9kpZM}mEceT^Anz>%k%6t*;AJ#MXlChM3M!etQF9|_^SQ>A| z?&1=Lz7Ee<=y{LkO+I$3JCaCvHT;1UZ7dmZA;@(hG-jTDk2w+U#1B3SK>G#c?;oB~ ziTr;bIMpy9;BI(-O-*m>*p$}OyJ7EE_x;h69(FIo;~vhcce#jDR$JXG@guHjckb;4 z;cWN;fuzU|5?td@rccdK=4;4pdV33^wpd#Z+JE*gNh!ckQB0CLD+r`45-js4ETDO-XX9X5b&7LU*3&rA(FD>yG3Mpk122Wgnx@WuLv0-^se@BSctx|CIfTV(J80IsQX#k9rOhK~LbFW%MW4tA&a-=?pQ#Qf?}iskOw3 zj%nO4$`3VhXTc^1qj)}kH!n}wjFPLf;ho8hn_a)DoP zVb_b$+{`LDG+|}+82e(-IrS7{rP`{OoWK##9=h=8uLTQ-DY|QWyZv2hi*2USeVY6?o*p&5&#c|=Hkj9+-@5`L| z-b5My1u=nMzWUdu$U6-1rRrBt84L0pQkoY26!;hw$Z_NU64CloFzwDPd%o9_u(C5c zD&cfk52&`#DomeU7H82gvG&(|DXs}4aIibohof$l12ZS2P~hyU1KqFZdhrDN-K}O_ zehIDxAzyl{2;EgIb+d3uL&uCEXP|BZ=W1rQ#%Tg;nOc2S{&uM4h(6i2kxogp-G93Nt>$?)Xy zydH|li{xgB91&~3*BVgGRwH%2qE+P>n-s;KSdo@E_H`VW>ig0tdjH<}_Yaqk#533O zy-02qb*^aC7O~9BXL^GBO0wQLePU7fo?e#?!&44qC(Ilb)6tqSP`Bi63VRS<|RkycWnTX)LQLynmKWm>SCmM z8edWH0NAvg!uGgEK2A5hWh)wRr+ZqbUZAb(B)Zwo%1GodTX7?kB~-|UfekMQoZ&Ei z==;}#0NlD@wpBGYOD9S>IQnX}A*dG7_$NP8UwbypUZf!w7&E<=#ymGbi@$9Hf0~ZJ3pS zxFxg#@Lyt9fd@e-BbLfw_w#Eup}gQ5eqT}gjvEKo4wIE7aIC<}iATRyGV?k8hwVHB`67%~MNYwlOf?bes zTEm;qcU3-igwig~%+^6L!mKD{`Jcf8-8U#NI^-^yOQjma00Be&iO5Nj=bkKxxk2s= zQHaFlz$Ug@X$z9fx0EXi;dU?u?S0$$kp>kR90zk{C?{0i?>$d9Vz%sTE!KIC?>ar{|{=D*Ta+FTwx43850=LV!Jhi>b`JFI-|AMn5E0=N>+!u&Ig!AZ2HVKNR!B8_ z<((X|qD)r*I_hhKShjl!MMimMEaLRQ1;}!;>Y&Vxi!)>HFQ@gSdv`=xeyYJwV3lyl z5pnYwThjF^G`I1wt_Ddm;97EBfdsWrZ19%!EQXxouAHYsU}A6O956H+#2`pS+UOhO(7GfBEo17!YEw=c z8g&0_Frt?B43WruW>akC19|i$W^q$a_w>Rvp3qKDI>MP;v$Sb2UMNvdbfpsSPCI<$6Eiu?cCjx z$W1u_CX$WUM5Y@?*qxtJ%GUQatC{{;0E|2$1&Q==n0=&W}iw@CWN3N#WYM(LF2~sJeT-_cg zQd|=Osx*v@8!5Q(D1;$G{^+2@zJGJqlugjRnCS6d9N-alvdg8M;$HSfv7kr{I$3^n zE4ZifFh9ggi4Z-CEiQ=`=s#phq%3bz*!J9q0QDu5EcHp|Ea9_@;&@q>_g*U`26eDd z$YCayqPeMV+F5L0C0?LjIOKZfH3#YiCDDgT1Qap2*Q!TNSS_E~Xa+Nr%+^DsZJTpL zTlX4M5w?XCFQ6>Ss`lA7FYbscD3#Zzv?B4(7ti-uv1N!8Y8;@e)&&}VmO7AOZ(dS*NDC)x54%b$ZH>4FAVt j&wrhZ{*MSh&du!3wH*3$pr-xg@9ELvtQDGWLWuf16r_ri literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/list_unmute.png b/TMessagesProj/src/main/res/drawable-xxhdpi/list_unmute.png new file mode 100644 index 0000000000000000000000000000000000000000..6bff7220494443e3802050eb69c2ba3f250866da GIT binary patch literal 487 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSWdSpS4N|DZ_xKM`S)iwjV@L$& z+v$c`OpYRLp(;tK$}H9S6HaAEJQE0ubG)D>r?58l)*T1^2}Vm57)m-;u?9sIJA& zUgl-CuEP!fi*`pn!lyDP-gTd^*|cw!2*+!^TQ4|%tus={iGJa`-Ml=MN!PdfvkObd zHvWvhvSqEeJ4Gfm33A3SNZTebN%ptnw=?B;Rcf-fDw#^nS93Mp=dgBBTIoF21)E;B zbKd>ivhS4L!L|3#*SCjlH!FE{zIJ1C+y%zJnsa(`CoK%L-{M-eRdMS_fs>vJ*HZ(O z(zf|FRJz>0zaXc&^C{!!mY(Ik+Dm3k^j1DIe?7y`)!y|8@EQ zIt8P`l9!6}xIOr5RUBU3tkcSSbXh>4^nTXwD{WG@Ib#?;>RssGU_D(36v&>gelF{r G5}E+MAhwSH literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_discuss.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_discuss.png new file mode 100644 index 0000000000000000000000000000000000000000..590c3d9a61c7657d50aea21562db7677ba7f123e GIT binary patch literal 2077 zcmV+&2;%pNP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS?he@j@4~l2HmQypUOF zj^-^>b2M?t%k=x}K4=iAq__qoXV;O}1hTi@Na*4b>+aUp))QQ-PE{)q#khv-ru#6J_H_v|K8fsh&{+UQ zJDotk{zNItFfb3S0zDv0l0Dg#eT}}xAp#x`mVjK0k8-B0kp1-dw%3Ej1=#{3S7lg#Yr3ZF(i`X^t}kG z`qL|ao?rP7BE_wT(|kzA!Oub7;dcTprZpq^_u}AUlH$+4NEp@s&`+kz-rkGw-vIq< zRvDjT#o@)HxGYj0*^O|FwSvzrPw+JNXZzx-o2KIO;^R_A$|vq)LwHUYT3BZOGUFtd zJHoYV+pGA1;B&7gp{S=;i#n29tGxz^JYyvTZ&BjtP#O)+7zQ^aHdaRpxZNqLS= z6ebGG3`6JO-ImBuoe3*`pgde!7CTflhXZR-TO8X+9Sf4%Vz)Y8zQbdMjqrTKz(IIA zHf?xP9^jCXqmFB$3B_H8@`7m1#*rYu1xAj)MrMGL=BP`WZt;d+q=)(tP(Hv~MoQM$ zenz-64c=-_=8hZuhGlvAtT7TO$vZw$XC)``dTB2k^+_W z{U*{nqOr!ZvYSFD3uppk|Hy_QKs zdZsv&Mlq$1e8qc?DRp4wSFD430%%PMy{}444AJ#W*~uvCNF?tEOueK39-o>nNl8nA zOT{|GDUh6}A}`?Osg$dybjom5ERZ|JE*QdB8N4fr$!SK(aYsRcDn3rz-6H5IdnyKY7UZI?*wHab)_bu^_Fb1kCNAv} zcYuMH7=8b=li$j|+kh4Z27v>>rF8)+4aFv@FWtnrKMV%MP6Xj+4I^UBh;5_}1&ROB zfV`ed6fQ;v18WrRgx`?+GvXc!_5qi=V7SvQx?U(f=34UZ*K%#BT|V*ZBQPE8TQoPi z8UYemTVq*-_jZ6vU9ySyoq#-6RTMBr#{%n3hx}PTl@%5R09%Y$3#_FTy0F!;zOvxKcYVT^t__#C+JXK)BZ!ve5*gkGfPW0H6LWEx&?s@u z02__?3M_3zUkB#)Dxte_aSUsrcB!jevbr1snh7?!fjRfbxSySCWH@-=ajM(c&qB!F z2}WfRBzA5B&x4>pji9SYn`w>9!f&7r9BA8NO|J(6mAnr0Mt~1NfqtW*`wlP-1fTz6 zgMOWp-wx)2cfmHG=@N(DL@53SG%A^(ew@rRqR}^gf!huG`T;$I=wU31^ws`Bumf}g z{Q~U*M?f0-0i}C`^2wZ!vu}+k7X>*&b{0^w)h3-{&H?!>#8$#Mvrhh%c*(C9_rt)G zU@NF5>6_=(AeUp9{#H>Oo{mmk`VNMs*gahD0y?0-9}EIvbdk=oW8N@@fW991o!}Gj z8K;290^}-YhYm#wpp9G?aL-D*;pz2D;2K1qpU!c!7-(9K2X_M%SAR;SSKDns_AFBJ zd%$j>gL)Na3q|>lYi)@F1?|7+icOy)8TqH#$Y}4RH!72Xe#YB?`yW%=g1FuZ@MDmM zv{Tl$L?fl4DQXSsN|@bNnj%^#wI*!wpm$34R}ly_ zDuotENij_ZbI~i>8^B*cBSSbsLG-KCq-F)06=+rPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS>Wl2OqRCodHnonpIR}{t*6Cq%* zNsmSH%{T zQcBudTC`FXs?sF>zL%MJ-^rPIGw;p3XI|#O=iGb#pX1D(dnd1_r=qF@RR^jLR2`@~ zP<5c{z{>1EZzs#1pPwHl{wR12Yyq2r=oXj(SHO60Z|_a6D~D*z2)GKel5|J9zC#q* z55~Y8$V;-3ZGTrbio(`|Nzf|E$NH{n5(VkbDWW-XbLFdh?j(xQqme|{5u5}&z+eQ8 z#giUEnq8s?}Xq zbBfvyJUX7Y5S-|4^GJKCG)vjYs<(n_iViwIb3#7l}F+}ur%0gN;uLsz|#F@{bjK$ z`wq&IL#sOTtvX1vZ`dIIyY_=m!7t!Ha2rg6_rQiU=g4#_x75lb;o1Ia1B7$s80D&* zmAh(CEvjjMsGDny@er5-Y4k6`V@ZyYU2%+0Ch0fg<4e11OZp_@5{k!wa!jKsghQ@_ zBqj#K;17_M&U(Hu?$~Rd*k`jQkDhAHwx2?A4fqMqUSn%m zGDqojg7fd;e+)hY|2R5#;5}y@!fWeMLpcEDVo5nGch#U;JUVUjubX4h2egodG|S{j zsK|UX_$E|}#!J>0_-#F`63<(wlxzGsw^j=CBS5uVFIlf~nZBg{yoVjC^`- zYz{WZ(8iP1+1m!)39X~?His9ptjx2o=~B>L2K&G#pqbsTY-GDH^2N|4n}f|UH1cHD z{;58T>PijeoaV!V2-3>;pwLxE#^k>N=-XAX z--9|*%`+IZ^xp^ZKld_qzX|q$A)rHJv)`p3V5{TI?_d&4uxY%`b@>sE*$b|LvPm)a zE}IUK>^d+}c2^w#in*@nM!Am|fd&Yq58AH-omVyj?>ouc+~PkVs}Xj?488!lcE1bO zh5UO1FK>|3&Da|`y~6nOcphA58UF=zVEqz&h22B0>4d{WUTVo`M=N-U^g+7E%vZ$F_@Cm|2^nAeR>n88H7qOowzndn?o}axCA#`~Iv{-Vb9j zzbs}(x)|E3z;{v%=aBshbi$5tAa(#x8?k%9(r++cjP=U8Dh5z2OUh~S51Wh+fH;3$ zRg-E(Sb@;*fs?EIowrYMNbi(`(YG8gw35N?y%_!Z6a}XK5D3 z;;kL1mpW8TpxBlcUnXe@V5B?A()m>w+3&LY_B)a0ERgL8#_@2P{lwq#q+Qo2=Cl*f z%h6G*)oj0&Z&QP`2@Gk5VD~~s%DC>2agpASDf(rLyx3ljOY(?no-u-rYLML_KQc;b zQ}0n~``r^H=*9MO3{2{Vw)Q?Xs1@pWjAtEQyI5XqFUQRJs6mg7ww)VsS5Iy2kJTW3 zR|(0^Lzx}hTT*h5XYIdOJOOno_z~B`*EIkD002ovPDHLkV1mg>OHlv- literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_mini_forumarrow.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_mini_forumarrow.png new file mode 100644 index 0000000000000000000000000000000000000000..c5b7d9fd559483917ff652181a42a5dbdeac818e GIT binary patch literal 633 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSWdSpS4N|DZ_xKM`*<()^$B+ol zw=?!@w+2cakGEhFxJ+m)zYD_cn-1+Z`RVq-}`9~SUtC{x__RK!W*(80zli}60xap2Adsq+VzOZ5_ zbz5(F@8ScNgL_{XGHlIGh@Z=9@c4x-gXu@%6seRn42 z!8?y^liFR=9ah+A{0$dry!pxPaz5u=mxb@o+5O%1g+;6JK;oCCKdR@KshQ5&r?P?H z_J_)w^MT$~Oj8{a_$MvjxIZtWWW~iVs*P`7oKjEJ&+k79*2HAx`mbt{;R3hI(^gEK!V1%ag`|Et2mTP-r1&A|lmpX( z>#NMCy%n33`lN34wavckC$LSZDcS${zM-hZ|FYlG?tapTHXOe96%_5Bu6{1-oD!M< DXA25p literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_signed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_signed.png new file mode 100644 index 0000000000000000000000000000000000000000..77b0812f7f42ccfa3c2f0fab433f09599b7efb0b GIT binary patch literal 1473 zcmY*Zdo`WocZ`r;D004P=JCf@L+ie0W zvoWdsI`RgA=&ocdpt@WA>xLoG-0cI79Rsi%8VZ0C&jXOn$Og7=006*6AOO6;z}9+AaOHjFeDG|JrzCXSpi( znjxew7m^i&!W8QHm^r4@#1wl}MU;$3^+QKmW0CcZf$wH5phTy7bbq*6Mgz|=xXCK* zW8<4yQ)LUHQn2P(Rz~tZ*UMaug16aPl`n?7o_dJ=b#G>yyIt!wh$^2k{W7in=jXKq z5OsM{PKcW;|JCxPb@HyBjXp^5b@yIY{SY;?h_v5UNIS8tK^+^{F(EqJ4@Q#_ktVRC zjRtv-&^FXDPtN-E_}ce?pu(51F=#iqjYAPi(V0d3Gpu|Gr$$Ss&G7VS_CmTiSZJGs z@H308ZfIu4#hqi}bb* z3k>9-aiw`;=Y9TzBBV>lWmZlzl2L{s>+`;8YRjffm|;VEqKi^LA=d}06==!pSGi9< z6SQYMpQk1wT)LbpU3ocZDcI|~S?bN&@0rZp(5Z+WUj7FOA!XgmD_z=FjGuBe;-13` zNJ(1P-ybks%Ee@hq(p>w7w^G<+nx5{JDmoSZoD6TzN&h+X{G!xDU6KP7nYrQeTewq<5vDFBV%!f1w%VqSZTr3j|FN9K-3zs5PUhPj8 z78Jh~<|#BQh|i#w1Tb_5eutg=ss*e?kQO6YjPt{x?4oYkdsMi6*^3%A7v_D>e7RFm zQ@l%|)O>j)_wKnVn0O#nu%{xszJsQHFDN>4vT<@s zvPX$iyBy|nUIatxVB!+hM`gAo%_WUo#?OOTNiTa_9%u)+nBZL%M&CBXDs6E8cKB*f zYW9Rm-w|ZykbXGJw{c6p@V(cnJ+F$xSrtxRNd!-~Bd)Cw#^oDbM8 zQVoW;###qB&QlB`eoV=xG4xD<+8JvZZs(=m(Ne|PB^wqX(O5BjM&~$jZb0i0Ol{=_ zMc{YES>6$g54_O)MZH24PD-javga>8GyHQ@m5S%$u`6$9n#NYMj)q^c!oz1re>e=m z=8H+seOtW`xsU0~audhVp|dzWzviFE@AmUYGW%d5N^ul4A^FF34!m;rHAvi{OI;ET zFYv_e+n6(kC%wbGK0@1PWV^pa|7ksb~>?dusPNu=zBZp&6WHK?yK#-*-eY)ecdNZoczQVI)fm1FY z-C*K2)2uK{O-ORqf|$w%gKd(o-)9%|+TY(3Jw*6G_feJ~rW9!5q|fW#(qCLPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS=EJ;K`RCodHoIh(6K^VsGL}DyN z0)hxa(8hFrfka4Y)=WhSqCiB3V7 zrLG3y4{#4^a27ORUxe482JuTU1P4^wgsY5E-$NSIQU`IS;Cq;dJt}O%6?io?IMq$s zsI6?|L9tAQ+OM=8fh@qh0r(Admf-oK!N^8pOE$Bhm==oqE7YC<*5KcaD0jHF3`1|Z z;CN&+vAx652gSA)Y_!aZIDYJlit=x86^6d&3>}($B);U+hsQ%Z7^WdnsOR|bmJ8R0 zv0J2#d_wv5p`MnpR%?G+<%!~6SB%hl1yD}K-_J0z2raC-!HnuT z!LM2gP_|e&W{g;$EOscb{3Wn(@QDG%3Kg@9?Lt1Vrm@Guav{SFG$sj6c`OY7(G6=) z42!QRD}L|RERte|)&%#-P@!OK99l1hOG6nPfkxz`FA6S{Ucju|G9}wQ@bImL<@vVd%M0*0v%m2P;>9 zUUWXarvL8ighwaApGp1Jo4rCBSB<=sYgT9J51Pt)`*g9}U#q=mB)01&$N4oYBcEzo zNpPXnhm!&;7c1wEPav(8yU@~15cq41!H4h@tQztmUt>YR9aXY&v2yO5^%%8g=zE^J zR`Gob?^qWqD7$jZ*WEz-RxVb~-X3ICP8an;V$kAY-5A8Dm5Y_LSD?C+8(Dw8F(_DJ zPiC8*@+yl5@oDuID`)Q*WL4H$c_{8B71ixEMpTK<7?(Jy&k$c$E>_OAEuIJq#-nG; zuHC*;a@#WU3ttkVaePtC`nw}goN6QjnS(U5-hHn33=!vyvbGgjIrNg8|I5V`D5<3l zlP+$%1uZdODcx%h)8h*l}{X%BhUnlTkvv3nnEONy`JL3 zJ%L$Mmn_qzT_cWWHgTpI5P-fF#cjZ;Al4Cq)}Rlkdn0)&80y5IBBWyYj)rB&<3ncxB`QB zil}bVMr~yy4~nIfpB$97*N}iF5dRU>QctMe)A5tchUD1 z{x^YYg1ZQw9RL6T07*qoM6N<$f=T|Gwg3PC literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_topic_create.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_topic_create.png new file mode 100644 index 0000000000000000000000000000000000000000..403bf993165a7da2ec42058603b29aff2e96c452 GIT binary patch literal 1435 zcmV;M1!Ve(P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS=1xZ9fRCodHn@y-yRTRg)WnWg7 z6;#&bks@TF149zS92A5O3Ji)IIB=?i2!aqR8t9-&HOWa}IA{_W7zKh@Rs_CmQY2^} zlwZk1E$Nks-G2Y4SMJ?s-?!KK*yrB+&R(#%XRp0J|7V}Q&)MhPS+f!)1QG%Xfx#ir zvW9NA+bhvs4u9QhwZ634H9`+?HpAVtsS$-+iv*by-RN+9+-f`OdZtTE;JAfynVD=| zyHRUjGinLfoo@mtfOrBZ0Te(W(49t50HE3GXOYrP;A8L&YFh}K*ZSyPKz@Kl?h9W( zb`K(d2(IJgoF&e0@DJpZS&5N{iG(-7F(`90Nsz5E0vUWBLuWfYFN{d9Mhl}8i0Ij_WVO-{&v4t&H@#~Yy( zVh{6B{95|ClqL!EjwhJz$EKx50CWX`Z#_udT<4MXh?)dYPvO>v z6`+%jL*0Ehf~){-chGR#PWQ}dPCd%plUJ$#2L~<-x~eqNJ4u+by9 zGVdGps!-cmg%j6@_Dq5VAOh!sF2$o<@0xt4CG6yXBfJ)lIZ5v#pO2?VK`Kmh~--BItP6`P4sH*u?A zy*MmO+B*G;mFL&8j;!`k0RkMo-q{P^ZB59acLBc}0O(SSn-txq7&XC0`MhoGe zGREZue`3DY62Dsq8@)WdOYUQ;N%KJQyw%DwPTZ_8%d9aW47ckq( zhT=;e4?;P(FFoAwOzk%4Q|J#p^-1)WDSs5l;ES?w=n(BOlanDNOT^45e|N>Wezp~Me?5yYl7~kOQy#K<%^ZJE%b%Q)vxCzcjzzL|2VQyYPy?x zlV*TaB_L^L{~W$i?FP`nT-zNy(CNk7YWP$Gz)^|2bB&-5D2yasX4~NPa4wKLK1SeI z)5(fL0CkE3DD4Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS?-AP12RCodHoLh)hRT#(5c)>Bc zFl7`dnf9PBHC+gbbZ~}<5ki~ZM4EzLq7a&Ssp$ejp%ulv=*tK(&P(-VO45Z;vYQX0 z4FyqW060X#{^s-Y7ZS4;jDSBtULr1WuDvWXgyU_X4;%qSi)_eNX`><2 zkAkCXz&I#dWOGd>=Z(g>WgX`_;2#G4Ae8qMbu|ee2U9?kvqTg2HDD#sB)$ZM$PAUn zzyNd9?;#CJsY7yBgYQ8znWDlN=m*O}om1VYjoON%naG#bq5Or`!;xOlYyv(;olRhI zR%gT!afvfY->rQ6oMy!^j<3EXDF&_C z@)bx&fhFqFbKY~@Xyp^|4YyIZex_>->49?^xS~lh^_)YSWnc@~4c-FJfg3Y9X*7mY zbCNZ2E0%m7OCZvEV;s1m+iL2&6>S~^)@t}1`WL}`Bi5Ex@agVmEddl$_VpPgeZZ1| zv|dB9!6I^WUvPa(TJEI!@C`UN>X&^|2Vy{1?4^|AFz8+~N2!lHDbk{kjaC$POGnuMmZwCZ-fukOx ztI_W!xR*TT4eBTccL*z1#oQpi1g&bQ`S0n2p)OWMK;L5F9`Fj7_E>v?Bxw>46`iEG zV#QqZB}ixGt{it2WnGh`3t$L)>%fn|5>4FmtpY=5#bU+mO_243K4lmxRYj6M#h~WK zF~jgubfK$aD;6u}R0*md>KVS3ry@ybz@u3F$gp~)p|ez0EU6M?kzNuyhS5UsI?g;` zICRd?Wl}Bhv<}X&r+p!65_1#8?*L8OuKbKbLoh_F-eSe<9b#5xEy?FhNX~;8KL)Ji z$gg3B-UWRL5GxidW^aN{8b($cy24c?W&wB=L;JwB9)o%!f5TH|sjXP7m}|Zv7Q9Zd ztmma8G0Wh38{FsNRF4^==cGxFsH`=Q6|=Vooq*)Lm}0UdayX_c?kl#|!~PZUCFdu~ zy3!^mioxB4Q>==)LFP4RP+(Lm)^Ve($I#`fB7pfo52O3RbsqBf(BFZdhdt#0mIrx zf>>?-)eZ0vUMcpH89hP!N~WGMq4Y^*=k7%Iz$b3Nv@~ohYSNDDl;gY z){uG`0`31??;(00{p}>?b5D7wTD~iWxF|-&nqF{x1h~@bru~yH5!YV>orC#AoB7WI zYq+HfNF2c0v>!=D6t4_np2t2uF3gxRiPksV;%)|ghb#YMA6bStuY*JVg6e%C@*$Oc z!!0Nw@k($Nb8r|XD9@wkyK(lngbZm&Jr6PuDJMaN9zE-q*WRxyr%|kkI}-ApHhKxm z;`@vpzsJ;c~3u%vo{ zD*DAc$WHYNUcJXypCm~0hjm`Bp0yn$Ej%sC+;#FH@`aBEk?%U4Elx1d3w&>5Xt-Is zb;UUpW0q~;doNKw_r|Ih_Z35fz$bbqF!kLntGBWVtoIS?ehy}}k*Ymbt3O3(mNAl~ zp5z;D`sF5omiYOsH)0NnGvc1$(ek0pA}DO+SSx9yR5 zOu2u@QB9zIz}hN7ovU14e-`d$n^dSr>2=~}6M34fiA$V?_n6SR!i_uiHNmvgs0;Lf zZqNx9fe_KT)i}`UgErVp`_~kuC5WK@CnQYIZOefsVZFShiMjrmqRCp&aj+)w6KvQ| p^I1uifhq%42C58H8Tc;){{W_N_3);fOIiQ`002ovPDHLkV1nQg@(BO{ literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_topics.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_topics.png new file mode 100644 index 0000000000000000000000000000000000000000..f813a7b6a5629cbd9d3e16254a7d9b8ac7b50c27 GIT binary patch literal 714 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(<^v49!D1}U6i==Pq0fyuIzrzj!ZpkdNA~8&zP0S50hj~}LJS6H6BBS8o%Q}Za&tawrhai`V$^!`wCq~YP zYo|=@*}A#U%=OKa%_f^{H^PN38o-o}ewfbm};u|-u zm$RIhUdSokTGgtzCTh`ng;hTscI0%}SBkA|NqQmnu|E0Yeg4elPpX$q>%Fj1-09q$ z_qy9-@0cXZtU3SS_@V`eet!?kOKmu(tN1VC%*k&X_?{(%mi|=QDE-F3(lKxP&VSlw z*NmAwetuuTxBY^*Q6Z-*vL_P6IF{`3T)lXP{pVj|r!Q;dFdr}F)#uLJzx%xT&8hb} z*Is=8;QomLPyLhOt&_*At~0`_}I-Kt;allJ9tifzH-X6s@qa<`^?|+ zHyEEK?s{~-q2P$st_61!9oEWUTG}AP$Q9~tSf#0sgg(r7IJLYjYF%a9yJXSJ zD(7rO_VY^kKKf$dl+ECKspzVTUzO9WBa*u|x!G-g%4OmEh-K2vB3Z^OD(6mIH8oy1 o<-3CKqd7A#uXnzopr0DbipnE(I) literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_viewintopic.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_viewintopic.png new file mode 100644 index 0000000000000000000000000000000000000000..b62881120974385cc96dc1a7b3773770bf175e36 GIT binary patch literal 2206 zcmV;P2x0e$P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS@2uVaiRCodHn_GxhRTRga@~%aA zDK!nrOB85^(ABKK5Pe9BB+U@)B@BB|gCgm{LJKmxnAKBIFGgu4U9b!b8O^{#Ey}`j zDm4=`#|kYk)9+7bf1TN`bN$Xa-x#yt&)#S6wf^fe-`VH3r%RVk>8wC!1xlA7;xTMHH#TW33 zKTfnhO)t`SA{P$3gB5X6F?SFw0_P?Y)}bK&z>oaJiQ?^EhpTj^IOzvIjfvz}<{ki* zUV7y(@GJjOqPTT)T8@Yud>-YU@n)dKv|%FuK^!!uDE{J$1gZW)pP4S(+DhDbmyL3Fw|YEC3Ca0rMJML?rjRcXMyuxD;dU?rjIVr#Aq=1(;(cCnJlFN zzPf=|fv!F}h3`*knpzFc4Zs)WIXE?m6fs!ovv7PIT$YI_gEHLn44+FfP$a4h2jE(U@v2OZCaYZKh5y#h6*c^ z@&NlBaj4bnnqtfSE>wwb?wL@Z!N}MEy(uxH4kWh}Udmu6$GzL1YP`fYEprQ$4DYHn{)oh@d z8i7m<6~=Yfo5~drmQ^Hp*SkbR?L?QJ|AOm$HxX*}SthgK>?(}z0!6;P(`EQlBbbNq z*M(0!i;TIXA>}v8Q>xX$FOc!&mLs60)t;!LOcmI@y=J5S!m+9oWx$FoyB#L6M8}mGI32*01^l z#vcP&#d?CT6{sB?AdoVPGuBU(V{OH$iS^6a zSeyN?(~vJ1Dh?s&zQ$iG6`9tYX?5=ZnIa-jHhahI4+&~^QdQeqlyjuwfWp_DaJ2Am zGIG`3f3#pQ2v{Eq$3 zYAE-8julP7=ezoufo_(*Ifn8)pCLG&WEvDO2*0(9r0C+@7LixB2VF>d0_#`w72|oH z&k{=r?*O391Ww+flj{!Iat2p#@Pi`{wJr3s?*+g}R})L#Wzc#avf|1#2CsIMOaa$q zGE7j0uL39r9P|Hki5s4;d5Z zA_Z&6A;z-p>Im`Hx{%xqteZz&`e)P>J79C&mqy`xk932z%CU!9y*=zuLTn?z)=-RocSR`y<7Kt)iQ_vD7+}}-xZj+YP&+aUPg%OeGYM=A#pQSPAi4w;vp3pL1Y#}JzO?vyRUZ@3WU$E( z%sW2F@zhdd!@&!lQyr$h7eaSD7*s@%@LUA6O(*JKBbchmW_rVl@Egg213h+Fr`Ju9 zM!^Jj+7R(FaPkcu9nS^hK=k`xIGERa@-bj0cn)j_I$hGplPINs08L6JY91%EjA*ng zBJy#AxwC-oL3A^gB---37kmMlfv!M1!G4fOwqSH!R6d1|Q=Xz%13eAzf?DHSmm`%|O5znDrbYtS5( zlnFX|b{Ygm16{`Z0sjw^L{sfN0lo(Eh@O=7SfXFb87)a`P)o+_wxO>VP%m)`{Fu?U zL#ijOK?8_-Gq6q(EtFalwg&i+qWnbveuv(O8t4m0%nnj3S zR(<~O>pSOtn7Lo>%-p&4ch8O2*HtGaVj#l8!Xka9pQXBXvcYP%2grD@ z3Ar4mi=S4ZT0bPHlyEKwL0?U=ris-ONxg1^BFmToBYr?1G6 zgN}BunuF^Go|jVSy%yx1#ote@=POPnnT|rzO0#MaieL~Bfb;*69D3=ZNo4rmuQ;i> ze@_SbW1d=GbCmJPR;*cBuaE^aKB{>%gn4xERy#e6ROd`6=2}wzlb?dVVr?1d&%kZ# zE0E}6H{v?q$~tIQZ+88=f2})N>t%p|+{S1x@~j&uNfx7%7Z*9(u(f=cq}Rh&TqYzB z+0J8I^6@NJ9oW?UmR>woxoCRntygtTF@AM zvNPGE14=fQPp*>Fvh}zFh0hvK*|| zBkNzjK+W4dv&5uy`HJJ7S-n+5`dF`fOAd|&uNQ7)IVZ~o)CfPp!Tl9cSqCClZJbfzPW6q00 zP5aStJuf!9P-GL;_Bbh{#Q4WaG{#Q##D~nR-San$^yA>Pcf8ZuDJ-SJwpGl+N(xym z8S^FC9+NdXpQ*bOYGk2z_tOyZY*|K%jV*b|tisWxsu--H@H9{5?u|*AJ%d3mB&_b^ zsynpus-Rc~k0g+kIhls8-l|)=B+8`TthGrx(%WO00pkRdOi_cey7=bypdK|Wb_fK7 z?Ju=M)iqxaJmyid%zijbjcITmZw690ei9~kR=tV;lA$7A2a`WBW02L-_DsRLb>lko~q?h-Y@HTCkbWA&NxvC0MgdxFWAdKj@=P15-DJt0RcvyL)I$az$Pvz8_{K+&=EWXr9H_U9bs?CdQ^b4j84cx+Xj<()Rm#b=+7Nbuk$!dmo zeu!oVvguAX?i3bhd5>mn9Npd&#+tu`A*8yd@bgZkT+HEkJtT0}DW3D!A``aN%vZv0 z1R_Pvis3xkbcOntIj*QP2RysikL?YJ!+!y%vcyK&8L*%>j^sUORKmCC`^#Q1bWvca z_v!ZZFb@yU{oSt)-+TYN8_fMapqkowL)2+5NH_-bfw?KPCizH>wq(leaZYfLz7nu$BbUw^^hN)Btw`bD?s5f1GaGCsTMuxTl!;<@EY>(nZ=7;T zb%Ub7Y&wr~D5EWo(BVxKEq-aho`tOK7s<#~3<$3s;n7iY$(d`6}d8_{ywkcTk$?Nxx#td@!@IZM>QqpF1E)2uSkSxb3g=XBs5& zNMX2yqC`x<-jC0iZ}C(LwNI^W+SW4kmdX6_eFHhsI0I2=euA!P0k!w(frR~CPwq|_$EOAwt7 z$infpZj7CW4L5~5LRMY>XN>oIlL42yRt=lU^~<9T?^07!I8+^^LuX&yNB%K8y3e}3 z*E!OrG)a|1(xaC{rR|k5lbyicn~UFpVS7vLkYS(Ak#vQ+%MG&-gHc&r3^A^Hy0m@P zixlERQ9C~&Y>Pj_bTO^!zVLti`>$Ca5} z6{U*y(W!jkcd*Bc0MDt#8^8BhcySYXdwp>~WhxJ{+56do#wMxcMdtSOR%o=Y&FQ`! zx_D3o*5t1cOMIon3Ke}{{Xn;VP9kl*c_Oz=r);~lm>k=tjbD5P+aA+aAR_vsu80XR z($phd^?91}hmOAi2P?OXXO9VR92EJ(oiYQncNgQuhks_f2WIlze#ToOj*QAvS?By% zxd6vr0IgB=xHWV*l5+l@e+uI5cZ-=bFLYu(+pMB>O~>-U>-C-nu({!0L?pXJ&FjgX z%?VwN;^Rae9uAPBh*k_(njMxuQEMXldChD&l}7S*gxzp~I5gU|FYOT#&QTYru=~^y zRongIHFfE+^JWFy1x3E1fwSP$-7&CJ_G3kdqmQ_e8$$J)1z*IDve!E<-rbVd>rMq zvN)C{92P^X=CV2YlMl*{(?Dk-U_M(fimd{IS+*ky+-acFcX6`{Ih#kFQj-8;`wF}e zn2LnBg~*)D8hhO`c1(Wq`8Q^#>R@}N5k~`6Su@VWzwbp8Q<$WVa}bdcy^wG(LY4=Y z-FX}%JL~@SPi?%|t_Ae15;zz9gEJpP(Ap7|uuvo$d~6NF!;P@@ehB9<06Cw~)?m=6 z+d4#<|MDPzUy@Ue3Sf`(>b|r*m<~N3PyUI5qUA$5W4{4eQbLOp5@C5XW!k4D{l-4h zM(c^ITdH&ghNyxNT34X9&RVK}DvZDoxS=Ns+V94MdSv(b>3}4-hq}8&I5*L5M-Ch? z{R^oYcajB{8az67V11R6D4h9mvzo>(>$X0vBfxgKKG2^SV1_MQ*=0|BwCAgf87T=^ zWEp$uDF`I8=M)`1ToHctjTL1KfAdf$cyI8nKP#4!_9|0b>hT+@g52AQ4@-~Nxo82R zi7KEOEOn_EFknb!P?<3+$Ng1(HANrr;?eYI2Xd zPl^6nY9mx`Z2|>3W=U&2a4NuPw0Ix8!;n{W{KMJdy9#b zhaWQr`yCH_Kd{Jadpb;LSLU@jVC?SJb$niJsxnFtzfK&sY6{Yl1hpwk{)b0tNwZww zQvNy|7{!`$hc;@?Th}^AhE|ogN>fQHAY)(sc#9)(C~!PY;rbC;xbv68s<>*omIOmdmQGYmIgv7U-f=F=zgYttUZ8x@G(X_zaIN z#^n(m)q|PW30voUv#%YqXRAQbx=4Z1d+4E-03~UO>)O+hqd{7A*cMKNE!GM)dDXwp zR&n@(hBn6ug&)8usAV>E)7hCGS19&T_iomdo%QaRc_ipT7w~v~D%bx)OF%6G2pt&eIZuV($n{6y65M?=g@EYr3CLM)_z%;L-|yR} z_vp{&G=+32WT!%iw0E^Z_Q=^eDgegL(I3(9{txNbz=JzW>pn#rx~|h1t)Ayc)3KE$ zu1hOwaM6H$;#OT5RPTQl%n$(9ngcHhG040+3NVoFpIO-b?SF-ziFiSpe^D6agDHSbOb}_vIcz! z)RGL522aR}QaV=2oOys`+`vCPhupo~z-B^kRmO*S^&~psg$VTWcmD8Xuqj4bN#Wc_59aFMn;C$r6vImiNMDX@V<#!nK1nVg9QiK zW^9NR;=I0jKem33hJzUABb${gwi4*%U6@F*AGz@-c9MEfE>UCm`O?A9r@&4R-XI{2 z+R6_tjK?o}hk=nuER7-0qaq;gz4lZp%X8`*vrD1~ZrJ5ooKU2CFMqAP`>1tQ)*tdG z^$)=7Jm)^-zc6mU<#5dVxbbJ0uXB z{0&o3IXk_`yiQoRL{rb^<#aiN^gbMW$_hl1Pk#>`4c-1@TCqeU)JD2V>dOAx@1XnQ z?q+AUhWv{J;ka<2l<%M3c~k4#v$=VE#y*Zw{$P*vV@en;P;)xbqEtF4HU6$?pwtYg{$of)T{yfVWiZEh}UXcz_?a} z3$E*D%}kOUU68bcLZ1YFwujYaMJV(2M2hdOD;`zsM3z|>9x0{c%rB++#e1W?p`K_2 z0eP>*0yQu=t9DZLr$SbXSK6B;o4JrA*@x7Qqs?evUmBUl>~!6e?}3#)XRYy=g!;Fk z_-M9r9NGS9GEJ_fWYFO%N(nfHUbP_7HIc6EP0r#H|wS2<~S$wOo7#cyJa)L&NwT0c^ zDSW)TWL#R@_QkN!Gxm2pd^bUIgcA_DQi>G8Hu%x>fc}_X@VXAviY*J=;e<7&`DtYR zp#_ta(?4En4!J%*2s`W}5SlL#ff)I$e~Z6|ZKkUutk3rA9m!lumcz)KI_GY17`d9I zqV~Xn?-nVs*WP$NO5y=lC5ex#rCq6|k6VM$^!?y_!>O3ybw$(YHMkezc$8m(Y#-u_ zjU9A{L5ICV%&{4s))V%Tc4m*+95b+NQ3bq9#r zkavh^KF$i|S~toHPXkdQ7ec1D0p|18 zhj`62;X}h0coNc;A;mFZ|6q2R;d8m>WVL{+iSk4)&@4O9P?8#l<8I=S(0EIplX+}} zt!NUPvSq&)Xx|1Wy)!vssJp&=`Jn4$^5EaEj+4NZ(ChsY*y*%-V;M((QhsP@vR<() zW^!#H&U)pc9F+9j9FK*0FIonMwC`q==+t2!9&1`+e<5U4st}s4aU`npw5~vUG$_Jw zwvzLGwY!Yx@QaSd*=@`KY|6yBG1%V*>ZmO@7C65*x@1UCOLx3V5i@=afZh-|zf*zw z%s)3Rqf-2FhkGihPmOqvY&Su_uJn%nKHZP`RXpfhm_kv-b|J-rcJN_-hS>`)X&7sK z(NOKCt2O(V_~fEs-FYdyL7$r2!-CnXF$MhN9yKM8>C{atYgEBny|2Z3XH?i}W)rNc zwUa1Xz@LT`GSZ{kG+^JV89(L5qE!MtA=3w$=31=2hj5}5E7;~FqTh(^dkf0n#c>pM z{(0{L6#M6R8~Gg`jiF>`s(a4{F7}KIc0j{Y`{iCbT_4Ifi=FZ!z3I?v16j3K|1h!w zmI;YZy&Gs}*zhN8-v}Ok5s>%NYakgR_Hc_nA1YwoPemw}lzKJeYy3tGO!30QbD_Cf zn!gb=VWkk;><}Dh-1U0)0c(j!tVhw zMjiXENg;al)a^X=W*Tajhnx=zT5pwX?vh=?u_{;GkJjf-2>#SG+X--}sDwDLc8pP4 z5c(ElWSokfK>KN(q;TN!)eeXs9wjyFZ2ZA=fwG*>Z=1RZJ2JdCv$?{bX2pc^-^jlc ze?E(T-hHN2q!fbaBeL-zxV$h{IB&j>^|vTxNl96d1^yFJg+)_&TGpe(n6k9Cc42ud zUGMP4T6}9eobWBmDJ(GsEWzF}?u2}>IB|Hih|E%l0oknBCVi-GqZ@KZ z7Fg4F8d(y1QFoq{7db2f77q_2tYvg9r+CfrPAxJWI z!LicU-UU`QUNAdY@X6mCd+;fRcIhYV_xVbvCkh-CI4taEP>3B}C`glfpo0+bPK`hX zTzG&*?V5N#^&zMw*Qi^YS?Gwz7$Q=8o~GMx;F8tgdwk*jO)lkc$#*36Bu1K5s*&<% zh^9i^^;`LrMD+K?m%BB{O<#G5O*Y(%Pm^%t=9r2)sz1HNWW}Hg2yRBZw`hfUyD~%>~|S=C1FVPSvgH9vwqa856jMj7l`Xi-#dU z3&JJbdb;m0Ebs!RgO*Qbij{QicX`3*nJh`JjtQn69v^rmo!y?8ffVmrka!|k+y0SY zxW$RKth;u?g9XdpE36S)@e_8C^PFffYhx~WT*Hf$3SqLT}$lti!b?1K)Gk&Z@Fqhk5uRO>e>{mpsG>b9ZHx7UDP!3@Z$ zaOYd4$tT`h490A|&|WWKd}3_0rOWVKi|7gHQ_-vlnalwdL!<3wesPXB;*SA2nU``l zz9>b>^`#mUa=2*sS21%YBE7M~jo-yBdYKWW6M3{;a=mmVbMZEUfMnoX-p|zp9fN0{ zk~B?Aqyc+k0!Ut^UlPY literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/raw/filter_reorder.json b/TMessagesProj/src/main/res/raw/filter_reorder.json new file mode 100644 index 000000000..25c420576 --- /dev/null +++ b/TMessagesProj/src/main/res/raw/filter_reorder.json @@ -0,0 +1 @@ +{"v":"5.9.6","fr":60,"ip":0,"op":61,"w":512,"h":512,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"reo Outlines 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[256,256,0],"ix":2,"l":2},"a":{"a":0,"k":[36,36,0],"ix":1,"l":2},"s":{"a":0,"k":[512,512,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0},"t":34,"s":[{"i":[[0,0],[0,0],[-0.117,0.117],[0,0]],"o":[[0,0],[0.117,0.117],[0,0],[0,0]],"v":[[-10.109,-23.167],[-0.212,-13.27],[0.212,-13.27],[10.109,-23.167]],"c":false}]},{"t":45,"s":[{"i":[[0,0],[0,0],[-0.117,0.117],[0,0]],"o":[[0,0],[0.117,0.117],[0,0],[0,0]],"v":[[-10.109,-5.007],[-0.212,4.89],[0.212,4.89],[10.109,-5.007]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[50]},{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":34,"s":[50]},{"t":45,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[50]},{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":34,"s":[50]},{"t":45,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[59,29.451],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-3.696,0],[0,0],[0,-3.699],[0,-3.125]],"o":[[0,0],[0,-3.699],[0,0],[4.267,0],[0,6.25],[0,0]],"v":[[-23.5,17.75],[-23.5,-11.052],[-17.376,-17.75],[17.519,-17.75],[23.5,-11.052],[23.5,5.01]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"t":45,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5.2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[35.5,26.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":61,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"reo Outlines","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[36,36,0],"ix":2,"l":2},"a":{"a":0,"k":[36,36,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.6,"y":0},"t":15,"s":[{"i":[[0,0],[0,0],[-0.117,0.117],[0,0]],"o":[[0,0],[0.117,0.117],[0,0],[0,0]],"v":[[-10.109,-5.007],[-0.212,4.89],[0.212,4.89],[10.109,-5.007]],"c":false}]},{"t":32,"s":[{"i":[[0,0],[0,0],[-0.117,0.117],[0,0]],"o":[[0,0],[0.117,0.117],[0,0],[0,0]],"v":[[-10.109,16.852],[-0.212,26.749],[0.212,26.749],[10.109,16.852]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":25,"s":[0]},{"t":32,"s":[50]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":25,"s":[100]},{"t":32,"s":[50]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":25,"s":[5]},{"t":32,"s":[2]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[59,29.451],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.6,"y":0},"t":6,"s":[{"i":[[-1.933,0],[0,0],[0,-1.933],[0,0],[1.933,0],[0,0],[0,1.933],[0,0]],"o":[[0,0],[1.933,0],[0,0],[0,1.933],[0,0],[-1.933,0],[0,0],[0,-1.933]],"v":[[-4,-7.5],[4,-7.5],[7.5,-4],[7.5,4],[4,7.5],[-4,7.5],[-7.5,4],[-7.5,-4]],"c":true}]},{"t":25,"s":[{"i":[[-1.933,0],[0,0],[0,-1.933],[0,0],[1.933,0],[0,0],[0,1.933],[0,0]],"o":[[0,0],[1.933,0],[0,0],[0,1.933],[0,0],[-1.933,0],[0,0],[0,-1.933]],"v":[[-28,-7.5],[-20,-7.5],[-16.5,-4],[-16.5,4],[-20,7.5],[-28,7.5],[-31.5,4],[-31.5,-4]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[60,53],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.6,"y":0},"t":3,"s":[{"i":[[-1.933,0],[0,0],[0,-1.933],[0,0],[1.933,0],[0,0],[0,1.933],[0,0]],"o":[[0,0],[1.933,0],[0,0],[0,1.933],[0,0],[-1.933,0],[0,0],[0,-1.933]],"v":[[-4,-7.5],[4,-7.5],[7.5,-4],[7.5,4],[4,7.5],[-4,7.5],[-7.5,4],[-7.5,-4]],"c":true}]},{"t":22,"s":[{"i":[[-1.933,0],[0,0],[0,-1.933],[0,0],[1.933,0],[0,0],[0,1.933],[0,0]],"o":[[0,0],[1.933,0],[0,0],[0,1.933],[0,0],[-1.933,0],[0,0],[0,-1.933]],"v":[[-28,-7.5],[-20,-7.5],[-16.5,-4],[-16.5,4],[-20,7.5],[-28,7.5],[-31.5,4],[-31.5,-4]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[36,53],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.933,0],[0,0],[0,-1.933],[0,0],[1.933,0],[0,0],[0,1.933],[0,0]],"o":[[0,0],[1.933,0],[0,0],[0,1.933],[0,0],[-1.933,0],[0,0],[0,-1.933]],"v":[[-4,-7.5],[4,-7.5],[7.5,-4],[7.5,4],[4,7.5],[-4,7.5],[-7.5,4],[-7.5,-4]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.978},"o":{"x":0.8,"y":0},"t":0,"s":[12,53],"to":[0,-6.333],"ti":[-7.833,1]},{"i":{"x":0.833,"y":0.839},"o":{"x":0.167,"y":0.161},"t":15,"s":[18,9],"to":[7.833,-1],"ti":[-0.167,-7.333]},{"i":{"x":0.833,"y":0.999},"o":{"x":0.167,"y":0.001},"t":20,"s":[60,9],"to":[0.042,1.839],"ti":[0.005,-4.289]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":23,"s":[60.047,20.602],"to":[-0.004,2.839],"ti":[0.013,-3.162]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0},"t":26,"s":[60.02,32.7],"to":[-0.045,11.112],"ti":[-0.167,-7.333]},{"t":40,"s":[60,53]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.4],"y":[1]},"o":{"x":[1],"y":[0]},"t":0,"s":[0]},{"t":20,"s":[90]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.6,"y":0},"t":15,"s":[{"i":[[0,0],[0,0],[-3.696,0],[0,0],[0,-3.699],[0,-3.125]],"o":[[0,0],[0,-3.699],[0,0],[4.267,0],[0,6.25],[0,0]],"v":[[-23.5,17.75],[-23.5,-11.052],[-17.376,-17.75],[17.519,-17.75],[23.5,-11.052],[23.5,5.01]],"c":false}]},{"t":32,"s":[{"i":[[0,0],[0,0],[-3.696,0],[0,0],[0,-3.699],[0,-3.125]],"o":[[0,0],[0,-3.699],[0,0],[4.267,0],[0,6.25],[0,0]],"v":[[-23.5,17.75],[-23.5,-11.052],[-17.376,-17.75],[17.519,-17.75],[23.5,-11.052],[23.5,26.869]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.753],"y":[0.386]},"o":{"x":[0.436],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.722],"y":[0.663]},"o":{"x":[0.402],"y":[0.197]},"t":9,"s":[10.82]},{"i":{"x":[0.679],"y":[0.748]},"o":{"x":[0.348],"y":[0.347]},"t":13,"s":[35.224]},{"i":{"x":[0.675],"y":[0.548]},"o":{"x":[0.344],"y":[0.38]},"t":14,"s":[42.626]},{"i":{"x":[0.662],"y":[0.696]},"o":{"x":[0.338],"y":[0.294]},"t":15,"s":[47.891]},{"i":{"x":[0.639],"y":[0.732]},"o":{"x":[0.314],"y":[0.319]},"t":17,"s":[64.73]},{"i":{"x":[0.65],"y":[0.661]},"o":{"x":[0.318],"y":[0.507]},"t":19,"s":[79.604]},{"i":{"x":[0.65],"y":[0.942]},"o":{"x":[0.318],"y":[0.49]},"t":20,"s":[83.079]},{"i":{"x":[0.651],"y":[0.516]},"o":{"x":[0.318],"y":[0.045]},"t":21,"s":[85.267]},{"i":{"x":[0.623],"y":[0.306]},"o":{"x":[0.294],"y":[0.995]},"t":22,"s":[87.881]},{"i":{"x":[0.595],"y":[1]},"o":{"x":[0.269],"y":[0.418]},"t":25,"s":[91.094]},{"t":32,"s":[100]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":25,"s":[5.2]},{"t":32,"s":[2]}],"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[35.5,26.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":3,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":61,"st":0,"ct":1,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/TMessagesProj/src/main/res/raw/hint_swipe_reply.json b/TMessagesProj/src/main/res/raw/hint_swipe_reply.json new file mode 100644 index 000000000..21899c402 --- /dev/null +++ b/TMessagesProj/src/main/res/raw/hint_swipe_reply.json @@ -0,0 +1 @@ +{"v":"5.5.9","fr":60,"ip":0,"op":120,"w":72,"h":72,"nm":"swipereply","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Oval","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":75,"s":[-100]},{"t":90,"s":[-90]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[46,36,0],"to":[-3.972,0,0],"ti":[1.667,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[22.167,36,0],"to":[-1.667,0,0],"ti":[-2.306,0,0]},{"t":75,"s":[36,36,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[12.5,12.5,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":30,"s":[18.333,18.333,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":60,"s":[8.333,8.333,100]},{"t":75,"s":[26.667,26.667,100]}],"ix":6}},"ao":0,"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Bubble","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":30,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2},"a":{"a":0,"k":[9,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":60,"s":[100,100,100]},{"t":70,"s":[20,20,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-0.14,-4.29],[0,0],[4.29,-0.14],[0,0],[1.37,1.09],[0.86,-0.01],[0,0],[-0.01,0.21],[0,0],[0,0],[-4.14,0.13]],"o":[[4.33,0],[0,0],[0,4.33],[0,0],[-1.88,0],[-1.51,1.59],[0,0],[2.79,-1.62],[0,0],[0,0],[0.24,-4.12],[0,0]],"v":[[1.5,-8],[9.5,-0.25],[9.5,0],[1.75,8],[1.5,8],[-3.48,6.26],[-9.3,8],[-9.5,8],[-6.5,4.09],[-6.5,-0.5],[-6.49,-0.46],[1.25,-8]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":70,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Reply","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"s":true,"x":{"a":0,"k":0,"ix":3},"y":{"a":0,"k":0,"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[50,50,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[9.55,0],[0,0],[0.59,0],[0.2,-0.19],[0,0],[-0.41,-0.44],[0,0],[0,0],[0,0],[-0.4,0.43],[0,0.27],[0,0],[-2.41,-4.54],[0,0],[-0.18,0.1],[0.01,0.14]],"o":[[0,0],[0,-0.59],[-0.27,0],[0,0],[-0.43,0.41],[0,0],[0,0],[0,0],[0.43,0.41],[0.19,-0.2],[0,0],[7.09,0],[0,0],[0.09,0.17],[0.12,-0.06],[-0.42,-11.87]],"v":[[-0.215,-5.596],[-0.215,-11.536],[-1.285,-12.606],[-2.015,-12.316],[-14.395,-0.786],[-14.445,0.734],[-14.395,0.784],[-14.395,0.784],[-2.015,12.314],[-0.505,12.264],[-0.215,11.534],[-0.215,5.604],[14.045,12.404],[14.055,12.404],[14.545,12.544],[14.735,12.214]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":120,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Oval 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[20]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[20]},{"t":35,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[46,36,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.398,0.398,0.593],"y":[1,1,-22.251]},"o":{"x":[0.202,0.202,0.175],"y":[0.15,0.15,10.582]},"t":0,"s":[0,0,100]},{"t":20,"s":[30,30,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[16,16],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"contour","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"fill","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Oval 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Track","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":50,"s":[20]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[36,36,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[16.667,16.667,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[{"i":[[0,-4.418],[0,0],[4.418,0],[0,4.418],[0,0],[-4.418,0]],"o":[[0,0],[0,4.418],[-4.418,0],[0,0],[0,-4.418],[4.418,0]],"v":[[8,-10],[8,-10],[0,-2],[-8,-10],[-8,-10],[0,-18]],"c":true}]},{"t":60,"s":[{"i":[[0,-4.418],[0,0],[4.418,0],[0,4.418],[0,0],[-4.418,0]],"o":[[0,0],[0,4.418],[-4.418,0],[0,0],[0,-4.418],[4.418,0]],"v":[[8,-6.667],[8,13.333],[0,21.333],[-8,13.333],[-8,-6.667],[0,-14.667]],"c":true}]}],"ix":2},"nm":"contour","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"fill","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":120,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/TMessagesProj/src/main/res/raw/not_available.json b/TMessagesProj/src/main/res/raw/not_available.json new file mode 100644 index 000000000..d5df52749 --- /dev/null +++ b/TMessagesProj/src/main/res/raw/not_available.json @@ -0,0 +1 @@ +{"tgs":1,"v":"5.5.2","fr":60,"ip":120,"op":300,"w":512,"h":512,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Layer 4","parent":3,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.659],"y":[1]},"o":{"x":[0.325],"y":[0]},"t":134,"s":[0]},{"i":{"x":[0.66],"y":[0.987]},"o":{"x":[0.326],"y":[0]},"t":147,"s":[0]},{"i":{"x":[0.659],"y":[1]},"o":{"x":[0.368],"y":[-0.006]},"t":163,"s":[5]},{"i":{"x":[0.66],"y":[0.995]},"o":{"x":[0.326],"y":[0]},"t":176,"s":[-5]},{"i":{"x":[0.659],"y":[1]},"o":{"x":[0.368],"y":[-0.006]},"t":188,"s":[5]},{"i":{"x":[0.66],"y":[0.99]},"o":{"x":[0.326],"y":[0]},"t":201,"s":[-5]},{"t":213,"s":[0]}]},"p":{"a":0,"k":[10.861,2.694,0]},"a":{"a":0,"k":[91.117,80.531,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,-2.937],[2.937,0],[0,2.937],[-2.937,0]],"o":[[0,2.937],[-2.937,0],[0,-2.937],[2.937,0]],"v":[[5.262,5.625],[-0.056,10.944],[-5.375,5.625],[-0.056,0.306]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[91.235,74.912]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,1.205],[0,0],[3.033,0],[0,-3.033],[0,0]],"o":[[0,0],[0,-3.075],[-3.033,0],[0,0],[-0.997,-0.332]],"v":[[5.262,24.917],[5.319,-13.932],[0,-19.292],[-5.319,-13.932],[-5.375,24.892]],"c":false}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Path","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[91.235,55.62]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Artboard","bm":0,"hd":false}],"ip":121,"op":229,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Null 4","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[256,419.93,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":124.539,"s":[169,169,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":138.154,"s":[177.45,160.55,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":146.322,"s":[163.93,174.07,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":167.199,"s":[172.38,165.62,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":179.906,"s":[165.62,172.38,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":192.615,"s":[172.38,165.62,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":205.322,"s":[165.62,172.38,100]},{"i":{"x":[0.1,0.1,0.33],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":218.031,"s":[172.38,165.62,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.377,0.377,0.377],"y":[0,0,0]},"t":238,"s":[185.9,185.9,100]},{"i":{"x":[0.3,0.3,0.03],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":258,"s":[180.83,180.83,100]},{"t":298,"s":[169,169,100]}]}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Artboard","parent":2,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":131.801,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":145.416,"s":[-5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":161,"s":[20]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":173.553,"s":[-10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":186.26,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":198.969,"s":[-10]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":211.678,"s":[10]},{"t":231.646484375,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":125.447,"s":[-4.987,-43.326,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":139.061,"s":[-8.876,-43.326,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":154.492,"s":[5.429,-43.326,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167.199,"s":[-14.641,-43.326,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":179.906,"s":[5.429,-43.326,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":192.615,"s":[-14.641,-43.326,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":205.322,"s":[5.429,-43.326,0],"to":[0,0,0],"ti":[0,0,0]},{"t":225.29296875,"s":[-4.987,-43.326,0]}]},"a":{"a":0,"k":[-6.75,25,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":218,"s":[200,200,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":225.199,"s":[202,198,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":231.199,"s":[198,202,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":237.199,"s":[202,198,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":243.199,"s":[199,201,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":249.199,"s":[201,199,100]},{"t":260,"s":[200,200,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.872},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[-17.535,0],[-5.983,9.1],[-1.953,6.316],[-0.332,2.535],[2.701,0.416],[1.62,-3.449],[0,0],[0.582,0],[0,1.205],[0,0],[3.033,0],[0,-3.033],[0,0],[1.122,0.249],[0,0],[3.033,0],[0,-3.033],[0,0],[1.08,-0.166],[0,0],[2.992,0],[0,-3.033],[0,0],[0.997,-0.582],[0,0],[2.992,0],[0,-3.033]],"o":[[0,19.155],[11.884,0],[5.235,-6.981],[1.496,-4.612],[0.416,-2.867],[-3.158,-0.499],[0,0],[-0.499,1.08],[-0.623,0],[0,0],[0,-3.075],[-3.033,0],[0,0],[-0.997,-0.332],[0,0],[0,-3.033],[-3.033,0],[0,0],[-1.08,0.083],[0,0],[0,-3.033],[-3.033,0],[0,0],[-1.122,0.457],[0,0],[0,-3.033],[-3.033,0],[0,0]],"v":[[-35.725,16.496],[-4.769,47.243],[21.325,33.573],[31.879,9.307],[35.66,-3.698],[31.754,-9.266],[24.774,-4.571],[18.915,8.435],[17.378,9.889],[16.297,8.31],[16.297,-36.149],[10.979,-41.509],[5.66,-36.149],[5.66,-2.95],[2.544,-3.823],[2.544,-41.925],[-2.775,-47.243],[-8.093,-41.925],[-8.093,-4.238],[-11.334,-3.864],[-11.334,-38.518],[-16.611,-43.878],[-21.93,-38.518],[-21.93,-0.956],[-25.088,0.665],[-25.088,-25.554],[-30.365,-30.872],[-35.725,-25.554]],"c":true}]},{"i":{"x":0.833,"y":0.816},"o":{"x":0.167,"y":0.128},"t":129.076,"s":[{"i":[[0,0],[-17.717,0],[-5.983,9.1],[0.502,6.592],[0.621,2.48],[2.667,-0.594],[0.241,-3.803],[0,0],[0.1,-0.007],[0.071,0.603],[0,0],[3.033,0],[0,-3.033],[0,0],[0.445,0.346],[-0.697,4.465],[3,0.626],[0.886,-3.98],[0.483,-3.126],[0.384,-0.013],[0,0],[3,0.365],[0.947,-2.881],[0.147,-4.943],[0.309,-0.129],[-0.01,-0.626],[5.349,0.041],[1.214,-6.18]],"o":[[0,19.155],[12.008,0],[5.235,-6.981],[-0.302,-4.839],[-0.666,-2.819],[-3.12,0.695],[0,0],[0.001,0.619],[-0.04,0.024],[0,0],[0,-3.075],[-3.033,0],[0,0],[-0.427,-0.366],[-0.431,-3.201],[0.697,-4.465],[-3,-0.626],[0,0],[-0.63,-0.321],[0,0],[1.424,-3.762],[-4.212,-0.513],[0,0],[-0.359,-0.234],[0.322,-0.168],[0.385,-1.9],[-2.971,-0.023],[-1.265,6.44]],"v":[[-34.759,16.469],[-4.893,47.243],[21.325,33.573],[27.806,8.641],[26.549,-4.845],[20.872,-8.59],[16.103,-1.66],[16.268,-1.346],[16.199,-0.595],[16.119,-1.315],[16.333,-3.962],[11.014,-9.322],[5.696,-3.962],[5.645,-2.95],[5.978,-3.656],[6.234,-17.531],[-0.004,-25.699],[-7.528,-20.062],[-8.333,-15.773],[-8.504,-15.832],[-8.328,-15.874],[-12.607,-23.511],[-20.227,-18.365],[-21.216,-10.644],[-21.453,-10.606],[-21.028,-10.617],[-25.793,-17.44],[-33.742,-7.152]],"c":true}]},{"i":{"x":0.29,"y":1},"o":{"x":0.167,"y":0.184},"t":134.523,"s":[{"i":[[0,0],[-17.804,0],[-4.849,7.392],[-1.022,6.117],[0.414,2.013],[0.443,0.992],[3.958,-0.424],[-0.355,-0.733],[0.16,-0.149],[0.017,1.064],[0,0],[3.033,0],[0,-3.033],[0,0],[0.114,0.371],[-0.163,1.046],[3.065,0.147],[0.274,-4.55],[0.113,-0.732],[0.053,0.06],[0,0],[3.032,0.063],[0.222,-2.998],[0.035,-1.158],[0.174,-0.133],[-0.002,-0.147],[3.582,0.01],[0.177,-5.03]],"o":[[0,19.155],[12.066,0],[4.945,-7.309],[0.927,-5.135],[-0.414,-2.013],[-0.443,-0.992],[-3.506,0.375],[-0.688,0.236],[-0.128,-0.969],[0,0],[0,-3.075],[-3.033,0],[0,0],[-0.157,-0.382],[-0.101,-0.75],[0.382,-4.531],[-3.065,-0.147],[0,0],[-0.171,-0.199],[0,0],[0.552,-4.428],[-4.251,-0.089],[0,0],[-0.11,-0.112],[0.075,-0.039],[0.217,-2.624],[-3.058,-0.005],[-0.296,1.508]],"v":[[-34.301,16.457],[-4.952,47.243],[20.272,33.86],[26.857,15.388],[26.97,4.27],[25.656,0.905],[19.906,-1.337],[16.723,2.49],[16.428,3],[16.296,-2.368],[16.339,-4.774],[11.02,-10.134],[5.702,-4.774],[5.638,-2.95],[5.674,-3.689],[5.72,-9.123],[-1.197,-16.448],[-8.156,-10.48],[-8.347,-7.975],[-8.526,-8.085],[-8.447,-8.23],[-14.337,-14.938],[-20.995,-9.001],[-21.263,-6.181],[-21.368,-5.988],[-21.234,-6.26],[-27.068,-11.549],[-34.028,-3.855]],"c":true}]},{"i":{"x":0.29,"y":1},"o":{"x":0.167,"y":0},"t":140.877,"s":[{"i":[[0,0],[-17.831,0],[-4.502,6.87],[-1.488,5.972],[1.115,2.58],[0.961,0.735],[1.183,0.107],[0.899,-0.119],[0.808,-0.249],[-0.011,0.103],[0,0],[2.761,1.423],[1.999,-1],[0,0],[0.012,0.378],[0,0],[3.084,0],[0.087,-4.724],[0,0],[-0.048,0.082],[0,0],[3.042,-0.029],[0,-3.033],[0,0],[0.133,-0.134],[0,0],[3.042,0],[-0.14,-4.678]],"o":[[0,19.155],[12.084,0],[4.856,-7.41],[1.302,-5.226],[-0.593,-1.371],[-0.807,-0.618],[-1.039,-0.094],[-0.899,0.119],[-0.082,0.017],[0,0],[0,-3.075],[-2.023,-1.042],[0,0],[-0.074,-0.387],[0,0],[0.285,-4.551],[-3.084,0],[0,0],[-0.031,-0.161],[0,0],[0.285,-4.632],[-4.263,0.041],[0,0],[-0.034,-0.075],[0,0],[0.165,-2.845],[-3.084,0],[0,0]],"v":[[-34.161,16.453],[-4.97,47.243],[19.95,33.948],[26.566,17.452],[26.691,7.099],[24.629,4.422],[21.845,3.414],[18.658,3.622],[16.498,4.099],[16.317,3.966],[16.36,3.96],[11.514,-2.634],[5.714,-3.399],[5.636,-2.95],[5.581,-3.699],[5.562,-6.551],[-1.562,-13.618],[-8.349,-7.55],[-8.351,-5.59],[-8.533,-5.716],[-8.484,-5.893],[-14.867,-12.315],[-21.23,-6.137],[-21.278,-4.817],[-21.342,-4.576],[-21.297,-4.927],[-27.458,-9.747],[-34.116,-2.846]],"c":true}]},{"i":{"x":0.833,"y":0.816},"o":{"x":0.71,"y":0},"t":206.23,"s":[{"i":[[0,0],[-17.831,0],[-4.502,6.87],[-1.488,5.972],[1.115,2.58],[0.961,0.735],[1.183,0.107],[0.899,-0.119],[0.808,-0.249],[-0.011,0.103],[0,0],[2.761,1.423],[1.999,-1],[0,0],[0.012,0.378],[0,0],[3.084,0],[0.087,-4.724],[0,0],[-0.048,0.082],[0,0],[3.042,-0.029],[0,-3.033],[0,0],[0.133,-0.134],[0,0],[3.042,0],[-0.14,-4.678]],"o":[[0,19.155],[12.084,0],[4.856,-7.41],[1.302,-5.226],[-0.593,-1.371],[-0.807,-0.618],[-1.039,-0.094],[-0.899,0.119],[-0.082,0.017],[0,0],[0,-3.075],[-2.023,-1.042],[0,0],[-0.074,-0.387],[0,0],[0.285,-4.551],[-3.084,0],[0,0],[-0.031,-0.161],[0,0],[0.285,-4.632],[-4.263,0.041],[0,0],[-0.034,-0.075],[0,0],[0.165,-2.845],[-3.084,0],[0,0]],"v":[[-34.161,16.453],[-4.97,47.243],[19.95,33.948],[26.566,17.452],[26.691,7.099],[24.629,4.422],[21.845,3.414],[18.658,3.622],[16.498,4.099],[16.317,3.966],[16.36,3.96],[11.514,-2.634],[5.714,-3.399],[5.636,-2.95],[5.581,-3.699],[5.562,-6.551],[-1.562,-13.618],[-8.349,-7.55],[-8.351,-5.59],[-8.533,-5.716],[-8.484,-5.893],[-14.867,-12.315],[-21.23,-6.137],[-21.278,-4.817],[-21.342,-4.576],[-21.297,-4.927],[-27.458,-9.747],[-34.116,-2.846]],"c":true}]},{"i":{"x":0.833,"y":0.872},"o":{"x":0.167,"y":0.184},"t":212.584,"s":[{"i":[[0,0],[-17.804,0],[-4.849,7.392],[-1.022,6.117],[0.414,2.013],[0.443,0.992],[3.958,-0.424],[-0.355,-0.733],[0.16,-0.149],[0.017,1.064],[0,0],[3.033,0],[0,-3.033],[0,0],[0.114,0.371],[-0.163,1.046],[3.065,0.147],[0.274,-4.55],[0.113,-0.732],[0.053,0.06],[0,0],[3.032,0.063],[0.222,-2.998],[0.035,-1.158],[0.174,-0.133],[-0.002,-0.147],[3.582,0.01],[0.177,-5.03]],"o":[[0,19.155],[12.066,0],[4.945,-7.309],[0.927,-5.135],[-0.414,-2.013],[-0.443,-0.992],[-3.506,0.375],[-0.688,0.236],[-0.128,-0.969],[0,0],[0,-3.075],[-3.033,0],[0,0],[-0.157,-0.382],[-0.101,-0.75],[0.382,-4.531],[-3.065,-0.147],[0,0],[-0.171,-0.199],[0,0],[0.552,-4.428],[-4.251,-0.089],[0,0],[-0.11,-0.112],[0.075,-0.039],[0.217,-2.624],[-3.058,-0.005],[-0.296,1.508]],"v":[[-34.301,16.457],[-4.952,47.243],[20.272,33.86],[26.857,15.388],[26.97,4.27],[25.656,0.905],[19.906,-1.337],[16.723,2.49],[16.428,3],[16.296,-2.368],[16.339,-4.774],[11.02,-10.134],[5.702,-4.774],[5.638,-2.95],[5.674,-3.689],[5.72,-9.123],[-1.197,-16.448],[-8.156,-10.48],[-8.347,-7.975],[-8.526,-8.085],[-8.447,-8.23],[-14.337,-14.938],[-20.995,-9.001],[-21.263,-6.181],[-21.368,-5.988],[-21.234,-6.26],[-27.068,-11.549],[-34.028,-3.855]],"c":true}]},{"i":{"x":0.32,"y":1},"o":{"x":0.167,"y":0.128},"t":218.031,"s":[{"i":[[0,0],[-17.717,0],[-5.983,9.1],[0.502,6.592],[0.621,2.48],[2.667,-0.594],[0.241,-3.803],[0,0],[0.1,-0.007],[0.071,0.603],[0,0],[3.033,0],[0,-3.033],[0,0],[0.445,0.346],[-0.697,4.465],[3,0.626],[0.886,-3.98],[0.483,-3.126],[0.384,-0.013],[0,0],[3,0.365],[0.947,-2.881],[0.147,-4.943],[0.309,-0.129],[-0.01,-0.626],[5.349,0.041],[1.214,-6.18]],"o":[[0,19.155],[12.008,0],[5.235,-6.981],[-0.302,-4.839],[-0.666,-2.819],[-3.12,0.695],[0,0],[0.001,0.619],[-0.04,0.024],[0,0],[0,-3.075],[-3.033,0],[0,0],[-0.427,-0.366],[-0.431,-3.201],[0.697,-4.465],[-3,-0.626],[0,0],[-0.63,-0.321],[0,0],[1.424,-3.762],[-4.212,-0.513],[0,0],[-0.359,-0.234],[0.322,-0.168],[0.385,-1.9],[-2.971,-0.023],[-1.265,6.44]],"v":[[-34.759,16.469],[-4.893,47.243],[21.325,33.573],[27.806,8.641],[26.549,-4.845],[20.872,-8.59],[16.103,-1.66],[16.268,-1.346],[16.199,-0.595],[16.119,-1.315],[16.333,-3.962],[11.014,-9.322],[5.696,-3.962],[5.645,-2.95],[5.978,-3.656],[6.234,-17.531],[-0.004,-25.699],[-7.528,-20.062],[-8.333,-15.773],[-8.504,-15.832],[-8.328,-15.874],[-12.607,-23.511],[-20.227,-18.365],[-21.216,-10.644],[-21.453,-10.606],[-21.028,-10.617],[-25.793,-17.44],[-33.742,-7.152]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":227.107,"s":[{"i":[[0,0],[-17.535,0],[-5.983,9.1],[-1.953,6.316],[-0.332,2.535],[2.701,0.416],[1.62,-3.449],[0,0],[0.582,0],[0,1.205],[0,0],[3.033,0],[0,-3.033],[0,0],[1.122,0.249],[0,0],[3.033,0],[0,-3.033],[0,0],[1.08,-0.166],[0,0],[2.992,0],[0,-3.033],[0,0],[0.997,-0.582],[0,0],[2.992,0],[0,-3.033]],"o":[[0,19.155],[11.884,0],[5.235,-6.981],[1.496,-4.612],[0.416,-2.867],[-3.158,-0.499],[0,0],[-0.499,1.08],[-0.623,0],[0,0],[0,-3.075],[-3.033,0],[0,0],[-0.997,-0.332],[0,0],[0,-3.033],[-3.033,0],[0,0],[-1.08,0.083],[0,0],[0,-3.033],[-3.033,0],[0,0],[-1.122,0.457],[0,0],[0,-3.033],[-3.033,0],[0,0]],"v":[[-35.725,16.496],[-4.769,47.243],[21.325,33.573],[31.879,9.307],[35.66,-3.698],[31.754,-9.266],[24.774,-4.571],[18.915,8.435],[17.378,9.889],[16.297,8.31],[16.297,-36.149],[10.979,-41.509],[5.66,-36.149],[5.66,-2.95],[2.544,-3.823],[2.544,-41.925],[-2.775,-47.243],[-8.093,-41.925],[-8.093,-4.238],[-11.334,-3.864],[-11.334,-38.518],[-16.611,-43.878],[-21.93,-38.518],[-21.93,-0.956],[-25.088,0.665],[-25.088,-25.554],[-30.365,-30.872],[-35.725,-25.554]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":245,"s":[{"i":[[0,0],[-17.535,0],[-5.983,9.1],[-2.683,6.042],[-0.629,2.478],[2.63,0.741],[2.015,-3.234],[0,0],[0.582,0],[0,1.205],[0,0],[2.942,0.739],[0.739,-2.942],[0,0],[1.122,0.249],[0,0],[2.984,0.545],[0.545,-2.984],[0,0],[1.08,-0.166],[0,0],[2.966,-0.39],[-0.395,-3.007],[0,0],[0.997,-0.582],[0,0],[2.968,-0.377],[-0.383,-3.009]],"o":[[0,19.155],[11.884,0],[5.235,-6.981],[2.029,-4.404],[0.75,-2.798],[-3.077,-0.867],[0,0],[-0.622,1.014],[-0.623,0],[0,0],[0.749,-2.982],[-2.942,-0.739],[0,0],[-0.997,-0.332],[0,0],[0.545,-2.984],[-2.984,-0.545],[0,0],[-1.08,0.083],[0,0],[-0.395,-3.007],[-3.007,0.395],[0,0],[-1.122,0.457],[0,0],[-0.383,-3.009],[-3.009,0.383],[0,0]],"v":[[-35.725,16.496],[-4.769,47.243],[21.325,33.573],[32.668,10.292],[37.954,-2.177],[34.731,-8.166],[27.246,-4.326],[19.897,7.899],[17.378,9.889],[16.297,8.31],[19.838,-34.904],[15.986,-41.399],[9.522,-37.497],[5.66,-2.95],[2.544,-3.823],[4.036,-40.995],[-0.241,-47.182],[-6.428,-42.904],[-8.093,-4.238],[-11.334,-3.864],[-12.361,-39.243],[-18.291,-43.871],[-22.866,-37.864],[-21.93,-0.956],[-25.088,0.665],[-26.972,-26.217],[-32.877,-30.827],[-37.524,-24.875]],"c":true}]},{"t":275,"s":[{"i":[[0,0],[-17.535,0],[-5.983,9.1],[-1.953,6.316],[-0.332,2.535],[2.701,0.416],[1.62,-3.449],[0,0],[0.582,0],[0,1.205],[0,0],[3.033,0],[0,-3.033],[0,0],[1.122,0.249],[0,0],[3.033,0],[0,-3.033],[0,0],[1.08,-0.166],[0,0],[2.992,0],[0,-3.033],[0,0],[0.997,-0.582],[0,0],[2.992,0],[0,-3.033]],"o":[[0,19.155],[11.884,0],[5.235,-6.981],[1.496,-4.612],[0.416,-2.867],[-3.158,-0.499],[0,0],[-0.499,1.08],[-0.623,0],[0,0],[0,-3.075],[-3.033,0],[0,0],[-0.997,-0.332],[0,0],[0,-3.033],[-3.033,0],[0,0],[-1.08,0.083],[0,0],[0,-3.033],[-3.033,0],[0,0],[-1.122,0.457],[0,0],[0,-3.033],[-3.033,0],[0,0]],"v":[[-35.725,16.496],[-4.769,47.243],[21.325,33.573],[31.879,9.307],[35.66,-3.698],[31.754,-9.266],[24.774,-4.571],[18.915,8.435],[17.378,9.889],[16.297,8.31],[16.297,-36.149],[10.979,-41.509],[5.66,-36.149],[5.66,-2.95],[2.544,-3.823],[2.544,-41.925],[-2.775,-47.243],[-8.093,-41.925],[-8.093,-4.238],[-11.334,-3.864],[-11.334,-38.518],[-16.611,-43.878],[-21.93,-38.518],[-21.93,-0.956],[-25.088,0.665],[-25.088,-25.554],[-30.365,-30.872],[-35.725,-25.554]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Path","bm":0,"hd":false}],"ip":0,"op":900,"st":0,"bm":0}]} \ No newline at end of file diff --git a/TMessagesProj/src/main/res/raw/topic_bubble.svg b/TMessagesProj/src/main/res/raw/topic_bubble.svg new file mode 100644 index 000000000..613a0412b --- /dev/null +++ b/TMessagesProj/src/main/res/raw/topic_bubble.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/TMessagesProj/src/main/res/raw/topics.json b/TMessagesProj/src/main/res/raw/topics.json new file mode 100644 index 000000000..d447d3c55 --- /dev/null +++ b/TMessagesProj/src/main/res/raw/topics.json @@ -0,0 +1 @@ +{"v":"5.9.6","fr":60,"ip":0,"op":180,"w":512,"h":512,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Frame 169 Outlines 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":42,"s":[50]},{"t":55,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.6,"y":0},"t":35,"s":[256,256,0],"to":[0,34,0],"ti":[0,-34,0]},{"t":55,"s":[256,460,0]}],"ix":2,"l":2},"a":{"a":0,"k":[45,45,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.4,0.4,0.4],"y":[1,1,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":35,"s":[569,569,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":42,"s":[455.2,455.2,100]},{"t":55,"s":[569,569,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[0,1.657],[-1.657,0],[0,0],[0,-1.657],[1.657,0],[0,0]],"o":[[0,-1.657],[0,0],[1.657,0],[0,1.657],[0,0],[-1.657,0]],"v":[[-10.5,0],[-7.5,-3],[-7.329,-3],[-4.329,0],[-7.329,3],[-7.5,3]],"c":true}]},{"t":25,"s":[{"i":[[0,1.657],[-1.657,0],[0,0],[0,-1.657],[1.657,0],[0,0]],"o":[[0,-1.657],[0,0],[1.657,0],[0,1.657],[0,0],[-1.657,0]],"v":[[-10.5,0],[-7.5,-3],[7.5,-3],[10.5,0],[7.5,3],[-7.5,3]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[48.031,32.947],"ix":2},"a":{"a":0,"k":[-7.469,-0.053],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":14,"s":[0,0]},{"t":15,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[0,1.657],[-1.657,0],[0,0],[0,-1.657],[1.657,0],[0,0]],"o":[[0,-1.657],[0,0],[1.657,0],[0,1.657],[0,0],[-1.657,0]],"v":[[-15,0],[-12,-3],[-11.779,-3],[-8.779,0],[-11.779,3],[-12,3]],"c":true}]},{"t":20,"s":[{"i":[[0,1.657],[-1.657,0],[0,0],[0,-1.657],[1.657,0],[0,0]],"o":[[0,-1.657],[0,0],[1.657,0],[0,1.657],[0,0],[-1.657,0]],"v":[[-15,0],[-12,-3],[12,-3],[15,0],[12,3],[-12,3]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[48.147,21.009],"ix":2},"a":{"a":0,"k":[-11.853,0.009],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":9,"s":[0,0]},{"t":10,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-6.627,0],[0,6.627],[6.627,0],[0,-6.627]],"o":[[6.627,0],[0,-6.627],[-6.627,0],[0,6.627]],"v":[[0,12],[12,0],[0,-12],[-12,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27,27],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":5,"s":[0,0]},{"t":15,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 6","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":228,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Frame 169 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.6,"y":0},"t":35,"s":[256,256,0],"to":[0,-34,0],"ti":[0,34,0]},{"t":55,"s":[256,52,0]}],"ix":2,"l":2},"a":{"a":0,"k":[45,45,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.4,0.4,0.4],"y":[1,1,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":35,"s":[569,569,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":42,"s":[650,650,100]},{"t":55,"s":[569,569,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0,0],[0,-1.657],[-1.657,0],[0,0],[0,1.657],[1.657,0]],"o":[[-1.657,0],[0,1.657],[0,0],[1.657,0],[0,-1.657],[0,0]],"v":[[-7.5,-3],[-10.5,0],[-7.5,3],[-7.312,3],[-4.312,0],[-7.312,-3]],"c":true}]},{"t":40,"s":[{"i":[[0,0],[0,-1.657],[-1.657,0],[0,0],[0,1.657],[1.657,0]],"o":[[-1.657,0],[0,1.657],[0,0],[1.657,0],[0,-1.657],[0,0]],"v":[[-7.5,-3],[-10.5,0],[-7.5,3],[7.5,3],[10.5,0],[7.5,-3]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[48.002,68.935],"ix":2},"a":{"a":0,"k":[-7.498,-0.065],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":29,"s":[0,0]},{"t":30,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[0,0],[0,-1.657],[-1.657,0],[0,0],[0,1.657],[1.657,0]],"o":[[-1.657,0],[0,1.657],[0,0],[1.657,0],[0,-1.657],[0,0]],"v":[[-12,-3],[-15,0],[-12,3],[-11.846,3],[-8.846,0],[-11.846,-3]],"c":true}]},{"t":35,"s":[{"i":[[0,0],[0,-1.657],[-1.657,0],[0,0],[0,1.657],[1.657,0]],"o":[[-1.657,0],[0,1.657],[0,0],[1.657,0],[0,-1.657],[0,0]],"v":[[-12,-3],[-15,0],[-12,3],[12,3],[15,0],[12,-3]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[47.979,56.922],"ix":2},"a":{"a":0,"k":[-12.021,-0.078],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":24,"s":[0,0]},{"t":25,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-6.627,0],[0,6.627],[6.627,0],[0,-6.627]],"o":[[6.627,0],[0,-6.627],[-6.627,0],[0,6.627]],"v":[[0,12],[12,0],[0,-12],[-12,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27,63],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":20,"s":[0,0]},{"t":30,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":225,"st":0,"ct":1,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/TMessagesProj/src/main/res/raw/voice_to_text.json b/TMessagesProj/src/main/res/raw/voice_to_text.json new file mode 100644 index 000000000..f1130e622 --- /dev/null +++ b/TMessagesProj/src/main/res/raw/voice_to_text.json @@ -0,0 +1 @@ +{"v":"5.9.6","fr":60,"ip":0,"op":120,"w":512,"h":512,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Artboard Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":8,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":9,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":10,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":11,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":13,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":14,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":15,"s":[0]},{"i":{"x":[0.833],"y":[0.479]},"o":{"x":[0.167],"y":[0]},"t":16,"s":[0]},{"i":{"x":[0.833],"y":[0.715]},"o":{"x":[0.167],"y":[0.083]},"t":17,"s":[0]},{"i":{"x":[0.833],"y":[0.865]},"o":{"x":[0.167],"y":[0.118]},"t":18,"s":[6.247]},{"i":{"x":[0.833],"y":[0.742]},"o":{"x":[0.167],"y":[0.219]},"t":19,"s":[21.333]},{"i":{"x":[0.833],"y":[0.839]},"o":{"x":[0.167],"y":[0.123]},"t":20,"s":[30.627]},{"i":{"x":[0.833],"y":[0.88]},"o":{"x":[0.167],"y":[0.172]},"t":21,"s":[50.16]},{"i":{"x":[0.833],"y":[0.781]},"o":{"x":[0.167],"y":[0.273]},"t":22,"s":[68.438]},{"i":{"x":[0.833],"y":[0.86]},"o":{"x":[0.167],"y":[0.134]},"t":23,"s":[76.448]},{"i":{"x":[0.833],"y":[0.889]},"o":{"x":[0.167],"y":[0.207]},"t":24,"s":[89.501]},{"i":{"x":[0.833],"y":[0.828]},"o":{"x":[0.167],"y":[0.339]},"t":25,"s":[98.316]},{"i":{"x":[0.833],"y":[0.911]},"o":{"x":[0.167],"y":[0.161]},"t":26,"s":[101.192]},{"i":{"x":[0.833],"y":[1.198]},"o":{"x":[0.167],"y":[1.343]},"t":27,"s":[104.264]},{"i":{"x":[0.833],"y":[0.627]},"o":{"x":[0.167],"y":[0.059]},"t":28,"s":[104.467]},{"i":{"x":[0.833],"y":[0.809]},"o":{"x":[0.167],"y":[0.107]},"t":29,"s":[103.781]},{"i":{"x":[0.833],"y":[0.873]},"o":{"x":[0.167],"y":[0.148]},"t":30,"s":[101.394]},{"i":{"x":[0.833],"y":[0.76]},"o":{"x":[0.167],"y":[0.244]},"t":31,"s":[98.311]},{"i":{"x":[0.833],"y":[0.847]},"o":{"x":[0.167],"y":[0.128]},"t":32,"s":[96.713]},{"i":{"x":[0.833],"y":[0.883]},"o":{"x":[0.167],"y":[0.184]},"t":33,"s":[93.707]},{"i":{"x":[0.833],"y":[0.793]},"o":{"x":[0.167],"y":[0.291]},"t":34,"s":[91.209]},{"i":{"x":[0.833],"y":[0.87]},"o":{"x":[0.167],"y":[0.14]},"t":35,"s":[90.206]},{"i":{"x":[0.833],"y":[0.897]},"o":{"x":[0.167],"y":[0.233]},"t":36,"s":[88.722]},{"i":{"x":[0.833],"y":[0.891]},"o":{"x":[0.167],"y":[0.428]},"t":37,"s":[87.893]},{"i":{"x":[0.833],"y":[1.284]},"o":{"x":[0.167],"y":[0.359]},"t":38,"s":[87.692]},{"i":{"x":[0.833],"y":[0.851]},"o":{"x":[0.167],"y":[0.064]},"t":39,"s":[87.632]},{"i":{"x":[0.833],"y":[0.72]},"o":{"x":[0.167],"y":[0.189]},"t":40,"s":[87.899]},{"i":{"x":[0.833],"y":[0.831]},"o":{"x":[0.167],"y":[0.119]},"t":41,"s":[88.111]},{"i":{"x":[0.833],"y":[0.878]},"o":{"x":[0.167],"y":[0.164]},"t":42,"s":[88.609]},{"i":{"x":[0.833],"y":[0.773]},"o":{"x":[0.167],"y":[0.262]},"t":43,"s":[89.124]},{"i":{"x":[0.833],"y":[0.855]},"o":{"x":[0.167],"y":[0.132]},"t":44,"s":[89.364]},{"i":{"x":[0.833],"y":[0.887]},"o":{"x":[0.167],"y":[0.196]},"t":45,"s":[89.778]},{"i":{"x":[0.833],"y":[0.81]},"o":{"x":[0.167],"y":[0.314]},"t":46,"s":[90.085]},{"i":{"x":[0.833],"y":[0.887]},"o":{"x":[0.167],"y":[0.148]},"t":47,"s":[90.195]},{"i":{"x":[0.833],"y":[0.919]},"o":{"x":[0.167],"y":[0.314]},"t":48,"s":[90.337]},{"i":{"x":[0.833],"y":[-1.909]},"o":{"x":[0.167],"y":[-3.478]},"t":49,"s":[90.389]},{"i":{"x":[0.833],"y":[0.768]},"o":{"x":[0.167],"y":[0.086]},"t":50,"s":[90.387]},{"i":{"x":[0.833],"y":[0.869]},"o":{"x":[0.167],"y":[0.13]},"t":51,"s":[90.347]},{"i":{"x":[0.833],"y":[0.748]},"o":{"x":[0.167],"y":[0.228]},"t":52,"s":[90.274]},{"i":{"x":[0.833],"y":[0.842]},"o":{"x":[0.167],"y":[0.125]},"t":53,"s":[90.233]},{"i":{"x":[0.833],"y":[0.881]},"o":{"x":[0.167],"y":[0.176]},"t":54,"s":[90.148]},{"i":{"x":[0.833],"y":[0.785]},"o":{"x":[0.167],"y":[0.279]},"t":55,"s":[90.073]},{"i":{"x":[0.833],"y":[0.863]},"o":{"x":[0.167],"y":[0.136]},"t":56,"s":[90.04]},{"i":{"x":[0.833],"y":[0.891]},"o":{"x":[0.167],"y":[0.213]},"t":57,"s":[89.989]},{"i":{"x":[0.833],"y":[0.84]},"o":{"x":[0.167],"y":[0.356]},"t":58,"s":[89.956]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.174]},"t":59,"s":[89.946]},{"t":60,"s":[89.937]}],"ix":10},"p":{"a":0,"k":[256,256,0],"ix":2,"l":2},"a":{"a":0,"k":[150,150,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[-0.154,-0.154,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.633,0.633,0]},"t":8,"s":[136.531,136.531,100]},{"i":{"x":[0.833,0.833,0.833],"y":[-0.395,-0.395,1]},"o":{"x":[0.167,0.167,0.167],"y":[1.154,1.154,0]},"t":9,"s":[136.531,136.531,100]},{"i":{"x":[0.833,0.833,0.833],"y":[-0.828,-0.828,1]},"o":{"x":[0.167,0.167,0.167],"y":[1.395,1.395,0]},"t":10,"s":[136.531,136.531,100]},{"i":{"x":[0.833,0.833,0.833],"y":[-1.604,-1.604,1]},"o":{"x":[0.167,0.167,0.167],"y":[1.828,1.828,0]},"t":11,"s":[136.531,136.531,100]},{"i":{"x":[0.833,0.833,0.833],"y":[-1.571,-1.571,1]},"o":{"x":[0.167,0.167,0.167],"y":[2.604,2.604,0]},"t":12,"s":[136.531,136.531,100]},{"i":{"x":[0.833,0.833,0.833],"y":[-0.785,-0.785,1]},"o":{"x":[0.167,0.167,0.167],"y":[2.571,2.571,0]},"t":13,"s":[136.531,136.531,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.708,0.708,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.362,0.362,0]},"t":14,"s":[136.531,136.531,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.885,0.885,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.134,0.134,0]},"t":15,"s":[141.464,141.464,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.809,0.809,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.306,0.306,0]},"t":16,"s":[152.189,152.189,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.891,0.891,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.148,0.148,0]},"t":17,"s":[156.208,156.208,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.939,0.939,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.357,0.357,0]},"t":18,"s":[161.42,161.42,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.357,0.357,1]},"o":{"x":[0.167,0.167,0.167],"y":[-0.228,-0.228,0]},"t":19,"s":[163.007,163.007,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.776,0.776,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.096,0.096,0]},"t":20,"s":[162.582,162.582,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.867,0.867,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.133,0.133,0]},"t":21,"s":[159.727,159.727,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.739,0.739,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.223,0.223,0]},"t":22,"s":[154.892,154.892,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.835,0.835,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.122,0.122,0]},"t":23,"s":[152.008,152.008,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.878,0.878,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.168,0.168,0]},"t":24,"s":[145.843,145.843,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.774,0.774,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.265,0.265,0]},"t":25,"s":[139.793,139.793,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.857,0.857,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.132,0.132,0]},"t":26,"s":[137.015,137.015,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.888,0.888,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.199,0.199,0]},"t":27,"s":[132.26,132.26,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.827,0.827,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.327,0.327,0]},"t":28,"s":[128.827,128.827,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.92,0.92,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.161,0.161,0]},"t":29,"s":[127.655,127.655,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.264,0.264,1]},"o":{"x":[0.167,0.167,0.167],"y":[-1.748,-1.748,0]},"t":30,"s":[126.391,126.391,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.629,0.629,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.094,0.094,0]},"t":31,"s":[126.449,126.449,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.802,0.802,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.107,0.107,0]},"t":32,"s":[126.899,126.899,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.871,0.871,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.144,0.144,0]},"t":33,"s":[128.454,128.454,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.748,0.748,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.234,0.234,0]},"t":34,"s":[130.597,130.597,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.84,0.84,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.124,0.124,0]},"t":35,"s":[131.782,131.782,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.88,0.88,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.174,0.174,0]},"t":36,"s":[134.184,134.184,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.781,0.781,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.273,0.273,0]},"t":37,"s":[136.404,136.404,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.862,0.862,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.135,0.135,0]},"t":38,"s":[137.378,137.378,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.892,0.892,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.211,0.211,0]},"t":39,"s":[138.962,138.962,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.859,0.859,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.366,0.366,0]},"t":40,"s":[139.998,139.998,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1.018,1.018,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.205,0.205,0]},"t":41,"s":[140.303,140.303,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.829,0.829,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.015,0.015,0]},"t":42,"s":[140.514,140.514,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.688,0.688,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.163,0.163,0]},"t":43,"s":[140.257,140.257,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.816,0.816,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.114,0.114,0]},"t":44,"s":[139.988,139.988,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.873,0.873,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.152,0.152,0]},"t":45,"s":[139.248,139.248,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.756,0.756,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.243,0.243,0]},"t":46,"s":[138.35,138.35,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.844,0.844,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.126,0.126,0]},"t":47,"s":[137.882,137.882,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.882,0.882,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.179,0.179,0]},"t":48,"s":[136.976,136.976,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.789,0.789,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.283,0.283,0]},"t":49,"s":[136.186,136.186,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.869,0.869,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.138,0.138,0]},"t":50,"s":[135.857,135.857,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.898,0.898,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.23,0.23,0]},"t":51,"s":[135.352,135.352,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.942,0.942,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.463,0.463,0]},"t":52,"s":[135.066,135.066,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.124,0.124,1]},"o":{"x":[0.167,0.167,0.167],"y":[-0.196,-0.196,0]},"t":53,"s":[135.003,135.003,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.854,0.854,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.092,0.092,0]},"t":54,"s":[135.022,135.022,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.714,0.714,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.194,0.194,0]},"t":55,"s":[135.2,135.2,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.824,0.824,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.118,0.118,0]},"t":56,"s":[135.334,135.334,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.875,0.875,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.159,0.159,0]},"t":57,"s":[135.66,135.66,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.762,0.762,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.251,0.251,0]},"t":58,"s":[136.021,136.021,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.128,0.128,0]},"t":59,"s":[136.2,136.2,100]},{"t":60,"s":[136.531,136.531,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":19,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[150,80],[150,150]],"c":false}]},{"t":44,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[170,50],[170,120]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":20,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":19,"s":[{"i":[[0,0],[0,0],[0,-10],[0,0]],"o":[[0,0],[0,10],[0,0],[0,0]],"v":[[150,80],[150,100],[150,130],[150,150]],"c":false}]},{"t":44,"s":[{"i":[[0,0],[0,0],[-9.267,-4.333],[0,0]],"o":[[0,0],[-9.267,4.333],[0,0],[0,0]],"v":[[200,35],[94.267,75.667],[94.267,94.333],[200,135]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":19,"s":[100]},{"t":33,"s":[20]}],"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":19,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[150,270],[150,230]],"c":false}]},{"t":44,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[150,270],[150,185]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":20,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":19,"s":[{"i":[[0,0],[-44.183,0],[0,44.183]],"o":[[0,44.183],[44.183,0],[0,0]],"v":[[-80,-40],[0,40],[80,-40]],"c":false}]},{"t":36,"s":[{"i":[[0,0],[0,0],[-10,-10]],"o":[[10,-10],[0,0],[0,0]],"v":[[-50,30],[0,-15],[50,30]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":20,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[150,190],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":123,"st":0,"ct":1,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/TMessagesProj/src/main/res/values/ids.xml b/TMessagesProj/src/main/res/values/ids.xml index ece991384..22500c8d3 100644 --- a/TMessagesProj/src/main/res/values/ids.xml +++ b/TMessagesProj/src/main/res/values/ids.xml @@ -10,6 +10,8 @@ + + @@ -21,6 +23,7 @@ + diff --git a/TMessagesProj/src/main/res/values/strings.xml b/TMessagesProj/src/main/res/values/strings.xml index 16f2113be..115cc3afd 100644 --- a/TMessagesProj/src/main/res/values/strings.xml +++ b/TMessagesProj/src/main/res/values/strings.xml @@ -277,8 +277,8 @@ Underline Regular **Telegram** needs access to your contacts so that you can connect with your friends across all your devices. Your contacts will be continuously synced with Telegram\'s heavily encrypted cloud servers. - NOT NOW - CONTINUE + Not now + Continue Your contacts on Telegram Connecting your contacts... This is your Archive @@ -676,7 +676,7 @@ Unlink Group Unlink Channel Unlink - LINK GROUP + Link Group Do you want to make **%1$s** the discussion board for **%2$s**? Do you want to make **%1$s** the discussion board for **%2$s**?\n\nAny member of this group will be able to see messages in the channel. Do you want to make **%1$s** the discussion board for **%2$s**?\n\nAnyone from the channel will be able to see messages in this group. @@ -883,6 +883,16 @@ un1 unmuted un2 un1 allowed new participants to speak un1 muted new participants + un1 changed group usernames from %1$s to %2$s + un1 deactivated %1$s username + un1 activated %1$s username + un1 changed group to forum + un1 changed forum to group + un1 created topic un2 + un1 changed topic name from un2 to un3 + un1 delete topic un2 + un1 pin topic un2 + un1 unpin topic un2 New Broadcast List Enter list name @@ -962,6 +972,7 @@ VIEW MESSAGE OPEN BOT sponsored + recommended What are sponsored\nmessages? Unlike other apps, Telegram never uses your private data to target ads. You are seeing this message only because someone chose this public one-to-many channel as a space to promote their messages. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored message. Unlike other apps, Telegram doesn\'t track whether you tapped on a sponsored message and doesn\'t profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can’t spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that. @@ -1647,7 +1658,9 @@ %s deleted from your contacts Username + also %1$s Your Username + username Sorry, this username is already taken. Sorry, this username is invalid. A username must have at least 5 characters. @@ -1819,8 +1832,8 @@ Chat list view Two lines Three lines - CREATE THEME - APPLY + Create Theme + Apply Theme Preview Select color Color Themes @@ -3428,6 +3441,7 @@ You You took a screenshot! un1 took a screenshot! + Album Limit Reached Inactive chats @@ -4828,6 +4842,8 @@ %1$d Dialogs unpinned Forward media Would you like to forward... + Save media + Would you like to save... This photo This media All %1$d photos @@ -4893,6 +4909,7 @@ Show accounts Hide accounts Go to message + Cancel reply Cancel forward Cancel editing @@ -5158,8 +5175,10 @@ Dismiss request requested to join %1$s added by %1$s %2$s - %1$s has been added to the group - %1$s has been added to the channel + %1$s added to the group + %1$s added to the channel + **%d members** added to the group + **%d subscribers** added to the channel Search member requests Your request to join the group was approved Your request to join the channel was approved @@ -5184,6 +5203,7 @@ Сopying and forwarding is not allowed in this channel. Сopying and forwarding is not allowed in this group. Copying and forwarding is not allowed from this bot. + This topic contains **%s** This message contains **%s** This message contains emoji from %s pack. This message contains emoji from **%d Packs**. @@ -5450,6 +5470,7 @@ Sorry, you can\'t add more than **%1$d** chats to a folder. Please create a new one. We are working to let you increase this limit in the future. You are a member of **%1$d** groups and channels. Please leave some before joining a new one — or upgrade to **Telegram Premium** to double the limit to **%2$d** groups and channels. You are a member of **%1$d** groups and channels. Please leave some before joining a new one. + "Subscribe to **Telegram Premium** to move the \"%1$s\" folder." You are a member of **%1$d** groups and channels. Please leave some before joining a new one. We are working to let you increase this limit in the future. Premium account Free @@ -5585,6 +5606,7 @@ Subscribe to **Telegram Premium** to unlock this emoji. Subscribe to **Telegram Premium** to unlock this reaction. Try sending these emoji in **Saved Messages** for free to test. + Subscribe to **Telegram Premium** to convert voice to text. More Unlock Restore @@ -5626,6 +5648,124 @@ Headset Headset (if connected) Using a headset microphone may reduce background noise but will slightly decrease audio quality. - %s Reaction + Enable alternative navigation + Disable alternative navigation + Reacted with %s to your message Search reactions + Not available + + Set username + Links order + Usernames order + Activate link + Deactivate link + tap to edit + active + not active + tap to edit + active + not active + Do you want to show this link on your info page? + Do you want to hide this link from your info page? + Do you want to show this link on the channel info page? + Do you want to hide this link from the channel info page? + Too many active links + Sorry, you have too many active public links already. Please hide one of your active public links first. + Hide + Show + Drag and drop links to change the order in which they will be displayed on your info page. + Drag and drop links to change the order in which they will be displayed on the channel info page. + Topics + New Topic + Choose topic title and icon + What do you want to discuss? + Topic was created + Create topic + Discard topic + Topic isn’t created, because you haven\'t posted a message.\n\nDo you want to discard this topic? + Discard + Edit topic + No topics here yet + You can start you first topic or tap on the\n%sicon to switch to the messages list. + Tap to create a topic + View as messages + Close topic + Reopen topic + Close topics + Reopen topics + View as topics + Are you sure you want delete these topics? + Are you sure you want delete %s? + Delete topic + Delete topics + Delete topics + Delete topics + Delete topics + Topics + Add + Chat History + Search icons + No icons found + Select topic + There is **%d topic** that is listed as exception. + There are **%d topics** that are listed as exceptions. + There are **%d topics** that are listed as exceptions. + There are **%d topics** that are listed as exceptions. + There are **%d topics** that are listed as exceptions. + In %s + Topic closed + Topic reopened + %s closed topic + %s reopened topic + The topic is closed + Almost done + Send first message to start the topic + %s, %d exception + %s, %d exceptions + %s, %d exceptions + %s, %d exceptions + %s, %d exceptions + Enable floating debug + Disable floating debug + Cleared local database. + Toggle navigation stiffness controls + Clear Send Message As... peers + Switch sharing alert dialogs mode (%1$s) + Normal + Less + More + Toggle sharing alert topics slow motion + The group chat will be divided into topics created by admins or users. + Swipe to Reply + Swipe left on any message to reply to it. + View in Topic + Pin this message in the topic? + Swipe left on a message to reply + Only groups with more than **%1$d member** can have topics enabled. + Only groups with more than **%1$d members** can have topics enabled. + Only groups with more than **%1$d members** can have topics enabled. + Only groups with more than **%1$d members** can have topics enabled. + Topics are currently unavailable in groups connected to channels. + topic creator + Manage topics + Create topics + %1$s renamed topic to %2$s + Topic icon changed + %1$s changed topic icon to %2$s + Topic deleted. + Topics deleted + Topics deleted + Topics deleted + Topic renamed + %s was created + %1$s closed %2$s + %1$s reopened %2$s + %1$s renamed topic to %2$s + %1$s changed the topic title and icon to %2$s + %1$s changed the topic icon to %2$s + %1$s changed the topic title and icon to %2$s + renamed topic to %s + changed topic icon to %s + changed the topic title and icon to %s + Select Topic Icon diff --git a/gradle.properties b/gradle.properties index 09e4ebed2..3281f3764 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,8 +13,8 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true #Sat Mar 12 05:53:50 MSK 2016 -APP_VERSION_NAME=9.0.2 -APP_VERSION_CODE=2808 +APP_VERSION_NAME=9.1.0 +APP_VERSION_CODE=2885 APP_PACKAGE=org.telegram.messenger RELEASE_KEY_PASSWORD=TelegramAndroidPswd RELEASE_KEY_ALIAS=tmessages