From ff5735503e068a6f1cada09b977f633df7caf98d Mon Sep 17 00:00:00 2001 From: DrKLO Date: Thu, 18 Jul 2019 20:01:39 +0700 Subject: [PATCH] Update to 5.9.0 --- TMessagesProj/build.gradle | 21 +- TMessagesProj/jni/Android.mk | 88 +- TMessagesProj/jni/Application.mk | 2 +- TMessagesProj/jni/TgNetWrapper.cpp | 21 +- TMessagesProj/jni/image.c | 94 +- TMessagesProj/jni/libtgvoip/BlockingQueue.h | 4 +- .../jni/libtgvoip/VoIPController.cpp | 4 +- .../libtgvoip/client/android/tg_voip_jni.cpp | 3 - TMessagesProj/jni/lottie.cpp | 237 + TMessagesProj/jni/lz4/lz4.c | 2299 +++++ TMessagesProj/jni/lz4/lz4.h | 682 ++ TMessagesProj/jni/lz4/lz4frame.c | 1838 ++++ TMessagesProj/jni/lz4/lz4frame.h | 606 ++ TMessagesProj/jni/lz4/lz4frame_static.h | 47 + TMessagesProj/jni/lz4/lz4hc.c | 1476 ++++ TMessagesProj/jni/lz4/lz4hc.h | 435 + TMessagesProj/jni/lz4/xxhash.c | 1030 +++ TMessagesProj/jni/lz4/xxhash.h | 328 + TMessagesProj/jni/rlottie/inc/rlottie.h | 438 + TMessagesProj/jni/rlottie/inc/rlottie_capi.h | 241 + TMessagesProj/jni/rlottie/inc/rlottiecommon.h | 237 + .../jni/rlottie/licenses/COPYING.FTL | 166 + .../jni/rlottie/licenses/COPYING.LGPL | 504 ++ .../jni/rlottie/licenses/COPYING.PIX | 42 + .../jni/rlottie/licenses/COPYING.RPD | 57 + .../jni/rlottie/licenses/COPYING.STB | 17 + .../rlottie/src/lottie/lottieanimation.cpp | 292 + .../jni/rlottie/src/lottie/lottieitem.cpp | 1732 ++++ .../jni/rlottie/src/lottie/lottieitem.h | 525 ++ .../jni/rlottie/src/lottie/lottiekeypath.cpp | 86 + .../jni/rlottie/src/lottie/lottiekeypath.h | 46 + .../jni/rlottie/src/lottie/lottieloader.cpp | 124 + .../jni/rlottie/src/lottie/lottieloader.h | 38 + .../jni/rlottie/src/lottie/lottiemodel.cpp | 324 + .../jni/rlottie/src/lottie/lottiemodel.h | 970 +++ .../jni/rlottie/src/lottie/lottieparser.cpp | 2620 ++++++ .../jni/rlottie/src/lottie/lottieparser.h | 36 + .../rlottie/src/lottie/lottieproxymodel.cpp | 0 .../jni/rlottie/src/lottie/lottieproxymodel.h | 335 + .../rlottie/src/lottie/rapidjson/allocators.h | 284 + .../lottie/rapidjson/cursorstreamwrapper.h | 78 + .../rlottie/src/lottie/rapidjson/document.h | 2652 ++++++ .../src/lottie/rapidjson/encodedstream.h | 299 + .../rlottie/src/lottie/rapidjson/encodings.h | 716 ++ .../rlottie/src/lottie/rapidjson/error/en.h | 74 + .../src/lottie/rapidjson/error/error.h | 161 + .../src/lottie/rapidjson/filereadstream.h | 99 + .../src/lottie/rapidjson/filewritestream.h | 104 + .../jni/rlottie/src/lottie/rapidjson/fwd.h | 151 + .../lottie/rapidjson/internal/biginteger.h | 290 + .../src/lottie/rapidjson/internal/diyfp.h | 271 + .../src/lottie/rapidjson/internal/dtoa.h | 245 + .../src/lottie/rapidjson/internal/ieee754.h | 78 + .../src/lottie/rapidjson/internal/itoa.h | 308 + .../src/lottie/rapidjson/internal/meta.h | 186 + .../src/lottie/rapidjson/internal/pow10.h | 55 + .../src/lottie/rapidjson/internal/regex.h | 740 ++ .../src/lottie/rapidjson/internal/stack.h | 232 + .../src/lottie/rapidjson/internal/strfunc.h | 69 + .../src/lottie/rapidjson/internal/strtod.h | 290 + .../src/lottie/rapidjson/internal/swap.h | 46 + .../src/lottie/rapidjson/istreamwrapper.h | 128 + .../src/lottie/rapidjson/memorybuffer.h | 70 + .../src/lottie/rapidjson/memorystream.h | 71 + .../lottie/rapidjson/msinttypes/inttypes.h | 316 + .../src/lottie/rapidjson/msinttypes/stdint.h | 300 + .../src/lottie/rapidjson/ostreamwrapper.h | 81 + .../rlottie/src/lottie/rapidjson/pointer.h | 1414 ++++ .../src/lottie/rapidjson/prettywriter.h | 277 + .../rlottie/src/lottie/rapidjson/rapidjson.h | 656 ++ .../jni/rlottie/src/lottie/rapidjson/reader.h | 2230 +++++ .../jni/rlottie/src/lottie/rapidjson/schema.h | 2497 ++++++ .../jni/rlottie/src/lottie/rapidjson/stream.h | 250 + .../src/lottie/rapidjson/stringbuffer.h | 121 + .../jni/rlottie/src/lottie/rapidjson/writer.h | 709 ++ TMessagesProj/jni/rlottie/src/vector/config.h | 13 + .../rlottie/src/vector/freetype/v_ft_math.cpp | 456 + .../rlottie/src/vector/freetype/v_ft_math.h | 438 + .../src/vector/freetype/v_ft_raster.cpp | 1456 ++++ .../rlottie/src/vector/freetype/v_ft_raster.h | 613 ++ .../src/vector/freetype/v_ft_stroker.cpp | 1917 +++++ .../src/vector/freetype/v_ft_stroker.h | 319 + .../rlottie/src/vector/freetype/v_ft_types.h | 160 + .../src/vector/pixman/pixman-arm-neon-asm.S | 497 ++ .../src/vector/pixman/pixman-arm-neon-asm.h | 1126 +++ .../vector/pixman/pixman-arma64-neon-asm.S | 418 + .../vector/pixman/pixman-arma64-neon-asm.h | 1223 +++ .../jni/rlottie/src/vector/pixman/vregion.cpp | 2086 +++++ .../jni/rlottie/src/vector/pixman/vregion.h | 89 + .../jni/rlottie/src/vector/stb/stb_image.cpp | 51 + .../jni/rlottie/src/vector/stb/stb_image.h | 7468 +++++++++++++++++ .../jni/rlottie/src/vector/vbezier.cpp | 138 + .../jni/rlottie/src/vector/vbezier.h | 134 + .../jni/rlottie/src/vector/vbitmap.cpp | 227 + .../jni/rlottie/src/vector/vbitmap.h | 59 + .../jni/rlottie/src/vector/vbrush.cpp | 114 + TMessagesProj/jni/rlottie/src/vector/vbrush.h | 95 + .../src/vector/vcompositionfunctions.cpp | 174 + .../jni/rlottie/src/vector/vcowptr.h | 122 + .../jni/rlottie/src/vector/vdasher.cpp | 215 + .../jni/rlottie/src/vector/vdasher.h | 57 + .../jni/rlottie/src/vector/vdebug.cpp | 754 ++ TMessagesProj/jni/rlottie/src/vector/vdebug.h | 183 + .../jni/rlottie/src/vector/vdrawable.cpp | 91 + .../jni/rlottie/src/vector/vdrawable.h | 68 + .../jni/rlottie/src/vector/vdrawhelper.cpp | 925 ++ .../jni/rlottie/src/vector/vdrawhelper.h | 248 + .../rlottie/src/vector/vdrawhelper_neon.cpp | 27 + .../jni/rlottie/src/vector/velapsedtimer.cpp | 47 + .../jni/rlottie/src/vector/velapsedtimer.h | 37 + .../jni/rlottie/src/vector/vglobal.h | 323 + .../jni/rlottie/src/vector/vimageloader.cpp | 226 + .../jni/rlottie/src/vector/vimageloader.h | 26 + .../jni/rlottie/src/vector/vinterpolator.cpp | 154 + .../jni/rlottie/src/vector/vinterpolator.h | 84 + TMessagesProj/jni/rlottie/src/vector/vline.h | 92 + .../jni/rlottie/src/vector/vmatrix.cpp | 732 ++ .../jni/rlottie/src/vector/vmatrix.h | 102 + .../jni/rlottie/src/vector/vpainter.cpp | 206 + .../jni/rlottie/src/vector/vpainter.h | 60 + .../jni/rlottie/src/vector/vpath.cpp | 699 ++ TMessagesProj/jni/rlottie/src/vector/vpath.h | 273 + .../jni/rlottie/src/vector/vpathmesure.cpp | 61 + .../jni/rlottie/src/vector/vpathmesure.h | 38 + TMessagesProj/jni/rlottie/src/vector/vpoint.h | 207 + .../jni/rlottie/src/vector/vraster.cpp | 444 + .../jni/rlottie/src/vector/vraster.h | 46 + .../jni/rlottie/src/vector/vrect.cpp | 64 + TMessagesProj/jni/rlottie/src/vector/vrect.h | 166 + TMessagesProj/jni/rlottie/src/vector/vrle.cpp | 731 ++ TMessagesProj/jni/rlottie/src/vector/vrle.h | 198 + .../jni/rlottie/src/vector/vstackallocator.h | 152 + .../jni/rlottie/src/vector/vtaskqueue.h | 83 + TMessagesProj/jni/tgnet/ApiScheme.h | 1 - .../jni/tgnet/ConnectionsManager.cpp | 154 +- TMessagesProj/jni/tgnet/ConnectionsManager.h | 6 +- TMessagesProj/jni/tgnet/Datacenter.cpp | 4 + TMessagesProj/jni/tgnet/Datacenter.h | 2 +- TMessagesProj/jni/tgnet/Defines.h | 7 - TMessagesProj/jni/tgnet/MTProtoScheme.cpp | 143 + TMessagesProj/jni/tgnet/MTProtoScheme.h | 84 +- TMessagesProj/jni/tgnet/Request.h | 1 - .../telegram/messenger/AccountInstance.java | 38 +- .../telegram/messenger/AndroidUtilities.java | 28 + .../telegram/messenger/ApplicationLoader.java | 16 +- .../messenger/AutoMessageHeardReceiver.java | 30 + .../telegram/messenger/BaseController.java | 78 + .../org/telegram/messenger/BuildVars.java | 4 +- .../messenger/ContactsController.java | 242 +- .../messenger/DownloadController.java | 92 +- .../telegram/messenger/FileLoadOperation.java | 16 +- .../org/telegram/messenger/FileLoader.java | 22 +- .../telegram/messenger/FileRefController.java | 106 +- .../messenger/GcmPushListenerService.java | 14 +- .../org/telegram/messenger/ImageLoader.java | 153 +- .../org/telegram/messenger/ImageLocation.java | 9 +- .../org/telegram/messenger/ImageReceiver.java | 203 +- .../telegram/messenger/LocaleController.java | 56 + .../messenger/LocationController.java | 479 +- .../messenger/LocationSharingService.java | 37 +- .../java/org/telegram/messenger/LruCache.java | 7 + .../telegram/messenger/MediaController.java | 4 +- ...ataQuery.java => MediaDataController.java} | 863 +- .../org/telegram/messenger/MessageObject.java | 1148 +-- .../messenger/MessagesController.java | 1889 +++-- .../telegram/messenger/MessagesStorage.java | 297 +- .../messenger/MusicBrowserService.java | 4 +- .../messenger/NotificationCenter.java | 30 +- .../messenger/NotificationsController.java | 272 +- .../telegram/messenger/SecretChatHelper.java | 313 +- .../messenger/SendMessagesHelper.java | 648 +- .../org/telegram/messenger/SharedConfig.java | 54 +- .../telegram/messenger/StatsController.java | 17 +- .../org/telegram/messenger/UserConfig.java | 9 +- .../telegram/messenger/WearReplyReceiver.java | 42 +- .../messenger/voip/VoIPBaseService.java | 3 - .../telegram/tgnet/ConnectionsManager.java | 160 +- .../main/java/org/telegram/tgnet/TLRPC.java | 975 ++- .../org/telegram/ui/ActionBar/ActionBar.java | 4 + .../ui/ActionBar/ActionBarLayout.java | 38 +- .../telegram/ui/ActionBar/ActionBarMenu.java | 48 +- .../ui/ActionBar/ActionBarMenuItem.java | 61 +- .../telegram/ui/ActionBar/BaseFragment.java | 52 +- .../telegram/ui/ActionBar/BottomSheet.java | 10 + .../ui/ActionBar/DarkAlertDialog.java | 18 +- .../java/org/telegram/ui/ActionBar/Theme.java | 158 +- .../ui/ActionBar/ThemeDescription.java | 21 +- .../org/telegram/ui/ActionIntroActivity.java | 596 ++ .../telegram/ui/Adapters/ContactsAdapter.java | 50 +- .../telegram/ui/Adapters/DialogsAdapter.java | 99 +- .../ui/Adapters/DialogsSearchAdapter.java | 201 +- .../ui/Adapters/LocationActivityAdapter.java | 130 +- .../telegram/ui/Adapters/MentionsAdapter.java | 10 +- .../telegram/ui/Adapters/SearchAdapter.java | 192 +- .../ui/Adapters/SearchAdapterHelper.java | 53 +- .../telegram/ui/Adapters/StickersAdapter.java | 127 +- .../telegram/ui/ArchivedStickersActivity.java | 12 +- .../java/org/telegram/ui/ArticleViewer.java | 24 + .../org/telegram/ui/CacheControlActivity.java | 4 +- .../java/org/telegram/ui/CallLogActivity.java | 65 +- .../ui/Cells/ArchivedStickerSetCell.java | 15 +- .../java/org/telegram/ui/Cells/BaseCell.java | 2 - .../telegram/ui/Cells/ChatMessageCell.java | 127 +- .../telegram/ui/Cells/ContextLinkCell.java | 15 +- .../org/telegram/ui/Cells/DialogCell.java | 73 +- .../telegram/ui/Cells/DrawerProfileCell.java | 8 +- .../ui/Cells/FeaturedStickerSetCell.java | 23 +- .../ui/Cells/FeaturedStickerSetInfoCell.java | 8 +- .../ui/Cells/GroupCreateUserCell.java | 2 + .../org/telegram/ui/Cells/InviteUserCell.java | 1 - .../telegram/ui/Cells/ManageChatUserCell.java | 2 + .../org/telegram/ui/Cells/MentionCell.java | 4 +- .../telegram/ui/Cells/ProfileSearchCell.java | 4 +- .../java/org/telegram/ui/Cells/RadioCell.java | 1 + .../ui/Cells/SharingLiveLocationCell.java | 56 +- .../org/telegram/ui/Cells/StickerCell.java | 20 +- .../telegram/ui/Cells/StickerEmojiCell.java | 24 +- .../org/telegram/ui/Cells/StickerSetCell.java | 8 +- .../java/org/telegram/ui/Cells/TextCell.java | 17 +- .../java/org/telegram/ui/Cells/ThemeCell.java | 2 - .../java/org/telegram/ui/Cells/UserCell.java | 35 +- .../UserCell.java => Cells/UserCell2.java} | 10 +- .../org/telegram/ui/ChangeNameActivity.java | 11 +- .../telegram/ui/ChangePhoneHelpActivity.java | 145 - .../telegram/ui/ChannelAdminLogActivity.java | 21 +- .../telegram/ui/ChannelCreateActivity.java | 3 +- .../org/telegram/ui/ChannelIntroActivity.java | 158 - .../java/org/telegram/ui/ChatActivity.java | 1425 ++-- .../org/telegram/ui/ChatEditActivity.java | 95 +- .../org/telegram/ui/ChatEditTypeActivity.java | 109 +- .../org/telegram/ui/ChatLinkActivity.java | 2 + .../telegram/ui/ChatRightsEditActivity.java | 356 +- .../org/telegram/ui/ChatUsersActivity.java | 340 +- .../telegram/ui/Components/AlertsCreator.java | 102 +- .../ui/Components/AvatarDrawable.java | 28 +- .../ui/Components/BackupImageView.java | 4 + .../ui/Components/ChatActivityEnterView.java | 167 +- .../ui/Components/ChatAttachAlert.java | 18 +- .../ui/Components/ChatAvatarContainer.java | 30 +- .../ui/Components/DialogsItemAnimator.java | 18 + .../ui/Components/EditTextBoldCursor.java | 53 +- .../ui/Components/EditTextCaption.java | 69 +- .../org/telegram/ui/Components/EmojiView.java | 105 +- .../ui/Components/EmptyTextProgressView.java | 4 +- .../ui/Components/FragmentContextView.java | 2 +- .../ui/Components/InstantCameraView.java | 4 +- .../ui/Components/MediaActionDrawable.java | 3 + .../ui/Components/PhotoPaintView.java | 2 + .../PhotoViewerCaptionEnterView.java | 5 + .../ui/Components/RLottieDrawable.java | 601 ++ .../ui/Components/RLottieImageView.java | 55 + .../ui/Components/RecyclerListView.java | 67 +- .../ui/Components/ScrollSlidingTabStrip.java | 26 +- .../ui/Components/ShareLocationDrawable.java | 99 +- .../ui/Components/SharingLocationsAlert.java | 2 +- .../Components/SizeNotifierFrameLayout.java | 6 +- .../ui/Components/StickerMasksView.java | 34 +- .../telegram/ui/Components/StickersAlert.java | 30 +- .../ui/Components/StickersArchiveAlert.java | 6 +- .../ui/Components/TermsOfServiceView.java | 2 +- .../telegram/ui/Components/TextStyleSpan.java | 164 + .../ui/Components/ThemeEditorView.java | 7 +- .../ui/Components/URLSpanBotCommand.java | 22 +- .../ui/Components/URLSpanBrowser.java | 21 + .../telegram/ui/Components/URLSpanMono.java | 30 +- .../ui/Components/URLSpanNoUnderline.java | 17 +- .../ui/Components/URLSpanReplacement.java | 20 + .../ui/Components/URLSpanUserMention.java | 23 +- .../org/telegram/ui/Components/UndoView.java | 145 +- .../ui/Components/voip/VoIPHelper.java | 293 +- .../org/telegram/ui/ContactAddActivity.java | 109 +- .../org/telegram/ui/ContactsActivity.java | 130 +- .../org/telegram/ui/ContentPreviewViewer.java | 69 +- .../ui/DialogOrContactPickerActivity.java | 674 ++ .../java/org/telegram/ui/DialogsActivity.java | 106 +- .../telegram/ui/FeaturedStickersActivity.java | 18 +- .../org/telegram/ui/GroupCreateActivity.java | 191 +- .../telegram/ui/GroupCreateFinalActivity.java | 73 +- .../telegram/ui/GroupStickersActivity.java | 36 +- .../java/org/telegram/ui/LaunchActivity.java | 68 +- .../org/telegram/ui/LocationActivity.java | 385 +- .../java/org/telegram/ui/LoginActivity.java | 18 +- .../java/org/telegram/ui/LogoutActivity.java | 2 +- .../java/org/telegram/ui/MediaActivity.java | 34 +- .../org/telegram/ui/NewContactActivity.java | 93 +- .../NotificationsCustomSettingsActivity.java | 355 +- .../org/telegram/ui/PassportActivity.java | 95 +- .../org/telegram/ui/PaymentFormActivity.java | 22 +- .../org/telegram/ui/PeopleNearbyActivity.java | 809 ++ .../org/telegram/ui/PhotoCropActivity.java | 2 +- .../java/org/telegram/ui/PhotoViewer.java | 41 +- .../org/telegram/ui/PollCreateActivity.java | 16 +- .../telegram/ui/PrivacySettingsActivity.java | 8 +- .../org/telegram/ui/PrivacyUsersActivity.java | 10 +- .../java/org/telegram/ui/ProfileActivity.java | 380 +- .../ui/ProfileNotificationsActivity.java | 187 +- .../telegram/ui/ProxySettingsActivity.java | 4 +- .../org/telegram/ui/SettingsActivity.java | 19 +- .../org/telegram/ui/StickersActivity.java | 44 +- .../java/org/telegram/ui/ThemeActivity.java | 58 +- .../ui/TwoStepVerificationActivity.java | 51 +- .../java/org/telegram/ui/WebviewActivity.java | 17 +- .../main/res/drawable-hdpi/channelintro.png | Bin 19738 -> 25617 bytes .../src/main/res/drawable-hdpi/ghost.png | Bin 0 -> 939 bytes .../main/res/drawable-hdpi/groups_create.png | Bin 0 -> 1075 bytes .../main/res/drawable-hdpi/groupsintro.png | Bin 0 -> 28891 bytes .../main/res/drawable-hdpi/groupsintro2.png | Bin 0 -> 27833 bytes .../src/main/res/drawable-hdpi/map_pin2.png | Bin 0 -> 2311 bytes .../main/res/drawable-hdpi/menu_location.png | Bin 0 -> 1091 bytes .../res/drawable-hdpi/miniplayer_close.png | Bin 260 -> 417 bytes .../src/main/res/drawable-hdpi/nearby_l.png | Bin 0 -> 1267 bytes .../src/main/res/drawable-hdpi/nearby_m.png | Bin 0 -> 910 bytes .../main/res/drawable-hdpi/phone_change.png | Bin 1614 -> 0 bytes .../src/main/res/drawable-hdpi/sim_new.png | Bin 0 -> 2935 bytes .../src/main/res/drawable-hdpi/sim_old.png | Bin 0 -> 2250 bytes .../main/res/drawable-mdpi/channelintro.png | Bin 12844 -> 15062 bytes .../src/main/res/drawable-mdpi/ghost.png | Bin 0 -> 608 bytes .../main/res/drawable-mdpi/groups_create.png | Bin 0 -> 642 bytes .../main/res/drawable-mdpi/groupsintro.png | Bin 0 -> 17416 bytes .../main/res/drawable-mdpi/groupsintro2.png | Bin 0 -> 17139 bytes .../src/main/res/drawable-mdpi/map_pin2.png | Bin 0 -> 1441 bytes .../main/res/drawable-mdpi/menu_location.png | Bin 0 -> 675 bytes .../res/drawable-mdpi/miniplayer_close.png | Bin 181 -> 286 bytes .../src/main/res/drawable-mdpi/nearby_l.png | Bin 0 -> 837 bytes .../src/main/res/drawable-mdpi/nearby_m.png | Bin 0 -> 624 bytes .../main/res/drawable-mdpi/phone_change.png | Bin 1088 -> 0 bytes .../src/main/res/drawable-mdpi/sim_new.png | Bin 0 -> 1842 bytes .../src/main/res/drawable-mdpi/sim_old.png | Bin 0 -> 1403 bytes .../main/res/drawable-xhdpi/channelintro.png | Bin 28857 -> 36747 bytes .../src/main/res/drawable-xhdpi/ghost.png | Bin 0 -> 1237 bytes .../main/res/drawable-xhdpi/groups_create.png | Bin 0 -> 1404 bytes .../main/res/drawable-xhdpi/groupsintro.png | Bin 0 -> 40462 bytes .../main/res/drawable-xhdpi/groupsintro2.png | Bin 0 -> 38994 bytes .../src/main/res/drawable-xhdpi/map_pin2.png | Bin 0 -> 3096 bytes .../main/res/drawable-xhdpi/menu_location.png | Bin 0 -> 1516 bytes .../res/drawable-xhdpi/miniplayer_close.png | Bin 299 -> 523 bytes .../src/main/res/drawable-xhdpi/nearby_l.png | Bin 0 -> 1872 bytes .../src/main/res/drawable-xhdpi/nearby_m.png | Bin 0 -> 1346 bytes .../main/res/drawable-xhdpi/phone_change.png | Bin 2409 -> 0 bytes .../src/main/res/drawable-xhdpi/sim_new.png | Bin 0 -> 3976 bytes .../src/main/res/drawable-xhdpi/sim_old.png | Bin 0 -> 3106 bytes .../main/res/drawable-xxhdpi/channelintro.png | Bin 41823 -> 60710 bytes .../src/main/res/drawable-xxhdpi/ghost.png | Bin 0 -> 1953 bytes .../res/drawable-xxhdpi/groups_create.png | Bin 0 -> 1538 bytes .../main/res/drawable-xxhdpi/groupsintro.png | Bin 0 -> 67015 bytes .../main/res/drawable-xxhdpi/groupsintro2.png | Bin 0 -> 63647 bytes .../src/main/res/drawable-xxhdpi/map_pin2.png | Bin 0 -> 5114 bytes .../res/drawable-xxhdpi/menu_location.png | Bin 0 -> 2301 bytes .../res/drawable-xxhdpi/miniplayer_close.png | Bin 404 -> 653 bytes .../src/main/res/drawable-xxhdpi/nearby_l.png | Bin 0 -> 3034 bytes .../src/main/res/drawable-xxhdpi/nearby_m.png | Bin 0 -> 2039 bytes .../main/res/drawable-xxhdpi/phone_change.png | Bin 3535 -> 0 bytes .../src/main/res/drawable-xxhdpi/sim_new.png | Bin 0 -> 6396 bytes .../src/main/res/drawable-xxhdpi/sim_old.png | Bin 0 -> 5495 bytes .../src/main/res/raw/contact_check.json | 1 + .../src/main/res/values-ar/strings.xml | 257 +- .../src/main/res/values-de/strings.xml | 137 +- .../src/main/res/values-es/strings.xml | 171 +- .../src/main/res/values-it/strings.xml | 145 +- .../src/main/res/values-ko/strings.xml | 219 +- .../src/main/res/values-nl/strings.xml | 229 +- .../src/main/res/values-pt-rBR/strings.xml | 209 +- TMessagesProj/src/main/res/values/ids.xml | 2 + TMessagesProj/src/main/res/values/strings.xml | 95 +- build.gradle | 6 +- 365 files changed, 76570 insertions(+), 6969 deletions(-) create mode 100644 TMessagesProj/jni/lottie.cpp create mode 100755 TMessagesProj/jni/lz4/lz4.c create mode 100755 TMessagesProj/jni/lz4/lz4.h create mode 100755 TMessagesProj/jni/lz4/lz4frame.c create mode 100755 TMessagesProj/jni/lz4/lz4frame.h create mode 100755 TMessagesProj/jni/lz4/lz4frame_static.h create mode 100755 TMessagesProj/jni/lz4/lz4hc.c create mode 100755 TMessagesProj/jni/lz4/lz4hc.h create mode 100755 TMessagesProj/jni/lz4/xxhash.c create mode 100755 TMessagesProj/jni/lz4/xxhash.h create mode 100755 TMessagesProj/jni/rlottie/inc/rlottie.h create mode 100755 TMessagesProj/jni/rlottie/inc/rlottie_capi.h create mode 100755 TMessagesProj/jni/rlottie/inc/rlottiecommon.h create mode 100755 TMessagesProj/jni/rlottie/licenses/COPYING.FTL create mode 100755 TMessagesProj/jni/rlottie/licenses/COPYING.LGPL create mode 100755 TMessagesProj/jni/rlottie/licenses/COPYING.PIX create mode 100755 TMessagesProj/jni/rlottie/licenses/COPYING.RPD create mode 100755 TMessagesProj/jni/rlottie/licenses/COPYING.STB create mode 100755 TMessagesProj/jni/rlottie/src/lottie/lottieanimation.cpp create mode 100755 TMessagesProj/jni/rlottie/src/lottie/lottieitem.cpp create mode 100755 TMessagesProj/jni/rlottie/src/lottie/lottieitem.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/lottiekeypath.cpp create mode 100755 TMessagesProj/jni/rlottie/src/lottie/lottiekeypath.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/lottieloader.cpp create mode 100755 TMessagesProj/jni/rlottie/src/lottie/lottieloader.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/lottiemodel.cpp create mode 100755 TMessagesProj/jni/rlottie/src/lottie/lottiemodel.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/lottieparser.cpp create mode 100755 TMessagesProj/jni/rlottie/src/lottie/lottieparser.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/lottieproxymodel.cpp create mode 100755 TMessagesProj/jni/rlottie/src/lottie/lottieproxymodel.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/allocators.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/cursorstreamwrapper.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/document.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/encodedstream.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/encodings.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/error/en.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/error/error.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/filereadstream.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/filewritestream.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/fwd.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/biginteger.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/diyfp.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/dtoa.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/ieee754.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/itoa.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/meta.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/pow10.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/regex.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/stack.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/strfunc.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/strtod.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/swap.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/istreamwrapper.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/memorybuffer.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/memorystream.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/msinttypes/inttypes.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/msinttypes/stdint.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/ostreamwrapper.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/pointer.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/prettywriter.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/rapidjson.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/reader.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/schema.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/stream.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/stringbuffer.h create mode 100755 TMessagesProj/jni/rlottie/src/lottie/rapidjson/writer.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/config.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_math.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_math.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_raster.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_raster.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_stroker.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_stroker.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_types.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arm-neon-asm.S create mode 100755 TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arm-neon-asm.h create mode 100644 TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arma64-neon-asm.S create mode 100644 TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arma64-neon-asm.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/pixman/vregion.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/pixman/vregion.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/stb/stb_image.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/stb/stb_image.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vbezier.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vbezier.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vbitmap.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vbitmap.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vbrush.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vbrush.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vcompositionfunctions.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vcowptr.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vdasher.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vdasher.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vdebug.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vdebug.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vdrawable.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vdrawable.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vdrawhelper.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vdrawhelper.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vdrawhelper_neon.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/velapsedtimer.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/velapsedtimer.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vglobal.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vimageloader.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vimageloader.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vinterpolator.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vinterpolator.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vline.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vmatrix.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vmatrix.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vpainter.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vpainter.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vpath.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vpath.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vpathmesure.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vpathmesure.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vpoint.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vraster.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vraster.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vrect.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vrect.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vrle.cpp create mode 100755 TMessagesProj/jni/rlottie/src/vector/vrle.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vstackallocator.h create mode 100755 TMessagesProj/jni/rlottie/src/vector/vtaskqueue.h create mode 100644 TMessagesProj/src/main/java/org/telegram/messenger/BaseController.java rename TMessagesProj/src/main/java/org/telegram/messenger/{DataQuery.java => MediaDataController.java} (80%) create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/ActionIntroActivity.java rename TMessagesProj/src/main/java/org/telegram/ui/{Cells2/UserCell.java => Cells/UserCell2.java} (97%) delete mode 100644 TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneHelpActivity.java delete mode 100644 TMessagesProj/src/main/java/org/telegram/ui/ChannelIntroActivity.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/RLottieDrawable.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/RLottieImageView.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/TextStyleSpan.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/DialogOrContactPickerActivity.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/PeopleNearbyActivity.java create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/ghost.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/groups_create.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/groupsintro.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/groupsintro2.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/map_pin2.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/menu_location.png mode change 100755 => 100644 TMessagesProj/src/main/res/drawable-hdpi/miniplayer_close.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/nearby_l.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/nearby_m.png delete mode 100755 TMessagesProj/src/main/res/drawable-hdpi/phone_change.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/sim_new.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/sim_old.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/ghost.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/groups_create.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/groupsintro.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/groupsintro2.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/map_pin2.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/menu_location.png mode change 100755 => 100644 TMessagesProj/src/main/res/drawable-mdpi/miniplayer_close.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/nearby_l.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/nearby_m.png delete mode 100755 TMessagesProj/src/main/res/drawable-mdpi/phone_change.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/sim_new.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/sim_old.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/ghost.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/groups_create.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/groupsintro.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/groupsintro2.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/map_pin2.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/menu_location.png mode change 100755 => 100644 TMessagesProj/src/main/res/drawable-xhdpi/miniplayer_close.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/nearby_l.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/nearby_m.png delete mode 100755 TMessagesProj/src/main/res/drawable-xhdpi/phone_change.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/sim_new.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/sim_old.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/ghost.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/groups_create.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/groupsintro.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/groupsintro2.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/map_pin2.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/menu_location.png mode change 100755 => 100644 TMessagesProj/src/main/res/drawable-xxhdpi/miniplayer_close.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/nearby_l.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/nearby_m.png delete mode 100755 TMessagesProj/src/main/res/drawable-xxhdpi/phone_change.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/sim_new.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/sim_old.png create mode 100644 TMessagesProj/src/main/res/raw/contact_check.json diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle index d1729a581..b91cd10ec 100644 --- a/TMessagesProj/build.gradle +++ b/TMessagesProj/build.gradle @@ -17,19 +17,18 @@ configurations.all { dependencies { implementation 'androidx.core:core:1.1.0-beta01' implementation 'androidx.palette:palette:1.0.0' - implementation 'androidx.exifinterface:exifinterface:1.0.0' - - implementation 'com.airbnb.android:lottie:3.0.3' + implementation 'androidx.exifinterface:exifinterface:1.1.0-beta01' compileOnly 'org.checkerframework:checker-qual:2.5.2' compileOnly 'org.checkerframework:checker-compat-qual:2.5.0' - implementation 'com.google.firebase:firebase-messaging:18.0.0' - implementation 'com.google.firebase:firebase-config:16.5.0' - implementation 'com.google.android.gms:play-services-maps:16.1.0' - implementation 'com.google.android.gms:play-services-auth:16.0.1' + implementation 'com.google.firebase:firebase-messaging:19.0.1' + implementation 'com.google.firebase:firebase-config:18.0.0' + implementation 'com.google.android.gms:play-services-maps:17.0.0' + implementation 'com.google.android.gms:play-services-auth:17.0.0' implementation 'com.google.android.gms:play-services-vision:16.2.0' - implementation 'com.google.android.gms:play-services-wallet:16.0.1' - implementation 'com.google.android.gms:play-services-wearable:16.0.1' + implementation 'com.google.android.gms:play-services-wallet:17.0.0' + implementation 'com.google.android.gms:play-services-wearable:17.0.0' + implementation 'com.google.android.gms:play-services-location:17.0.0' implementation 'net.hockeyapp.android:HockeySDK:5.1.1' implementation 'com.googlecode.mp4parser:isoparser:1.0.6' implementation 'com.stripe:stripe-android:2.0.2' @@ -243,7 +242,7 @@ android { } } - defaultConfig.versionCode = 1608 + defaultConfig.versionCode = 1648 applicationVariants.all { variant -> variant.outputs.all { output -> @@ -277,7 +276,7 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 27 - versionName "5.7.1" + versionName "5.9.0" vectorDrawables.generatedDensities = ['mdpi', 'hdpi', 'xhdpi', 'xxhdpi'] diff --git a/TMessagesProj/jni/Android.mk b/TMessagesProj/jni/Android.mk index 6d9f49958..441ef1287 100755 --- a/TMessagesProj/jni/Android.mk +++ b/TMessagesProj/jni/Android.mk @@ -133,7 +133,7 @@ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) -LOCAL_CFLAGS := -Wall -DANDROID -DHAVE_MALLOC_H -DHAVE_PTHREAD -DWEBP_USE_THREAD -finline-functions -ffast-math -ffunction-sections -fdata-sections -O0 +LOCAL_CFLAGS := -Wall -DANDROID -DHAVE_MALLOC_H -DHAVE_PTHREAD -DWEBP_USE_THREAD -finline-functions -ffast-math -ffunction-sections -fdata-sections -Os LOCAL_C_INCLUDES += ./jni/libwebp/src LOCAL_ARM_MODE := arm LOCAL_STATIC_LIBRARIES := cpufeatures @@ -217,7 +217,7 @@ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_CPPFLAGS := -frtti -LOCAL_CFLAGS += '-DVERSION="1.3.1"' -DFLAC__NO_MD5 -DFLAC__INTEGER_ONLY_LIBRARY -DFLAC__NO_ASM +LOCAL_CFLAGS += -DVERSION="1.3.1" -DFLAC__NO_MD5 -DFLAC__INTEGER_ONLY_LIBRARY -DFLAC__NO_ASM LOCAL_CFLAGS += -D_REENTRANT -DPIC -DU_COMMON_IMPLEMENTATION -fPIC -DHAVE_SYS_PARAM_H LOCAL_CFLAGS += -O3 -funroll-loops -finline-functions LOCAL_LDLIBS := -lz -lm @@ -269,6 +269,81 @@ LOCAL_SRC_FILES := \ include $(BUILD_STATIC_LIBRARY) +include $(CLEAR_VARS) + +LOCAL_ARM_MODE := arm +LOCAL_MODULE := lz4 +LOCAL_CFLAGS := -w -std=c11 -O3 + +LOCAL_SRC_FILES := \ +./lz4/lz4.c \ +./lz4/lz4frame.c \ +./lz4/xxhash.c + +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_ARM_MODE := arm +LOCAL_MODULE := rlottie +LOCAL_CPPFLAGS := -DNDEBUG -Wall -std=c++14 -DANDROID -fno-rtti -DHAVE_PTHREAD -finline-functions -ffast-math -Os -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -Wnon-virtual-dtor -Woverloaded-virtual -Wno-unused-parameter -fvisibility=hidden +ifeq ($(TARGET_ARCH_ABI),$(filter $(TARGET_ARCH_ABI),armeabi-v7a)) + LOCAL_CFLAGS := -DUSE_ARM_NEON -fno-integrated-as + LOCAL_CPPFLAGS += -DUSE_ARM_NEON -fno-integrated-as +else ifeq ($(TARGET_ARCH_ABI),$(filter $(TARGET_ARCH_ABI),arm64-v8a)) + LOCAL_CFLAGS := -DUSE_ARM_NEON -D__ARM64_NEON__ -fno-integrated-as + LOCAL_CPPFLAGS += -DUSE_ARM_NEON -D__ARM64_NEON__ -fno-integrated-as +endif + +LOCAL_C_INCLUDES := \ +./jni/rlottie/inc \ +./jni/rlottie/src/vector/ \ +./jni/rlottie/src/vector/pixman \ +./jni/rlottie/src/vector/freetype \ +./jni/rlottie/src/vector/stb + +LOCAL_SRC_FILES := \ +./rlottie/src/lottie/lottieanimation.cpp \ +./rlottie/src/lottie/lottieitem.cpp \ +./rlottie/src/lottie/lottiekeypath.cpp \ +./rlottie/src/lottie/lottieloader.cpp \ +./rlottie/src/lottie/lottiemodel.cpp \ +./rlottie/src/lottie/lottieparser.cpp \ +./rlottie/src/lottie/lottieproxymodel.cpp \ +./rlottie/src/vector/freetype/v_ft_math.cpp \ +./rlottie/src/vector/freetype/v_ft_raster.cpp \ +./rlottie/src/vector/freetype/v_ft_stroker.cpp \ +./rlottie/src/vector/pixman/vregion.cpp \ +./rlottie/src/vector/stb/stb_image.cpp \ +./rlottie/src/vector/vbezier.cpp \ +./rlottie/src/vector/vbitmap.cpp \ +./rlottie/src/vector/vbrush.cpp \ +./rlottie/src/vector/vcompositionfunctions.cpp \ +./rlottie/src/vector/vdasher.cpp \ +./rlottie/src/vector/vdebug.cpp \ +./rlottie/src/vector/vdrawable.cpp \ +./rlottie/src/vector/vdrawhelper.cpp \ +./rlottie/src/vector/vdrawhelper_neon.cpp \ +./rlottie/src/vector/velapsedtimer.cpp \ +./rlottie/src/vector/vimageloader.cpp \ +./rlottie/src/vector/vinterpolator.cpp \ +./rlottie/src/vector/vmatrix.cpp \ +./rlottie/src/vector/vpainter.cpp \ +./rlottie/src/vector/vpath.cpp \ +./rlottie/src/vector/vpathmesure.cpp \ +./rlottie/src/vector/vraster.cpp \ +./rlottie/src/vector/vrect.cpp \ +./rlottie/src/vector/vrle.cpp + +ifeq ($(TARGET_ARCH_ABI),$(filter $(TARGET_ARCH_ABI),armeabi-v7a)) + LOCAL_SRC_FILES += ./rlottie/src/vector/pixman/pixman-arm-neon-asm.S.neon +else ifeq ($(TARGET_ARCH_ABI),$(filter $(TARGET_ARCH_ABI),arm64-v8a)) + LOCAL_SRC_FILES += ./rlottie/src/vector/pixman/pixman-arma64-neon-asm.S.neon +endif + +LOCAL_STATIC_LIBRARIES := cpufeatures +include $(BUILD_STATIC_LIBRARY) + include $(CLEAR_VARS) LOCAL_PRELINK_MODULE := false @@ -277,8 +352,8 @@ LOCAL_CFLAGS := -w -std=c11 -Os -DNULL=0 -DSOCKLEN_T=socklen_t -DLOCALE_NOT_USE LOCAL_CFLAGS += -Drestrict='' -D__EMX__ -DOPUS_BUILD -DFIXED_POINT -DUSE_ALLOCA -DHAVE_LRINT -DHAVE_LRINTF -fno-math-errno LOCAL_CFLAGS += -DANDROID_NDK -DDISABLE_IMPORTGL -fno-strict-aliasing -fprefetch-loop-arrays -DAVOID_TABLES -DANDROID_TILE_BASED_DECODE -DANDROID_ARMV6_IDCT -ffast-math -D__STDC_CONSTANT_MACROS LOCAL_CPPFLAGS := -DBSD=1 -ffast-math -Os -funroll-loops -std=c++11 -LOCAL_LDLIBS := -ljnigraphics -llog -lz -latomic -lEGL -lGLESv2 -landroid -LOCAL_STATIC_LIBRARIES := webp sqlite tgnet swscale avformat avcodec avresample avutil voip flac +LOCAL_LDLIBS := -ljnigraphics -llog -lz -lEGL -lGLESv2 -landroid +LOCAL_STATIC_LIBRARIES := webp sqlite lz4 rlottie tgnet swscale avformat avcodec avresample avutil voip flac LOCAL_SRC_FILES := \ ./opus/src/opus.c \ @@ -485,7 +560,9 @@ LOCAL_C_INCLUDES := \ ./jni/emoji \ ./jni/exoplayer/include \ ./jni/exoplayer/libFLAC/include \ -./jni/intro +./jni/intro \ +./jni/rlottie/inc \ +./jni/lz4 LOCAL_SRC_FILES += \ ./libyuv/source/compare_common.cc \ @@ -545,6 +622,7 @@ LOCAL_SRC_FILES += \ ./intro/IntroRenderer.c \ ./utilities.cpp \ ./gifvideo.cpp \ +./lottie.cpp \ ./SqliteWrapper.cpp \ ./TgNetWrapper.cpp \ ./NativeLoader.cpp \ diff --git a/TMessagesProj/jni/Application.mk b/TMessagesProj/jni/Application.mk index 23dc08609..b39ba1442 100644 --- a/TMessagesProj/jni/Application.mk +++ b/TMessagesProj/jni/Application.mk @@ -1,3 +1,3 @@ APP_PLATFORM := android-14 NDK_TOOLCHAIN_VERSION := clang -APP_STL := gnustl_static \ No newline at end of file +APP_STL := c++_static \ No newline at end of file diff --git a/TMessagesProj/jni/TgNetWrapper.cpp b/TMessagesProj/jni/TgNetWrapper.cpp index 3298360ef..97aba7855 100644 --- a/TMessagesProj/jni/TgNetWrapper.cpp +++ b/TMessagesProj/jni/TgNetWrapper.cpp @@ -344,6 +344,16 @@ void setLangCode(JNIEnv *env, jclass c, jint instanceNum, jstring langCode) { } } +void setRegId(JNIEnv *env, jclass c, jint instanceNum, jstring regId) { + const char *regIdStr = env->GetStringUTFChars(regId, 0); + + ConnectionsManager::getInstance(instanceNum).setRegId(std::string(regIdStr)); + + if (regIdStr != 0) { + env->ReleaseStringUTFChars(regId, regIdStr); + } +} + void setSystemLangCode(JNIEnv *env, jclass c, jint instanceNum, jstring langCode) { const char *langCodeStr = env->GetStringUTFChars(langCode, 0); @@ -354,7 +364,7 @@ void setSystemLangCode(JNIEnv *env, jclass c, jint instanceNum, jstring langCode } } -void init(JNIEnv *env, jclass c, jint instanceNum, jint version, jint layer, jint apiId, jstring deviceModel, jstring systemVersion, jstring appVersion, jstring langCode, jstring systemLangCode, jstring configPath, jstring logPath, jint userId, jboolean enablePushConnection, jboolean hasNetwork, jint networkType) { +void init(JNIEnv *env, jclass c, jint instanceNum, jint version, jint layer, jint apiId, jstring deviceModel, jstring systemVersion, jstring appVersion, jstring langCode, jstring systemLangCode, jstring configPath, jstring logPath, jstring regId, jint userId, jboolean enablePushConnection, jboolean hasNetwork, jint networkType) { const char *deviceModelStr = env->GetStringUTFChars(deviceModel, 0); const char *systemVersionStr = env->GetStringUTFChars(systemVersion, 0); const char *appVersionStr = env->GetStringUTFChars(appVersion, 0); @@ -362,8 +372,9 @@ void init(JNIEnv *env, jclass c, jint instanceNum, jint version, jint layer, jin const char *systemLangCodeStr = env->GetStringUTFChars(systemLangCode, 0); const char *configPathStr = env->GetStringUTFChars(configPath, 0); const char *logPathStr = env->GetStringUTFChars(logPath, 0); + const char *regIdStr = env->GetStringUTFChars(regId, 0); - ConnectionsManager::getInstance(instanceNum).init((uint32_t) version, layer, apiId, std::string(deviceModelStr), std::string(systemVersionStr), std::string(appVersionStr), std::string(langCodeStr), std::string(systemLangCodeStr), std::string(configPathStr), std::string(logPathStr), userId, true, enablePushConnection, hasNetwork, networkType); + ConnectionsManager::getInstance(instanceNum).init((uint32_t) version, layer, apiId, std::string(deviceModelStr), std::string(systemVersionStr), std::string(appVersionStr), std::string(langCodeStr), std::string(systemLangCodeStr), std::string(configPathStr), std::string(logPathStr), std::string(regIdStr), userId, true, enablePushConnection, hasNetwork, networkType); if (deviceModelStr != 0) { env->ReleaseStringUTFChars(deviceModel, deviceModelStr); @@ -386,6 +397,9 @@ void init(JNIEnv *env, jclass c, jint instanceNum, jint version, jint layer, jin if (logPathStr != 0) { env->ReleaseStringUTFChars(logPath, logPathStr); } + if (regIdStr != 0) { + env->ReleaseStringUTFChars(regId, regIdStr); + } } void setJava(JNIEnv *env, jclass c, jboolean useJavaByteBuffers) { @@ -410,8 +424,9 @@ static JNINativeMethod ConnectionsManagerMethods[] = { {"native_setProxySettings", "(ILjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", (void *) setProxySettings}, {"native_getConnectionState", "(I)I", (void *) getConnectionState}, {"native_setUserId", "(II)V", (void *) setUserId}, - {"native_init", "(IIIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZZI)V", (void *) init}, + {"native_init", "(IIIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZZI)V", (void *) init}, {"native_setLangCode", "(ILjava/lang/String;)V", (void *) setLangCode}, + {"native_setRegId", "(ILjava/lang/String;)V", (void *) setRegId}, {"native_setSystemLangCode", "(ILjava/lang/String;)V", (void *) setSystemLangCode}, {"native_switchBackend", "(I)V", (void *) switchBackend}, {"native_pauseNetwork", "(I)V", (void *) pauseNetwork}, diff --git a/TMessagesProj/jni/image.c b/TMessagesProj/jni/image.c index 5aa93f93c..b3e451732 100644 --- a/TMessagesProj/jni/image.c +++ b/TMessagesProj/jni/image.c @@ -59,7 +59,7 @@ jint imageOnJNILoad(JavaVM *vm, JNIEnv *env) { } static inline uint64_t getColors(const uint8_t *p) { - return p[0] + (p[1] << 16) + ((uint64_t) p[2] << 32); + return p[0] + (p[1] << 16) + ((uint64_t) p[2] << 32) + ((uint64_t) p[3] << 48); } static inline uint64_t getColors565(const uint8_t *p) { @@ -134,6 +134,7 @@ static void fastBlurMore(int32_t w, int32_t h, int32_t stride, uint8_t *pix, int pix[yi] = res; \ pix[yi + 1] = res >> 16; \ pix[yi + 2] = res >> 32; \ + pix[yi + 3] = res >> 48; \ rgballsum += rgb[x + (start) * w] - 2 * rgb[x + (middle) * w] + rgb[x + (end) * w]; \ rgbsum += rgballsum; \ y++; \ @@ -235,6 +236,7 @@ static void fastBlur(int32_t w, int32_t h, int32_t stride, uint8_t *pix, int32_t pix[yi] = res; \ pix[yi + 1] = res >> 16; \ pix[yi + 2] = res >> 32; \ + pix[yi + 3] = res >> 48; \ rgballsum += rgb[x + (start) * w] - 2 * rgb[x + (middle) * w] + rgb[x + (end) * w]; \ rgbsum += rgballsum; \ y++; \ @@ -711,44 +713,44 @@ JNIEXPORT jboolean Java_org_telegram_messenger_Utilities_loadWebpImage(JNIEnv *e #define SQUARE(i) ((i)*(i)) inline static void zeroClearInt(int* p, size_t count) { memset(p, 0, sizeof(int) * count); } -JNIEXPORT void Java_org_telegram_messenger_Utilities_stackBlurBitmap(JNIEnv* env, jclass class, jobject bitmap, jint radius){ - if(radius<1) return; +JNIEXPORT void Java_org_telegram_messenger_Utilities_stackBlurBitmap(JNIEnv* env, jclass class, jobject bitmap, jint radius) { + if (radius < 1) return; AndroidBitmapInfo info; - if(AndroidBitmap_getInfo(env, bitmap, &info)!=ANDROID_BITMAP_RESULT_SUCCESS) + if (AndroidBitmap_getInfo(env, bitmap, &info) != ANDROID_BITMAP_RESULT_SUCCESS) return; - if(info.format!=ANDROID_BITMAP_FORMAT_RGBA_8888) + if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) return; - int w=info.width; - int h=info.height; - int stride=info.stride; + int w = info.width; + int h = info.height; + int stride = info.stride; - unsigned char* pixels=0; + unsigned char *pixels = 0; AndroidBitmap_lockPixels(env, bitmap, (void **) &pixels); - if(!pixels){ + if (!pixels) { return; } // Constants //const int radius = (int)inradius; // Transform unsigned into signed for further operations const int wm = w - 1; const int hm = h - 1; - const int wh = w*h; + const int wh = w * h; const int div = radius + radius + 1; const int r1 = radius + 1; - const int divsum = SQUARE((div+1)>>1); + const int divsum = SQUARE((div + 1) >> 1); // Small buffers - int stack[div*3]; - zeroClearInt(stack, div*3); + int stack[div * 3]; + zeroClearInt(stack, div * 3); - int vmin[MAX(w,h)]; - zeroClearInt(vmin, MAX(w,h)); + int vmin[MAX(w, h)]; + zeroClearInt(vmin, MAX(w, h)); // Large buffers - int *r = malloc(wh*sizeof(int)); - int *g = malloc(wh*sizeof(int)); - int *b = malloc(wh*sizeof(int)); + int *r = malloc(wh * sizeof(int)); + int *g = malloc(wh * sizeof(int)); + int *b = malloc(wh * sizeof(int)); zeroClearInt(r, wh); zeroClearInt(g, wh); zeroClearInt(b, wh); @@ -756,27 +758,27 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_stackBlurBitmap(JNIEnv* env const size_t dvcount = 256 * divsum; int *dv = malloc(sizeof(int) * dvcount); int i; - for (i = 0;(size_t)i < dvcount;i++) { + for (i = 0; (size_t) i < dvcount; i++) { dv[i] = (i / divsum); } // Variables int x, y; int *sir; - int routsum,goutsum,boutsum; - int rinsum,ginsum,binsum; + int routsum, goutsum, boutsum; + int rinsum, ginsum, binsum; int rsum, gsum, bsum, p, yp; int stackpointer; int stackstart; int rbs; int yw = 0, yi = 0; - for (y = 0;y < h;y++) { + for (y = 0; y < h; y++) { rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; - for(i = -radius;i <= radius;i++){ - sir = &stack[(i + radius)*3]; - int offset = (y*stride + (MIN(wm, MAX(i, 0)))*4); + for (i = -radius; i <= radius; i++) { + sir = &stack[(i + radius) * 3]; + int offset = (y * stride + (MIN(wm, MAX(i, 0))) * 4); sir[0] = pixels[offset]; sir[1] = pixels[offset + 1]; sir[2] = pixels[offset + 2]; @@ -785,7 +787,7 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_stackBlurBitmap(JNIEnv* env rsum += sir[0] * rbs; gsum += sir[1] * rbs; bsum += sir[2] * rbs; - if (i > 0){ + if (i > 0) { rinsum += sir[0]; ginsum += sir[1]; binsum += sir[2]; @@ -797,7 +799,7 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_stackBlurBitmap(JNIEnv* env } stackpointer = radius; - for (x = 0;x < w;x++) { + for (x = 0; x < w; x++) { r[yi] = dv[rsum]; g[yi] = dv[gsum]; b[yi] = dv[bsum]; @@ -807,17 +809,17 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_stackBlurBitmap(JNIEnv* env bsum -= boutsum; stackstart = stackpointer - radius + div; - sir = &stack[(stackstart % div)*3]; + sir = &stack[(stackstart % div) * 3]; routsum -= sir[0]; goutsum -= sir[1]; boutsum -= sir[2]; - if (y == 0){ + if (y == 0) { vmin[x] = MIN(x + radius + 1, wm); } - int offset = (y*stride + vmin[x]*4); + int offset = (y * stride + vmin[x] * 4); sir[0] = pixels[offset]; sir[1] = pixels[offset + 1]; sir[2] = pixels[offset + 2]; @@ -830,7 +832,7 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_stackBlurBitmap(JNIEnv* env bsum += binsum; stackpointer = (stackpointer + 1) % div; - sir = &stack[(stackpointer % div)*3]; + sir = &stack[(stackpointer % div) * 3]; routsum += sir[0]; goutsum += sir[1]; @@ -845,13 +847,13 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_stackBlurBitmap(JNIEnv* env yw += w; } - for (x = 0;x < w;x++) { + for (x = 0; x < w; x++) { rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; - yp = -radius*w; - for(i = -radius;i <= radius;i++) { + yp = -radius * w; + for (i = -radius; i <= radius; i++) { yi = MAX(0, yp) + x; - sir = &stack[(i + radius)*3]; + sir = &stack[(i + radius) * 3]; sir[0] = r[yi]; sir[1] = g[yi]; @@ -859,9 +861,9 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_stackBlurBitmap(JNIEnv* env rbs = r1 - abs(i); - rsum += r[yi]*rbs; - gsum += g[yi]*rbs; - bsum += b[yi]*rbs; + rsum += r[yi] * rbs; + gsum += g[yi] * rbs; + bsum += b[yi] * rbs; if (i > 0) { rinsum += sir[0]; @@ -878,9 +880,9 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_stackBlurBitmap(JNIEnv* env } } stackpointer = radius; - for (y = 0;y < h;y++) { - int offset = stride*y+x*4; - pixels[offset] = dv[rsum]; + for (y = 0; y < h; y++) { + int offset = stride * y + x * 4; + pixels[offset] = dv[rsum]; pixels[offset + 1] = dv[gsum]; pixels[offset + 2] = dv[bsum]; rsum -= routsum; @@ -888,14 +890,14 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_stackBlurBitmap(JNIEnv* env bsum -= boutsum; stackstart = stackpointer - radius + div; - sir = &stack[(stackstart % div)*3]; + sir = &stack[(stackstart % div) * 3]; routsum -= sir[0]; goutsum -= sir[1]; boutsum -= sir[2]; - if (x == 0){ - vmin[y] = (MIN(y + r1, hm))*w; + if (x == 0) { + vmin[y] = (MIN(y + r1, hm)) * w; } p = x + vmin[y]; @@ -912,7 +914,7 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_stackBlurBitmap(JNIEnv* env bsum += binsum; stackpointer = (stackpointer + 1) % div; - sir = &stack[stackpointer*3]; + sir = &stack[stackpointer * 3]; routsum += sir[0]; goutsum += sir[1]; diff --git a/TMessagesProj/jni/libtgvoip/BlockingQueue.h b/TMessagesProj/jni/libtgvoip/BlockingQueue.h index df7eda327..347a50261 100644 --- a/TMessagesProj/jni/libtgvoip/BlockingQueue.h +++ b/TMessagesProj/jni/libtgvoip/BlockingQueue.h @@ -12,8 +12,6 @@ #include "threading.h" #include "utils.h" -using namespace std; - namespace tgvoip{ template @@ -80,7 +78,7 @@ private: return r; } - list queue; + std::list queue; size_t capacity; //tgvoip_lock_t lock; Semaphore semaphore; diff --git a/TMessagesProj/jni/libtgvoip/VoIPController.cpp b/TMessagesProj/jni/libtgvoip/VoIPController.cpp index 467c0c142..d39bc5af5 100755 --- a/TMessagesProj/jni/libtgvoip/VoIPController.cpp +++ b/TMessagesProj/jni/libtgvoip/VoIPController.cpp @@ -2388,10 +2388,10 @@ simpleAudioBlock random_id:long random_bytes:string raw_data:string = DecryptedA stm->jitterBuffer->SetMinPacketCount((uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_initial_delay_20", 6)); stm->decoder=NULL; }else if(stm->type==STREAM_TYPE_VIDEO){ - if(!stm->packetReassembler){ + /*if(!stm->packetReassembler){ stm->packetReassembler=make_shared(); stm->packetReassembler->SetCallback(bind(&VoIPController::ProcessIncomingVideoFrame, this, placeholders::_1, placeholders::_2, placeholders::_3, placeholders::_4)); - } + }*/ }else{ LOGW("Unknown incoming stream type: %d", stm->type); continue; diff --git a/TMessagesProj/jni/libtgvoip/client/android/tg_voip_jni.cpp b/TMessagesProj/jni/libtgvoip/client/android/tg_voip_jni.cpp index 9f5df69c5..9371e8dd1 100644 --- a/TMessagesProj/jni/libtgvoip/client/android/tg_voip_jni.cpp +++ b/TMessagesProj/jni/libtgvoip/client/android/tg_voip_jni.cpp @@ -295,9 +295,6 @@ namespace tgvoip { cfg.statsDumpFilePath=jni::JavaStringToStdString(env, statsDumpPath); } - // remove before push - cfg.enableVideoReceive=cfg.enableVideoSend=true; - ((VoIPController*)(intptr_t)inst)->SetConfig(cfg); } diff --git a/TMessagesProj/jni/lottie.cpp b/TMessagesProj/jni/lottie.cpp new file mode 100644 index 000000000..08e97fa90 --- /dev/null +++ b/TMessagesProj/jni/lottie.cpp @@ -0,0 +1,237 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "c_utils.h" + +extern "C" { +using namespace rlottie; + +typedef struct LottieInfo { + + ~LottieInfo() { + if (decompressBuffer != nullptr) { + delete[]decompressBuffer; + decompressBuffer = nullptr; + } + } + + std::unique_ptr animation; + size_t frameCount = 0; + int32_t fps = 30; + bool precache = false; + bool createCache = false; + std::string path; + std::string cacheFile; + uint8_t *decompressBuffer = nullptr; + uint32_t maxFrameSize = 0; + uint32_t imageSize = 0; + uint32_t fileOffset = 0; + bool nextFrameIsCacheFrame = false; +}; + +jlong Java_org_telegram_ui_Components_RLottieDrawable_create(JNIEnv *env, jclass clazz, jstring src, jintArray data, jboolean precache) { + LottieInfo *info = new LottieInfo(); + + char const *srcString = env->GetStringUTFChars(src, 0); + info->path = srcString; + info->animation = rlottie::Animation::loadFromFile(info->path); + if (srcString != 0) { + env->ReleaseStringUTFChars(src, srcString); + } + if (info->animation == nullptr) { + delete info; + return 0; + } + info->frameCount = info->animation->totalFrame(); + info->fps = (int) info->animation->frameRate(); + if (info->fps > 60 || info->frameCount > 600) { + delete info; + return 0; + } + info->precache = precache; + if (info->precache) { + info->cacheFile = info->path; + info->cacheFile += ".cache"; + FILE *precacheFile = fopen(info->cacheFile.c_str(), "r+"); + if (precacheFile == nullptr) { + info->createCache = true; + } else { + uint8_t temp; + size_t read = fread(&temp, sizeof(uint8_t), 1, precacheFile); + info->createCache = read != 1 || temp == 0; + if (!info->createCache) { + fread(&(info->maxFrameSize), sizeof(uint32_t), 1, precacheFile); + fread(&(info->imageSize), sizeof(uint32_t), 1, precacheFile); + info->fileOffset = 9; + } + fclose(precacheFile); + } + } + + jint *dataArr = env->GetIntArrayElements(data, 0); + if (dataArr != nullptr) { + dataArr[0] = (jint) info->frameCount; + dataArr[1] = (jint) info->animation->frameRate(); + dataArr[2] = info->createCache ? 1 : 0; + env->ReleaseIntArrayElements(data, dataArr, 0); + } + return (jlong) (intptr_t) info; +} + +jlong Java_org_telegram_ui_Components_RLottieDrawable_createWithJson(JNIEnv *env, jclass clazz, jstring json, jstring name, jintArray data) { + LottieInfo *info = new LottieInfo(); + + char const *jsonString = env->GetStringUTFChars(json, 0); + char const *nameString = env->GetStringUTFChars(name, 0); + info->animation = rlottie::Animation::loadFromData(jsonString, nameString); + if (jsonString != 0) { + env->ReleaseStringUTFChars(json, jsonString); + } + if (nameString != 0) { + env->ReleaseStringUTFChars(name, nameString); + } + if (info->animation == nullptr) { + delete info; + return 0; + } + info->frameCount = info->animation->totalFrame(); + info->fps = (int) info->animation->frameRate(); + + jint *dataArr = env->GetIntArrayElements(data, 0); + if (dataArr != nullptr) { + dataArr[0] = (int) info->frameCount; + dataArr[1] = (int) info->animation->frameRate(); + dataArr[2] = 0; + env->ReleaseIntArrayElements(data, dataArr, 0); + } + return (jlong) (intptr_t) info; +} + +void Java_org_telegram_ui_Components_RLottieDrawable_destroy(JNIEnv *env, jclass clazz, jlong ptr) { + if (ptr == NULL) { + return; + } + LottieInfo *info = (LottieInfo *) (intptr_t) ptr; + delete info; +} + +void Java_org_telegram_ui_Components_RLottieDrawable_setLayerColor(JNIEnv *env, jclass clazz, jlong ptr, jstring layer, jint color) { + if (ptr == NULL || layer == nullptr) { + return; + } + LottieInfo *info = (LottieInfo *) (intptr_t) ptr; + char const *layerString = env->GetStringUTFChars(layer, 0); + info->animation->setValue(layerString, Color(((color) & 0xff) / 255.0f, ((color >> 8) & 0xff) / 255.0f, ((color >> 16) & 0xff) / 255.0f)); + if (layerString != 0) { + env->ReleaseStringUTFChars(layer, layerString); + } +} + +void Java_org_telegram_ui_Components_RLottieDrawable_createCache(JNIEnv *env, jclass clazz, jlong ptr, jobject bitmap, jint w, jint h, jint stride) { + if (ptr == NULL || bitmap == nullptr) { + return; + } + LottieInfo *info = (LottieInfo *) (intptr_t) ptr; + + FILE *precacheFile = fopen(info->cacheFile.c_str(), "r+"); + if (precacheFile != nullptr) { + uint8_t temp; + size_t read = fread(&temp, sizeof(uint8_t), 1, precacheFile); + fclose(precacheFile); + if (read == 1 && temp != 0) { + return; + } + } + + void *pixels; + if (info->nextFrameIsCacheFrame && info->createCache && info->frameCount != 0 && w * 4 == stride && AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0) { + precacheFile = fopen(info->cacheFile.c_str(), "w+"); + if (precacheFile != nullptr) { + fseek(precacheFile, info->fileOffset = 9, SEEK_SET); + + uint32_t size; + uint32_t firstFrameSize = 0; + info->maxFrameSize = 0; + int bound = LZ4_compressBound(w * h * 4); + uint8_t *compressBuffer = new uint8_t[bound]; + Surface surface((uint32_t *) pixels, (size_t) w, (size_t) h, (size_t) stride); + //int64_t time = ConnectionsManager::getInstance(0).getCurrentTimeMillis(); + //int totalSize = 0; + int framesPerUpdate = info->fps < 60 ? 1 : 2; + for (size_t a = 0; a < info->frameCount; a += framesPerUpdate) { + info->animation->renderSync(a, surface); + size = (uint32_t) LZ4_compress_default((const char *) pixels, (char *) compressBuffer, w * h * 4, bound); + //totalSize += size; + if (a == 0) { + firstFrameSize = size; + } + info->maxFrameSize = MAX(info->maxFrameSize, size); + fwrite(&size, sizeof(uint32_t), 1, precacheFile); + fwrite(compressBuffer, sizeof(uint8_t), size, precacheFile); + } + delete[] compressBuffer; + //DEBUG_D("total size %s = %d, time = %lld ms", info->path.c_str(), totalSize, (ConnectionsManager::getInstance(0).getCurrentTimeMillis() - time)); + fseek(precacheFile, 0, SEEK_SET); + uint8_t byte = 1; + info->imageSize = (uint32_t) w * h * 4; + fwrite(&byte, sizeof(uint8_t), 1, precacheFile); + fwrite(&info->maxFrameSize, sizeof(uint32_t), 1, precacheFile); + fwrite(&info->imageSize, sizeof(uint32_t), 1, precacheFile); + fflush(precacheFile); + int fd = fileno(precacheFile); + fsync(fd); + info->createCache = false; + info->fileOffset = 9 + sizeof(uint32_t) + firstFrameSize; + fclose(precacheFile); + } + AndroidBitmap_unlockPixels(env, bitmap); + } +} + +jint Java_org_telegram_ui_Components_RLottieDrawable_getFrame(JNIEnv *env, jclass clazz, jlong ptr, jint frame, jobject bitmap, jint w, jint h, jint stride) { + if (ptr == NULL || bitmap == nullptr) { + return 0; + } + LottieInfo *info = (LottieInfo *) (intptr_t) ptr; + void *pixels; + if (AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0) { + bool loadedFromCache = false; + if (info->precache && !info->createCache && w * 4 == stride && info->maxFrameSize <= w * h * 4 && info->imageSize == w * h * 4) { + FILE *precacheFile = fopen(info->cacheFile.c_str(), "r"); + if (precacheFile != nullptr) { + fseek(precacheFile, info->fileOffset, SEEK_SET); + if (info->decompressBuffer == nullptr) { + info->decompressBuffer = new uint8_t[info->maxFrameSize]; + } + uint32_t frameSize; + fread(&frameSize, sizeof(uint32_t), 1, precacheFile); + if (frameSize <= info->maxFrameSize) { + fread(info->decompressBuffer, sizeof(uint8_t), frameSize, precacheFile); + info->fileOffset += 4 + frameSize; + LZ4_decompress_safe((const char *) info->decompressBuffer, (char *) pixels, frameSize, w * h * 4); + loadedFromCache = true; + } + fclose(precacheFile); + int framesPerUpdate = info->fps < 60 ? 1 : 2; + if (frame + framesPerUpdate >= info->frameCount) { + info->fileOffset = 9; + } + } + } + if (!loadedFromCache) { + Surface surface((uint32_t *) pixels, (size_t) w, (size_t) h, (size_t) stride); + info->animation->renderSync((size_t) frame, surface); + info->nextFrameIsCacheFrame = true; + } + + AndroidBitmap_unlockPixels(env, bitmap); + } + return frame; +} +} diff --git a/TMessagesProj/jni/lz4/lz4.c b/TMessagesProj/jni/lz4/lz4.c new file mode 100755 index 000000000..e614c4577 --- /dev/null +++ b/TMessagesProj/jni/lz4/lz4.c @@ -0,0 +1,2299 @@ +/* + LZ4 - Fast LZ compression algorithm + Copyright (C) 2011-present, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ + +/*-************************************ +* Tuning parameters +**************************************/ +/* + * LZ4_HEAPMODE : + * Select how default compression functions will allocate memory for their hash table, + * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()). + */ +#ifndef LZ4_HEAPMODE +# define LZ4_HEAPMODE 0 +#endif + +/* + * ACCELERATION_DEFAULT : + * Select "acceleration" for LZ4_compress_fast() when parameter value <= 0 + */ +#define ACCELERATION_DEFAULT 1 + + +/*-************************************ +* CPU Feature Detection +**************************************/ +/* LZ4_FORCE_MEMORY_ACCESS + * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. + * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. + * The below switch allow to select different access method for improved performance. + * Method 0 (default) : use `memcpy()`. Safe and portable. + * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). + * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. + * Method 2 : direct access. This method is portable but violate C standard. + * It can generate buggy code on targets which assembly generation depends on alignment. + * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) + * See https://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details. + * Prefer these methods in priority order (0 > 1 > 2) + */ +#ifndef LZ4_FORCE_MEMORY_ACCESS /* can be defined externally */ +# if defined(__GNUC__) && \ + ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) \ + || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# define LZ4_FORCE_MEMORY_ACCESS 2 +# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || defined(__GNUC__) +# define LZ4_FORCE_MEMORY_ACCESS 1 +# endif +#endif + +/* + * LZ4_FORCE_SW_BITCOUNT + * Define this parameter if your target system or compiler does not support hardware bit count + */ +#if defined(_MSC_VER) && defined(_WIN32_WCE) /* Visual Studio for WinCE doesn't support Hardware bit count */ +# define LZ4_FORCE_SW_BITCOUNT +#endif + + + +/*-************************************ +* Dependency +**************************************/ +/* + * LZ4_SRC_INCLUDED: + * Amalgamation flag, whether lz4.c is included + */ +#ifndef LZ4_SRC_INCLUDED +# define LZ4_SRC_INCLUDED 1 +#endif + +#ifndef LZ4_STATIC_LINKING_ONLY +#define LZ4_STATIC_LINKING_ONLY +#endif + +#ifndef LZ4_DISABLE_DEPRECATE_WARNINGS +#define LZ4_DISABLE_DEPRECATE_WARNINGS /* due to LZ4_decompress_safe_withPrefix64k */ +#endif + +#include "lz4.h" +/* see also "memory routines" below */ + + +/*-************************************ +* Compiler Options +**************************************/ +#ifdef _MSC_VER /* Visual Studio */ +# include +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# pragma warning(disable : 4293) /* disable: C4293: too large shift (32-bits) */ +#endif /* _MSC_VER */ + +#ifndef LZ4_FORCE_INLINE +# ifdef _MSC_VER /* Visual Studio */ +# define LZ4_FORCE_INLINE static __forceinline +# else +# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# ifdef __GNUC__ +# define LZ4_FORCE_INLINE static inline __attribute__((always_inline)) +# else +# define LZ4_FORCE_INLINE static inline +# endif +# else +# define LZ4_FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +# endif /* _MSC_VER */ +#endif /* LZ4_FORCE_INLINE */ + +/* LZ4_FORCE_O2_GCC_PPC64LE and LZ4_FORCE_O2_INLINE_GCC_PPC64LE + * gcc on ppc64le generates an unrolled SIMDized loop for LZ4_wildCopy8, + * together with a simple 8-byte copy loop as a fall-back path. + * However, this optimization hurts the decompression speed by >30%, + * because the execution does not go to the optimized loop + * for typical compressible data, and all of the preamble checks + * before going to the fall-back path become useless overhead. + * This optimization happens only with the -O3 flag, and -O2 generates + * a simple 8-byte copy loop. + * With gcc on ppc64le, all of the LZ4_decompress_* and LZ4_wildCopy8 + * functions are annotated with __attribute__((optimize("O2"))), + * and also LZ4_wildCopy8 is forcibly inlined, so that the O2 attribute + * of LZ4_wildCopy8 does not affect the compression speed. + */ +#if defined(__PPC64__) && defined(__LITTLE_ENDIAN__) && defined(__GNUC__) && !defined(__clang__) +# define LZ4_FORCE_O2_GCC_PPC64LE __attribute__((optimize("O2"))) +# define LZ4_FORCE_O2_INLINE_GCC_PPC64LE __attribute__((optimize("O2"))) LZ4_FORCE_INLINE +#else +# define LZ4_FORCE_O2_GCC_PPC64LE +# define LZ4_FORCE_O2_INLINE_GCC_PPC64LE static +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) +# define expect(expr,value) (__builtin_expect ((expr),(value)) ) +#else +# define expect(expr,value) (expr) +#endif + +#ifndef likely +#define likely(expr) expect((expr) != 0, 1) +#endif +#ifndef unlikely +#define unlikely(expr) expect((expr) != 0, 0) +#endif + + +/*-************************************ +* Memory routines +**************************************/ +#include /* malloc, calloc, free */ +#define ALLOC(s) malloc(s) +#define ALLOC_AND_ZERO(s) calloc(1,s) +#define FREEMEM(p) free(p) +#include /* memset, memcpy */ +#define MEM_INIT(p,v,s) memset((p),(v),(s)) + + +/*-************************************ +* Types +**************************************/ +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; + typedef int32_t S32; + typedef uint64_t U64; + typedef uintptr_t uptrval; +#else + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; + typedef signed int S32; + typedef unsigned long long U64; + typedef size_t uptrval; /* generally true, except OpenVMS-64 */ +#endif + +#if defined(__x86_64__) + typedef U64 reg_t; /* 64-bits in x32 mode */ +#else + typedef size_t reg_t; /* 32-bits in x32 mode */ +#endif + +typedef enum { + notLimited = 0, + limitedOutput = 1, + fillOutput = 2 +} limitedOutput_directive; + + +/*-************************************ +* Reading and writing into memory +**************************************/ +static unsigned LZ4_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} + + +#if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2) +/* lie to the compiler about data alignment; use with caution */ + +static U16 LZ4_read16(const void* memPtr) { return *(const U16*) memPtr; } +static U32 LZ4_read32(const void* memPtr) { return *(const U32*) memPtr; } +static reg_t LZ4_read_ARCH(const void* memPtr) { return *(const reg_t*) memPtr; } + +static void LZ4_write16(void* memPtr, U16 value) { *(U16*)memPtr = value; } +static void LZ4_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; } + +#elif defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==1) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U16 u16; U32 u32; reg_t uArch; } __attribute__((packed)) unalign; + +static U16 LZ4_read16(const void* ptr) { return ((const unalign*)ptr)->u16; } +static U32 LZ4_read32(const void* ptr) { return ((const unalign*)ptr)->u32; } +static reg_t LZ4_read_ARCH(const void* ptr) { return ((const unalign*)ptr)->uArch; } + +static void LZ4_write16(void* memPtr, U16 value) { ((unalign*)memPtr)->u16 = value; } +static void LZ4_write32(void* memPtr, U32 value) { ((unalign*)memPtr)->u32 = value; } + +#else /* safe and portable access using memcpy() */ + +static U16 LZ4_read16(const void* memPtr) +{ + U16 val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static U32 LZ4_read32(const void* memPtr) +{ + U32 val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static reg_t LZ4_read_ARCH(const void* memPtr) +{ + reg_t val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static void LZ4_write16(void* memPtr, U16 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} + +static void LZ4_write32(void* memPtr, U32 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} + +#endif /* LZ4_FORCE_MEMORY_ACCESS */ + + +static U16 LZ4_readLE16(const void* memPtr) +{ + if (LZ4_isLittleEndian()) { + return LZ4_read16(memPtr); + } else { + const BYTE* p = (const BYTE*)memPtr; + return (U16)((U16)p[0] + (p[1]<<8)); + } +} + +static void LZ4_writeLE16(void* memPtr, U16 value) +{ + if (LZ4_isLittleEndian()) { + LZ4_write16(memPtr, value); + } else { + BYTE* p = (BYTE*)memPtr; + p[0] = (BYTE) value; + p[1] = (BYTE)(value>>8); + } +} + +/* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */ +LZ4_FORCE_O2_INLINE_GCC_PPC64LE +void LZ4_wildCopy8(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { memcpy(d,s,8); d+=8; s+=8; } while (d= 16. */ +LZ4_FORCE_O2_INLINE_GCC_PPC64LE void +LZ4_wildCopy32(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { memcpy(d,s,16); memcpy(d+16,s+16,16); d+=32; s+=32; } while (d 65535) /* max supported by LZ4 format */ +# error "LZ4_DISTANCE_MAX is too big : must be <= 65535" +#endif + +#define ML_BITS 4 +#define ML_MASK ((1U<=1) +# include +#else +# ifndef assert +# define assert(condition) ((void)0) +# endif +#endif + +#define LZ4_STATIC_ASSERT(c) { enum { LZ4_static_assert = 1/(int)(!!(c)) }; } /* use after variable declarations */ + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2) +# include +static int g_debuglog_enable = 1; +# define DEBUGLOG(l, ...) { \ + if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \ + fprintf(stderr, __FILE__ ": "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " \n"); \ + } } +#else +# define DEBUGLOG(l, ...) {} /* disabled */ +#endif + + +/*-************************************ +* Common functions +**************************************/ +static unsigned LZ4_NbCommonBytes (reg_t val) +{ + if (LZ4_isLittleEndian()) { + if (sizeof(val)==8) { +# if defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanForward64( &r, (U64)val ); + return (int)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_ctzll((U64)val) >> 3); +# else + static const int DeBruijnBytePos[64] = { 0, 0, 0, 0, 0, 1, 1, 2, + 0, 3, 1, 3, 1, 4, 2, 7, + 0, 2, 3, 6, 1, 5, 3, 5, + 1, 3, 4, 4, 2, 5, 6, 7, + 7, 0, 1, 2, 3, 3, 4, 6, + 2, 6, 5, 5, 3, 4, 5, 6, + 7, 1, 2, 4, 6, 4, 4, 5, + 7, 2, 6, 5, 7, 6, 7, 7 }; + return DeBruijnBytePos[((U64)((val & -(long long)val) * 0x0218A392CDABBD3FULL)) >> 58]; +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r; + _BitScanForward( &r, (U32)val ); + return (int)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_ctz((U32)val) >> 3); +# else + static const int DeBruijnBytePos[32] = { 0, 0, 3, 0, 3, 1, 3, 0, + 3, 2, 2, 1, 3, 2, 0, 1, + 3, 3, 1, 2, 2, 2, 2, 0, + 3, 1, 2, 0, 1, 0, 1, 1 }; + return DeBruijnBytePos[((U32)((val & -(S32)val) * 0x077CB531U)) >> 27]; +# endif + } + } else /* Big Endian CPU */ { + if (sizeof(val)==8) { /* 64-bits */ +# if defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanReverse64( &r, val ); + return (unsigned)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_clzll((U64)val) >> 3); +# else + static const U32 by32 = sizeof(val)*4; /* 32 on 64 bits (goal), 16 on 32 bits. + Just to avoid some static analyzer complaining about shift by 32 on 32-bits target. + Note that this code path is never triggered in 32-bits mode. */ + unsigned r; + if (!(val>>by32)) { r=4; } else { r=0; val>>=by32; } + if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } + r += (!val); + return r; +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanReverse( &r, (unsigned long)val ); + return (unsigned)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_clz((U32)val) >> 3); +# else + unsigned r; + if (!(val>>16)) { r=2; val>>=8; } else { r=0; val>>=24; } + r += (!val); + return r; +# endif + } + } +} + +#define STEPSIZE sizeof(reg_t) +LZ4_FORCE_INLINE +unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit) +{ + const BYTE* const pStart = pIn; + + if (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { + pIn+=STEPSIZE; pMatch+=STEPSIZE; + } else { + return LZ4_NbCommonBytes(diff); + } } + + while (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { pIn+=STEPSIZE; pMatch+=STEPSIZE; continue; } + pIn += LZ4_NbCommonBytes(diff); + return (unsigned)(pIn - pStart); + } + + if ((STEPSIZE==8) && (pIn<(pInLimit-3)) && (LZ4_read32(pMatch) == LZ4_read32(pIn))) { pIn+=4; pMatch+=4; } + if ((pIn<(pInLimit-1)) && (LZ4_read16(pMatch) == LZ4_read16(pIn))) { pIn+=2; pMatch+=2; } + if ((pIn compression run slower on incompressible data */ + + +/*-************************************ +* Local Structures and types +**************************************/ +typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t; + +/** + * This enum distinguishes several different modes of accessing previous + * content in the stream. + * + * - noDict : There is no preceding content. + * - withPrefix64k : Table entries up to ctx->dictSize before the current blob + * blob being compressed are valid and refer to the preceding + * content (of length ctx->dictSize), which is available + * contiguously preceding in memory the content currently + * being compressed. + * - usingExtDict : Like withPrefix64k, but the preceding content is somewhere + * else in memory, starting at ctx->dictionary with length + * ctx->dictSize. + * - usingDictCtx : Like usingExtDict, but everything concerning the preceding + * content is in a separate context, pointed to by + * ctx->dictCtx. ctx->dictionary, ctx->dictSize, and table + * entries in the current context that refer to positions + * preceding the beginning of the current compression are + * ignored. Instead, ctx->dictCtx->dictionary and ctx->dictCtx + * ->dictSize describe the location and size of the preceding + * content, and matches are found by looking in the ctx + * ->dictCtx->hashTable. + */ +typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive; +typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; + + +/*-************************************ +* Local Utils +**************************************/ +int LZ4_versionNumber (void) { return LZ4_VERSION_NUMBER; } +const char* LZ4_versionString(void) { return LZ4_VERSION_STRING; } +int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); } +int LZ4_sizeofState() { return LZ4_STREAMSIZE; } + + +/*-************************************ +* Internal Definitions used in Tests +**************************************/ +#if defined (__cplusplus) +extern "C" { +#endif + +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize); + +int LZ4_decompress_safe_forceExtDict(const char* in, char* out, int inSize, int outSize, const void* dict, size_t dictSize); + +#if defined (__cplusplus) +} +#endif + +/*-****************************** +* Compression functions +********************************/ +static U32 LZ4_hash4(U32 sequence, tableType_t const tableType) +{ + if (tableType == byU16) + return ((sequence * 2654435761U) >> ((MINMATCH*8)-(LZ4_HASHLOG+1))); + else + return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG)); +} + +static U32 LZ4_hash5(U64 sequence, tableType_t const tableType) +{ + const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; + if (LZ4_isLittleEndian()) { + const U64 prime5bytes = 889523592379ULL; + return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); + } else { + const U64 prime8bytes = 11400714785074694791ULL; + return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); + } +} + +LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) +{ + if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); + return LZ4_hash4(LZ4_read32(p), tableType); +} + +static void LZ4_putIndexOnHash(U32 idx, U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: /* fallthrough */ + case byPtr: { /* illegal! */ assert(0); return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = idx; return; } + case byU16: { U16* hashTable = (U16*) tableBase; assert(idx < 65536); hashTable[h] = (U16)idx; return; } + } +} + +static void LZ4_putPositionOnHash(const BYTE* p, U32 h, + void* tableBase, tableType_t const tableType, + const BYTE* srcBase) +{ + switch (tableType) + { + case clearedTable: { /* illegal! */ assert(0); return; } + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + LZ4_putPositionOnHash(p, h, tableBase, tableType, srcBase); +} + +/* LZ4_getIndexOnHash() : + * Index of match position registered in hash table. + * hash position must be calculated by using base+index, or dictBase+index. + * Assumption 1 : only valid if tableType == byU32 or byU16. + * Assumption 2 : h is presumed valid (within limits of hash table) + */ +static U32 LZ4_getIndexOnHash(U32 h, const void* tableBase, tableType_t tableType) +{ + LZ4_STATIC_ASSERT(LZ4_MEMORY_USAGE > 2); + if (tableType == byU32) { + const U32* const hashTable = (const U32*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-2))); + return hashTable[h]; + } + if (tableType == byU16) { + const U16* const hashTable = (const U16*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-1))); + return hashTable[h]; + } + assert(0); return 0; /* forbidden case */ +} + +static const BYTE* LZ4_getPositionOnHash(U32 h, const void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + if (tableType == byPtr) { const BYTE* const* hashTable = (const BYTE* const*) tableBase; return hashTable[h]; } + if (tableType == byU32) { const U32* const hashTable = (const U32*) tableBase; return hashTable[h] + srcBase; } + { const U16* const hashTable = (const U16*) tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ +} + +LZ4_FORCE_INLINE const BYTE* LZ4_getPosition(const BYTE* p, + const void* tableBase, tableType_t tableType, + const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + return LZ4_getPositionOnHash(h, tableBase, tableType, srcBase); +} + +LZ4_FORCE_INLINE void LZ4_prepareTable( + LZ4_stream_t_internal* const cctx, + const int inputSize, + const tableType_t tableType) { + /* If compression failed during the previous step, then the context + * is marked as dirty, therefore, it has to be fully reset. + */ + if (cctx->dirty) { + DEBUGLOG(5, "LZ4_prepareTable: Full reset for %p", cctx); + MEM_INIT(cctx, 0, sizeof(LZ4_stream_t_internal)); + return; + } + + /* If the table hasn't been used, it's guaranteed to be zeroed out, and is + * therefore safe to use no matter what mode we're in. Otherwise, we figure + * out if it's safe to leave as is or whether it needs to be reset. + */ + if (cctx->tableType != clearedTable) { + if (cctx->tableType != tableType + || (tableType == byU16 && cctx->currentOffset + inputSize >= 0xFFFFU) + || (tableType == byU32 && cctx->currentOffset > 1 GB) + || tableType == byPtr + || inputSize >= 4 KB) + { + DEBUGLOG(4, "LZ4_prepareTable: Resetting table in %p", cctx); + MEM_INIT(cctx->hashTable, 0, LZ4_HASHTABLESIZE); + cctx->currentOffset = 0; + cctx->tableType = clearedTable; + } else { + DEBUGLOG(4, "LZ4_prepareTable: Re-use hash table (no reset)"); + } + } + + /* Adding a gap, so all previous entries are > LZ4_DISTANCE_MAX back, is faster + * than compressing without a gap. However, compressing with + * currentOffset == 0 is faster still, so we preserve that case. + */ + if (cctx->currentOffset != 0 && tableType == byU32) { + DEBUGLOG(5, "LZ4_prepareTable: adding 64KB to currentOffset"); + cctx->currentOffset += 64 KB; + } + + /* Finally, clear history */ + cctx->dictCtx = NULL; + cctx->dictionary = NULL; + cctx->dictSize = 0; +} + +/** LZ4_compress_generic() : + inlined, to ensure branches are decided at compilation time */ +LZ4_FORCE_INLINE int LZ4_compress_generic( + LZ4_stream_t_internal* const cctx, + const char* const source, + char* const dest, + const int inputSize, + int *inputConsumed, /* only written when outputDirective == fillOutput */ + const int maxOutputSize, + const limitedOutput_directive outputDirective, + const tableType_t tableType, + const dict_directive dictDirective, + const dictIssue_directive dictIssue, + const int acceleration) +{ + int result; + const BYTE* ip = (const BYTE*) source; + + U32 const startIndex = cctx->currentOffset; + const BYTE* base = (const BYTE*) source - startIndex; + const BYTE* lowLimit; + + const LZ4_stream_t_internal* dictCtx = (const LZ4_stream_t_internal*) cctx->dictCtx; + const BYTE* const dictionary = + dictDirective == usingDictCtx ? dictCtx->dictionary : cctx->dictionary; + const U32 dictSize = + dictDirective == usingDictCtx ? dictCtx->dictSize : cctx->dictSize; + const U32 dictDelta = (dictDirective == usingDictCtx) ? startIndex - dictCtx->currentOffset : 0; /* make indexes in dictCtx comparable with index in current context */ + + int const maybe_extMem = (dictDirective == usingExtDict) || (dictDirective == usingDictCtx); + U32 const prefixIdxLimit = startIndex - dictSize; /* used when dictDirective == dictSmall */ + const BYTE* const dictEnd = dictionary + dictSize; + const BYTE* anchor = (const BYTE*) source; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimitPlusOne = iend - MFLIMIT + 1; + const BYTE* const matchlimit = iend - LASTLITERALS; + + /* the dictCtx currentOffset is indexed on the start of the dictionary, + * while a dictionary in the current context precedes the currentOffset */ + const BYTE* dictBase = (dictDirective == usingDictCtx) ? + dictionary + dictSize - dictCtx->currentOffset : + dictionary + dictSize - startIndex; + + BYTE* op = (BYTE*) dest; + BYTE* const olimit = op + maxOutputSize; + + U32 offset = 0; + U32 forwardH; + + DEBUGLOG(5, "LZ4_compress_generic: srcSize=%i, tableType=%u", inputSize, tableType); + /* If init conditions are not met, we don't have to mark stream + * as having dirty context, since no action was taken yet */ + if (outputDirective == fillOutput && maxOutputSize < 1) return 0; /* Impossible to store anything */ + if ((U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported inputSize, too large (or negative) */ + if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) return 0; /* Size too large (not within 64K limit) */ + if (tableType==byPtr) assert(dictDirective==noDict); /* only supported use case with byPtr */ + assert(acceleration >= 1); + + lowLimit = (const BYTE*)source - (dictDirective == withPrefix64k ? dictSize : 0); + + /* Update context state */ + if (dictDirective == usingDictCtx) { + /* Subsequent linked blocks can't use the dictionary. */ + /* Instead, they use the block we just compressed. */ + cctx->dictCtx = NULL; + cctx->dictSize = (U32)inputSize; + } else { + cctx->dictSize += (U32)inputSize; + } + cctx->currentOffset += (U32)inputSize; + cctx->tableType = (U16)tableType; + + if (inputSizehashTable, tableType, base); + ip++; forwardH = LZ4_hashPosition(ip, tableType); + + /* Main Loop */ + for ( ; ; ) { + const BYTE* match; + BYTE* token; + + /* Find a match */ + if (tableType == byPtr) { + const BYTE* forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, base); + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base); + + } while ( (match+LZ4_DISTANCE_MAX < ip) + || (LZ4_read32(match) != LZ4_read32(ip)) ); + + } else { /* byU32, byU16 */ + + const BYTE* forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + U32 const current = (U32)(forwardIp - base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex <= current); + assert(forwardIp - base < (ptrdiff_t)(2 GB - 1)); + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + assert(tableType == byU32); + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + matchIndex += dictDelta; /* make dictCtx index comparable with current context */ + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else if (dictDirective==usingExtDict) { + if (matchIndex < startIndex) { + DEBUGLOG(7, "extDict candidate: matchIndex=%5u < startIndex=%5u", matchIndex, startIndex); + assert(startIndex - matchIndex >= MINMATCH); + match = dictBase + matchIndex; + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else { /* single continuous memory segment */ + match = base + matchIndex; + } + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + + if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) continue; /* match outside of valid area */ + assert(matchIndex < current); + if ((tableType != byU16) && (matchIndex+LZ4_DISTANCE_MAX < current)) continue; /* too far */ + if (tableType == byU16) assert((current - matchIndex) <= LZ4_DISTANCE_MAX); /* too_far presumed impossible with byU16 */ + + if (LZ4_read32(match) == LZ4_read32(ip)) { + if (maybe_extMem) offset = current - matchIndex; + break; /* match found */ + } + + } while(1); + } + + /* Catch up */ + while (((ip>anchor) & (match > lowLimit)) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; } + + /* Encode Literals */ + { unsigned const litLength = (unsigned)(ip - anchor); + token = op++; + if ((outputDirective == limitedOutput) && /* Check output buffer overflow */ + (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit)) ) + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + + if ((outputDirective == fillOutput) && + (unlikely(op + (litLength+240)/255 /* litlen */ + litLength /* literals */ + 2 /* offset */ + 1 /* token */ + MFLIMIT - MINMATCH /* min last literals so last match is <= end - MFLIMIT */ > olimit))) { + op--; + goto _last_literals; + } + if (litLength >= RUN_MASK) { + int len = (int)(litLength - RUN_MASK); + *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255; + *op++ = (BYTE)len; + } + else *token = (BYTE)(litLength< olimit)) { + /* the match was too close to the end, rewind and go to last literals */ + op = token; + goto _last_literals; + } + + /* Encode Offset */ + if (maybe_extMem) { /* static test */ + DEBUGLOG(6, " with offset=%u (ext if > %i)", offset, (int)(ip - (const BYTE*)source)); + assert(offset <= LZ4_DISTANCE_MAX && offset > 0); + LZ4_writeLE16(op, (U16)offset); op+=2; + } else { + DEBUGLOG(6, " with offset=%u (same segment)", (U32)(ip - match)); + assert(ip-match <= LZ4_DISTANCE_MAX); + LZ4_writeLE16(op, (U16)(ip - match)); op+=2; + } + + /* Encode MatchLength */ + { unsigned matchCode; + + if ( (dictDirective==usingExtDict || dictDirective==usingDictCtx) + && (lowLimit==dictionary) /* match within extDict */ ) { + const BYTE* limit = ip + (dictEnd-match); + assert(dictEnd > match); + if (limit > matchlimit) limit = matchlimit; + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit); + ip += (size_t)matchCode + MINMATCH; + if (ip==limit) { + unsigned const more = LZ4_count(limit, (const BYTE*)source, matchlimit); + matchCode += more; + ip += more; + } + DEBUGLOG(6, " with matchLength=%u starting in extDict", matchCode+MINMATCH); + } else { + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit); + ip += (size_t)matchCode + MINMATCH; + DEBUGLOG(6, " with matchLength=%u", matchCode+MINMATCH); + } + + if ((outputDirective) && /* Check output buffer overflow */ + (unlikely(op + (1 + LASTLITERALS) + (matchCode>>8) > olimit)) ) { + if (outputDirective == fillOutput) { + /* Match description too long : reduce it */ + U32 newMatchCode = 15 /* in token */ - 1 /* to avoid needing a zero byte */ + ((U32)(olimit - op) - 2 - 1 - LASTLITERALS) * 255; + ip -= matchCode - newMatchCode; + matchCode = newMatchCode; + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } + if (matchCode >= ML_MASK) { + *token += ML_MASK; + matchCode -= ML_MASK; + LZ4_write32(op, 0xFFFFFFFF); + while (matchCode >= 4*255) { + op+=4; + LZ4_write32(op, 0xFFFFFFFF); + matchCode -= 4*255; + } + op += matchCode / 255; + *op++ = (BYTE)(matchCode % 255); + } else + *token += (BYTE)(matchCode); + } + + anchor = ip; + + /* Test end of chunk */ + if (ip >= mflimitPlusOne) break; + + /* Fill table */ + LZ4_putPosition(ip-2, cctx->hashTable, tableType, base); + + /* Test next position */ + if (tableType == byPtr) { + + match = LZ4_getPosition(ip, cctx->hashTable, tableType, base); + LZ4_putPosition(ip, cctx->hashTable, tableType, base); + if ( (match+LZ4_DISTANCE_MAX >= ip) + && (LZ4_read32(match) == LZ4_read32(ip)) ) + { token=op++; *token=0; goto _next_match; } + + } else { /* byU32, byU16 */ + + U32 const h = LZ4_hashPosition(ip, tableType); + U32 const current = (U32)(ip-base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex < current); + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + matchIndex += dictDelta; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else if (dictDirective==usingExtDict) { + if (matchIndex < startIndex) { + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else { /* single memory segment */ + match = base + matchIndex; + } + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + assert(matchIndex < current); + if ( ((dictIssue==dictSmall) ? (matchIndex >= prefixIdxLimit) : 1) + && ((tableType==byU16) ? 1 : (matchIndex+LZ4_DISTANCE_MAX >= current)) + && (LZ4_read32(match) == LZ4_read32(ip)) ) { + token=op++; + *token=0; + if (maybe_extMem) offset = current - matchIndex; + DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i", + (int)(anchor-(const BYTE*)source), 0, (int)(ip-(const BYTE*)source)); + goto _next_match; + } + } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition(++ip, tableType); + + } + +_last_literals: + /* Encode Last Literals */ + { size_t lastRun = (size_t)(iend - anchor); + if ( (outputDirective) && /* Check output buffer overflow */ + (op + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > olimit)) { + if (outputDirective == fillOutput) { + /* adapt lastRun to fill 'dst' */ + assert(olimit >= op); + lastRun = (size_t)(olimit-op) - 1; + lastRun -= (lastRun+240)/255; + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } + if (lastRun >= RUN_MASK) { + size_t accumulator = lastRun - RUN_MASK; + *op++ = RUN_MASK << ML_BITS; + for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRun< 0); + return result; +} + + +int LZ4_compress_fast_extState(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + LZ4_stream_t_internal* const ctx = & LZ4_initStream(state, sizeof(LZ4_stream_t)) -> internal_donotuse; + assert(ctx != NULL); + if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + if (maxOutputSize >= LZ4_compressBound(inputSize)) { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (inputSize < LZ4_64Klimit) {; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + +/** + * LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see comment in lz4.h on LZ4_resetStream_fast() for a definition of + * "correctly initialized"). + */ +int LZ4_compress_fast_extState_fastReset(void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration) +{ + LZ4_stream_t_internal* ctx = &((LZ4_stream_t*)state)->internal_donotuse; + if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + + if (dstCapacity >= LZ4_compressBound(srcSize)) { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + + +int LZ4_compress_fast(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + int result; +#if (LZ4_HEAPMODE) + LZ4_stream_t* ctxPtr = ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctxPtr == NULL) return 0; +#else + LZ4_stream_t ctx; + LZ4_stream_t* const ctxPtr = &ctx; +#endif + result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration); + +#if (LZ4_HEAPMODE) + FREEMEM(ctxPtr); +#endif + return result; +} + + +int LZ4_compress_default(const char* src, char* dst, int srcSize, int maxOutputSize) +{ + return LZ4_compress_fast(src, dst, srcSize, maxOutputSize, 1); +} + + +/* hidden debug function */ +/* strangely enough, gcc generates faster code when this function is uncommented, even if unused */ +int LZ4_compress_fast_force(const char* src, char* dst, int srcSize, int dstCapacity, int acceleration) +{ + LZ4_stream_t ctx; + LZ4_initStream(&ctx, sizeof(ctx)); + + if (srcSize < LZ4_64Klimit) { + return LZ4_compress_generic(&ctx.internal_donotuse, src, dst, srcSize, NULL, dstCapacity, limitedOutput, byU16, noDict, noDictIssue, acceleration); + } else { + tableType_t const addrMode = (sizeof(void*) > 4) ? byU32 : byPtr; + return LZ4_compress_generic(&ctx.internal_donotuse, src, dst, srcSize, NULL, dstCapacity, limitedOutput, addrMode, noDict, noDictIssue, acceleration); + } +} + + +/* Note!: This function leaves the stream in an unclean/broken state! + * It is not safe to subsequently use the same state with a _fastReset() or + * _continue() call without resetting it. */ +static int LZ4_compress_destSize_extState (LZ4_stream_t* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize) +{ + void* const s = LZ4_initStream(state, sizeof (*state)); + assert(s != NULL); (void)s; + + if (targetDstSize >= LZ4_compressBound(*srcSizePtr)) { /* compression success is guaranteed */ + return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, 1); + } else { + if (*srcSizePtr < LZ4_64Klimit) { + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, byU16, noDict, noDictIssue, 1); + } else { + tableType_t const addrMode = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, addrMode, noDict, noDictIssue, 1); + } } +} + + +int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize) +{ +#if (LZ4_HEAPMODE) + LZ4_stream_t* ctx = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctx == NULL) return 0; +#else + LZ4_stream_t ctxBody; + LZ4_stream_t* ctx = &ctxBody; +#endif + + int result = LZ4_compress_destSize_extState(ctx, src, dst, srcSizePtr, targetDstSize); + +#if (LZ4_HEAPMODE) + FREEMEM(ctx); +#endif + return result; +} + + + +/*-****************************** +* Streaming functions +********************************/ + +LZ4_stream_t* LZ4_createStream(void) +{ + LZ4_stream_t* const lz4s = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); + LZ4_STATIC_ASSERT(LZ4_STREAMSIZE >= sizeof(LZ4_stream_t_internal)); /* A compilation error here means LZ4_STREAMSIZE is not large enough */ + DEBUGLOG(4, "LZ4_createStream %p", lz4s); + if (lz4s == NULL) return NULL; + LZ4_initStream(lz4s, sizeof(*lz4s)); + return lz4s; +} + +#ifndef _MSC_VER /* for some reason, Visual fails the aligment test on 32-bit x86 : + it reports an aligment of 8-bytes, + while actually aligning LZ4_stream_t on 4 bytes. */ +static size_t LZ4_stream_t_alignment(void) +{ + struct { char c; LZ4_stream_t t; } t_a; + return sizeof(t_a) - sizeof(t_a.t); +} +#endif + +LZ4_stream_t* LZ4_initStream (void* buffer, size_t size) +{ + DEBUGLOG(5, "LZ4_initStream"); + if (buffer == NULL) return NULL; + if (size < sizeof(LZ4_stream_t)) return NULL; +#ifndef _MSC_VER /* for some reason, Visual fails the aligment test on 32-bit x86 : + it reports an aligment of 8-bytes, + while actually aligning LZ4_stream_t on 4 bytes. */ + if (((size_t)buffer) & (LZ4_stream_t_alignment() - 1)) return NULL; /* alignment check */ +#endif + MEM_INIT(buffer, 0, sizeof(LZ4_stream_t)); + return (LZ4_stream_t*)buffer; +} + +/* resetStream is now deprecated, + * prefer initStream() which is more general */ +void LZ4_resetStream (LZ4_stream_t* LZ4_stream) +{ + DEBUGLOG(5, "LZ4_resetStream (ctx:%p)", LZ4_stream); + MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t)); +} + +void LZ4_resetStream_fast(LZ4_stream_t* ctx) { + LZ4_prepareTable(&(ctx->internal_donotuse), 0, byU32); +} + +int LZ4_freeStream (LZ4_stream_t* LZ4_stream) +{ + if (!LZ4_stream) return 0; /* support free on NULL */ + DEBUGLOG(5, "LZ4_freeStream %p", LZ4_stream); + FREEMEM(LZ4_stream); + return (0); +} + + +#define HASH_UNIT sizeof(reg_t) +int LZ4_loadDict (LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) +{ + LZ4_stream_t_internal* dict = &LZ4_dict->internal_donotuse; + const tableType_t tableType = byU32; + const BYTE* p = (const BYTE*)dictionary; + const BYTE* const dictEnd = p + dictSize; + const BYTE* base; + + DEBUGLOG(4, "LZ4_loadDict (%i bytes from %p into %p)", dictSize, dictionary, LZ4_dict); + + /* It's necessary to reset the context, + * and not just continue it with prepareTable() + * to avoid any risk of generating overflowing matchIndex + * when compressing using this dictionary */ + LZ4_resetStream(LZ4_dict); + + /* We always increment the offset by 64 KB, since, if the dict is longer, + * we truncate it to the last 64k, and if it's shorter, we still want to + * advance by a whole window length so we can provide the guarantee that + * there are only valid offsets in the window, which allows an optimization + * in LZ4_compress_fast_continue() where it uses noDictIssue even when the + * dictionary isn't a full 64k. */ + + if ((dictEnd - p) > 64 KB) p = dictEnd - 64 KB; + base = dictEnd - 64 KB - dict->currentOffset; + dict->dictionary = p; + dict->dictSize = (U32)(dictEnd - p); + dict->currentOffset += 64 KB; + dict->tableType = tableType; + + if (dictSize < (int)HASH_UNIT) { + return 0; + } + + while (p <= dictEnd-HASH_UNIT) { + LZ4_putPosition(p, dict->hashTable, tableType, base); + p+=3; + } + + return (int)dict->dictSize; +} + +void LZ4_attach_dictionary(LZ4_stream_t *working_stream, const LZ4_stream_t *dictionary_stream) { + /* Calling LZ4_resetStream_fast() here makes sure that changes will not be + * erased by subsequent calls to LZ4_resetStream_fast() in case stream was + * marked as having dirty context, e.g. requiring full reset. + */ + LZ4_resetStream_fast(working_stream); + + if (dictionary_stream != NULL) { + /* If the current offset is zero, we will never look in the + * external dictionary context, since there is no value a table + * entry can take that indicate a miss. In that case, we need + * to bump the offset to something non-zero. + */ + if (working_stream->internal_donotuse.currentOffset == 0) { + working_stream->internal_donotuse.currentOffset = 64 KB; + } + working_stream->internal_donotuse.dictCtx = &(dictionary_stream->internal_donotuse); + } else { + working_stream->internal_donotuse.dictCtx = NULL; + } +} + + +static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, int nextSize) +{ + assert(nextSize >= 0); + if (LZ4_dict->currentOffset + (unsigned)nextSize > 0x80000000) { /* potential ptrdiff_t overflow (32-bits mode) */ + /* rescale hash table */ + U32 const delta = LZ4_dict->currentOffset - 64 KB; + const BYTE* dictEnd = LZ4_dict->dictionary + LZ4_dict->dictSize; + int i; + DEBUGLOG(4, "LZ4_renormDictT"); + for (i=0; ihashTable[i] < delta) LZ4_dict->hashTable[i]=0; + else LZ4_dict->hashTable[i] -= delta; + } + LZ4_dict->currentOffset = 64 KB; + if (LZ4_dict->dictSize > 64 KB) LZ4_dict->dictSize = 64 KB; + LZ4_dict->dictionary = dictEnd - LZ4_dict->dictSize; + } +} + + +int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream, + const char* source, char* dest, + int inputSize, int maxOutputSize, + int acceleration) +{ + const tableType_t tableType = byU32; + LZ4_stream_t_internal* streamPtr = &LZ4_stream->internal_donotuse; + const BYTE* dictEnd = streamPtr->dictionary + streamPtr->dictSize; + + DEBUGLOG(5, "LZ4_compress_fast_continue (inputSize=%i)", inputSize); + + if (streamPtr->dirty) return 0; /* Uninitialized structure detected */ + LZ4_renormDictT(streamPtr, inputSize); /* avoid index overflow */ + if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + + /* invalidate tiny dictionaries */ + if ( (streamPtr->dictSize-1 < 4-1) /* intentional underflow */ + && (dictEnd != (const BYTE*)source) ) { + DEBUGLOG(5, "LZ4_compress_fast_continue: dictSize(%u) at addr:%p is too small", streamPtr->dictSize, streamPtr->dictionary); + streamPtr->dictSize = 0; + streamPtr->dictionary = (const BYTE*)source; + dictEnd = (const BYTE*)source; + } + + /* Check overlapping input/dictionary space */ + { const BYTE* sourceEnd = (const BYTE*) source + inputSize; + if ((sourceEnd > streamPtr->dictionary) && (sourceEnd < dictEnd)) { + streamPtr->dictSize = (U32)(dictEnd - sourceEnd); + if (streamPtr->dictSize > 64 KB) streamPtr->dictSize = 64 KB; + if (streamPtr->dictSize < 4) streamPtr->dictSize = 0; + streamPtr->dictionary = dictEnd - streamPtr->dictSize; + } + } + + /* prefix mode : source data follows dictionary */ + if (dictEnd == (const BYTE*)source) { + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) + return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, dictSmall, acceleration); + else + return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, noDictIssue, acceleration); + } + + /* external dictionary mode */ + { int result; + if (streamPtr->dictCtx) { + /* We depend here on the fact that dictCtx'es (produced by + * LZ4_loadDict) guarantee that their tables contain no references + * to offsets between dictCtx->currentOffset - 64 KB and + * dictCtx->currentOffset - dictCtx->dictSize. This makes it safe + * to use noDictIssue even when the dict isn't a full 64 KB. + */ + if (inputSize > 4 KB) { + /* For compressing large blobs, it is faster to pay the setup + * cost to copy the dictionary's tables into the active context, + * so that the compression loop is only looking into one table. + */ + memcpy(streamPtr, streamPtr->dictCtx, sizeof(LZ4_stream_t)); + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingDictCtx, noDictIssue, acceleration); + } + } else { + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, dictSmall, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } + } + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)inputSize; + return result; + } +} + + +/* Hidden debug function, to force-test external dictionary mode */ +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize) +{ + LZ4_stream_t_internal* streamPtr = &LZ4_dict->internal_donotuse; + int result; + + LZ4_renormDictT(streamPtr, srcSize); + + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, dictSmall, 1); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, noDictIssue, 1); + } + + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)srcSize; + + return result; +} + + +/*! LZ4_saveDict() : + * If previously compressed data block is not guaranteed to remain available at its memory location, + * save it into a safer place (char* safeBuffer). + * Note : you don't need to call LZ4_loadDict() afterwards, + * dictionary is immediately usable, you can therefore call LZ4_compress_fast_continue(). + * Return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error. + */ +int LZ4_saveDict (LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize) +{ + LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse; + const BYTE* const previousDictEnd = dict->dictionary + dict->dictSize; + + if ((U32)dictSize > 64 KB) dictSize = 64 KB; /* useless to define a dictionary > 64 KB */ + if ((U32)dictSize > dict->dictSize) dictSize = (int)dict->dictSize; + + memmove(safeBuffer, previousDictEnd - dictSize, dictSize); + + dict->dictionary = (const BYTE*)safeBuffer; + dict->dictSize = (U32)dictSize; + + return dictSize; +} + + + +/*-******************************* + * Decompression functions + ********************************/ + +typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive; +typedef enum { decode_full_block = 0, partial_decode = 1 } earlyEnd_directive; + +#undef MIN +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) + +/* Read the variable-length literal or match length. + * + * ip - pointer to use as input. + * lencheck - end ip. Return an error if ip advances >= lencheck. + * loop_check - check ip >= lencheck in body of loop. Returns loop_error if so. + * initial_check - check ip >= lencheck before start of loop. Returns initial_error if so. + * error (output) - error code. Should be set to 0 before call. + */ +typedef enum { loop_error = -2, initial_error = -1, ok = 0 } variable_length_error; +LZ4_FORCE_INLINE unsigned +read_variable_length(const BYTE**ip, const BYTE* lencheck, int loop_check, int initial_check, variable_length_error* error) +{ + unsigned length = 0; + unsigned s; + if (initial_check && unlikely((*ip) >= lencheck)) { /* overflow detection */ + *error = initial_error; + return length; + } + do { + s = **ip; + (*ip)++; + length += s; + if (loop_check && unlikely((*ip) >= lencheck)) { /* overflow detection */ + *error = loop_error; + return length; + } + } while (s==255); + + return length; +} + +/*! LZ4_decompress_generic() : + * This generic decompression function covers all use cases. + * It shall be instantiated several times, using different sets of directives. + * Note that it is important for performance that this function really get inlined, + * in order to remove useless branches during compilation optimization. + */ +LZ4_FORCE_INLINE int +LZ4_decompress_generic( + const char* const src, + char* const dst, + int srcSize, + int outputSize, /* If endOnInput==endOnInputSize, this value is `dstCapacity` */ + + endCondition_directive endOnInput, /* endOnOutputSize, endOnInputSize */ + earlyEnd_directive partialDecoding, /* full, partial */ + dict_directive dict, /* noDict, withPrefix64k, usingExtDict */ + const BYTE* const lowPrefix, /* always <= dst, == dst when no prefix */ + const BYTE* const dictStart, /* only if dict==usingExtDict */ + const size_t dictSize /* note : = 0 if noDict */ + ) +{ + if (src == NULL) return -1; + + { const BYTE* ip = (const BYTE*) src; + const BYTE* const iend = ip + srcSize; + + BYTE* op = (BYTE*) dst; + BYTE* const oend = op + outputSize; + BYTE* cpy; + + const BYTE* const dictEnd = (dictStart == NULL) ? NULL : dictStart + dictSize; + + const int safeDecode = (endOnInput==endOnInputSize); + const int checkOffset = ((safeDecode) && (dictSize < (int)(64 KB))); + + + /* Set up the "end" pointers for the shortcut. */ + const BYTE* const shortiend = iend - (endOnInput ? 14 : 8) /*maxLL*/ - 2 /*offset*/; + const BYTE* const shortoend = oend - (endOnInput ? 14 : 8) /*maxLL*/ - 18 /*maxML*/; + + const BYTE* match; + size_t offset; + unsigned token; + size_t length; + + + DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i, dstSize:%i)", srcSize, outputSize); + + /* Special cases */ + assert(lowPrefix <= op); + if ((endOnInput) && (unlikely(outputSize==0))) return ((srcSize==1) && (*ip==0)) ? 0 : -1; /* Empty output buffer */ + if ((!endOnInput) && (unlikely(outputSize==0))) return (*ip==0 ? 1 : -1); + if ((endOnInput) && unlikely(srcSize==0)) return -1; + + /* Currently the fast loop shows a regression on qualcomm arm chips. */ +#if LZ4_FAST_DEC_LOOP + if ((oend - op) < FASTLOOP_SAFE_DISTANCE) { + DEBUGLOG(6, "skip fast decode loop"); + goto safe_decode; + } + + /* Fast loop : decode sequences as long as output < iend-FASTLOOP_SAFE_DISTANCE */ + while (1) { + /* Main fastloop assertion: We can always wildcopy FASTLOOP_SAFE_DISTANCE */ + assert(oend - op >= FASTLOOP_SAFE_DISTANCE); + if (endOnInput) assert(ip < iend); + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + assert(!endOnInput || ip <= iend); /* ip < iend before the increment */ + + /* decode literal length */ + if (length == RUN_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend-RUN_MASK, endOnInput, endOnInput, &error); + if (error == initial_error) goto _output_error; + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)(op))) goto _output_error; /* overflow detection */ + if ((safeDecode) && unlikely((uptrval)(ip)+length<(uptrval)(ip))) goto _output_error; /* overflow detection */ + + /* copy literals */ + cpy = op+length; + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if (endOnInput) { /* LZ4_decompress_safe() */ + if ((cpy>oend-32) || (ip+length>iend-32)) goto safe_literal_copy; + LZ4_wildCopy32(op, ip, cpy); + } else { /* LZ4_decompress_fast() */ + if (cpy>oend-8) goto safe_literal_copy; + LZ4_wildCopy8(op, ip, cpy); /* LZ4_decompress_fast() cannot copy more than 8 bytes at a time : + * it doesn't know input length, and only relies on end-of-block properties */ + } + ip += length; op = cpy; + } else { + cpy = op+length; + if (endOnInput) { /* LZ4_decompress_safe() */ + DEBUGLOG(7, "copy %u bytes in a 16-bytes stripe", (unsigned)length); + /* We don't need to check oend, since we check it once for each loop below */ + if (ip > iend-(16 + 1/*max lit + offset + nextToken*/)) goto safe_literal_copy; + /* Literals can only be 14, but hope compilers optimize if we copy by a register size */ + memcpy(op, ip, 16); + } else { /* LZ4_decompress_fast() */ + /* LZ4_decompress_fast() cannot copy more than 8 bytes at a time : + * it doesn't know input length, and relies on end-of-block properties */ + memcpy(op, ip, 8); + if (length > 8) memcpy(op+8, ip+8, 8); + } + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + + /* get matchlength */ + length = token & ML_MASK; + + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */ + + if (length == ML_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend - LASTLITERALS + 1, endOnInput, 0, &error); + if (error != ok) goto _output_error; + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ + length += MINMATCH; + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } + } else { + length += MINMATCH; + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } + + /* Fastpath check: Avoids a branch in LZ4_wildCopy32 if true */ + if (!(dict == usingExtDict) || (match >= lowPrefix)) { + if (offset >= 8) { + memcpy(op, match, 8); + memcpy(op+8, match+8, 8); + memcpy(op+16, match+16, 2); + op += length; + continue; + } } } + + /* match starting within external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + if (unlikely(op+length > oend-LASTLITERALS)) { + if (partialDecoding) length = MIN(length, (size_t)(oend-op)); + else goto _output_error; /* doesn't respect parsing restriction */ + } + + if (length <= (size_t)(lowPrefix-match)) { + /* match fits entirely within external dictionary : just copy */ + memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) *op++ = *copyFrom++; + } else { + memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + + /* copy match within block */ + cpy = op + length; + + assert((op <= oend) && (oend-op >= 32)); + if (unlikely(offset<16)) { + LZ4_memcpy_using_offset(op, match, cpy, offset); + } else { + LZ4_wildCopy32(op, match, cpy); + } + + op = cpy; /* wildcopy correction */ + } + safe_decode: +#endif + + /* Main Loop : decode remaining sequences where output < FASTLOOP_SAFE_DISTANCE */ + while (1) { + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + assert(!endOnInput || ip <= iend); /* ip < iend before the increment */ + + /* A two-stage shortcut for the most common case: + * 1) If the literal length is 0..14, and there is enough space, + * enter the shortcut and copy 16 bytes on behalf of the literals + * (in the fast mode, only 8 bytes can be safely copied this way). + * 2) Further if the match length is 4..18, copy 18 bytes in a similar + * manner; but we ensure that there's enough space in the output for + * those 18 bytes earlier, upon entering the shortcut (in other words, + * there is a combined check for both stages). + */ + if ( (endOnInput ? length != RUN_MASK : length <= 8) + /* strictly "less than" on input, to re-enter the loop with at least one byte */ + && likely((endOnInput ? ip < shortiend : 1) & (op <= shortoend)) ) { + /* Copy the literals */ + memcpy(op, ip, endOnInput ? 16 : 8); + op += length; ip += length; + + /* The second stage: prepare for match copying, decode full info. + * If it doesn't work out, the info won't be wasted. */ + length = token & ML_MASK; /* match length */ + offset = LZ4_readLE16(ip); ip += 2; + match = op - offset; + assert(match <= op); /* check overflow */ + + /* Do not deal with overlapping matches. */ + if ( (length != ML_MASK) + && (offset >= 8) + && (dict==withPrefix64k || match >= lowPrefix) ) { + /* Copy the match. */ + memcpy(op + 0, match + 0, 8); + memcpy(op + 8, match + 8, 8); + memcpy(op +16, match +16, 2); + op += length + MINMATCH; + /* Both stages worked, load the next token. */ + continue; + } + + /* The second stage didn't work out, but the info is ready. + * Propel it right to the point of match copying. */ + goto _copy_match; + } + + /* decode literal length */ + if (length == RUN_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend-RUN_MASK, endOnInput, endOnInput, &error); + if (error == initial_error) goto _output_error; + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)(op))) goto _output_error; /* overflow detection */ + if ((safeDecode) && unlikely((uptrval)(ip)+length<(uptrval)(ip))) goto _output_error; /* overflow detection */ + } + + /* copy literals */ + cpy = op+length; +#if LZ4_FAST_DEC_LOOP + safe_literal_copy: +#endif + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if ( ((endOnInput) && ((cpy>oend-MFLIMIT) || (ip+length>iend-(2+1+LASTLITERALS))) ) + || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) + { + if (partialDecoding) { + if (cpy > oend) { cpy = oend; assert(op<=oend); length = (size_t)(oend-op); } /* Partial decoding : stop in the middle of literal segment */ + if ((endOnInput) && (ip+length > iend)) goto _output_error; /* Error : read attempt beyond end of input buffer */ + } else { + if ((!endOnInput) && (cpy != oend)) goto _output_error; /* Error : block decoding must stop exactly there */ + if ((endOnInput) && ((ip+length != iend) || (cpy > oend))) goto _output_error; /* Error : input must be consumed */ + } + memcpy(op, ip, length); + ip += length; + op += length; + if (!partialDecoding || (cpy == oend)) { + /* Necessarily EOF, due to parsing restrictions */ + break; + } + + } else { + LZ4_wildCopy8(op, ip, cpy); /* may overwrite up to WILDCOPYLENGTH beyond cpy */ + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + + /* get matchlength */ + length = token & ML_MASK; + + _copy_match: + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */ + if (!partialDecoding) { + assert(oend > op); + assert(oend - op >= 4); + LZ4_write32(op, 0); /* silence an msan warning when offset==0; costs <1%; */ + } /* note : when partialDecoding, there is no guarantee that at least 4 bytes remain available in output buffer */ + + if (length == ML_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend - LASTLITERALS + 1, endOnInput, 0, &error); + if (error != ok) goto _output_error; + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ + } + length += MINMATCH; + +#if LZ4_FAST_DEC_LOOP + safe_match_copy: +#endif + /* match starting within external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + if (unlikely(op+length > oend-LASTLITERALS)) { + if (partialDecoding) length = MIN(length, (size_t)(oend-op)); + else goto _output_error; /* doesn't respect parsing restriction */ + } + + if (length <= (size_t)(lowPrefix-match)) { + /* match fits entirely within external dictionary : just copy */ + memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) *op++ = *copyFrom++; + } else { + memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + + /* copy match within block */ + cpy = op + length; + + /* partialDecoding : may end anywhere within the block */ + assert(op<=oend); + if (partialDecoding && (cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { + size_t const mlen = MIN(length, (size_t)(oend-op)); + const BYTE* const matchEnd = match + mlen; + BYTE* const copyEnd = op + mlen; + if (matchEnd > op) { /* overlap copy */ + while (op < copyEnd) *op++ = *match++; + } else { + memcpy(op, match, mlen); + } + op = copyEnd; + if (op==oend) break; + continue; + } + + if (unlikely(offset<8)) { + op[0] = match[0]; + op[1] = match[1]; + op[2] = match[2]; + op[3] = match[3]; + match += inc32table[offset]; + memcpy(op+4, match, 4); + match -= dec64table[offset]; + } else { + memcpy(op, match, 8); + match += 8; + } + op += 8; + + if (unlikely(cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { + BYTE* const oCopyLimit = oend - (WILDCOPYLENGTH-1); + if (cpy > oend-LASTLITERALS) goto _output_error; /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + if (op < oCopyLimit) { + LZ4_wildCopy8(op, match, oCopyLimit); + match += oCopyLimit - op; + op = oCopyLimit; + } + while (op < cpy) *op++ = *match++; + } else { + memcpy(op, match, 8); + if (length > 16) LZ4_wildCopy8(op+8, match+8, cpy); + } + op = cpy; /* wildcopy correction */ + } + + /* end of decoding */ + if (endOnInput) + return (int) (((char*)op)-dst); /* Nb of output bytes decoded */ + else + return (int) (((const char*)ip)-src); /* Nb of input bytes read */ + + /* Overflow error detected */ + _output_error: + return (int) (-(((const char*)ip)-src))-1; + } +} + + +/*===== Instantiate the API decoding functions. =====*/ + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, + endOnInputSize, decode_full_block, noDict, + (BYTE*)dest, NULL, 0); +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe_partial(const char* src, char* dst, int compressedSize, int targetOutputSize, int dstCapacity) +{ + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(src, dst, compressedSize, dstCapacity, + endOnInputSize, partial_decode, + noDict, (BYTE*)dst, NULL, 0); +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_fast(const char* source, char* dest, int originalSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, decode_full_block, withPrefix64k, + (BYTE*)dest - 64 KB, NULL, 0); +} + +/*===== Instantiate a few more decoding cases, used more than once. =====*/ + +LZ4_FORCE_O2_GCC_PPC64LE /* Exported, an obsolete API function. */ +int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, withPrefix64k, + (BYTE*)dest - 64 KB, NULL, 0); +} + +/* Another obsolete API function, paired with the previous one. */ +int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize) +{ + /* LZ4_decompress_fast doesn't validate match offsets, + * and thus serves well with any prefixed dictionary. */ + return LZ4_decompress_fast(source, dest, originalSize); +} + +LZ4_FORCE_O2_GCC_PPC64LE +static int LZ4_decompress_safe_withSmallPrefix(const char* source, char* dest, int compressedSize, int maxOutputSize, + size_t prefixSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, noDict, + (BYTE*)dest-prefixSize, NULL, 0); +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, + int compressedSize, int maxOutputSize, + const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, usingExtDict, + (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + +LZ4_FORCE_O2_GCC_PPC64LE +static int LZ4_decompress_fast_extDict(const char* source, char* dest, int originalSize, + const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, decode_full_block, usingExtDict, + (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + +/* The "double dictionary" mode, for use with e.g. ring buffers: the first part + * of the dictionary is passed as prefix, and the second via dictStart + dictSize. + * These routines are used only once, in LZ4_decompress_*_continue(). + */ +LZ4_FORCE_INLINE +int LZ4_decompress_safe_doubleDict(const char* source, char* dest, int compressedSize, int maxOutputSize, + size_t prefixSize, const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, usingExtDict, + (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize); +} + +LZ4_FORCE_INLINE +int LZ4_decompress_fast_doubleDict(const char* source, char* dest, int originalSize, + size_t prefixSize, const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, decode_full_block, usingExtDict, + (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize); +} + +/*===== streaming decompression functions =====*/ + +LZ4_streamDecode_t* LZ4_createStreamDecode(void) +{ + LZ4_streamDecode_t* lz4s = (LZ4_streamDecode_t*) ALLOC_AND_ZERO(sizeof(LZ4_streamDecode_t)); + LZ4_STATIC_ASSERT(LZ4_STREAMDECODESIZE >= sizeof(LZ4_streamDecode_t_internal)); /* A compilation error here means LZ4_STREAMDECODESIZE is not large enough */ + return lz4s; +} + +int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream) +{ + if (LZ4_stream == NULL) return 0; /* support free on NULL */ + FREEMEM(LZ4_stream); + return 0; +} + +/*! LZ4_setStreamDecode() : + * Use this function to instruct where to find the dictionary. + * This function is not necessary if previous data is still available where it was decoded. + * Loading a size of 0 is allowed (same effect as no dictionary). + * @return : 1 if OK, 0 if error + */ +int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + lz4sd->prefixSize = (size_t) dictSize; + lz4sd->prefixEnd = (const BYTE*) dictionary + dictSize; + lz4sd->externalDict = NULL; + lz4sd->extDictSize = 0; + return 1; +} + +/*! LZ4_decoderRingBufferSize() : + * when setting a ring buffer for streaming decompression (optional scenario), + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * Note : in a ring buffer scenario, + * blocks are presumed decompressed next to each other. + * When not enough space remains for next block (remainingSize < maxBlockSize), + * decoding resumes from beginning of ring buffer. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +int LZ4_decoderRingBufferSize(int maxBlockSize) +{ + if (maxBlockSize < 0) return 0; + if (maxBlockSize > LZ4_MAX_INPUT_SIZE) return 0; + if (maxBlockSize < 16) maxBlockSize = 16; + return LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize); +} + +/* +*_continue() : + These decoding functions allow decompression of multiple blocks in "streaming" mode. + Previously decoded blocks must still be available at the memory position where they were decoded. + If it's not possible, save the relevant part of decoded data into a safe buffer, + and indicate where it stands using LZ4_setStreamDecode() +*/ +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + int result; + + if (lz4sd->prefixSize == 0) { + /* The first call, no dictionary yet. */ + assert(lz4sd->extDictSize == 0); + result = LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } else if (lz4sd->prefixEnd == (BYTE*)dest) { + /* They're rolling the current segment. */ + if (lz4sd->prefixSize >= 64 KB - 1) + result = LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); + else if (lz4sd->extDictSize == 0) + result = LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, + lz4sd->prefixSize); + else + result = LZ4_decompress_safe_doubleDict(source, dest, compressedSize, maxOutputSize, + lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += (size_t)result; + lz4sd->prefixEnd += result; + } else { + /* The buffer wraps around, or they're switching to another buffer. */ + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, + lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } + + return result; +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int originalSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + int result; + assert(originalSize >= 0); + + if (lz4sd->prefixSize == 0) { + assert(lz4sd->extDictSize == 0); + result = LZ4_decompress_fast(source, dest, originalSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } else if (lz4sd->prefixEnd == (BYTE*)dest) { + if (lz4sd->prefixSize >= 64 KB - 1 || lz4sd->extDictSize == 0) + result = LZ4_decompress_fast(source, dest, originalSize); + else + result = LZ4_decompress_fast_doubleDict(source, dest, originalSize, + lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += (size_t)originalSize; + lz4sd->prefixEnd += originalSize; + } else { + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_fast_extDict(source, dest, originalSize, + lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } + + return result; +} + + +/* +Advanced decoding functions : +*_usingDict() : + These decoding functions work the same as "_continue" ones, + the dictionary must be explicitly provided within parameters +*/ + +int LZ4_decompress_safe_usingDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) +{ + if (dictSize==0) + return LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); + if (dictStart+dictSize == dest) { + if (dictSize >= 64 KB - 1) + return LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); + return LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, dictSize); + } + return LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, dictStart, dictSize); +} + +int LZ4_decompress_fast_usingDict(const char* source, char* dest, int originalSize, const char* dictStart, int dictSize) +{ + if (dictSize==0 || dictStart+dictSize == dest) + return LZ4_decompress_fast(source, dest, originalSize); + return LZ4_decompress_fast_extDict(source, dest, originalSize, dictStart, dictSize); +} + + +/*=************************************************* +* Obsolete Functions +***************************************************/ +/* obsolete compression functions */ +int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize) +{ + return LZ4_compress_default(source, dest, inputSize, maxOutputSize); +} +int LZ4_compress(const char* source, char* dest, int inputSize) +{ + return LZ4_compress_default(source, dest, inputSize, LZ4_compressBound(inputSize)); +} +int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize) +{ + return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1); +} +int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize) +{ + return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1); +} +int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int dstCapacity) +{ + return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, dstCapacity, 1); +} +int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize) +{ + return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1); +} + +/* +These decompression functions are deprecated and should no longer be used. +They are only provided here for compatibility with older user programs. +- LZ4_uncompress is totally equivalent to LZ4_decompress_fast +- LZ4_uncompress_unknownOutputSize is totally equivalent to LZ4_decompress_safe +*/ +int LZ4_uncompress (const char* source, char* dest, int outputSize) +{ + return LZ4_decompress_fast(source, dest, outputSize); +} +int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize) +{ + return LZ4_decompress_safe(source, dest, isize, maxOutputSize); +} + +/* Obsolete Streaming functions */ + +int LZ4_sizeofStreamState() { return LZ4_STREAMSIZE; } + +int LZ4_resetStreamState(void* state, char* inputBuffer) +{ + (void)inputBuffer; + LZ4_resetStream((LZ4_stream_t*)state); + return 0; +} + +void* LZ4_create (char* inputBuffer) +{ + (void)inputBuffer; + return LZ4_createStream(); +} + +char* LZ4_slideInputBuffer (void* state) +{ + /* avoid const char * -> char * conversion warning */ + return (char *)(uptrval)((LZ4_stream_t*)state)->internal_donotuse.dictionary; +} + +#endif /* LZ4_COMMONDEFS_ONLY */ diff --git a/TMessagesProj/jni/lz4/lz4.h b/TMessagesProj/jni/lz4/lz4.h new file mode 100755 index 000000000..a9c932ccb --- /dev/null +++ b/TMessagesProj/jni/lz4/lz4.h @@ -0,0 +1,682 @@ +/* + * LZ4 - Fast LZ compression algorithm + * Header File + * Copyright (C) 2011-present, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ +#if defined (__cplusplus) +extern "C" { +#endif + +#ifndef LZ4_H_2983827168210 +#define LZ4_H_2983827168210 + +/* --- Dependency --- */ +#include /* size_t */ + + +/** + Introduction + + LZ4 is lossless compression algorithm, providing compression speed at 500 MB/s per core, + scalable with multi-cores CPU. It features an extremely fast decoder, with speed in + multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. + + The LZ4 compression library provides in-memory compression and decompression functions. + It gives full buffer control to user. + Compression can be done in: + - a single step (described as Simple Functions) + - a single step, reusing a context (described in Advanced Functions) + - unbounded multiple steps (described as Streaming compression) + + lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md). + Decompressing a block requires additional metadata, such as its compressed size. + Each application is free to encode and pass such metadata in whichever way it wants. + + lz4.h only handle blocks, it can not generate Frames. + + Blocks are different from Frames (doc/lz4_Frame_format.md). + Frames bundle both blocks and metadata in a specified manner. + This are required for compressed data to be self-contained and portable. + Frame format is delivered through a companion API, declared in lz4frame.h. + Note that the `lz4` CLI can only manage frames. +*/ + +/*^*************************************************************** +* Export parameters +*****************************************************************/ +/* +* LZ4_DLL_EXPORT : +* Enable exporting of functions when building a Windows DLL +* LZ4LIB_VISIBILITY : +* Control library symbols visibility. +*/ +#ifndef LZ4LIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define LZ4LIB_VISIBILITY +# endif +#endif +#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) +# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY +#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) +# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ +#else +# define LZ4LIB_API LZ4LIB_VISIBILITY +#endif + +/*------ Version ------*/ +#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ +#define LZ4_VERSION_MINOR 9 /* for new (non-breaking) interface capabilities */ +#define LZ4_VERSION_RELEASE 1 /* for tweaks, bug-fixes, or development */ + +#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) + +#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE +#define LZ4_QUOTE(str) #str +#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) +#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) + +LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version */ +LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version */ + + +/*-************************************ +* Tuning parameter +**************************************/ +/*! + * LZ4_MEMORY_USAGE : + * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.) + * Increasing memory usage improves compression ratio. + * Reduced memory usage may improve speed, thanks to better cache locality. + * Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache + */ +#ifndef LZ4_MEMORY_USAGE +# define LZ4_MEMORY_USAGE 14 +#endif + + +/*-************************************ +* Simple Functions +**************************************/ +/*! LZ4_compress_default() : + Compresses 'srcSize' bytes from buffer 'src' + into already allocated 'dst' buffer of size 'dstCapacity'. + Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). + It also runs faster, so it's a recommended setting. + If the function cannot compress 'src' into a more limited 'dst' budget, + compression stops *immediately*, and the function result is zero. + In which case, 'dst' content is undefined (invalid). + srcSize : max supported value is LZ4_MAX_INPUT_SIZE. + dstCapacity : size of buffer 'dst' (which must be already allocated) + @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) + or 0 if compression fails + Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer). +*/ +LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity); + +/*! LZ4_decompress_safe() : + compressedSize : is the exact complete size of the compressed block. + dstCapacity : is the size of destination buffer, which must be already allocated. + @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + If destination buffer is not large enough, decoding will stop and output an error code (negative value). + If the source stream is detected malformed, the function will stop decoding and return a negative result. + Note : This function is protected against malicious data packets (never writes outside 'dst' buffer, nor read outside 'source' buffer). +*/ +LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity); + + +/*-************************************ +* Advanced Functions +**************************************/ +#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ +#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) + +/*! LZ4_compressBound() : + Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) + This function is primarily useful for memory allocation purposes (destination buffer size). + Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). + Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize) + inputSize : max supported value is LZ4_MAX_INPUT_SIZE + return : maximum output size in a "worst case" scenario + or 0, if input size is incorrect (too large or negative) +*/ +LZ4LIB_API int LZ4_compressBound(int inputSize); + +/*! LZ4_compress_fast() : + Same as LZ4_compress_default(), but allows selection of "acceleration" factor. + The larger the acceleration value, the faster the algorithm, but also the lesser the compression. + It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. + An acceleration value of "1" is the same as regular LZ4_compress_default() + Values <= 0 will be replaced by ACCELERATION_DEFAULT (currently == 1, see lz4.c). +*/ +LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! LZ4_compress_fast_extState() : + * Same as LZ4_compress_fast(), using an externally allocated memory space for its state. + * Use LZ4_sizeofState() to know how much memory must be allocated, + * and allocate it on 8-bytes boundaries (using `malloc()` typically). + * Then, provide this buffer as `void* state` to compression function. + */ +LZ4LIB_API int LZ4_sizeofState(void); +LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! LZ4_compress_destSize() : + * Reverse the logic : compresses as much data as possible from 'src' buffer + * into already allocated buffer 'dst', of size >= 'targetDestSize'. + * This function either compresses the entire 'src' content into 'dst' if it's large enough, + * or fill 'dst' buffer completely with as much data as possible from 'src'. + * note: acceleration parameter is fixed to "default". + * + * *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'. + * New value is necessarily <= input value. + * @return : Nb bytes written into 'dst' (necessarily <= targetDestSize) + * or 0 if compression fails. +*/ +LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize); + + +/*! LZ4_decompress_safe_partial() : + * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', + * into destination buffer 'dst' of size 'dstCapacity'. + * Up to 'targetOutputSize' bytes will be decoded. + * The function stops decoding on reaching this objective, + * which can boost performance when only the beginning of a block is required. + * + * @return : the number of bytes decoded in `dst` (necessarily <= dstCapacity) + * If source stream is detected malformed, function returns a negative result. + * + * Note : @return can be < targetOutputSize, if compressed block contains less data. + * + * Note 2 : this function features 2 parameters, targetOutputSize and dstCapacity, + * and expects targetOutputSize <= dstCapacity. + * It effectively stops decoding on reaching targetOutputSize, + * so dstCapacity is kind of redundant. + * This is because in a previous version of this function, + * decoding operation would not "break" a sequence in the middle. + * As a consequence, there was no guarantee that decoding would stop at exactly targetOutputSize, + * it could write more bytes, though only up to dstCapacity. + * Some "margin" used to be required for this operation to work properly. + * This is no longer necessary. + * The function nonetheless keeps its signature, in an effort to not break API. + */ +LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity); + + +/*-********************************************* +* Streaming Compression Functions +***********************************************/ +typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ + +LZ4LIB_API LZ4_stream_t* LZ4_createStream(void); +LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr); + +/*! LZ4_resetStream_fast() : v1.9.0+ + * Use this to prepare an LZ4_stream_t for a new chain of dependent blocks + * (e.g., LZ4_compress_fast_continue()). + * + * An LZ4_stream_t must be initialized once before usage. + * This is automatically done when created by LZ4_createStream(). + * However, should the LZ4_stream_t be simply declared on stack (for example), + * it's necessary to initialize it first, using LZ4_initStream(). + * + * After init, start any new stream with LZ4_resetStream_fast(). + * A same LZ4_stream_t can be re-used multiple times consecutively + * and compress multiple streams, + * provided that it starts each new stream with LZ4_resetStream_fast(). + * + * LZ4_resetStream_fast() is much faster than LZ4_initStream(), + * but is not compatible with memory regions containing garbage data. + * + * Note: it's only useful to call LZ4_resetStream_fast() + * in the context of streaming compression. + * The *extState* functions perform their own resets. + * Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive. + */ +LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr); + +/*! LZ4_loadDict() : + * Use this function to reference a static dictionary into LZ4_stream_t. + * The dictionary must remain available during compression. + * LZ4_loadDict() triggers a reset, so any previous data will be forgotten. + * The same dictionary will have to be loaded on decompression side for successful decoding. + * Dictionary are useful for better compression of small data (KB range). + * While LZ4 accept any input as dictionary, + * results are generally better when using Zstandard's Dictionary Builder. + * Loading a size of 0 is allowed, and is the same as reset. + * @return : loaded dictionary size, in bytes (necessarily <= 64 KB) + */ +LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); + +/*! LZ4_compress_fast_continue() : + * Compress 'src' content using data from previously compressed blocks, for better compression ratio. + * 'dst' buffer must be already allocated. + * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. + * + * @return : size of compressed block + * or 0 if there is an error (typically, cannot fit into 'dst'). + * + * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block. + * Each block has precise boundaries. + * Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata. + * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together. + * + * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory ! + * + * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB. + * Make sure that buffers are separated, by at least one byte. + * This construction ensures that each block only depends on previous block. + * + * Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. + * + * Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed. + */ +LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_saveDict() : + * If last 64KB data cannot be guaranteed to remain available at its current memory location, + * save it into a safer place (char* safeBuffer). + * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(), + * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables. + * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error. + */ +LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize); + + +/*-********************************************** +* Streaming Decompression Functions +* Bufferless synchronous API +************************************************/ +typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* tracking context */ + +/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() : + * creation / destruction of streaming decompression tracking context. + * A tracking context can be re-used multiple times. + */ +LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void); +LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream); + +/*! LZ4_setStreamDecode() : + * An LZ4_streamDecode_t context can be allocated once and re-used multiple times. + * Use this function to start decompression of a new stream of blocks. + * A dictionary can optionally be set. Use NULL or size 0 for a reset order. + * Dictionary is presumed stable : it must remain accessible and unmodified during next decompression. + * @return : 1 if OK, 0 if error + */ +LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize); + +/*! LZ4_decoderRingBufferSize() : v1.8.2+ + * Note : in a ring buffer scenario (optional), + * blocks are presumed decompressed next to each other + * up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize), + * at which stage it resumes from beginning of ring buffer. + * When setting such a ring buffer for streaming decompression, + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize); +#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) /* for static allocation; maxBlockSize presumed valid */ + +/*! LZ4_decompress_*_continue() : + * These decoding functions allow decompression of consecutive blocks in "streaming" mode. + * A block is an unsplittable entity, it must be presented entirely to a decompression function. + * Decompression functions only accepts one block at a time. + * The last 64KB of previously decoded data *must* remain available and unmodified at the memory position where they were decoded. + * If less than 64KB of data has been decoded, all the data must be present. + * + * Special : if decompression side sets a ring buffer, it must respect one of the following conditions : + * - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize). + * maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes. + * In which case, encoding and decoding buffers do not need to be synchronized. + * Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize. + * - Synchronized mode : + * Decompression buffer size is _exactly_ the same as compression buffer size, + * and follows exactly same update rule (block boundaries at same positions), + * and decoding function is provided with exact decompressed size of each block (exception for last block of the stream), + * _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB). + * - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes. + * In which case, encoding and decoding buffers do not need to be synchronized, + * and encoding ring buffer can have any size, including small ones ( < 64 KB). + * + * Whenever these conditions are not possible, + * save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression, + * then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block. +*/ +LZ4LIB_API int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int srcSize, int dstCapacity); + + +/*! LZ4_decompress_*_usingDict() : + * These decoding functions work the same as + * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_*_continue() + * They are stand-alone, and don't need an LZ4_streamDecode_t structure. + * Dictionary is presumed stable : it must remain accessible and unmodified during decompression. + * Performance tip : Decompression speed can be substantially increased + * when dst == dictStart + dictSize. + */ +LZ4LIB_API int LZ4_decompress_safe_usingDict (const char* src, char* dst, int srcSize, int dstCapcity, const char* dictStart, int dictSize); + + +/*^************************************* + * !!!!!! STATIC LINKING ONLY !!!!!! + ***************************************/ + +/*-**************************************************************************** + * Experimental section + * + * Symbols declared in this section must be considered unstable. Their + * signatures or semantics may change, or they may be removed altogether in the + * future. They are therefore only safe to depend on when the caller is + * statically linked against the library. + * + * To protect against unsafe usage, not only are the declarations guarded, + * the definitions are hidden by default + * when building LZ4 as a shared/dynamic library. + * + * In order to access these declarations, + * define LZ4_STATIC_LINKING_ONLY in your application + * before including LZ4's headers. + * + * In order to make their implementations accessible dynamically, you must + * define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library. + ******************************************************************************/ + +#ifdef LZ4_PUBLISH_STATIC_FUNCTIONS +#define LZ4LIB_STATIC_API LZ4LIB_API +#else +#define LZ4LIB_STATIC_API +#endif + +#ifdef LZ4_STATIC_LINKING_ONLY + + +/*! LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. + * It is only safe to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized"). + * From a high level, the difference is that + * this function initializes the provided state with a call to something like LZ4_resetStream_fast() + * while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream(). + */ +LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_attach_dictionary() : + * This is an experimental API that allows + * efficient use of a static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a + * working LZ4_stream_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDict() should + * be expected to work. + * + * Alternatively, the provided dictionaryStream may be NULL, + * in which case any existing dictionary stream is unset. + * + * If a dictionary is provided, it replaces any pre-existing stream history. + * The dictionary contents are the only history that can be referenced and + * logically immediately precede the data compressed in the first subsequent + * compression call. + * + * The dictionary will only remain attached to the working stream through the + * first compression call, at the end of which it is cleared. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the completion of the first compression call on the stream. + */ +LZ4LIB_STATIC_API void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream); + +#endif + + +/*-************************************************************ + * PRIVATE DEFINITIONS + ************************************************************** + * Do not use these definitions directly. + * They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. + * Accessing members will expose code to API and/or ABI break in future versions of the library. + **************************************************************/ +#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) +#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) +#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ + +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +#include + +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + uint32_t hashTable[LZ4_HASH_SIZE_U32]; + uint32_t currentOffset; + uint16_t dirty; + uint16_t tableType; + const uint8_t* dictionary; + const LZ4_stream_t_internal* dictCtx; + uint32_t dictSize; +}; + +typedef struct { + const uint8_t* externalDict; + size_t extDictSize; + const uint8_t* prefixEnd; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + +#else + +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + unsigned int hashTable[LZ4_HASH_SIZE_U32]; + unsigned int currentOffset; + unsigned short dirty; + unsigned short tableType; + const unsigned char* dictionary; + const LZ4_stream_t_internal* dictCtx; + unsigned int dictSize; +}; + +typedef struct { + const unsigned char* externalDict; + const unsigned char* prefixEnd; + size_t extDictSize; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + +#endif + +/*! LZ4_stream_t : + * information structure to track an LZ4 stream. + * LZ4_stream_t can also be created using LZ4_createStream(), which is recommended. + * The structure definition can be convenient for static allocation + * (on stack, or as part of larger structure). + * Init this structure with LZ4_initStream() before first use. + * note : only use this definition in association with static linking ! + * this definition is not API/ABI safe, and may change in a future version. + */ +#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4 + ((sizeof(void*)==16) ? 4 : 0) /*AS-400*/ ) +#define LZ4_STREAMSIZE (LZ4_STREAMSIZE_U64 * sizeof(unsigned long long)) +union LZ4_stream_u { + unsigned long long table[LZ4_STREAMSIZE_U64]; + LZ4_stream_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_stream_t */ + +/*! LZ4_initStream() : v1.9.0+ + * An LZ4_stream_t structure must be initialized at least once. + * This is automatically done when invoking LZ4_createStream(), + * but it's not when the structure is simply declared on stack (for example). + * + * Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t. + * It can also initialize any arbitrary buffer of sufficient size, + * and will @return a pointer of proper type upon initialization. + * + * Note : initialization fails if size and alignment conditions are not respected. + * In which case, the function will @return NULL. + * Note2: An LZ4_stream_t structure guarantees correct alignment and size. + * Note3: Before v1.9.0, use LZ4_resetStream() instead + */ +LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* buffer, size_t size); + + +/*! LZ4_streamDecode_t : + * information structure to track an LZ4 stream during decompression. + * init this structure using LZ4_setStreamDecode() before first use. + * note : only use in association with static linking ! + * this definition is not API/ABI safe, + * and may change in a future version ! + */ +#define LZ4_STREAMDECODESIZE_U64 (4 + ((sizeof(void*)==16) ? 2 : 0) /*AS-400*/ ) +#define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long)) +union LZ4_streamDecode_u { + unsigned long long table[LZ4_STREAMDECODESIZE_U64]; + LZ4_streamDecode_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_streamDecode_t */ + + +/*-************************************ +* Obsolete Functions +**************************************/ + +/*! Deprecation warnings + * + * Deprecated functions make the compiler generate a warning when invoked. + * This is meant to invite users to update their source code. + * Should deprecation warnings be a problem, it is generally possible to disable them, + * typically with -Wno-deprecated-declarations for gcc + * or _CRT_SECURE_NO_WARNINGS in Visual. + * + * Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS + * before including the header file. + */ +#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS +# define LZ4_DEPRECATED(message) /* disable deprecation warnings */ +#else +# define LZ4_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ +# define LZ4_DEPRECATED(message) [[deprecated(message)]] +# elif (LZ4_GCC_VERSION >= 405) || defined(__clang__) +# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) +# elif (LZ4_GCC_VERSION >= 301) +# define LZ4_DEPRECATED(message) __attribute__((deprecated)) +# elif defined(_MSC_VER) +# define LZ4_DEPRECATED(message) __declspec(deprecated(message)) +# else +# pragma message("WARNING: You need to implement LZ4_DEPRECATED for this compiler") +# define LZ4_DEPRECATED(message) +# endif +#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */ + +/* Obsolete compression functions */ +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* source, char* dest, int sourceSize); +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* source, char* dest, int sourceSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); + +/* Obsolete decompression functions */ +LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize); +LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); + +/* Obsolete streaming functions; degraded functionality; do not use! + * + * In order to perform streaming compression, these functions depended on data + * that is no longer tracked in the state. They have been preserved as well as + * possible: using them will still produce a correct output. However, they don't + * actually retain any history between compression calls. The compression ratio + * achieved will therefore be no better than compressing each chunk + * independently. + */ +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void); +LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state); + +/* Obsolete streaming decoding functions */ +LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); +LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); + +/*! LZ4_decompress_fast() : **unsafe!** + * These functions used to be faster than LZ4_decompress_safe(), + * but it has changed, and they are now slower than LZ4_decompress_safe(). + * This is because LZ4_decompress_fast() doesn't know the input size, + * and therefore must progress more cautiously in the input buffer to not read beyond the end of block. + * On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability. + * As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated. + * + * The last remaining LZ4_decompress_fast() specificity is that + * it can decompress a block without knowing its compressed size. + * Such functionality could be achieved in a more secure manner, + * by also providing the maximum size of input buffer, + * but it would require new prototypes, and adaptation of the implementation to this new use case. + * + * Parameters: + * originalSize : is the uncompressed size to regenerate. + * `dst` must be already allocated, its size must be >= 'originalSize' bytes. + * @return : number of bytes read from source buffer (== compressed size). + * The function expects to finish at block's end exactly. + * If the source stream is detected malformed, the function stops decoding and returns a negative result. + * note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer. + * However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds. + * Also, since match offsets are not validated, match reads from 'src' may underflow too. + * These issues never happen if input (compressed) data is correct. + * But they may happen if input data is invalid (error or intentional tampering). + * As a consequence, use these functions in trusted environments with trusted data **only**. + */ + +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe() instead") +LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_continue() instead") +LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_usingDict() instead") +LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize); + +/*! LZ4_resetStream() : + * An LZ4_stream_t structure must be initialized at least once. + * This is done with LZ4_initStream(), or LZ4_resetStream(). + * Consider switching to LZ4_initStream(), + * invoking LZ4_resetStream() will trigger deprecation warnings in the future. + */ +LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); + + +#endif /* LZ4_H_2983827168210 */ + + +#if defined (__cplusplus) +} +#endif diff --git a/TMessagesProj/jni/lz4/lz4frame.c b/TMessagesProj/jni/lz4/lz4frame.c new file mode 100755 index 000000000..a10e4af09 --- /dev/null +++ b/TMessagesProj/jni/lz4/lz4frame.c @@ -0,0 +1,1838 @@ +/* + * LZ4 auto-framing library + * Copyright (C) 2011-2016, Yann Collet. + * + * BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You can contact the author at : + * - LZ4 homepage : http://www.lz4.org + * - LZ4 source repository : https://github.com/lz4/lz4 + */ + +/* LZ4F is a stand-alone API to create LZ4-compressed Frames + * in full conformance with specification v1.6.1 . + * This library rely upon memory management capabilities (malloc, free) + * provided either by , + * or redirected towards another library of user's choice + * (see Memory Routines below). + */ + + +/*-************************************ +* Compiler Options +**************************************/ +#ifdef _MSC_VER /* Visual Studio */ +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +#endif + + +/*-************************************ +* Tuning parameters +**************************************/ +/* + * LZ4F_HEAPMODE : + * Select how default compression functions will allocate memory for their hash table, + * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()). + */ +#ifndef LZ4F_HEAPMODE +# define LZ4F_HEAPMODE 0 +#endif + + +/*-************************************ +* Memory routines +**************************************/ +/* + * User may redirect invocations of + * malloc(), calloc() and free() + * towards another library or solution of their choice + * by modifying below section. + */ +#include /* malloc, calloc, free */ +#ifndef LZ4_SRC_INCLUDED /* avoid redefinition when sources are coalesced */ +# define ALLOC(s) malloc(s) +# define ALLOC_AND_ZERO(s) calloc(1,(s)) +# define FREEMEM(p) free(p) +#endif + +#include /* memset, memcpy, memmove */ +#ifndef LZ4_SRC_INCLUDED /* avoid redefinition when sources are coalesced */ +# define MEM_INIT(p,v,s) memset((p),(v),(s)) +#endif + + +/*-************************************ +* Library declarations +**************************************/ +#define LZ4F_STATIC_LINKING_ONLY +#include "lz4frame.h" +#define LZ4_STATIC_LINKING_ONLY +#include "lz4.h" +#define LZ4_HC_STATIC_LINKING_ONLY +#include "lz4hc.h" +#define XXH_STATIC_LINKING_ONLY +#include "xxhash.h" + + +/*-************************************ +* Debug +**************************************/ +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=1) +# include +#else +# ifndef assert +# define assert(condition) ((void)0) +# endif +#endif + +#define LZ4F_STATIC_ASSERT(c) { enum { LZ4F_static_assert = 1/(int)(!!(c)) }; } /* use only *after* variable declarations */ + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2) && !defined(DEBUGLOG) +# include +static int g_debuglog_enable = 1; +# define DEBUGLOG(l, ...) { \ + if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \ + fprintf(stderr, __FILE__ ": "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " \n"); \ + } } +#else +# define DEBUGLOG(l, ...) {} /* disabled */ +#endif + + +/*-************************************ +* Basic Types +**************************************/ +#if !defined (__VMS) && (defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; + typedef int32_t S32; + typedef uint64_t U64; +#else + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; + typedef signed int S32; + typedef unsigned long long U64; +#endif + + +/* unoptimized version; solves endianess & alignment issues */ +static U32 LZ4F_readLE32 (const void* src) +{ + const BYTE* const srcPtr = (const BYTE*)src; + U32 value32 = srcPtr[0]; + value32 += ((U32)srcPtr[1])<< 8; + value32 += ((U32)srcPtr[2])<<16; + value32 += ((U32)srcPtr[3])<<24; + return value32; +} + +static void LZ4F_writeLE32 (void* dst, U32 value32) +{ + BYTE* const dstPtr = (BYTE*)dst; + dstPtr[0] = (BYTE)value32; + dstPtr[1] = (BYTE)(value32 >> 8); + dstPtr[2] = (BYTE)(value32 >> 16); + dstPtr[3] = (BYTE)(value32 >> 24); +} + +static U64 LZ4F_readLE64 (const void* src) +{ + const BYTE* const srcPtr = (const BYTE*)src; + U64 value64 = srcPtr[0]; + value64 += ((U64)srcPtr[1]<<8); + value64 += ((U64)srcPtr[2]<<16); + value64 += ((U64)srcPtr[3]<<24); + value64 += ((U64)srcPtr[4]<<32); + value64 += ((U64)srcPtr[5]<<40); + value64 += ((U64)srcPtr[6]<<48); + value64 += ((U64)srcPtr[7]<<56); + return value64; +} + +static void LZ4F_writeLE64 (void* dst, U64 value64) +{ + BYTE* const dstPtr = (BYTE*)dst; + dstPtr[0] = (BYTE)value64; + dstPtr[1] = (BYTE)(value64 >> 8); + dstPtr[2] = (BYTE)(value64 >> 16); + dstPtr[3] = (BYTE)(value64 >> 24); + dstPtr[4] = (BYTE)(value64 >> 32); + dstPtr[5] = (BYTE)(value64 >> 40); + dstPtr[6] = (BYTE)(value64 >> 48); + dstPtr[7] = (BYTE)(value64 >> 56); +} + + +/*-************************************ +* Constants +**************************************/ +#ifndef LZ4_SRC_INCLUDED /* avoid double definition */ +# define KB *(1<<10) +# define MB *(1<<20) +# define GB *(1<<30) +#endif + +#define _1BIT 0x01 +#define _2BITS 0x03 +#define _3BITS 0x07 +#define _4BITS 0x0F +#define _8BITS 0xFF + +#define LZ4F_MAGIC_SKIPPABLE_START 0x184D2A50U +#define LZ4F_MAGICNUMBER 0x184D2204U +#define LZ4F_BLOCKUNCOMPRESSED_FLAG 0x80000000U +#define LZ4F_BLOCKSIZEID_DEFAULT LZ4F_max64KB + +static const size_t minFHSize = LZ4F_HEADER_SIZE_MIN; /* 7 */ +static const size_t maxFHSize = LZ4F_HEADER_SIZE_MAX; /* 19 */ +static const size_t BHSize = 4; /* block header : size, and compress flag */ +static const size_t BFSize = 4; /* block footer : checksum (optional) */ + + +/*-************************************ +* Structures and local types +**************************************/ +typedef struct LZ4F_cctx_s +{ + LZ4F_preferences_t prefs; + U32 version; + U32 cStage; + const LZ4F_CDict* cdict; + size_t maxBlockSize; + size_t maxBufferSize; + BYTE* tmpBuff; + BYTE* tmpIn; + size_t tmpInSize; + U64 totalInSize; + XXH32_state_t xxh; + void* lz4CtxPtr; + U16 lz4CtxAlloc; /* sized for: 0 = none, 1 = lz4 ctx, 2 = lz4hc ctx */ + U16 lz4CtxState; /* in use as: 0 = none, 1 = lz4 ctx, 2 = lz4hc ctx */ +} LZ4F_cctx_t; + + +/*-************************************ +* Error management +**************************************/ +#define LZ4F_GENERATE_STRING(STRING) #STRING, +static const char* LZ4F_errorStrings[] = { LZ4F_LIST_ERRORS(LZ4F_GENERATE_STRING) }; + + +unsigned LZ4F_isError(LZ4F_errorCode_t code) +{ + return (code > (LZ4F_errorCode_t)(-LZ4F_ERROR_maxCode)); +} + +const char* LZ4F_getErrorName(LZ4F_errorCode_t code) +{ + static const char* codeError = "Unspecified error code"; + if (LZ4F_isError(code)) return LZ4F_errorStrings[-(int)(code)]; + return codeError; +} + +LZ4F_errorCodes LZ4F_getErrorCode(size_t functionResult) +{ + if (!LZ4F_isError(functionResult)) return LZ4F_OK_NoError; + return (LZ4F_errorCodes)(-(ptrdiff_t)functionResult); +} + +static LZ4F_errorCode_t err0r(LZ4F_errorCodes code) +{ + /* A compilation error here means sizeof(ptrdiff_t) is not large enough */ + LZ4F_STATIC_ASSERT(sizeof(ptrdiff_t) >= sizeof(size_t)); + return (LZ4F_errorCode_t)-(ptrdiff_t)code; +} + +unsigned LZ4F_getVersion(void) { return LZ4F_VERSION; } + +int LZ4F_compressionLevel_max(void) { return LZ4HC_CLEVEL_MAX; } + +size_t LZ4F_getBlockSize(unsigned blockSizeID) +{ + static const size_t blockSizes[4] = { 64 KB, 256 KB, 1 MB, 4 MB }; + + if (blockSizeID == 0) blockSizeID = LZ4F_BLOCKSIZEID_DEFAULT; + if (blockSizeID < LZ4F_max64KB || blockSizeID > LZ4F_max4MB) + return err0r(LZ4F_ERROR_maxBlockSize_invalid); + blockSizeID -= LZ4F_max64KB; + return blockSizes[blockSizeID]; +} + +/*-************************************ +* Private functions +**************************************/ +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) + +static BYTE LZ4F_headerChecksum (const void* header, size_t length) +{ + U32 const xxh = XXH32(header, length, 0); + return (BYTE)(xxh >> 8); +} + + +/*-************************************ +* Simple-pass compression functions +**************************************/ +static LZ4F_blockSizeID_t LZ4F_optimalBSID(const LZ4F_blockSizeID_t requestedBSID, + const size_t srcSize) +{ + LZ4F_blockSizeID_t proposedBSID = LZ4F_max64KB; + size_t maxBlockSize = 64 KB; + while (requestedBSID > proposedBSID) { + if (srcSize <= maxBlockSize) + return proposedBSID; + proposedBSID = (LZ4F_blockSizeID_t)((int)proposedBSID + 1); + maxBlockSize <<= 2; + } + return requestedBSID; +} + +/*! LZ4F_compressBound_internal() : + * Provides dstCapacity given a srcSize to guarantee operation success in worst case situations. + * prefsPtr is optional : if NULL is provided, preferences will be set to cover worst case scenario. + * @return is always the same for a srcSize and prefsPtr, so it can be relied upon to size reusable buffers. + * When srcSize==0, LZ4F_compressBound() provides an upper bound for LZ4F_flush() and LZ4F_compressEnd() operations. + */ +static size_t LZ4F_compressBound_internal(size_t srcSize, + const LZ4F_preferences_t* preferencesPtr, + size_t alreadyBuffered) +{ + LZ4F_preferences_t prefsNull = LZ4F_INIT_PREFERENCES; + prefsNull.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled; /* worst case */ + { const LZ4F_preferences_t* const prefsPtr = (preferencesPtr==NULL) ? &prefsNull : preferencesPtr; + U32 const flush = prefsPtr->autoFlush | (srcSize==0); + LZ4F_blockSizeID_t const blockID = prefsPtr->frameInfo.blockSizeID; + size_t const blockSize = LZ4F_getBlockSize(blockID); + size_t const maxBuffered = blockSize - 1; + size_t const bufferedSize = MIN(alreadyBuffered, maxBuffered); + size_t const maxSrcSize = srcSize + bufferedSize; + unsigned const nbFullBlocks = (unsigned)(maxSrcSize / blockSize); + size_t const partialBlockSize = maxSrcSize & (blockSize-1); + size_t const lastBlockSize = flush ? partialBlockSize : 0; + unsigned const nbBlocks = nbFullBlocks + (lastBlockSize>0); + + size_t const blockCRCSize = BFSize * prefsPtr->frameInfo.blockChecksumFlag; + size_t const frameEnd = BHSize + (prefsPtr->frameInfo.contentChecksumFlag*BFSize); + + return ((BHSize + blockCRCSize) * nbBlocks) + + (blockSize * nbFullBlocks) + lastBlockSize + frameEnd; + } +} + +size_t LZ4F_compressFrameBound(size_t srcSize, const LZ4F_preferences_t* preferencesPtr) +{ + LZ4F_preferences_t prefs; + size_t const headerSize = maxFHSize; /* max header size, including optional fields */ + + if (preferencesPtr!=NULL) prefs = *preferencesPtr; + else MEM_INIT(&prefs, 0, sizeof(prefs)); + prefs.autoFlush = 1; + + return headerSize + LZ4F_compressBound_internal(srcSize, &prefs, 0);; +} + + +/*! LZ4F_compressFrame_usingCDict() : + * Compress srcBuffer using a dictionary, in a single step. + * cdict can be NULL, in which case, no dictionary is used. + * dstBuffer MUST be >= LZ4F_compressFrameBound(srcSize, preferencesPtr). + * The LZ4F_preferences_t structure is optional : you may provide NULL as argument, + * however, it's the only way to provide a dictID, so it's not recommended. + * @return : number of bytes written into dstBuffer, + * or an error code if it fails (can be tested using LZ4F_isError()) + */ +size_t LZ4F_compressFrame_usingCDict(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_CDict* cdict, + const LZ4F_preferences_t* preferencesPtr) +{ + LZ4F_preferences_t prefs; + LZ4F_compressOptions_t options; + BYTE* const dstStart = (BYTE*) dstBuffer; + BYTE* dstPtr = dstStart; + BYTE* const dstEnd = dstStart + dstCapacity; + + if (preferencesPtr!=NULL) + prefs = *preferencesPtr; + else + MEM_INIT(&prefs, 0, sizeof(prefs)); + if (prefs.frameInfo.contentSize != 0) + prefs.frameInfo.contentSize = (U64)srcSize; /* auto-correct content size if selected (!=0) */ + + prefs.frameInfo.blockSizeID = LZ4F_optimalBSID(prefs.frameInfo.blockSizeID, srcSize); + prefs.autoFlush = 1; + if (srcSize <= LZ4F_getBlockSize(prefs.frameInfo.blockSizeID)) + prefs.frameInfo.blockMode = LZ4F_blockIndependent; /* only one block => no need for inter-block link */ + + MEM_INIT(&options, 0, sizeof(options)); + options.stableSrc = 1; + + if (dstCapacity < LZ4F_compressFrameBound(srcSize, &prefs)) /* condition to guarantee success */ + return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); + + { size_t const headerSize = LZ4F_compressBegin_usingCDict(cctx, dstBuffer, dstCapacity, cdict, &prefs); /* write header */ + if (LZ4F_isError(headerSize)) return headerSize; + dstPtr += headerSize; /* header size */ } + + assert(dstEnd >= dstPtr); + { size_t const cSize = LZ4F_compressUpdate(cctx, dstPtr, (size_t)(dstEnd-dstPtr), srcBuffer, srcSize, &options); + if (LZ4F_isError(cSize)) return cSize; + dstPtr += cSize; } + + assert(dstEnd >= dstPtr); + { size_t const tailSize = LZ4F_compressEnd(cctx, dstPtr, (size_t)(dstEnd-dstPtr), &options); /* flush last block, and generate suffix */ + if (LZ4F_isError(tailSize)) return tailSize; + dstPtr += tailSize; } + + assert(dstEnd >= dstStart); + return (size_t)(dstPtr - dstStart); +} + + +/*! LZ4F_compressFrame() : + * Compress an entire srcBuffer into a valid LZ4 frame, in a single step. + * dstBuffer MUST be >= LZ4F_compressFrameBound(srcSize, preferencesPtr). + * The LZ4F_preferences_t structure is optional : you can provide NULL as argument. All preferences will be set to default. + * @return : number of bytes written into dstBuffer. + * or an error code if it fails (can be tested using LZ4F_isError()) + */ +size_t LZ4F_compressFrame(void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_preferences_t* preferencesPtr) +{ + size_t result; +#if (LZ4F_HEAPMODE) + LZ4F_cctx_t *cctxPtr; + result = LZ4F_createCompressionContext(&cctxPtr, LZ4F_VERSION); + if (LZ4F_isError(result)) return result; +#else + LZ4F_cctx_t cctx; + LZ4_stream_t lz4ctx; + LZ4F_cctx_t *cctxPtr = &cctx; + + DEBUGLOG(4, "LZ4F_compressFrame"); + MEM_INIT(&cctx, 0, sizeof(cctx)); + cctx.version = LZ4F_VERSION; + cctx.maxBufferSize = 5 MB; /* mess with real buffer size to prevent dynamic allocation; works only because autoflush==1 & stableSrc==1 */ + if (preferencesPtr == NULL || + preferencesPtr->compressionLevel < LZ4HC_CLEVEL_MIN) + { + LZ4_initStream(&lz4ctx, sizeof(lz4ctx)); + cctxPtr->lz4CtxPtr = &lz4ctx; + cctxPtr->lz4CtxAlloc = 1; + cctxPtr->lz4CtxState = 1; + } +#endif + + result = LZ4F_compressFrame_usingCDict(cctxPtr, dstBuffer, dstCapacity, + srcBuffer, srcSize, + NULL, preferencesPtr); + +#if (LZ4F_HEAPMODE) + LZ4F_freeCompressionContext(cctxPtr); +#else + if (preferencesPtr != NULL && + preferencesPtr->compressionLevel >= LZ4HC_CLEVEL_MIN) + { + FREEMEM(cctxPtr->lz4CtxPtr); + } +#endif + return result; +} + + +/*-*************************************************** +* Dictionary compression +*****************************************************/ + +struct LZ4F_CDict_s { + void* dictContent; + LZ4_stream_t* fastCtx; + LZ4_streamHC_t* HCCtx; +}; /* typedef'd to LZ4F_CDict within lz4frame_static.h */ + +/*! LZ4F_createCDict() : + * When compressing multiple messages / blocks with the same dictionary, it's recommended to load it just once. + * LZ4F_createCDict() will create a digested dictionary, ready to start future compression operations without startup delay. + * LZ4F_CDict can be created once and shared by multiple threads concurrently, since its usage is read-only. + * `dictBuffer` can be released after LZ4F_CDict creation, since its content is copied within CDict + * @return : digested dictionary for compression, or NULL if failed */ +LZ4F_CDict* LZ4F_createCDict(const void* dictBuffer, size_t dictSize) +{ + const char* dictStart = (const char*)dictBuffer; + LZ4F_CDict* cdict = (LZ4F_CDict*) ALLOC(sizeof(*cdict)); + DEBUGLOG(4, "LZ4F_createCDict"); + if (!cdict) return NULL; + if (dictSize > 64 KB) { + dictStart += dictSize - 64 KB; + dictSize = 64 KB; + } + cdict->dictContent = ALLOC(dictSize); + cdict->fastCtx = LZ4_createStream(); + cdict->HCCtx = LZ4_createStreamHC(); + if (!cdict->dictContent || !cdict->fastCtx || !cdict->HCCtx) { + LZ4F_freeCDict(cdict); + return NULL; + } + memcpy(cdict->dictContent, dictStart, dictSize); + LZ4_loadDict (cdict->fastCtx, (const char*)cdict->dictContent, (int)dictSize); + LZ4_setCompressionLevel(cdict->HCCtx, LZ4HC_CLEVEL_DEFAULT); + LZ4_loadDictHC(cdict->HCCtx, (const char*)cdict->dictContent, (int)dictSize); + return cdict; +} + +void LZ4F_freeCDict(LZ4F_CDict* cdict) +{ + if (cdict==NULL) return; /* support free on NULL */ + FREEMEM(cdict->dictContent); + LZ4_freeStream(cdict->fastCtx); + LZ4_freeStreamHC(cdict->HCCtx); + FREEMEM(cdict); +} + + +/*-********************************* +* Advanced compression functions +***********************************/ + +/*! LZ4F_createCompressionContext() : + * The first thing to do is to create a compressionContext object, which will be used in all compression operations. + * This is achieved using LZ4F_createCompressionContext(), which takes as argument a version and an LZ4F_preferences_t structure. + * The version provided MUST be LZ4F_VERSION. It is intended to track potential incompatible differences between different binaries. + * The function will provide a pointer to an allocated LZ4F_compressionContext_t object. + * If the result LZ4F_errorCode_t is not OK_NoError, there was an error during context creation. + * Object can release its memory using LZ4F_freeCompressionContext(); + */ +LZ4F_errorCode_t LZ4F_createCompressionContext(LZ4F_compressionContext_t* LZ4F_compressionContextPtr, unsigned version) +{ + LZ4F_cctx_t* const cctxPtr = (LZ4F_cctx_t*)ALLOC_AND_ZERO(sizeof(LZ4F_cctx_t)); + if (cctxPtr==NULL) return err0r(LZ4F_ERROR_allocation_failed); + + cctxPtr->version = version; + cctxPtr->cStage = 0; /* Next stage : init stream */ + + *LZ4F_compressionContextPtr = (LZ4F_compressionContext_t)cctxPtr; + + return LZ4F_OK_NoError; +} + + +LZ4F_errorCode_t LZ4F_freeCompressionContext(LZ4F_compressionContext_t LZ4F_compressionContext) +{ + LZ4F_cctx_t* const cctxPtr = (LZ4F_cctx_t*)LZ4F_compressionContext; + + if (cctxPtr != NULL) { /* support free on NULL */ + FREEMEM(cctxPtr->lz4CtxPtr); /* works because LZ4_streamHC_t and LZ4_stream_t are simple POD types */ + FREEMEM(cctxPtr->tmpBuff); + FREEMEM(LZ4F_compressionContext); + } + + return LZ4F_OK_NoError; +} + + +/** + * This function prepares the internal LZ4(HC) stream for a new compression, + * resetting the context and attaching the dictionary, if there is one. + * + * It needs to be called at the beginning of each independent compression + * stream (i.e., at the beginning of a frame in blockLinked mode, or at the + * beginning of each block in blockIndependent mode). + */ +static void LZ4F_initStream(void* ctx, + const LZ4F_CDict* cdict, + int level, + LZ4F_blockMode_t blockMode) { + if (level < LZ4HC_CLEVEL_MIN) { + if (cdict != NULL || blockMode == LZ4F_blockLinked) { + /* In these cases, we will call LZ4_compress_fast_continue(), + * which needs an already reset context. Otherwise, we'll call a + * one-shot API. The non-continued APIs internally perform their own + * resets at the beginning of their calls, where they know what + * tableType they need the context to be in. So in that case this + * would be misguided / wasted work. */ + LZ4_resetStream_fast((LZ4_stream_t*)ctx); + } + LZ4_attach_dictionary((LZ4_stream_t *)ctx, cdict ? cdict->fastCtx : NULL); + } else { + LZ4_resetStreamHC_fast((LZ4_streamHC_t*)ctx, level); + LZ4_attach_HC_dictionary((LZ4_streamHC_t *)ctx, cdict ? cdict->HCCtx : NULL); + } +} + + +/*! LZ4F_compressBegin_usingCDict() : + * init streaming compression and writes frame header into dstBuffer. + * dstBuffer must be >= LZ4F_HEADER_SIZE_MAX bytes. + * @return : number of bytes written into dstBuffer for the header + * or an error code (can be tested using LZ4F_isError()) + */ +size_t LZ4F_compressBegin_usingCDict(LZ4F_cctx* cctxPtr, + void* dstBuffer, size_t dstCapacity, + const LZ4F_CDict* cdict, + const LZ4F_preferences_t* preferencesPtr) +{ + LZ4F_preferences_t prefNull; + BYTE* const dstStart = (BYTE*)dstBuffer; + BYTE* dstPtr = dstStart; + BYTE* headerStart; + + if (dstCapacity < maxFHSize) return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); + MEM_INIT(&prefNull, 0, sizeof(prefNull)); + if (preferencesPtr == NULL) preferencesPtr = &prefNull; + cctxPtr->prefs = *preferencesPtr; + + /* Ctx Management */ + { U16 const ctxTypeID = (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) ? 1 : 2; + if (cctxPtr->lz4CtxAlloc < ctxTypeID) { + FREEMEM(cctxPtr->lz4CtxPtr); + if (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) { + cctxPtr->lz4CtxPtr = LZ4_createStream(); + } else { + cctxPtr->lz4CtxPtr = LZ4_createStreamHC(); + } + if (cctxPtr->lz4CtxPtr == NULL) + return err0r(LZ4F_ERROR_allocation_failed); + cctxPtr->lz4CtxAlloc = ctxTypeID; + cctxPtr->lz4CtxState = ctxTypeID; + } else if (cctxPtr->lz4CtxState != ctxTypeID) { + /* otherwise, a sufficient buffer is allocated, but we need to + * reset it to the correct context type */ + if (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) { + LZ4_initStream((LZ4_stream_t *) cctxPtr->lz4CtxPtr, sizeof (LZ4_stream_t)); + } else { + LZ4_initStreamHC((LZ4_streamHC_t *) cctxPtr->lz4CtxPtr, sizeof(LZ4_streamHC_t)); + LZ4_setCompressionLevel((LZ4_streamHC_t *) cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel); + } + cctxPtr->lz4CtxState = ctxTypeID; + } + } + + /* Buffer Management */ + if (cctxPtr->prefs.frameInfo.blockSizeID == 0) + cctxPtr->prefs.frameInfo.blockSizeID = LZ4F_BLOCKSIZEID_DEFAULT; + cctxPtr->maxBlockSize = LZ4F_getBlockSize(cctxPtr->prefs.frameInfo.blockSizeID); + + { size_t const requiredBuffSize = preferencesPtr->autoFlush ? + ((cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) ? 64 KB : 0) : /* only needs past data up to window size */ + cctxPtr->maxBlockSize + ((cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) ? 128 KB : 0); + + if (cctxPtr->maxBufferSize < requiredBuffSize) { + cctxPtr->maxBufferSize = 0; + FREEMEM(cctxPtr->tmpBuff); + cctxPtr->tmpBuff = (BYTE*)ALLOC_AND_ZERO(requiredBuffSize); + if (cctxPtr->tmpBuff == NULL) return err0r(LZ4F_ERROR_allocation_failed); + cctxPtr->maxBufferSize = requiredBuffSize; + } } + cctxPtr->tmpIn = cctxPtr->tmpBuff; + cctxPtr->tmpInSize = 0; + (void)XXH32_reset(&(cctxPtr->xxh), 0); + + /* context init */ + cctxPtr->cdict = cdict; + if (cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) { + /* frame init only for blockLinked : blockIndependent will be init at each block */ + LZ4F_initStream(cctxPtr->lz4CtxPtr, cdict, cctxPtr->prefs.compressionLevel, LZ4F_blockLinked); + } + if (preferencesPtr->compressionLevel >= LZ4HC_CLEVEL_MIN) { + LZ4_favorDecompressionSpeed((LZ4_streamHC_t*)cctxPtr->lz4CtxPtr, (int)preferencesPtr->favorDecSpeed); + } + + /* Magic Number */ + LZ4F_writeLE32(dstPtr, LZ4F_MAGICNUMBER); + dstPtr += 4; + headerStart = dstPtr; + + /* FLG Byte */ + *dstPtr++ = (BYTE)(((1 & _2BITS) << 6) /* Version('01') */ + + ((cctxPtr->prefs.frameInfo.blockMode & _1BIT ) << 5) + + ((cctxPtr->prefs.frameInfo.blockChecksumFlag & _1BIT ) << 4) + + ((unsigned)(cctxPtr->prefs.frameInfo.contentSize > 0) << 3) + + ((cctxPtr->prefs.frameInfo.contentChecksumFlag & _1BIT ) << 2) + + (cctxPtr->prefs.frameInfo.dictID > 0) ); + /* BD Byte */ + *dstPtr++ = (BYTE)((cctxPtr->prefs.frameInfo.blockSizeID & _3BITS) << 4); + /* Optional Frame content size field */ + if (cctxPtr->prefs.frameInfo.contentSize) { + LZ4F_writeLE64(dstPtr, cctxPtr->prefs.frameInfo.contentSize); + dstPtr += 8; + cctxPtr->totalInSize = 0; + } + /* Optional dictionary ID field */ + if (cctxPtr->prefs.frameInfo.dictID) { + LZ4F_writeLE32(dstPtr, cctxPtr->prefs.frameInfo.dictID); + dstPtr += 4; + } + /* Header CRC Byte */ + *dstPtr = LZ4F_headerChecksum(headerStart, (size_t)(dstPtr - headerStart)); + dstPtr++; + + cctxPtr->cStage = 1; /* header written, now request input data block */ + return (size_t)(dstPtr - dstStart); +} + + +/*! LZ4F_compressBegin() : + * init streaming compression and writes frame header into dstBuffer. + * dstBuffer must be >= LZ4F_HEADER_SIZE_MAX bytes. + * preferencesPtr can be NULL, in which case default parameters are selected. + * @return : number of bytes written into dstBuffer for the header + * or an error code (can be tested using LZ4F_isError()) + */ +size_t LZ4F_compressBegin(LZ4F_cctx* cctxPtr, + void* dstBuffer, size_t dstCapacity, + const LZ4F_preferences_t* preferencesPtr) +{ + return LZ4F_compressBegin_usingCDict(cctxPtr, dstBuffer, dstCapacity, + NULL, preferencesPtr); +} + + +/* LZ4F_compressBound() : + * @return minimum capacity of dstBuffer for a given srcSize to handle worst case scenario. + * LZ4F_preferences_t structure is optional : if NULL, preferences will be set to cover worst case scenario. + * This function cannot fail. + */ +size_t LZ4F_compressBound(size_t srcSize, const LZ4F_preferences_t* preferencesPtr) +{ + return LZ4F_compressBound_internal(srcSize, preferencesPtr, (size_t)-1); +} + + +typedef int (*compressFunc_t)(void* ctx, const char* src, char* dst, int srcSize, int dstSize, int level, const LZ4F_CDict* cdict); + + +/*! LZ4F_makeBlock(): + * compress a single block, add header and optional checksum. + * assumption : dst buffer capacity is >= BHSize + srcSize + crcSize + */ +static size_t LZ4F_makeBlock(void* dst, + const void* src, size_t srcSize, + compressFunc_t compress, void* lz4ctx, int level, + const LZ4F_CDict* cdict, + LZ4F_blockChecksum_t crcFlag) +{ + BYTE* const cSizePtr = (BYTE*)dst; + U32 cSize = (U32)compress(lz4ctx, (const char*)src, (char*)(cSizePtr+BHSize), + (int)(srcSize), (int)(srcSize-1), + level, cdict); + if (cSize == 0) { /* compression failed */ + cSize = (U32)srcSize; + LZ4F_writeLE32(cSizePtr, cSize | LZ4F_BLOCKUNCOMPRESSED_FLAG); + memcpy(cSizePtr+BHSize, src, srcSize); + } else { + LZ4F_writeLE32(cSizePtr, cSize); + } + if (crcFlag) { + U32 const crc32 = XXH32(cSizePtr+BHSize, cSize, 0); /* checksum of compressed data */ + LZ4F_writeLE32(cSizePtr+BHSize+cSize, crc32); + } + return BHSize + cSize + ((U32)crcFlag)*BFSize; +} + + +static int LZ4F_compressBlock(void* ctx, const char* src, char* dst, int srcSize, int dstCapacity, int level, const LZ4F_CDict* cdict) +{ + int const acceleration = (level < 0) ? -level + 1 : 1; + LZ4F_initStream(ctx, cdict, level, LZ4F_blockIndependent); + if (cdict) { + return LZ4_compress_fast_continue((LZ4_stream_t*)ctx, src, dst, srcSize, dstCapacity, acceleration); + } else { + return LZ4_compress_fast_extState_fastReset(ctx, src, dst, srcSize, dstCapacity, acceleration); + } +} + +static int LZ4F_compressBlock_continue(void* ctx, const char* src, char* dst, int srcSize, int dstCapacity, int level, const LZ4F_CDict* cdict) +{ + int const acceleration = (level < 0) ? -level + 1 : 1; + (void)cdict; /* init once at beginning of frame */ + return LZ4_compress_fast_continue((LZ4_stream_t*)ctx, src, dst, srcSize, dstCapacity, acceleration); +} + +static int LZ4F_compressBlockHC(void* ctx, const char* src, char* dst, int srcSize, int dstCapacity, int level, const LZ4F_CDict* cdict) +{ + LZ4F_initStream(ctx, cdict, level, LZ4F_blockIndependent); + if (cdict) { + return LZ4_compress_HC_continue((LZ4_streamHC_t*)ctx, src, dst, srcSize, dstCapacity); + } + return LZ4_compress_HC_extStateHC_fastReset(ctx, src, dst, srcSize, dstCapacity, level); +} + +static int LZ4F_compressBlockHC_continue(void* ctx, const char* src, char* dst, int srcSize, int dstCapacity, int level, const LZ4F_CDict* cdict) +{ + (void)level; (void)cdict; /* init once at beginning of frame */ + return LZ4_compress_HC_continue((LZ4_streamHC_t*)ctx, src, dst, srcSize, dstCapacity); +} + +static compressFunc_t LZ4F_selectCompression(LZ4F_blockMode_t blockMode, int level) +{ + if (level < LZ4HC_CLEVEL_MIN) { + if (blockMode == LZ4F_blockIndependent) return LZ4F_compressBlock; + return LZ4F_compressBlock_continue; + } + if (blockMode == LZ4F_blockIndependent) return LZ4F_compressBlockHC; + return LZ4F_compressBlockHC_continue; +} + +static int LZ4F_localSaveDict(LZ4F_cctx_t* cctxPtr) +{ + if (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) + return LZ4_saveDict ((LZ4_stream_t*)(cctxPtr->lz4CtxPtr), (char*)(cctxPtr->tmpBuff), 64 KB); + return LZ4_saveDictHC ((LZ4_streamHC_t*)(cctxPtr->lz4CtxPtr), (char*)(cctxPtr->tmpBuff), 64 KB); +} + +typedef enum { notDone, fromTmpBuffer, fromSrcBuffer } LZ4F_lastBlockStatus; + +/*! LZ4F_compressUpdate() : + * LZ4F_compressUpdate() can be called repetitively to compress as much data as necessary. + * dstBuffer MUST be >= LZ4F_compressBound(srcSize, preferencesPtr). + * LZ4F_compressOptions_t structure is optional : you can provide NULL as argument. + * @return : the number of bytes written into dstBuffer. It can be zero, meaning input data was just buffered. + * or an error code if it fails (which can be tested using LZ4F_isError()) + */ +size_t LZ4F_compressUpdate(LZ4F_cctx* cctxPtr, + void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_compressOptions_t* compressOptionsPtr) +{ + LZ4F_compressOptions_t cOptionsNull; + size_t const blockSize = cctxPtr->maxBlockSize; + const BYTE* srcPtr = (const BYTE*)srcBuffer; + const BYTE* const srcEnd = srcPtr + srcSize; + BYTE* const dstStart = (BYTE*)dstBuffer; + BYTE* dstPtr = dstStart; + LZ4F_lastBlockStatus lastBlockCompressed = notDone; + compressFunc_t const compress = LZ4F_selectCompression(cctxPtr->prefs.frameInfo.blockMode, cctxPtr->prefs.compressionLevel); + + DEBUGLOG(4, "LZ4F_compressUpdate (srcSize=%zu)", srcSize); + + if (cctxPtr->cStage != 1) return err0r(LZ4F_ERROR_GENERIC); + if (dstCapacity < LZ4F_compressBound_internal(srcSize, &(cctxPtr->prefs), cctxPtr->tmpInSize)) + return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); + MEM_INIT(&cOptionsNull, 0, sizeof(cOptionsNull)); + if (compressOptionsPtr == NULL) compressOptionsPtr = &cOptionsNull; + + /* complete tmp buffer */ + if (cctxPtr->tmpInSize > 0) { /* some data already within tmp buffer */ + size_t const sizeToCopy = blockSize - cctxPtr->tmpInSize; + if (sizeToCopy > srcSize) { + /* add src to tmpIn buffer */ + memcpy(cctxPtr->tmpIn + cctxPtr->tmpInSize, srcBuffer, srcSize); + srcPtr = srcEnd; + cctxPtr->tmpInSize += srcSize; + /* still needs some CRC */ + } else { + /* complete tmpIn block and then compress it */ + lastBlockCompressed = fromTmpBuffer; + memcpy(cctxPtr->tmpIn + cctxPtr->tmpInSize, srcBuffer, sizeToCopy); + srcPtr += sizeToCopy; + + dstPtr += LZ4F_makeBlock(dstPtr, + cctxPtr->tmpIn, blockSize, + compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel, + cctxPtr->cdict, + cctxPtr->prefs.frameInfo.blockChecksumFlag); + + if (cctxPtr->prefs.frameInfo.blockMode==LZ4F_blockLinked) cctxPtr->tmpIn += blockSize; + cctxPtr->tmpInSize = 0; + } + } + + while ((size_t)(srcEnd - srcPtr) >= blockSize) { + /* compress full blocks */ + lastBlockCompressed = fromSrcBuffer; + dstPtr += LZ4F_makeBlock(dstPtr, + srcPtr, blockSize, + compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel, + cctxPtr->cdict, + cctxPtr->prefs.frameInfo.blockChecksumFlag); + srcPtr += blockSize; + } + + if ((cctxPtr->prefs.autoFlush) && (srcPtr < srcEnd)) { + /* compress remaining input < blockSize */ + lastBlockCompressed = fromSrcBuffer; + dstPtr += LZ4F_makeBlock(dstPtr, + srcPtr, (size_t)(srcEnd - srcPtr), + compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel, + cctxPtr->cdict, + cctxPtr->prefs.frameInfo.blockChecksumFlag); + srcPtr = srcEnd; + } + + /* preserve dictionary if necessary */ + if ((cctxPtr->prefs.frameInfo.blockMode==LZ4F_blockLinked) && (lastBlockCompressed==fromSrcBuffer)) { + if (compressOptionsPtr->stableSrc) { + cctxPtr->tmpIn = cctxPtr->tmpBuff; + } else { + int const realDictSize = LZ4F_localSaveDict(cctxPtr); + if (realDictSize==0) return err0r(LZ4F_ERROR_GENERIC); + cctxPtr->tmpIn = cctxPtr->tmpBuff + realDictSize; + } + } + + /* keep tmpIn within limits */ + if ((cctxPtr->tmpIn + blockSize) > (cctxPtr->tmpBuff + cctxPtr->maxBufferSize) /* necessarily LZ4F_blockLinked && lastBlockCompressed==fromTmpBuffer */ + && !(cctxPtr->prefs.autoFlush)) + { + int const realDictSize = LZ4F_localSaveDict(cctxPtr); + cctxPtr->tmpIn = cctxPtr->tmpBuff + realDictSize; + } + + /* some input data left, necessarily < blockSize */ + if (srcPtr < srcEnd) { + /* fill tmp buffer */ + size_t const sizeToCopy = (size_t)(srcEnd - srcPtr); + memcpy(cctxPtr->tmpIn, srcPtr, sizeToCopy); + cctxPtr->tmpInSize = sizeToCopy; + } + + if (cctxPtr->prefs.frameInfo.contentChecksumFlag == LZ4F_contentChecksumEnabled) + (void)XXH32_update(&(cctxPtr->xxh), srcBuffer, srcSize); + + cctxPtr->totalInSize += srcSize; + return (size_t)(dstPtr - dstStart); +} + + +/*! LZ4F_flush() : + * When compressed data must be sent immediately, without waiting for a block to be filled, + * invoke LZ4_flush(), which will immediately compress any remaining data stored within LZ4F_cctx. + * The result of the function is the number of bytes written into dstBuffer. + * It can be zero, this means there was no data left within LZ4F_cctx. + * The function outputs an error code if it fails (can be tested using LZ4F_isError()) + * LZ4F_compressOptions_t* is optional. NULL is a valid argument. + */ +size_t LZ4F_flush(LZ4F_cctx* cctxPtr, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* compressOptionsPtr) +{ + BYTE* const dstStart = (BYTE*)dstBuffer; + BYTE* dstPtr = dstStart; + compressFunc_t compress; + + if (cctxPtr->tmpInSize == 0) return 0; /* nothing to flush */ + if (cctxPtr->cStage != 1) return err0r(LZ4F_ERROR_GENERIC); + if (dstCapacity < (cctxPtr->tmpInSize + BHSize + BFSize)) + return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); + (void)compressOptionsPtr; /* not yet useful */ + + /* select compression function */ + compress = LZ4F_selectCompression(cctxPtr->prefs.frameInfo.blockMode, cctxPtr->prefs.compressionLevel); + + /* compress tmp buffer */ + dstPtr += LZ4F_makeBlock(dstPtr, + cctxPtr->tmpIn, cctxPtr->tmpInSize, + compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel, + cctxPtr->cdict, + cctxPtr->prefs.frameInfo.blockChecksumFlag); + assert(((void)"flush overflows dstBuffer!", (size_t)(dstPtr - dstStart) <= dstCapacity)); + + if (cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) + cctxPtr->tmpIn += cctxPtr->tmpInSize; + cctxPtr->tmpInSize = 0; + + /* keep tmpIn within limits */ + if ((cctxPtr->tmpIn + cctxPtr->maxBlockSize) > (cctxPtr->tmpBuff + cctxPtr->maxBufferSize)) { /* necessarily LZ4F_blockLinked */ + int const realDictSize = LZ4F_localSaveDict(cctxPtr); + cctxPtr->tmpIn = cctxPtr->tmpBuff + realDictSize; + } + + return (size_t)(dstPtr - dstStart); +} + + +/*! LZ4F_compressEnd() : + * When you want to properly finish the compressed frame, just call LZ4F_compressEnd(). + * It will flush whatever data remained within compressionContext (like LZ4_flush()) + * but also properly finalize the frame, with an endMark and an (optional) checksum. + * LZ4F_compressOptions_t structure is optional : you can provide NULL as argument. + * @return: the number of bytes written into dstBuffer (necessarily >= 4 (endMark size)) + * or an error code if it fails (can be tested using LZ4F_isError()) + * The context can then be used again to compress a new frame, starting with LZ4F_compressBegin(). + */ +size_t LZ4F_compressEnd(LZ4F_cctx* cctxPtr, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* compressOptionsPtr) +{ + BYTE* const dstStart = (BYTE*)dstBuffer; + BYTE* dstPtr = dstStart; + + size_t const flushSize = LZ4F_flush(cctxPtr, dstBuffer, dstCapacity, compressOptionsPtr); + if (LZ4F_isError(flushSize)) return flushSize; + dstPtr += flushSize; + + assert(flushSize <= dstCapacity); + dstCapacity -= flushSize; + + if (dstCapacity < 4) return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); + LZ4F_writeLE32(dstPtr, 0); + dstPtr += 4; /* endMark */ + + if (cctxPtr->prefs.frameInfo.contentChecksumFlag == LZ4F_contentChecksumEnabled) { + U32 const xxh = XXH32_digest(&(cctxPtr->xxh)); + if (dstCapacity < 8) return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); + LZ4F_writeLE32(dstPtr, xxh); + dstPtr+=4; /* content Checksum */ + } + + cctxPtr->cStage = 0; /* state is now re-usable (with identical preferences) */ + cctxPtr->maxBufferSize = 0; /* reuse HC context */ + + if (cctxPtr->prefs.frameInfo.contentSize) { + if (cctxPtr->prefs.frameInfo.contentSize != cctxPtr->totalInSize) + return err0r(LZ4F_ERROR_frameSize_wrong); + } + + return (size_t)(dstPtr - dstStart); +} + + +/*-*************************************************** +* Frame Decompression +*****************************************************/ + +typedef enum { + dstage_getFrameHeader=0, dstage_storeFrameHeader, + dstage_init, + dstage_getBlockHeader, dstage_storeBlockHeader, + dstage_copyDirect, dstage_getBlockChecksum, + dstage_getCBlock, dstage_storeCBlock, + dstage_flushOut, + dstage_getSuffix, dstage_storeSuffix, + dstage_getSFrameSize, dstage_storeSFrameSize, + dstage_skipSkippable +} dStage_t; + +struct LZ4F_dctx_s { + LZ4F_frameInfo_t frameInfo; + U32 version; + dStage_t dStage; + U64 frameRemainingSize; + size_t maxBlockSize; + size_t maxBufferSize; + BYTE* tmpIn; + size_t tmpInSize; + size_t tmpInTarget; + BYTE* tmpOutBuffer; + const BYTE* dict; + size_t dictSize; + BYTE* tmpOut; + size_t tmpOutSize; + size_t tmpOutStart; + XXH32_state_t xxh; + XXH32_state_t blockChecksum; + BYTE header[LZ4F_HEADER_SIZE_MAX]; +}; /* typedef'd to LZ4F_dctx in lz4frame.h */ + + +/*! LZ4F_createDecompressionContext() : + * Create a decompressionContext object, which will track all decompression operations. + * Provides a pointer to a fully allocated and initialized LZ4F_decompressionContext object. + * Object can later be released using LZ4F_freeDecompressionContext(). + * @return : if != 0, there was an error during context creation. + */ +LZ4F_errorCode_t LZ4F_createDecompressionContext(LZ4F_dctx** LZ4F_decompressionContextPtr, unsigned versionNumber) +{ + LZ4F_dctx* const dctx = (LZ4F_dctx*)ALLOC_AND_ZERO(sizeof(LZ4F_dctx)); + if (dctx == NULL) { /* failed allocation */ + *LZ4F_decompressionContextPtr = NULL; + return err0r(LZ4F_ERROR_allocation_failed); + } + + dctx->version = versionNumber; + *LZ4F_decompressionContextPtr = dctx; + return LZ4F_OK_NoError; +} + +LZ4F_errorCode_t LZ4F_freeDecompressionContext(LZ4F_dctx* dctx) +{ + LZ4F_errorCode_t result = LZ4F_OK_NoError; + if (dctx != NULL) { /* can accept NULL input, like free() */ + result = (LZ4F_errorCode_t)dctx->dStage; + FREEMEM(dctx->tmpIn); + FREEMEM(dctx->tmpOutBuffer); + FREEMEM(dctx); + } + return result; +} + + +/*==--- Streaming Decompression operations ---==*/ + +void LZ4F_resetDecompressionContext(LZ4F_dctx* dctx) +{ + dctx->dStage = dstage_getFrameHeader; + dctx->dict = NULL; + dctx->dictSize = 0; +} + + +/*! LZ4F_decodeHeader() : + * input : `src` points at the **beginning of the frame** + * output : set internal values of dctx, such as + * dctx->frameInfo and dctx->dStage. + * Also allocates internal buffers. + * @return : nb Bytes read from src (necessarily <= srcSize) + * or an error code (testable with LZ4F_isError()) + */ +static size_t LZ4F_decodeHeader(LZ4F_dctx* dctx, const void* src, size_t srcSize) +{ + unsigned blockMode, blockChecksumFlag, contentSizeFlag, contentChecksumFlag, dictIDFlag, blockSizeID; + size_t frameHeaderSize; + const BYTE* srcPtr = (const BYTE*)src; + + /* need to decode header to get frameInfo */ + if (srcSize < minFHSize) return err0r(LZ4F_ERROR_frameHeader_incomplete); /* minimal frame header size */ + MEM_INIT(&(dctx->frameInfo), 0, sizeof(dctx->frameInfo)); + + /* special case : skippable frames */ + if ((LZ4F_readLE32(srcPtr) & 0xFFFFFFF0U) == LZ4F_MAGIC_SKIPPABLE_START) { + dctx->frameInfo.frameType = LZ4F_skippableFrame; + if (src == (void*)(dctx->header)) { + dctx->tmpInSize = srcSize; + dctx->tmpInTarget = 8; + dctx->dStage = dstage_storeSFrameSize; + return srcSize; + } else { + dctx->dStage = dstage_getSFrameSize; + return 4; + } + } + + /* control magic number */ + if (LZ4F_readLE32(srcPtr) != LZ4F_MAGICNUMBER) + return err0r(LZ4F_ERROR_frameType_unknown); + dctx->frameInfo.frameType = LZ4F_frame; + + /* Flags */ + { U32 const FLG = srcPtr[4]; + U32 const version = (FLG>>6) & _2BITS; + blockChecksumFlag = (FLG>>4) & _1BIT; + blockMode = (FLG>>5) & _1BIT; + contentSizeFlag = (FLG>>3) & _1BIT; + contentChecksumFlag = (FLG>>2) & _1BIT; + dictIDFlag = FLG & _1BIT; + /* validate */ + if (((FLG>>1)&_1BIT) != 0) return err0r(LZ4F_ERROR_reservedFlag_set); /* Reserved bit */ + if (version != 1) return err0r(LZ4F_ERROR_headerVersion_wrong); /* Version Number, only supported value */ + } + + /* Frame Header Size */ + frameHeaderSize = minFHSize + (contentSizeFlag?8:0) + (dictIDFlag?4:0); + + if (srcSize < frameHeaderSize) { + /* not enough input to fully decode frame header */ + if (srcPtr != dctx->header) + memcpy(dctx->header, srcPtr, srcSize); + dctx->tmpInSize = srcSize; + dctx->tmpInTarget = frameHeaderSize; + dctx->dStage = dstage_storeFrameHeader; + return srcSize; + } + + { U32 const BD = srcPtr[5]; + blockSizeID = (BD>>4) & _3BITS; + /* validate */ + if (((BD>>7)&_1BIT) != 0) return err0r(LZ4F_ERROR_reservedFlag_set); /* Reserved bit */ + if (blockSizeID < 4) return err0r(LZ4F_ERROR_maxBlockSize_invalid); /* 4-7 only supported values for the time being */ + if (((BD>>0)&_4BITS) != 0) return err0r(LZ4F_ERROR_reservedFlag_set); /* Reserved bits */ + } + + /* check header */ + assert(frameHeaderSize > 5); + { BYTE const HC = LZ4F_headerChecksum(srcPtr+4, frameHeaderSize-5); + if (HC != srcPtr[frameHeaderSize-1]) + return err0r(LZ4F_ERROR_headerChecksum_invalid); + } + + /* save */ + dctx->frameInfo.blockMode = (LZ4F_blockMode_t)blockMode; + dctx->frameInfo.blockChecksumFlag = (LZ4F_blockChecksum_t)blockChecksumFlag; + dctx->frameInfo.contentChecksumFlag = (LZ4F_contentChecksum_t)contentChecksumFlag; + dctx->frameInfo.blockSizeID = (LZ4F_blockSizeID_t)blockSizeID; + dctx->maxBlockSize = LZ4F_getBlockSize(blockSizeID); + if (contentSizeFlag) + dctx->frameRemainingSize = + dctx->frameInfo.contentSize = LZ4F_readLE64(srcPtr+6); + if (dictIDFlag) + dctx->frameInfo.dictID = LZ4F_readLE32(srcPtr + frameHeaderSize - 5); + + dctx->dStage = dstage_init; + + return frameHeaderSize; +} + + +/*! LZ4F_headerSize() : + * @return : size of frame header + * or an error code, which can be tested using LZ4F_isError() + */ +size_t LZ4F_headerSize(const void* src, size_t srcSize) +{ + if (src == NULL) return err0r(LZ4F_ERROR_srcPtr_wrong); + + /* minimal srcSize to determine header size */ + if (srcSize < LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH) + return err0r(LZ4F_ERROR_frameHeader_incomplete); + + /* special case : skippable frames */ + if ((LZ4F_readLE32(src) & 0xFFFFFFF0U) == LZ4F_MAGIC_SKIPPABLE_START) + return 8; + + /* control magic number */ + if (LZ4F_readLE32(src) != LZ4F_MAGICNUMBER) + return err0r(LZ4F_ERROR_frameType_unknown); + + /* Frame Header Size */ + { BYTE const FLG = ((const BYTE*)src)[4]; + U32 const contentSizeFlag = (FLG>>3) & _1BIT; + U32 const dictIDFlag = FLG & _1BIT; + return minFHSize + (contentSizeFlag?8:0) + (dictIDFlag?4:0); + } +} + +/*! LZ4F_getFrameInfo() : + * This function extracts frame parameters (max blockSize, frame checksum, etc.). + * Usage is optional. Objective is to provide relevant information for allocation purposes. + * This function works in 2 situations : + * - At the beginning of a new frame, in which case it will decode this information from `srcBuffer`, and start the decoding process. + * Amount of input data provided must be large enough to successfully decode the frame header. + * A header size is variable, but is guaranteed to be <= LZ4F_HEADER_SIZE_MAX bytes. It's possible to provide more input data than this minimum. + * - After decoding has been started. In which case, no input is read, frame parameters are extracted from dctx. + * The number of bytes consumed from srcBuffer will be updated within *srcSizePtr (necessarily <= original value). + * Decompression must resume from (srcBuffer + *srcSizePtr). + * @return : an hint about how many srcSize bytes LZ4F_decompress() expects for next call, + * or an error code which can be tested using LZ4F_isError() + * note 1 : in case of error, dctx is not modified. Decoding operations can resume from where they stopped. + * note 2 : frame parameters are *copied into* an already allocated LZ4F_frameInfo_t structure. + */ +LZ4F_errorCode_t LZ4F_getFrameInfo(LZ4F_dctx* dctx, + LZ4F_frameInfo_t* frameInfoPtr, + const void* srcBuffer, size_t* srcSizePtr) +{ + LZ4F_STATIC_ASSERT(dstage_getFrameHeader < dstage_storeFrameHeader); + if (dctx->dStage > dstage_storeFrameHeader) { + /* frameInfo already decoded */ + size_t o=0, i=0; + *srcSizePtr = 0; + *frameInfoPtr = dctx->frameInfo; + /* returns : recommended nb of bytes for LZ4F_decompress() */ + return LZ4F_decompress(dctx, NULL, &o, NULL, &i, NULL); + } else { + if (dctx->dStage == dstage_storeFrameHeader) { + /* frame decoding already started, in the middle of header => automatic fail */ + *srcSizePtr = 0; + return err0r(LZ4F_ERROR_frameDecoding_alreadyStarted); + } else { + size_t const hSize = LZ4F_headerSize(srcBuffer, *srcSizePtr); + if (LZ4F_isError(hSize)) { *srcSizePtr=0; return hSize; } + if (*srcSizePtr < hSize) { + *srcSizePtr=0; + return err0r(LZ4F_ERROR_frameHeader_incomplete); + } + + { size_t decodeResult = LZ4F_decodeHeader(dctx, srcBuffer, hSize); + if (LZ4F_isError(decodeResult)) { + *srcSizePtr = 0; + } else { + *srcSizePtr = decodeResult; + decodeResult = BHSize; /* block header size */ + } + *frameInfoPtr = dctx->frameInfo; + return decodeResult; + } } } +} + + +/* LZ4F_updateDict() : + * only used for LZ4F_blockLinked mode */ +static void LZ4F_updateDict(LZ4F_dctx* dctx, + const BYTE* dstPtr, size_t dstSize, const BYTE* dstBufferStart, + unsigned withinTmp) +{ + if (dctx->dictSize==0) + dctx->dict = (const BYTE*)dstPtr; /* priority to dictionary continuity */ + + if (dctx->dict + dctx->dictSize == dstPtr) { /* dictionary continuity, directly within dstBuffer */ + dctx->dictSize += dstSize; + return; + } + + assert(dstPtr >= dstBufferStart); + if ((size_t)(dstPtr - dstBufferStart) + dstSize >= 64 KB) { /* history in dstBuffer becomes large enough to become dictionary */ + dctx->dict = (const BYTE*)dstBufferStart; + dctx->dictSize = (size_t)(dstPtr - dstBufferStart) + dstSize; + return; + } + + assert(dstSize < 64 KB); /* if dstSize >= 64 KB, dictionary would be set into dstBuffer directly */ + + /* dstBuffer does not contain whole useful history (64 KB), so it must be saved within tmpOut */ + + if ((withinTmp) && (dctx->dict == dctx->tmpOutBuffer)) { /* continue history within tmpOutBuffer */ + /* withinTmp expectation : content of [dstPtr,dstSize] is same as [dict+dictSize,dstSize], so we just extend it */ + assert(dctx->dict + dctx->dictSize == dctx->tmpOut + dctx->tmpOutStart); + dctx->dictSize += dstSize; + return; + } + + if (withinTmp) { /* copy relevant dict portion in front of tmpOut within tmpOutBuffer */ + size_t const preserveSize = (size_t)(dctx->tmpOut - dctx->tmpOutBuffer); + size_t copySize = 64 KB - dctx->tmpOutSize; + const BYTE* const oldDictEnd = dctx->dict + dctx->dictSize - dctx->tmpOutStart; + if (dctx->tmpOutSize > 64 KB) copySize = 0; + if (copySize > preserveSize) copySize = preserveSize; + + memcpy(dctx->tmpOutBuffer + preserveSize - copySize, oldDictEnd - copySize, copySize); + + dctx->dict = dctx->tmpOutBuffer; + dctx->dictSize = preserveSize + dctx->tmpOutStart + dstSize; + return; + } + + if (dctx->dict == dctx->tmpOutBuffer) { /* copy dst into tmp to complete dict */ + if (dctx->dictSize + dstSize > dctx->maxBufferSize) { /* tmp buffer not large enough */ + size_t const preserveSize = 64 KB - dstSize; + memcpy(dctx->tmpOutBuffer, dctx->dict + dctx->dictSize - preserveSize, preserveSize); + dctx->dictSize = preserveSize; + } + memcpy(dctx->tmpOutBuffer + dctx->dictSize, dstPtr, dstSize); + dctx->dictSize += dstSize; + return; + } + + /* join dict & dest into tmp */ + { size_t preserveSize = 64 KB - dstSize; + if (preserveSize > dctx->dictSize) preserveSize = dctx->dictSize; + memcpy(dctx->tmpOutBuffer, dctx->dict + dctx->dictSize - preserveSize, preserveSize); + memcpy(dctx->tmpOutBuffer + preserveSize, dstPtr, dstSize); + dctx->dict = dctx->tmpOutBuffer; + dctx->dictSize = preserveSize + dstSize; + } +} + + + +/*! LZ4F_decompress() : + * Call this function repetitively to regenerate compressed data in srcBuffer. + * The function will attempt to decode up to *srcSizePtr bytes from srcBuffer + * into dstBuffer of capacity *dstSizePtr. + * + * The number of bytes regenerated into dstBuffer will be provided within *dstSizePtr (necessarily <= original value). + * + * The number of bytes effectively read from srcBuffer will be provided within *srcSizePtr (necessarily <= original value). + * If number of bytes read is < number of bytes provided, then decompression operation is not complete. + * Remaining data will have to be presented again in a subsequent invocation. + * + * The function result is an hint of the better srcSize to use for next call to LZ4F_decompress. + * Schematically, it's the size of the current (or remaining) compressed block + header of next block. + * Respecting the hint provides a small boost to performance, since it allows less buffer shuffling. + * Note that this is just a hint, and it's always possible to any srcSize value. + * When a frame is fully decoded, @return will be 0. + * If decompression failed, @return is an error code which can be tested using LZ4F_isError(). + */ +size_t LZ4F_decompress(LZ4F_dctx* dctx, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const LZ4F_decompressOptions_t* decompressOptionsPtr) +{ + LZ4F_decompressOptions_t optionsNull; + const BYTE* const srcStart = (const BYTE*)srcBuffer; + const BYTE* const srcEnd = srcStart + *srcSizePtr; + const BYTE* srcPtr = srcStart; + BYTE* const dstStart = (BYTE*)dstBuffer; + BYTE* const dstEnd = dstStart + *dstSizePtr; + BYTE* dstPtr = dstStart; + const BYTE* selectedIn = NULL; + unsigned doAnotherStage = 1; + size_t nextSrcSizeHint = 1; + + + MEM_INIT(&optionsNull, 0, sizeof(optionsNull)); + if (decompressOptionsPtr==NULL) decompressOptionsPtr = &optionsNull; + *srcSizePtr = 0; + *dstSizePtr = 0; + + /* behaves as a state machine */ + + while (doAnotherStage) { + + switch(dctx->dStage) + { + + case dstage_getFrameHeader: + if ((size_t)(srcEnd-srcPtr) >= maxFHSize) { /* enough to decode - shortcut */ + size_t const hSize = LZ4F_decodeHeader(dctx, srcPtr, (size_t)(srcEnd-srcPtr)); /* will update dStage appropriately */ + if (LZ4F_isError(hSize)) return hSize; + srcPtr += hSize; + break; + } + dctx->tmpInSize = 0; + if (srcEnd-srcPtr == 0) return minFHSize; /* 0-size input */ + dctx->tmpInTarget = minFHSize; /* minimum size to decode header */ + dctx->dStage = dstage_storeFrameHeader; + /* fall-through */ + + case dstage_storeFrameHeader: + { size_t const sizeToCopy = MIN(dctx->tmpInTarget - dctx->tmpInSize, (size_t)(srcEnd - srcPtr)); + memcpy(dctx->header + dctx->tmpInSize, srcPtr, sizeToCopy); + dctx->tmpInSize += sizeToCopy; + srcPtr += sizeToCopy; + } + if (dctx->tmpInSize < dctx->tmpInTarget) { + nextSrcSizeHint = (dctx->tmpInTarget - dctx->tmpInSize) + BHSize; /* rest of header + nextBlockHeader */ + doAnotherStage = 0; /* not enough src data, ask for some more */ + break; + } + { size_t const hSize = LZ4F_decodeHeader(dctx, dctx->header, dctx->tmpInTarget); /* will update dStage appropriately */ + if (LZ4F_isError(hSize)) return hSize; + } + break; + + case dstage_init: + if (dctx->frameInfo.contentChecksumFlag) (void)XXH32_reset(&(dctx->xxh), 0); + /* internal buffers allocation */ + { size_t const bufferNeeded = dctx->maxBlockSize + + ((dctx->frameInfo.blockMode==LZ4F_blockLinked) ? 128 KB : 0); + if (bufferNeeded > dctx->maxBufferSize) { /* tmp buffers too small */ + dctx->maxBufferSize = 0; /* ensure allocation will be re-attempted on next entry*/ + FREEMEM(dctx->tmpIn); + dctx->tmpIn = (BYTE*)ALLOC(dctx->maxBlockSize + BFSize /* block checksum */); + if (dctx->tmpIn == NULL) + return err0r(LZ4F_ERROR_allocation_failed); + FREEMEM(dctx->tmpOutBuffer); + dctx->tmpOutBuffer= (BYTE*)ALLOC(bufferNeeded); + if (dctx->tmpOutBuffer== NULL) + return err0r(LZ4F_ERROR_allocation_failed); + dctx->maxBufferSize = bufferNeeded; + } } + dctx->tmpInSize = 0; + dctx->tmpInTarget = 0; + dctx->tmpOut = dctx->tmpOutBuffer; + dctx->tmpOutStart = 0; + dctx->tmpOutSize = 0; + + dctx->dStage = dstage_getBlockHeader; + /* fall-through */ + + case dstage_getBlockHeader: + if ((size_t)(srcEnd - srcPtr) >= BHSize) { + selectedIn = srcPtr; + srcPtr += BHSize; + } else { + /* not enough input to read cBlockSize field */ + dctx->tmpInSize = 0; + dctx->dStage = dstage_storeBlockHeader; + } + + if (dctx->dStage == dstage_storeBlockHeader) /* can be skipped */ + case dstage_storeBlockHeader: + { size_t const remainingInput = (size_t)(srcEnd - srcPtr); + size_t const wantedData = BHSize - dctx->tmpInSize; + size_t const sizeToCopy = MIN(wantedData, remainingInput); + memcpy(dctx->tmpIn + dctx->tmpInSize, srcPtr, sizeToCopy); + srcPtr += sizeToCopy; + dctx->tmpInSize += sizeToCopy; + + if (dctx->tmpInSize < BHSize) { /* not enough input for cBlockSize */ + nextSrcSizeHint = BHSize - dctx->tmpInSize; + doAnotherStage = 0; + break; + } + selectedIn = dctx->tmpIn; + } /* if (dctx->dStage == dstage_storeBlockHeader) */ + + /* decode block header */ + { size_t const nextCBlockSize = LZ4F_readLE32(selectedIn) & 0x7FFFFFFFU; + size_t const crcSize = dctx->frameInfo.blockChecksumFlag * BFSize; + if (nextCBlockSize==0) { /* frameEnd signal, no more block */ + dctx->dStage = dstage_getSuffix; + break; + } + if (nextCBlockSize > dctx->maxBlockSize) + return err0r(LZ4F_ERROR_maxBlockSize_invalid); + if (LZ4F_readLE32(selectedIn) & LZ4F_BLOCKUNCOMPRESSED_FLAG) { + /* next block is uncompressed */ + dctx->tmpInTarget = nextCBlockSize; + if (dctx->frameInfo.blockChecksumFlag) { + (void)XXH32_reset(&dctx->blockChecksum, 0); + } + dctx->dStage = dstage_copyDirect; + break; + } + /* next block is a compressed block */ + dctx->tmpInTarget = nextCBlockSize + crcSize; + dctx->dStage = dstage_getCBlock; + if (dstPtr==dstEnd) { + nextSrcSizeHint = BHSize + nextCBlockSize + crcSize; + doAnotherStage = 0; + } + break; + } + + case dstage_copyDirect: /* uncompressed block */ + { size_t const minBuffSize = MIN((size_t)(srcEnd-srcPtr), (size_t)(dstEnd-dstPtr)); + size_t const sizeToCopy = MIN(dctx->tmpInTarget, minBuffSize); + memcpy(dstPtr, srcPtr, sizeToCopy); + if (dctx->frameInfo.blockChecksumFlag) { + (void)XXH32_update(&dctx->blockChecksum, srcPtr, sizeToCopy); + } + if (dctx->frameInfo.contentChecksumFlag) + (void)XXH32_update(&dctx->xxh, srcPtr, sizeToCopy); + if (dctx->frameInfo.contentSize) + dctx->frameRemainingSize -= sizeToCopy; + + /* history management (linked blocks only)*/ + if (dctx->frameInfo.blockMode == LZ4F_blockLinked) + LZ4F_updateDict(dctx, dstPtr, sizeToCopy, dstStart, 0); + + srcPtr += sizeToCopy; + dstPtr += sizeToCopy; + if (sizeToCopy == dctx->tmpInTarget) { /* all done */ + if (dctx->frameInfo.blockChecksumFlag) { + dctx->tmpInSize = 0; + dctx->dStage = dstage_getBlockChecksum; + } else + dctx->dStage = dstage_getBlockHeader; /* new block */ + break; + } + dctx->tmpInTarget -= sizeToCopy; /* need to copy more */ + nextSrcSizeHint = dctx->tmpInTarget + + +(dctx->frameInfo.blockChecksumFlag ? BFSize : 0) + + BHSize /* next header size */; + doAnotherStage = 0; + break; + } + + /* check block checksum for recently transferred uncompressed block */ + case dstage_getBlockChecksum: + { const void* crcSrc; + if ((srcEnd-srcPtr >= 4) && (dctx->tmpInSize==0)) { + crcSrc = srcPtr; + srcPtr += 4; + } else { + size_t const stillToCopy = 4 - dctx->tmpInSize; + size_t const sizeToCopy = MIN(stillToCopy, (size_t)(srcEnd-srcPtr)); + memcpy(dctx->header + dctx->tmpInSize, srcPtr, sizeToCopy); + dctx->tmpInSize += sizeToCopy; + srcPtr += sizeToCopy; + if (dctx->tmpInSize < 4) { /* all input consumed */ + doAnotherStage = 0; + break; + } + crcSrc = dctx->header; + } + { U32 const readCRC = LZ4F_readLE32(crcSrc); + U32 const calcCRC = XXH32_digest(&dctx->blockChecksum); + if (readCRC != calcCRC) + return err0r(LZ4F_ERROR_blockChecksum_invalid); + } } + dctx->dStage = dstage_getBlockHeader; /* new block */ + break; + + case dstage_getCBlock: + if ((size_t)(srcEnd-srcPtr) < dctx->tmpInTarget) { + dctx->tmpInSize = 0; + dctx->dStage = dstage_storeCBlock; + break; + } + /* input large enough to read full block directly */ + selectedIn = srcPtr; + srcPtr += dctx->tmpInTarget; + + if (0) /* jump over next block */ + case dstage_storeCBlock: + { size_t const wantedData = dctx->tmpInTarget - dctx->tmpInSize; + size_t const inputLeft = (size_t)(srcEnd-srcPtr); + size_t const sizeToCopy = MIN(wantedData, inputLeft); + memcpy(dctx->tmpIn + dctx->tmpInSize, srcPtr, sizeToCopy); + dctx->tmpInSize += sizeToCopy; + srcPtr += sizeToCopy; + if (dctx->tmpInSize < dctx->tmpInTarget) { /* need more input */ + nextSrcSizeHint = (dctx->tmpInTarget - dctx->tmpInSize) + + (dctx->frameInfo.blockChecksumFlag ? BFSize : 0) + + BHSize /* next header size */; + doAnotherStage = 0; + break; + } + selectedIn = dctx->tmpIn; + } + + /* At this stage, input is large enough to decode a block */ + if (dctx->frameInfo.blockChecksumFlag) { + dctx->tmpInTarget -= 4; + assert(selectedIn != NULL); /* selectedIn is defined at this stage (either srcPtr, or dctx->tmpIn) */ + { U32 const readBlockCrc = LZ4F_readLE32(selectedIn + dctx->tmpInTarget); + U32 const calcBlockCrc = XXH32(selectedIn, dctx->tmpInTarget, 0); + if (readBlockCrc != calcBlockCrc) + return err0r(LZ4F_ERROR_blockChecksum_invalid); + } } + + if ((size_t)(dstEnd-dstPtr) >= dctx->maxBlockSize) { + const char* dict = (const char*)dctx->dict; + size_t dictSize = dctx->dictSize; + int decodedSize; + if (dict && dictSize > 1 GB) { + /* the dictSize param is an int, avoid truncation / sign issues */ + dict += dictSize - 64 KB; + dictSize = 64 KB; + } + /* enough capacity in `dst` to decompress directly there */ + decodedSize = LZ4_decompress_safe_usingDict( + (const char*)selectedIn, (char*)dstPtr, + (int)dctx->tmpInTarget, (int)dctx->maxBlockSize, + dict, (int)dictSize); + if (decodedSize < 0) return err0r(LZ4F_ERROR_GENERIC); /* decompression failed */ + if (dctx->frameInfo.contentChecksumFlag) + XXH32_update(&(dctx->xxh), dstPtr, (size_t)decodedSize); + if (dctx->frameInfo.contentSize) + dctx->frameRemainingSize -= (size_t)decodedSize; + + /* dictionary management */ + if (dctx->frameInfo.blockMode==LZ4F_blockLinked) + LZ4F_updateDict(dctx, dstPtr, (size_t)decodedSize, dstStart, 0); + + dstPtr += decodedSize; + dctx->dStage = dstage_getBlockHeader; + break; + } + + /* not enough place into dst : decode into tmpOut */ + /* ensure enough place for tmpOut */ + if (dctx->frameInfo.blockMode == LZ4F_blockLinked) { + if (dctx->dict == dctx->tmpOutBuffer) { + if (dctx->dictSize > 128 KB) { + memcpy(dctx->tmpOutBuffer, dctx->dict + dctx->dictSize - 64 KB, 64 KB); + dctx->dictSize = 64 KB; + } + dctx->tmpOut = dctx->tmpOutBuffer + dctx->dictSize; + } else { /* dict not within tmp */ + size_t const reservedDictSpace = MIN(dctx->dictSize, 64 KB); + dctx->tmpOut = dctx->tmpOutBuffer + reservedDictSpace; + } } + + /* Decode block */ + { const char* dict = (const char*)dctx->dict; + size_t dictSize = dctx->dictSize; + int decodedSize; + if (dict && dictSize > 1 GB) { + /* the dictSize param is an int, avoid truncation / sign issues */ + dict += dictSize - 64 KB; + dictSize = 64 KB; + } + decodedSize = LZ4_decompress_safe_usingDict( + (const char*)selectedIn, (char*)dctx->tmpOut, + (int)dctx->tmpInTarget, (int)dctx->maxBlockSize, + dict, (int)dictSize); + if (decodedSize < 0) /* decompression failed */ + return err0r(LZ4F_ERROR_decompressionFailed); + if (dctx->frameInfo.contentChecksumFlag) + XXH32_update(&(dctx->xxh), dctx->tmpOut, (size_t)decodedSize); + if (dctx->frameInfo.contentSize) + dctx->frameRemainingSize -= (size_t)decodedSize; + dctx->tmpOutSize = (size_t)decodedSize; + dctx->tmpOutStart = 0; + dctx->dStage = dstage_flushOut; + } + /* fall-through */ + + case dstage_flushOut: /* flush decoded data from tmpOut to dstBuffer */ + { size_t const sizeToCopy = MIN(dctx->tmpOutSize - dctx->tmpOutStart, (size_t)(dstEnd-dstPtr)); + memcpy(dstPtr, dctx->tmpOut + dctx->tmpOutStart, sizeToCopy); + + /* dictionary management */ + if (dctx->frameInfo.blockMode == LZ4F_blockLinked) + LZ4F_updateDict(dctx, dstPtr, sizeToCopy, dstStart, 1 /*withinTmp*/); + + dctx->tmpOutStart += sizeToCopy; + dstPtr += sizeToCopy; + + if (dctx->tmpOutStart == dctx->tmpOutSize) { /* all flushed */ + dctx->dStage = dstage_getBlockHeader; /* get next block */ + break; + } + /* could not flush everything : stop there, just request a block header */ + doAnotherStage = 0; + nextSrcSizeHint = BHSize; + break; + } + + case dstage_getSuffix: + if (dctx->frameRemainingSize) + return err0r(LZ4F_ERROR_frameSize_wrong); /* incorrect frame size decoded */ + if (!dctx->frameInfo.contentChecksumFlag) { /* no checksum, frame is completed */ + nextSrcSizeHint = 0; + LZ4F_resetDecompressionContext(dctx); + doAnotherStage = 0; + break; + } + if ((srcEnd - srcPtr) < 4) { /* not enough size for entire CRC */ + dctx->tmpInSize = 0; + dctx->dStage = dstage_storeSuffix; + } else { + selectedIn = srcPtr; + srcPtr += 4; + } + + if (dctx->dStage == dstage_storeSuffix) /* can be skipped */ + case dstage_storeSuffix: + { size_t const remainingInput = (size_t)(srcEnd - srcPtr); + size_t const wantedData = 4 - dctx->tmpInSize; + size_t const sizeToCopy = MIN(wantedData, remainingInput); + memcpy(dctx->tmpIn + dctx->tmpInSize, srcPtr, sizeToCopy); + srcPtr += sizeToCopy; + dctx->tmpInSize += sizeToCopy; + if (dctx->tmpInSize < 4) { /* not enough input to read complete suffix */ + nextSrcSizeHint = 4 - dctx->tmpInSize; + doAnotherStage=0; + break; + } + selectedIn = dctx->tmpIn; + } /* if (dctx->dStage == dstage_storeSuffix) */ + + /* case dstage_checkSuffix: */ /* no direct entry, avoid initialization risks */ + { U32 const readCRC = LZ4F_readLE32(selectedIn); + U32 const resultCRC = XXH32_digest(&(dctx->xxh)); + if (readCRC != resultCRC) + return err0r(LZ4F_ERROR_contentChecksum_invalid); + nextSrcSizeHint = 0; + LZ4F_resetDecompressionContext(dctx); + doAnotherStage = 0; + break; + } + + case dstage_getSFrameSize: + if ((srcEnd - srcPtr) >= 4) { + selectedIn = srcPtr; + srcPtr += 4; + } else { + /* not enough input to read cBlockSize field */ + dctx->tmpInSize = 4; + dctx->tmpInTarget = 8; + dctx->dStage = dstage_storeSFrameSize; + } + + if (dctx->dStage == dstage_storeSFrameSize) + case dstage_storeSFrameSize: + { size_t const sizeToCopy = MIN(dctx->tmpInTarget - dctx->tmpInSize, + (size_t)(srcEnd - srcPtr) ); + memcpy(dctx->header + dctx->tmpInSize, srcPtr, sizeToCopy); + srcPtr += sizeToCopy; + dctx->tmpInSize += sizeToCopy; + if (dctx->tmpInSize < dctx->tmpInTarget) { + /* not enough input to get full sBlockSize; wait for more */ + nextSrcSizeHint = dctx->tmpInTarget - dctx->tmpInSize; + doAnotherStage = 0; + break; + } + selectedIn = dctx->header + 4; + } /* if (dctx->dStage == dstage_storeSFrameSize) */ + + /* case dstage_decodeSFrameSize: */ /* no direct entry */ + { size_t const SFrameSize = LZ4F_readLE32(selectedIn); + dctx->frameInfo.contentSize = SFrameSize; + dctx->tmpInTarget = SFrameSize; + dctx->dStage = dstage_skipSkippable; + break; + } + + case dstage_skipSkippable: + { size_t const skipSize = MIN(dctx->tmpInTarget, (size_t)(srcEnd-srcPtr)); + srcPtr += skipSize; + dctx->tmpInTarget -= skipSize; + doAnotherStage = 0; + nextSrcSizeHint = dctx->tmpInTarget; + if (nextSrcSizeHint) break; /* still more to skip */ + /* frame fully skipped : prepare context for a new frame */ + LZ4F_resetDecompressionContext(dctx); + break; + } + } /* switch (dctx->dStage) */ + } /* while (doAnotherStage) */ + + /* preserve history within tmp whenever necessary */ + LZ4F_STATIC_ASSERT((unsigned)dstage_init == 2); + if ( (dctx->frameInfo.blockMode==LZ4F_blockLinked) /* next block will use up to 64KB from previous ones */ + && (dctx->dict != dctx->tmpOutBuffer) /* dictionary is not already within tmp */ + && (!decompressOptionsPtr->stableDst) /* cannot rely on dst data to remain there for next call */ + && ((unsigned)(dctx->dStage)-2 < (unsigned)(dstage_getSuffix)-2) ) /* valid stages : [init ... getSuffix[ */ + { + if (dctx->dStage == dstage_flushOut) { + size_t const preserveSize = (size_t)(dctx->tmpOut - dctx->tmpOutBuffer); + size_t copySize = 64 KB - dctx->tmpOutSize; + const BYTE* oldDictEnd = dctx->dict + dctx->dictSize - dctx->tmpOutStart; + if (dctx->tmpOutSize > 64 KB) copySize = 0; + if (copySize > preserveSize) copySize = preserveSize; + + if (copySize > 0) + memcpy(dctx->tmpOutBuffer + preserveSize - copySize, oldDictEnd - copySize, copySize); + + dctx->dict = dctx->tmpOutBuffer; + dctx->dictSize = preserveSize + dctx->tmpOutStart; + } else { + const BYTE* const oldDictEnd = dctx->dict + dctx->dictSize; + size_t const newDictSize = MIN(dctx->dictSize, 64 KB); + + if (newDictSize > 0) + memcpy(dctx->tmpOutBuffer, oldDictEnd - newDictSize, newDictSize); + + dctx->dict = dctx->tmpOutBuffer; + dctx->dictSize = newDictSize; + dctx->tmpOut = dctx->tmpOutBuffer + newDictSize; + } + } + + *srcSizePtr = (size_t)(srcPtr - srcStart); + *dstSizePtr = (size_t)(dstPtr - dstStart); + return nextSrcSizeHint; +} + +/*! LZ4F_decompress_usingDict() : + * Same as LZ4F_decompress(), using a predefined dictionary. + * Dictionary is used "in place", without any preprocessing. + * It must remain accessible throughout the entire frame decoding. + */ +size_t LZ4F_decompress_usingDict(LZ4F_dctx* dctx, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const void* dict, size_t dictSize, + const LZ4F_decompressOptions_t* decompressOptionsPtr) +{ + if (dctx->dStage <= dstage_init) { + dctx->dict = (const BYTE*)dict; + dctx->dictSize = dictSize; + } + return LZ4F_decompress(dctx, dstBuffer, dstSizePtr, + srcBuffer, srcSizePtr, + decompressOptionsPtr); +} diff --git a/TMessagesProj/jni/lz4/lz4frame.h b/TMessagesProj/jni/lz4/lz4frame.h new file mode 100755 index 000000000..742c2528e --- /dev/null +++ b/TMessagesProj/jni/lz4/lz4frame.h @@ -0,0 +1,606 @@ +/* + LZ4 auto-framing library + Header File + Copyright (C) 2011-2017, Yann Collet. + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ + +/* LZ4F is a stand-alone API able to create and decode LZ4 frames + * conformant with specification v1.6.1 in doc/lz4_Frame_format.md . + * Generated frames are compatible with `lz4` CLI. + * + * LZ4F also offers streaming capabilities. + * + * lz4.h is not required when using lz4frame.h, + * except to extract common constant such as LZ4_VERSION_NUMBER. + * */ + +#ifndef LZ4F_H_09782039843 +#define LZ4F_H_09782039843 + +#if defined (__cplusplus) +extern "C" { +#endif + +/* --- Dependency --- */ +#include /* size_t */ + + +/** + Introduction + + lz4frame.h implements LZ4 frame specification (doc/lz4_Frame_format.md). + lz4frame.h provides frame compression functions that take care + of encoding standard metadata alongside LZ4-compressed blocks. +*/ + +/*-*************************************************************** + * Compiler specifics + *****************************************************************/ +/* LZ4_DLL_EXPORT : + * Enable exporting of functions when building a Windows DLL + * LZ4FLIB_API : + * Control library symbols visibility. + */ +#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) +# define LZ4FLIB_API __declspec(dllexport) +#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) +# define LZ4FLIB_API __declspec(dllimport) +#elif defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4FLIB_API __attribute__ ((__visibility__ ("default"))) +#else +# define LZ4FLIB_API +#endif + +#ifdef LZ4F_DISABLE_DEPRECATE_WARNINGS +# define LZ4F_DEPRECATE(x) x +#else +# if defined(_MSC_VER) +# define LZ4F_DEPRECATE(x) x /* __declspec(deprecated) x - only works with C++ */ +# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 6)) +# define LZ4F_DEPRECATE(x) x __attribute__((deprecated)) +# else +# define LZ4F_DEPRECATE(x) x /* no deprecation warning for this compiler */ +# endif +#endif + + +/*-************************************ + * Error management + **************************************/ +typedef size_t LZ4F_errorCode_t; + +LZ4FLIB_API unsigned LZ4F_isError(LZ4F_errorCode_t code); /**< tells when a function result is an error code */ +LZ4FLIB_API const char* LZ4F_getErrorName(LZ4F_errorCode_t code); /**< return error code string; for debugging */ + + +/*-************************************ + * Frame compression types + **************************************/ +/* #define LZ4F_ENABLE_OBSOLETE_ENUMS // uncomment to enable obsolete enums */ +#ifdef LZ4F_ENABLE_OBSOLETE_ENUMS +# define LZ4F_OBSOLETE_ENUM(x) , LZ4F_DEPRECATE(x) = LZ4F_##x +#else +# define LZ4F_OBSOLETE_ENUM(x) +#endif + +/* The larger the block size, the (slightly) better the compression ratio, + * though there are diminishing returns. + * Larger blocks also increase memory usage on both compression and decompression sides. */ +typedef enum { + LZ4F_default=0, + LZ4F_max64KB=4, + LZ4F_max256KB=5, + LZ4F_max1MB=6, + LZ4F_max4MB=7 + LZ4F_OBSOLETE_ENUM(max64KB) + LZ4F_OBSOLETE_ENUM(max256KB) + LZ4F_OBSOLETE_ENUM(max1MB) + LZ4F_OBSOLETE_ENUM(max4MB) +} LZ4F_blockSizeID_t; + +/* Linked blocks sharply reduce inefficiencies when using small blocks, + * they compress better. + * However, some LZ4 decoders are only compatible with independent blocks */ +typedef enum { + LZ4F_blockLinked=0, + LZ4F_blockIndependent + LZ4F_OBSOLETE_ENUM(blockLinked) + LZ4F_OBSOLETE_ENUM(blockIndependent) +} LZ4F_blockMode_t; + +typedef enum { + LZ4F_noContentChecksum=0, + LZ4F_contentChecksumEnabled + LZ4F_OBSOLETE_ENUM(noContentChecksum) + LZ4F_OBSOLETE_ENUM(contentChecksumEnabled) +} LZ4F_contentChecksum_t; + +typedef enum { + LZ4F_noBlockChecksum=0, + LZ4F_blockChecksumEnabled +} LZ4F_blockChecksum_t; + +typedef enum { + LZ4F_frame=0, + LZ4F_skippableFrame + LZ4F_OBSOLETE_ENUM(skippableFrame) +} LZ4F_frameType_t; + +#ifdef LZ4F_ENABLE_OBSOLETE_ENUMS +typedef LZ4F_blockSizeID_t blockSizeID_t; +typedef LZ4F_blockMode_t blockMode_t; +typedef LZ4F_frameType_t frameType_t; +typedef LZ4F_contentChecksum_t contentChecksum_t; +#endif + +/*! LZ4F_frameInfo_t : + * makes it possible to set or read frame parameters. + * Structure must be first init to 0, using memset() or LZ4F_INIT_FRAMEINFO, + * setting all parameters to default. + * It's then possible to update selectively some parameters */ +typedef struct { + LZ4F_blockSizeID_t blockSizeID; /* max64KB, max256KB, max1MB, max4MB; 0 == default */ + LZ4F_blockMode_t blockMode; /* LZ4F_blockLinked, LZ4F_blockIndependent; 0 == default */ + LZ4F_contentChecksum_t contentChecksumFlag; /* 1: frame terminated with 32-bit checksum of decompressed data; 0: disabled (default) */ + LZ4F_frameType_t frameType; /* read-only field : LZ4F_frame or LZ4F_skippableFrame */ + unsigned long long contentSize; /* Size of uncompressed content ; 0 == unknown */ + unsigned dictID; /* Dictionary ID, sent by compressor to help decoder select correct dictionary; 0 == no dictID provided */ + LZ4F_blockChecksum_t blockChecksumFlag; /* 1: each block followed by a checksum of block's compressed data; 0: disabled (default) */ +} LZ4F_frameInfo_t; + +#define LZ4F_INIT_FRAMEINFO { LZ4F_default, LZ4F_blockLinked, LZ4F_noContentChecksum, LZ4F_frame, 0ULL, 0U, LZ4F_noBlockChecksum } /* v1.8.3+ */ + +/*! LZ4F_preferences_t : + * makes it possible to supply advanced compression instructions to streaming interface. + * Structure must be first init to 0, using memset() or LZ4F_INIT_PREFERENCES, + * setting all parameters to default. + * All reserved fields must be set to zero. */ +typedef struct { + LZ4F_frameInfo_t frameInfo; + int compressionLevel; /* 0: default (fast mode); values > LZ4HC_CLEVEL_MAX count as LZ4HC_CLEVEL_MAX; values < 0 trigger "fast acceleration" */ + unsigned autoFlush; /* 1: always flush; reduces usage of internal buffers */ + unsigned favorDecSpeed; /* 1: parser favors decompression speed vs compression ratio. Only works for high compression modes (>= LZ4HC_CLEVEL_OPT_MIN) */ /* v1.8.2+ */ + unsigned reserved[3]; /* must be zero for forward compatibility */ +} LZ4F_preferences_t; + +#define LZ4F_INIT_PREFERENCES { LZ4F_INIT_FRAMEINFO, 0, 0u, 0u, { 0u, 0u, 0u } } /* v1.8.3+ */ + + +/*-********************************* +* Simple compression function +***********************************/ + +LZ4FLIB_API int LZ4F_compressionLevel_max(void); /* v1.8.0+ */ + +/*! LZ4F_compressFrameBound() : + * Returns the maximum possible compressed size with LZ4F_compressFrame() given srcSize and preferences. + * `preferencesPtr` is optional. It can be replaced by NULL, in which case, the function will assume default preferences. + * Note : this result is only usable with LZ4F_compressFrame(). + * It may also be used with LZ4F_compressUpdate() _if no flush() operation_ is performed. + */ +LZ4FLIB_API size_t LZ4F_compressFrameBound(size_t srcSize, const LZ4F_preferences_t* preferencesPtr); + +/*! LZ4F_compressFrame() : + * Compress an entire srcBuffer into a valid LZ4 frame. + * dstCapacity MUST be >= LZ4F_compressFrameBound(srcSize, preferencesPtr). + * The LZ4F_preferences_t structure is optional : you can provide NULL as argument. All preferences will be set to default. + * @return : number of bytes written into dstBuffer. + * or an error code if it fails (can be tested using LZ4F_isError()) + */ +LZ4FLIB_API size_t LZ4F_compressFrame(void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_preferences_t* preferencesPtr); + + +/*-*********************************** +* Advanced compression functions +*************************************/ +typedef struct LZ4F_cctx_s LZ4F_cctx; /* incomplete type */ +typedef LZ4F_cctx* LZ4F_compressionContext_t; /* for compatibility with previous API version */ + +typedef struct { + unsigned stableSrc; /* 1 == src content will remain present on future calls to LZ4F_compress(); skip copying src content within tmp buffer */ + unsigned reserved[3]; +} LZ4F_compressOptions_t; + +/*--- Resource Management ---*/ + +#define LZ4F_VERSION 100 /* This number can be used to check for an incompatible API breaking change */ +LZ4FLIB_API unsigned LZ4F_getVersion(void); + +/*! LZ4F_createCompressionContext() : + * The first thing to do is to create a compressionContext object, which will be used in all compression operations. + * This is achieved using LZ4F_createCompressionContext(), which takes as argument a version. + * The version provided MUST be LZ4F_VERSION. It is intended to track potential version mismatch, notably when using DLL. + * The function will provide a pointer to a fully allocated LZ4F_cctx object. + * If @return != zero, there was an error during context creation. + * Object can release its memory using LZ4F_freeCompressionContext(); + */ +LZ4FLIB_API LZ4F_errorCode_t LZ4F_createCompressionContext(LZ4F_cctx** cctxPtr, unsigned version); +LZ4FLIB_API LZ4F_errorCode_t LZ4F_freeCompressionContext(LZ4F_cctx* cctx); + + +/*---- Compression ----*/ + +#define LZ4F_HEADER_SIZE_MIN 7 /* LZ4 Frame header size can vary, depending on selected paramaters */ +#define LZ4F_HEADER_SIZE_MAX 19 + +/*! LZ4F_compressBegin() : + * will write the frame header into dstBuffer. + * dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. + * `prefsPtr` is optional : you can provide NULL as argument, all preferences will then be set to default. + * @return : number of bytes written into dstBuffer for the header + * or an error code (which can be tested using LZ4F_isError()) + */ +LZ4FLIB_API size_t LZ4F_compressBegin(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_preferences_t* prefsPtr); + +/*! LZ4F_compressBound() : + * Provides minimum dstCapacity required to guarantee success of + * LZ4F_compressUpdate(), given a srcSize and preferences, for a worst case scenario. + * When srcSize==0, LZ4F_compressBound() provides an upper bound for LZ4F_flush() and LZ4F_compressEnd() instead. + * Note that the result is only valid for a single invocation of LZ4F_compressUpdate(). + * When invoking LZ4F_compressUpdate() multiple times, + * if the output buffer is gradually filled up instead of emptied and re-used from its start, + * one must check if there is enough remaining capacity before each invocation, using LZ4F_compressBound(). + * @return is always the same for a srcSize and prefsPtr. + * prefsPtr is optional : when NULL is provided, preferences will be set to cover worst case scenario. + * tech details : + * @return includes the possibility that internal buffer might already be filled by up to (blockSize-1) bytes. + * It also includes frame footer (ending + checksum), since it might be generated by LZ4F_compressEnd(). + * @return doesn't include frame header, as it was already generated by LZ4F_compressBegin(). + */ +LZ4FLIB_API size_t LZ4F_compressBound(size_t srcSize, const LZ4F_preferences_t* prefsPtr); + +/*! LZ4F_compressUpdate() : + * LZ4F_compressUpdate() can be called repetitively to compress as much data as necessary. + * Important rule: dstCapacity MUST be large enough to ensure operation success even in worst case situations. + * This value is provided by LZ4F_compressBound(). + * If this condition is not respected, LZ4F_compress() will fail (result is an errorCode). + * LZ4F_compressUpdate() doesn't guarantee error recovery. + * When an error occurs, compression context must be freed or resized. + * `cOptPtr` is optional : NULL can be provided, in which case all options are set to default. + * @return : number of bytes written into `dstBuffer` (it can be zero, meaning input data was just buffered). + * or an error code if it fails (which can be tested using LZ4F_isError()) + */ +LZ4FLIB_API size_t LZ4F_compressUpdate(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_compressOptions_t* cOptPtr); + +/*! LZ4F_flush() : + * When data must be generated and sent immediately, without waiting for a block to be completely filled, + * it's possible to call LZ4_flush(). It will immediately compress any data buffered within cctx. + * `dstCapacity` must be large enough to ensure the operation will be successful. + * `cOptPtr` is optional : it's possible to provide NULL, all options will be set to default. + * @return : nb of bytes written into dstBuffer (can be zero, when there is no data stored within cctx) + * or an error code if it fails (which can be tested using LZ4F_isError()) + * Note : LZ4F_flush() is guaranteed to be successful when dstCapacity >= LZ4F_compressBound(0, prefsPtr). + */ +LZ4FLIB_API size_t LZ4F_flush(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* cOptPtr); + +/*! LZ4F_compressEnd() : + * To properly finish an LZ4 frame, invoke LZ4F_compressEnd(). + * It will flush whatever data remained within `cctx` (like LZ4_flush()) + * and properly finalize the frame, with an endMark and a checksum. + * `cOptPtr` is optional : NULL can be provided, in which case all options will be set to default. + * @return : nb of bytes written into dstBuffer, necessarily >= 4 (endMark), + * or an error code if it fails (which can be tested using LZ4F_isError()) + * Note : LZ4F_compressEnd() is guaranteed to be successful when dstCapacity >= LZ4F_compressBound(0, prefsPtr). + * A successful call to LZ4F_compressEnd() makes `cctx` available again for another compression task. + */ +LZ4FLIB_API size_t LZ4F_compressEnd(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* cOptPtr); + + +/*-********************************* +* Decompression functions +***********************************/ +typedef struct LZ4F_dctx_s LZ4F_dctx; /* incomplete type */ +typedef LZ4F_dctx* LZ4F_decompressionContext_t; /* compatibility with previous API versions */ + +typedef struct { + unsigned stableDst; /* pledges that last 64KB decompressed data will remain available unmodified. This optimization skips storage operations in tmp buffers. */ + unsigned reserved[3]; /* must be set to zero for forward compatibility */ +} LZ4F_decompressOptions_t; + + +/* Resource management */ + +/*! LZ4F_createDecompressionContext() : + * Create an LZ4F_dctx object, to track all decompression operations. + * The version provided MUST be LZ4F_VERSION. + * The function provides a pointer to an allocated and initialized LZ4F_dctx object. + * The result is an errorCode, which can be tested using LZ4F_isError(). + * dctx memory can be released using LZ4F_freeDecompressionContext(); + * Result of LZ4F_freeDecompressionContext() indicates current state of decompressionContext when being released. + * That is, it should be == 0 if decompression has been completed fully and correctly. + */ +LZ4FLIB_API LZ4F_errorCode_t LZ4F_createDecompressionContext(LZ4F_dctx** dctxPtr, unsigned version); +LZ4FLIB_API LZ4F_errorCode_t LZ4F_freeDecompressionContext(LZ4F_dctx* dctx); + + +/*-*********************************** +* Streaming decompression functions +*************************************/ + +#define LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH 5 + +/*! LZ4F_headerSize() : v1.9.0+ + * Provide the header size of a frame starting at `src`. + * `srcSize` must be >= LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH, + * which is enough to decode the header length. + * @return : size of frame header + * or an error code, which can be tested using LZ4F_isError() + * note : Frame header size is variable, but is guaranteed to be + * >= LZ4F_HEADER_SIZE_MIN bytes, and <= LZ4F_HEADER_SIZE_MAX bytes. + */ +size_t LZ4F_headerSize(const void* src, size_t srcSize); + +/*! LZ4F_getFrameInfo() : + * This function extracts frame parameters (max blockSize, dictID, etc.). + * Its usage is optional: user can call LZ4F_decompress() directly. + * + * Extracted information will fill an existing LZ4F_frameInfo_t structure. + * This can be useful for allocation and dictionary identification purposes. + * + * LZ4F_getFrameInfo() can work in the following situations : + * + * 1) At the beginning of a new frame, before any invocation of LZ4F_decompress(). + * It will decode header from `srcBuffer`, + * consuming the header and starting the decoding process. + * + * Input size must be large enough to contain the full frame header. + * Frame header size can be known beforehand by LZ4F_headerSize(). + * Frame header size is variable, but is guaranteed to be >= LZ4F_HEADER_SIZE_MIN bytes, + * and not more than <= LZ4F_HEADER_SIZE_MAX bytes. + * Hence, blindly providing LZ4F_HEADER_SIZE_MAX bytes or more will always work. + * It's allowed to provide more input data than the header size, + * LZ4F_getFrameInfo() will only consume the header. + * + * If input size is not large enough, + * aka if it's smaller than header size, + * function will fail and return an error code. + * + * 2) After decoding has been started, + * it's possible to invoke LZ4F_getFrameInfo() anytime + * to extract already decoded frame parameters stored within dctx. + * + * Note that, if decoding has barely started, + * and not yet read enough information to decode the header, + * LZ4F_getFrameInfo() will fail. + * + * The number of bytes consumed from srcBuffer will be updated in *srcSizePtr (necessarily <= original value). + * LZ4F_getFrameInfo() only consumes bytes when decoding has not yet started, + * and when decoding the header has been successful. + * Decompression must then resume from (srcBuffer + *srcSizePtr). + * + * @return : a hint about how many srcSize bytes LZ4F_decompress() expects for next call, + * or an error code which can be tested using LZ4F_isError(). + * note 1 : in case of error, dctx is not modified. Decoding operation can resume from beginning safely. + * note 2 : frame parameters are *copied into* an already allocated LZ4F_frameInfo_t structure. + */ +LZ4FLIB_API size_t LZ4F_getFrameInfo(LZ4F_dctx* dctx, + LZ4F_frameInfo_t* frameInfoPtr, + const void* srcBuffer, size_t* srcSizePtr); + +/*! LZ4F_decompress() : + * Call this function repetitively to regenerate compressed data from `srcBuffer`. + * The function will read up to *srcSizePtr bytes from srcBuffer, + * and decompress data into dstBuffer, of capacity *dstSizePtr. + * + * The nb of bytes consumed from srcBuffer will be written into *srcSizePtr (necessarily <= original value). + * The nb of bytes decompressed into dstBuffer will be written into *dstSizePtr (necessarily <= original value). + * + * The function does not necessarily read all input bytes, so always check value in *srcSizePtr. + * Unconsumed source data must be presented again in subsequent invocations. + * + * `dstBuffer` can freely change between each consecutive function invocation. + * `dstBuffer` content will be overwritten. + * + * @return : an hint of how many `srcSize` bytes LZ4F_decompress() expects for next call. + * Schematically, it's the size of the current (or remaining) compressed block + header of next block. + * Respecting the hint provides some small speed benefit, because it skips intermediate buffers. + * This is just a hint though, it's always possible to provide any srcSize. + * + * When a frame is fully decoded, @return will be 0 (no more data expected). + * When provided with more bytes than necessary to decode a frame, + * LZ4F_decompress() will stop reading exactly at end of current frame, and @return 0. + * + * If decompression failed, @return is an error code, which can be tested using LZ4F_isError(). + * After a decompression error, the `dctx` context is not resumable. + * Use LZ4F_resetDecompressionContext() to return to clean state. + * + * After a frame is fully decoded, dctx can be used again to decompress another frame. + */ +LZ4FLIB_API size_t LZ4F_decompress(LZ4F_dctx* dctx, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const LZ4F_decompressOptions_t* dOptPtr); + + +/*! LZ4F_resetDecompressionContext() : added in v1.8.0 + * In case of an error, the context is left in "undefined" state. + * In which case, it's necessary to reset it, before re-using it. + * This method can also be used to abruptly stop any unfinished decompression, + * and start a new one using same context resources. */ +LZ4FLIB_API void LZ4F_resetDecompressionContext(LZ4F_dctx* dctx); /* always successful */ + + + +#if defined (__cplusplus) +} +#endif + +#endif /* LZ4F_H_09782039843 */ + +#if defined(LZ4F_STATIC_LINKING_ONLY) && !defined(LZ4F_H_STATIC_09782039843) +#define LZ4F_H_STATIC_09782039843 + +#if defined (__cplusplus) +extern "C" { +#endif + +/* These declarations are not stable and may change in the future. + * They are therefore only safe to depend on + * when the caller is statically linked against the library. + * To access their declarations, define LZ4F_STATIC_LINKING_ONLY. + * + * By default, these symbols aren't published into shared/dynamic libraries. + * You can override this behavior and force them to be published + * by defining LZ4F_PUBLISH_STATIC_FUNCTIONS. + * Use at your own risk. + */ +#ifdef LZ4F_PUBLISH_STATIC_FUNCTIONS +#define LZ4FLIB_STATIC_API LZ4FLIB_API +#else +#define LZ4FLIB_STATIC_API +#endif + + +/* --- Error List --- */ +#define LZ4F_LIST_ERRORS(ITEM) \ + ITEM(OK_NoError) \ + ITEM(ERROR_GENERIC) \ + ITEM(ERROR_maxBlockSize_invalid) \ + ITEM(ERROR_blockMode_invalid) \ + ITEM(ERROR_contentChecksumFlag_invalid) \ + ITEM(ERROR_compressionLevel_invalid) \ + ITEM(ERROR_headerVersion_wrong) \ + ITEM(ERROR_blockChecksum_invalid) \ + ITEM(ERROR_reservedFlag_set) \ + ITEM(ERROR_allocation_failed) \ + ITEM(ERROR_srcSize_tooLarge) \ + ITEM(ERROR_dstMaxSize_tooSmall) \ + ITEM(ERROR_frameHeader_incomplete) \ + ITEM(ERROR_frameType_unknown) \ + ITEM(ERROR_frameSize_wrong) \ + ITEM(ERROR_srcPtr_wrong) \ + ITEM(ERROR_decompressionFailed) \ + ITEM(ERROR_headerChecksum_invalid) \ + ITEM(ERROR_contentChecksum_invalid) \ + ITEM(ERROR_frameDecoding_alreadyStarted) \ + ITEM(ERROR_maxCode) + +#define LZ4F_GENERATE_ENUM(ENUM) LZ4F_##ENUM, + +/* enum list is exposed, to handle specific errors */ +typedef enum { LZ4F_LIST_ERRORS(LZ4F_GENERATE_ENUM) + _LZ4F_dummy_error_enum_for_c89_never_used } LZ4F_errorCodes; + +LZ4FLIB_STATIC_API LZ4F_errorCodes LZ4F_getErrorCode(size_t functionResult); + +LZ4FLIB_STATIC_API size_t LZ4F_getBlockSize(unsigned); + +/********************************** + * Bulk processing dictionary API + *********************************/ + +/* A Dictionary is useful for the compression of small messages (KB range). + * It dramatically improves compression efficiency. + * + * LZ4 can ingest any input as dictionary, though only the last 64 KB are useful. + * Best results are generally achieved by using Zstandard's Dictionary Builder + * to generate a high-quality dictionary from a set of samples. + * + * Loading a dictionary has a cost, since it involves construction of tables. + * The Bulk processing dictionary API makes it possible to share this cost + * over an arbitrary number of compression jobs, even concurrently, + * markedly improving compression latency for these cases. + * + * The same dictionary will have to be used on the decompression side + * for decoding to be successful. + * To help identify the correct dictionary at decoding stage, + * the frame header allows optional embedding of a dictID field. + */ +typedef struct LZ4F_CDict_s LZ4F_CDict; + +/*! LZ4_createCDict() : + * When compressing multiple messages / blocks using the same dictionary, it's recommended to load it just once. + * LZ4_createCDict() will create a digested dictionary, ready to start future compression operations without startup delay. + * LZ4_CDict can be created once and shared by multiple threads concurrently, since its usage is read-only. + * `dictBuffer` can be released after LZ4_CDict creation, since its content is copied within CDict */ +LZ4FLIB_STATIC_API LZ4F_CDict* LZ4F_createCDict(const void* dictBuffer, size_t dictSize); +LZ4FLIB_STATIC_API void LZ4F_freeCDict(LZ4F_CDict* CDict); + + +/*! LZ4_compressFrame_usingCDict() : + * Compress an entire srcBuffer into a valid LZ4 frame using a digested Dictionary. + * cctx must point to a context created by LZ4F_createCompressionContext(). + * If cdict==NULL, compress without a dictionary. + * dstBuffer MUST be >= LZ4F_compressFrameBound(srcSize, preferencesPtr). + * If this condition is not respected, function will fail (@return an errorCode). + * The LZ4F_preferences_t structure is optional : you may provide NULL as argument, + * but it's not recommended, as it's the only way to provide dictID in the frame header. + * @return : number of bytes written into dstBuffer. + * or an error code if it fails (can be tested using LZ4F_isError()) */ +LZ4FLIB_STATIC_API size_t LZ4F_compressFrame_usingCDict( + LZ4F_cctx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + const LZ4F_CDict* cdict, + const LZ4F_preferences_t* preferencesPtr); + + +/*! LZ4F_compressBegin_usingCDict() : + * Inits streaming dictionary compression, and writes the frame header into dstBuffer. + * dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. + * `prefsPtr` is optional : you may provide NULL as argument, + * however, it's the only way to provide dictID in the frame header. + * @return : number of bytes written into dstBuffer for the header, + * or an error code (which can be tested using LZ4F_isError()) */ +LZ4FLIB_STATIC_API size_t LZ4F_compressBegin_usingCDict( + LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_CDict* cdict, + const LZ4F_preferences_t* prefsPtr); + + +/*! LZ4F_decompress_usingDict() : + * Same as LZ4F_decompress(), using a predefined dictionary. + * Dictionary is used "in place", without any preprocessing. + * It must remain accessible throughout the entire frame decoding. */ +LZ4FLIB_STATIC_API size_t LZ4F_decompress_usingDict( + LZ4F_dctx* dctxPtr, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const void* dict, size_t dictSize, + const LZ4F_decompressOptions_t* decompressOptionsPtr); + +#if defined (__cplusplus) +} +#endif + +#endif /* defined(LZ4F_STATIC_LINKING_ONLY) && !defined(LZ4F_H_STATIC_09782039843) */ diff --git a/TMessagesProj/jni/lz4/lz4frame_static.h b/TMessagesProj/jni/lz4/lz4frame_static.h new file mode 100755 index 000000000..925a2c5c3 --- /dev/null +++ b/TMessagesProj/jni/lz4/lz4frame_static.h @@ -0,0 +1,47 @@ +/* + LZ4 auto-framing library + Header File for static linking only + Copyright (C) 2011-2016, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ + +#ifndef LZ4FRAME_STATIC_H_0398209384 +#define LZ4FRAME_STATIC_H_0398209384 + +/* The declarations that formerly were made here have been merged into + * lz4frame.h, protected by the LZ4F_STATIC_LINKING_ONLY macro. Going forward, + * it is recommended to simply include that header directly. + */ + +#define LZ4F_STATIC_LINKING_ONLY +#include "lz4frame.h" + +#endif /* LZ4FRAME_STATIC_H_0398209384 */ diff --git a/TMessagesProj/jni/lz4/lz4hc.c b/TMessagesProj/jni/lz4/lz4hc.c new file mode 100755 index 000000000..936f73969 --- /dev/null +++ b/TMessagesProj/jni/lz4/lz4hc.c @@ -0,0 +1,1476 @@ +/* + LZ4 HC - High Compression Mode of LZ4 + Copyright (C) 2011-2017, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ +/* note : lz4hc is not an independent module, it requires lz4.h/lz4.c for proper compilation */ + + +/* ************************************* +* Tuning Parameter +***************************************/ + +/*! HEAPMODE : + * Select how default compression function will allocate workplace memory, + * in stack (0:fastest), or in heap (1:requires malloc()). + * Since workplace is rather large, heap mode is recommended. + */ +#ifndef LZ4HC_HEAPMODE +# define LZ4HC_HEAPMODE 1 +#endif + + +/*=== Dependency ===*/ +#define LZ4_HC_STATIC_LINKING_ONLY +#include "lz4hc.h" + + +/*=== Common LZ4 definitions ===*/ +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wunused-function" +#endif +#if defined (__clang__) +# pragma clang diagnostic ignored "-Wunused-function" +#endif + +/*=== Enums ===*/ +typedef enum { noDictCtx, usingDictCtxHc } dictCtx_directive; + + +#define LZ4_COMMONDEFS_ONLY +#ifndef LZ4_SRC_INCLUDED +#include "lz4.c" /* LZ4_count, constants, mem */ +#endif + +/*=== Constants ===*/ +#define OPTIMAL_ML (int)((ML_MASK-1)+MINMATCH) +#define LZ4_OPT_NUM (1<<12) + + +/*=== Macros ===*/ +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) +#define MAX(a,b) ( (a) > (b) ? (a) : (b) ) +#define HASH_FUNCTION(i) (((i) * 2654435761U) >> ((MINMATCH*8)-LZ4HC_HASH_LOG)) +#define DELTANEXTMAXD(p) chainTable[(p) & LZ4HC_MAXD_MASK] /* flexible, LZ4HC_MAXD dependent */ +#define DELTANEXTU16(table, pos) table[(U16)(pos)] /* faster */ +/* Make fields passed to, and updated by LZ4HC_encodeSequence explicit */ +#define UPDATABLE(ip, op, anchor) &ip, &op, &anchor + +static U32 LZ4HC_hashPtr(const void* ptr) { return HASH_FUNCTION(LZ4_read32(ptr)); } + + +/************************************** +* HC Compression +**************************************/ +static void LZ4HC_clearTables (LZ4HC_CCtx_internal* hc4) +{ + MEM_INIT((void*)hc4->hashTable, 0, sizeof(hc4->hashTable)); + MEM_INIT(hc4->chainTable, 0xFF, sizeof(hc4->chainTable)); +} + +static void LZ4HC_init_internal (LZ4HC_CCtx_internal* hc4, const BYTE* start) +{ + uptrval startingOffset = (uptrval)(hc4->end - hc4->base); + if (startingOffset > 1 GB) { + LZ4HC_clearTables(hc4); + startingOffset = 0; + } + startingOffset += 64 KB; + hc4->nextToUpdate = (U32) startingOffset; + hc4->base = start - startingOffset; + hc4->end = start; + hc4->dictBase = start - startingOffset; + hc4->dictLimit = (U32) startingOffset; + hc4->lowLimit = (U32) startingOffset; +} + + +/* Update chains up to ip (excluded) */ +LZ4_FORCE_INLINE void LZ4HC_Insert (LZ4HC_CCtx_internal* hc4, const BYTE* ip) +{ + U16* const chainTable = hc4->chainTable; + U32* const hashTable = hc4->hashTable; + const BYTE* const base = hc4->base; + U32 const target = (U32)(ip - base); + U32 idx = hc4->nextToUpdate; + + while (idx < target) { + U32 const h = LZ4HC_hashPtr(base+idx); + size_t delta = idx - hashTable[h]; + if (delta>LZ4_DISTANCE_MAX) delta = LZ4_DISTANCE_MAX; + DELTANEXTU16(chainTable, idx) = (U16)delta; + hashTable[h] = idx; + idx++; + } + + hc4->nextToUpdate = target; +} + +/** LZ4HC_countBack() : + * @return : negative value, nb of common bytes before ip/match */ +LZ4_FORCE_INLINE +int LZ4HC_countBack(const BYTE* const ip, const BYTE* const match, + const BYTE* const iMin, const BYTE* const mMin) +{ + int back = 0; + int const min = (int)MAX(iMin - ip, mMin - match); + assert(min <= 0); + assert(ip >= iMin); assert((size_t)(ip-iMin) < (1U<<31)); + assert(match >= mMin); assert((size_t)(match - mMin) < (1U<<31)); + while ( (back > min) + && (ip[back-1] == match[back-1]) ) + back--; + return back; +} + +/* LZ4HC_countPattern() : + * pattern32 must be a sample of repetitive pattern of length 1, 2 or 4 (but not 3!) */ +static unsigned +LZ4HC_countPattern(const BYTE* ip, const BYTE* const iEnd, U32 const pattern32) +{ + const BYTE* const iStart = ip; + reg_t const pattern = (sizeof(pattern)==8) ? (reg_t)pattern32 + (((reg_t)pattern32) << 32) : pattern32; + + while (likely(ip < iEnd-(sizeof(pattern)-1))) { + reg_t const diff = LZ4_read_ARCH(ip) ^ pattern; + if (!diff) { ip+=sizeof(pattern); continue; } + ip += LZ4_NbCommonBytes(diff); + return (unsigned)(ip - iStart); + } + + if (LZ4_isLittleEndian()) { + reg_t patternByte = pattern; + while ((ip>= 8; + } + } else { /* big endian */ + U32 bitOffset = (sizeof(pattern)*8) - 8; + while (ip < iEnd) { + BYTE const byte = (BYTE)(pattern >> bitOffset); + if (*ip != byte) break; + ip ++; bitOffset -= 8; + } + } + + return (unsigned)(ip - iStart); +} + +/* LZ4HC_reverseCountPattern() : + * pattern must be a sample of repetitive pattern of length 1, 2 or 4 (but not 3!) + * read using natural platform endianess */ +static unsigned +LZ4HC_reverseCountPattern(const BYTE* ip, const BYTE* const iLow, U32 pattern) +{ + const BYTE* const iStart = ip; + + while (likely(ip >= iLow+4)) { + if (LZ4_read32(ip-4) != pattern) break; + ip -= 4; + } + { const BYTE* bytePtr = (const BYTE*)(&pattern) + 3; /* works for any endianess */ + while (likely(ip>iLow)) { + if (ip[-1] != *bytePtr) break; + ip--; bytePtr--; + } } + return (unsigned)(iStart - ip); +} + +typedef enum { rep_untested, rep_not, rep_confirmed } repeat_state_e; +typedef enum { favorCompressionRatio=0, favorDecompressionSpeed } HCfavor_e; + +LZ4_FORCE_INLINE int +LZ4HC_InsertAndGetWiderMatch ( + LZ4HC_CCtx_internal* hc4, + const BYTE* const ip, + const BYTE* const iLowLimit, + const BYTE* const iHighLimit, + int longest, + const BYTE** matchpos, + const BYTE** startpos, + const int maxNbAttempts, + const int patternAnalysis, + const int chainSwap, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed) +{ + U16* const chainTable = hc4->chainTable; + U32* const HashTable = hc4->hashTable; + const LZ4HC_CCtx_internal * const dictCtx = hc4->dictCtx; + const BYTE* const base = hc4->base; + const U32 dictLimit = hc4->dictLimit; + const BYTE* const lowPrefixPtr = base + dictLimit; + const U32 ipIndex = (U32)(ip - base); + const U32 lowestMatchIndex = (hc4->lowLimit + 64 KB > ipIndex) ? hc4->lowLimit : ipIndex - LZ4_DISTANCE_MAX; + const BYTE* const dictBase = hc4->dictBase; + int const lookBackLength = (int)(ip-iLowLimit); + int nbAttempts = maxNbAttempts; + U32 matchChainPos = 0; + U32 const pattern = LZ4_read32(ip); + U32 matchIndex; + repeat_state_e repeat = rep_untested; + size_t srcPatternLength = 0; + + DEBUGLOG(7, "LZ4HC_InsertAndGetWiderMatch"); + /* First Match */ + LZ4HC_Insert(hc4, ip); + matchIndex = HashTable[LZ4HC_hashPtr(ip)]; + DEBUGLOG(7, "First match at index %u / %u (lowestMatchIndex)", + matchIndex, lowestMatchIndex); + + while ((matchIndex>=lowestMatchIndex) && (nbAttempts)) { + int matchLength=0; + nbAttempts--; + assert(matchIndex < ipIndex); + if (favorDecSpeed && (ipIndex - matchIndex < 8)) { + /* do nothing */ + } else if (matchIndex >= dictLimit) { /* within current Prefix */ + const BYTE* const matchPtr = base + matchIndex; + assert(matchPtr >= lowPrefixPtr); + assert(matchPtr < ip); + assert(longest >= 1); + if (LZ4_read16(iLowLimit + longest - 1) == LZ4_read16(matchPtr - lookBackLength + longest - 1)) { + if (LZ4_read32(matchPtr) == pattern) { + int const back = lookBackLength ? LZ4HC_countBack(ip, matchPtr, iLowLimit, lowPrefixPtr) : 0; + matchLength = MINMATCH + (int)LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, iHighLimit); + matchLength -= back; + if (matchLength > longest) { + longest = matchLength; + *matchpos = matchPtr + back; + *startpos = ip + back; + } } } + } else { /* lowestMatchIndex <= matchIndex < dictLimit */ + const BYTE* const matchPtr = dictBase + matchIndex; + if (LZ4_read32(matchPtr) == pattern) { + const BYTE* const dictStart = dictBase + hc4->lowLimit; + int back = 0; + const BYTE* vLimit = ip + (dictLimit - matchIndex); + if (vLimit > iHighLimit) vLimit = iHighLimit; + matchLength = (int)LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, vLimit) + MINMATCH; + if ((ip+matchLength == vLimit) && (vLimit < iHighLimit)) + matchLength += LZ4_count(ip+matchLength, lowPrefixPtr, iHighLimit); + back = lookBackLength ? LZ4HC_countBack(ip, matchPtr, iLowLimit, dictStart) : 0; + matchLength -= back; + if (matchLength > longest) { + longest = matchLength; + *matchpos = base + matchIndex + back; /* virtual pos, relative to ip, to retrieve offset */ + *startpos = ip + back; + } } } + + if (chainSwap && matchLength==longest) { /* better match => select a better chain */ + assert(lookBackLength==0); /* search forward only */ + if (matchIndex + (U32)longest <= ipIndex) { + U32 distanceToNextMatch = 1; + int pos; + for (pos = 0; pos <= longest - MINMATCH; pos++) { + U32 const candidateDist = DELTANEXTU16(chainTable, matchIndex + (U32)pos); + if (candidateDist > distanceToNextMatch) { + distanceToNextMatch = candidateDist; + matchChainPos = (U32)pos; + } } + if (distanceToNextMatch > 1) { + if (distanceToNextMatch > matchIndex) break; /* avoid overflow */ + matchIndex -= distanceToNextMatch; + continue; + } } } + + { U32 const distNextMatch = DELTANEXTU16(chainTable, matchIndex); + if (patternAnalysis && distNextMatch==1 && matchChainPos==0) { + U32 const matchCandidateIdx = matchIndex-1; + /* may be a repeated pattern */ + if (repeat == rep_untested) { + if ( ((pattern & 0xFFFF) == (pattern >> 16)) + & ((pattern & 0xFF) == (pattern >> 24)) ) { + repeat = rep_confirmed; + srcPatternLength = LZ4HC_countPattern(ip+sizeof(pattern), iHighLimit, pattern) + sizeof(pattern); + } else { + repeat = rep_not; + } } + if ( (repeat == rep_confirmed) + && (matchCandidateIdx >= dictLimit) ) { /* same segment only */ + const BYTE* const matchPtr = base + matchCandidateIdx; + if (LZ4_read32(matchPtr) == pattern) { /* good candidate */ + size_t const forwardPatternLength = LZ4HC_countPattern(matchPtr+sizeof(pattern), iHighLimit, pattern) + sizeof(pattern); + const BYTE* const lowestMatchPtr = (lowPrefixPtr + LZ4_DISTANCE_MAX >= ip) ? lowPrefixPtr : ip - LZ4_DISTANCE_MAX; + size_t const backLength = LZ4HC_reverseCountPattern(matchPtr, lowestMatchPtr, pattern); + size_t const currentSegmentLength = backLength + forwardPatternLength; + + if ( (currentSegmentLength >= srcPatternLength) /* current pattern segment large enough to contain full srcPatternLength */ + && (forwardPatternLength <= srcPatternLength) ) { /* haven't reached this position yet */ + matchIndex = matchCandidateIdx + (U32)forwardPatternLength - (U32)srcPatternLength; /* best position, full pattern, might be followed by more match */ + } else { + matchIndex = matchCandidateIdx - (U32)backLength; /* farthest position in current segment, will find a match of length currentSegmentLength + maybe some back */ + if (lookBackLength==0) { /* no back possible */ + size_t const maxML = MIN(currentSegmentLength, srcPatternLength); + if ((size_t)longest < maxML) { + assert(base + matchIndex < ip); + if (ip - (base+matchIndex) > LZ4_DISTANCE_MAX) break; + assert(maxML < 2 GB); + longest = (int)maxML; + *matchpos = base + matchIndex; /* virtual pos, relative to ip, to retrieve offset */ + *startpos = ip; + } + { U32 const distToNextPattern = DELTANEXTU16(chainTable, matchIndex); + if (distToNextPattern > matchIndex) break; /* avoid overflow */ + matchIndex -= distToNextPattern; + } } } + continue; + } } + } } /* PA optimization */ + + /* follow current chain */ + matchIndex -= DELTANEXTU16(chainTable, matchIndex + matchChainPos); + + } /* while ((matchIndex>=lowestMatchIndex) && (nbAttempts)) */ + + if ( dict == usingDictCtxHc + && nbAttempts + && ipIndex - lowestMatchIndex < LZ4_DISTANCE_MAX) { + size_t const dictEndOffset = (size_t)(dictCtx->end - dictCtx->base); + U32 dictMatchIndex = dictCtx->hashTable[LZ4HC_hashPtr(ip)]; + assert(dictEndOffset <= 1 GB); + matchIndex = dictMatchIndex + lowestMatchIndex - (U32)dictEndOffset; + while (ipIndex - matchIndex <= LZ4_DISTANCE_MAX && nbAttempts--) { + const BYTE* const matchPtr = dictCtx->base + dictMatchIndex; + + if (LZ4_read32(matchPtr) == pattern) { + int mlt; + int back = 0; + const BYTE* vLimit = ip + (dictEndOffset - dictMatchIndex); + if (vLimit > iHighLimit) vLimit = iHighLimit; + mlt = (int)LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, vLimit) + MINMATCH; + back = lookBackLength ? LZ4HC_countBack(ip, matchPtr, iLowLimit, dictCtx->base + dictCtx->dictLimit) : 0; + mlt -= back; + if (mlt > longest) { + longest = mlt; + *matchpos = base + matchIndex + back; + *startpos = ip + back; + } } + + { U32 const nextOffset = DELTANEXTU16(dictCtx->chainTable, dictMatchIndex); + dictMatchIndex -= nextOffset; + matchIndex -= nextOffset; + } } } + + return longest; +} + +LZ4_FORCE_INLINE +int LZ4HC_InsertAndFindBestMatch(LZ4HC_CCtx_internal* const hc4, /* Index table will be updated */ + const BYTE* const ip, const BYTE* const iLimit, + const BYTE** matchpos, + const int maxNbAttempts, + const int patternAnalysis, + const dictCtx_directive dict) +{ + const BYTE* uselessPtr = ip; + /* note : LZ4HC_InsertAndGetWiderMatch() is able to modify the starting position of a match (*startpos), + * but this won't be the case here, as we define iLowLimit==ip, + * so LZ4HC_InsertAndGetWiderMatch() won't be allowed to search past ip */ + return LZ4HC_InsertAndGetWiderMatch(hc4, ip, ip, iLimit, MINMATCH-1, matchpos, &uselessPtr, maxNbAttempts, patternAnalysis, 0 /*chainSwap*/, dict, favorCompressionRatio); +} + +/* LZ4HC_encodeSequence() : + * @return : 0 if ok, + * 1 if buffer issue detected */ +LZ4_FORCE_INLINE int LZ4HC_encodeSequence ( + const BYTE** ip, + BYTE** op, + const BYTE** anchor, + int matchLength, + const BYTE* const match, + limitedOutput_directive limit, + BYTE* oend) +{ + size_t length; + BYTE* const token = (*op)++; + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG >= 6) + static const BYTE* start = NULL; + static U32 totalCost = 0; + U32 const pos = (start==NULL) ? 0 : (U32)(*anchor - start); + U32 const ll = (U32)(*ip - *anchor); + U32 const llAdd = (ll>=15) ? ((ll-15) / 255) + 1 : 0; + U32 const mlAdd = (matchLength>=19) ? ((matchLength-19) / 255) + 1 : 0; + U32 const cost = 1 + llAdd + ll + 2 + mlAdd; + if (start==NULL) start = *anchor; /* only works for single segment */ + /* g_debuglog_enable = (pos >= 2228) & (pos <= 2262); */ + DEBUGLOG(6, "pos:%7u -- literals:%3u, match:%4i, offset:%5u, cost:%3u + %u", + pos, + (U32)(*ip - *anchor), matchLength, (U32)(*ip-match), + cost, totalCost); + totalCost += cost; +#endif + + /* Encode Literal length */ + length = (size_t)(*ip - *anchor); + if ((limit) && ((*op + (length / 255) + length + (2 + 1 + LASTLITERALS)) > oend)) return 1; /* Check output limit */ + if (length >= RUN_MASK) { + size_t len = length - RUN_MASK; + *token = (RUN_MASK << ML_BITS); + for(; len >= 255 ; len -= 255) *(*op)++ = 255; + *(*op)++ = (BYTE)len; + } else { + *token = (BYTE)(length << ML_BITS); + } + + /* Copy Literals */ + LZ4_wildCopy8(*op, *anchor, (*op) + length); + *op += length; + + /* Encode Offset */ + assert( (*ip - match) <= LZ4_DISTANCE_MAX ); /* note : consider providing offset as a value, rather than as a pointer difference */ + LZ4_writeLE16(*op, (U16)(*ip-match)); *op += 2; + + /* Encode MatchLength */ + assert(matchLength >= MINMATCH); + length = (size_t)matchLength - MINMATCH; + if ((limit) && (*op + (length / 255) + (1 + LASTLITERALS) > oend)) return 1; /* Check output limit */ + if (length >= ML_MASK) { + *token += ML_MASK; + length -= ML_MASK; + for(; length >= 510 ; length -= 510) { *(*op)++ = 255; *(*op)++ = 255; } + if (length >= 255) { length -= 255; *(*op)++ = 255; } + *(*op)++ = (BYTE)length; + } else { + *token += (BYTE)(length); + } + + /* Prepare next loop */ + *ip += matchLength; + *anchor = *ip; + + return 0; +} + +LZ4_FORCE_INLINE int LZ4HC_compress_hashChain ( + LZ4HC_CCtx_internal* const ctx, + const char* const source, + char* const dest, + int* srcSizePtr, + int const maxOutputSize, + unsigned maxNbAttempts, + const limitedOutput_directive limit, + const dictCtx_directive dict + ) +{ + const int inputSize = *srcSizePtr; + const int patternAnalysis = (maxNbAttempts > 128); /* levels 9+ */ + + const BYTE* ip = (const BYTE*) source; + const BYTE* anchor = ip; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = (iend - LASTLITERALS); + + BYTE* optr = (BYTE*) dest; + BYTE* op = (BYTE*) dest; + BYTE* oend = op + maxOutputSize; + + int ml0, ml, ml2, ml3; + const BYTE* start0; + const BYTE* ref0; + const BYTE* ref = NULL; + const BYTE* start2 = NULL; + const BYTE* ref2 = NULL; + const BYTE* start3 = NULL; + const BYTE* ref3 = NULL; + + /* init */ + *srcSizePtr = 0; + if (limit == fillOutput) oend -= LASTLITERALS; /* Hack for support LZ4 format restriction */ + if (inputSize < LZ4_minLength) goto _last_literals; /* Input too small, no compression (all literals) */ + + /* Main Loop */ + while (ip <= mflimit) { + ml = LZ4HC_InsertAndFindBestMatch(ctx, ip, matchlimit, &ref, maxNbAttempts, patternAnalysis, dict); + if (ml encode ML1 */ + optr = op; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ref, limit, oend)) goto _dest_overflow; + continue; + } + + if (start0 < ip) { /* first match was skipped at least once */ + if (start2 < ip + ml0) { /* squeezing ML1 between ML0(original ML1) and ML2 */ + ip = start0; ref = ref0; ml = ml0; /* restore initial ML1 */ + } } + + /* Here, start0==ip */ + if ((start2 - ip) < 3) { /* First Match too small : removed */ + ml = ml2; + ip = start2; + ref =ref2; + goto _Search2; + } + +_Search3: + /* At this stage, we have : + * ml2 > ml1, and + * ip1+3 <= ip2 (usually < ip1+ml1) */ + if ((start2 - ip) < OPTIMAL_ML) { + int correction; + int new_ml = ml; + if (new_ml > OPTIMAL_ML) new_ml = OPTIMAL_ML; + if (ip+new_ml > start2 + ml2 - MINMATCH) new_ml = (int)(start2 - ip) + ml2 - MINMATCH; + correction = new_ml - (int)(start2 - ip); + if (correction > 0) { + start2 += correction; + ref2 += correction; + ml2 -= correction; + } + } + /* Now, we have start2 = ip+new_ml, with new_ml = min(ml, OPTIMAL_ML=18) */ + + if (start2 + ml2 <= mflimit) { + ml3 = LZ4HC_InsertAndGetWiderMatch(ctx, + start2 + ml2 - 3, start2, matchlimit, ml2, &ref3, &start3, + maxNbAttempts, patternAnalysis, 0, dict, favorCompressionRatio); + } else { + ml3 = ml2; + } + + if (ml3 == ml2) { /* No better match => encode ML1 and ML2 */ + /* ip & ref are known; Now for ml */ + if (start2 < ip+ml) ml = (int)(start2 - ip); + /* Now, encode 2 sequences */ + optr = op; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ref, limit, oend)) goto _dest_overflow; + ip = start2; + optr = op; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml2, ref2, limit, oend)) goto _dest_overflow; + continue; + } + + if (start3 < ip+ml+3) { /* Not enough space for match 2 : remove it */ + if (start3 >= (ip+ml)) { /* can write Seq1 immediately ==> Seq2 is removed, so Seq3 becomes Seq1 */ + if (start2 < ip+ml) { + int correction = (int)(ip+ml - start2); + start2 += correction; + ref2 += correction; + ml2 -= correction; + if (ml2 < MINMATCH) { + start2 = start3; + ref2 = ref3; + ml2 = ml3; + } + } + + optr = op; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ref, limit, oend)) goto _dest_overflow; + ip = start3; + ref = ref3; + ml = ml3; + + start0 = start2; + ref0 = ref2; + ml0 = ml2; + goto _Search2; + } + + start2 = start3; + ref2 = ref3; + ml2 = ml3; + goto _Search3; + } + + /* + * OK, now we have 3 ascending matches; + * let's write the first one ML1. + * ip & ref are known; Now decide ml. + */ + if (start2 < ip+ml) { + if ((start2 - ip) < OPTIMAL_ML) { + int correction; + if (ml > OPTIMAL_ML) ml = OPTIMAL_ML; + if (ip + ml > start2 + ml2 - MINMATCH) ml = (int)(start2 - ip) + ml2 - MINMATCH; + correction = ml - (int)(start2 - ip); + if (correction > 0) { + start2 += correction; + ref2 += correction; + ml2 -= correction; + } + } else { + ml = (int)(start2 - ip); + } + } + optr = op; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ref, limit, oend)) goto _dest_overflow; + + /* ML2 becomes ML1 */ + ip = start2; ref = ref2; ml = ml2; + + /* ML3 becomes ML2 */ + start2 = start3; ref2 = ref3; ml2 = ml3; + + /* let's find a new ML3 */ + goto _Search3; + } + +_last_literals: + /* Encode Last Literals */ + { size_t lastRunSize = (size_t)(iend - anchor); /* literals */ + size_t litLength = (lastRunSize + 255 - RUN_MASK) / 255; + size_t const totalSize = 1 + litLength + lastRunSize; + if (limit == fillOutput) oend += LASTLITERALS; /* restore correct value */ + if (limit && (op + totalSize > oend)) { + if (limit == limitedOutput) return 0; /* Check output limit */ + /* adapt lastRunSize to fill 'dest' */ + lastRunSize = (size_t)(oend - op) - 1; + litLength = (lastRunSize + 255 - RUN_MASK) / 255; + lastRunSize -= litLength; + } + ip = anchor + lastRunSize; + + if (lastRunSize >= RUN_MASK) { + size_t accumulator = lastRunSize - RUN_MASK; + *op++ = (RUN_MASK << ML_BITS); + for(; accumulator >= 255 ; accumulator -= 255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRunSize << ML_BITS); + } + memcpy(op, anchor, lastRunSize); + op += lastRunSize; + } + + /* End */ + *srcSizePtr = (int) (((const char*)ip) - source); + return (int) (((char*)op)-dest); + +_dest_overflow: + if (limit == fillOutput) { + op = optr; /* restore correct out pointer */ + goto _last_literals; + } + return 0; +} + + +static int LZ4HC_compress_optimal( LZ4HC_CCtx_internal* ctx, + const char* const source, char* dst, + int* srcSizePtr, int dstCapacity, + int const nbSearches, size_t sufficient_len, + const limitedOutput_directive limit, int const fullUpdate, + const dictCtx_directive dict, + HCfavor_e favorDecSpeed); + + +LZ4_FORCE_INLINE int LZ4HC_compress_generic_internal ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + const limitedOutput_directive limit, + const dictCtx_directive dict + ) +{ + typedef enum { lz4hc, lz4opt } lz4hc_strat_e; + typedef struct { + lz4hc_strat_e strat; + U32 nbSearches; + U32 targetLength; + } cParams_t; + static const cParams_t clTable[LZ4HC_CLEVEL_MAX+1] = { + { lz4hc, 2, 16 }, /* 0, unused */ + { lz4hc, 2, 16 }, /* 1, unused */ + { lz4hc, 2, 16 }, /* 2, unused */ + { lz4hc, 4, 16 }, /* 3 */ + { lz4hc, 8, 16 }, /* 4 */ + { lz4hc, 16, 16 }, /* 5 */ + { lz4hc, 32, 16 }, /* 6 */ + { lz4hc, 64, 16 }, /* 7 */ + { lz4hc, 128, 16 }, /* 8 */ + { lz4hc, 256, 16 }, /* 9 */ + { lz4opt, 96, 64 }, /*10==LZ4HC_CLEVEL_OPT_MIN*/ + { lz4opt, 512,128 }, /*11 */ + { lz4opt,16384,LZ4_OPT_NUM }, /* 12==LZ4HC_CLEVEL_MAX */ + }; + + DEBUGLOG(4, "LZ4HC_compress_generic(ctx=%p, src=%p, srcSize=%d)", ctx, src, *srcSizePtr); + + if (limit == fillOutput && dstCapacity < 1) return 0; /* Impossible to store anything */ + if ((U32)*srcSizePtr > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported input size (too large or negative) */ + + ctx->end += *srcSizePtr; + if (cLevel < 1) cLevel = LZ4HC_CLEVEL_DEFAULT; /* note : convention is different from lz4frame, maybe something to review */ + cLevel = MIN(LZ4HC_CLEVEL_MAX, cLevel); + { cParams_t const cParam = clTable[cLevel]; + HCfavor_e const favor = ctx->favorDecSpeed ? favorDecompressionSpeed : favorCompressionRatio; + int result; + + if (cParam.strat == lz4hc) { + result = LZ4HC_compress_hashChain(ctx, + src, dst, srcSizePtr, dstCapacity, + cParam.nbSearches, limit, dict); + } else { + assert(cParam.strat == lz4opt); + result = LZ4HC_compress_optimal(ctx, + src, dst, srcSizePtr, dstCapacity, + (int)cParam.nbSearches, cParam.targetLength, limit, + cLevel == LZ4HC_CLEVEL_MAX, /* ultra mode */ + dict, favor); + } + if (result <= 0) ctx->dirty = 1; + return result; + } +} + +static void LZ4HC_setExternalDict(LZ4HC_CCtx_internal* ctxPtr, const BYTE* newBlock); + +static int +LZ4HC_compress_generic_noDictCtx ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + limitedOutput_directive limit + ) +{ + assert(ctx->dictCtx == NULL); + return LZ4HC_compress_generic_internal(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit, noDictCtx); +} + +static int +LZ4HC_compress_generic_dictCtx ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + limitedOutput_directive limit + ) +{ + const size_t position = (size_t)(ctx->end - ctx->base) - ctx->lowLimit; + assert(ctx->dictCtx != NULL); + if (position >= 64 KB) { + ctx->dictCtx = NULL; + return LZ4HC_compress_generic_noDictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } else if (position == 0 && *srcSizePtr > 4 KB) { + memcpy(ctx, ctx->dictCtx, sizeof(LZ4HC_CCtx_internal)); + LZ4HC_setExternalDict(ctx, (const BYTE *)src); + ctx->compressionLevel = (short)cLevel; + return LZ4HC_compress_generic_noDictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } else { + return LZ4HC_compress_generic_internal(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit, usingDictCtxHc); + } +} + +static int +LZ4HC_compress_generic ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + limitedOutput_directive limit + ) +{ + if (ctx->dictCtx == NULL) { + return LZ4HC_compress_generic_noDictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } else { + return LZ4HC_compress_generic_dictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } +} + + +int LZ4_sizeofStateHC(void) { return (int)sizeof(LZ4_streamHC_t); } + +#ifndef _MSC_VER /* for some reason, Visual fails the aligment test on 32-bit x86 : + * it reports an aligment of 8-bytes, + * while actually aligning LZ4_streamHC_t on 4 bytes. */ +static size_t LZ4_streamHC_t_alignment(void) +{ + struct { char c; LZ4_streamHC_t t; } t_a; + return sizeof(t_a) - sizeof(t_a.t); +} +#endif + +/* state is presumed correctly initialized, + * in which case its size and alignment have already been validate */ +int LZ4_compress_HC_extStateHC_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) +{ + LZ4HC_CCtx_internal* const ctx = &((LZ4_streamHC_t*)state)->internal_donotuse; +#ifndef _MSC_VER /* for some reason, Visual fails the aligment test on 32-bit x86 : + * it reports an aligment of 8-bytes, + * while actually aligning LZ4_streamHC_t on 4 bytes. */ + assert(((size_t)state & (LZ4_streamHC_t_alignment() - 1)) == 0); /* check alignment */ +#endif + if (((size_t)(state)&(sizeof(void*)-1)) != 0) return 0; /* Error : state is not aligned for pointers (32 or 64 bits) */ + LZ4_resetStreamHC_fast((LZ4_streamHC_t*)state, compressionLevel); + LZ4HC_init_internal (ctx, (const BYTE*)src); + if (dstCapacity < LZ4_compressBound(srcSize)) + return LZ4HC_compress_generic (ctx, src, dst, &srcSize, dstCapacity, compressionLevel, limitedOutput); + else + return LZ4HC_compress_generic (ctx, src, dst, &srcSize, dstCapacity, compressionLevel, notLimited); +} + +int LZ4_compress_HC_extStateHC (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) +{ + LZ4_streamHC_t* const ctx = LZ4_initStreamHC(state, sizeof(*ctx)); + if (ctx==NULL) return 0; /* init failure */ + return LZ4_compress_HC_extStateHC_fastReset(state, src, dst, srcSize, dstCapacity, compressionLevel); +} + +int LZ4_compress_HC(const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) +{ +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + LZ4_streamHC_t* const statePtr = (LZ4_streamHC_t*)ALLOC(sizeof(LZ4_streamHC_t)); +#else + LZ4_streamHC_t state; + LZ4_streamHC_t* const statePtr = &state; +#endif + int const cSize = LZ4_compress_HC_extStateHC(statePtr, src, dst, srcSize, dstCapacity, compressionLevel); +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + FREEMEM(statePtr); +#endif + return cSize; +} + +/* state is presumed sized correctly (>= sizeof(LZ4_streamHC_t)) */ +int LZ4_compress_HC_destSize(void* state, const char* source, char* dest, int* sourceSizePtr, int targetDestSize, int cLevel) +{ + LZ4_streamHC_t* const ctx = LZ4_initStreamHC(state, sizeof(*ctx)); + if (ctx==NULL) return 0; /* init failure */ + LZ4HC_init_internal(&ctx->internal_donotuse, (const BYTE*) source); + LZ4_setCompressionLevel(ctx, cLevel); + return LZ4HC_compress_generic(&ctx->internal_donotuse, source, dest, sourceSizePtr, targetDestSize, cLevel, fillOutput); +} + + + +/************************************** +* Streaming Functions +**************************************/ +/* allocation */ +LZ4_streamHC_t* LZ4_createStreamHC(void) +{ + LZ4_streamHC_t* const LZ4_streamHCPtr = (LZ4_streamHC_t*)ALLOC(sizeof(LZ4_streamHC_t)); + if (LZ4_streamHCPtr==NULL) return NULL; + LZ4_initStreamHC(LZ4_streamHCPtr, sizeof(*LZ4_streamHCPtr)); /* full initialization, malloc'ed buffer can be full of garbage */ + return LZ4_streamHCPtr; +} + +int LZ4_freeStreamHC (LZ4_streamHC_t* LZ4_streamHCPtr) +{ + DEBUGLOG(4, "LZ4_freeStreamHC(%p)", LZ4_streamHCPtr); + if (!LZ4_streamHCPtr) return 0; /* support free on NULL */ + FREEMEM(LZ4_streamHCPtr); + return 0; +} + + +LZ4_streamHC_t* LZ4_initStreamHC (void* buffer, size_t size) +{ + LZ4_streamHC_t* const LZ4_streamHCPtr = (LZ4_streamHC_t*)buffer; + if (buffer == NULL) return NULL; + if (size < sizeof(LZ4_streamHC_t)) return NULL; +#ifndef _MSC_VER /* for some reason, Visual fails the aligment test on 32-bit x86 : + * it reports an aligment of 8-bytes, + * while actually aligning LZ4_streamHC_t on 4 bytes. */ + if (((size_t)buffer) & (LZ4_streamHC_t_alignment() - 1)) return NULL; /* alignment check */ +#endif + /* if compilation fails here, LZ4_STREAMHCSIZE must be increased */ + LZ4_STATIC_ASSERT(sizeof(LZ4HC_CCtx_internal) <= LZ4_STREAMHCSIZE); + DEBUGLOG(4, "LZ4_initStreamHC(%p, %u)", LZ4_streamHCPtr, (unsigned)size); + /* end-base will trigger a clearTable on starting compression */ + LZ4_streamHCPtr->internal_donotuse.end = (const BYTE *)(ptrdiff_t)-1; + LZ4_streamHCPtr->internal_donotuse.base = NULL; + LZ4_streamHCPtr->internal_donotuse.dictCtx = NULL; + LZ4_streamHCPtr->internal_donotuse.favorDecSpeed = 0; + LZ4_streamHCPtr->internal_donotuse.dirty = 0; + LZ4_setCompressionLevel(LZ4_streamHCPtr, LZ4HC_CLEVEL_DEFAULT); + return LZ4_streamHCPtr; +} + +/* just a stub */ +void LZ4_resetStreamHC (LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel) +{ + LZ4_initStreamHC(LZ4_streamHCPtr, sizeof(*LZ4_streamHCPtr)); + LZ4_setCompressionLevel(LZ4_streamHCPtr, compressionLevel); +} + +void LZ4_resetStreamHC_fast (LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel) +{ + DEBUGLOG(4, "LZ4_resetStreamHC_fast(%p, %d)", LZ4_streamHCPtr, compressionLevel); + if (LZ4_streamHCPtr->internal_donotuse.dirty) { + LZ4_initStreamHC(LZ4_streamHCPtr, sizeof(*LZ4_streamHCPtr)); + } else { + /* preserve end - base : can trigger clearTable's threshold */ + LZ4_streamHCPtr->internal_donotuse.end -= (uptrval)LZ4_streamHCPtr->internal_donotuse.base; + LZ4_streamHCPtr->internal_donotuse.base = NULL; + LZ4_streamHCPtr->internal_donotuse.dictCtx = NULL; + } + LZ4_setCompressionLevel(LZ4_streamHCPtr, compressionLevel); +} + +void LZ4_setCompressionLevel(LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel) +{ + DEBUGLOG(5, "LZ4_setCompressionLevel(%p, %d)", LZ4_streamHCPtr, compressionLevel); + if (compressionLevel < 1) compressionLevel = LZ4HC_CLEVEL_DEFAULT; + if (compressionLevel > LZ4HC_CLEVEL_MAX) compressionLevel = LZ4HC_CLEVEL_MAX; + LZ4_streamHCPtr->internal_donotuse.compressionLevel = (short)compressionLevel; +} + +void LZ4_favorDecompressionSpeed(LZ4_streamHC_t* LZ4_streamHCPtr, int favor) +{ + LZ4_streamHCPtr->internal_donotuse.favorDecSpeed = (favor!=0); +} + +/* LZ4_loadDictHC() : + * LZ4_streamHCPtr is presumed properly initialized */ +int LZ4_loadDictHC (LZ4_streamHC_t* LZ4_streamHCPtr, + const char* dictionary, int dictSize) +{ + LZ4HC_CCtx_internal* const ctxPtr = &LZ4_streamHCPtr->internal_donotuse; + DEBUGLOG(4, "LZ4_loadDictHC(%p, %p, %d)", LZ4_streamHCPtr, dictionary, dictSize); + assert(LZ4_streamHCPtr != NULL); + if (dictSize > 64 KB) { + dictionary += (size_t)dictSize - 64 KB; + dictSize = 64 KB; + } + /* need a full initialization, there are bad side-effects when using resetFast() */ + { int const cLevel = ctxPtr->compressionLevel; + LZ4_initStreamHC(LZ4_streamHCPtr, sizeof(*LZ4_streamHCPtr)); + LZ4_setCompressionLevel(LZ4_streamHCPtr, cLevel); + } + LZ4HC_init_internal (ctxPtr, (const BYTE*)dictionary); + ctxPtr->end = (const BYTE*)dictionary + dictSize; + if (dictSize >= 4) LZ4HC_Insert (ctxPtr, ctxPtr->end-3); + return dictSize; +} + +void LZ4_attach_HC_dictionary(LZ4_streamHC_t *working_stream, const LZ4_streamHC_t *dictionary_stream) { + working_stream->internal_donotuse.dictCtx = dictionary_stream != NULL ? &(dictionary_stream->internal_donotuse) : NULL; +} + +/* compression */ + +static void LZ4HC_setExternalDict(LZ4HC_CCtx_internal* ctxPtr, const BYTE* newBlock) +{ + DEBUGLOG(4, "LZ4HC_setExternalDict(%p, %p)", ctxPtr, newBlock); + if (ctxPtr->end >= ctxPtr->base + ctxPtr->dictLimit + 4) + LZ4HC_Insert (ctxPtr, ctxPtr->end-3); /* Referencing remaining dictionary content */ + + /* Only one memory segment for extDict, so any previous extDict is lost at this stage */ + ctxPtr->lowLimit = ctxPtr->dictLimit; + ctxPtr->dictLimit = (U32)(ctxPtr->end - ctxPtr->base); + ctxPtr->dictBase = ctxPtr->base; + ctxPtr->base = newBlock - ctxPtr->dictLimit; + ctxPtr->end = newBlock; + ctxPtr->nextToUpdate = ctxPtr->dictLimit; /* match referencing will resume from there */ +} + +static int LZ4_compressHC_continue_generic (LZ4_streamHC_t* LZ4_streamHCPtr, + const char* src, char* dst, + int* srcSizePtr, int dstCapacity, + limitedOutput_directive limit) +{ + LZ4HC_CCtx_internal* const ctxPtr = &LZ4_streamHCPtr->internal_donotuse; + DEBUGLOG(4, "LZ4_compressHC_continue_generic(ctx=%p, src=%p, srcSize=%d)", + LZ4_streamHCPtr, src, *srcSizePtr); + assert(ctxPtr != NULL); + /* auto-init if forgotten */ + if (ctxPtr->base == NULL) LZ4HC_init_internal (ctxPtr, (const BYTE*) src); + + /* Check overflow */ + if ((size_t)(ctxPtr->end - ctxPtr->base) > 2 GB) { + size_t dictSize = (size_t)(ctxPtr->end - ctxPtr->base) - ctxPtr->dictLimit; + if (dictSize > 64 KB) dictSize = 64 KB; + LZ4_loadDictHC(LZ4_streamHCPtr, (const char*)(ctxPtr->end) - dictSize, (int)dictSize); + } + + /* Check if blocks follow each other */ + if ((const BYTE*)src != ctxPtr->end) + LZ4HC_setExternalDict(ctxPtr, (const BYTE*)src); + + /* Check overlapping input/dictionary space */ + { const BYTE* sourceEnd = (const BYTE*) src + *srcSizePtr; + const BYTE* const dictBegin = ctxPtr->dictBase + ctxPtr->lowLimit; + const BYTE* const dictEnd = ctxPtr->dictBase + ctxPtr->dictLimit; + if ((sourceEnd > dictBegin) && ((const BYTE*)src < dictEnd)) { + if (sourceEnd > dictEnd) sourceEnd = dictEnd; + ctxPtr->lowLimit = (U32)(sourceEnd - ctxPtr->dictBase); + if (ctxPtr->dictLimit - ctxPtr->lowLimit < 4) ctxPtr->lowLimit = ctxPtr->dictLimit; + } + } + + return LZ4HC_compress_generic (ctxPtr, src, dst, srcSizePtr, dstCapacity, ctxPtr->compressionLevel, limit); +} + +int LZ4_compress_HC_continue (LZ4_streamHC_t* LZ4_streamHCPtr, const char* src, char* dst, int srcSize, int dstCapacity) +{ + if (dstCapacity < LZ4_compressBound(srcSize)) + return LZ4_compressHC_continue_generic (LZ4_streamHCPtr, src, dst, &srcSize, dstCapacity, limitedOutput); + else + return LZ4_compressHC_continue_generic (LZ4_streamHCPtr, src, dst, &srcSize, dstCapacity, notLimited); +} + +int LZ4_compress_HC_continue_destSize (LZ4_streamHC_t* LZ4_streamHCPtr, const char* src, char* dst, int* srcSizePtr, int targetDestSize) +{ + return LZ4_compressHC_continue_generic(LZ4_streamHCPtr, src, dst, srcSizePtr, targetDestSize, fillOutput); +} + + + +/* dictionary saving */ + +int LZ4_saveDictHC (LZ4_streamHC_t* LZ4_streamHCPtr, char* safeBuffer, int dictSize) +{ + LZ4HC_CCtx_internal* const streamPtr = &LZ4_streamHCPtr->internal_donotuse; + int const prefixSize = (int)(streamPtr->end - (streamPtr->base + streamPtr->dictLimit)); + DEBUGLOG(4, "LZ4_saveDictHC(%p, %p, %d)", LZ4_streamHCPtr, safeBuffer, dictSize); + if (dictSize > 64 KB) dictSize = 64 KB; + if (dictSize < 4) dictSize = 0; + if (dictSize > prefixSize) dictSize = prefixSize; + memmove(safeBuffer, streamPtr->end - dictSize, dictSize); + { U32 const endIndex = (U32)(streamPtr->end - streamPtr->base); + streamPtr->end = (const BYTE*)safeBuffer + dictSize; + streamPtr->base = streamPtr->end - endIndex; + streamPtr->dictLimit = endIndex - (U32)dictSize; + streamPtr->lowLimit = endIndex - (U32)dictSize; + if (streamPtr->nextToUpdate < streamPtr->dictLimit) streamPtr->nextToUpdate = streamPtr->dictLimit; + } + return dictSize; +} + + +/*************************************************** +* Deprecated Functions +***************************************************/ + +/* These functions currently generate deprecation warnings */ + +/* Wrappers for deprecated compression functions */ +int LZ4_compressHC(const char* src, char* dst, int srcSize) { return LZ4_compress_HC (src, dst, srcSize, LZ4_compressBound(srcSize), 0); } +int LZ4_compressHC_limitedOutput(const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_HC(src, dst, srcSize, maxDstSize, 0); } +int LZ4_compressHC2(const char* src, char* dst, int srcSize, int cLevel) { return LZ4_compress_HC (src, dst, srcSize, LZ4_compressBound(srcSize), cLevel); } +int LZ4_compressHC2_limitedOutput(const char* src, char* dst, int srcSize, int maxDstSize, int cLevel) { return LZ4_compress_HC(src, dst, srcSize, maxDstSize, cLevel); } +int LZ4_compressHC_withStateHC (void* state, const char* src, char* dst, int srcSize) { return LZ4_compress_HC_extStateHC (state, src, dst, srcSize, LZ4_compressBound(srcSize), 0); } +int LZ4_compressHC_limitedOutput_withStateHC (void* state, const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_HC_extStateHC (state, src, dst, srcSize, maxDstSize, 0); } +int LZ4_compressHC2_withStateHC (void* state, const char* src, char* dst, int srcSize, int cLevel) { return LZ4_compress_HC_extStateHC(state, src, dst, srcSize, LZ4_compressBound(srcSize), cLevel); } +int LZ4_compressHC2_limitedOutput_withStateHC (void* state, const char* src, char* dst, int srcSize, int maxDstSize, int cLevel) { return LZ4_compress_HC_extStateHC(state, src, dst, srcSize, maxDstSize, cLevel); } +int LZ4_compressHC_continue (LZ4_streamHC_t* ctx, const char* src, char* dst, int srcSize) { return LZ4_compress_HC_continue (ctx, src, dst, srcSize, LZ4_compressBound(srcSize)); } +int LZ4_compressHC_limitedOutput_continue (LZ4_streamHC_t* ctx, const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_HC_continue (ctx, src, dst, srcSize, maxDstSize); } + + +/* Deprecated streaming functions */ +int LZ4_sizeofStreamStateHC(void) { return LZ4_STREAMHCSIZE; } + +/* state is presumed correctly sized, aka >= sizeof(LZ4_streamHC_t) + * @return : 0 on success, !=0 if error */ +int LZ4_resetStreamStateHC(void* state, char* inputBuffer) +{ + LZ4_streamHC_t* const hc4 = LZ4_initStreamHC(state, sizeof(*hc4)); + if (hc4 == NULL) return 1; /* init failed */ + LZ4HC_init_internal (&hc4->internal_donotuse, (const BYTE*)inputBuffer); + return 0; +} + +void* LZ4_createHC (const char* inputBuffer) +{ + LZ4_streamHC_t* const hc4 = LZ4_createStreamHC(); + if (hc4 == NULL) return NULL; /* not enough memory */ + LZ4HC_init_internal (&hc4->internal_donotuse, (const BYTE*)inputBuffer); + return hc4; +} + +int LZ4_freeHC (void* LZ4HC_Data) +{ + if (!LZ4HC_Data) return 0; /* support free on NULL */ + FREEMEM(LZ4HC_Data); + return 0; +} + +int LZ4_compressHC2_continue (void* LZ4HC_Data, const char* src, char* dst, int srcSize, int cLevel) +{ + return LZ4HC_compress_generic (&((LZ4_streamHC_t*)LZ4HC_Data)->internal_donotuse, src, dst, &srcSize, 0, cLevel, notLimited); +} + +int LZ4_compressHC2_limitedOutput_continue (void* LZ4HC_Data, const char* src, char* dst, int srcSize, int dstCapacity, int cLevel) +{ + return LZ4HC_compress_generic (&((LZ4_streamHC_t*)LZ4HC_Data)->internal_donotuse, src, dst, &srcSize, dstCapacity, cLevel, limitedOutput); +} + +char* LZ4_slideInputBufferHC(void* LZ4HC_Data) +{ + LZ4_streamHC_t *ctx = (LZ4_streamHC_t*)LZ4HC_Data; + const BYTE *bufferStart = ctx->internal_donotuse.base + ctx->internal_donotuse.lowLimit; + LZ4_resetStreamHC_fast(ctx, ctx->internal_donotuse.compressionLevel); + /* avoid const char * -> char * conversion warning :( */ + return (char *)(uptrval)bufferStart; +} + + +/* ================================================ + * LZ4 Optimal parser (levels [LZ4HC_CLEVEL_OPT_MIN - LZ4HC_CLEVEL_MAX]) + * ===============================================*/ +typedef struct { + int price; + int off; + int mlen; + int litlen; +} LZ4HC_optimal_t; + +/* price in bytes */ +LZ4_FORCE_INLINE int LZ4HC_literalsPrice(int const litlen) +{ + int price = litlen; + assert(litlen >= 0); + if (litlen >= (int)RUN_MASK) + price += 1 + ((litlen-(int)RUN_MASK) / 255); + return price; +} + + +/* requires mlen >= MINMATCH */ +LZ4_FORCE_INLINE int LZ4HC_sequencePrice(int litlen, int mlen) +{ + int price = 1 + 2 ; /* token + 16-bit offset */ + assert(litlen >= 0); + assert(mlen >= MINMATCH); + + price += LZ4HC_literalsPrice(litlen); + + if (mlen >= (int)(ML_MASK+MINMATCH)) + price += 1 + ((mlen-(int)(ML_MASK+MINMATCH)) / 255); + + return price; +} + + +typedef struct { + int off; + int len; +} LZ4HC_match_t; + +LZ4_FORCE_INLINE LZ4HC_match_t +LZ4HC_FindLongerMatch(LZ4HC_CCtx_internal* const ctx, + const BYTE* ip, const BYTE* const iHighLimit, + int minLen, int nbSearches, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed) +{ + LZ4HC_match_t match = { 0 , 0 }; + const BYTE* matchPtr = NULL; + /* note : LZ4HC_InsertAndGetWiderMatch() is able to modify the starting position of a match (*startpos), + * but this won't be the case here, as we define iLowLimit==ip, + * so LZ4HC_InsertAndGetWiderMatch() won't be allowed to search past ip */ + int matchLength = LZ4HC_InsertAndGetWiderMatch(ctx, ip, ip, iHighLimit, minLen, &matchPtr, &ip, nbSearches, 1 /*patternAnalysis*/, 1 /*chainSwap*/, dict, favorDecSpeed); + if (matchLength <= minLen) return match; + if (favorDecSpeed) { + if ((matchLength>18) & (matchLength<=36)) matchLength=18; /* favor shortcut */ + } + match.len = matchLength; + match.off = (int)(ip-matchPtr); + return match; +} + + +static int LZ4HC_compress_optimal ( LZ4HC_CCtx_internal* ctx, + const char* const source, + char* dst, + int* srcSizePtr, + int dstCapacity, + int const nbSearches, + size_t sufficient_len, + const limitedOutput_directive limit, + int const fullUpdate, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed) +{ +#define TRAILING_LITERALS 3 + LZ4HC_optimal_t opt[LZ4_OPT_NUM + TRAILING_LITERALS]; /* ~64 KB, which is a bit large for stack... */ + + const BYTE* ip = (const BYTE*) source; + const BYTE* anchor = ip; + const BYTE* const iend = ip + *srcSizePtr; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = iend - LASTLITERALS; + BYTE* op = (BYTE*) dst; + BYTE* opSaved = (BYTE*) dst; + BYTE* oend = op + dstCapacity; + + /* init */ + DEBUGLOG(5, "LZ4HC_compress_optimal(dst=%p, dstCapa=%u)", dst, (unsigned)dstCapacity); + *srcSizePtr = 0; + if (limit == fillOutput) oend -= LASTLITERALS; /* Hack for support LZ4 format restriction */ + if (sufficient_len >= LZ4_OPT_NUM) sufficient_len = LZ4_OPT_NUM-1; + + /* Main Loop */ + assert(ip - anchor < LZ4_MAX_INPUT_SIZE); + while (ip <= mflimit) { + int const llen = (int)(ip - anchor); + int best_mlen, best_off; + int cur, last_match_pos = 0; + + LZ4HC_match_t const firstMatch = LZ4HC_FindLongerMatch(ctx, ip, matchlimit, MINMATCH-1, nbSearches, dict, favorDecSpeed); + if (firstMatch.len==0) { ip++; continue; } + + if ((size_t)firstMatch.len > sufficient_len) { + /* good enough solution : immediate encoding */ + int const firstML = firstMatch.len; + const BYTE* const matchPos = ip - firstMatch.off; + opSaved = op; + if ( LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), firstML, matchPos, limit, oend) ) /* updates ip, op and anchor */ + goto _dest_overflow; + continue; + } + + /* set prices for first positions (literals) */ + { int rPos; + for (rPos = 0 ; rPos < MINMATCH ; rPos++) { + int const cost = LZ4HC_literalsPrice(llen + rPos); + opt[rPos].mlen = 1; + opt[rPos].off = 0; + opt[rPos].litlen = llen + rPos; + opt[rPos].price = cost; + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i) -- initial setup", + rPos, cost, opt[rPos].litlen); + } } + /* set prices using initial match */ + { int mlen = MINMATCH; + int const matchML = firstMatch.len; /* necessarily < sufficient_len < LZ4_OPT_NUM */ + int const offset = firstMatch.off; + assert(matchML < LZ4_OPT_NUM); + for ( ; mlen <= matchML ; mlen++) { + int const cost = LZ4HC_sequencePrice(llen, mlen); + opt[mlen].mlen = mlen; + opt[mlen].off = offset; + opt[mlen].litlen = llen; + opt[mlen].price = cost; + DEBUGLOG(7, "rPos:%3i => price:%3i (matchlen=%i) -- initial setup", + mlen, cost, mlen); + } } + last_match_pos = firstMatch.len; + { int addLit; + for (addLit = 1; addLit <= TRAILING_LITERALS; addLit ++) { + opt[last_match_pos+addLit].mlen = 1; /* literal */ + opt[last_match_pos+addLit].off = 0; + opt[last_match_pos+addLit].litlen = addLit; + opt[last_match_pos+addLit].price = opt[last_match_pos].price + LZ4HC_literalsPrice(addLit); + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i) -- initial setup", + last_match_pos+addLit, opt[last_match_pos+addLit].price, addLit); + } } + + /* check further positions */ + for (cur = 1; cur < last_match_pos; cur++) { + const BYTE* const curPtr = ip + cur; + LZ4HC_match_t newMatch; + + if (curPtr > mflimit) break; + DEBUGLOG(7, "rPos:%u[%u] vs [%u]%u", + cur, opt[cur].price, opt[cur+1].price, cur+1); + if (fullUpdate) { + /* not useful to search here if next position has same (or lower) cost */ + if ( (opt[cur+1].price <= opt[cur].price) + /* in some cases, next position has same cost, but cost rises sharply after, so a small match would still be beneficial */ + && (opt[cur+MINMATCH].price < opt[cur].price + 3/*min seq price*/) ) + continue; + } else { + /* not useful to search here if next position has same (or lower) cost */ + if (opt[cur+1].price <= opt[cur].price) continue; + } + + DEBUGLOG(7, "search at rPos:%u", cur); + if (fullUpdate) + newMatch = LZ4HC_FindLongerMatch(ctx, curPtr, matchlimit, MINMATCH-1, nbSearches, dict, favorDecSpeed); + else + /* only test matches of minimum length; slightly faster, but misses a few bytes */ + newMatch = LZ4HC_FindLongerMatch(ctx, curPtr, matchlimit, last_match_pos - cur, nbSearches, dict, favorDecSpeed); + if (!newMatch.len) continue; + + if ( ((size_t)newMatch.len > sufficient_len) + || (newMatch.len + cur >= LZ4_OPT_NUM) ) { + /* immediate encoding */ + best_mlen = newMatch.len; + best_off = newMatch.off; + last_match_pos = cur + 1; + goto encode; + } + + /* before match : set price with literals at beginning */ + { int const baseLitlen = opt[cur].litlen; + int litlen; + for (litlen = 1; litlen < MINMATCH; litlen++) { + int const price = opt[cur].price - LZ4HC_literalsPrice(baseLitlen) + LZ4HC_literalsPrice(baseLitlen+litlen); + int const pos = cur + litlen; + if (price < opt[pos].price) { + opt[pos].mlen = 1; /* literal */ + opt[pos].off = 0; + opt[pos].litlen = baseLitlen+litlen; + opt[pos].price = price; + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i)", + pos, price, opt[pos].litlen); + } } } + + /* set prices using match at position = cur */ + { int const matchML = newMatch.len; + int ml = MINMATCH; + + assert(cur + newMatch.len < LZ4_OPT_NUM); + for ( ; ml <= matchML ; ml++) { + int const pos = cur + ml; + int const offset = newMatch.off; + int price; + int ll; + DEBUGLOG(7, "testing price rPos %i (last_match_pos=%i)", + pos, last_match_pos); + if (opt[cur].mlen == 1) { + ll = opt[cur].litlen; + price = ((cur > ll) ? opt[cur - ll].price : 0) + + LZ4HC_sequencePrice(ll, ml); + } else { + ll = 0; + price = opt[cur].price + LZ4HC_sequencePrice(0, ml); + } + + assert((U32)favorDecSpeed <= 1); + if (pos > last_match_pos+TRAILING_LITERALS + || price <= opt[pos].price - (int)favorDecSpeed) { + DEBUGLOG(7, "rPos:%3i => price:%3i (matchlen=%i)", + pos, price, ml); + assert(pos < LZ4_OPT_NUM); + if ( (ml == matchML) /* last pos of last match */ + && (last_match_pos < pos) ) + last_match_pos = pos; + opt[pos].mlen = ml; + opt[pos].off = offset; + opt[pos].litlen = ll; + opt[pos].price = price; + } } } + /* complete following positions with literals */ + { int addLit; + for (addLit = 1; addLit <= TRAILING_LITERALS; addLit ++) { + opt[last_match_pos+addLit].mlen = 1; /* literal */ + opt[last_match_pos+addLit].off = 0; + opt[last_match_pos+addLit].litlen = addLit; + opt[last_match_pos+addLit].price = opt[last_match_pos].price + LZ4HC_literalsPrice(addLit); + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i)", last_match_pos+addLit, opt[last_match_pos+addLit].price, addLit); + } } + } /* for (cur = 1; cur <= last_match_pos; cur++) */ + + assert(last_match_pos < LZ4_OPT_NUM + TRAILING_LITERALS); + best_mlen = opt[last_match_pos].mlen; + best_off = opt[last_match_pos].off; + cur = last_match_pos - best_mlen; + + encode: /* cur, last_match_pos, best_mlen, best_off must be set */ + assert(cur < LZ4_OPT_NUM); + assert(last_match_pos >= 1); /* == 1 when only one candidate */ + DEBUGLOG(6, "reverse traversal, looking for shortest path (last_match_pos=%i)", last_match_pos); + { int candidate_pos = cur; + int selected_matchLength = best_mlen; + int selected_offset = best_off; + while (1) { /* from end to beginning */ + int const next_matchLength = opt[candidate_pos].mlen; /* can be 1, means literal */ + int const next_offset = opt[candidate_pos].off; + DEBUGLOG(7, "pos %i: sequence length %i", candidate_pos, selected_matchLength); + opt[candidate_pos].mlen = selected_matchLength; + opt[candidate_pos].off = selected_offset; + selected_matchLength = next_matchLength; + selected_offset = next_offset; + if (next_matchLength > candidate_pos) break; /* last match elected, first match to encode */ + assert(next_matchLength > 0); /* can be 1, means literal */ + candidate_pos -= next_matchLength; + } } + + /* encode all recorded sequences in order */ + { int rPos = 0; /* relative position (to ip) */ + while (rPos < last_match_pos) { + int const ml = opt[rPos].mlen; + int const offset = opt[rPos].off; + if (ml == 1) { ip++; rPos++; continue; } /* literal; note: can end up with several literals, in which case, skip them */ + rPos += ml; + assert(ml >= MINMATCH); + assert((offset >= 1) && (offset <= LZ4_DISTANCE_MAX)); + opSaved = op; + if ( LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ip - offset, limit, oend) ) /* updates ip, op and anchor */ + goto _dest_overflow; + } } + } /* while (ip <= mflimit) */ + + _last_literals: + /* Encode Last Literals */ + { size_t lastRunSize = (size_t)(iend - anchor); /* literals */ + size_t litLength = (lastRunSize + 255 - RUN_MASK) / 255; + size_t const totalSize = 1 + litLength + lastRunSize; + if (limit == fillOutput) oend += LASTLITERALS; /* restore correct value */ + if (limit && (op + totalSize > oend)) { + if (limit == limitedOutput) return 0; /* Check output limit */ + /* adapt lastRunSize to fill 'dst' */ + lastRunSize = (size_t)(oend - op) - 1; + litLength = (lastRunSize + 255 - RUN_MASK) / 255; + lastRunSize -= litLength; + } + ip = anchor + lastRunSize; + + if (lastRunSize >= RUN_MASK) { + size_t accumulator = lastRunSize - RUN_MASK; + *op++ = (RUN_MASK << ML_BITS); + for(; accumulator >= 255 ; accumulator -= 255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRunSize << ML_BITS); + } + memcpy(op, anchor, lastRunSize); + op += lastRunSize; + } + + /* End */ + *srcSizePtr = (int) (((const char*)ip) - source); + return (int) ((char*)op-dst); + + _dest_overflow: + if (limit == fillOutput) { + op = opSaved; /* restore correct out pointer */ + goto _last_literals; + } + return 0; + } diff --git a/TMessagesProj/jni/lz4/lz4hc.h b/TMessagesProj/jni/lz4/lz4hc.h new file mode 100755 index 000000000..cdc6d895e --- /dev/null +++ b/TMessagesProj/jni/lz4/lz4hc.h @@ -0,0 +1,435 @@ +/* + LZ4 HC - High Compression Mode of LZ4 + Header File + Copyright (C) 2011-2017, Yann Collet. + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ +#ifndef LZ4_HC_H_19834876238432 +#define LZ4_HC_H_19834876238432 + +#if defined (__cplusplus) +extern "C" { +#endif + +/* --- Dependency --- */ +/* note : lz4hc requires lz4.h/lz4.c for compilation */ +#include "lz4.h" /* stddef, LZ4LIB_API, LZ4_DEPRECATED */ + + +/* --- Useful constants --- */ +#define LZ4HC_CLEVEL_MIN 3 +#define LZ4HC_CLEVEL_DEFAULT 9 +#define LZ4HC_CLEVEL_OPT_MIN 10 +#define LZ4HC_CLEVEL_MAX 12 + + +/*-************************************ + * Block Compression + **************************************/ +/*! LZ4_compress_HC() : + * Compress data from `src` into `dst`, using the powerful but slower "HC" algorithm. + * `dst` must be already allocated. + * Compression is guaranteed to succeed if `dstCapacity >= LZ4_compressBound(srcSize)` (see "lz4.h") + * Max supported `srcSize` value is LZ4_MAX_INPUT_SIZE (see "lz4.h") + * `compressionLevel` : any value between 1 and LZ4HC_CLEVEL_MAX will work. + * Values > LZ4HC_CLEVEL_MAX behave the same as LZ4HC_CLEVEL_MAX. + * @return : the number of bytes written into 'dst' + * or 0 if compression fails. + */ +LZ4LIB_API int LZ4_compress_HC (const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel); + + +/* Note : + * Decompression functions are provided within "lz4.h" (BSD license) + */ + + +/*! LZ4_compress_HC_extStateHC() : + * Same as LZ4_compress_HC(), but using an externally allocated memory segment for `state`. + * `state` size is provided by LZ4_sizeofStateHC(). + * Memory segment must be aligned on 8-bytes boundaries (which a normal malloc() should do properly). + */ +LZ4LIB_API int LZ4_sizeofStateHC(void); +LZ4LIB_API int LZ4_compress_HC_extStateHC(void* stateHC, const char* src, char* dst, int srcSize, int maxDstSize, int compressionLevel); + + +/*! LZ4_compress_HC_destSize() : v1.9.0+ + * Will compress as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided in 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr is updated to indicate how much bytes were read from `src` + */ +LZ4LIB_API int LZ4_compress_HC_destSize(void* stateHC, + const char* src, char* dst, + int* srcSizePtr, int targetDstSize, + int compressionLevel); + + +/*-************************************ + * Streaming Compression + * Bufferless synchronous API + **************************************/ + typedef union LZ4_streamHC_u LZ4_streamHC_t; /* incomplete type (defined later) */ + +/*! LZ4_createStreamHC() and LZ4_freeStreamHC() : + * These functions create and release memory for LZ4 HC streaming state. + * Newly created states are automatically initialized. + * A same state can be used multiple times consecutively, + * starting with LZ4_resetStreamHC_fast() to start a new stream of blocks. + */ +LZ4LIB_API LZ4_streamHC_t* LZ4_createStreamHC(void); +LZ4LIB_API int LZ4_freeStreamHC (LZ4_streamHC_t* streamHCPtr); + +/* + These functions compress data in successive blocks of any size, + using previous blocks as dictionary, to improve compression ratio. + One key assumption is that previous blocks (up to 64 KB) remain read-accessible while compressing next blocks. + There is an exception for ring buffers, which can be smaller than 64 KB. + Ring-buffer scenario is automatically detected and handled within LZ4_compress_HC_continue(). + + Before starting compression, state must be allocated and properly initialized. + LZ4_createStreamHC() does both, though compression level is set to LZ4HC_CLEVEL_DEFAULT. + + Selecting the compression level can be done with LZ4_resetStreamHC_fast() (starts a new stream) + or LZ4_setCompressionLevel() (anytime, between blocks in the same stream) (experimental). + LZ4_resetStreamHC_fast() only works on states which have been properly initialized at least once, + which is automatically the case when state is created using LZ4_createStreamHC(). + + After reset, a first "fictional block" can be designated as initial dictionary, + using LZ4_loadDictHC() (Optional). + + Invoke LZ4_compress_HC_continue() to compress each successive block. + The number of blocks is unlimited. + Previous input blocks, including initial dictionary when present, + must remain accessible and unmodified during compression. + + It's allowed to update compression level anytime between blocks, + using LZ4_setCompressionLevel() (experimental). + + 'dst' buffer should be sized to handle worst case scenarios + (see LZ4_compressBound(), it ensures compression success). + In case of failure, the API does not guarantee recovery, + so the state _must_ be reset. + To ensure compression success + whenever `dst` buffer size cannot be made >= LZ4_compressBound(), + consider using LZ4_compress_HC_continue_destSize(). + + Whenever previous input blocks can't be preserved unmodified in-place during compression of next blocks, + it's possible to copy the last blocks into a more stable memory space, using LZ4_saveDictHC(). + Return value of LZ4_saveDictHC() is the size of dictionary effectively saved into 'safeBuffer' (<= 64 KB) + + After completing a streaming compression, + it's possible to start a new stream of blocks, using the same LZ4_streamHC_t state, + just by resetting it, using LZ4_resetStreamHC_fast(). +*/ + +LZ4LIB_API void LZ4_resetStreamHC_fast(LZ4_streamHC_t* streamHCPtr, int compressionLevel); /* v1.9.0+ */ +LZ4LIB_API int LZ4_loadDictHC (LZ4_streamHC_t* streamHCPtr, const char* dictionary, int dictSize); + +LZ4LIB_API int LZ4_compress_HC_continue (LZ4_streamHC_t* streamHCPtr, + const char* src, char* dst, + int srcSize, int maxDstSize); + +/*! LZ4_compress_HC_continue_destSize() : v1.9.0+ + * Similar to LZ4_compress_HC_continue(), + * but will read as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided into 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr will be updated to indicate how much bytes were read from `src`. + * Note that this function may not consume the entire input. + */ +LZ4LIB_API int LZ4_compress_HC_continue_destSize(LZ4_streamHC_t* LZ4_streamHCPtr, + const char* src, char* dst, + int* srcSizePtr, int targetDstSize); + +LZ4LIB_API int LZ4_saveDictHC (LZ4_streamHC_t* streamHCPtr, char* safeBuffer, int maxDictSize); + + + +/*^********************************************** + * !!!!!! STATIC LINKING ONLY !!!!!! + ***********************************************/ + +/*-****************************************************************** + * PRIVATE DEFINITIONS : + * Do not use these definitions directly. + * They are merely exposed to allow static allocation of `LZ4_streamHC_t`. + * Declare an `LZ4_streamHC_t` directly, rather than any type below. + * Even then, only do so in the context of static linking, as definitions may change between versions. + ********************************************************************/ + +#define LZ4HC_DICTIONARY_LOGSIZE 16 +#define LZ4HC_MAXD (1<= 199901L) /* C99 */) +#include + +typedef struct LZ4HC_CCtx_internal LZ4HC_CCtx_internal; +struct LZ4HC_CCtx_internal +{ + uint32_t hashTable[LZ4HC_HASHTABLESIZE]; + uint16_t chainTable[LZ4HC_MAXD]; + const uint8_t* end; /* next block here to continue on current prefix */ + const uint8_t* base; /* All index relative to this position */ + const uint8_t* dictBase; /* alternate base for extDict */ + uint32_t dictLimit; /* below that point, need extDict */ + uint32_t lowLimit; /* below that point, no more dict */ + uint32_t nextToUpdate; /* index from which to continue dictionary update */ + short compressionLevel; + int8_t favorDecSpeed; /* favor decompression speed if this flag set, + otherwise, favor compression ratio */ + int8_t dirty; /* stream has to be fully reset if this flag is set */ + const LZ4HC_CCtx_internal* dictCtx; +}; + +#else + +typedef struct LZ4HC_CCtx_internal LZ4HC_CCtx_internal; +struct LZ4HC_CCtx_internal +{ + unsigned int hashTable[LZ4HC_HASHTABLESIZE]; + unsigned short chainTable[LZ4HC_MAXD]; + const unsigned char* end; /* next block here to continue on current prefix */ + const unsigned char* base; /* All index relative to this position */ + const unsigned char* dictBase; /* alternate base for extDict */ + unsigned int dictLimit; /* below that point, need extDict */ + unsigned int lowLimit; /* below that point, no more dict */ + unsigned int nextToUpdate; /* index from which to continue dictionary update */ + short compressionLevel; + char favorDecSpeed; /* favor decompression speed if this flag set, + otherwise, favor compression ratio */ + char dirty; /* stream has to be fully reset if this flag is set */ + const LZ4HC_CCtx_internal* dictCtx; +}; + +#endif + + +/* Do not use these definitions directly ! + * Declare or allocate an LZ4_streamHC_t instead. + */ +#define LZ4_STREAMHCSIZE (4*LZ4HC_HASHTABLESIZE + 2*LZ4HC_MAXD + 56 + ((sizeof(void*)==16) ? 56 : 0) /* AS400*/ ) /* 262200 or 262256*/ +#define LZ4_STREAMHCSIZE_SIZET (LZ4_STREAMHCSIZE / sizeof(size_t)) +union LZ4_streamHC_u { + size_t table[LZ4_STREAMHCSIZE_SIZET]; + LZ4HC_CCtx_internal internal_donotuse; +}; /* previously typedef'd to LZ4_streamHC_t */ + +/* LZ4_streamHC_t : + * This structure allows static allocation of LZ4 HC streaming state. + * This can be used to allocate statically, on state, or as part of a larger structure. + * + * Such state **must** be initialized using LZ4_initStreamHC() before first use. + * + * Note that invoking LZ4_initStreamHC() is not required when + * the state was created using LZ4_createStreamHC() (which is recommended). + * Using the normal builder, a newly created state is automatically initialized. + * + * Static allocation shall only be used in combination with static linking. + */ + +/* LZ4_initStreamHC() : v1.9.0+ + * Required before first use of a statically allocated LZ4_streamHC_t. + * Before v1.9.0 : use LZ4_resetStreamHC() instead + */ +LZ4LIB_API LZ4_streamHC_t* LZ4_initStreamHC (void* buffer, size_t size); + + +/*-************************************ +* Deprecated Functions +**************************************/ +/* see lz4.h LZ4_DISABLE_DEPRECATE_WARNINGS to turn off deprecation warnings */ + +/* deprecated compression functions */ +LZ4_DEPRECATED("use LZ4_compress_HC() instead") LZ4LIB_API int LZ4_compressHC (const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_HC() instead") LZ4LIB_API int LZ4_compressHC_limitedOutput (const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_HC() instead") LZ4LIB_API int LZ4_compressHC2 (const char* source, char* dest, int inputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_compress_HC() instead") LZ4LIB_API int LZ4_compressHC2_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") LZ4LIB_API int LZ4_compressHC_withStateHC (void* state, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") LZ4LIB_API int LZ4_compressHC_limitedOutput_withStateHC (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") LZ4LIB_API int LZ4_compressHC2_withStateHC (void* state, const char* source, char* dest, int inputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") LZ4LIB_API int LZ4_compressHC2_limitedOutput_withStateHC(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") LZ4LIB_API int LZ4_compressHC_continue (LZ4_streamHC_t* LZ4_streamHCPtr, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") LZ4LIB_API int LZ4_compressHC_limitedOutput_continue (LZ4_streamHC_t* LZ4_streamHCPtr, const char* source, char* dest, int inputSize, int maxOutputSize); + +/* Obsolete streaming functions; degraded functionality; do not use! + * + * In order to perform streaming compression, these functions depended on data + * that is no longer tracked in the state. They have been preserved as well as + * possible: using them will still produce a correct output. However, use of + * LZ4_slideInputBufferHC() will truncate the history of the stream, rather + * than preserve a window-sized chunk of history. + */ +LZ4_DEPRECATED("use LZ4_createStreamHC() instead") LZ4LIB_API void* LZ4_createHC (const char* inputBuffer); +LZ4_DEPRECATED("use LZ4_saveDictHC() instead") LZ4LIB_API char* LZ4_slideInputBufferHC (void* LZ4HC_Data); +LZ4_DEPRECATED("use LZ4_freeStreamHC() instead") LZ4LIB_API int LZ4_freeHC (void* LZ4HC_Data); +LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") LZ4LIB_API int LZ4_compressHC2_continue (void* LZ4HC_Data, const char* source, char* dest, int inputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") LZ4LIB_API int LZ4_compressHC2_limitedOutput_continue (void* LZ4HC_Data, const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_createStreamHC() instead") LZ4LIB_API int LZ4_sizeofStreamStateHC(void); +LZ4_DEPRECATED("use LZ4_initStreamHC() instead") LZ4LIB_API int LZ4_resetStreamStateHC(void* state, char* inputBuffer); + + +/* LZ4_resetStreamHC() is now replaced by LZ4_initStreamHC(). + * The intention is to emphasize the difference with LZ4_resetStreamHC_fast(), + * which is now the recommended function to start a new stream of blocks, + * but cannot be used to initialize a memory segment containing arbitrary garbage data. + * + * It is recommended to switch to LZ4_initStreamHC(). + * LZ4_resetStreamHC() will generate deprecation warnings in a future version. + */ +LZ4LIB_API void LZ4_resetStreamHC (LZ4_streamHC_t* streamHCPtr, int compressionLevel); + + +#if defined (__cplusplus) +} +#endif + +#endif /* LZ4_HC_H_19834876238432 */ + + +/*-************************************************** + * !!!!! STATIC LINKING ONLY !!!!! + * Following definitions are considered experimental. + * They should not be linked from DLL, + * as there is no guarantee of API stability yet. + * Prototypes will be promoted to "stable" status + * after successfull usage in real-life scenarios. + ***************************************************/ +#ifdef LZ4_HC_STATIC_LINKING_ONLY /* protection macro */ +#ifndef LZ4_HC_SLO_098092834 +#define LZ4_HC_SLO_098092834 + +#if defined (__cplusplus) +extern "C" { +#endif + +/*! LZ4_setCompressionLevel() : v1.8.0+ (experimental) + * It's possible to change compression level + * between successive invocations of LZ4_compress_HC_continue*() + * for dynamic adaptation. + */ +LZ4LIB_STATIC_API void LZ4_setCompressionLevel( + LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel); + +/*! LZ4_favorDecompressionSpeed() : v1.8.2+ (experimental) + * Opt. Parser will favor decompression speed over compression ratio. + * Only applicable to levels >= LZ4HC_CLEVEL_OPT_MIN. + */ +LZ4LIB_STATIC_API void LZ4_favorDecompressionSpeed( + LZ4_streamHC_t* LZ4_streamHCPtr, int favor); + +/*! LZ4_resetStreamHC_fast() : v1.9.0+ + * When an LZ4_streamHC_t is known to be in a internally coherent state, + * it can often be prepared for a new compression with almost no work, only + * sometimes falling back to the full, expensive reset that is always required + * when the stream is in an indeterminate state (i.e., the reset performed by + * LZ4_resetStreamHC()). + * + * LZ4_streamHCs are guaranteed to be in a valid state when: + * - returned from LZ4_createStreamHC() + * - reset by LZ4_resetStreamHC() + * - memset(stream, 0, sizeof(LZ4_streamHC_t)) + * - the stream was in a valid state and was reset by LZ4_resetStreamHC_fast() + * - the stream was in a valid state and was then used in any compression call + * that returned success + * - the stream was in an indeterminate state and was used in a compression + * call that fully reset the state (LZ4_compress_HC_extStateHC()) and that + * returned success + * + * Note: + * A stream that was last used in a compression call that returned an error + * may be passed to this function. However, it will be fully reset, which will + * clear any existing history and settings from the context. + */ +LZ4LIB_STATIC_API void LZ4_resetStreamHC_fast( + LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel); + +/*! LZ4_compress_HC_extStateHC_fastReset() : + * A variant of LZ4_compress_HC_extStateHC(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStreamHC_fast() for a definition of + * "correctly initialized"). From a high level, the difference is that this + * function initializes the provided state with a call to + * LZ4_resetStreamHC_fast() while LZ4_compress_HC_extStateHC() starts with a + * call to LZ4_resetStreamHC(). + */ +LZ4LIB_STATIC_API int LZ4_compress_HC_extStateHC_fastReset ( + void* state, + const char* src, char* dst, + int srcSize, int dstCapacity, + int compressionLevel); + +/*! LZ4_attach_HC_dictionary() : + * This is an experimental API that allows for the efficient use of a + * static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_streamHC_t into a + * working LZ4_streamHC_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDictHC() should + * be expected to work. + * + * Alternatively, the provided dictionary stream pointer may be NULL, in which + * case any existing dictionary stream is unset. + * + * A dictionary should only be attached to a stream without any history (i.e., + * a stream that has just been reset). + * + * The dictionary will remain attached to the working stream only for the + * current stream session. Calls to LZ4_resetStreamHC(_fast) will remove the + * dictionary context association from the working stream. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the lifetime of the stream session. + */ +LZ4LIB_STATIC_API void LZ4_attach_HC_dictionary( + LZ4_streamHC_t *working_stream, + const LZ4_streamHC_t *dictionary_stream); + +#if defined (__cplusplus) +} +#endif + +#endif /* LZ4_HC_SLO_098092834 */ +#endif /* LZ4_HC_STATIC_LINKING_ONLY */ diff --git a/TMessagesProj/jni/lz4/xxhash.c b/TMessagesProj/jni/lz4/xxhash.c new file mode 100755 index 000000000..ff28749e3 --- /dev/null +++ b/TMessagesProj/jni/lz4/xxhash.c @@ -0,0 +1,1030 @@ +/* +* xxHash - Fast Hash algorithm +* Copyright (C) 2012-2016, Yann Collet +* +* BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* You can contact the author at : +* - xxHash homepage: http://www.xxhash.com +* - xxHash source repository : https://github.com/Cyan4973/xxHash +*/ + + +/* ************************************* +* Tuning parameters +***************************************/ +/*!XXH_FORCE_MEMORY_ACCESS : + * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. + * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. + * The below switch allow to select different access method for improved performance. + * Method 0 (default) : use `memcpy()`. Safe and portable. + * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). + * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. + * Method 2 : direct access. This method doesn't depend on compiler but violate C standard. + * It can generate buggy code on targets which do not support unaligned memory accesses. + * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) + * See http://stackoverflow.com/a/32095106/646947 for details. + * Prefer these methods in priority order (0 > 1 > 2) + */ +#ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ +# if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) \ + || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) \ + || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# define XXH_FORCE_MEMORY_ACCESS 2 +# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || \ + (defined(__GNUC__) && ( defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) \ + || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) \ + || defined(__ARM_ARCH_7S__) )) +# define XXH_FORCE_MEMORY_ACCESS 1 +# endif +#endif + +/*!XXH_ACCEPT_NULL_INPUT_POINTER : + * If input pointer is NULL, xxHash default behavior is to dereference it, triggering a segfault. + * When this macro is enabled, xxHash actively checks input for null pointer. + * It it is, result for null input pointers is the same as a null-length input. + */ +#ifndef XXH_ACCEPT_NULL_INPUT_POINTER /* can be defined externally */ +# define XXH_ACCEPT_NULL_INPUT_POINTER 0 +#endif + +/*!XXH_FORCE_NATIVE_FORMAT : + * By default, xxHash library provides endian-independent Hash values, based on little-endian convention. + * Results are therefore identical for little-endian and big-endian CPU. + * This comes at a performance cost for big-endian CPU, since some swapping is required to emulate little-endian format. + * Should endian-independence be of no importance for your application, you may set the #define below to 1, + * to improve speed for Big-endian CPU. + * This option has no impact on Little_Endian CPU. + */ +#ifndef XXH_FORCE_NATIVE_FORMAT /* can be defined externally */ +# define XXH_FORCE_NATIVE_FORMAT 0 +#endif + +/*!XXH_FORCE_ALIGN_CHECK : + * This is a minor performance trick, only useful with lots of very small keys. + * It means : check for aligned/unaligned input. + * The check costs one initial branch per hash; + * set it to 0 when the input is guaranteed to be aligned, + * or when alignment doesn't matter for performance. + */ +#ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */ +# if defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64) +# define XXH_FORCE_ALIGN_CHECK 0 +# else +# define XXH_FORCE_ALIGN_CHECK 1 +# endif +#endif + + +/* ************************************* +* Includes & Memory related functions +***************************************/ +/*! Modify the local functions below should you wish to use some other memory routines +* for malloc(), free() */ +#include +static void* XXH_malloc(size_t s) { return malloc(s); } +static void XXH_free (void* p) { free(p); } +/*! and for memcpy() */ +#include +static void* XXH_memcpy(void* dest, const void* src, size_t size) { return memcpy(dest,src,size); } + +#include /* assert */ + +#define XXH_STATIC_LINKING_ONLY +#include "xxhash.h" + + +/* ************************************* +* Compiler Specific Options +***************************************/ +#ifdef _MSC_VER /* Visual Studio */ +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# define FORCE_INLINE static __forceinline +#else +# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# ifdef __GNUC__ +# define FORCE_INLINE static inline __attribute__((always_inline)) +# else +# define FORCE_INLINE static inline +# endif +# else +# define FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +#endif + + +/* ************************************* +* Basic Types +***************************************/ +#ifndef MEM_MODULE +# if !defined (__VMS) \ + && (defined (__cplusplus) \ + || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; +# else + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; +# endif +#endif + +#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) + +/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */ +static U32 XXH_read32(const void* memPtr) { return *(const U32*) memPtr; } + +#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U32 u32; } __attribute__((packed)) unalign; +static U32 XXH_read32(const void* ptr) { return ((const unalign*)ptr)->u32; } + +#else + +/* portable and safe solution. Generally efficient. + * see : http://stackoverflow.com/a/32095106/646947 + */ +static U32 XXH_read32(const void* memPtr) +{ + U32 val; + memcpy(&val, memPtr, sizeof(val)); + return val; +} + +#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ + + +/* **************************************** +* Compiler-specific Functions and Macros +******************************************/ +#define XXH_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) + +/* Note : although _rotl exists for minGW (GCC under windows), performance seems poor */ +#if defined(_MSC_VER) +# define XXH_rotl32(x,r) _rotl(x,r) +# define XXH_rotl64(x,r) _rotl64(x,r) +#else +# define XXH_rotl32(x,r) ((x << r) | (x >> (32 - r))) +# define XXH_rotl64(x,r) ((x << r) | (x >> (64 - r))) +#endif + +#if defined(_MSC_VER) /* Visual Studio */ +# define XXH_swap32 _byteswap_ulong +#elif XXH_GCC_VERSION >= 403 +# define XXH_swap32 __builtin_bswap32 +#else +static U32 XXH_swap32 (U32 x) +{ + return ((x << 24) & 0xff000000 ) | + ((x << 8) & 0x00ff0000 ) | + ((x >> 8) & 0x0000ff00 ) | + ((x >> 24) & 0x000000ff ); +} +#endif + + +/* ************************************* +* Architecture Macros +***************************************/ +typedef enum { XXH_bigEndian=0, XXH_littleEndian=1 } XXH_endianess; + +/* XXH_CPU_LITTLE_ENDIAN can be defined externally, for example on the compiler command line */ +#ifndef XXH_CPU_LITTLE_ENDIAN +static int XXH_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} +# define XXH_CPU_LITTLE_ENDIAN XXH_isLittleEndian() +#endif + + +/* *************************** +* Memory reads +*****************************/ +typedef enum { XXH_aligned, XXH_unaligned } XXH_alignment; + +FORCE_INLINE U32 XXH_readLE32_align(const void* ptr, XXH_endianess endian, XXH_alignment align) +{ + if (align==XXH_unaligned) + return endian==XXH_littleEndian ? XXH_read32(ptr) : XXH_swap32(XXH_read32(ptr)); + else + return endian==XXH_littleEndian ? *(const U32*)ptr : XXH_swap32(*(const U32*)ptr); +} + +FORCE_INLINE U32 XXH_readLE32(const void* ptr, XXH_endianess endian) +{ + return XXH_readLE32_align(ptr, endian, XXH_unaligned); +} + +static U32 XXH_readBE32(const void* ptr) +{ + return XXH_CPU_LITTLE_ENDIAN ? XXH_swap32(XXH_read32(ptr)) : XXH_read32(ptr); +} + + +/* ************************************* +* Macros +***************************************/ +#define XXH_STATIC_ASSERT(c) { enum { XXH_sa = 1/(int)(!!(c)) }; } /* use after variable declarations */ +XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; } + + +/* ******************************************************************* +* 32-bit hash functions +*********************************************************************/ +static const U32 PRIME32_1 = 2654435761U; +static const U32 PRIME32_2 = 2246822519U; +static const U32 PRIME32_3 = 3266489917U; +static const U32 PRIME32_4 = 668265263U; +static const U32 PRIME32_5 = 374761393U; + +static U32 XXH32_round(U32 seed, U32 input) +{ + seed += input * PRIME32_2; + seed = XXH_rotl32(seed, 13); + seed *= PRIME32_1; + return seed; +} + +/* mix all bits */ +static U32 XXH32_avalanche(U32 h32) +{ + h32 ^= h32 >> 15; + h32 *= PRIME32_2; + h32 ^= h32 >> 13; + h32 *= PRIME32_3; + h32 ^= h32 >> 16; + return(h32); +} + +#define XXH_get32bits(p) XXH_readLE32_align(p, endian, align) + +static U32 +XXH32_finalize(U32 h32, const void* ptr, size_t len, + XXH_endianess endian, XXH_alignment align) + +{ + const BYTE* p = (const BYTE*)ptr; + +#define PROCESS1 \ + h32 += (*p++) * PRIME32_5; \ + h32 = XXH_rotl32(h32, 11) * PRIME32_1 ; + +#define PROCESS4 \ + h32 += XXH_get32bits(p) * PRIME32_3; \ + p+=4; \ + h32 = XXH_rotl32(h32, 17) * PRIME32_4 ; + + switch(len&15) /* or switch(bEnd - p) */ + { + case 12: PROCESS4; + /* fallthrough */ + case 8: PROCESS4; + /* fallthrough */ + case 4: PROCESS4; + return XXH32_avalanche(h32); + + case 13: PROCESS4; + /* fallthrough */ + case 9: PROCESS4; + /* fallthrough */ + case 5: PROCESS4; + PROCESS1; + return XXH32_avalanche(h32); + + case 14: PROCESS4; + /* fallthrough */ + case 10: PROCESS4; + /* fallthrough */ + case 6: PROCESS4; + PROCESS1; + PROCESS1; + return XXH32_avalanche(h32); + + case 15: PROCESS4; + /* fallthrough */ + case 11: PROCESS4; + /* fallthrough */ + case 7: PROCESS4; + /* fallthrough */ + case 3: PROCESS1; + /* fallthrough */ + case 2: PROCESS1; + /* fallthrough */ + case 1: PROCESS1; + /* fallthrough */ + case 0: return XXH32_avalanche(h32); + } + assert(0); + return h32; /* reaching this point is deemed impossible */ +} + + +FORCE_INLINE U32 +XXH32_endian_align(const void* input, size_t len, U32 seed, + XXH_endianess endian, XXH_alignment align) +{ + const BYTE* p = (const BYTE*)input; + const BYTE* bEnd = p + len; + U32 h32; + +#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) + if (p==NULL) { + len=0; + bEnd=p=(const BYTE*)(size_t)16; + } +#endif + + if (len>=16) { + const BYTE* const limit = bEnd - 15; + U32 v1 = seed + PRIME32_1 + PRIME32_2; + U32 v2 = seed + PRIME32_2; + U32 v3 = seed + 0; + U32 v4 = seed - PRIME32_1; + + do { + v1 = XXH32_round(v1, XXH_get32bits(p)); p+=4; + v2 = XXH32_round(v2, XXH_get32bits(p)); p+=4; + v3 = XXH32_round(v3, XXH_get32bits(p)); p+=4; + v4 = XXH32_round(v4, XXH_get32bits(p)); p+=4; + } while (p < limit); + + h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7) + + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18); + } else { + h32 = seed + PRIME32_5; + } + + h32 += (U32)len; + + return XXH32_finalize(h32, p, len&15, endian, align); +} + + +XXH_PUBLIC_API unsigned int XXH32 (const void* input, size_t len, unsigned int seed) +{ +#if 0 + /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ + XXH32_state_t state; + XXH32_reset(&state, seed); + XXH32_update(&state, input, len); + return XXH32_digest(&state); +#else + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if (XXH_FORCE_ALIGN_CHECK) { + if ((((size_t)input) & 3) == 0) { /* Input is 4-bytes aligned, leverage the speed benefit */ + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned); + else + return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned); + } } + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned); + else + return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned); +#endif +} + + + +/*====== Hash streaming ======*/ + +XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void) +{ + return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t)); +} +XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr) +{ + XXH_free(statePtr); + return XXH_OK; +} + +XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t* srcState) +{ + memcpy(dstState, srcState, sizeof(*dstState)); +} + +XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, unsigned int seed) +{ + XXH32_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ + memset(&state, 0, sizeof(state)); + state.v1 = seed + PRIME32_1 + PRIME32_2; + state.v2 = seed + PRIME32_2; + state.v3 = seed + 0; + state.v4 = seed - PRIME32_1; + /* do not write into reserved, planned to be removed in a future version */ + memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved)); + return XXH_OK; +} + + +FORCE_INLINE XXH_errorcode +XXH32_update_endian(XXH32_state_t* state, const void* input, size_t len, XXH_endianess endian) +{ + if (input==NULL) +#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) + return XXH_OK; +#else + return XXH_ERROR; +#endif + + { const BYTE* p = (const BYTE*)input; + const BYTE* const bEnd = p + len; + + state->total_len_32 += (unsigned)len; + state->large_len |= (len>=16) | (state->total_len_32>=16); + + if (state->memsize + len < 16) { /* fill in tmp buffer */ + XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, len); + state->memsize += (unsigned)len; + return XXH_OK; + } + + if (state->memsize) { /* some data left from previous update */ + XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, 16-state->memsize); + { const U32* p32 = state->mem32; + state->v1 = XXH32_round(state->v1, XXH_readLE32(p32, endian)); p32++; + state->v2 = XXH32_round(state->v2, XXH_readLE32(p32, endian)); p32++; + state->v3 = XXH32_round(state->v3, XXH_readLE32(p32, endian)); p32++; + state->v4 = XXH32_round(state->v4, XXH_readLE32(p32, endian)); + } + p += 16-state->memsize; + state->memsize = 0; + } + + if (p <= bEnd-16) { + const BYTE* const limit = bEnd - 16; + U32 v1 = state->v1; + U32 v2 = state->v2; + U32 v3 = state->v3; + U32 v4 = state->v4; + + do { + v1 = XXH32_round(v1, XXH_readLE32(p, endian)); p+=4; + v2 = XXH32_round(v2, XXH_readLE32(p, endian)); p+=4; + v3 = XXH32_round(v3, XXH_readLE32(p, endian)); p+=4; + v4 = XXH32_round(v4, XXH_readLE32(p, endian)); p+=4; + } while (p<=limit); + + state->v1 = v1; + state->v2 = v2; + state->v3 = v3; + state->v4 = v4; + } + + if (p < bEnd) { + XXH_memcpy(state->mem32, p, (size_t)(bEnd-p)); + state->memsize = (unsigned)(bEnd-p); + } + } + + return XXH_OK; +} + + +XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* state_in, const void* input, size_t len) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_update_endian(state_in, input, len, XXH_littleEndian); + else + return XXH32_update_endian(state_in, input, len, XXH_bigEndian); +} + + +FORCE_INLINE U32 +XXH32_digest_endian (const XXH32_state_t* state, XXH_endianess endian) +{ + U32 h32; + + if (state->large_len) { + h32 = XXH_rotl32(state->v1, 1) + + XXH_rotl32(state->v2, 7) + + XXH_rotl32(state->v3, 12) + + XXH_rotl32(state->v4, 18); + } else { + h32 = state->v3 /* == seed */ + PRIME32_5; + } + + h32 += state->total_len_32; + + return XXH32_finalize(h32, state->mem32, state->memsize, endian, XXH_aligned); +} + + +XXH_PUBLIC_API unsigned int XXH32_digest (const XXH32_state_t* state_in) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_digest_endian(state_in, XXH_littleEndian); + else + return XXH32_digest_endian(state_in, XXH_bigEndian); +} + + +/*====== Canonical representation ======*/ + +/*! Default XXH result types are basic unsigned 32 and 64 bits. +* The canonical representation follows human-readable write convention, aka big-endian (large digits first). +* These functions allow transformation of hash result into and from its canonical format. +* This way, hash values can be written into a file or buffer, remaining comparable across different systems. +*/ + +XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash) +{ + XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t)); + if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash); + memcpy(dst, &hash, sizeof(*dst)); +} + +XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src) +{ + return XXH_readBE32(src); +} + + +#ifndef XXH_NO_LONG_LONG + +/* ******************************************************************* +* 64-bit hash functions +*********************************************************************/ + +/*====== Memory access ======*/ + +#ifndef MEM_MODULE +# define MEM_MODULE +# if !defined (__VMS) \ + && (defined (__cplusplus) \ + || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + typedef uint64_t U64; +# else + /* if compiler doesn't support unsigned long long, replace by another 64-bit type */ + typedef unsigned long long U64; +# endif +#endif + + +#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) + +/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */ +static U64 XXH_read64(const void* memPtr) { return *(const U64*) memPtr; } + +#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U32 u32; U64 u64; } __attribute__((packed)) unalign64; +static U64 XXH_read64(const void* ptr) { return ((const unalign64*)ptr)->u64; } + +#else + +/* portable and safe solution. Generally efficient. + * see : http://stackoverflow.com/a/32095106/646947 + */ + +static U64 XXH_read64(const void* memPtr) +{ + U64 val; + memcpy(&val, memPtr, sizeof(val)); + return val; +} + +#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ + +#if defined(_MSC_VER) /* Visual Studio */ +# define XXH_swap64 _byteswap_uint64 +#elif XXH_GCC_VERSION >= 403 +# define XXH_swap64 __builtin_bswap64 +#else +static U64 XXH_swap64 (U64 x) +{ + return ((x << 56) & 0xff00000000000000ULL) | + ((x << 40) & 0x00ff000000000000ULL) | + ((x << 24) & 0x0000ff0000000000ULL) | + ((x << 8) & 0x000000ff00000000ULL) | + ((x >> 8) & 0x00000000ff000000ULL) | + ((x >> 24) & 0x0000000000ff0000ULL) | + ((x >> 40) & 0x000000000000ff00ULL) | + ((x >> 56) & 0x00000000000000ffULL); +} +#endif + +FORCE_INLINE U64 XXH_readLE64_align(const void* ptr, XXH_endianess endian, XXH_alignment align) +{ + if (align==XXH_unaligned) + return endian==XXH_littleEndian ? XXH_read64(ptr) : XXH_swap64(XXH_read64(ptr)); + else + return endian==XXH_littleEndian ? *(const U64*)ptr : XXH_swap64(*(const U64*)ptr); +} + +FORCE_INLINE U64 XXH_readLE64(const void* ptr, XXH_endianess endian) +{ + return XXH_readLE64_align(ptr, endian, XXH_unaligned); +} + +static U64 XXH_readBE64(const void* ptr) +{ + return XXH_CPU_LITTLE_ENDIAN ? XXH_swap64(XXH_read64(ptr)) : XXH_read64(ptr); +} + + +/*====== xxh64 ======*/ + +static const U64 PRIME64_1 = 11400714785074694791ULL; +static const U64 PRIME64_2 = 14029467366897019727ULL; +static const U64 PRIME64_3 = 1609587929392839161ULL; +static const U64 PRIME64_4 = 9650029242287828579ULL; +static const U64 PRIME64_5 = 2870177450012600261ULL; + +static U64 XXH64_round(U64 acc, U64 input) +{ + acc += input * PRIME64_2; + acc = XXH_rotl64(acc, 31); + acc *= PRIME64_1; + return acc; +} + +static U64 XXH64_mergeRound(U64 acc, U64 val) +{ + val = XXH64_round(0, val); + acc ^= val; + acc = acc * PRIME64_1 + PRIME64_4; + return acc; +} + +static U64 XXH64_avalanche(U64 h64) +{ + h64 ^= h64 >> 33; + h64 *= PRIME64_2; + h64 ^= h64 >> 29; + h64 *= PRIME64_3; + h64 ^= h64 >> 32; + return h64; +} + + +#define XXH_get64bits(p) XXH_readLE64_align(p, endian, align) + +static U64 +XXH64_finalize(U64 h64, const void* ptr, size_t len, + XXH_endianess endian, XXH_alignment align) +{ + const BYTE* p = (const BYTE*)ptr; + +#define PROCESS1_64 \ + h64 ^= (*p++) * PRIME64_5; \ + h64 = XXH_rotl64(h64, 11) * PRIME64_1; + +#define PROCESS4_64 \ + h64 ^= (U64)(XXH_get32bits(p)) * PRIME64_1; \ + p+=4; \ + h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3; + +#define PROCESS8_64 { \ + U64 const k1 = XXH64_round(0, XXH_get64bits(p)); \ + p+=8; \ + h64 ^= k1; \ + h64 = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4; \ +} + + switch(len&31) { + case 24: PROCESS8_64; + /* fallthrough */ + case 16: PROCESS8_64; + /* fallthrough */ + case 8: PROCESS8_64; + return XXH64_avalanche(h64); + + case 28: PROCESS8_64; + /* fallthrough */ + case 20: PROCESS8_64; + /* fallthrough */ + case 12: PROCESS8_64; + /* fallthrough */ + case 4: PROCESS4_64; + return XXH64_avalanche(h64); + + case 25: PROCESS8_64; + /* fallthrough */ + case 17: PROCESS8_64; + /* fallthrough */ + case 9: PROCESS8_64; + PROCESS1_64; + return XXH64_avalanche(h64); + + case 29: PROCESS8_64; + /* fallthrough */ + case 21: PROCESS8_64; + /* fallthrough */ + case 13: PROCESS8_64; + /* fallthrough */ + case 5: PROCESS4_64; + PROCESS1_64; + return XXH64_avalanche(h64); + + case 26: PROCESS8_64; + /* fallthrough */ + case 18: PROCESS8_64; + /* fallthrough */ + case 10: PROCESS8_64; + PROCESS1_64; + PROCESS1_64; + return XXH64_avalanche(h64); + + case 30: PROCESS8_64; + /* fallthrough */ + case 22: PROCESS8_64; + /* fallthrough */ + case 14: PROCESS8_64; + /* fallthrough */ + case 6: PROCESS4_64; + PROCESS1_64; + PROCESS1_64; + return XXH64_avalanche(h64); + + case 27: PROCESS8_64; + /* fallthrough */ + case 19: PROCESS8_64; + /* fallthrough */ + case 11: PROCESS8_64; + PROCESS1_64; + PROCESS1_64; + PROCESS1_64; + return XXH64_avalanche(h64); + + case 31: PROCESS8_64; + /* fallthrough */ + case 23: PROCESS8_64; + /* fallthrough */ + case 15: PROCESS8_64; + /* fallthrough */ + case 7: PROCESS4_64; + /* fallthrough */ + case 3: PROCESS1_64; + /* fallthrough */ + case 2: PROCESS1_64; + /* fallthrough */ + case 1: PROCESS1_64; + /* fallthrough */ + case 0: return XXH64_avalanche(h64); + } + + /* impossible to reach */ + assert(0); + return 0; /* unreachable, but some compilers complain without it */ +} + +FORCE_INLINE U64 +XXH64_endian_align(const void* input, size_t len, U64 seed, + XXH_endianess endian, XXH_alignment align) +{ + const BYTE* p = (const BYTE*)input; + const BYTE* bEnd = p + len; + U64 h64; + +#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) + if (p==NULL) { + len=0; + bEnd=p=(const BYTE*)(size_t)32; + } +#endif + + if (len>=32) { + const BYTE* const limit = bEnd - 32; + U64 v1 = seed + PRIME64_1 + PRIME64_2; + U64 v2 = seed + PRIME64_2; + U64 v3 = seed + 0; + U64 v4 = seed - PRIME64_1; + + do { + v1 = XXH64_round(v1, XXH_get64bits(p)); p+=8; + v2 = XXH64_round(v2, XXH_get64bits(p)); p+=8; + v3 = XXH64_round(v3, XXH_get64bits(p)); p+=8; + v4 = XXH64_round(v4, XXH_get64bits(p)); p+=8; + } while (p<=limit); + + h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18); + h64 = XXH64_mergeRound(h64, v1); + h64 = XXH64_mergeRound(h64, v2); + h64 = XXH64_mergeRound(h64, v3); + h64 = XXH64_mergeRound(h64, v4); + + } else { + h64 = seed + PRIME64_5; + } + + h64 += (U64) len; + + return XXH64_finalize(h64, p, len, endian, align); +} + + +XXH_PUBLIC_API unsigned long long XXH64 (const void* input, size_t len, unsigned long long seed) +{ +#if 0 + /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ + XXH64_state_t state; + XXH64_reset(&state, seed); + XXH64_update(&state, input, len); + return XXH64_digest(&state); +#else + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if (XXH_FORCE_ALIGN_CHECK) { + if ((((size_t)input) & 7)==0) { /* Input is aligned, let's leverage the speed advantage */ + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned); + else + return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned); + } } + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned); + else + return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned); +#endif +} + +/*====== Hash Streaming ======*/ + +XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void) +{ + return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t)); +} +XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr) +{ + XXH_free(statePtr); + return XXH_OK; +} + +XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dstState, const XXH64_state_t* srcState) +{ + memcpy(dstState, srcState, sizeof(*dstState)); +} + +XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, unsigned long long seed) +{ + XXH64_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ + memset(&state, 0, sizeof(state)); + state.v1 = seed + PRIME64_1 + PRIME64_2; + state.v2 = seed + PRIME64_2; + state.v3 = seed + 0; + state.v4 = seed - PRIME64_1; + /* do not write into reserved, planned to be removed in a future version */ + memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved)); + return XXH_OK; +} + +FORCE_INLINE XXH_errorcode +XXH64_update_endian (XXH64_state_t* state, const void* input, size_t len, XXH_endianess endian) +{ + if (input==NULL) +#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) + return XXH_OK; +#else + return XXH_ERROR; +#endif + + { const BYTE* p = (const BYTE*)input; + const BYTE* const bEnd = p + len; + + state->total_len += len; + + if (state->memsize + len < 32) { /* fill in tmp buffer */ + XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, len); + state->memsize += (U32)len; + return XXH_OK; + } + + if (state->memsize) { /* tmp buffer is full */ + XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, 32-state->memsize); + state->v1 = XXH64_round(state->v1, XXH_readLE64(state->mem64+0, endian)); + state->v2 = XXH64_round(state->v2, XXH_readLE64(state->mem64+1, endian)); + state->v3 = XXH64_round(state->v3, XXH_readLE64(state->mem64+2, endian)); + state->v4 = XXH64_round(state->v4, XXH_readLE64(state->mem64+3, endian)); + p += 32-state->memsize; + state->memsize = 0; + } + + if (p+32 <= bEnd) { + const BYTE* const limit = bEnd - 32; + U64 v1 = state->v1; + U64 v2 = state->v2; + U64 v3 = state->v3; + U64 v4 = state->v4; + + do { + v1 = XXH64_round(v1, XXH_readLE64(p, endian)); p+=8; + v2 = XXH64_round(v2, XXH_readLE64(p, endian)); p+=8; + v3 = XXH64_round(v3, XXH_readLE64(p, endian)); p+=8; + v4 = XXH64_round(v4, XXH_readLE64(p, endian)); p+=8; + } while (p<=limit); + + state->v1 = v1; + state->v2 = v2; + state->v3 = v3; + state->v4 = v4; + } + + if (p < bEnd) { + XXH_memcpy(state->mem64, p, (size_t)(bEnd-p)); + state->memsize = (unsigned)(bEnd-p); + } + } + + return XXH_OK; +} + +XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* state_in, const void* input, size_t len) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_update_endian(state_in, input, len, XXH_littleEndian); + else + return XXH64_update_endian(state_in, input, len, XXH_bigEndian); +} + +FORCE_INLINE U64 XXH64_digest_endian (const XXH64_state_t* state, XXH_endianess endian) +{ + U64 h64; + + if (state->total_len >= 32) { + U64 const v1 = state->v1; + U64 const v2 = state->v2; + U64 const v3 = state->v3; + U64 const v4 = state->v4; + + h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18); + h64 = XXH64_mergeRound(h64, v1); + h64 = XXH64_mergeRound(h64, v2); + h64 = XXH64_mergeRound(h64, v3); + h64 = XXH64_mergeRound(h64, v4); + } else { + h64 = state->v3 /*seed*/ + PRIME64_5; + } + + h64 += (U64) state->total_len; + + return XXH64_finalize(h64, state->mem64, (size_t)state->total_len, endian, XXH_aligned); +} + +XXH_PUBLIC_API unsigned long long XXH64_digest (const XXH64_state_t* state_in) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_digest_endian(state_in, XXH_littleEndian); + else + return XXH64_digest_endian(state_in, XXH_bigEndian); +} + + +/*====== Canonical representation ======*/ + +XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash) +{ + XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t)); + if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash); + memcpy(dst, &hash, sizeof(*dst)); +} + +XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src) +{ + return XXH_readBE64(src); +} + +#endif /* XXH_NO_LONG_LONG */ diff --git a/TMessagesProj/jni/lz4/xxhash.h b/TMessagesProj/jni/lz4/xxhash.h new file mode 100755 index 000000000..d6bad9433 --- /dev/null +++ b/TMessagesProj/jni/lz4/xxhash.h @@ -0,0 +1,328 @@ +/* + xxHash - Extremely Fast Hash algorithm + Header File + Copyright (C) 2012-2016, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - xxHash source repository : https://github.com/Cyan4973/xxHash +*/ + +/* Notice extracted from xxHash homepage : + +xxHash is an extremely fast Hash algorithm, running at RAM speed limits. +It also successfully passes all tests from the SMHasher suite. + +Comparison (single thread, Windows Seven 32 bits, using SMHasher on a Core 2 Duo @3GHz) + +Name Speed Q.Score Author +xxHash 5.4 GB/s 10 +CrapWow 3.2 GB/s 2 Andrew +MumurHash 3a 2.7 GB/s 10 Austin Appleby +SpookyHash 2.0 GB/s 10 Bob Jenkins +SBox 1.4 GB/s 9 Bret Mulvey +Lookup3 1.2 GB/s 9 Bob Jenkins +SuperFastHash 1.2 GB/s 1 Paul Hsieh +CityHash64 1.05 GB/s 10 Pike & Alakuijala +FNV 0.55 GB/s 5 Fowler, Noll, Vo +CRC32 0.43 GB/s 9 +MD5-32 0.33 GB/s 10 Ronald L. Rivest +SHA1-32 0.28 GB/s 10 + +Q.Score is a measure of quality of the hash function. +It depends on successfully passing SMHasher test set. +10 is a perfect score. + +A 64-bit version, named XXH64, is available since r35. +It offers much better speed, but for 64-bit applications only. +Name Speed on 64 bits Speed on 32 bits +XXH64 13.8 GB/s 1.9 GB/s +XXH32 6.8 GB/s 6.0 GB/s +*/ + +#ifndef XXHASH_H_5627135585666179 +#define XXHASH_H_5627135585666179 1 + +#if defined (__cplusplus) +extern "C" { +#endif + + +/* **************************** +* Definitions +******************************/ +#include /* size_t */ +typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode; + + +/* **************************** + * API modifier + ******************************/ +/** XXH_INLINE_ALL (and XXH_PRIVATE_API) + * This is useful to include xxhash functions in `static` mode + * in order to inline them, and remove their symbol from the public list. + * Inlining can offer dramatic performance improvement on small keys. + * Methodology : + * #define XXH_INLINE_ALL + * #include "xxhash.h" + * `xxhash.c` is automatically included. + * It's not useful to compile and link it as a separate module. + */ +#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) +# ifndef XXH_STATIC_LINKING_ONLY +# define XXH_STATIC_LINKING_ONLY +# endif +# if defined(__GNUC__) +# define XXH_PUBLIC_API static __inline __attribute__((unused)) +# elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# define XXH_PUBLIC_API static inline +# elif defined(_MSC_VER) +# define XXH_PUBLIC_API static __inline +# else + /* this version may generate warnings for unused static functions */ +# define XXH_PUBLIC_API static +# endif +#else +# define XXH_PUBLIC_API /* do nothing */ +#endif /* XXH_INLINE_ALL || XXH_PRIVATE_API */ + +/*! XXH_NAMESPACE, aka Namespace Emulation : + * + * If you want to include _and expose_ xxHash functions from within your own library, + * but also want to avoid symbol collisions with other libraries which may also include xxHash, + * + * you can use XXH_NAMESPACE, to automatically prefix any public symbol from xxhash library + * with the value of XXH_NAMESPACE (therefore, avoid NULL and numeric values). + * + * Note that no change is required within the calling program as long as it includes `xxhash.h` : + * regular symbol name will be automatically translated by this header. + */ +#ifdef XXH_NAMESPACE +# define XXH_CAT(A,B) A##B +# define XXH_NAME2(A,B) XXH_CAT(A,B) +# define XXH_versionNumber XXH_NAME2(XXH_NAMESPACE, XXH_versionNumber) +# define XXH32 XXH_NAME2(XXH_NAMESPACE, XXH32) +# define XXH32_createState XXH_NAME2(XXH_NAMESPACE, XXH32_createState) +# define XXH32_freeState XXH_NAME2(XXH_NAMESPACE, XXH32_freeState) +# define XXH32_reset XXH_NAME2(XXH_NAMESPACE, XXH32_reset) +# define XXH32_update XXH_NAME2(XXH_NAMESPACE, XXH32_update) +# define XXH32_digest XXH_NAME2(XXH_NAMESPACE, XXH32_digest) +# define XXH32_copyState XXH_NAME2(XXH_NAMESPACE, XXH32_copyState) +# define XXH32_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH32_canonicalFromHash) +# define XXH32_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH32_hashFromCanonical) +# define XXH64 XXH_NAME2(XXH_NAMESPACE, XXH64) +# define XXH64_createState XXH_NAME2(XXH_NAMESPACE, XXH64_createState) +# define XXH64_freeState XXH_NAME2(XXH_NAMESPACE, XXH64_freeState) +# define XXH64_reset XXH_NAME2(XXH_NAMESPACE, XXH64_reset) +# define XXH64_update XXH_NAME2(XXH_NAMESPACE, XXH64_update) +# define XXH64_digest XXH_NAME2(XXH_NAMESPACE, XXH64_digest) +# define XXH64_copyState XXH_NAME2(XXH_NAMESPACE, XXH64_copyState) +# define XXH64_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH64_canonicalFromHash) +# define XXH64_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH64_hashFromCanonical) +#endif + + +/* ************************************* +* Version +***************************************/ +#define XXH_VERSION_MAJOR 0 +#define XXH_VERSION_MINOR 6 +#define XXH_VERSION_RELEASE 5 +#define XXH_VERSION_NUMBER (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE) +XXH_PUBLIC_API unsigned XXH_versionNumber (void); + + +/*-********************************************************************** +* 32-bit hash +************************************************************************/ +typedef unsigned int XXH32_hash_t; + +/*! XXH32() : + Calculate the 32-bit hash of sequence "length" bytes stored at memory address "input". + The memory between input & input+length must be valid (allocated and read-accessible). + "seed" can be used to alter the result predictably. + Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark) : 5.4 GB/s */ +XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t length, unsigned int seed); + +/*====== Streaming ======*/ +typedef struct XXH32_state_s XXH32_state_t; /* incomplete type */ +XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void); +XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr); +XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dst_state, const XXH32_state_t* src_state); + +XXH_PUBLIC_API XXH_errorcode XXH32_reset (XXH32_state_t* statePtr, unsigned int seed); +XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* statePtr, const void* input, size_t length); +XXH_PUBLIC_API XXH32_hash_t XXH32_digest (const XXH32_state_t* statePtr); + +/* + * Streaming functions generate the xxHash of an input provided in multiple segments. + * Note that, for small input, they are slower than single-call functions, due to state management. + * For small inputs, prefer `XXH32()` and `XXH64()`, which are better optimized. + * + * XXH state must first be allocated, using XXH*_createState() . + * + * Start a new hash by initializing state with a seed, using XXH*_reset(). + * + * Then, feed the hash state by calling XXH*_update() as many times as necessary. + * The function returns an error code, with 0 meaning OK, and any other value meaning there is an error. + * + * Finally, a hash value can be produced anytime, by using XXH*_digest(). + * This function returns the nn-bits hash as an int or long long. + * + * It's still possible to continue inserting input into the hash state after a digest, + * and generate some new hashes later on, by calling again XXH*_digest(). + * + * When done, free XXH state space if it was allocated dynamically. + */ + +/*====== Canonical representation ======*/ + +typedef struct { unsigned char digest[4]; } XXH32_canonical_t; +XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash); +XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src); + +/* Default result type for XXH functions are primitive unsigned 32 and 64 bits. + * The canonical representation uses human-readable write convention, aka big-endian (large digits first). + * These functions allow transformation of hash result into and from its canonical format. + * This way, hash values can be written into a file / memory, and remain comparable on different systems and programs. + */ + + +#ifndef XXH_NO_LONG_LONG +/*-********************************************************************** +* 64-bit hash +************************************************************************/ +typedef unsigned long long XXH64_hash_t; + +/*! XXH64() : + Calculate the 64-bit hash of sequence of length "len" stored at memory address "input". + "seed" can be used to alter the result predictably. + This function runs faster on 64-bit systems, but slower on 32-bit systems (see benchmark). +*/ +XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t length, unsigned long long seed); + +/*====== Streaming ======*/ +typedef struct XXH64_state_s XXH64_state_t; /* incomplete type */ +XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void); +XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr); +XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dst_state, const XXH64_state_t* src_state); + +XXH_PUBLIC_API XXH_errorcode XXH64_reset (XXH64_state_t* statePtr, unsigned long long seed); +XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* statePtr, const void* input, size_t length); +XXH_PUBLIC_API XXH64_hash_t XXH64_digest (const XXH64_state_t* statePtr); + +/*====== Canonical representation ======*/ +typedef struct { unsigned char digest[8]; } XXH64_canonical_t; +XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash); +XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src); +#endif /* XXH_NO_LONG_LONG */ + + + +#ifdef XXH_STATIC_LINKING_ONLY + +/* ================================================================================================ + This section contains declarations which are not guaranteed to remain stable. + They may change in future versions, becoming incompatible with a different version of the library. + These declarations should only be used with static linking. + Never use them in association with dynamic linking ! +=================================================================================================== */ + +/* These definitions are only present to allow + * static allocation of XXH state, on stack or in a struct for example. + * Never **ever** use members directly. */ + +#if !defined (__VMS) \ + && (defined (__cplusplus) \ + || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + +struct XXH32_state_s { + uint32_t total_len_32; + uint32_t large_len; + uint32_t v1; + uint32_t v2; + uint32_t v3; + uint32_t v4; + uint32_t mem32[4]; + uint32_t memsize; + uint32_t reserved; /* never read nor write, might be removed in a future version */ +}; /* typedef'd to XXH32_state_t */ + +struct XXH64_state_s { + uint64_t total_len; + uint64_t v1; + uint64_t v2; + uint64_t v3; + uint64_t v4; + uint64_t mem64[4]; + uint32_t memsize; + uint32_t reserved[2]; /* never read nor write, might be removed in a future version */ +}; /* typedef'd to XXH64_state_t */ + +# else + +struct XXH32_state_s { + unsigned total_len_32; + unsigned large_len; + unsigned v1; + unsigned v2; + unsigned v3; + unsigned v4; + unsigned mem32[4]; + unsigned memsize; + unsigned reserved; /* never read nor write, might be removed in a future version */ +}; /* typedef'd to XXH32_state_t */ + +# ifndef XXH_NO_LONG_LONG /* remove 64-bit support */ +struct XXH64_state_s { + unsigned long long total_len; + unsigned long long v1; + unsigned long long v2; + unsigned long long v3; + unsigned long long v4; + unsigned long long mem64[4]; + unsigned memsize; + unsigned reserved[2]; /* never read nor write, might be removed in a future version */ +}; /* typedef'd to XXH64_state_t */ +# endif + +# endif + + +#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) +# include "xxhash.c" /* include xxhash function bodies as `static`, for inlining */ +#endif + +#endif /* XXH_STATIC_LINKING_ONLY */ + + +#if defined (__cplusplus) +} +#endif + +#endif /* XXHASH_H_5627135585666179 */ diff --git a/TMessagesProj/jni/rlottie/inc/rlottie.h b/TMessagesProj/jni/rlottie/inc/rlottie.h new file mode 100755 index 000000000..adfa05d39 --- /dev/null +++ b/TMessagesProj/jni/rlottie/inc/rlottie.h @@ -0,0 +1,438 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _RLOTTIE_H_ +#define _RLOTTIE_H_ + +#include +#include +#include + +#ifdef _WIN32 +#ifdef LOT_BUILD +#ifdef DLL_EXPORT +#define LOT_EXPORT __declspec(dllexport) +#else +#define LOT_EXPORT +#endif +#else +#define LOT_EXPORT __declspec(dllimport) +#endif +#else +#ifdef __GNUC__ +#if __GNUC__ >= 4 +#define LOT_EXPORT __attribute__((visibility("default"))) +#else +#define LOT_EXPORT +#endif +#else +#define LOT_EXPORT +#endif +#endif + +class AnimationImpl; +struct LOTNode; +struct LOTLayerNode; + +namespace rlottie { + +struct Color { + Color() = default; + Color(float r, float g , float b):_r(r), _g(g), _b(b){} + float r() const {return _r;} + float g() const {return _g;} + float b() const {return _b;} +private: + float _r{0}; + float _g{0}; + float _b{0}; +}; + +struct Size { + Size() = default; + Size(float w, float h):_w(w), _h(h){} + float w() const {return _w;} + float h() const {return _h;} +private: + float _w{0}; + float _h{0}; +}; + +struct Point { + Point() = default; + Point(float x, float y):_x(x), _y(y){} + float x() const {return _x;} + float y() const {return _y;} +private: + float _x{0}; + float _y{0}; +}; + +struct FrameInfo { + explicit FrameInfo(uint32_t frame): _frameNo(frame){} + uint32_t curFrame() const {return _frameNo;} +private: + uint32_t _frameNo; +}; + +enum class Property { + Color, /*!< Color property of Fill object , value type is rlottie::Color */ + FillOpacity, /*!< Opacity property of Fill object , value type is float [ 0 .. 100] */ + StrokeOpacity, /*!< Opacity property of Stroke object , value type is float [ 0 .. 100] */ + StrokeWidth, /*!< stroke with property of Stroke object , value type is float */ + TrAnchor, /*!< Transform Anchor property of Layer and Group object , value type is rlottie::Point */ + TrPosition, /*!< Transform Position property of Layer and Group object , value type is rlottie::Point */ + TrScale, /*!< Transform Scale property of Layer and Group object , value type is rlottie::Size. range[0 ..100] */ + TrRotation, /*!< Transform Scale property of Layer and Group object , value type is float. range[0 .. 360] in degrees*/ + TrOpacity /*!< Transform Opacity property of Layer and Group object , value type is float [ 0 .. 100] */ +}; + +struct Color_Type{}; +struct Point_Type{}; +struct Size_Type{}; +struct Float_Type{}; +template struct MapType; + +class LOT_EXPORT Surface { +public: + /** + * @brief Surface object constructor. + * + * @param[in] buffer surface buffer. + * @param[in] width surface width. + * @param[in] height surface height. + * @param[in] bytesPerLine number of bytes in a surface scanline. + * + * @note Default surface format is ARGB32_Premultiplied. + * + * @internal + */ + Surface(uint32_t *buffer, size_t width, size_t height, size_t bytesPerLine); + + /** + * @brief Sets the Draw Area available on the Surface. + * + * Lottie will use the draw region size to generate frame image + * and will update only the draw rgion of the surface. + * + * @param[in] x region area x position. + * @param[in] y region area y position. + * @param[in] width region area width. + * @param[in] height region area height. + * + * @note Default surface format is ARGB32_Premultiplied. + * @note Default draw region area is [ 0 , 0, surface width , surface height] + * + * @internal + */ + void setDrawRegion(size_t x, size_t y, size_t width, size_t height); + + /** + * @brief Returns width of the surface. + * + * @return surface width + * + * @internal + * + */ + size_t width() const {return mWidth;} + + /** + * @brief Returns height of the surface. + * + * @return surface height + * + * @internal + */ + size_t height() const {return mHeight;} + + /** + * @brief Returns number of bytes in the surface scanline. + * + * @return number of bytes in scanline. + * + * @internal + */ + size_t bytesPerLine() const {return mBytesPerLine;} + + /** + * @brief Returns buffer attached tp the surface. + * + * @return buffer attaced to the Surface. + * + * @internal + */ + uint32_t *buffer() const {return mBuffer;} + + /** + * @brief Returns drawable area width of the surface. + * + * @return drawable area width + * + * @note Default value is width() of the surface + * + * @internal + * + */ + size_t drawRegionWidth() const {return mDrawArea.w;} + + /** + * @brief Returns drawable area height of the surface. + * + * @return drawable area height + * + * @note Default value is height() of the surface + * + * @internal + */ + size_t drawRegionHeight() const {return mDrawArea.h;} + + /** + * @brief Returns drawable area's x position of the surface. + * + * @return drawable area's x potition. + * + * @note Default value is 0 + * + * @internal + */ + size_t drawRegionPosX() const {return mDrawArea.x;} + + /** + * @brief Returns drawable area's y position of the surface. + * + * @return drawable area's y potition. + * + * @note Default value is 0 + * + * @internal + */ + size_t drawRegionPosY() const {return mDrawArea.y;} + + /** + * @brief Default constructor. + */ + Surface() = default; +private: + uint32_t *mBuffer{nullptr}; + size_t mWidth{0}; + size_t mHeight{0}; + size_t mBytesPerLine{0}; + struct { + size_t x{0}; + size_t y{0}; + size_t w{0}; + size_t h{0}; + }mDrawArea; +}; + +using LayerInfoList = std::vector>; + +class LOT_EXPORT Animation { +public: + + /** + * @brief Constructs an animation object from file path. + * + * @param[in] path Lottie resource file path + * + * @return Animation object that can render the contents of the + * Lottie resource represented by file path. + * + * @internal + */ + static std::unique_ptr + loadFromFile(const std::string &path); + + /** + * @brief Constructs an animation object from JSON string data. + * + * @param[in] jsonData The JSON string data. + * @param[in] key the string that will be used to cache the JSON string data. + * @param[in] resourcePath the path will be used to search for external resource. + * + * @return Animation object that can render the contents of the + * Lottie resource represented by JSON string data. + * + * @internal + */ + static std::unique_ptr + loadFromData(std::string jsonData, const std::string &key, const std::string &resourcePath=""); + + /** + * @brief Returns default framerate of the Lottie resource. + * + * @return framerate of the Lottie resource + * + * @internal + * + */ + double frameRate() const; + + /** + * @brief Returns total number of frames present in the Lottie resource. + * + * @return frame count of the Lottie resource. + * + * @note frame number starts with 0. + * + * @internal + */ + size_t totalFrame() const; + + /** + * @brief Returns default viewport size of the Lottie resource. + * + * @param[out] width default width of the viewport. + * @param[out] height default height of the viewport. + * + * @internal + * + */ + void size(size_t &width, size_t &height) const; + + /** + * @brief Returns total animation duration of Lottie resource in second. + * it uses totalFrame() and frameRate() to calculate the duration. + * duration = totalFrame() / frameRate(). + * + * @return total animation duration in second. + * @retval 0 if the Lottie resource has no animation. + * + * @see totalFrame() + * @see frameRate() + * + * @internal + */ + double duration() const; + + /** + * @brief Returns frame number for a given position. + * this function helps to map the position value retuned + * by the animator to a frame number in side the Lottie resource. + * frame_number = lerp(start_frame, endframe, pos); + * + * @param[in] pos normalized position value [0 ... 1] + * + * @return frame numer maps to the position value [startFrame .... endFrame] + * + * @internal + */ + size_t frameAtPos(double pos); + + /** + * @brief Renders the content to surface synchronously. + * for performance use the async rendering @see render + * + * @param[in] frameNo Content corresponds to the @p frameNo needs to be drawn + * @param[in] surface Surface in which content will be drawn + * + * @internal + */ + void renderSync(size_t frameNo, Surface surface); + + /** + * @brief Returns root layer of the composition updated with + * content of the Lottie resource at frame number @p frameNo. + * + * @param[in] frameNo Content corresponds to the @p frameNo needs to be extracted. + * @param[in] width content viewbox width + * @param[in] height content viewbox height + * + * @return Root layer node. + * + * @internal + */ + const LOTLayerNode * renderTree(size_t frameNo, size_t width, size_t height) const; + + /** + * @brief Returns Layer information{name, inFrame, outFrame} of all the child layers of the composition. + * + * + * @return List of Layer Information of the Composition. + * + * @see LayerInfoList + * @internal + */ + const LayerInfoList& layers() const; + + /** + * @brief Sets property value for the specified {@link KeyPath}. This {@link KeyPath} can resolve + * to multiple contents. In that case, the callback's value will apply to all of them. + * + * Keypath should conatin object names separated by (.) and can handle globe(**) or wildchar(*). + * + * @usage + * To change fillcolor property of fill1 object in the layer1->group1->fill1 hirarchy to RED color + * + * player->setValue("layer1.group1.fill1", rlottie::Color(1, 0, 0); + * + * if all the color property inside group1 needs to be changed to GREEN color + * + * player->setValue("**.group1.**", rlottie::Color(0, 1, 0); + * + * @internal + */ + template + void setValue(const std::string &keypath, AnyValue value) + { + setValue(MapType>{}, prop, keypath, value); + } + + /** + * @brief default destructor + * + * @internal + */ + ~Animation(); + +private: + void setValue(Color_Type, Property, const std::string &, Color); + void setValue(Float_Type, Property, const std::string &, float); + void setValue(Size_Type, Property, const std::string &, Size); + void setValue(Point_Type, Property, const std::string &, Point); + + void setValue(Color_Type, Property, const std::string &, std::function &&); + void setValue(Float_Type, Property, const std::string &, std::function &&); + void setValue(Size_Type, Property, const std::string &, std::function &&); + void setValue(Point_Type, Property, const std::string &, std::function &&); + /** + * @brief default constructor + * + * @internal + */ + Animation(); + + std::unique_ptr d; +}; + +//Map Property to Value type +template<> struct MapType>: Color_Type{}; +template<> struct MapType>: Float_Type{}; +template<> struct MapType>: Float_Type{}; +template<> struct MapType>: Float_Type{}; +template<> struct MapType>: Float_Type{}; +template<> struct MapType>: Float_Type{}; +template<> struct MapType>: Point_Type{}; +template<> struct MapType>: Point_Type{}; +template<> struct MapType>: Size_Type{}; + + +} // namespace lotplayer + +#endif // _RLOTTIE_H_ diff --git a/TMessagesProj/jni/rlottie/inc/rlottie_capi.h b/TMessagesProj/jni/rlottie/inc/rlottie_capi.h new file mode 100755 index 000000000..a5cb70ad5 --- /dev/null +++ b/TMessagesProj/jni/rlottie/inc/rlottie_capi.h @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _RLOTTIE_CAPI_H_ +#define _RLOTTIE_CAPI_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Lottie_Animation_S Lottie_Animation; + +/** + * @brief Constructs an animation object from file path. + * + * @param[in] path Lottie resource file path + * + * @return Animation object that can build the contents of the + * Lottie resource represented by file path. + * + * @see lottie_animation_destroy() + * + * @ingroup Lottie_Animation + * @internal + */ +LOT_EXPORT Lottie_Animation *lottie_animation_from_file(const char *path); + +/** + * @brief Constructs an animation object from JSON string data. + * + * @param[in] data The JSON string data. + * @param[in] key the string that will be used to cache the JSON string data. + * @param[in] resource_path the path that will be used to load external resource needed by the JSON data. + * + * @return Animation object that can build the contents of the + * Lottie resource represented by JSON string data. + * + * @ingroup Lottie_Animation + * @internal + */ +LOT_EXPORT Lottie_Animation *lottie_animation_from_data(const char *data, const char *key, const char *resource_path); + +/** + * @brief Free given Animation object resource. + * + * @param[in] animation Animation object to free. + * + * @see lottie_animation_from_file() + * @see lottie_animation_from_data() + * + * @ingroup Lottie_Animation + * @internal + */ +LOT_EXPORT void lottie_animation_destroy(Lottie_Animation *animation); + +/** + * @brief Returns default viewport size of the Lottie resource. + * + * @param[in] animation Animation object. + * @param[out] w default width of the viewport. + * @param[out] h default height of the viewport. + * + * @ingroup Lottie_Animation + * @internal + */ +LOT_EXPORT void lottie_animation_get_size(const Lottie_Animation *animation, size_t *width, size_t *height); + +/** + * @brief Returns total animation duration of Lottie resource in second. + * it uses totalFrame() and frameRate() to calculate the duration. + * duration = totalFrame() / frameRate(). + * + * @param[in] animation Animation object. + * + * @return total animation duration in second. + * @c 0 if the Lottie resource has no animation. + * + * @see lottie_animation_get_totalframe() + * @see lottie_animation_get_framerate() + * + * @ingroup Lottie_Animation + * @internal + */ +LOT_EXPORT double lottie_animation_get_duration(const Lottie_Animation *animation); + +/** + * @brief Returns total number of frames present in the Lottie resource. + * + * @param[in] animation Animation object. + * + * @return frame count of the Lottie resource.* + * + * @note frame number starts with 0. + * + * @see lottie_animation_get_duration() + * @see lottie_animation_get_framerate() + * + * @ingroup Lottie_Animation + * @internal + */ +LOT_EXPORT size_t lottie_animation_get_totalframe(const Lottie_Animation *animation); + +/** + * @brief Returns default framerate of the Lottie resource. + * + * @param[in] animation Animation object. + * + * @return framerate of the Lottie resource + * + * @ingroup Lottie_Animation + * @internal + * + */ +LOT_EXPORT double lottie_animation_get_framerate(const Lottie_Animation *animation); + +/** + * @brief Get the render tree which contains the snapshot of the animation object + * at frame = @c frame_num, the content of the animation in that frame number. + * + * @param[in] animation Animation object. + * @param[in] frame_num Content corresponds to the @p frame_num needs to be drawn + * @param[in] width requested snapshot viewport width. + * @param[in] height requested snapshot viewport height. + * + * @return Animation snapshot tree. + * + * @note: User has to traverse the tree for rendering. + * + * @see LOTLayerNode + * @see LOTNode + * + * @ingroup Lottie_Animation + * @internal + */ +LOT_EXPORT const LOTLayerNode * lottie_animation_render_tree(Lottie_Animation *animation, + size_t frame_num, + size_t width, size_t height); + +/** + * @brief Maps position to frame number and returns it. + * + * @param[in] animation Animation object. + * @param[in] pos position in the range [ 0.0 .. 1.0 ]. + * + * @return mapped frame numbe in the range [ start_frame .. end_frame ]. + * @c 0 if the Lottie resource has no animation. + * + * + * @ingroup Lottie_Animation + * @internal + */ +LOT_EXPORT size_t lottie_animation_get_frame_at_pos(const Lottie_Animation *animation, float pos); + +/** + * @brief Request to render the content of the frame @p frame_num to buffer @p buffer. + * + * @param[in] animation Animation object. + * @param[in] frame_num the frame number needs to be rendered. + * @param[in] buffer surface buffer use for rendering. + * @param[in] width width of the surface + * @param[in] height height of the surface + * @param[in] bytes_per_line stride of the surface in bytes. + * + * + * @ingroup Lottie_Animation + * @internal + */ +LOT_EXPORT void +lottie_animation_render(Lottie_Animation *animation, + size_t frame_num, + uint32_t *buffer, + size_t width, + size_t height, + size_t bytes_per_line); + +/** + * @brief Request to render the content of the frame @p frame_num to buffer @p buffer asynchronously. + * + * @param[in] animation Animation object. + * @param[in] frame_num the frame number needs to be rendered. + * @param[in] buffer surface buffer use for rendering. + * @param[in] width width of the surface + * @param[in] height height of the surface + * @param[in] bytes_per_line stride of the surface in bytes. + * + * @note user must call lottie_animation_render_flush() to make sure render is finished. + * + * @ingroup Lottie_Animation + * @internal + */ +LOT_EXPORT void +lottie_animation_render_async(Lottie_Animation *animation, + size_t frame_num, + uint32_t *buffer, + size_t width, + size_t height, + size_t bytes_per_line); + +/** + * @brief Request to finish the current async renderer job for this animation object. + * If render is finished then this call returns immidiately. + * If not, it waits till render job finish and then return. + * + * @param[in] animation Animation object. + * + * @warning User must call lottie_animation_render_async() and lottie_animation_render_flush() + * in pair to get the benefit of async rendering. + * + * @return the pixel buffer it finished rendering. + * + * @ingroup Lottie_Animation + * @internal + */ +LOT_EXPORT uint32_t * +lottie_animation_render_flush(Lottie_Animation *animation); + +#ifdef __cplusplus +} +#endif + +#endif //_RLOTTIE_CAPI_H_ + diff --git a/TMessagesProj/jni/rlottie/inc/rlottiecommon.h b/TMessagesProj/jni/rlottie/inc/rlottiecommon.h new file mode 100755 index 000000000..b3ac0229e --- /dev/null +++ b/TMessagesProj/jni/rlottie/inc/rlottiecommon.h @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _RLOTTIE_COMMON_H_ +#define _RLOTTIE_COMMON_H_ + +#ifdef _WIN32 +#ifdef LOT_BUILD +#ifdef DLL_EXPORT +#define LOT_EXPORT __declspec(dllexport) +#else +#define LOT_EXPORT +#endif +#else +#define LOT_EXPORT __declspec(dllimport) +#endif +#else +#ifdef __GNUC__ +#if __GNUC__ >= 4 +#define LOT_EXPORT __attribute__((visibility("default"))) +#else +#define LOT_EXPORT +#endif +#else +#define LOT_EXPORT +#endif +#endif + + +/** + * @defgroup Lottie_Animation Lottie_Animation + * + * Lottie Animation is a modern style vector based animation design. Its animation + * resource(within json format) could be generated by Adobe After Effect using + * bodymovin plugin. You can find a good examples in Lottie Community which + * shares many free resources(see: www.lottiefiles.com). + * + * This Lottie_Animation is a common engine to manipulate, control Lottie + * Animation from the Lottie resource - json file. It provides a scene-graph + * node tree per frames by user demand as well as rasterized frame images. + * + */ + +/** + * @ingroup Lottie_Animation + */ + + +/** + * @brief Enumeration for Lottie Player error code. + */ +typedef enum +{ + //TODO: Coding convention?? + LOT_ANIMATION_ERROR_NONE = 0, + LOT_ANIMATION_ERROR_NOT_PERMITTED, + LOT_ANIMATION_ERROR_OUT_OF_MEMORY, + LOT_ANIMATION_ERROR_INVALID_PARAMETER, + LOT_ANIMATION_ERROR_RESULT_OUT_OF_RANGE, + LOT_ANIMATION_ERROR_ALREADY_IN_PROGRESS, + LOT_ANIMATION_ERROR_UNKNOWN +} LOTErrorType; + +typedef enum +{ + BrushSolid = 0, + BrushGradient +} LOTBrushType; + +typedef enum +{ + FillEvenOdd = 0, + FillWinding +} LOTFillRule; + +typedef enum +{ + JoinMiter = 0, + JoinBevel, + JoinRound +} LOTJoinStyle; + +typedef enum +{ + CapFlat = 0, + CapSquare, + CapRound +} LOTCapStyle; + +typedef enum +{ + GradientLinear = 0, + GradientRadial +} LOTGradientType; + +typedef struct LOTGradientStop +{ + float pos; + unsigned char r, g, b, a; +} LOTGradientStop; + +typedef enum +{ + MaskAdd = 0, + MaskSubstract, + MaskIntersect, + MaskDifference +} LOTMaskType; + +typedef struct LOTMask { + struct { + const float *ptPtr; + int ptCount; + const char* elmPtr; + int elmCount; + } mPath; + LOTMaskType mMode; + int mAlpha; +}LOTMask; + +typedef enum +{ + MatteNone = 0, + MatteAlpha, + MatteAlphaInv, + MatteLuma, + MatteLumaInv +} LOTMatteType; + +typedef struct LOTNode { + +#define ChangeFlagNone 0x0000 +#define ChangeFlagPath 0x0001 +#define ChangeFlagPaint 0x0010 +#define ChangeFlagAll (ChangeFlagPath & ChangeFlagPaint) + + struct { + const float *ptPtr; + int ptCount; + const char* elmPtr; + int elmCount; + } mPath; + + struct { + unsigned char r, g, b, a; + } mColor; + + struct { + unsigned char enable; + int width; + LOTCapStyle cap; + LOTJoinStyle join; + int meterLimit; + float* dashArray; + int dashArraySize; + } mStroke; + + struct { + LOTGradientType type; + LOTGradientStop *stopPtr; + unsigned int stopCount; + struct { + float x, y; + } start, end, center, focal; + float cradius; + float fradius; + } mGradient; + + struct { + unsigned char* data; + int width; + int height; + struct { + float m11; float m12; float m13; + float m21; float m22; float m23; + float m31; float m32; float m33; + } mMatrix; + } mImageInfo; + + int mFlag; + LOTBrushType mBrushType; + LOTFillRule mFillRule; +} LOTNode; + + + +typedef struct LOTLayerNode { + + struct { + LOTMask *ptr; + unsigned int size; + } mMaskList; + + struct { + const float *ptPtr; + int ptCount; + const char* elmPtr; + int elmCount; + } mClipPath; + + struct { + struct LOTLayerNode **ptr; + unsigned int size; + } mLayerList; + + struct { + LOTNode **ptr; + unsigned int size; + } mNodeList; + + LOTMatteType mMatte; + int mVisible; + int mAlpha; + const char *name; + +} LOTLayerNode; + +/** + * @} + */ + +#endif // _RLOTTIE_COMMON_H_ diff --git a/TMessagesProj/jni/rlottie/licenses/COPYING.FTL b/TMessagesProj/jni/rlottie/licenses/COPYING.FTL new file mode 100755 index 000000000..5f673ffad --- /dev/null +++ b/TMessagesProj/jni/rlottie/licenses/COPYING.FTL @@ -0,0 +1,166 @@ + The FreeType Project LICENSE + ---------------------------- + + 2006-Jan-27 + + Copyright 1996-2002, 2006 by + David Turner, Robert Wilhelm, and Werner Lemberg + + + +Introduction +============ + + The FreeType Project is distributed in several archive packages; + some of them may contain, in addition to the FreeType font engine, + various tools and contributions which rely on, or relate to, the + FreeType Project. + + This license applies to all files found in such packages, and + which do not fall under their own explicit license. The license + affects thus the FreeType font engine, the test programs, + documentation and makefiles, at the very least. + + This license was inspired by the BSD, Artistic, and IJG + (Independent JPEG Group) licenses, which all encourage inclusion + and use of free software in commercial and freeware products + alike. As a consequence, its main points are that: + + o We don't promise that this software works. However, we will be + interested in any kind of bug reports. (`as is' distribution) + + o You can use this software for whatever you want, in parts or + full form, without having to pay us. (`royalty-free' usage) + + o You may not pretend that you wrote this software. If you use + it, or only parts of it, in a program, you must acknowledge + somewhere in your documentation that you have used the + FreeType code. (`credits') + + We specifically permit and encourage the inclusion of this + software, with or without modifications, in commercial products. + We disclaim all warranties covering The FreeType Project and + assume no liability related to The FreeType Project. + + + Finally, many people asked us for a preferred form for a + credit/disclaimer to use in compliance with this license. We thus + encourage you to use the following text: + + """ + Portions of this software are copyright � The FreeType + Project (www.freetype.org). All rights reserved. + """ + + Please replace with the value from the FreeType version you + actually use. + + +Legal Terms +=========== + +0. Definitions +-------------- + + Throughout this license, the terms `package', `FreeType Project', + and `FreeType archive' refer to the set of files originally + distributed by the authors (David Turner, Robert Wilhelm, and + Werner Lemberg) as the `FreeType Project', be they named as alpha, + beta or final release. + + `You' refers to the licensee, or person using the project, where + `using' is a generic term including compiling the project's source + code as well as linking it to form a `program' or `executable'. + This program is referred to as `a program using the FreeType + engine'. + + This license applies to all files distributed in the original + FreeType Project, including all source code, binaries and + documentation, unless otherwise stated in the file in its + original, unmodified form as distributed in the original archive. + If you are unsure whether or not a particular file is covered by + this license, you must contact us to verify this. + + The FreeType Project is copyright (C) 1996-2000 by David Turner, + Robert Wilhelm, and Werner Lemberg. All rights reserved except as + specified below. + +1. No Warranty +-------------- + + THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO + USE, OF THE FREETYPE PROJECT. + +2. Redistribution +----------------- + + This license grants a worldwide, royalty-free, perpetual and + irrevocable right and license to use, execute, perform, compile, + display, copy, create derivative works of, distribute and + sublicense the FreeType Project (in both source and object code + forms) and derivative works thereof for any purpose; and to + authorize others to exercise some or all of the rights granted + herein, subject to the following conditions: + + o Redistribution of source code must retain this license file + (`FTL.TXT') unaltered; any additions, deletions or changes to + the original files must be clearly indicated in accompanying + documentation. The copyright notices of the unaltered, + original files must be preserved in all copies of source + files. + + o Redistribution in binary form must provide a disclaimer that + states that the software is based in part of the work of the + FreeType Team, in the distribution documentation. We also + encourage you to put an URL to the FreeType web page in your + documentation, though this isn't mandatory. + + These conditions apply to any software derived from or based on + the FreeType Project, not just the unmodified files. If you use + our work, you must acknowledge us. However, no fee need be paid + to us. + +3. Advertising +-------------- + + Neither the FreeType authors and contributors nor you shall use + the name of the other for commercial, advertising, or promotional + purposes without specific prior written permission. + + We suggest, but do not require, that you use one or more of the + following phrases to refer to this software in your documentation + or advertising materials: `FreeType Project', `FreeType Engine', + `FreeType library', or `FreeType Distribution'. + + As you have not signed this license, you are not required to + accept it. However, as the FreeType Project is copyrighted + material, only this license, or another one contracted with the + authors, grants you the right to use, distribute, and modify it. + Therefore, by using, distributing, or modifying the FreeType + Project, you indicate that you understand and accept all the terms + of this license. + +4. Contacts +----------- + + There are two mailing lists related to FreeType: + + o freetype@nongnu.org + + Discusses general use and applications of FreeType, as well as + future and wanted additions to the library and distribution. + If you are looking for support, start in this list if you + haven't found anything to help you in the documentation. + + o freetype-devel@nongnu.org + + Discusses bugs, as well as engine internals, design issues, + specific licenses, porting, etc. + + Our home page can be found at + + http://www.freetype.org diff --git a/TMessagesProj/jni/rlottie/licenses/COPYING.LGPL b/TMessagesProj/jni/rlottie/licenses/COPYING.LGPL new file mode 100755 index 000000000..5ab7695ab --- /dev/null +++ b/TMessagesProj/jni/rlottie/licenses/COPYING.LGPL @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/TMessagesProj/jni/rlottie/licenses/COPYING.PIX b/TMessagesProj/jni/rlottie/licenses/COPYING.PIX new file mode 100755 index 000000000..5826f3c73 --- /dev/null +++ b/TMessagesProj/jni/rlottie/licenses/COPYING.PIX @@ -0,0 +1,42 @@ +The following is the MIT license, agreed upon by most contributors. +Copyright holders of new code should use this license statement where +possible. They may also add themselves to the list below. + +/* + * Copyright 1987, 1988, 1989, 1998 The Open Group + * Copyright 1987, 1988, 1989 Digital Equipment Corporation + * Copyright 1999, 2004, 2008 Keith Packard + * Copyright 2000 SuSE, Inc. + * Copyright 2000 Keith Packard, member of The XFree86 Project, Inc. + * Copyright 2004, 2005, 2007, 2008, 2009, 2010 Red Hat, Inc. + * Copyright 2004 Nicholas Miell + * Copyright 2005 Lars Knoll & Zack Rusin, Trolltech + * Copyright 2005 Trolltech AS + * Copyright 2007 Luca Barbato + * Copyright 2008 Aaron Plattner, NVIDIA Corporation + * Copyright 2008 Rodrigo Kumpera + * Copyright 2008 André Tupinambá + * Copyright 2008 Mozilla Corporation + * Copyright 2008 Frederic Plourde + * Copyright 2009, Oracle and/or its affiliates. All rights reserved. + * Copyright 2009, 2010 Nokia Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ diff --git a/TMessagesProj/jni/rlottie/licenses/COPYING.RPD b/TMessagesProj/jni/rlottie/licenses/COPYING.RPD new file mode 100755 index 000000000..7ccc161c8 --- /dev/null +++ b/TMessagesProj/jni/rlottie/licenses/COPYING.RPD @@ -0,0 +1,57 @@ +Tencent is pleased to support the open source community by making RapidJSON available. + +Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. + +If you have downloaded a copy of the RapidJSON binary from Tencent, please note that the RapidJSON binary is licensed under the MIT License. +If you have downloaded a copy of the RapidJSON source code from Tencent, please note that RapidJSON source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms. Your integration of RapidJSON into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within RapidJSON. To avoid the problematic JSON license in your own projects, it's sufficient to exclude the bin/jsonchecker/ directory, as it's the only code under the JSON license. +A copy of the MIT License is included in this file. + +Other dependencies and licenses: + +Open Source Software Licensed Under the BSD License: +-------------------------------------------------------------------- + +The msinttypes r29 +Copyright (c) 2006-2013 Alexander Chemeris +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name of copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Open Source Software Licensed Under the JSON License: +-------------------------------------------------------------------- + +json.org +Copyright (c) 2002 JSON.org +All Rights Reserved. + +JSON_checker +Copyright (c) 2002 JSON.org +All Rights Reserved. + + +Terms of the JSON License: +--------------------------------------------------- + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Terms of the MIT License: +-------------------------------------------------------------------- + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/TMessagesProj/jni/rlottie/licenses/COPYING.STB b/TMessagesProj/jni/rlottie/licenses/COPYING.STB new file mode 100755 index 000000000..1da71ca8d --- /dev/null +++ b/TMessagesProj/jni/rlottie/licenses/COPYING.STB @@ -0,0 +1,17 @@ + MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/TMessagesProj/jni/rlottie/src/lottie/lottieanimation.cpp b/TMessagesProj/jni/rlottie/src/lottie/lottieanimation.cpp new file mode 100755 index 000000000..f18ca4330 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/lottieanimation.cpp @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include "config.h" +#include "lottieitem.h" +#include "lottieloader.h" +#include "lottiemodel.h" +#include "rlottie.h" + +#include + +using namespace rlottie; + +struct RenderTask { + RenderTask() { receiver = sender.get_future(); } + std::promise sender; + std::future receiver; + AnimationImpl * playerImpl{nullptr}; + size_t frameNo{0}; + Surface surface; +}; +using SharedRenderTask = std::shared_ptr; + +class AnimationImpl { +public: + void init(const std::shared_ptr &model); + bool update(size_t frameNo, const VSize &size); + VSize size() const { return mCompItem->size(); } + double duration() const { return mModel->duration(); } + double frameRate() const { return mModel->frameRate(); } + size_t totalFrame() const { return mModel->totalFrame(); } + size_t frameAtPos(double pos) const { return mModel->frameAtPos(pos); } + Surface render(size_t frameNo, const Surface &surface); + const LOTLayerNode * renderTree(size_t frameNo, const VSize &size); + + const LayerInfoList &layerInfoList() const + { + return mModel->layerInfoList(); + } + void setValue(const std::string &keypath, LOTVariant &&value); + void removeFilter(const std::string &keypath, Property prop); + +private: + std::string mFilePath; + std::shared_ptr mModel; + std::unique_ptr mCompItem; + SharedRenderTask mTask; + std::atomic mRenderInProgress; +}; + +void AnimationImpl::setValue(const std::string &keypath, LOTVariant &&value) +{ + if (keypath.empty()) return; + mCompItem->setValue(keypath, value); +} + +const LOTLayerNode *AnimationImpl::renderTree(size_t frameNo, const VSize &size) +{ + if (update(frameNo, size)) { + mCompItem->buildRenderTree(); + } + return mCompItem->renderTree(); +} + +bool AnimationImpl::update(size_t frameNo, const VSize &size) +{ + frameNo += mModel->startFrame(); + + if (frameNo > mModel->endFrame()) frameNo = mModel->endFrame(); + + if (frameNo < mModel->startFrame()) frameNo = mModel->startFrame(); + + mCompItem->resize(size); + return mCompItem->update(frameNo); +} + +Surface AnimationImpl::render(size_t frameNo, const Surface &surface) +{ + bool renderInProgress = mRenderInProgress.load(); + if (renderInProgress) { + vCritical << "Already Rendering Scheduled for this Animation"; + return surface; + } + + mRenderInProgress.store(true); + update(frameNo, + VSize(surface.drawRegionWidth(), surface.drawRegionHeight())); + mCompItem->render(surface); + mRenderInProgress.store(false); + + return surface; +} + +void AnimationImpl::init(const std::shared_ptr &model) +{ + mModel = model; + mCompItem = std::make_unique(mModel.get()); + mRenderInProgress = false; +} + +/** + * \breif Brief abput the Api. + * Description about the setFilePath Api + * @param path add the details + */ +std::unique_ptr Animation::loadFromData( + std::string jsonData, const std::string &key, + const std::string &resourcePath) +{ + if (jsonData.empty()) { + vWarning << "jason data is empty"; + return nullptr; + } + + LottieLoader loader; + if (loader.loadFromData(std::move(jsonData), key, + (resourcePath.empty() ? " " : resourcePath))) { + auto animation = std::unique_ptr(new Animation); + animation->d->init(loader.model()); + return animation; + } + return nullptr; +} + +std::unique_ptr Animation::loadFromFile(const std::string &path) +{ + if (path.empty()) { + vWarning << "File path is empty"; + return nullptr; + } + + LottieLoader loader; + if (loader.load(path)) { + auto animation = std::unique_ptr(new Animation); + animation->d->init(loader.model()); + return animation; + } + return nullptr; +} + +void Animation::size(size_t &width, size_t &height) const +{ + VSize sz = d->size(); + + width = sz.width(); + height = sz.height(); +} + +double Animation::duration() const +{ + return d->duration(); +} + +double Animation::frameRate() const +{ + return d->frameRate(); +} + +size_t Animation::totalFrame() const +{ + return d->totalFrame(); +} + +size_t Animation::frameAtPos(double pos) +{ + return d->frameAtPos(pos); +} + +const LOTLayerNode *Animation::renderTree(size_t frameNo, size_t width, + size_t height) const +{ + return d->renderTree(frameNo, VSize(width, height)); +} + +void Animation::renderSync(size_t frameNo, Surface surface) +{ + d->render(frameNo, surface); +} + +const LayerInfoList &Animation::layers() const +{ + return d->layerInfoList(); +} + +void Animation::setValue(Color_Type, Property prop, const std::string &keypath, + Color value) +{ + d->setValue(keypath, + LOTVariant(prop, [value](const FrameInfo &) { return value; })); +} + +void Animation::setValue(Float_Type, Property prop, const std::string &keypath, + float value) +{ + d->setValue(keypath, + LOTVariant(prop, [value](const FrameInfo &) { return value; })); +} + +void Animation::setValue(Size_Type, Property prop, const std::string &keypath, + Size value) +{ + d->setValue(keypath, + LOTVariant(prop, [value](const FrameInfo &) { return value; })); +} + +void Animation::setValue(Point_Type, Property prop, const std::string &keypath, + Point value) +{ + d->setValue(keypath, + LOTVariant(prop, [value](const FrameInfo &) { return value; })); +} + +void Animation::setValue(Color_Type, Property prop, const std::string &keypath, + std::function &&value) +{ + d->setValue(keypath, LOTVariant(prop, value)); +} + +void Animation::setValue(Float_Type, Property prop, const std::string &keypath, + std::function &&value) +{ + d->setValue(keypath, LOTVariant(prop, value)); +} + +void Animation::setValue(Size_Type, Property prop, const std::string &keypath, + std::function &&value) +{ + d->setValue(keypath, LOTVariant(prop, value)); +} + +void Animation::setValue(Point_Type, Property prop, const std::string &keypath, + std::function &&value) +{ + d->setValue(keypath, LOTVariant(prop, value)); +} + +Animation::Animation() : d(std::make_unique()) {} + +/* + * this is only to supress build fail + * because unique_ptr expects the destructor in the same translation unit. + */ +Animation::~Animation() {} + +Surface::Surface(uint32_t *buffer, size_t width, size_t height, + size_t bytesPerLine) + : mBuffer(buffer), + mWidth(width), + mHeight(height), + mBytesPerLine(bytesPerLine) +{ + mDrawArea.w = mWidth; + mDrawArea.h = mHeight; +} + +void Surface::setDrawRegion(size_t x, size_t y, size_t width, size_t height) +{ + if ((x + width > mWidth) || (y + height > mHeight)) return; + + mDrawArea.x = x; + mDrawArea.y = y; + mDrawArea.w = width; + mDrawArea.h = height; +} + +#ifdef LOTTIE_LOGGING_SUPPORT +void initLogging() +{ +#if defined(__ARM_NEON__) + set_log_level(LogLevel::OFF); +#else + initialize(GuaranteedLogger(), "/tmp/", "rlottie", 1); + set_log_level(LogLevel::INFO); +#endif +} + +V_CONSTRUCTOR_FUNCTION(initLogging) +#endif diff --git a/TMessagesProj/jni/rlottie/src/lottie/lottieitem.cpp b/TMessagesProj/jni/rlottie/src/lottie/lottieitem.cpp new file mode 100755 index 000000000..7305a8580 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/lottieitem.cpp @@ -0,0 +1,1732 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "lottieitem.h" +#include +#include +#include +#include "lottiekeypath.h" +#include "vbitmap.h" +#include "vdasher.h" +#include "vpainter.h" +#include "vraster.h" + +/* Lottie Layer Rules + * 1. time stretch is pre calculated and applied to all the properties of the + * lottilayer model and all its children + * 2. The frame property could be reversed using,time-reverse layer property in + * AE. which means (start frame > endFrame) 3. + */ + +static bool transformProp(rlottie::Property prop) +{ + switch (prop) { + case rlottie::Property::TrAnchor: + case rlottie::Property::TrScale: + case rlottie::Property::TrOpacity: + case rlottie::Property::TrPosition: + case rlottie::Property::TrRotation: + return true; + default: + return false; + } +} +static bool fillProp(rlottie::Property prop) +{ + switch (prop) { + case rlottie::Property::Color: + case rlottie::Property::FillOpacity: + return true; + default: + return false; + } +} + +static bool strokeProp(rlottie::Property prop) +{ + switch (prop) { + case rlottie::Property::Color: + case rlottie::Property::StrokeOpacity: + case rlottie::Property::StrokeWidth: + return true; + default: + return false; + } +} + +LOTCompItem::LOTCompItem(LOTModel *model) + : mUpdateViewBox(false), mCurFrameNo(-1) +{ + mCompData = model->mRoot.get(); + mRootLayer = createLayerItem(mCompData->mRootLayer.get()); + mRootLayer->setComplexContent(false); + mViewSize = mCompData->size(); +} + +void LOTCompItem::setValue(const std::string &keypath, LOTVariant &value) +{ + LOTKeyPath key(keypath); + mRootLayer->resolveKeyPath(key, 0, value); + mCurFrameNo = -1; +} + +std::unique_ptr LOTCompItem::createLayerItem( + LOTLayerData *layerData) +{ + switch (layerData->mLayerType) { + case LayerType::Precomp: { + return std::make_unique(layerData); + } + case LayerType::Solid: { + return std::make_unique(layerData); + } + case LayerType::Shape: { + return std::make_unique(layerData); + } + case LayerType::Null: { + return std::make_unique(layerData); + } + case LayerType::Image: { + return std::make_unique(layerData); + } + default: + return nullptr; + break; + } +} + +void LOTCompItem::resize(const VSize &size) +{ + if (mViewSize == size) return; + mViewSize = size; + mUpdateViewBox = true; +} + +VSize LOTCompItem::size() const +{ + return mViewSize; +} + +bool LOTCompItem::update(int frameNo) +{ + // check if cached frame is same as requested frame. + if (!mUpdateViewBox && (mCurFrameNo == frameNo)) return false; + + /* + * if viewbox dosen't scale exactly to the viewport + * we scale the viewbox keeping AspectRatioPreserved and then align the + * viewbox to the viewport using AlignCenter rule. + */ + VSize viewPort = mViewSize; + VSize viewBox = mCompData->size(); + + float sx = float(viewPort.width()) / viewBox.width(); + float sy = float(viewPort.height()) / viewBox.height(); + float scale = fmin(sx, sy); + float tx = (viewPort.width() - viewBox.width() * scale) * 0.5; + float ty = (viewPort.height() - viewBox.height() * scale) * 0.5; + + VMatrix m; + m.translate(tx, ty).scale(scale, scale); + mRootLayer->update(frameNo, m, 1.0); + + mCurFrameNo = frameNo; + mUpdateViewBox = false; + return true; +} + +void LOTCompItem::buildRenderTree() +{ + mRootLayer->buildLayerNode(); +} + +const LOTLayerNode *LOTCompItem::renderTree() const +{ + return mRootLayer->layerNode(); +} + +bool LOTCompItem::render(const rlottie::Surface &surface) +{ + VBitmap bitmap(reinterpret_cast(surface.buffer()), surface.width(), + surface.height(), surface.bytesPerLine(), + VBitmap::Format::ARGB32); + + /* schedule all preprocess task for this frame at once. + */ + mDrawableList.clear(); + mRootLayer->renderList(mDrawableList); + VRect clip(0, 0, surface.drawRegionWidth(), surface.drawRegionHeight()); + for (auto &e : mDrawableList) { + e->preprocess(clip); + } + + VPainter painter(&bitmap); + // set sub surface area for drawing. + painter.setDrawRegion( + VRect(surface.drawRegionPosX(), surface.drawRegionPosY(), + surface.drawRegionWidth(), surface.drawRegionHeight())); + mRootLayer->render(&painter, {}, {}); + + return true; +} + +void LOTMaskItem::update(int frameNo, const VMatrix & parentMatrix, + float /*parentAlpha*/, const DirtyFlag &flag) +{ + if (flag.testFlag(DirtyFlagBit::None) && mData->isStatic()) return; + + if (mData->mShape.isStatic()) { + if (mLocalPath.empty()) { + mData->mShape.value(frameNo).toPath(mLocalPath); + } + } else { + mData->mShape.value(frameNo).toPath(mLocalPath); + } + /* mask item dosen't inherit opacity */ + mCombinedAlpha = mData->opacity(frameNo); + + mFinalPath.clone(mLocalPath); + mFinalPath.transform(parentMatrix); + + mRasterizer.rasterize(mFinalPath); + mRasterRequest = true; +} + +VRle LOTMaskItem::rle() +{ + if (mRasterRequest) { + mRasterRequest = false; + if (!vCompare(mCombinedAlpha, 1.0f)) + mRasterizer.rle() *= (mCombinedAlpha * 255); + if (mData->mInv) mRasterizer.rle().invert(); + } + return mRasterizer.rle(); +} + +void LOTLayerItem::buildLayerNode() +{ + if (!mLayerCNode) { + mLayerCNode = std::make_unique(); + mLayerCNode->mMaskList.ptr = nullptr; + mLayerCNode->mMaskList.size = 0; + mLayerCNode->mLayerList.ptr = nullptr; + mLayerCNode->mLayerList.size = 0; + mLayerCNode->mNodeList.ptr = nullptr; + mLayerCNode->mNodeList.size = 0; + mLayerCNode->mMatte = MatteNone; + mLayerCNode->mVisible = 0; + mLayerCNode->mAlpha = 255; + mLayerCNode->mClipPath.ptPtr = nullptr; + mLayerCNode->mClipPath.elmPtr = nullptr; + mLayerCNode->mClipPath.ptCount = 0; + mLayerCNode->mClipPath.elmCount = 0; + mLayerCNode->name = name().c_str(); + } + if (complexContent()) mLayerCNode->mAlpha = combinedAlpha() * 255; + mLayerCNode->mVisible = visible(); + // update matte + if (hasMatte()) { + switch (mLayerData->mMatteType) { + case MatteType::Alpha: + mLayerCNode->mMatte = MatteAlpha; + break; + case MatteType::AlphaInv: + mLayerCNode->mMatte = MatteAlphaInv; + break; + case MatteType::Luma: + mLayerCNode->mMatte = MatteLuma; + break; + case MatteType::LumaInv: + mLayerCNode->mMatte = MatteLumaInv; + break; + default: + mLayerCNode->mMatte = MatteNone; + break; + } + } + if (mLayerMask) { + mMasksCNode.clear(); + mMasksCNode.resize(mLayerMask->mMasks.size()); + size_t i = 0; + for (const auto &mask : mLayerMask->mMasks) { + LOTMask * cNode = &mMasksCNode[i++]; + const std::vector &elm = mask.mFinalPath.elements(); + const std::vector & pts = mask.mFinalPath.points(); + const float *ptPtr = reinterpret_cast(pts.data()); + const char * elmPtr = reinterpret_cast(elm.data()); + cNode->mPath.ptPtr = ptPtr; + cNode->mPath.ptCount = pts.size(); + cNode->mPath.elmPtr = elmPtr; + cNode->mPath.elmCount = elm.size(); + cNode->mAlpha = mask.mCombinedAlpha * 255; + switch (mask.maskMode()) { + case LOTMaskData::Mode::Add: + cNode->mMode = MaskAdd; + break; + case LOTMaskData::Mode::Substarct: + cNode->mMode = MaskSubstract; + break; + case LOTMaskData::Mode::Intersect: + cNode->mMode = MaskIntersect; + break; + case LOTMaskData::Mode::Difference: + cNode->mMode = MaskDifference; + break; + default: + cNode->mMode = MaskAdd; + break; + } + } + mLayerCNode->mMaskList.ptr = mMasksCNode.data(); + mLayerCNode->mMaskList.size = mMasksCNode.size(); + } +} + +void LOTLayerItem::render(VPainter *painter, const VRle &inheritMask, + const VRle &matteRle) +{ + mDrawableList.clear(); + renderList(mDrawableList); + + VRle mask; + if (mLayerMask) { + mask = mLayerMask->maskRle(painter->clipBoundingRect()); + if (!inheritMask.empty()) mask = mask & inheritMask; + // if resulting mask is empty then return. + if (mask.empty()) return; + } else { + mask = inheritMask; + } + + for (auto &i : mDrawableList) { + painter->setBrush(i->mBrush); + VRle rle = i->rle(); + if (matteRle.empty()) { + if (mask.empty()) { + // no mask no matte + painter->drawRle(VPoint(), rle); + } else { + // only mask + painter->drawRle(rle, mask); + } + + } else { + if (!mask.empty()) rle = rle & mask; + + if (rle.empty()) continue; + if (matteType() == MatteType::AlphaInv) { + rle = rle - matteRle; + painter->drawRle(VPoint(), rle); + } else { + // render with matteRle as clip. + painter->drawRle(rle, matteRle); + } + } + } +} + +LOTLayerMaskItem::LOTLayerMaskItem(LOTLayerData *layerData) +{ + mMasks.reserve(layerData->mMasks.size()); + + for (auto &i : layerData->mMasks) { + mMasks.emplace_back(i.get()); + mStatic &= i->isStatic(); + } +} + +void LOTLayerMaskItem::update(int frameNo, const VMatrix &parentMatrix, + float parentAlpha, const DirtyFlag &flag) +{ + if (flag.testFlag(DirtyFlagBit::None) && isStatic()) return; + + for (auto &i : mMasks) { + i.update(frameNo, parentMatrix, parentAlpha, flag); + } + mDirty = true; +} + +VRle LOTLayerMaskItem::maskRle(const VRect &clipRect) +{ + if (!mDirty) return mRle; + + VRle rle; + for (auto &i : mMasks) { + switch (i.maskMode()) { + case LOTMaskData::Mode::Add: { + rle = rle + i.rle(); + break; + } + case LOTMaskData::Mode::Substarct: { + if (rle.empty() && !clipRect.empty()) rle = VRle::toRle(clipRect); + rle = rle - i.rle(); + break; + } + case LOTMaskData::Mode::Intersect: { + if (rle.empty() && !clipRect.empty()) rle = VRle::toRle(clipRect); + rle = rle & i.rle(); + break; + } + case LOTMaskData::Mode::Difference: { + rle = rle ^ i.rle(); + break; + } + default: + break; + } + } + + if (!rle.empty() && !rle.unique()) { + mRle.clone(rle); + } else { + mRle = rle; + } + mDirty = false; + return mRle; +} + +LOTLayerItem::LOTLayerItem(LOTLayerData *layerData) : mLayerData(layerData) +{ + if (mLayerData->mHasMask) + mLayerMask = std::make_unique(mLayerData); +} + +bool LOTLayerItem::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (!keyPath.matches(name(), depth)) { + return false; + } + + if (!keyPath.skip(name())) { + if (keyPath.fullyResolvesTo(name(), depth) && + transformProp(value.property())) { + //@TODO handle propery update. + } + } + return true; +} + +bool LOTShapeLayerItem::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (LOTLayerItem::resolveKeyPath(keyPath, depth, value)) { + if (keyPath.propagate(name(), depth)) { + uint newDepth = keyPath.nextDepth(name(), depth); + mRoot->resolveKeyPath(keyPath, newDepth, value); + } + return true; + } + return false; +} + +bool LOTCompLayerItem::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (LOTLayerItem::resolveKeyPath(keyPath, depth, value)) { + if (keyPath.propagate(name(), depth)) { + uint newDepth = keyPath.nextDepth(name(), depth); + for (const auto &layer : mLayers) { + layer->resolveKeyPath(keyPath, newDepth, value); + } + } + return true; + } + return false; +} + +void LOTLayerItem::update(int frameNumber, const VMatrix &parentMatrix, + float parentAlpha) +{ + mFrameNo = frameNumber; + // 1. check if the layer is part of the current frame + if (!visible()) return; + + float alpha = parentAlpha * opacity(frameNo()); + if (vIsZero(alpha)) { + mCombinedAlpha = 0; + return; + } + + // 2. calculate the parent matrix and alpha + VMatrix m = matrix(frameNo()); + m *= parentMatrix; + + // 3. update the dirty flag based on the change + if (!mCombinedMatrix.fuzzyCompare(m)) { + mDirtyFlag |= DirtyFlagBit::Matrix; + } + if (!vCompare(mCombinedAlpha, alpha)) { + mDirtyFlag |= DirtyFlagBit::Alpha; + } + mCombinedMatrix = m; + mCombinedAlpha = alpha; + + // 4. update the mask + if (mLayerMask) { + mLayerMask->update(frameNo(), m, alpha, mDirtyFlag); + } + + // 5. if no parent property change and layer is static then nothing to do. + if (!mLayerData->precompLayer() && flag().testFlag(DirtyFlagBit::None) && + isStatic()) + return; + + // 6. update the content of the layer + updateContent(); + + // 7. reset the dirty flag + mDirtyFlag = DirtyFlagBit::None; +} + +VMatrix LOTLayerItem::matrix(int frameNo) const +{ + return mParentLayer + ? (mLayerData->matrix(frameNo) * mParentLayer->matrix(frameNo)) + : mLayerData->matrix(frameNo); +} + +bool LOTLayerItem::visible() const +{ + if (frameNo() >= mLayerData->inFrame() && + frameNo() < mLayerData->outFrame()) + return true; + else + return false; +} + +LOTCompLayerItem::LOTCompLayerItem(LOTLayerData *layerModel) + : LOTLayerItem(layerModel) +{ + // 1. create layer item + for (auto &i : mLayerData->mChildren) { + LOTLayerData *layerModel = static_cast(i.get()); + auto layerItem = LOTCompItem::createLayerItem(layerModel); + if (layerItem) mLayers.push_back(std::move(layerItem)); + } + + // 2. update parent layer + for (const auto &layer : mLayers) { + int id = layer->parentId(); + if (id >= 0) { + auto search = + std::find_if(mLayers.begin(), mLayers.end(), + [id](const auto &val) { return val->id() == id; }); + if (search != mLayers.end()) layer->setParentLayer((*search).get()); + } + } + + // 3. keep the layer in back-to-front order. + // as lottie model keeps the data in front-toback-order. + std::reverse(mLayers.begin(), mLayers.end()); + + // 4. check if its a nested composition + if (!layerModel->layerSize().empty()) { + mClipper = std::make_unique(layerModel->layerSize()); + } + + if (mLayers.size() > 1) setComplexContent(true); +} + +void LOTCompLayerItem::buildLayerNode() +{ + LOTLayerItem::buildLayerNode(); + if (mClipper) { + const std::vector &elm = mClipper->mPath.elements(); + const std::vector & pts = mClipper->mPath.points(); + const float *ptPtr = reinterpret_cast(pts.data()); + const char * elmPtr = reinterpret_cast(elm.data()); + layerNode()->mClipPath.ptPtr = ptPtr; + layerNode()->mClipPath.elmPtr = elmPtr; + layerNode()->mClipPath.ptCount = 2 * pts.size(); + layerNode()->mClipPath.elmCount = elm.size(); + } + if (mLayers.size() != mLayersCNode.size()) { + for (const auto &layer : mLayers) { + layer->buildLayerNode(); + mLayersCNode.push_back(layer->layerNode()); + } + layerNode()->mLayerList.ptr = mLayersCNode.data(); + layerNode()->mLayerList.size = mLayersCNode.size(); + } else { + for (const auto &layer : mLayers) { + layer->buildLayerNode(); + } + } +} + +void LOTCompLayerItem::render(VPainter *painter, const VRle &inheritMask, + const VRle &matteRle) +{ + if (vIsZero(combinedAlpha())) return; + + if (vCompare(combinedAlpha(), 1.0)) { + renderHelper(painter, inheritMask, matteRle); + } else { + if (complexContent()) { + VSize size = painter->clipBoundingRect().size(); + VPainter srcPainter; + VBitmap srcBitmap(size.width(), size.height(), + VBitmap::Format::ARGB32); + srcPainter.begin(&srcBitmap); + renderHelper(&srcPainter, inheritMask, matteRle); + srcPainter.end(); + painter->drawBitmap(VPoint(), srcBitmap, combinedAlpha() * 255); + } else { + renderHelper(painter, inheritMask, matteRle); + } + } +} + +void LOTCompLayerItem::renderHelper(VPainter *painter, const VRle &inheritMask, + const VRle &matteRle) +{ + VRle mask; + if (mLayerMask) { + mask = mLayerMask->maskRle(painter->clipBoundingRect()); + if (!inheritMask.empty()) mask = mask & inheritMask; + // if resulting mask is empty then return. + if (mask.empty()) return; + } else { + mask = inheritMask; + } + + if (mClipper) { + if (mask.empty()) { + mask = mClipper->rle(); + } else { + mask = mClipper->rle() & mask; + } + } + + LOTLayerItem *matte = nullptr; + for (const auto &layer : mLayers) { + if (layer->hasMatte()) { + matte = layer.get(); + } else { + if (layer->visible()) { + if (matte) { + if (matte->visible()) + renderMatteLayer(painter, mask, matteRle, matte, + layer.get()); + } else { + layer->render(painter, mask, matteRle); + } + } + matte = nullptr; + } + } +} + +void LOTCompLayerItem::renderMatteLayer(VPainter *painter, const VRle &mask, + const VRle & matteRle, + LOTLayerItem *layer, LOTLayerItem *src) +{ + VSize size = painter->clipBoundingRect().size(); + // Decide if we can use fast matte. + // 1. draw src layer to matte buffer + VPainter srcPainter; + src->bitmap().reset(size.width(), size.height(), + VBitmap::Format::ARGB32); + srcPainter.begin(&src->bitmap()); + src->render(&srcPainter, mask, matteRle); + srcPainter.end(); + + // 2. draw layer to layer buffer + VPainter layerPainter; + layer->bitmap().reset(size.width(), size.height(), + VBitmap::Format::ARGB32); + layerPainter.begin(&layer->bitmap()); + layer->render(&layerPainter, mask, matteRle); + + // 2.1update composition mode + switch (layer->matteType()) { + case MatteType::Alpha: + case MatteType::Luma: { + layerPainter.setCompositionMode( + VPainter::CompositionMode::CompModeDestIn); + break; + } + case MatteType::AlphaInv: + case MatteType::LumaInv: { + layerPainter.setCompositionMode( + VPainter::CompositionMode::CompModeDestOut); + break; + } + default: + break; + } + + // 2.2 update srcBuffer if the matte is luma type + if (layer->matteType() == MatteType::Luma || + layer->matteType() == MatteType::LumaInv) { + src->bitmap().updateLuma(); + } + + // 2.3 draw src buffer as mask + layerPainter.drawBitmap(VPoint(), src->bitmap()); + layerPainter.end(); + // 3. draw the result buffer into painter + painter->drawBitmap(VPoint(), layer->bitmap()); +} + +void LOTClipperItem::update(const VMatrix &matrix) +{ + mPath.reset(); + mPath.addRect(VRectF(0, 0, mSize.width(), mSize.height())); + mPath.transform(matrix); + mRasterizer.rasterize(mPath); +} + +VRle LOTClipperItem::rle() +{ + return mRasterizer.rle(); +} + +void LOTCompLayerItem::updateContent() +{ + if (mClipper && flag().testFlag(DirtyFlagBit::Matrix)) { + mClipper->update(combinedMatrix()); + } + int mappedFrame = mLayerData->timeRemap(frameNo()); + float alpha = combinedAlpha(); + if (complexContent()) alpha = 1; + for (const auto &layer : mLayers) { + layer->update(mappedFrame, combinedMatrix(), alpha); + } +} + +void LOTCompLayerItem::renderList(std::vector &list) +{ + if (!visible() || vIsZero(combinedAlpha())) return; + + LOTLayerItem *matte = nullptr; + for (const auto &layer : mLayers) { + if (layer->hasMatte()) { + matte = layer.get(); + } else { + if (layer->visible()) { + if (matte) { + if (matte->visible()) { + layer->renderList(list); + matte->renderList(list); + } + } else { + layer->renderList(list); + } + } + matte = nullptr; + } + } +} + +LOTSolidLayerItem::LOTSolidLayerItem(LOTLayerData *layerData) + : LOTLayerItem(layerData) +{ +} + +void LOTSolidLayerItem::updateContent() +{ + if (flag() & DirtyFlagBit::Matrix) { + VPath path; + path.addRect( + VRectF(0, 0, mLayerData->solidWidth(), mLayerData->solidHeight())); + path.transform(combinedMatrix()); + mRenderNode.mFlag |= VDrawable::DirtyState::Path; + mRenderNode.mPath = path; + } + if (flag() & DirtyFlagBit::Alpha) { + LottieColor color = mLayerData->solidColor(); + VBrush brush(color.toColor(combinedAlpha())); + mRenderNode.setBrush(brush); + mRenderNode.mFlag |= VDrawable::DirtyState::Brush; + } +} + +void LOTSolidLayerItem::buildLayerNode() +{ + LOTLayerItem::buildLayerNode(); + + mDrawableList.clear(); + renderList(mDrawableList); + + mCNodeList.clear(); + for (auto &i : mDrawableList) { + LOTDrawable *lotDrawable = static_cast(i); + lotDrawable->sync(); + mCNodeList.push_back(lotDrawable->mCNode.get()); + } + layerNode()->mNodeList.ptr = mCNodeList.data(); + layerNode()->mNodeList.size = mCNodeList.size(); +} + +void LOTSolidLayerItem::renderList(std::vector &list) +{ + if (!visible() || vIsZero(combinedAlpha())) return; + + list.push_back(&mRenderNode); +} + +LOTImageLayerItem::LOTImageLayerItem(LOTLayerData *layerData) + : LOTLayerItem(layerData) +{ + VBrush brush(mLayerData->mAsset->bitmap()); + mRenderNode.setBrush(brush); +} + +void LOTImageLayerItem::updateContent() +{ + if (flag() & DirtyFlagBit::Matrix) { + VPath path; + path.addRect(VRectF(0, 0, mLayerData->mAsset->mWidth, + mLayerData->mAsset->mHeight)); + path.transform(combinedMatrix()); + mRenderNode.mFlag |= VDrawable::DirtyState::Path; + mRenderNode.mPath = path; + mRenderNode.mBrush.setMatrix(combinedMatrix()); + } + + if (flag() & DirtyFlagBit::Alpha) { + //@TODO handle alpha with the image. + } +} + +void LOTImageLayerItem::renderList(std::vector &list) +{ + if (!visible() || vIsZero(combinedAlpha())) return; + + list.push_back(&mRenderNode); +} + +void LOTImageLayerItem::buildLayerNode() +{ + LOTLayerItem::buildLayerNode(); + + mDrawableList.clear(); + renderList(mDrawableList); + + mCNodeList.clear(); + for (auto &i : mDrawableList) { + LOTDrawable *lotDrawable = static_cast(i); + lotDrawable->sync(); + + lotDrawable->mCNode->mImageInfo.data = + lotDrawable->mBrush.mTexture.data(); + lotDrawable->mCNode->mImageInfo.width = + lotDrawable->mBrush.mTexture.width(); + lotDrawable->mCNode->mImageInfo.height = + lotDrawable->mBrush.mTexture.height(); + + lotDrawable->mCNode->mImageInfo.mMatrix.m11 = combinedMatrix().m_11(); + lotDrawable->mCNode->mImageInfo.mMatrix.m12 = combinedMatrix().m_12(); + lotDrawable->mCNode->mImageInfo.mMatrix.m13 = combinedMatrix().m_13(); + + lotDrawable->mCNode->mImageInfo.mMatrix.m21 = combinedMatrix().m_21(); + lotDrawable->mCNode->mImageInfo.mMatrix.m22 = combinedMatrix().m_22(); + lotDrawable->mCNode->mImageInfo.mMatrix.m23 = combinedMatrix().m_23(); + + lotDrawable->mCNode->mImageInfo.mMatrix.m31 = combinedMatrix().m_tx(); + lotDrawable->mCNode->mImageInfo.mMatrix.m32 = combinedMatrix().m_ty(); + lotDrawable->mCNode->mImageInfo.mMatrix.m33 = combinedMatrix().m_33(); + + mCNodeList.push_back(lotDrawable->mCNode.get()); + } + layerNode()->mNodeList.ptr = mCNodeList.data(); + layerNode()->mNodeList.size = mCNodeList.size(); +} + +LOTNullLayerItem::LOTNullLayerItem(LOTLayerData *layerData) + : LOTLayerItem(layerData) +{ +} +void LOTNullLayerItem::updateContent() {} + +LOTShapeLayerItem::LOTShapeLayerItem(LOTLayerData *layerData) + : LOTLayerItem(layerData), + mRoot(std::make_unique(nullptr)) +{ + mRoot->addChildren(layerData); + + std::vector list; + mRoot->processPaintItems(list); + + if (layerData->hasPathOperator()) { + list.clear(); + mRoot->processTrimItems(list); + } +} + +std::unique_ptr LOTShapeLayerItem::createContentItem( + LOTData *contentData) +{ + switch (contentData->type()) { + case LOTData::Type::ShapeGroup: { + return std::make_unique( + static_cast(contentData)); + } + case LOTData::Type::Rect: { + return std::make_unique( + static_cast(contentData)); + } + case LOTData::Type::Ellipse: { + return std::make_unique( + static_cast(contentData)); + } + case LOTData::Type::Shape: { + return std::make_unique( + static_cast(contentData)); + } + case LOTData::Type::Polystar: { + return std::make_unique( + static_cast(contentData)); + } + case LOTData::Type::Fill: { + return std::make_unique( + static_cast(contentData)); + } + case LOTData::Type::GFill: { + return std::make_unique( + static_cast(contentData)); + } + case LOTData::Type::Stroke: { + return std::make_unique( + static_cast(contentData)); + } + case LOTData::Type::GStroke: { + return std::make_unique( + static_cast(contentData)); + } + case LOTData::Type::Repeater: { + return std::make_unique( + static_cast(contentData)); + } + case LOTData::Type::Trim: { + return std::make_unique( + static_cast(contentData)); + } + default: + return nullptr; + break; + } +} + +void LOTShapeLayerItem::updateContent() +{ + mRoot->update(frameNo(), combinedMatrix(), combinedAlpha(), flag()); + + if (mLayerData->hasPathOperator()) { + mRoot->applyTrim(); + } +} + +void LOTShapeLayerItem::buildLayerNode() +{ + LOTLayerItem::buildLayerNode(); + + mDrawableList.clear(); + renderList(mDrawableList); + + mCNodeList.clear(); + for (auto &i : mDrawableList) { + LOTDrawable *lotDrawable = static_cast(i); + lotDrawable->sync(); + mCNodeList.push_back(lotDrawable->mCNode.get()); + } + layerNode()->mNodeList.ptr = mCNodeList.data(); + layerNode()->mNodeList.size = mCNodeList.size(); +} + +void LOTShapeLayerItem::renderList(std::vector &list) +{ + if (!visible() || vIsZero(combinedAlpha())) return; + mRoot->renderList(list); +} + +bool LOTContentGroupItem::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (!keyPath.matches(name(), depth)) { + return false; + } + + if (!keyPath.skip(name())) { + if (keyPath.fullyResolvesTo(name(), depth) && + transformProp(value.property())) { + //@TODO handle property update + } + } + + if (keyPath.propagate(name(), depth)) { + uint newDepth = keyPath.nextDepth(name(), depth); + for (auto &child : mContents) { + child->resolveKeyPath(keyPath, newDepth, value); + } + } + return true; +} + +bool LOTFillItem::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (!keyPath.matches(mModel.name(), depth)) { + return false; + } + + if (keyPath.fullyResolvesTo(mModel.name(), depth) && + fillProp(value.property())) { + mModel.filter().addValue(value); + return true; + } + return false; +} + +bool LOTStrokeItem::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (!keyPath.matches(mModel.name(), depth)) { + return false; + } + + if (keyPath.fullyResolvesTo(mModel.name(), depth) && + strokeProp(value.property())) { + mModel.filter().addValue(value); + return true; + } + return false; +} + +LOTContentGroupItem::LOTContentGroupItem(LOTGroupData *data) + : LOTContentItem(ContentType::Group), mData(data) +{ + addChildren(mData); +} + +void LOTContentGroupItem::addChildren(LOTGroupData *data) +{ + if (!data) return; + + for (auto &i : data->mChildren) { + auto content = LOTShapeLayerItem::createContentItem(i.get()); + if (content) { + content->setParent(this); + mContents.push_back(std::move(content)); + } + } + + // keep the content in back-to-front order. + std::reverse(mContents.begin(), mContents.end()); +} + +void LOTContentGroupItem::update(int frameNo, const VMatrix &parentMatrix, + float parentAlpha, const DirtyFlag &flag) +{ + VMatrix m = parentMatrix; + float alpha = parentAlpha; + DirtyFlag newFlag = flag; + + if (mData && mData->mTransform) { + // update the matrix and the flag + if ((flag & DirtyFlagBit::Matrix) || !mData->mTransform->isStatic()) { + newFlag |= DirtyFlagBit::Matrix; + } + m = mData->mTransform->matrix(frameNo); + m *= parentMatrix; + alpha *= mData->mTransform->opacity(frameNo); + + if (!vCompare(alpha, parentAlpha)) { + newFlag |= DirtyFlagBit::Alpha; + } + } + + mMatrix = m; + + for (const auto &content : mContents) { + content->update(frameNo, m, alpha, newFlag); + } +} + +void LOTContentGroupItem::applyTrim() +{ + for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) { + auto content = (*i).get(); + switch (content->type()) { + case ContentType::Trim: { + static_cast(content)->update(); + break; + } + case ContentType::Group: { + static_cast(content)->applyTrim(); + break; + } + default: + break; + } + } +} + +void LOTContentGroupItem::renderList(std::vector &list) +{ + for (const auto &content : mContents) { + content->renderList(list); + } +} + +void LOTContentGroupItem::processPaintItems( + std::vector &list) +{ + int curOpCount = list.size(); + for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) { + auto content = (*i).get(); + switch (content->type()) { + case ContentType::Path: { + list.push_back(static_cast(content)); + break; + } + case ContentType::Paint: { + static_cast(content)->addPathItems(list, + curOpCount); + break; + } + case ContentType::Group: { + static_cast(content)->processPaintItems( + list); + break; + } + default: + break; + } + } +} + +void LOTContentGroupItem::processTrimItems(std::vector &list) +{ + int curOpCount = list.size(); + for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) { + auto content = (*i).get(); + + switch (content->type()) { + case ContentType::Path: { + list.push_back(static_cast(content)); + break; + } + case ContentType::Trim: { + static_cast(content)->addPathItems(list, curOpCount); + break; + } + case ContentType::Group: { + static_cast(content)->processTrimItems(list); + break; + } + default: + break; + } + } +} + +/* + * LOTPathDataItem uses 3 path objects for path object reuse. + * mLocalPath - keeps track of the local path of the item before + * applying path operation and transformation. + * mTemp - keeps a referece to the mLocalPath and can be updated by the + * path operation objects(trim, merge path), + * mFinalPath - it takes a deep copy of the intermediate path(mTemp) each time + * when the path is dirty(so if path changes every frame we don't realloc just + * copy to the final path). NOTE: As path objects are COW objects we have to be + * carefull about the refcount so that we don't generate deep copy while + * modifying the path objects. + */ +void LOTPathDataItem::update(int frameNo, const VMatrix &, float, + const DirtyFlag &flag) +{ + mPathChanged = false; + + // 1. update the local path if needed + if (hasChanged(frameNo)) { + // loose the reference to mLocalPath if any + // from the last frame update. + mTemp = VPath(); + + updatePath(mLocalPath, frameNo); + mPathChanged = true; + mNeedUpdate = true; + } + // 2. keep a reference path in temp in case there is some + // path operation like trim which will update the path. + // we don't want to update the local path. + mTemp = mLocalPath; + + // 3. compute the final path with parentMatrix + if ((flag & DirtyFlagBit::Matrix) || mPathChanged) { + mPathChanged = true; + } +} + +const VPath &LOTPathDataItem::finalPath() +{ + if (mPathChanged || mNeedUpdate) { + mFinalPath.clone(mTemp); + mFinalPath.transform( + static_cast(parent())->matrix()); + mNeedUpdate = false; + } + return mFinalPath; +} +LOTRectItem::LOTRectItem(LOTRectData *data) + : LOTPathDataItem(data->isStatic()), mData(data) +{ +} + +void LOTRectItem::updatePath(VPath &path, int frameNo) +{ + VPointF pos = mData->mPos.value(frameNo); + VPointF size = mData->mSize.value(frameNo); + float roundness = mData->mRound.value(frameNo); + VRectF r(pos.x() - size.x() / 2, pos.y() - size.y() / 2, size.x(), + size.y()); + + path.reset(); + path.addRoundRect(r, roundness, mData->direction()); +} + +LOTEllipseItem::LOTEllipseItem(LOTEllipseData *data) + : LOTPathDataItem(data->isStatic()), mData(data) +{ +} + +void LOTEllipseItem::updatePath(VPath &path, int frameNo) +{ + VPointF pos = mData->mPos.value(frameNo); + VPointF size = mData->mSize.value(frameNo); + VRectF r(pos.x() - size.x() / 2, pos.y() - size.y() / 2, size.x(), + size.y()); + + path.reset(); + path.addOval(r, mData->direction()); +} + +LOTShapeItem::LOTShapeItem(LOTShapeData *data) + : LOTPathDataItem(data->isStatic()), mData(data) +{ +} + +void LOTShapeItem::updatePath(VPath &path, int frameNo) +{ + mData->mShape.value(frameNo).toPath(path); +} + +LOTPolystarItem::LOTPolystarItem(LOTPolystarData *data) + : LOTPathDataItem(data->isStatic()), mData(data) +{ +} + +void LOTPolystarItem::updatePath(VPath &path, int frameNo) +{ + VPointF pos = mData->mPos.value(frameNo); + float points = mData->mPointCount.value(frameNo); + float innerRadius = mData->mInnerRadius.value(frameNo); + float outerRadius = mData->mOuterRadius.value(frameNo); + float innerRoundness = mData->mInnerRoundness.value(frameNo); + float outerRoundness = mData->mOuterRoundness.value(frameNo); + float rotation = mData->mRotation.value(frameNo); + + path.reset(); + VMatrix m; + + if (mData->mType == LOTPolystarData::PolyType::Star) { + path.addPolystar(points, innerRadius, outerRadius, innerRoundness, + outerRoundness, 0.0, 0.0, 0.0, mData->direction()); + } else { + path.addPolygon(points, outerRadius, outerRoundness, 0.0, 0.0, 0.0, + mData->direction()); + } + + m.translate(pos.x(), pos.y()).rotate(rotation); + m.rotate(rotation); + path.transform(m); +} + +/* + * PaintData Node handling + * + */ +LOTPaintDataItem::LOTPaintDataItem(bool staticContent) + : LOTContentItem(ContentType::Paint), mStaticContent(staticContent) +{ +} + +void LOTPaintDataItem::update(int frameNo, const VMatrix & /*parentMatrix*/, + float parentAlpha, const DirtyFlag &flag) +{ + mRenderNodeUpdate = true; + mParentAlpha = parentAlpha; + mFlag = flag; + mFrameNo = frameNo; + + updateContent(frameNo); +} + +void LOTPaintDataItem::updateRenderNode() +{ + bool dirty = false; + for (auto &i : mPathItems) { + if (i->dirty()) { + dirty = true; + break; + } + } + + if (dirty) { + mPath.reset(); + + for (auto &i : mPathItems) { + mPath.addPath(i->finalPath()); + } + mDrawable.setPath(mPath); + } else { + if (mDrawable.mFlag & VDrawable::DirtyState::Path) + mDrawable.mPath = mPath; + } +} + +void LOTPaintDataItem::renderList(std::vector &list) +{ + if (mRenderNodeUpdate) { + updateRenderNode(); + LOTPaintDataItem::updateRenderNode(); + mRenderNodeUpdate = false; + } + list.push_back(&mDrawable); +} + +void LOTPaintDataItem::addPathItems(std::vector &list, + int startOffset) +{ + std::copy(list.begin() + startOffset, list.end(), + back_inserter(mPathItems)); +} + +LOTFillItem::LOTFillItem(LOTFillData *data) + : LOTPaintDataItem(data->isStatic()), mModel(data) +{ +} + +void LOTFillItem::updateContent(int frameNo) +{ + mColor = mModel.color(frameNo).toColor(mModel.opacity(frameNo)); +} + +void LOTFillItem::updateRenderNode() +{ + VColor color = mColor; + + color.setAlpha(color.a * parentAlpha()); + VBrush brush(color); + mDrawable.setBrush(brush); + mDrawable.setFillRule(mModel.fillRule()); +} + +LOTGFillItem::LOTGFillItem(LOTGFillData *data) + : LOTPaintDataItem(data->isStatic()), mData(data) +{ +} + +void LOTGFillItem::updateContent(int frameNo) +{ + mAlpha = mData->opacity(frameNo); + mData->update(mGradient, frameNo); + mGradient->mMatrix = static_cast(parent())->matrix(); + mFillRule = mData->fillRule(); +} + +void LOTGFillItem::updateRenderNode() +{ + mGradient->setAlpha(mAlpha * parentAlpha()); + mDrawable.setBrush(VBrush(mGradient.get())); + mDrawable.setFillRule(mFillRule); +} + +LOTStrokeItem::LOTStrokeItem(LOTStrokeData *data) + : LOTPaintDataItem(data->isStatic()), mModel(data) +{ + mDashArraySize = 0; +} + +void LOTStrokeItem::updateContent(int frameNo) +{ + mColor = mModel.color(frameNo).toColor(mModel.opacity(frameNo)); + mWidth = mModel.strokeWidth(frameNo); + if (mModel.hasDashInfo()) { + mDashArraySize = mModel.getDashInfo(frameNo, mDashArray); + } +} + +static float getScale(const VMatrix &matrix) +{ + constexpr float SQRT_2 = 1.41421; + VPointF p1(0, 0); + VPointF p2(SQRT_2, SQRT_2); + p1 = matrix.map(p1); + p2 = matrix.map(p2); + VPointF final = p2 - p1; + + return std::sqrt(final.x() * final.x() + final.y() * final.y()) / 2.0; +} + +void LOTStrokeItem::updateRenderNode() +{ + VColor color = mColor; + + color.setAlpha(color.a * parentAlpha()); + VBrush brush(color); + mDrawable.setBrush(brush); + float scale = + getScale(static_cast(parent())->matrix()); + mDrawable.setStrokeInfo(mModel.capStyle(), mModel.joinStyle(), + mModel.meterLimit(), mWidth * scale); + if (mDashArraySize) { + for (int i = 0; i < mDashArraySize; i++) mDashArray[i] *= scale; + + /* AE draw the dash even if dash value is 0 */ + if (vCompare(mDashArray[0], 0.0f)) mDashArray[0] = 0.1; + + mDrawable.setDashInfo(mDashArray, mDashArraySize); + } +} + +LOTGStrokeItem::LOTGStrokeItem(LOTGStrokeData *data) + : LOTPaintDataItem(data->isStatic()), mData(data) +{ + mDashArraySize = 0; +} + +void LOTGStrokeItem::updateContent(int frameNo) +{ + mAlpha = mData->opacity(frameNo); + mData->update(mGradient, frameNo); + mGradient->mMatrix = static_cast(parent())->matrix(); + mCap = mData->capStyle(); + mJoin = mData->joinStyle(); + mMiterLimit = mData->meterLimit(); + mWidth = mData->width(frameNo); + if (mData->hasDashInfo()) { + mDashArraySize = mData->getDashInfo(frameNo, mDashArray); + } +} + +void LOTGStrokeItem::updateRenderNode() +{ + float scale = getScale(mGradient->mMatrix); + mGradient->setAlpha(mAlpha * parentAlpha()); + mDrawable.setBrush(VBrush(mGradient.get())); + mDrawable.setStrokeInfo(mCap, mJoin, mMiterLimit, mWidth * scale); + if (mDashArraySize) { + for (int i = 0; i < mDashArraySize; i++) mDashArray[i] *= scale; + mDrawable.setDashInfo(mDashArray, mDashArraySize); + } +} + +LOTTrimItem::LOTTrimItem(LOTTrimData *data) + : LOTContentItem(ContentType::Trim), mData(data) +{ +} + +void LOTTrimItem::update(int frameNo, const VMatrix & /*parentMatrix*/, + float /*parentAlpha*/, const DirtyFlag & /*flag*/) +{ + mDirty = false; + + if (mCache.mFrameNo == frameNo) return; + + LOTTrimData::Segment segment = mData->segment(frameNo); + + if (!(vCompare(mCache.mSegment.start, segment.start) && + vCompare(mCache.mSegment.end, segment.end))) { + mDirty = true; + mCache.mSegment = segment; + } + mCache.mFrameNo = frameNo; +} + +void LOTTrimItem::update() +{ + // when both path and trim are not dirty + if (!(mDirty || pathDirty())) return; + + if (vCompare(mCache.mSegment.start, mCache.mSegment.end)) { + for (auto &i : mPathItems) { + i->updatePath(VPath()); + } + return; + } + + if (vCompare(std::fabs(mCache.mSegment.start - mCache.mSegment.end), 1)) { + for (auto &i : mPathItems) { + i->updatePath(i->localPath()); + } + return; + } + + if (mData->type() == LOTTrimData::TrimType::Simultaneously) { + for (auto &i : mPathItems) { + VPathMesure pm; + pm.setStart(mCache.mSegment.start); + pm.setEnd(mCache.mSegment.end); + i->updatePath(pm.trim(i->localPath())); + } + } else { // LOTTrimData::TrimType::Individually + float totalLength = 0.0; + for (auto &i : mPathItems) { + totalLength += i->localPath().length(); + } + float start = totalLength * mCache.mSegment.start; + float end = totalLength * mCache.mSegment.end; + + if (start < end) { + float curLen = 0.0; + for (auto &i : mPathItems) { + if (curLen > end) { + // update with empty path. + i->updatePath(VPath()); + continue; + } + float len = i->localPath().length(); + + if (curLen < start && curLen + len < start) { + curLen += len; + // update with empty path. + i->updatePath(VPath()); + continue; + } else if (start <= curLen && end >= curLen + len) { + // inside segment + curLen += len; + continue; + } else { + float local_start = start > curLen ? start - curLen : 0; + local_start /= len; + float local_end = curLen + len < end ? len : end - curLen; + local_end /= len; + VPathMesure pm; + pm.setStart(local_start); + pm.setEnd(local_end); + VPath p = pm.trim(i->localPath()); + i->updatePath(p); + curLen += len; + } + } + } + } +} + +void LOTTrimItem::addPathItems(std::vector &list, + int startOffset) +{ + std::copy(list.begin() + startOffset, list.end(), + back_inserter(mPathItems)); +} + +LOTRepeaterItem::LOTRepeaterItem(LOTRepeaterData *data) : mRepeaterData(data) +{ + assert(mRepeaterData->content()); + + mCopies = mRepeaterData->maxCopies(); + + for (int i = 0; i < mCopies; i++) { + auto content = + std::make_unique(mRepeaterData->content()); + content->setParent(this); + mContents.push_back(std::move(content)); + } +} + +void LOTRepeaterItem::update(int frameNo, const VMatrix &parentMatrix, + float parentAlpha, const DirtyFlag &flag) +{ + DirtyFlag newFlag = flag; + + float copies = mRepeaterData->copies(frameNo); + int visibleCopies = int(copies); + + if (visibleCopies == 0) { + mHidden = true; + return; + } else { + mHidden = false; + } + + if (!mRepeaterData->isStatic()) newFlag |= DirtyFlagBit::Matrix; + + float offset = mRepeaterData->offset(frameNo); + float startOpacity = mRepeaterData->mTransform.startOpacity(frameNo); + float endOpacity = mRepeaterData->mTransform.endOpacity(frameNo); + + newFlag |= DirtyFlagBit::Alpha; + + for (int i = 0; i < mCopies; ++i) { + float newAlpha = + parentAlpha * lerp(startOpacity, endOpacity, i / copies); + + // hide rest of the copies , @TODO find a better solution. + if (i >= visibleCopies) newAlpha = 0; + + VMatrix result = mRepeaterData->mTransform.matrix(frameNo, i + offset) * + parentMatrix; + mContents[i]->update(frameNo, result, newAlpha, newFlag); + } +} + +void LOTRepeaterItem::renderList(std::vector &list) +{ + if (mHidden) return; + return LOTContentGroupItem::renderList(list); +} + +static void updateGStops(LOTNode *n, const VGradient *grad) +{ + if (grad->mStops.size() != n->mGradient.stopCount) { + if (n->mGradient.stopCount) free(n->mGradient.stopPtr); + n->mGradient.stopCount = grad->mStops.size(); + n->mGradient.stopPtr = (LOTGradientStop *)malloc( + n->mGradient.stopCount * sizeof(LOTGradientStop)); + } + + LOTGradientStop *ptr = n->mGradient.stopPtr; + for (const auto &i : grad->mStops) { + ptr->pos = i.first; + ptr->a = i.second.alpha() * grad->alpha(); + ptr->r = i.second.red(); + ptr->g = i.second.green(); + ptr->b = i.second.blue(); + ptr++; + } +} + +void LOTDrawable::sync() +{ + if (!mCNode) { + mCNode = std::make_unique(); + mCNode->mGradient.stopPtr = nullptr; + mCNode->mGradient.stopCount = 0; + } + + mCNode->mFlag = ChangeFlagNone; + if (mFlag & DirtyState::None) return; + + if (mFlag & DirtyState::Path) { + if (mStroke.mDash.size()) { + VDasher dasher(mStroke.mDash.data(), mStroke.mDash.size()); + mPath = dasher.dashed(mPath); + } + const std::vector &elm = mPath.elements(); + const std::vector & pts = mPath.points(); + const float *ptPtr = reinterpret_cast(pts.data()); + const char * elmPtr = reinterpret_cast(elm.data()); + mCNode->mPath.elmPtr = elmPtr; + mCNode->mPath.elmCount = elm.size(); + mCNode->mPath.ptPtr = ptPtr; + mCNode->mPath.ptCount = 2 * pts.size(); + mCNode->mFlag |= ChangeFlagPath; + } + + if (mStroke.enable) { + mCNode->mStroke.width = mStroke.width; + mCNode->mStroke.meterLimit = mStroke.meterLimit; + mCNode->mStroke.enable = 1; + + switch (mStroke.cap) { + case CapStyle::Flat: + mCNode->mStroke.cap = LOTCapStyle::CapFlat; + break; + case CapStyle::Square: + mCNode->mStroke.cap = LOTCapStyle::CapSquare; + break; + case CapStyle::Round: + mCNode->mStroke.cap = LOTCapStyle::CapRound; + break; + } + + switch (mStroke.join) { + case JoinStyle::Miter: + mCNode->mStroke.join = LOTJoinStyle::JoinMiter; + break; + case JoinStyle::Bevel: + mCNode->mStroke.join = LOTJoinStyle::JoinBevel; + break; + case JoinStyle::Round: + mCNode->mStroke.join = LOTJoinStyle::JoinRound; + break; + default: + mCNode->mStroke.join = LOTJoinStyle::JoinMiter; + break; + } + } else { + mCNode->mStroke.enable = 0; + } + + switch (mFillRule) { + case FillRule::EvenOdd: + mCNode->mFillRule = LOTFillRule::FillEvenOdd; + break; + default: + mCNode->mFillRule = LOTFillRule::FillWinding; + break; + } + + switch (mBrush.type()) { + case VBrush::Type::Solid: + mCNode->mBrushType = LOTBrushType::BrushSolid; + mCNode->mColor.r = mBrush.mColor.r; + mCNode->mColor.g = mBrush.mColor.g; + mCNode->mColor.b = mBrush.mColor.b; + mCNode->mColor.a = mBrush.mColor.a; + break; + case VBrush::Type::LinearGradient: { + mCNode->mBrushType = LOTBrushType::BrushGradient; + mCNode->mGradient.type = LOTGradientType::GradientLinear; + VPointF s = mBrush.mGradient->mMatrix.map( + {mBrush.mGradient->linear.x1, mBrush.mGradient->linear.y1}); + VPointF e = mBrush.mGradient->mMatrix.map( + {mBrush.mGradient->linear.x2, mBrush.mGradient->linear.y2}); + mCNode->mGradient.start.x = s.x(); + mCNode->mGradient.start.y = s.y(); + mCNode->mGradient.end.x = e.x(); + mCNode->mGradient.end.y = e.y(); + updateGStops(mCNode.get(), mBrush.mGradient); + break; + } + case VBrush::Type::RadialGradient: { + mCNode->mBrushType = LOTBrushType::BrushGradient; + mCNode->mGradient.type = LOTGradientType::GradientRadial; + VPointF c = mBrush.mGradient->mMatrix.map( + {mBrush.mGradient->radial.cx, mBrush.mGradient->radial.cy}); + VPointF f = mBrush.mGradient->mMatrix.map( + {mBrush.mGradient->radial.fx, mBrush.mGradient->radial.fy}); + mCNode->mGradient.center.x = c.x(); + mCNode->mGradient.center.y = c.y(); + mCNode->mGradient.focal.x = f.x(); + mCNode->mGradient.focal.y = f.y(); + + float scale = getScale(mBrush.mGradient->mMatrix); + mCNode->mGradient.cradius = mBrush.mGradient->radial.cradius * scale; + mCNode->mGradient.fradius = mBrush.mGradient->radial.fradius * scale; + updateGStops(mCNode.get(), mBrush.mGradient); + break; + } + default: + break; + } +} diff --git a/TMessagesProj/jni/rlottie/src/lottie/lottieitem.h b/TMessagesProj/jni/rlottie/src/lottie/lottieitem.h new file mode 100755 index 000000000..407994dee --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/lottieitem.h @@ -0,0 +1,525 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LOTTIEITEM_H +#define LOTTIEITEM_H + +#include +#include + +#include"lottieproxymodel.h" +#include"vmatrix.h" +#include"vpath.h" +#include"vpoint.h" +#include"vpathmesure.h" +#include"rlottiecommon.h" +#include"rlottie.h" +#include"vpainter.h" +#include"vdrawable.h" +#include"lottiekeypath.h" + +V_USE_NAMESPACE + +enum class DirtyFlagBit : uchar +{ + None = 0x00, + Matrix = 0x01, + Alpha = 0x02, + All = (Matrix | Alpha) +}; + +class LOTLayerItem; +class LOTMaskItem; +class VDrawable; + +class LOTDrawable : public VDrawable +{ +public: + void sync(); +public: + std::unique_ptr mCNode{nullptr}; + + ~LOTDrawable() { + if (mCNode && mCNode->mGradient.stopPtr) + free(mCNode->mGradient.stopPtr); + } +}; + +class LOTCompItem +{ +public: + explicit LOTCompItem(LOTModel *model); + static std::unique_ptr createLayerItem(LOTLayerData *layerData); + bool update(int frameNo); + void resize(const VSize &size); + VSize size() const; + void buildRenderTree(); + const LOTLayerNode * renderTree()const; + bool render(const rlottie::Surface &surface); + void setValue(const std::string &keypath, LOTVariant &value); +private: + VMatrix mScaleMatrix; + VSize mViewSize; + LOTCompositionData *mCompData; + std::unique_ptr mRootLayer; + bool mUpdateViewBox; + int mCurFrameNo; + std::vector mRenderList; + std::vector mDrawableList; +}; + +class LOTLayerMaskItem; + +class LOTClipperItem +{ +public: + explicit LOTClipperItem(VSize size): mSize(size){} + void update(const VMatrix &matrix); + VRle rle(); +public: + VSize mSize; + VPath mPath; + VRasterizer mRasterizer; +}; + +typedef vFlag DirtyFlag; + +class LOTLayerItem +{ +public: + virtual ~LOTLayerItem() = default; + LOTLayerItem& operator=(LOTLayerItem&&) noexcept = delete; + LOTLayerItem(LOTLayerData *layerData); + int id() const {return mLayerData->id();} + int parentId() const {return mLayerData->parentId();} + void setParentLayer(LOTLayerItem *parent){mParentLayer = parent;} + void setComplexContent(bool value) { mComplexContent = value;} + bool complexContent() const {return mComplexContent;} + virtual void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha); + VMatrix matrix(int frameNo) const; + virtual void renderList(std::vector &){} + virtual void render(VPainter *painter, const VRle &mask, const VRle &matteRle); + bool hasMatte() { if (mLayerData->mMatteType == MatteType::None) return false; return true; } + MatteType matteType() const { return mLayerData->mMatteType;} + bool visible() const; + virtual void buildLayerNode(); + LOTLayerNode * layerNode() const {return mLayerCNode.get();} + const std::string & name() const {return mLayerData->name();} + virtual bool resolveKeyPath(LOTKeyPath &keyPath, uint depth, LOTVariant &value); + VBitmap& bitmap() {return mRenderBuffer;} +protected: + virtual void updateContent() = 0; + inline VMatrix combinedMatrix() const {return mCombinedMatrix;} + inline int frameNo() const {return mFrameNo;} + inline float combinedAlpha() const {return mCombinedAlpha;} + inline bool isStatic() const {return mLayerData->isStatic();} + float opacity(int frameNo) const {return mLayerData->opacity(frameNo);} + inline DirtyFlag flag() const {return mDirtyFlag;} +protected: + std::vector mMasksCNode; + std::unique_ptr mLayerCNode; + std::vector mDrawableList; + std::unique_ptr mLayerMask; + LOTLayerData *mLayerData{nullptr}; + LOTLayerItem *mParentLayer{nullptr}; + VMatrix mCombinedMatrix; + VBitmap mRenderBuffer; + float mCombinedAlpha{0.0}; + int mFrameNo{-1}; + DirtyFlag mDirtyFlag{DirtyFlagBit::All}; + bool mComplexContent{false}; +}; + +class LOTCompLayerItem: public LOTLayerItem +{ +public: + explicit LOTCompLayerItem(LOTLayerData *layerData); + void renderList(std::vector &list)final; + void render(VPainter *painter, const VRle &mask, const VRle &matteRle) final; + void buildLayerNode() final; + bool resolveKeyPath(LOTKeyPath &keyPath, uint depth, LOTVariant &value) override; +protected: + void updateContent() final; +private: + void renderHelper(VPainter *painter, const VRle &mask, const VRle &matteRle); + void renderMatteLayer(VPainter *painter, const VRle &inheritMask, const VRle &matteRle, + LOTLayerItem *layer, LOTLayerItem *src); +private: + std::vector mLayersCNode; + std::vector> mLayers; + std::unique_ptr mClipper; +}; + +class LOTSolidLayerItem: public LOTLayerItem +{ +public: + explicit LOTSolidLayerItem(LOTLayerData *layerData); + void buildLayerNode() final; +protected: + void updateContent() final; + void renderList(std::vector &list) final; +private: + std::vector mCNodeList; + LOTDrawable mRenderNode; +}; + +class LOTContentItem; +class LOTContentGroupItem; +class LOTShapeLayerItem: public LOTLayerItem +{ +public: + explicit LOTShapeLayerItem(LOTLayerData *layerData); + static std::unique_ptr createContentItem(LOTData *contentData); + void renderList(std::vector &list)final; + void buildLayerNode() final; + bool resolveKeyPath(LOTKeyPath &keyPath, uint depth, LOTVariant &value) override; +protected: + void updateContent() final; + std::vector mCNodeList; + std::unique_ptr mRoot; +}; + +class LOTNullLayerItem: public LOTLayerItem +{ +public: + explicit LOTNullLayerItem(LOTLayerData *layerData); +protected: + void updateContent() final; +}; + +class LOTImageLayerItem: public LOTLayerItem +{ +public: + explicit LOTImageLayerItem(LOTLayerData *layerData); + void buildLayerNode() final; +protected: + void updateContent() final; + void renderList(std::vector &list) final; +private: + std::vector mCNodeList; + LOTDrawable mRenderNode; +}; + +class LOTMaskItem +{ +public: + explicit LOTMaskItem(LOTMaskData *data): mData(data){} + void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, const DirtyFlag &flag); + LOTMaskData::Mode maskMode() const { return mData->mMode;} + VRle rle(); +public: + LOTMaskData *mData; + float mCombinedAlpha{0}; + VMatrix mCombinedMatrix; + VPath mLocalPath; + VPath mFinalPath; + VRasterizer mRasterizer; + bool mRasterRequest{false}; +}; + +/* + * Handels mask property of a layer item + */ +class LOTLayerMaskItem +{ +public: + explicit LOTLayerMaskItem(LOTLayerData *layerData); + void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, const DirtyFlag &flag); + bool isStatic() const {return mStatic;} + VRle maskRle(const VRect &clipRect); +public: + std::vector mMasks; + VRle mRle; + bool mStatic{true}; + bool mDirty{true}; +}; + +class LOTPathDataItem; +class LOTPaintDataItem; +class LOTTrimItem; + +enum class ContentType +{ + Unknown, + Group, + Path, + Paint, + Trim +}; + +class LOTContentItem +{ +public: + virtual ~LOTContentItem() = default; + LOTContentItem& operator=(LOTContentItem&&) noexcept = delete; + LOTContentItem(ContentType type=ContentType::Unknown):mType(type) {} + virtual void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, const DirtyFlag &flag) = 0; + virtual void renderList(std::vector &){} + void setParent(LOTContentItem *parent) {mParent = parent;} + LOTContentItem *parent() const {return mParent;} + virtual bool resolveKeyPath(LOTKeyPath &, uint, LOTVariant &) {return false;} + ContentType type() const {return mType;} +private: + ContentType mType{ContentType::Unknown}; + LOTContentItem *mParent{nullptr}; +}; + +class LOTContentGroupItem: public LOTContentItem +{ +public: + explicit LOTContentGroupItem(LOTGroupData *data=nullptr); + void addChildren(LOTGroupData *data); + void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, const DirtyFlag &flag) override; + void applyTrim(); + void processTrimItems(std::vector &list); + void processPaintItems(std::vector &list); + void renderList(std::vector &list) override; + const VMatrix & matrix() const { return mMatrix;} + const std::string & name() const + { + static const std::string TAG = "__"; + return mData ? mData->name() : TAG; + } + bool resolveKeyPath(LOTKeyPath &keyPath, uint depth, LOTVariant &value) override; +protected: + LOTGroupData *mData{nullptr}; + std::vector> mContents; + VMatrix mMatrix; +}; + +class LOTPathDataItem : public LOTContentItem +{ +public: + LOTPathDataItem(bool staticPath): LOTContentItem(ContentType::Path), mStaticPath(staticPath){} + void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, const DirtyFlag &flag) final; + bool dirty() const {return mPathChanged;} + const VPath &localPath() const {return mTemp;} + const VPath &finalPath(); + void updatePath(const VPath &path) {mTemp = path; mPathChanged = true; mNeedUpdate = true;} + bool staticPath() const { return mStaticPath; } +protected: + virtual void updatePath(VPath& path, int frameNo) = 0; + virtual bool hasChanged(int prevFrame, int curFrame) = 0; +private: + bool hasChanged(int frameNo) { + int prevFrame = mFrameNo; + mFrameNo = frameNo; + if (prevFrame == -1) return true; + if (mStaticPath || + (prevFrame == frameNo)) return false; + return hasChanged(prevFrame, frameNo); + } + VPath mLocalPath; + VPath mTemp; + VPath mFinalPath; + int mFrameNo{-1}; + bool mPathChanged{true}; + bool mNeedUpdate{true}; + bool mStaticPath; +}; + +class LOTRectItem: public LOTPathDataItem +{ +public: + explicit LOTRectItem(LOTRectData *data); +protected: + void updatePath(VPath& path, int frameNo) final; + LOTRectData *mData; + + bool hasChanged(int prevFrame, int curFrame) final { + return (mData->mPos.changed(prevFrame, curFrame) || + mData->mSize.changed(prevFrame, curFrame) || + mData->mRound.changed(prevFrame, curFrame)) ? true : false; + } +}; + +class LOTEllipseItem: public LOTPathDataItem +{ +public: + explicit LOTEllipseItem(LOTEllipseData *data); +private: + void updatePath(VPath& path, int frameNo) final; + LOTEllipseData *mData; + bool hasChanged(int prevFrame, int curFrame) final { + return (mData->mPos.changed(prevFrame, curFrame) || + mData->mSize.changed(prevFrame, curFrame)) ? true : false; + } +}; + +class LOTShapeItem: public LOTPathDataItem +{ +public: + explicit LOTShapeItem(LOTShapeData *data); +private: + void updatePath(VPath& path, int frameNo) final; + LOTShapeData *mData; + bool hasChanged(int prevFrame, int curFrame) final { + return mData->mShape.changed(prevFrame, curFrame); + } +}; + +class LOTPolystarItem: public LOTPathDataItem +{ +public: + explicit LOTPolystarItem(LOTPolystarData *data); +private: + void updatePath(VPath& path, int frameNo) final; + LOTPolystarData *mData; + + bool hasChanged(int prevFrame, int curFrame) final { + return (mData->mPos.changed(prevFrame, curFrame) || + mData->mPointCount.changed(prevFrame, curFrame) || + mData->mInnerRadius.changed(prevFrame, curFrame) || + mData->mOuterRadius.changed(prevFrame, curFrame) || + mData->mInnerRoundness.changed(prevFrame, curFrame) || + mData->mOuterRoundness.changed(prevFrame, curFrame) || + mData->mRotation.changed(prevFrame, curFrame)) ? true : false; + } +}; + + + +class LOTPaintDataItem : public LOTContentItem +{ +public: + LOTPaintDataItem(bool staticContent); + void addPathItems(std::vector &list, int startOffset); + void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, const DirtyFlag &flag) override; + void renderList(std::vector &list) final; +protected: + virtual void updateContent(int frameNo) = 0; + virtual void updateRenderNode(); + inline float parentAlpha() const {return mParentAlpha;} +protected: + std::vector mPathItems; + LOTDrawable mDrawable; + VPath mPath; + float mParentAlpha{1.0f}; + int mFrameNo{-1}; + DirtyFlag mFlag; + bool mStaticContent; + bool mRenderNodeUpdate{true}; +}; + +class LOTFillItem : public LOTPaintDataItem +{ +public: + explicit LOTFillItem(LOTFillData *data); +protected: + void updateContent(int frameNo) final; + void updateRenderNode() final; + bool resolveKeyPath(LOTKeyPath &keyPath, uint depth, LOTVariant &value) final; +private: + LOTProxyModel mModel; + VColor mColor; +}; + +class LOTGFillItem : public LOTPaintDataItem +{ +public: + explicit LOTGFillItem(LOTGFillData *data); +protected: + void updateContent(int frameNo) final; + void updateRenderNode() final; +private: + LOTGFillData *mData; + std::unique_ptr mGradient; + float mAlpha{1.0}; + FillRule mFillRule{FillRule::Winding}; +}; + +class LOTStrokeItem : public LOTPaintDataItem +{ +public: + explicit LOTStrokeItem(LOTStrokeData *data); +protected: + void updateContent(int frameNo) final; + void updateRenderNode() final; + bool resolveKeyPath(LOTKeyPath &keyPath, uint depth, LOTVariant &value) final; +private: + LOTProxyModel mModel; + VColor mColor; + float mWidth{0}; + float mDashArray[6]; + int mDashArraySize{0}; +}; + +class LOTGStrokeItem : public LOTPaintDataItem +{ +public: + explicit LOTGStrokeItem(LOTGStrokeData *data); +protected: + void updateContent(int frameNo) final; + void updateRenderNode() final; +private: + LOTGStrokeData *mData; + std::unique_ptr mGradient; + CapStyle mCap{CapStyle::Flat}; + JoinStyle mJoin{JoinStyle::Miter}; + float mMiterLimit{0}; + VColor mColor; + float mAlpha{1.0}; + float mWidth{0}; + float mDashArray[6]; + int mDashArraySize{0}; +}; + + +// Trim Item + +class LOTTrimItem : public LOTContentItem +{ +public: + LOTTrimItem(LOTTrimData *data); + void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, const DirtyFlag &flag) final; + void update(); + void addPathItems(std::vector &list, int startOffset); +private: + bool pathDirty() const { + for (auto &i : mPathItems) { + if (i->dirty()) + return true; + } + return false; + } + struct Cache { + int mFrameNo{-1}; + LOTTrimData::Segment mSegment{}; + }; + Cache mCache; + std::vector mPathItems; + LOTTrimData *mData; + bool mDirty{true}; +}; + +class LOTRepeaterItem : public LOTContentGroupItem +{ +public: + explicit LOTRepeaterItem(LOTRepeaterData *data); + void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, const DirtyFlag &flag) final; + void renderList(std::vector &list) final; +private: + LOTRepeaterData *mRepeaterData; + bool mHidden{false}; + int mCopies{0}; +}; + + +#endif // LOTTIEITEM_H + + diff --git a/TMessagesProj/jni/rlottie/src/lottie/lottiekeypath.cpp b/TMessagesProj/jni/rlottie/src/lottie/lottiekeypath.cpp new file mode 100755 index 000000000..e8b6c6aef --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/lottiekeypath.cpp @@ -0,0 +1,86 @@ +#include "lottiekeypath.h" + +#include + +LOTKeyPath::LOTKeyPath(const std::string &keyPath) +{ + std::stringstream ss(keyPath); + std::string item; + + while (getline(ss, item, '.')) { + mKeys.push_back(item); + } +} + +bool LOTKeyPath::matches(const std::string &key, uint depth) +{ + if (skip(key)) { + // This is an object we programatically create. + return true; + } + if (depth > size()) { + return false; + } + if ((mKeys[depth] == key) || (mKeys[depth] == "*") || + (mKeys[depth] == "**")) { + return true; + } + return false; +} + +uint LOTKeyPath::nextDepth(const std::string key, uint depth) +{ + if (skip(key)) { + // If it's a container then we added programatically and it isn't a part + // of the keypath. + return depth; + } + if (mKeys[depth] != "**") { + // If it's not a globstar then it is part of the keypath. + return depth + 1; + } + if (depth == size()) { + // The last key is a globstar. + return depth; + } + if (mKeys[depth + 1] == key) { + // We are a globstar and the next key is our current key so consume + // both. + return depth + 2; + } + return depth; +} + +bool LOTKeyPath::fullyResolvesTo(const std::string key, uint depth) +{ + if (depth > mKeys.size()) { + return false; + } + + bool isLastDepth = (depth == size()); + + if (!isGlobstar(depth)) { + bool matches = (mKeys[depth] == key) || isGlob(depth); + return (isLastDepth || (depth == size() - 1 && endsWithGlobstar())) && + matches; + } + + bool isGlobstarButNextKeyMatches = !isLastDepth && mKeys[depth + 1] == key; + if (isGlobstarButNextKeyMatches) { + return depth == size() - 1 || + (depth == size() - 2 && endsWithGlobstar()); + } + + if (isLastDepth) { + return true; + } + + if (depth + 1 < size()) { + // We are a globstar but there is more than 1 key after the globstar we + // we can't fully match. + return false; + } + // Return whether the next key (which we now know is the last one) is the + // same as the current key. + return mKeys[depth + 1] == key; +} diff --git a/TMessagesProj/jni/rlottie/src/lottie/lottiekeypath.h b/TMessagesProj/jni/rlottie/src/lottie/lottiekeypath.h new file mode 100755 index 000000000..527788f73 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/lottiekeypath.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LOTTIEKEYPATH_H +#define LOTTIEKEYPATH_H + +#include "vglobal.h" +#include +#include + +class LOTKeyPath{ +public: + LOTKeyPath(const std::string &keyPath); + bool matches(const std::string &key, uint depth); + uint nextDepth(const std::string key, uint depth); + bool fullyResolvesTo(const std::string key, uint depth); + + bool propagate(const std::string key, uint depth) { + return skip(key) ? true : (depth < size()) || (mKeys[depth] == "**"); + } + bool skip(const std::string &key) const { return key == "__";} +private: + bool isGlobstar(uint depth) const {return mKeys[depth] == "**";} + bool isGlob(uint depth) const {return mKeys[depth] == "*";} + bool endsWithGlobstar() const { return mKeys.back() == "**"; } + uint size() const {return mKeys.size() - 1;} +private: + std::vector mKeys; +}; + +#endif //LOTTIEKEYPATH_H diff --git a/TMessagesProj/jni/rlottie/src/lottie/lottieloader.cpp b/TMessagesProj/jni/rlottie/src/lottie/lottieloader.cpp new file mode 100755 index 000000000..50178bda5 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/lottieloader.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "lottieloader.h" +#include "lottieparser.h" + +#include +#include +#include +using namespace std; + +#ifdef LOTTIE_CACHE_SUPPORT + +class LottieFileCache { +public: + static LottieFileCache &instance() + { + static LottieFileCache CACHE; + return CACHE; + } + std::shared_ptr find(const std::string &key) + { + auto search = mHash.find(key); + if (search != mHash.end()) { + return search->second; + } else { + return nullptr; + } + } + void add(const std::string &key, std::shared_ptr value) + { + mHash[key] = std::move(value); + } + +private: + LottieFileCache() = default; + + std::unordered_map> mHash; +}; + +#else + +class LottieFileCache { +public: + static LottieFileCache &instance() + { + static LottieFileCache CACHE; + return CACHE; + } + std::shared_ptr find(const std::string &) { return nullptr; } + void add(const std::string &, std::shared_ptr) {} +}; + +#endif + +static std::string dirname(const std::string &path) +{ + const char *ptr = strrchr(path.c_str(), '/'); + int len = int(ptr + 1 - path.c_str()); // +1 to include '/' + return std::string(path, 0, len); +} + +bool LottieLoader::load(const std::string &path) +{ + mModel = LottieFileCache::instance().find(path); + if (mModel) return true; + + std::ifstream f; + f.open(path); + + if (!f.is_open()) { + vCritical << "failed to open file = " << path.c_str(); + return false; + } else { + std::stringstream buf; + buf << f.rdbuf(); + + LottieParser parser(const_cast(buf.str().data()), dirname(path).c_str()); + if (parser.hasParsingError()) { + f.close(); + return false; + } + mModel = parser.model(); + LottieFileCache::instance().add(path, mModel); + + f.close(); + } + + return true; +} + +bool LottieLoader::loadFromData(std::string &&jsonData, const std::string &key, + const std::string &resourcePath) +{ + mModel = LottieFileCache::instance().find(key); + if (mModel) return true; + + LottieParser parser(const_cast(jsonData.c_str()), + resourcePath.c_str()); + mModel = parser.model(); + LottieFileCache::instance().add(key, mModel); + + return true; +} + +std::shared_ptr LottieLoader::model() +{ + return mModel; +} diff --git a/TMessagesProj/jni/rlottie/src/lottie/lottieloader.h b/TMessagesProj/jni/rlottie/src/lottie/lottieloader.h new file mode 100755 index 000000000..d7228bbce --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/lottieloader.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LOTTIELOADER_H +#define LOTTIELOADER_H + +#include +#include + +class LOTModel; +class LottieLoader +{ +public: + bool load(const std::string &filePath); + bool loadFromData(std::string &&jsonData, const std::string &key, const std::string &resourcePath); + std::shared_ptr model(); +private: + std::shared_ptr mModel; +}; + +#endif // LOTTIELOADER_H + + diff --git a/TMessagesProj/jni/rlottie/src/lottie/lottiemodel.cpp b/TMessagesProj/jni/rlottie/src/lottie/lottiemodel.cpp new file mode 100755 index 000000000..e51941cad --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/lottiemodel.cpp @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "lottiemodel.h" +#include +#include +#include +#include "vimageloader.h" +#include "vline.h" + +/* + * We process the iterator objects in the children list + * by iterating from back to front. when we find a repeater object + * we remove the objects from satrt till repeater object and then place + * under a new shape group object which we add it as children to the repeater + * object. + * Then we visit the childrens of the newly created shape group object to + * process the remaining repeater object(when children list contains more than + * one repeater). + * + */ +class LottieRepeaterProcesser { +public: + void visitChildren(LOTGroupData *obj) + { + for (auto i = obj->mChildren.rbegin(); i != obj->mChildren.rend(); + ++i) { + auto child = (*i).get(); + if (child->type() == LOTData::Type::Repeater) { + LOTRepeaterData *repeater = + static_cast(child); + // check if this repeater is already processed + // can happen if the layer is an asset and referenced by + // multiple layer. + if (repeater->content()) continue; + + repeater->setContent(std::make_shared()); + LOTShapeGroupData *content = repeater->content(); + // 1. increment the reverse iterator to point to the + // object before the repeater + ++i; + // 2. move all the children till repater to the group + std::move(obj->mChildren.begin(), i.base(), + back_inserter(content->mChildren)); + // 3. erase the objects from the original children list + obj->mChildren.erase(obj->mChildren.begin(), i.base()); + + // 5. visit newly created group to process remaining repeater + // object. + visitChildren(content); + // 6. exit the loop as the current iterators are invalid + break; + } else { + visit(child); + } + } + } + + void visit(LOTData *obj) + { + switch (obj->mType) { + case LOTData::Type::Repeater: + case LOTData::Type::ShapeGroup: + case LOTData::Type::Layer: { + visitChildren(static_cast(obj)); + break; + } + default: + break; + } + } +}; + +void LOTCompositionData::processRepeaterObjects() +{ + LottieRepeaterProcesser visitor; + visitor.visit(mRootLayer.get()); +} + +VMatrix LOTRepeaterTransform::matrix(int frameNo, float multiplier) const +{ + VPointF scale = mScale.value(frameNo) / 100.f; + scale.setX(std::pow(scale.x(), multiplier)); + scale.setY(std::pow(scale.y(), multiplier)); + VMatrix m; + m.translate(mPosition.value(frameNo) * multiplier) + .translate(mAnchor.value(frameNo)) + .scale(scale) + .rotate(mRotation.value(frameNo) * multiplier) + .translate(-mAnchor.value(frameNo)); + + return m; +} + +VMatrix TransformData::matrix(int frameNo, bool autoOrient) const +{ + VMatrix m; + VPointF position; + if (mSeparate) { + position.setX(mX.value(frameNo)); + position.setY(mY.value(frameNo)); + } else { + position = mPosition.value(frameNo); + } + + float angle = autoOrient ? mPosition.angle(frameNo) : 0; + if (m3D) { + m.translate(position) + .rotate(m3D->mRz.value(frameNo) + angle) + .rotate(m3D->mRy.value(frameNo), VMatrix::Axis::Y) + .rotate(m3D->mRx.value(frameNo), VMatrix::Axis::X) + .scale(mScale.value(frameNo) / 100.f) + .translate(-mAnchor.value(frameNo)); + } else { + m.translate(position) + .rotate(mRotation.value(frameNo) + angle) + .scale(mScale.value(frameNo) / 100.f) + .translate(-mAnchor.value(frameNo)); + } + return m; +} + +int LOTStrokeData::getDashInfo(int frameNo, float *array) const +{ + if (!mDash.mDashCount) return 0; + // odd case + if (mDash.mDashCount % 2) { + for (int i = 0; i < mDash.mDashCount; i++) { + array[i] = mDash.mDashArray[i].value(frameNo); + } + return mDash.mDashCount; + } else { // even case when last gap info is not provided. + int i; + for (i = 0; i < mDash.mDashCount - 1; i++) { + array[i] = mDash.mDashArray[i].value(frameNo); + } + array[i] = array[i - 1]; + array[i + 1] = mDash.mDashArray[i].value(frameNo); + return mDash.mDashCount + 1; + } +} + +int LOTGStrokeData::getDashInfo(int frameNo, float *array) const +{ + if (!mDash.mDashCount) return 0; + // odd case + if (mDash.mDashCount % 2) { + for (int i = 0; i < mDash.mDashCount; i++) { + array[i] = mDash.mDashArray[i].value(frameNo); + } + return mDash.mDashCount; + } else { // even case when last gap info is not provided. + int i; + for (i = 0; i < mDash.mDashCount - 1; i++) { + array[i] = mDash.mDashArray[i].value(frameNo); + } + array[i] = array[i - 1]; + array[i + 1] = mDash.mDashArray[i].value(frameNo); + return mDash.mDashCount + 1; + } +} + +/** + * Both the color stops and opacity stops are in the same array. + * There are {@link #colorPoints} colors sequentially as: + * [ + * ..., + * position, + * red, + * green, + * blue, + * ... + * ] + * + * The remainder of the array is the opacity stops sequentially as: + * [ + * ..., + * position, + * opacity, + * ... + * ] + */ +void LOTGradient::populate(VGradientStops &stops, int frameNo) +{ + LottieGradient gradData = mGradient.value(frameNo); + int size = gradData.mGradient.size(); + float * ptr = gradData.mGradient.data(); + int colorPoints = mColorPoints; + if (colorPoints == -1) { // for legacy bodymovin (ref: lottie-android) + colorPoints = size / 4; + } + int opacityArraySize = size - colorPoints * 4; + float *opacityPtr = ptr + (colorPoints * 4); + stops.clear(); + int j = 0; + for (int i = 0; i < colorPoints; i++) { + float colorStop = ptr[0]; + LottieColor color = LottieColor(ptr[1], ptr[2], ptr[3]); + if (opacityArraySize) { + if (j == opacityArraySize) { + // already reached the end + float stop1 = opacityPtr[j - 4]; + float op1 = opacityPtr[j - 3]; + float stop2 = opacityPtr[j - 2]; + float op2 = opacityPtr[j - 1]; + if (colorStop > stop2) { + stops.push_back( + std::make_pair(colorStop, color.toColor(op2))); + } else { + float progress = (colorStop - stop1) / (stop2 - stop1); + float opacity = op1 + progress * (op2 - op1); + stops.push_back( + std::make_pair(colorStop, color.toColor(opacity))); + } + continue; + } + for (; j < opacityArraySize; j += 2) { + float opacityStop = opacityPtr[j]; + if (opacityStop < colorStop) { + // add a color using opacity stop + stops.push_back(std::make_pair( + opacityStop, color.toColor(opacityPtr[j + 1]))); + continue; + } + // add a color using color stop + if (j == 0) { + stops.push_back(std::make_pair( + colorStop, color.toColor(opacityPtr[j + 1]))); + } else { + float progress = (colorStop - opacityPtr[j - 2]) / + (opacityPtr[j] - opacityPtr[j - 2]); + float opacity = + opacityPtr[j - 1] + + progress * (opacityPtr[j + 1] - opacityPtr[j - 1]); + stops.push_back( + std::make_pair(colorStop, color.toColor(opacity))); + } + j += 2; + break; + } + } else { + stops.push_back(std::make_pair(colorStop, color.toColor())); + } + ptr += 4; + } +} + +void LOTGradient::update(std::unique_ptr &grad, int frameNo) +{ + bool init = false; + if (!grad) { + if (mGradientType == 1) + grad = std::make_unique(0, 0, 0, 0); + else + grad = std::make_unique(0, 0, 0, 0, 0, 0); + grad->mSpread = VGradient::Spread::Pad; + init = true; + } + + if (!mGradient.isStatic() || init) { + populate(grad->mStops, frameNo); + } + + if (mGradientType == 1) { // linear gradient + VPointF start = mStartPoint.value(frameNo); + VPointF end = mEndPoint.value(frameNo); + grad->linear.x1 = start.x(); + grad->linear.y1 = start.y(); + grad->linear.x2 = end.x(); + grad->linear.y2 = end.y(); + } else { // radial gradient + VPointF start = mStartPoint.value(frameNo); + VPointF end = mEndPoint.value(frameNo); + grad->radial.cx = start.x(); + grad->radial.cy = start.y(); + grad->radial.cradius = + VLine::length(start.x(), start.y(), end.x(), end.y()); + /* + * Focal point is the point lives in highlight length distance from + * center along the line (start, end) and rotated by highlight angle. + * below calculation first finds the quadrant(angle) on which the point + * lives by applying inverse slope formula then adds the rotation angle + * to find the final angle. then point is retrived using circle equation + * of center, angle and distance. + */ + float progress = mHighlightLength.value(frameNo) / 100.0f; + if (vCompare(progress, 1.0f)) progress = 0.99f; + float startAngle = VLine(start, end).angle(); + float highlightAngle = mHighlightAngle.value(frameNo); + float angle = ((startAngle + highlightAngle) * M_PI) / 180.0f; + grad->radial.fx = + grad->radial.cx + std::cos(angle) * progress * grad->radial.cradius; + grad->radial.fy = + grad->radial.cy + std::sin(angle) * progress * grad->radial.cradius; + // Lottie dosen't have any focal radius concept. + grad->radial.fradius = 0; + } +} + +void LOTAsset::loadImageData(std::string data) +{ + if (!data.empty()) + mBitmap = VImageLoader::instance().load(data.c_str(), data.length()); +} + +void LOTAsset::loadImagePath(std::string path) +{ + if (!path.empty()) mBitmap = VImageLoader::instance().load(path.c_str()); +} diff --git a/TMessagesProj/jni/rlottie/src/lottie/lottiemodel.h b/TMessagesProj/jni/rlottie/src/lottie/lottiemodel.h new file mode 100755 index 000000000..e91a63b79 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/lottiemodel.h @@ -0,0 +1,970 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LOTModel_H +#define LOTModel_H + +#include +#include +#include +#include +#include"vpoint.h" +#include"vrect.h" +#include"vinterpolator.h" +#include"vmatrix.h" +#include"vbezier.h" +#include"vbrush.h" +#include"vpath.h" + +V_USE_NAMESPACE + +class LOTCompositionData; +class LOTLayerData; +class LOTTransformData; +class LOTShapeGroupData; +class LOTShapeData; +class LOTRectData; +class LOTEllipseData; +class LOTTrimData; +class LOTRepeaterData; +class LOTFillData; +class LOTStrokeData; +class LOTGroupData; +class LOTGFillData; +class LOTGStrokeData; +class LottieShapeData; +class LOTPolystarData; +class LOTMaskData; + +enum class MatteType +{ + None = 0, + Alpha = 1, + AlphaInv, + Luma, + LumaInv +}; + +enum class LayerType { + Precomp = 0, + Solid = 1, + Image = 2, + Null = 3, + Shape = 4, + Text = 5 +}; + +class LottieColor +{ +public: + LottieColor() = default; + LottieColor(float red, float green , float blue):r(red), g(green),b(blue){} + VColor toColor(float a=1){ return VColor((255 * r), (255 * g), (255 * b), (255 * a));} + friend inline LottieColor operator+(const LottieColor &c1, const LottieColor &c2); + friend inline LottieColor operator-(const LottieColor &c1, const LottieColor &c2); +public: + float r{1}; + float g{1}; + float b{1}; +}; + +inline LottieColor operator-(const LottieColor &c1, const LottieColor &c2) +{ + return LottieColor(c1.r - c2.r, c1.g - c2.g, c1.b - c2.b); +} +inline LottieColor operator+(const LottieColor &c1, const LottieColor &c2) +{ + return LottieColor(c1.r + c2.r, c1.g + c2.g, c1.b + c2.b); +} + +inline const LottieColor operator*(const LottieColor &c, float m) +{ return LottieColor(c.r*m, c.g*m, c.b*m); } + +inline const LottieColor operator*(float m, const LottieColor &c) +{ return LottieColor(c.r*m, c.g*m, c.b*m); } + +class LottieShapeData +{ +public: + void reserve(int size) { + mPoints.reserve(mPoints.size() + size); + } + void toPath(VPath& path) { + path.reset(); + + if (mPoints.empty()) return; + + int size = mPoints.size(); + const VPointF *points = mPoints.data(); + /* reserve exact memory requirement at once + * ptSize = size + 1(size + close) + * elmSize = size/3 cubic + 1 move + 1 close + */ + path.reserve(size + 1 , size/3 + 2); + path.moveTo(points[0]); + for (int i = 1 ; i < size; i+=3) { + path.cubicTo(points[i], points[i+1], points[i+2]); + } + if (mClosed) + path.close(); + } +public: + std::vector mPoints; + bool mClosed = false; /* "c" */ +}; + + + +template +inline T lerp(const T& start, const T& end, float t) +{ + return start + t * (end - start); +} + +inline LottieShapeData lerp(const LottieShapeData& start, const LottieShapeData& end, float t) +{ + // Usal case both start and end path has same size + // In case its different then truncate the larger path and do the interpolation. + LottieShapeData result; + auto size = std::min(start.mPoints.size(), end.mPoints.size()); + result.reserve(size); + for (unsigned int i = 0 ; i < size; i++) { + result.mPoints.push_back(start.mPoints[i] + t * (end.mPoints[i] - start.mPoints[i])); + } + return result; +} + +template +struct LOTKeyFrameValue +{ + T mStartValue; + T mEndValue; + T value(float t) const { + return lerp(mStartValue, mEndValue, t); + } + float angle(float ) const { return 0;} +}; + +template <> +struct LOTKeyFrameValue +{ + VPointF mStartValue; + VPointF mEndValue; + VPointF mInTangent; + VPointF mOutTangent; + bool mPathKeyFrame = false; + + VPointF value(float t) const { + if (mPathKeyFrame) { + /* + * position along the path calcualated + * using bezier at progress length (t * bezlen) + */ + VBezier b = VBezier::fromPoints(mStartValue, mStartValue + mOutTangent, + mEndValue + mInTangent, mEndValue); + return b.pointAt(b.tAtLength(t * b.length())); + + } else { + return lerp(mStartValue, mEndValue, t); + } + } + + float angle(float t) const { + if (mPathKeyFrame) { + VBezier b = VBezier::fromPoints(mStartValue, mStartValue + mOutTangent, + mEndValue + mInTangent, mEndValue); + return b.angleAt(b.tAtLength(t * b.length())); + } + return 0; + } +}; + + +template +class LOTKeyFrame +{ +public: + float progress(int frameNo) const { + return mInterpolator ? mInterpolator->value((frameNo - mStartFrame) / (mEndFrame - mStartFrame)) : 0; + } + T value(int frameNo) const { + return mValue.value(progress(frameNo)); + } + float angle(int frameNo) const { + return mValue.angle(progress(frameNo)); + } + +public: + float mStartFrame{0}; + float mEndFrame{0}; + std::shared_ptr mInterpolator; + LOTKeyFrameValue mValue; +}; + +template +class LOTAnimInfo +{ +public: + T value(int frameNo) const { + if (mKeyFrames.front().mStartFrame >= frameNo) + return mKeyFrames.front().mValue.mStartValue; + if(mKeyFrames.back().mEndFrame <= frameNo) + return mKeyFrames.back().mValue.mEndValue; + + for(const auto &keyFrame : mKeyFrames) { + if (frameNo >= keyFrame.mStartFrame && frameNo < keyFrame.mEndFrame) + return keyFrame.value(frameNo); + } + return T(); + } + + float angle(int frameNo) const { + if ((mKeyFrames.front().mStartFrame >= frameNo) || + (mKeyFrames.back().mEndFrame <= frameNo) ) + return 0; + + for(const auto &keyFrame : mKeyFrames) { + if (frameNo >= keyFrame.mStartFrame && frameNo < keyFrame.mEndFrame) + return keyFrame.angle(frameNo); + } + return 0; + } + + bool changed(int prevFrame, int curFrame) const { + int first = mKeyFrames.front().mStartFrame; + int last = mKeyFrames.back().mEndFrame; + + if ((first > prevFrame && first > curFrame) || + (last < prevFrame && last < curFrame)) { + return false; + } + + return true; + } + +public: + std::vector> mKeyFrames; +}; + +template +class LOTAnimatable +{ +public: + LOTAnimatable() { construct(impl.mValue, {}); } + explicit LOTAnimatable(T value) { construct(impl.mValue, std::move(value)); } + + const LOTAnimInfo& animation() const {return *(impl.mAnimInfo.get());} + const T& value() const {return impl.mValue;} + + LOTAnimInfo& animation() + { + if (mStatic) { + destroy(); + construct(impl.mAnimInfo, std::make_unique>()); + mStatic = false; + } + return *(impl.mAnimInfo.get()); + } + + T& value() + { + assert(mStatic); + return impl.mValue; + } + + // delete special member functions + LOTAnimatable(const LOTAnimatable &) = delete; + LOTAnimatable(LOTAnimatable &&) = delete; + LOTAnimatable& operator=(const LOTAnimatable&) = delete; + LOTAnimatable& operator=(LOTAnimatable&&) = delete; + + ~LOTAnimatable() {destroy();} + + bool isStatic() const {return mStatic;} + + T value(int frameNo) const { + return isStatic() ? value() : animation().value(frameNo); + } + + float angle(int frameNo) const { + return isStatic() ? 0 : animation().angle(frameNo); + } + + bool changed(int prevFrame, int curFrame) const { + return isStatic() ? false : animation().changed(prevFrame, curFrame); + } +private: + template + void construct(Tp& member, Tp&& val) + { + new (&member) Tp(std::move(val)); + } + + void destroy() { + if (mStatic) { + impl.mValue.~T(); + } else { + using std::unique_ptr; + impl.mAnimInfo.~unique_ptr>(); + } + } + union details { + std::unique_ptr> mAnimInfo; + T mValue; + details(){} + ~details(){} + }impl; + bool mStatic{true}; +}; + +enum class LottieBlendMode +{ + Normal = 0, + Multiply = 1, + Screen = 2, + OverLay = 3 +}; + +class LOTDataVisitor; +class LOTData +{ +public: + enum class Type :short { + Composition = 1, + Layer, + ShapeGroup, + Transform, + Fill, + Stroke, + GFill, + GStroke, + Rect, + Ellipse, + Shape, + Polystar, + Trim, + Repeater + }; + explicit LOTData(LOTData::Type type): mType(type){} + inline LOTData::Type type() const {return mType;} + bool isStatic() const{return mStatic;} + void setStatic(bool value) {mStatic = value;} + bool hidden() const {return mHidden;} + const std::string& name() const{ return mName;} +public: + std::string mName; + bool mStatic{true}; + bool mHidden{false}; + LOTData::Type mType; +}; + +class LOTGroupData: public LOTData +{ +public: + explicit LOTGroupData(LOTData::Type type):LOTData(type){} +public: + std::vector> mChildren; + std::shared_ptr mTransform; +}; + +class LOTShapeGroupData : public LOTGroupData +{ +public: + LOTShapeGroupData():LOTGroupData(LOTData::Type::ShapeGroup){} +}; + +class LOTLayerData; +struct LOTAsset +{ + enum class Type : unsigned char{ + Precomp, + Image, + Char + }; + bool isStatic() const {return mStatic;} + void setStatic(bool value) {mStatic = value;} + VBitmap bitmap() const {return mBitmap;} + void loadImageData(std::string data); + void loadImagePath(std::string Path); + Type mAssetType{Type::Precomp}; + bool mStatic{true}; + std::string mRefId; // ref id + std::vector> mLayers; + // image asset data + int mWidth{0}; + int mHeight{0}; + VBitmap mBitmap; +}; + +struct LOT3DData +{ + LOTAnimatable mRx{0}; + LOTAnimatable mRy{0}; + LOTAnimatable mRz{0}; +}; + +struct TransformData +{ + VMatrix matrix(int frameNo, bool autoOrient = false) const; + float opacity(int frameNo) const { return mOpacity.value(frameNo)/100.0f; } + bool isStatic() const { return mStatic;} + std::unique_ptr m3D; + LOTAnimatable mRotation{0}; /* "r" */ + LOTAnimatable mScale{{100, 100}}; /* "s" */ + LOTAnimatable mPosition; /* "p" */ + LOTAnimatable mX{0}; + LOTAnimatable mY{0}; + LOTAnimatable mAnchor; /* "a" */ + LOTAnimatable mOpacity{100}; /* "o" */ + bool mSeparate{false}; + bool mStatic{false}; +}; + +class LOTTransformData : public LOTData +{ +public: + LOTTransformData():LOTData(LOTData::Type::Transform){} + void set(std::unique_ptr data) + { + setStatic(data->isStatic()); + if (isStatic()) { + new (&impl.mStaticData) static_data(data->matrix(0), data->opacity(0)); + } else { + new (&impl.mData) std::unique_ptr(std::move(data)); + } + } + VMatrix matrix(int frameNo, bool autoOrient = false) const + { + if (isStatic()) return impl.mStaticData.mMatrix; + return impl.mData->matrix(frameNo, autoOrient); + } + float opacity(int frameNo) const + { + if (isStatic()) return impl.mStaticData.mOpacity; + return impl.mData->opacity(frameNo); + } + + LOTTransformData& operator=(LOTTransformData&&) = delete; + ~LOTTransformData() {destroy();} + +private: + void destroy() { + if (isStatic()) { + impl.mStaticData.~static_data(); + } else { + using std::unique_ptr; + impl.mData.~unique_ptr(); + } + } + struct static_data { + static_data(VMatrix &&m, float opacity): + mOpacity(opacity), mMatrix(std::move(m)){} + float mOpacity; + VMatrix mMatrix; + }; + union details { + std::unique_ptr mData; + static_data mStaticData; + details(){} + ~details(){} + }impl; +}; + +class LOTLayerData : public LOTGroupData +{ +public: + LOTLayerData():LOTGroupData(LOTData::Type::Layer){} + bool hasPathOperator() const noexcept {return mHasPathOperator;} + bool hasGradient() const noexcept {return mHasGradient;} + bool hasMask() const noexcept {return mHasMask;} + bool hasRepeater() const noexcept {return mHasRepeater;} + int id() const noexcept{ return mId;} + int parentId() const noexcept{ return mParentId;} + int inFrame() const noexcept{return mInFrame;} + int outFrame() const noexcept{return mOutFrame;} + int startFrame() const noexcept{return mStartFrame;} + int solidWidth() const noexcept{return mSolidLayer.mWidth;} + int solidHeight() const noexcept{return mSolidLayer.mHeight;} + LottieColor solidColor() const noexcept{return mSolidLayer.mColor;} + bool autoOrient() const noexcept{return mAutoOrient;} + int timeRemap(int frameNo) const; + VSize layerSize() const {return mLayerSize;} + bool precompLayer() const {return mLayerType == LayerType::Precomp;} + VMatrix matrix(int frameNo) const + { + return mTransform ? mTransform->matrix(frameNo, autoOrient()) : VMatrix{}; + } + float opacity(int frameNo) const + { + return mTransform ? mTransform->opacity(frameNo) : 1.0f; + } +public: + struct SolidLayer { + int mWidth{0}; + int mHeight{0}; + LottieColor mColor; + }; + + MatteType mMatteType{MatteType::None}; + LayerType mLayerType{LayerType::Null}; //lottie layer type (solid/shape/precomp) + int mParentId{-1}; // Lottie the id of the parent in the composition + int mId{-1}; // Lottie the group id used for parenting. + long mInFrame{0}; + long mOutFrame{0}; + long mStartFrame{0}; + VSize mLayerSize; + LottieBlendMode mBlendMode{LottieBlendMode::Normal}; + float mTimeStreatch{1.0f}; + std::string mPreCompRefId; + LOTAnimatable mTimeRemap; /* "tm" */ + SolidLayer mSolidLayer; + bool mHasPathOperator{false}; + bool mHasMask{false}; + bool mHasRepeater{false}; + bool mHasGradient{false}; + bool mAutoOrient{false}; + std::vector> mMasks; + LOTCompositionData *mCompRef{nullptr}; + std::shared_ptr mAsset; +}; + +using LayerInfo = std::tuple; + +class LOTCompositionData : public LOTData +{ +public: + LOTCompositionData():LOTData(LOTData::Type::Composition){} + const std::vector &layerInfoList() const { return mLayerInfoList;} + double duration() const { + return frameDuration() / frameRate(); // in second + } + size_t frameAtPos(double pos) const { + if (pos < 0) pos = 0; + if (pos > 1) pos = 1; + return pos * frameDuration(); + } + long frameAtTime(double timeInSec) const { + return frameAtPos(timeInSec / duration()); + } + size_t totalFrame() const {return mEndFrame - mStartFrame;} + long frameDuration() const {return mEndFrame - mStartFrame -1;} + float frameRate() const {return mFrameRate;} + long startFrame() const {return mStartFrame;} + long endFrame() const {return mEndFrame;} + VSize size() const {return mSize;} + void processRepeaterObjects(); +public: + std::string mVersion; + VSize mSize; + long mStartFrame{0}; + long mEndFrame{0}; + float mFrameRate{60}; + LottieBlendMode mBlendMode{LottieBlendMode::Normal}; + std::shared_ptr mRootLayer; + std::unordered_map> mAssets; + + std::vector mLayerInfoList; + +}; + +/** + * TimeRemap has the value in time domain(in sec) + * To get the proper mapping first we get the mapped time at the current frame Number + * then we need to convert mapped time to frame number using the composition time line + * Ex: at frame 10 the mappend time is 0.5(500 ms) which will be convert to frame number + * 30 if the frame rate is 60. or will result to frame number 15 if the frame rate is 30. + */ +inline int LOTLayerData::timeRemap(int frameNo) const +{ + /* + * only consider startFrame() when there is no timeRemap. + * when a layer has timeremap bodymovin updates the startFrame() + * of all child layer so we don't have to take care of it. + */ + frameNo = mTimeRemap.isStatic() ? frameNo - startFrame(): + mCompRef->frameAtTime(mTimeRemap.value(frameNo)); + /* Apply time streatch if it has any. + * Time streatch is just a factor by which the animation will speedup or slow + * down with respect to the overal animation. + * Time streach factor is already applied to the layers inFrame and outFrame. + * @TODO need to find out if timestreatch also affects the in and out frame of the + * child layers or not. */ + return frameNo / mTimeStreatch; +} + +class LOTFillData : public LOTData +{ +public: + LOTFillData():LOTData(LOTData::Type::Fill){} + LottieColor color(int frameNo) const {return mColor.value(frameNo);} + float opacity(int frameNo) const {return mOpacity.value(frameNo)/100.0f;} + FillRule fillRule() const {return mFillRule;} +public: + FillRule mFillRule{FillRule::Winding}; /* "r" */ + LOTAnimatable mColor; /* "c" */ + LOTAnimatable mOpacity{100}; /* "o" */ + bool mEnabled{true}; /* "fillEnabled" */ +}; + +struct LOTDashProperty +{ + LOTAnimatable mDashArray[5]; /* "d" "g" "o"*/ + int mDashCount{0}; + bool mStatic{true}; +}; + +class LOTStrokeData : public LOTData +{ +public: + LOTStrokeData():LOTData(LOTData::Type::Stroke){} + LottieColor color(int frameNo) const {return mColor.value(frameNo);} + float opacity(int frameNo) const {return mOpacity.value(frameNo)/100.0f;} + float strokeWidth(int frameNo) const {return mWidth.value(frameNo);} + CapStyle capStyle() const {return mCapStyle;} + JoinStyle joinStyle() const {return mJoinStyle;} + float meterLimit() const{return mMeterLimit;} + bool hasDashInfo() const { return !(mDash.mDashCount == 0);} + int getDashInfo(int frameNo, float *array) const; +public: + LOTAnimatable mColor; /* "c" */ + LOTAnimatable mOpacity{100}; /* "o" */ + LOTAnimatable mWidth{0}; /* "w" */ + CapStyle mCapStyle{CapStyle::Flat}; /* "lc" */ + JoinStyle mJoinStyle{JoinStyle::Miter}; /* "lj" */ + float mMeterLimit{0}; /* "ml" */ + LOTDashProperty mDash; + bool mEnabled{true}; /* "fillEnabled" */ +}; + +class LottieGradient +{ +public: + friend inline LottieGradient operator+(const LottieGradient &g1, const LottieGradient &g2); + friend inline LottieGradient operator-(const LottieGradient &g1, const LottieGradient &g2); + friend inline LottieGradient operator*(float m, const LottieGradient &g); +public: + std::vector mGradient; +}; + +inline LottieGradient operator+(const LottieGradient &g1, const LottieGradient &g2) +{ + if (g1.mGradient.size() != g2.mGradient.size()) + return g1; + + LottieGradient newG; + newG.mGradient = g1.mGradient; + + auto g2It = g2.mGradient.begin(); + for(auto &i : newG.mGradient) { + i = i + *g2It; + g2It++; + } + + return newG; +} + +inline LottieGradient operator-(const LottieGradient &g1, const LottieGradient &g2) +{ + if (g1.mGradient.size() != g2.mGradient.size()) + return g1; + LottieGradient newG; + newG.mGradient = g1.mGradient; + + auto g2It = g2.mGradient.begin(); + for(auto &i : newG.mGradient) { + i = i - *g2It; + g2It++; + } + + return newG; +} + +inline LottieGradient operator*(float m, const LottieGradient &g) +{ + LottieGradient newG; + newG.mGradient = g.mGradient; + + for(auto &i : newG.mGradient) { + i = i * m; + } + return newG; +} + + + +class LOTGradient : public LOTData +{ +public: + explicit LOTGradient(LOTData::Type type):LOTData(type){} + inline float opacity(int frameNo) const {return mOpacity.value(frameNo)/100.0;} + void update(std::unique_ptr &grad, int frameNo); + +private: + void populate(VGradientStops &stops, int frameNo); +public: + int mGradientType{1}; /* "t" Linear=1 , Radial = 2*/ + LOTAnimatable mStartPoint; /* "s" */ + LOTAnimatable mEndPoint; /* "e" */ + LOTAnimatable mHighlightLength{0}; /* "h" */ + LOTAnimatable mHighlightAngle{0}; /* "a" */ + LOTAnimatable mOpacity{100}; /* "o" */ + LOTAnimatable mGradient; /* "g" */ + int mColorPoints{-1}; + bool mEnabled{true}; /* "fillEnabled" */ +}; + +class LOTGFillData : public LOTGradient +{ +public: + LOTGFillData():LOTGradient(LOTData::Type::GFill){} + FillRule fillRule() const {return mFillRule;} +public: + FillRule mFillRule{FillRule::Winding}; /* "r" */ +}; + +class LOTGStrokeData : public LOTGradient +{ +public: + LOTGStrokeData():LOTGradient(LOTData::Type::GStroke){} + float width(int frameNo) const {return mWidth.value(frameNo);} + CapStyle capStyle() const {return mCapStyle;} + JoinStyle joinStyle() const {return mJoinStyle;} + float meterLimit() const{return mMeterLimit;} + bool hasDashInfo() const { return !(mDash.mDashCount == 0);} + int getDashInfo(int frameNo, float *array) const; +public: + LOTAnimatable mWidth; /* "w" */ + CapStyle mCapStyle{CapStyle::Flat}; /* "lc" */ + JoinStyle mJoinStyle{JoinStyle::Miter}; /* "lj" */ + float mMeterLimit{0}; /* "ml" */ + LOTDashProperty mDash; +}; + +class LOTPath : public LOTData +{ +public: + explicit LOTPath(LOTData::Type type):LOTData(type){} + VPath::Direction direction() { if (mDirection == 3) return VPath::Direction::CCW; + else return VPath::Direction::CW;} +public: + int mDirection{1}; +}; + +class LOTShapeData : public LOTPath +{ +public: + LOTShapeData():LOTPath(LOTData::Type::Shape){} + void process(); +public: + LOTAnimatable mShape; +}; + +class LOTMaskData +{ +public: + enum class Mode { + None, + Add, + Substarct, + Intersect, + Difference + }; + float opacity(int frameNo) const {return mOpacity.value(frameNo)/100.0f;} + bool isStatic() const {return mIsStatic;} +public: + LOTAnimatable mShape; + LOTAnimatable mOpacity{100}; + bool mInv{false}; + bool mIsStatic{true}; + LOTMaskData::Mode mMode; +}; + +class LOTRectData : public LOTPath +{ +public: + LOTRectData():LOTPath(LOTData::Type::Rect){} +public: + LOTAnimatable mPos; + LOTAnimatable mSize; + LOTAnimatable mRound{0}; +}; + +class LOTEllipseData : public LOTPath +{ +public: + LOTEllipseData():LOTPath(LOTData::Type::Ellipse){} +public: + LOTAnimatable mPos; + LOTAnimatable mSize; +}; + +class LOTPolystarData : public LOTPath +{ +public: + enum class PolyType { + Star = 1, + Polygon = 2 + }; + LOTPolystarData():LOTPath(LOTData::Type::Polystar){} +public: + LOTPolystarData::PolyType mType{PolyType::Polygon}; + LOTAnimatable mPos; + LOTAnimatable mPointCount{0}; + LOTAnimatable mInnerRadius{0}; + LOTAnimatable mOuterRadius{0}; + LOTAnimatable mInnerRoundness{0}; + LOTAnimatable mOuterRoundness{0}; + LOTAnimatable mRotation{0}; +}; + +class LOTTrimData : public LOTData +{ +public: + struct Segment { + float start{0}; + float end{0}; + Segment() {} + Segment(float s, float e):start(s), end(e) {} + }; + enum class TrimType { + Simultaneously, + Individually + }; + LOTTrimData():LOTData(LOTData::Type::Trim){} + /* + * if start > end vector trims the path as a loop ( 2 segment) + * if start < end vector trims the path without loop ( 1 segment). + * if no offset then there is no loop. + */ + Segment segment(int frameNo) const { + float start = mStart.value(frameNo)/100.0f; + float end = mEnd.value(frameNo)/100.0f; + float offset = fmod(mOffset.value(frameNo), 360.0f)/ 360.0f; + + float diff = fabs(start - end); + if (vCompare(diff, 0.0f)) return Segment(0, 0); + if (vCompare(diff, 1.0f)) return Segment(0, 1); + + if (offset > 0) { + start += offset; + end += offset; + if (start <= 1 && end <=1) { + return noloop(start, end); + } else if (start > 1 && end > 1) { + return noloop(start - 1, end - 1); + } else { + if (start > 1) return loop(start - 1 , end); + else return loop(start , end - 1); + } + } else { + start += offset; + end += offset; + if (start >= 0 && end >= 0) { + return noloop(start, end); + } else if (start < 0 && end < 0) { + return noloop(1 + start, 1 + end); + } else { + if (start < 0) return loop(1 + start, end); + else return loop(start , 1 + end); + } + } + } + LOTTrimData::TrimType type() const {return mTrimType;} +private: + Segment noloop(float start, float end) const{ + assert(start >= 0); + assert(end >= 0); + Segment s; + s.start = std::min(start, end); + s.end = std::max(start, end); + return s; + } + Segment loop(float start, float end) const{ + assert(start >= 0); + assert(end >= 0); + Segment s; + s.start = std::max(start, end); + s.end = std::min(start, end); + return s; + } +public: + LOTAnimatable mStart{0}; + LOTAnimatable mEnd{0}; + LOTAnimatable mOffset{0}; + LOTTrimData::TrimType mTrimType{TrimType::Simultaneously}; +}; + +class LOTRepeaterTransform +{ +public: + VMatrix matrix(int frameNo, float multiplier) const; + float startOpacity(int frameNo) const { return mStartOpacity.value(frameNo)/100;} + float endOpacity(int frameNo) const { return mEndOpacity.value(frameNo)/100;} + bool isStatic() const + { + return mRotation.isStatic() && + mScale.isStatic() && + mPosition.isStatic() && + mAnchor.isStatic() && + mStartOpacity.isStatic() && + mEndOpacity.isStatic(); + } +public: + LOTAnimatable mRotation{0}; /* "r" */ + LOTAnimatable mScale{{100, 100}}; /* "s" */ + LOTAnimatable mPosition; /* "p" */ + LOTAnimatable mAnchor; /* "a" */ + LOTAnimatable mStartOpacity{100}; /* "so" */ + LOTAnimatable mEndOpacity{100}; /* "eo" */ +}; + +class LOTRepeaterData : public LOTData +{ +public: + LOTRepeaterData():LOTData(LOTData::Type::Repeater){} + LOTShapeGroupData *content() const { return mContent ? mContent.get() : nullptr; } + void setContent(std::shared_ptr content) {mContent = std::move(content);} + int maxCopies() const { return int(mMaxCopies);} + float copies(int frameNo) const {return mCopies.value(frameNo);} + float offset(int frameNo) const {return mOffset.value(frameNo);} +public: + std::shared_ptr mContent{nullptr}; + LOTRepeaterTransform mTransform; + LOTAnimatable mCopies{0}; + LOTAnimatable mOffset{0}; + float mMaxCopies{0.0}; +}; + +class LOTModel +{ +public: + bool isStatic() const {return mRoot->isStatic();} + double duration() const {return mRoot->duration();} + size_t totalFrame() const {return mRoot->totalFrame();} + size_t frameDuration() const {return mRoot->frameDuration();} + size_t frameRate() const {return mRoot->frameRate();} + size_t startFrame() const {return mRoot->startFrame();} + size_t endFrame() const {return mRoot->endFrame();} + size_t frameAtPos(double pos) const {return mRoot->frameAtPos(pos);} + const std::vector &layerInfoList() const { return mRoot->layerInfoList();} +public: + std::shared_ptr mRoot; +}; + +#endif // LOTModel_H diff --git a/TMessagesProj/jni/rlottie/src/lottie/lottieparser.cpp b/TMessagesProj/jni/rlottie/src/lottie/lottieparser.cpp new file mode 100755 index 000000000..ad4d53fcc --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/lottieparser.cpp @@ -0,0 +1,2620 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "lottieparser.h" + +//#define DEBUG_PARSER + +//#define DEBUG_PRINT_TREE + +// This parser implements JSON token-by-token parsing with an API that is +// more direct; we don't have to create handler object and +// callbacks. Instead, we retrieve values from the JSON stream by calling +// GetInt(), GetDouble(), GetString() and GetBool(), traverse into structures +// by calling EnterObject() and EnterArray(), and skip over unwanted data by +// calling SkipValue(). As we know the lottie file structure this way will be +// the efficient way of parsing the file. +// +// If you aren't sure of what's next in the JSON data, you can use PeekType() +// and PeekValue() to look ahead to the next object before reading it. +// +// If you call the wrong retrieval method--e.g. GetInt when the next JSON token +// is not an int, EnterObject or EnterArray when there isn't actually an object +// or array to read--the stream parsing will end immediately and no more data +// will be delivered. +// +// After calling EnterObject, you retrieve keys via NextObjectKey() and values +// via the normal getters. When NextObjectKey() returns null, you have exited +// the object, or you can call SkipObject() to skip to the end of the object +// immediately. If you fetch the entire object (i.e. NextObjectKey() returned +// null), you should not call SkipObject(). +// +// After calling EnterArray(), you must alternate between calling +// NextArrayValue() to see if the array has more data, and then retrieving +// values via the normal getters. You can call SkipArray() to skip to the end of +// the array immediately. If you fetch the entire array (i.e. NextArrayValue() +// returned null), you should not call SkipArray(). +// +// This parser uses in-situ strings, so the JSON buffer will be altered during +// the parse. + +#include +#include + +#include "lottiemodel.h" +#include "rapidjson/document.h" + +RAPIDJSON_DIAG_PUSH +#ifdef __GNUC__ +RAPIDJSON_DIAG_OFF(effc++) +#endif + +using namespace rapidjson; + +template +std::string to_string(T value) +{ + std::ostringstream os ; + os << value ; + return os.str() ; +} + +class LookaheadParserHandler { +public: + bool Null() + { + st_ = kHasNull; + v_.SetNull(); + return true; + } + bool Bool(bool b) + { + st_ = kHasBool; + v_.SetBool(b); + return true; + } + bool Int(int i) + { + st_ = kHasNumber; + v_.SetInt(i); + return true; + } + bool Uint(unsigned u) + { + st_ = kHasNumber; + v_.SetUint(u); + return true; + } + bool Int64(int64_t i) + { + st_ = kHasNumber; + v_.SetInt64(i); + return true; + } + bool Uint64(uint64_t u) + { + st_ = kHasNumber; + v_.SetUint64(u); + return true; + } + bool Double(double d) + { + st_ = kHasNumber; + v_.SetDouble(d); + return true; + } + bool RawNumber(const char *, SizeType, bool) { return false; } + bool String(const char *str, SizeType length, bool) + { + st_ = kHasString; + v_.SetString(str, length); + return true; + } + bool StartObject() + { + st_ = kEnteringObject; + return true; + } + bool Key(const char *str, SizeType length, bool) + { + st_ = kHasKey; + v_.SetString(str, length); + return true; + } + bool EndObject(SizeType) + { + st_ = kExitingObject; + return true; + } + bool StartArray() + { + st_ = kEnteringArray; + return true; + } + bool EndArray(SizeType) + { + st_ = kExitingArray; + return true; + } + +protected: + explicit LookaheadParserHandler(char *str); + void ParseNext(); + +protected: + enum LookaheadParsingState { + kInit, + kError, + kHasNull, + kHasBool, + kHasNumber, + kHasString, + kHasKey, + kEnteringObject, + kExitingObject, + kEnteringArray, + kExitingArray + }; + + Value v_; + LookaheadParsingState st_; + Reader r_; + InsituStringStream ss_; + + static const int parseFlags = kParseDefaultFlags | kParseInsituFlag; +}; + +class LottieParserImpl : protected LookaheadParserHandler { +public: + LottieParserImpl(char *str, const char *dir_path) + : LookaheadParserHandler(str), mDirPath(dir_path) + { + } + +public: + bool EnterObject(); + bool EnterArray(); + const char *NextObjectKey(); + bool NextArrayValue(); + int GetInt(); + double GetDouble(); + const char *GetString(); + bool GetBool(); + void GetNull(); + + void SkipObject(); + void SkipArray(); + void SkipValue(); + Value *PeekValue(); + int PeekType(); // returns a rapidjson::Type, or -1 for no value (at end of + // object/array) + + bool IsValid() { return st_ != kError; } + + void Skip(const char *key); + LottieBlendMode getBlendMode(); + CapStyle getLineCap(); + JoinStyle getLineJoin(); + FillRule getFillRule(); + LOTTrimData::TrimType getTrimType(); + MatteType getMatteType(); + LayerType getLayerType(); + + std::shared_ptr composition() const + { + return mComposition; + } + void parseComposition(); + void parseAssets(LOTCompositionData *comp); + std::shared_ptr parseAsset(); + void parseLayers(LOTCompositionData *comp); + std::shared_ptr parseLayer(bool record = false); + void parseMaskProperty(LOTLayerData *layer); + void parseShapesAttr(LOTLayerData *layer); + void parseObject(LOTGroupData *parent); + std::shared_ptr parseMaskObject(); + std::shared_ptr parseObjectTypeAttr(); + std::shared_ptr parseGroupObject(); + std::shared_ptr parseRectObject(); + std::shared_ptr parseEllipseObject(); + std::shared_ptr parseShapeObject(); + std::shared_ptr parsePolystarObject(); + + std::shared_ptr parseTransformObject(bool ddd = false); + std::shared_ptr parseFillObject(); + std::shared_ptr parseGFillObject(); + std::shared_ptr parseStrokeObject(); + std::shared_ptr parseGStrokeObject(); + std::shared_ptr parseTrimObject(); + std::shared_ptr parseReapeaterObject(); + + void parseGradientProperty(LOTGradient *gradient, const char *key); + + VPointF parseInperpolatorPoint(); + + void getValue(VPointF &val); + void getValue(float &val); + void getValue(LottieColor &val); + void getValue(int &val); + void getValue(LottieShapeData &shape); + void getValue(LottieGradient &gradient); + void getValue(std::vector &v); + void getValue(LOTRepeaterTransform &); + + template + bool parseKeyFrameValue(const char *key, LOTKeyFrameValue &value); + template + void parseKeyFrame(LOTAnimInfo &obj); + template + void parseProperty(LOTAnimatable &obj); + template + void parsePropertyHelper(LOTAnimatable &obj); + + void parseShapeKeyFrame(LOTAnimInfo &obj); + void parseShapeProperty(LOTAnimatable &obj); + void parseDashProperty(LOTDashProperty &dash); + + std::shared_ptr interpolator(VPointF, VPointF, std::string); + + LottieColor toColor(const char *str); + + void resolveLayerRefs(); + + bool hasParsingError(); + +protected: + std::unordered_map> + mInterpolatorCache; + std::shared_ptr mComposition; + LOTCompositionData * compRef{nullptr}; + LOTLayerData * curLayerRef{nullptr}; + std::vector> mLayersToUpdate; + std::string mDirPath; + std::vector mLayerInfoList; + void SkipOut(int depth); + bool parsingError{false}; +}; + +LookaheadParserHandler::LookaheadParserHandler(char *str) + : v_(), st_(kInit), r_(), ss_(str) +{ + r_.IterativeParseInit(); + ParseNext(); +} + +void LookaheadParserHandler::ParseNext() +{ + if (r_.HasParseError()) { + st_ = kError; + return; + } + + if (!r_.IterativeParseNext(ss_, *this)) { + vCritical << "Lottie file parsing error"; + st_ = kError; + } +} + +bool LottieParserImpl::EnterObject() +{ + if (st_ != kEnteringObject) { + st_ = kError; + return false; + } + + ParseNext(); + return true; +} + +bool LottieParserImpl::EnterArray() +{ + if (st_ != kEnteringArray) { + st_ = kError; + return false; + } + + ParseNext(); + return true; +} + +const char *LottieParserImpl::NextObjectKey() +{ + if (st_ == kHasKey) { + const char *result = v_.GetString(); + ParseNext(); + return result; + } + + /* SPECIAL CASE + * The parser works with a prdefined rule that it will be only + * while (NextObjectKey()) for each object but in case of our nested group + * object we can call multiple time NextObjectKey() while exiting the object + * so ignore those and don't put parser in the error state. + * */ + if (st_ == kExitingArray || st_ == kEnteringObject) { + // #ifdef DEBUG_PARSER + // vDebug<<"Object: Exiting nested loop"; + // #endif + return 0; + } + + if (st_ != kExitingObject) { + st_ = kError; + return 0; + } + + ParseNext(); + return 0; +} + +bool LottieParserImpl::NextArrayValue() +{ + if (st_ == kExitingArray) { + ParseNext(); + return false; + } + + /* SPECIAL CASE + * same as NextObjectKey() + */ + if (st_ == kExitingObject) { + // #ifdef DEBUG_PARSER + // vDebug<<"Array: Exiting nested loop"; + // #endif + return 0; + } + + if (st_ == kError || st_ == kHasKey) { + st_ = kError; + return false; + } + + return true; +} + +int LottieParserImpl::GetInt() +{ + if (st_ != kHasNumber || !v_.IsInt()) { + st_ = kError; + return 0; + } + + int result = v_.GetInt(); + ParseNext(); + return result; +} + +double LottieParserImpl::GetDouble() +{ + if (st_ != kHasNumber) { + st_ = kError; + return 0.; + } + + double result = v_.GetDouble(); + ParseNext(); + return result; +} + +bool LottieParserImpl::GetBool() +{ + if (st_ != kHasBool) { + st_ = kError; + return false; + } + + bool result = v_.GetBool(); + ParseNext(); + return result; +} + +void LottieParserImpl::GetNull() +{ + if (st_ != kHasNull) { + st_ = kError; + return; + } + + ParseNext(); +} + +const char *LottieParserImpl::GetString() +{ + if (st_ != kHasString) { + st_ = kError; + return 0; + } + + const char *result = v_.GetString(); + ParseNext(); + return result; +} + +void LottieParserImpl::SkipOut(int depth) +{ + do { + if (st_ == kEnteringArray || st_ == kEnteringObject) { + ++depth; + } else if (st_ == kExitingArray || st_ == kExitingObject) { + --depth; + } else if (st_ == kError) { + return; + } + + ParseNext(); + } while (depth > 0); +} + +void LottieParserImpl::SkipValue() +{ + SkipOut(0); +} + +void LottieParserImpl::SkipArray() +{ + SkipOut(1); +} + +void LottieParserImpl::SkipObject() +{ + SkipOut(1); +} + +Value *LottieParserImpl::PeekValue() +{ + if (st_ >= kHasNull && st_ <= kHasKey) { + return &v_; + } + + return 0; +} + +int LottieParserImpl::PeekType() +{ + if (st_ >= kHasNull && st_ <= kHasKey) { + return v_.GetType(); + } + + if (st_ == kEnteringArray) { + return kArrayType; + } + + if (st_ == kEnteringObject) { + return kObjectType; + } + + return -1; +} + +void LottieParserImpl::Skip(const char * /*key*/) +{ + if (PeekType() == kArrayType) { + EnterArray(); + SkipArray(); + } else if (PeekType() == kObjectType) { + EnterObject(); + SkipObject(); + } else { + SkipValue(); + } +} + +LottieBlendMode LottieParserImpl::getBlendMode() { + LottieBlendMode mode = LottieBlendMode::Normal; + if (PeekType() != kNumberType) { + parsingError = true; + return mode; + } + + switch (GetInt()) { + case 1: + mode = LottieBlendMode::Multiply; + break; + case 2: + mode = LottieBlendMode::Screen; + break; + case 3: + mode = LottieBlendMode::OverLay; + break; + default: + break; + } + return mode; +} + +void LottieParserImpl::resolveLayerRefs() +{ + for (const auto &i : mLayersToUpdate) { + LOTLayerData *layer = i.get(); + auto search = compRef->mAssets.find(layer->mPreCompRefId); + if (search != compRef->mAssets.end()) { + if (layer->mLayerType == LayerType::Image) { + layer->mAsset = search->second; + } else if (layer->mLayerType == LayerType::Precomp) { + layer->mChildren = search->second->mLayers; + layer->setStatic(layer->isStatic() && + search->second->isStatic()); + } + } + } +} + +bool LottieParserImpl::hasParsingError() { + return parsingError; +} + +void LottieParserImpl::parseComposition() { + if (PeekType() != kObjectType) { + parsingError = true; + return; + } + EnterObject(); + std::shared_ptr sharedComposition = std::make_shared(); + LOTCompositionData *comp = sharedComposition.get(); + compRef = comp; + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "v")) { + if (PeekType() != kStringType) { + parsingError = true; + return; + } + comp->mVersion = std::string(GetString()); + } else if (0 == strcmp(key, "w")) { + if (PeekType() != kNumberType) { + parsingError = true; + return; + } + comp->mSize.setWidth(GetInt()); + } else if (0 == strcmp(key, "h")) { + if (PeekType() != kNumberType) { + parsingError = true; + return; + } + comp->mSize.setHeight(GetInt()); + } else if (0 == strcmp(key, "ip")) { + if (PeekType() != kNumberType) { + parsingError = true; + return; + } + comp->mStartFrame = GetDouble(); + } else if (0 == strcmp(key, "op")) { + if (PeekType() != kNumberType) { + parsingError = true; + return; + } + comp->mEndFrame = GetDouble(); + } else if (0 == strcmp(key, "fr")) { + if (PeekType() != kNumberType) { + parsingError = true; + return; + } + comp->mFrameRate = GetDouble(); + } else if (0 == strcmp(key, "assets")) { + parseAssets(comp); + } else if (0 == strcmp(key, "layers")) { + parseLayers(comp); + } else { +#ifdef DEBUG_PARSER + vWarning << "Composition Attribute Skipped : " << key; +#endif + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return; + } + resolveLayerRefs(); + comp->setStatic(comp->mRootLayer->isStatic()); + comp->mRootLayer->mInFrame = comp->mStartFrame; + comp->mRootLayer->mOutFrame = comp->mEndFrame; + + comp->mLayerInfoList = std::move(mLayerInfoList); + + mComposition = sharedComposition; +} + +void LottieParserImpl::parseAssets(LOTCompositionData *composition) { + if (PeekType() != kArrayType) { + parsingError = true; + return; + } + EnterArray(); + while (NextArrayValue()) { + if (parsingError) { + return; + } + std::shared_ptr asset = parseAsset(); + if (asset == nullptr) { + return; + } + composition->mAssets[asset->mRefId] = asset; + } + if (!IsValid()) { + parsingError = true; + return; + } + // update the precomp layers with the actual layer object +} + +static constexpr const unsigned char B64index[256] = { + 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, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51}; + +std::string b64decode(const void *data, const size_t len) +{ + unsigned char *p = (unsigned char *)data; + int pad = len > 0 && (len % 4 || p[len - 1] == '='); + const size_t L = ((len + 3) / 4 - pad) * 4; + std::string str(L / 4 * 3 + pad, '\0'); + + for (size_t i = 0, j = 0; i < L; i += 4) { + int n = B64index[p[i]] << 18 | B64index[p[i + 1]] << 12 | + B64index[p[i + 2]] << 6 | B64index[p[i + 3]]; + str[j++] = n >> 16; + str[j++] = n >> 8 & 0xFF; + str[j++] = n & 0xFF; + } + if (pad) { + int n = B64index[p[L]] << 18 | B64index[p[L + 1]] << 12; + str[str.size() - 1] = n >> 16; + + if (len > L + 2 && p[L + 2] != '=') { + n |= B64index[p[L + 2]] << 6; + str.push_back(n >> 8 & 0xFF); + } + } + return str; +} + +static std::string convertFromBase64(const std::string &str) +{ + // usual header look like "data:image/png;base64," + // so need to skip till ','. + int startIndex = str.find(",", 0); + startIndex += 1; // skip "," + int length = str.length() - startIndex; + + const char *b64Data = str.c_str() + startIndex; + + return b64decode(b64Data, length); +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/layers/shape.json + * + */ +std::shared_ptr LottieParserImpl::parseAsset() { + std::shared_ptr sharedAsset = std::make_shared(); + if (PeekType() != kObjectType) { + parsingError = true; + return sharedAsset; + } + LOTAsset *asset = sharedAsset.get(); + std::string filename; + std::string relativePath; + bool embededResource = false; + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "w")) { + if (PeekType() != kNumberType) { + parsingError = true; + return sharedAsset; + } + asset->mWidth = GetInt(); + } else if (0 == strcmp(key, "h")) { + if (PeekType() != kNumberType) { + parsingError = true; + return sharedAsset; + } + asset->mHeight = GetInt(); + } else if (0 == strcmp(key, "p")) { /* image name */ + asset->mAssetType = LOTAsset::Type::Image; + if (PeekType() != kStringType) { + parsingError = true; + return sharedAsset; + } + filename = std::string(GetString()); + } else if (0 == strcmp(key, "u")) { /* relative image path */ + if (PeekType() != kStringType) { + parsingError = true; + return sharedAsset; + } + relativePath = std::string(GetString()); + } else if (0 == strcmp(key, "e")) { /* relative image path */ + embededResource = GetInt(); + } else if (0 == strcmp(key, "id")) { /* reference id*/ + if (PeekType() == kStringType) { + asset->mRefId = std::string(GetString()); + } else { + if (PeekType() != kNumberType) { + parsingError = true; + return sharedAsset; + } + asset->mRefId = to_string(GetInt()); + } + } else if (0 == strcmp(key, "layers")) { + asset->mAssetType = LOTAsset::Type::Precomp; + if (PeekType() != kArrayType) { + parsingError = true; + return sharedAsset; + } + EnterArray(); + bool staticFlag = true; + while (NextArrayValue()) { + if (parsingError) { + return sharedAsset; + } + std::shared_ptr layer = parseLayer(); + staticFlag = staticFlag && layer->isStatic(); + asset->mLayers.push_back(layer); + } + if (!IsValid()) { + parsingError = true; + return sharedAsset; + } + asset->setStatic(staticFlag); + } else { +#ifdef DEBUG_PARSER + vWarning << "Asset Attribute Skipped : " << key; +#endif + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return sharedAsset; + } + + if (asset->mAssetType == LOTAsset::Type::Image) { + if (embededResource) { + // embeder resource should start with "data:" + if (filename.compare(0, 5, "data:") == 0) { + asset->loadImageData(convertFromBase64(filename)); + } + } else { + asset->loadImagePath(mDirPath + relativePath + filename); + } + } + + return sharedAsset; +} + +void LottieParserImpl::parseLayers(LOTCompositionData *comp) { + comp->mRootLayer = std::make_shared(); + comp->mRootLayer->mLayerType = LayerType::Precomp; + comp->mRootLayer->mName = std::string("__"); + bool staticFlag = true; + if (PeekType() != kArrayType) { + parsingError = true; + return; + } + EnterArray(); + while (NextArrayValue()) { + if (parsingError) { + return; + } + std::shared_ptr layer = parseLayer(true); + staticFlag = staticFlag && layer->isStatic(); + comp->mRootLayer->mChildren.push_back(layer); + } + if (!IsValid()) { + parsingError = true; + return; + } + comp->mRootLayer->setStatic(staticFlag); +} + +LottieColor LottieParserImpl::toColor(const char *str) +{ + LottieColor color; + int len = strlen(str); + + // some resource has empty color string + // return a default color for those cases. + if (!len) return color; + + if (len != 7 || str[0] != '#') { + parsingError = true; + return color; + } + + char tmp[3] = {'\0', '\0', '\0'}; + tmp[0] = str[1]; + tmp[1] = str[2]; + color.b = std::strtol(tmp, NULL, 16) / 255.0; + + tmp[0] = str[3]; + tmp[1] = str[4]; + color.g = std::strtol(tmp, NULL, 16) / 255.0; + + tmp[0] = str[5]; + tmp[1] = str[6]; + color.r = std::strtol(tmp, NULL, 16) / 255.0; + + return color; +} + +MatteType LottieParserImpl::getMatteType() { + if (PeekType() != kNumberType) { + parsingError = true; + return MatteType::None; + } + switch (GetInt()) { + case 1: + return MatteType::Alpha; + break; + case 2: + return MatteType::AlphaInv; + break; + case 3: + return MatteType::Luma; + break; + case 4: + return MatteType::LumaInv; + break; + default: + return MatteType::None; + break; + } +} + +LayerType LottieParserImpl::getLayerType() { + if (PeekType() != kNumberType) { + parsingError = true; + return LayerType::Null; + } + switch (GetInt()) { + case 0: + return LayerType::Precomp; + break; + case 1: + return LayerType::Solid; + break; + case 2: + return LayerType::Image; + break; + case 3: + return LayerType::Null; + break; + case 4: + return LayerType::Shape; + break; + case 5: + return LayerType::Text; + break; + default: + return LayerType::Null; + break; + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/layers/shape.json + * + */ +std::shared_ptr LottieParserImpl::parseLayer(bool record) { + std::shared_ptr sharedLayer = + std::make_shared(); + LOTLayerData *layer = sharedLayer.get(); + if (PeekType() != kObjectType) { + parsingError = true; + return sharedLayer; + } + curLayerRef = layer; + bool ddd = true; + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "ty")) { /* Type of layer*/ + layer->mLayerType = getLayerType(); + } else if (0 == strcmp(key, "nm")) { /*Layer name*/ + if (PeekType() != kStringType) { + parsingError = true; + return sharedLayer; + } + layer->mName = GetString(); + } else if (0 == strcmp(key, "ind")) { /*Layer index in AE. Used for + parenting and expressions.*/ + if (PeekType() != kNumberType) { + parsingError = true; + return sharedLayer; + } + layer->mId = GetInt(); + } else if (0 == strcmp(key, "ddd")) { /*3d layer */ + if (PeekType() != kNumberType) { + parsingError = true; + return sharedLayer; + } + ddd = GetInt(); + } else if (0 == + strcmp(key, + "parent")) { /*Layer Parent. Uses "ind" of parent.*/ + if (PeekType() != kNumberType) { + parsingError = true; + return sharedLayer; + } + layer->mParentId = GetInt(); + } else if (0 == strcmp(key, "refId")) { /*preComp Layer reference id*/ + if (PeekType() != kStringType) { + parsingError = true; + return sharedLayer; + } + layer->mPreCompRefId = std::string(GetString()); + layer->mHasGradient = true; + mLayersToUpdate.push_back(sharedLayer); + } else if (0 == strcmp(key, "sr")) { // "Layer Time Stretching" + if (PeekType() != kNumberType) { + parsingError = true; + return sharedLayer; + } + layer->mTimeStreatch = GetDouble(); + } else if (0 == strcmp(key, "tm")) { // time remapping + parseProperty(layer->mTimeRemap); + } else if (0 == strcmp(key, "ip")) { + if (PeekType() != kNumberType) { + parsingError = true; + return sharedLayer; + } + layer->mInFrame = round(GetDouble()); + } else if (0 == strcmp(key, "op")) { + if (PeekType() != kNumberType) { + parsingError = true; + return sharedLayer; + } + layer->mOutFrame = round(GetDouble()); + } else if (0 == strcmp(key, "st")) { + if (PeekType() != kNumberType) { + parsingError = true; + return sharedLayer; + } + layer->mStartFrame = GetDouble(); + } else if (0 == strcmp(key, "bm")) { + layer->mBlendMode = getBlendMode(); + } else if (0 == strcmp(key, "ks")) { + if (PeekType() != kObjectType) { + parsingError = true; + return sharedLayer; + } + EnterObject(); + layer->mTransform = parseTransformObject(ddd); + } else if (0 == strcmp(key, "shapes")) { + parseShapesAttr(layer); + } else if (0 == strcmp(key, "w")) { + layer->mLayerSize.setWidth(GetInt()); + } else if (0 == strcmp(key, "h")) { + layer->mLayerSize.setHeight(GetInt()); + } else if (0 == strcmp(key, "sw")) { + layer->mSolidLayer.mWidth = GetInt(); + } else if (0 == strcmp(key, "sh")) { + layer->mSolidLayer.mHeight = GetInt(); + } else if (0 == strcmp(key, "sc")) { + layer->mSolidLayer.mColor = toColor(GetString()); + } else if (0 == strcmp(key, "tt")) { + layer->mMatteType = getMatteType(); + } else if (0 == strcmp(key, "hasMask")) { + layer->mHasMask = GetBool(); + } else if (0 == strcmp(key, "masksProperties")) { + parseMaskProperty(layer); + } else if (0 == strcmp(key, "ao")) { + layer->mAutoOrient = GetInt(); + } else if (0 == strcmp(key, "hd")) { + layer->mHidden = GetBool(); + } else { +#ifdef DEBUG_PARSER + vWarning << "Layer Attribute Skipped : " << key; +#endif + Skip(key); + } + } + if (!IsValid() || layer->mTransform == nullptr) { + parsingError = true; + return sharedLayer; + } + + layer->mCompRef = compRef; + + if (layer->hidden()) { + // if layer is hidden, only data that is usefull is its + // transform matrix(when it is a parent of some other layer) + // so force it to be a Null Layer and release all resource. + layer->setStatic(layer->mTransform->isStatic()); + layer->mLayerType = LayerType::Null; + layer->mChildren = {}; + return sharedLayer; + } + + // update the static property of layer + bool staticFlag = true; + for (const auto &child : layer->mChildren) { + staticFlag &= child.get()->isStatic(); + } + + for (const auto &mask : layer->mMasks) { + staticFlag &= mask->isStatic(); + } + + layer->setStatic(staticFlag && layer->mTransform->isStatic()); + + if (record) { + mLayerInfoList.push_back( + LayerInfo(layer->mName, layer->mInFrame, layer->mOutFrame)); + } + return sharedLayer; +} + +void LottieParserImpl::parseMaskProperty(LOTLayerData *layer) { + if (PeekType() != kArrayType) { + parsingError = true; + return; + } + EnterArray(); + while (NextArrayValue()) { + if (parsingError) { + return; + } + layer->mMasks.push_back(parseMaskObject()); + } + if (!IsValid()) { + parsingError = true; + return; + } +} + +std::shared_ptr LottieParserImpl::parseMaskObject() +{ + std::shared_ptr sharedMask = std::make_shared(); + LOTMaskData * obj = sharedMask.get(); + + if (PeekType() != kObjectType) { + parsingError = true; + return sharedMask; + } + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "inv")) { + obj->mInv = GetBool(); + } else if (0 == strcmp(key, "mode")) { + const char *str = GetString(); + switch (str[0]) { + case 'n': + obj->mMode = LOTMaskData::Mode::None; + break; + case 'a': + obj->mMode = LOTMaskData::Mode::Add; + break; + case 's': + obj->mMode = LOTMaskData::Mode::Substarct; + break; + case 'i': + obj->mMode = LOTMaskData::Mode::Intersect; + break; + case 'f': + obj->mMode = LOTMaskData::Mode::Difference; + break; + default: + obj->mMode = LOTMaskData::Mode::None; + break; + } + } else if (0 == strcmp(key, "pt")) { + parseShapeProperty(obj->mShape); + } else if (0 == strcmp(key, "o")) { + parseProperty(obj->mOpacity); + } else { + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return sharedMask; + } + obj->mIsStatic = obj->mShape.isStatic() && obj->mOpacity.isStatic(); + return sharedMask; +} + +void LottieParserImpl::parseShapesAttr(LOTLayerData *layer) { + if (PeekType() != kArrayType) { + parsingError = true; + return; + } + EnterArray(); + while (NextArrayValue()) { + if (parsingError) { + return; + } + parseObject(layer); + } + if (!IsValid()) { + parsingError = true; + return; + } +} + +std::shared_ptr LottieParserImpl::parseObjectTypeAttr() { + if (PeekType() != kStringType) { + parsingError = true; + return nullptr; + } + const char *type = GetString(); + if (0 == strcmp(type, "gr")) { + return parseGroupObject(); + } else if (0 == strcmp(type, "rc")) { + return parseRectObject(); + } else if (0 == strcmp(type, "el")) { + return parseEllipseObject(); + } else if (0 == strcmp(type, "tr")) { + return parseTransformObject(); + } else if (0 == strcmp(type, "fl")) { + return parseFillObject(); + } else if (0 == strcmp(type, "st")) { + return parseStrokeObject(); + } else if (0 == strcmp(type, "gf")) { + curLayerRef->mHasGradient = true; + return parseGFillObject(); + } else if (0 == strcmp(type, "gs")) { + curLayerRef->mHasGradient = true; + return parseGStrokeObject(); + } else if (0 == strcmp(type, "sh")) { + return parseShapeObject(); + } else if (0 == strcmp(type, "sr")) { + return parsePolystarObject(); + } else if (0 == strcmp(type, "tm")) { + curLayerRef->mHasPathOperator = true; + return parseTrimObject(); + } else if (0 == strcmp(type, "rp")) { + curLayerRef->mHasRepeater = true; + return parseReapeaterObject(); + } else if (0 == strcmp(type, "mm")) { + vWarning << "Merge Path is not supported yet"; + return nullptr; + } else { +#ifdef DEBUG_PARSER + vDebug << "The Object Type not yet handled = " << type; +#endif + return nullptr; + } +} + +void LottieParserImpl::parseObject(LOTGroupData *parent) +{ + if (PeekType() != kObjectType) { + parsingError = true; + return; + } + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "ty")) { + auto child = parseObjectTypeAttr(); + if (child && !child->hidden()) parent->mChildren.push_back(child); + } else { + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + } +} + +std::shared_ptr LottieParserImpl::parseGroupObject() { + std::shared_ptr sharedGroup = + std::make_shared(); + + LOTShapeGroupData *group = sharedGroup.get(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + group->mName = GetString(); + } else if (0 == strcmp(key, "it")) { + if (PeekType() != kArrayType) { + parsingError = true; + return sharedGroup; + } + EnterArray(); + while (NextArrayValue()) { + if (parsingError) { + return sharedGroup; + } + if (PeekType() != kObjectType) { + parsingError = true; + return sharedGroup; + } + parseObject(group); + } + if (!IsValid()) { + parsingError = true; + return sharedGroup; + } + if (group->mChildren.back()->mType == LOTData::Type::Transform) { + group->mTransform = std::static_pointer_cast( + group->mChildren.back()); + group->mChildren.pop_back(); + } + } else { + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return sharedGroup; + } + bool staticFlag = true; + for (const auto &child : group->mChildren) { + staticFlag &= child.get()->isStatic(); + } + + group->setStatic(staticFlag && group->mTransform->isStatic()); + + return sharedGroup; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/rect.json + */ +std::shared_ptr LottieParserImpl::parseRectObject() +{ + std::shared_ptr sharedRect = std::make_shared(); + LOTRectData * obj = sharedRect.get(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->mName = GetString(); + } else if (0 == strcmp(key, "p")) { + parseProperty(obj->mPos); + } else if (0 == strcmp(key, "s")) { + parseProperty(obj->mSize); + } else if (0 == strcmp(key, "r")) { + parseProperty(obj->mRound); + } else if (0 == strcmp(key, "d")) { + obj->mDirection = GetInt(); + } else if (0 == strcmp(key, "hd")) { + obj->mHidden = GetBool(); + } else { + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return sharedRect; + } + obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic() && + obj->mRound.isStatic()); + return sharedRect; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/ellipse.json + */ +std::shared_ptr LottieParserImpl::parseEllipseObject() +{ + std::shared_ptr sharedEllipse = + std::make_shared(); + LOTEllipseData *obj = sharedEllipse.get(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->mName = GetString(); + } else if (0 == strcmp(key, "p")) { + parseProperty(obj->mPos); + } else if (0 == strcmp(key, "s")) { + parseProperty(obj->mSize); + } else if (0 == strcmp(key, "d")) { + obj->mDirection = GetInt(); + } else if (0 == strcmp(key, "hd")) { + obj->mHidden = GetBool(); + } else { + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return sharedEllipse; + } + obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic()); + return sharedEllipse; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/shape.json + */ +std::shared_ptr LottieParserImpl::parseShapeObject() +{ + std::shared_ptr sharedShape = + std::make_shared(); + LOTShapeData *obj = sharedShape.get(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->mName = GetString(); + } else if (0 == strcmp(key, "ks")) { + parseShapeProperty(obj->mShape); + } else if (0 == strcmp(key, "d")) { + obj->mDirection = GetInt(); + } else if (0 == strcmp(key, "hd")) { + obj->mHidden = GetBool(); + } else { +#ifdef DEBUG_PARSER + vDebug << "Shape property ignored :" << key; +#endif + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return sharedShape; + } + obj->setStatic(obj->mShape.isStatic()); + + return sharedShape; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/star.json + */ +std::shared_ptr LottieParserImpl::parsePolystarObject() +{ + std::shared_ptr sharedPolystar = + std::make_shared(); + LOTPolystarData *obj = sharedPolystar.get(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->mName = GetString(); + } else if (0 == strcmp(key, "p")) { + parseProperty(obj->mPos); + } else if (0 == strcmp(key, "pt")) { + parseProperty(obj->mPointCount); + } else if (0 == strcmp(key, "ir")) { + parseProperty(obj->mInnerRadius); + } else if (0 == strcmp(key, "is")) { + parseProperty(obj->mInnerRoundness); + } else if (0 == strcmp(key, "or")) { + parseProperty(obj->mOuterRadius); + } else if (0 == strcmp(key, "os")) { + parseProperty(obj->mOuterRoundness); + } else if (0 == strcmp(key, "r")) { + parseProperty(obj->mRotation); + } else if (0 == strcmp(key, "sy")) { + int starType = GetInt(); + if (starType == 1) obj->mType = LOTPolystarData::PolyType::Star; + if (starType == 2) obj->mType = LOTPolystarData::PolyType::Polygon; + } else if (0 == strcmp(key, "d")) { + obj->mDirection = GetInt(); + } else if (0 == strcmp(key, "hd")) { + obj->mHidden = GetBool(); + } else { +#ifdef DEBUG_PARSER + vDebug << "Polystar property ignored :" << key; +#endif + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return sharedPolystar; + } + obj->setStatic( + obj->mPos.isStatic() && obj->mPointCount.isStatic() && + obj->mInnerRadius.isStatic() && obj->mInnerRoundness.isStatic() && + obj->mOuterRadius.isStatic() && obj->mOuterRoundness.isStatic() && + obj->mRotation.isStatic()); + + return sharedPolystar; +} + +LOTTrimData::TrimType LottieParserImpl::getTrimType() { + if (PeekType() != kNumberType) { + parsingError = true; + return LOTTrimData::TrimType::Individually; + } + switch (GetInt()) { + case 1: + return LOTTrimData::TrimType::Simultaneously; + case 2: + return LOTTrimData::TrimType::Individually; + default: + parsingError = true; + break; + } + return LOTTrimData::TrimType::Individually; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/trim.json + */ +std::shared_ptr LottieParserImpl::parseTrimObject() +{ + std::shared_ptr sharedTrim = std::make_shared(); + LOTTrimData * obj = sharedTrim.get(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->mName = GetString(); + } else if (0 == strcmp(key, "s")) { + parseProperty(obj->mStart); + } else if (0 == strcmp(key, "e")) { + parseProperty(obj->mEnd); + } else if (0 == strcmp(key, "o")) { + parseProperty(obj->mOffset); + } else if (0 == strcmp(key, "m")) { + obj->mTrimType = getTrimType(); + } else if (0 == strcmp(key, "hd")) { + obj->mHidden = GetBool(); + } else { +#ifdef DEBUG_PARSER + vDebug << "Trim property ignored :" << key; +#endif + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return sharedTrim; + } + obj->setStatic(obj->mStart.isStatic() && obj->mEnd.isStatic() && + obj->mOffset.isStatic()); + return sharedTrim; +} + +void LottieParserImpl::getValue(LOTRepeaterTransform &obj) +{ + EnterObject(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "a")) { + parseProperty(obj.mAnchor); + } else if (0 == strcmp(key, "p")) { + parseProperty(obj.mPosition); + } else if (0 == strcmp(key, "r")) { + parseProperty(obj.mRotation); + } else if (0 == strcmp(key, "s")) { + parseProperty(obj.mScale); + } else if (0 == strcmp(key, "so")) { + parseProperty(obj.mStartOpacity); + } else if (0 == strcmp(key, "eo")) { + parseProperty(obj.mEndOpacity); + } else { + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + } +} + +std::shared_ptr LottieParserImpl::parseReapeaterObject() +{ + std::shared_ptr sharedRepeater = + std::make_shared(); + LOTRepeaterData *obj = sharedRepeater.get(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->mName = GetString(); + } else if (0 == strcmp(key, "c")) { + parseProperty(obj->mCopies); + float maxCopy = 0.0; + if (!obj->mCopies.isStatic()) { + for (auto &keyFrame : obj->mCopies.animation().mKeyFrames) { + if (maxCopy < keyFrame.mValue.mStartValue) + maxCopy = keyFrame.mValue.mStartValue; + if (maxCopy < keyFrame.mValue.mEndValue) + maxCopy = keyFrame.mValue.mEndValue; + } + } else { + maxCopy = obj->mCopies.value(); + } + obj->mMaxCopies = maxCopy; + } else if (0 == strcmp(key, "o")) { + parseProperty(obj->mOffset); + } else if (0 == strcmp(key, "tr")) { + getValue(obj->mTransform); + } else if (0 == strcmp(key, "hd")) { + obj->mHidden = GetBool(); + } else { +#ifdef DEBUG_PARSER + vDebug << "Repeater property ignored :" << key; +#endif + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return sharedRepeater; + } + obj->setStatic(obj->mCopies.isStatic() && obj->mOffset.isStatic() && + obj->mTransform.isStatic()); + + return sharedRepeater; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/transform.json + */ +std::shared_ptr LottieParserImpl::parseTransformObject( + bool ddd) +{ + std::shared_ptr sharedTransform = + std::make_shared(); + + auto obj = std::make_unique(); + if (ddd) obj->m3D = std::make_unique(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + sharedTransform->mName = GetString(); + } else if (0 == strcmp(key, "a")) { + parseProperty(obj->mAnchor); + } else if (0 == strcmp(key, "p")) { + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "k")) { + parsePropertyHelper(obj->mPosition); + } else if (0 == strcmp(key, "s")) { + obj->mSeparate = GetBool(); + } else if (obj->mSeparate && (0 == strcmp(key, "x"))) { + parseProperty(obj->mX); + } else if (obj->mSeparate && (0 == strcmp(key, "y"))) { + parseProperty(obj->mY); + } else { + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return sharedTransform; + } + } else if (0 == strcmp(key, "r")) { + parseProperty(obj->mRotation); + } else if (0 == strcmp(key, "s")) { + parseProperty(obj->mScale); + } else if (0 == strcmp(key, "o")) { + parseProperty(obj->mOpacity); + } else if (0 == strcmp(key, "hd")) { + sharedTransform->mHidden = GetBool(); + } else if (0 == strcmp(key, "rx")) { + parseProperty(obj->m3D->mRx); + } else if (0 == strcmp(key, "ry")) { + parseProperty(obj->m3D->mRy); + } else if (0 == strcmp(key, "rz")) { + parseProperty(obj->m3D->mRz); + } else { + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return sharedTransform; + } + obj->mStatic = obj->mAnchor.isStatic() && obj->mPosition.isStatic() && + obj->mRotation.isStatic() && obj->mScale.isStatic() && + obj->mX.isStatic() && obj->mY.isStatic() && + obj->mOpacity.isStatic(); + if (obj->m3D) { + obj->mStatic = obj->mStatic && obj->m3D->mRx.isStatic() && + obj->m3D->mRy.isStatic() && obj->m3D->mRz.isStatic(); + } + + sharedTransform->set(std::move(obj)); + + return sharedTransform; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/fill.json + */ +std::shared_ptr LottieParserImpl::parseFillObject() +{ + std::shared_ptr sharedFill = std::make_shared(); + LOTFillData * obj = sharedFill.get(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->mName = GetString(); + } else if (0 == strcmp(key, "c")) { + parseProperty(obj->mColor); + } else if (0 == strcmp(key, "o")) { + parseProperty(obj->mOpacity); + } else if (0 == strcmp(key, "fillEnabled")) { + obj->mEnabled = GetBool(); + } else if (0 == strcmp(key, "r")) { + obj->mFillRule = getFillRule(); + } else if (0 == strcmp(key, "hd")) { + obj->mHidden = GetBool(); + } else { +#ifdef DEBUG_PARSER + vWarning << "Fill property skipped = " << key; +#endif + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return sharedFill; + } + obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic()); + + return sharedFill; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineCap.json + */ +CapStyle LottieParserImpl::getLineCap() { + if (PeekType() != kNumberType) { + parsingError = true; + return CapStyle::Square; + } + switch (GetInt()) { + case 1: + return CapStyle::Flat; + break; + case 2: + return CapStyle::Round; + break; + default: + return CapStyle::Square; + break; + } +} + +FillRule LottieParserImpl::getFillRule() { + if (PeekType() != kNumberType) { + parsingError = true; + return FillRule::Winding; + } + switch (GetInt()) { + case 1: + return FillRule::Winding; + break; + case 2: + return FillRule::EvenOdd; + break; + default: + return FillRule::Winding; + break; + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineJoin.json + */ +JoinStyle LottieParserImpl::getLineJoin() { + if (PeekType() != kNumberType) { + parsingError = true; + return JoinStyle::Bevel; + } + switch (GetInt()) { + case 1: + return JoinStyle::Miter; + break; + case 2: + return JoinStyle::Round; + break; + default: + return JoinStyle::Bevel; + break; + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/stroke.json + */ +std::shared_ptr LottieParserImpl::parseStrokeObject() { + std::shared_ptr sharedStroke = + std::make_shared(); + LOTStrokeData *obj = sharedStroke.get(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->mName = GetString(); + } else if (0 == strcmp(key, "c")) { + parseProperty(obj->mColor); + } else if (0 == strcmp(key, "o")) { + parseProperty(obj->mOpacity); + } else if (0 == strcmp(key, "w")) { + parseProperty(obj->mWidth); + } else if (0 == strcmp(key, "fillEnabled")) { + obj->mEnabled = GetBool(); + } else if (0 == strcmp(key, "lc")) { + obj->mCapStyle = getLineCap(); + } else if (0 == strcmp(key, "lj")) { + obj->mJoinStyle = getLineJoin(); + } else if (0 == strcmp(key, "ml")) { + if (PeekType() != kNumberType) { + parsingError = true; + return sharedStroke; + } + obj->mMeterLimit = GetDouble(); + } else if (0 == strcmp(key, "d")) { + parseDashProperty(obj->mDash); + } else if (0 == strcmp(key, "hd")) { + obj->mHidden = GetBool(); + } else { +#ifdef DEBUG_PARSER + vWarning << "Stroke property skipped = " << key; +#endif + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return sharedStroke; + } + obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic() && + obj->mWidth.isStatic() && obj->mDash.mStatic); + return sharedStroke; +} + +void LottieParserImpl::parseGradientProperty(LOTGradient *obj, const char *key) { + if (0 == strcmp(key, "t")) { + if (PeekType() != kNumberType) { + parsingError = true; + return; + } + obj->mGradientType = GetInt(); + } else if (0 == strcmp(key, "o")) { + parseProperty(obj->mOpacity); + } else if (0 == strcmp(key, "s")) { + parseProperty(obj->mStartPoint); + } else if (0 == strcmp(key, "e")) { + parseProperty(obj->mEndPoint); + } else if (0 == strcmp(key, "h")) { + parseProperty(obj->mHighlightLength); + } else if (0 == strcmp(key, "a")) { + parseProperty(obj->mHighlightAngle); + } else if (0 == strcmp(key, "g")) { + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "k")) { + parseProperty(obj->mGradient); + } else if (0 == strcmp(key, "p")) { + obj->mColorPoints = GetInt(); + } else { + Skip(nullptr); + } + } + if (!IsValid()) { + parsingError = true; + return; + } + } else if (0 == strcmp(key, "hd")) { + obj->mHidden = GetBool(); + } else { +#ifdef DEBUG_PARSER + vWarning << "Gradient property skipped = " << key; +#endif + Skip(key); + } + if (!IsValid()) { + parsingError = true; + return; + } + obj->setStatic( + obj->mOpacity.isStatic() && obj->mStartPoint.isStatic() && + obj->mEndPoint.isStatic() && obj->mHighlightAngle.isStatic() && + obj->mHighlightLength.isStatic() && obj->mGradient.isStatic()); +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gfill.json + */ +std::shared_ptr LottieParserImpl::parseGFillObject() +{ + std::shared_ptr sharedGFill = + std::make_shared(); + LOTGFillData *obj = sharedGFill.get(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->mName = GetString(); + } else if (0 == strcmp(key, "r")) { + obj->mFillRule = getFillRule(); + } else { + parseGradientProperty(obj, key); + } + } + if (!IsValid()) { + parsingError = true; + } + return sharedGFill; +} + +void LottieParserImpl::parseDashProperty(LOTDashProperty &dash) { + dash.mDashCount = 0; + dash.mStatic = true; + if (PeekType() != kArrayType) { + parsingError = true; + return; + } + EnterArray(); + while (NextArrayValue()) { + if (parsingError) { + return; + } + if (PeekType() != kObjectType) { + parsingError = true; + return; + } + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "v")) { + parseProperty(dash.mDashArray[dash.mDashCount++]); + } else { + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return; + } + } + if (!IsValid()) { + parsingError = true; + return; + } + + // update the staic proprty + for (int i = 0; i < dash.mDashCount; i++) { + if (!dash.mDashArray[i].isStatic()) { + dash.mStatic = false; + break; + } + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gstroke.json + */ +std::shared_ptr LottieParserImpl::parseGStrokeObject() { + std::shared_ptr sharedGStroke = + std::make_shared(); + LOTGStrokeData *obj = sharedGStroke.get(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->mName = GetString(); + } else if (0 == strcmp(key, "w")) { + parseProperty(obj->mWidth); + } else if (0 == strcmp(key, "lc")) { + obj->mCapStyle = getLineCap(); + } else if (0 == strcmp(key, "lj")) { + obj->mJoinStyle = getLineJoin(); + } else if (0 == strcmp(key, "ml")) { + if (PeekType() != kNumberType) { + parsingError = true; + return sharedGStroke; + } + obj->mMeterLimit = GetDouble(); + } else if (0 == strcmp(key, "d")) { + parseDashProperty(obj->mDash); + } else { + parseGradientProperty(obj, key); + } + } + if (!IsValid()) { + parsingError = true; + return sharedGStroke; + } + + obj->setStatic(obj->isStatic() && obj->mWidth.isStatic() && + obj->mDash.mStatic); + return sharedGStroke; +} + +void LottieParserImpl::getValue(std::vector &v) { + if (PeekType() != kArrayType) { + parsingError = true; + return; + } + EnterArray(); + while (NextArrayValue()) { + if (parsingError) { + return; + } + if (PeekType() != kArrayType) { + parsingError = true; + return; + } + EnterArray(); + VPointF pt; + getValue(pt); + v.push_back(pt); + } + if (!IsValid()) { + parsingError = true; + return; + } +} + +void LottieParserImpl::getValue(VPointF &pt) +{ + float val[4]; + int i = 0; + + if (PeekType() == kArrayType) EnterArray(); + + while (NextArrayValue()) { + if (parsingError) { + return; + } + val[i++] = GetDouble(); + } + if (!IsValid()) { + parsingError = true; + return; + } + + pt.setX(val[0]); + pt.setY(val[1]); +} + +void LottieParserImpl::getValue(float &val) +{ + if (PeekType() == kArrayType) { + EnterArray(); + if (NextArrayValue()) val = GetDouble(); + // discard rest + while (NextArrayValue()) { + if (parsingError) { + return; + } + GetDouble(); + } + if (!IsValid()) { + parsingError = true; + return; + } + } else if (PeekType() == kNumberType) { + val = GetDouble(); + } else { + parsingError = true; + } +} + +void LottieParserImpl::getValue(LottieColor &color) +{ + float val[4]; + int i = 0; + if (PeekType() == kArrayType) EnterArray(); + + while (NextArrayValue()) { + if (parsingError) { + return; + } + val[i++] = GetDouble(); + } + if (!IsValid()) { + parsingError = true; + return; + } + color.r = val[2]; + color.g = val[1]; + color.b = val[0]; +} + +void LottieParserImpl::getValue(LottieGradient &grad) +{ + if (PeekType() == kArrayType) EnterArray(); + + while (NextArrayValue()) { + if (parsingError) { + return; + } + grad.mGradient.push_back(GetDouble()); + } + if (!IsValid()) { + parsingError = true; + return; + } +} + +void LottieParserImpl::getValue(int &val) +{ + if (PeekType() == kArrayType) { + EnterArray(); + while (NextArrayValue()) { + if (parsingError) { + return; + } + val = GetInt(); + } + if (!IsValid()) { + parsingError = true; + return; + } + } else if (PeekType() == kNumberType) { + val = GetInt(); + } else { + parsingError = true; + } +} + +void LottieParserImpl::getValue(LottieShapeData &obj) +{ + std::vector inPoint; /* "i" */ + std::vector outPoint; /* "o" */ + std::vector vertices; /* "v" */ + std::vector points; + bool closed = false; + + /* + * The shape object could be wrapped by a array + * if its part of the keyframe object + */ + bool arrayWrapper = (PeekType() == kArrayType); + if (arrayWrapper) EnterArray(); + + if (PeekType() != kObjectType) { + parsingError = true; + return; + } + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "i")) { + getValue(inPoint); + } else if (0 == strcmp(key, "o")) { + getValue(outPoint); + } else if (0 == strcmp(key, "v")) { + getValue(vertices); + } else if (0 == strcmp(key, "c")) { + closed = GetBool(); + } else { + parsingError = true; + Skip(nullptr); + } + } + if (!IsValid()) { + parsingError = true; + return; + } + // exit properly from the array + if (arrayWrapper) NextArrayValue(); + + // shape data could be empty. + if (inPoint.empty() || outPoint.empty() || vertices.empty()) return; + + /* + * Convert the AE shape format to + * list of bazier curves + * The final structure will be Move +size*Cubic + Cubic (if the path is + * closed one) + */ + if (inPoint.size() != outPoint.size() || + inPoint.size() != vertices.size()) { + vCritical << "The Shape data are corrupted"; + points = std::vector(); + } else { + int size = vertices.size(); + points.reserve(3 * size + 4); + points.push_back(vertices[0]); + for (int i = 1; i < size; i++) { + points.push_back(vertices[i - 1] + + outPoint[i - 1]); // CP1 = start + outTangent + points.push_back(vertices[i] + + inPoint[i]); // CP2 = end + inTangent + points.push_back(vertices[i]); // end point + } + + if (closed) { + points.push_back(vertices[size - 1] + + outPoint[size - 1]); // CP1 = start + outTangent + points.push_back(vertices[0] + + inPoint[0]); // CP2 = end + inTangent + points.push_back(vertices[0]); // end point + } + } + obj.mPoints = std::move(points); + obj.mClosed = closed; +} + +VPointF LottieParserImpl::parseInperpolatorPoint() +{ + VPointF cp; + if (PeekType() != kObjectType) { + parsingError = true; + return cp; + } + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "x")) { + getValue(cp.rx()); + } + if (0 == strcmp(key, "y")) { + getValue(cp.ry()); + } + } + if (!IsValid()) { + parsingError = true; + } + return cp; +} + +template +bool LottieParserImpl::parseKeyFrameValue(const char *, LOTKeyFrameValue &) +{ + return false; +} + +template <> +bool LottieParserImpl::parseKeyFrameValue(const char * key, + LOTKeyFrameValue &value) +{ + if (0 == strcmp(key, "ti")) { + value.mPathKeyFrame = true; + getValue(value.mInTangent); + } else if (0 == strcmp(key, "to")) { + value.mPathKeyFrame = true; + getValue(value.mOutTangent); + } else { + return false; + } + return true; +} + +std::shared_ptr LottieParserImpl::interpolator( + VPointF inTangent, VPointF outTangent, std::string key) +{ + if (key.empty()) { + std::array temp; + snprintf(temp.data(), temp.size(), "%.2f_%.2f_%.2f_%.2f", inTangent.x(), + inTangent.y(), outTangent.x(), outTangent.y()); + key = temp.data(); + } + + auto search = mInterpolatorCache.find(key); + if (search != mInterpolatorCache.end()) { + return search->second; + } else { + auto obj = std::make_shared( + VInterpolator(outTangent, inTangent)); + mInterpolatorCache[std::move(key)] = obj; + return obj; + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/multiDimensionalKeyframed.json + */ +template +void LottieParserImpl::parseKeyFrame(LOTAnimInfo &obj) { + struct ParsedField { + std::string interpolatorKey; + bool interpolator{false}; + bool value{false}; + bool hold{false}; + bool noEndValue{true}; + }; + + EnterObject(); + ParsedField parsed; + LOTKeyFrame keyframe; + VPointF inTangent; + VPointF outTangent; + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "i")) { + parsed.interpolator = true; + inTangent = parseInperpolatorPoint(); + } else if (0 == strcmp(key, "o")) { + outTangent = parseInperpolatorPoint(); + } else if (0 == strcmp(key, "t")) { + keyframe.mStartFrame = GetDouble(); + } else if (0 == strcmp(key, "s")) { + parsed.value = true; + getValue(keyframe.mValue.mStartValue); + continue; + } else if (0 == strcmp(key, "e")) { + parsed.noEndValue = false; + getValue(keyframe.mValue.mEndValue); + continue; + } else if (0 == strcmp(key, "n")) { + if (PeekType() == kStringType) { + parsed.interpolatorKey = GetString(); + } else { + if (PeekType() != kArrayType) { + parsingError = true; + return; + } + EnterArray(); + while (NextArrayValue()) { + if (parsingError) { + return; + } + if (PeekType() != kStringType) { + parsingError = true; + return; + } + if (parsed.interpolatorKey.empty()) { + parsed.interpolatorKey = GetString(); + } else { + // skip rest of the string + GetString(); + } + } + if (!IsValid()) { + parsingError = true; + return; + } + } + continue; + } else if (parseKeyFrameValue(key, keyframe.mValue)) { + continue; + } else if (0 == strcmp(key, "h")) { + parsed.hold = GetInt(); + continue; + } else { +#ifdef DEBUG_PARSER + vDebug << "key frame property skipped = " << key; +#endif + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + return; + } + + if (!obj.mKeyFrames.empty()) { + // update the endFrame value of current keyframe + obj.mKeyFrames.back().mEndFrame = keyframe.mStartFrame; + // if no end value provided, copy start value to previous frame + if (parsed.value && parsed.noEndValue) { + obj.mKeyFrames.back().mValue.mEndValue = + keyframe.mValue.mStartValue; + } + } + + if (parsed.hold) { + keyframe.mValue.mEndValue = keyframe.mValue.mStartValue; + keyframe.mEndFrame = keyframe.mStartFrame; + obj.mKeyFrames.push_back(keyframe); + } else if (parsed.interpolator) { + keyframe.mInterpolator = interpolator( + inTangent, outTangent, std::move(parsed.interpolatorKey)); + obj.mKeyFrames.push_back(keyframe); + } else { + // its the last frame discard. + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shapeKeyframed.json + */ + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shape.json + */ +void LottieParserImpl::parseShapeProperty(LOTAnimatable &obj) { + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "k")) { + if (PeekType() == kArrayType) { + EnterArray(); + while (NextArrayValue()) { + if (parsingError) { + return; + } + if (PeekType() != kObjectType) { + parsingError = true; + return; + } + parseKeyFrame(obj.animation()); + } + if (!IsValid()) { + parsingError = true; + return; + } + } else { + getValue(obj.value()); + } + } else { +#ifdef DEBUG_PARSER + vDebug << "shape property ignored = " << key; +#endif + Skip(nullptr); + } + } + if (!IsValid()) { + parsingError = true; + } +} + +template +void LottieParserImpl::parsePropertyHelper(LOTAnimatable &obj) { + if (PeekType() == kNumberType) { + /*single value property with no animation*/ + getValue(obj.value()); + } else { + if (PeekType() != kArrayType) { + parsingError = true; + return; + } + EnterArray(); + while (NextArrayValue()) { + if (parsingError) { + return; + } + /* property with keyframe info*/ + if (PeekType() == kObjectType) { + parseKeyFrame(obj.animation()); + } else { + /* Read before modifying. + * as there is no way of knowing if the + * value of the array is either array of numbers + * or array of object without entering the array + * thats why this hack is there + */ + if (PeekType() != kNumberType) { + parsingError = true; + return; + } + /*multi value property with no animation*/ + getValue(obj.value()); + /*break here as we already reached end of array*/ + break; + } + } + if (!IsValid()) { + parsingError = true; + return; + } + } +} + +/* + * https://github.com/airbnb/lottie-web/tree/master/docs/json/properties + */ +template +void LottieParserImpl::parseProperty(LOTAnimatable &obj) +{ + EnterObject(); + while (const char *key = NextObjectKey()) { + if (parsingError) { + return; + } + if (0 == strcmp(key, "k")) { + parsePropertyHelper(obj); + } else { + Skip(key); + } + } + if (!IsValid()) { + parsingError = true; + } +} + +#ifdef DEBUG_PRINT_TREE + +class LOTDataInspector { +public: + void visit(LOTCompositionData *obj, std::string level) + { + vDebug << " { " << level << "Composition:: a: " << !obj->isStatic() + << ", v: " << obj->mVersion << ", stFm: " << obj->startFrame() + << ", endFm: " << obj->endFrame() + << ", W: " << obj->size().width() + << ", H: " << obj->size().height() << "\n"; + level.append("\t"); + visit(obj->mRootLayer.get(), level); + level.erase(level.end() - 1, level.end()); + vDebug << " } " << level << "Composition End\n"; + } + void visit(LOTLayerData *obj, std::string level) + { + vDebug << level << "{ " << layerType(obj->mLayerType) + << ", name: " << obj->name() << ", id:" << obj->mId + << " Pid:" << obj->mParentId << ", a:" << !obj->isStatic() + << ", " << matteType(obj->mMatteType) + << ", mask:" << obj->hasMask() << ", inFm:" << obj->mInFrame + << ", outFm:" << obj->mOutFrame << ", stFm:" << obj->mStartFrame + << ", ts:" << obj->mTimeStreatch << ", ao:" << obj->autoOrient() + << ", ddd:" << (obj->mTransform ? obj->mTransform->ddd() : false) + << ", W:" << obj->layerSize().width() + << ", H:" << obj->layerSize().height(); + + if (obj->mLayerType == LayerType::Image) + vDebug << level << "\t{ " + << "ImageInfo:" + << " W :" << obj->mAsset->mWidth + << ", H :" << obj->mAsset->mHeight << " }" + << "\n"; + else { + vDebug << level; + } + visitChildren(static_cast(obj), level); + vDebug << level << "} " << layerType(obj->mLayerType).c_str() + << ", id: " << obj->mId << "\n"; + } + void visitChildren(LOTGroupData *obj, std::string level) + { + level.append("\t"); + for (const auto &child : obj->mChildren) visit(child.get(), level); + if (obj->mTransform) visit(obj->mTransform.get(), level); + } + + void visit(LOTData *obj, std::string level) + { + switch (obj->mType) { + case LOTData::Type::Repeater: { + auto r = static_cast(obj); + vDebug << level << "{ Repeater: name: " << obj->name() + << " , a:" << !obj->isStatic() + << ", copies:" << r->maxCopies() + << ", offset:" << r->offset(0); + visitChildren(r->mContent.get(), level); + vDebug << level << "} Repeater"; + break; + } + case LOTData::Type::ShapeGroup: { + vDebug << level << "{ ShapeGroup: name: " << obj->name() + << " , a:" << !obj->isStatic(); + visitChildren(static_cast(obj), level); + vDebug << level << "} ShapeGroup"; + break; + } + case LOTData::Type::Layer: { + visit(static_cast(obj), level); + break; + } + case LOTData::Type::Trim: { + vDebug << level << "{ Trim: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case LOTData::Type::Rect: { + vDebug << level << "{ Rect: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case LOTData::Type::Ellipse: { + vDebug << level << "{ Ellipse: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case LOTData::Type::Shape: { + vDebug << level << "{ Shape: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case LOTData::Type::Polystar: { + vDebug << level << "{ Polystar: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case LOTData::Type::Transform: { + vDebug << level << "{ Transform: name: " << obj->name() + << " , a: " << !obj->isStatic() << " }"; + break; + } + case LOTData::Type::Stroke: { + vDebug << level << "{ Stroke: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case LOTData::Type::GStroke: { + vDebug << level << "{ GStroke: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case LOTData::Type::Fill: { + vDebug << level << "{ Fill: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case LOTData::Type::GFill: { + auto f = static_cast(obj); + vDebug << level << "{ GFill: name: " << obj->name() + << " , a:" << !f->isStatic() << ", ty:" << f->mGradientType + << ", s:" << f->mStartPoint.value(0) + << ", e:" << f->mEndPoint.value(0) << " }"; + break; + } + default: + break; + } + } + + std::string matteType(MatteType type) + { + switch (type) { + case MatteType::None: + return "Matte::None"; + break; + case MatteType::Alpha: + return "Matte::Alpha"; + break; + case MatteType::AlphaInv: + return "Matte::AlphaInv"; + break; + case MatteType::Luma: + return "Matte::Luma"; + break; + case MatteType::LumaInv: + return "Matte::LumaInv"; + break; + default: + return "Matte::Unknown"; + break; + } + } + std::string layerType(LayerType type) + { + switch (type) { + case LayerType::Precomp: + return "Layer::Precomp"; + break; + case LayerType::Null: + return "Layer::Null"; + break; + case LayerType::Shape: + return "Layer::Shape"; + break; + case LayerType::Solid: + return "Layer::Solid"; + break; + case LayerType::Image: + return "Layer::Image"; + break; + case LayerType::Text: + return "Layer::Text"; + break; + default: + return "Layer::Unknown"; + break; + } + } +}; + +#endif + +LottieParser::~LottieParser() +{ + delete d; +} + +LottieParser::LottieParser(char *str, const char *dir_path) + : d(new LottieParserImpl(str, dir_path)) +{ + d->parseComposition(); + if (d->hasParsingError()) { + parsingError = true; + } +} + +std::shared_ptr LottieParser::model() +{ + std::shared_ptr model = std::make_shared(); + model->mRoot = d->composition(); + model->mRoot->processRepeaterObjects(); + +#ifdef DEBUG_PRINT_TREE + LOTDataInspector inspector; + inspector.visit(model->mRoot.get(), ""); +#endif + + return model; +} + +bool LottieParser::hasParsingError() { + return parsingError; +} + +RAPIDJSON_DIAG_POP diff --git a/TMessagesProj/jni/rlottie/src/lottie/lottieparser.h b/TMessagesProj/jni/rlottie/src/lottie/lottieparser.h new file mode 100755 index 000000000..1c2bf9676 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/lottieparser.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LOTTIEPARSER_H +#define LOTTIEPARSER_H + +#include "lottiemodel.h" + +class LottieParserImpl; +class LottieParser { +public: + ~LottieParser(); + LottieParser(char* str, const char *dir_path); + std::shared_ptr model(); + bool hasParsingError(); +private: + LottieParserImpl *d; + bool parsingError = false; +}; + +#endif // LOTTIEPARSER_H diff --git a/TMessagesProj/jni/rlottie/src/lottie/lottieproxymodel.cpp b/TMessagesProj/jni/rlottie/src/lottie/lottieproxymodel.cpp new file mode 100755 index 000000000..e69de29bb diff --git a/TMessagesProj/jni/rlottie/src/lottie/lottieproxymodel.h b/TMessagesProj/jni/rlottie/src/lottie/lottieproxymodel.h new file mode 100755 index 000000000..e7d467243 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/lottieproxymodel.h @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LOTTIEPROXYMODEL_H +#define LOTTIEPROXYMODEL_H + +#include +#include +#include +#include "lottiemodel.h" +#include "rlottie.h" + +// Naive way to implement std::variant +// refactor it when we move to c++17 +// users should make sure proper combination +// of id and value are passed while creating the object. +class LOTVariant +{ +public: + using ValueFunc = std::function; + using ColorFunc = std::function; + using PointFunc = std::function; + using SizeFunc = std::function; + + LOTVariant(rlottie::Property prop, const ValueFunc &v):mPropery(prop), mTag(Value) + { + construct(impl.valueFunc, v); + } + + LOTVariant(rlottie::Property prop, ValueFunc &&v):mPropery(prop), mTag(Value) + { + moveConstruct(impl.valueFunc, std::move(v)); + } + + LOTVariant(rlottie::Property prop, const ColorFunc &v):mPropery(prop), mTag(Color) + { + construct(impl.colorFunc, v); + } + + LOTVariant(rlottie::Property prop, ColorFunc &&v):mPropery(prop), mTag(Color) + { + moveConstruct(impl.colorFunc, std::move(v)); + } + + LOTVariant(rlottie::Property prop, const PointFunc &v):mPropery(prop), mTag(Point) + { + construct(impl.pointFunc, v); + } + + LOTVariant(rlottie::Property prop, PointFunc &&v):mPropery(prop), mTag(Point) + { + moveConstruct(impl.pointFunc, std::move(v)); + } + + LOTVariant(rlottie::Property prop, const SizeFunc &v):mPropery(prop), mTag(Size) + { + construct(impl.sizeFunc, v); + } + + LOTVariant(rlottie::Property prop, SizeFunc &&v):mPropery(prop), mTag(Size) + { + moveConstruct(impl.sizeFunc, std::move(v)); + } + + rlottie::Property property() const { return mPropery; } + + const ColorFunc& color() const + { + assert(mTag == Color); + return impl.colorFunc; + } + + const ValueFunc& value() const + { + assert(mTag == Value); + return impl.valueFunc; + } + + const PointFunc& point() const + { + assert(mTag == Point); + return impl.pointFunc; + } + + const SizeFunc& size() const + { + assert(mTag == Size); + return impl.sizeFunc; + } + + LOTVariant() = default; + ~LOTVariant() noexcept {Destroy();} + LOTVariant(const LOTVariant& other) { Copy(other);} + LOTVariant(LOTVariant&& other) noexcept { Move(std::move(other));} + LOTVariant& operator=(LOTVariant&& other) { Destroy(); Move(std::move(other)); return *this;} + LOTVariant& operator=(const LOTVariant& other) { Destroy(); Copy(other); return *this;} +private: + template + void construct(T& member, const T& val) + { + new (&member) T(val); + } + + template + void moveConstruct(T& member, T&& val) + { + new (&member) T(std::move(val)); + } + + void Move(LOTVariant&& other) + { + switch (other.mTag) { + case Type::Value: + moveConstruct(impl.valueFunc, std::move(other.impl.valueFunc)); + break; + case Type::Color: + moveConstruct(impl.colorFunc, std::move(other.impl.colorFunc)); + break; + case Type::Point: + moveConstruct(impl.pointFunc, std::move(other.impl.pointFunc)); + break; + case Type::Size: + moveConstruct(impl.sizeFunc, std::move(other.impl.sizeFunc)); + break; + default: + break; + } + mTag = other.mTag; + mPropery = other.mPropery; + other.mTag = MonoState; + } + + void Copy(const LOTVariant& other) + { + switch (other.mTag) { + case Type::Value: + construct(impl.valueFunc, other.impl.valueFunc); + break; + case Type::Color: + construct(impl.colorFunc, other.impl.colorFunc); + break; + case Type::Point: + construct(impl.pointFunc, other.impl.pointFunc); + break; + case Type::Size: + construct(impl.sizeFunc, other.impl.sizeFunc); + break; + default: + break; + } + mTag = other.mTag; + mPropery = other.mPropery; + } + + void Destroy() + { + switch(mTag) { + case MonoState: { + break; + } + case Value: { + impl.valueFunc.~ValueFunc(); + break; + } + case Color: { + impl.colorFunc.~ColorFunc(); + break; + } + case Point: { + impl.pointFunc.~PointFunc(); + break; + } + case Size: { + impl.sizeFunc.~SizeFunc(); + break; + } + } + } + + enum Type {MonoState, Value, Color, Point , Size}; + rlottie::Property mPropery; + Type mTag{MonoState}; + union details{ + ColorFunc colorFunc; + ValueFunc valueFunc; + PointFunc pointFunc; + SizeFunc sizeFunc; + details(){} + ~details(){} + }impl; +}; + +class LOTFilter +{ +public: + void addValue(LOTVariant &value) + { + uint index = static_cast(value.property()); + if (mBitset.test(index)) { + std::replace_if(mFilters.begin(), + mFilters.end(), + [&value](const LOTVariant &e) {return e.property() == value.property();}, + value); + } else { + mBitset.set(index); + mFilters.push_back(value); + } + } + + void removeValue(LOTVariant &value) + { + uint index = static_cast(value.property()); + if (mBitset.test(index)) { + mBitset.reset(index); + mFilters.erase(std::remove_if(mFilters.begin(), + mFilters.end(), + [&value](const LOTVariant &e) {return e.property() == value.property();}), + mFilters.end()); + } + } + bool hasFilter(rlottie::Property prop) const + { + return mBitset.test(static_cast(prop)); + } + LottieColor color(rlottie::Property prop, int frame) const + { + rlottie::FrameInfo info(frame); + rlottie::Color col = data(prop).color()(info); + return LottieColor(col.r(), col.g(), col.b()); + } + float opacity(rlottie::Property prop, int frame) const + { + rlottie::FrameInfo info(frame); + float val = data(prop).value()(info); + return val/100; + } + float value(rlottie::Property prop, int frame) const + { + rlottie::FrameInfo info(frame); + return data(prop).value()(info); + } +private: + const LOTVariant& data(rlottie::Property prop) const + { + auto result = std::find_if(mFilters.begin(), + mFilters.end(), + [prop](const LOTVariant &e){return e.property() == prop;}); + return *result; + } + std::bitset<32> mBitset{0}; + std::vector mFilters; +}; + +template +class LOTProxyModel +{ +public: + LOTProxyModel(T *model): _modelData(model) {} + LOTFilter& filter() {return mFilter;} + const std::string & name() const {return _modelData->name();} + LottieColor color(int frame) const + { + if (mFilter.hasFilter(rlottie::Property::Color)) { + return mFilter.color(rlottie::Property::Color, frame); + } + return _modelData->color(frame); + } + float opacity(int frame) const + { + if (mFilter.hasFilter(rlottie::Property::StrokeOpacity)) { + return mFilter.opacity(rlottie::Property::StrokeOpacity, frame); + } + return _modelData->opacity(frame); + } + float strokeWidth(int frame) const + { + if (mFilter.hasFilter(rlottie::Property::StrokeWidth)) { + return mFilter.value(rlottie::Property::StrokeWidth, frame); + } + return _modelData->strokeWidth(frame); + } + float meterLimit() const {return _modelData->meterLimit();} + CapStyle capStyle() const {return _modelData->capStyle();} + JoinStyle joinStyle() const {return _modelData->joinStyle();} + bool hasDashInfo() const { return _modelData->hasDashInfo();} + int getDashInfo(int frameNo, float *array) const {return _modelData->getDashInfo(frameNo, array);} + +private: + T *_modelData; + LOTFilter mFilter; +}; + +template <> +class LOTProxyModel +{ +public: + LOTProxyModel(LOTFillData *model): _modelData(model) {} + LOTFilter& filter() {return mFilter;} + const std::string & name() const {return _modelData->name();} + LottieColor color(int frame) const + { + if (mFilter.hasFilter(rlottie::Property::Color)) { + return mFilter.color(rlottie::Property::Color, frame); + } + return _modelData->color(frame); + } + float opacity(int frame) const + { + if (mFilter.hasFilter(rlottie::Property::FillOpacity)) { + return mFilter.opacity(rlottie::Property::FillOpacity, frame); + } + return _modelData->opacity(frame); + } + FillRule fillRule() const {return _modelData->fillRule();} +private: + LOTFillData *_modelData; + LOTFilter mFilter; +}; + +#endif // LOTTIEITEM_H diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/allocators.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/allocators.h new file mode 100755 index 000000000..cc67c8971 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/allocators.h @@ -0,0 +1,284 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ALLOCATORS_H_ +#define RAPIDJSON_ALLOCATORS_H_ + +#include "rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Allocator + +/*! \class rapidjson::Allocator + \brief Concept for allocating, resizing and freeing memory block. + + Note that Malloc() and Realloc() are non-static but Free() is static. + + So if an allocator need to support Free(), it needs to put its pointer in + the header of memory block. + +\code +concept Allocator { + static const bool kNeedFree; //!< Whether this allocator needs to call Free(). + + // Allocate a memory block. + // \param size of the memory block in bytes. + // \returns pointer to the memory block. + void* Malloc(size_t size); + + // Resize a memory block. + // \param originalPtr The pointer to current memory block. Null pointer is permitted. + // \param originalSize The current size in bytes. (Design issue: since some allocator may not book-keep this, explicitly pass to it can save memory.) + // \param newSize the new size in bytes. + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize); + + // Free a memory block. + // \param pointer to the memory block. Null pointer is permitted. + static void Free(void *ptr); +}; +\endcode +*/ + + +/*! \def RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY + \ingroup RAPIDJSON_CONFIG + \brief User-defined kDefaultChunkCapacity definition. + + User can define this as any \c size that is a power of 2. +*/ + +#ifndef RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY +#define RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY (64 * 1024) +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// CrtAllocator + +//! C-runtime library allocator. +/*! This class is just wrapper for standard C library memory routines. + \note implements Allocator concept +*/ +class CrtAllocator { +public: + static const bool kNeedFree = true; + void* Malloc(size_t size) { + if (size) // behavior of malloc(0) is implementation defined. + return std::malloc(size); + else + return NULL; // standardize to returning NULL. + } + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + (void)originalSize; + if (newSize == 0) { + std::free(originalPtr); + return NULL; + } + return std::realloc(originalPtr, newSize); + } + static void Free(void *ptr) { std::free(ptr); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// MemoryPoolAllocator + +//! Default memory allocator used by the parser and DOM. +/*! This allocator allocate memory blocks from pre-allocated memory chunks. + + It does not free memory blocks. And Realloc() only allocate new memory. + + The memory chunks are allocated by BaseAllocator, which is CrtAllocator by default. + + User may also supply a buffer as the first chunk. + + If the user-buffer is full then additional chunks are allocated by BaseAllocator. + + The user-buffer is not deallocated by this allocator. + + \tparam BaseAllocator the allocator type for allocating memory chunks. Default is CrtAllocator. + \note implements Allocator concept +*/ +template +class MemoryPoolAllocator { +public: + static const bool kNeedFree = false; //!< Tell users that no need to call Free() with this allocator. (concept Allocator) + + //! Constructor with chunkSize. + /*! \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(0), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + } + + //! Constructor with user-supplied buffer. + /*! The user buffer will be used firstly. When it is full, memory pool allocates new chunk with chunk size. + + The user buffer will not be deallocated when this allocator is destructed. + + \param buffer User supplied buffer. + \param size Size of the buffer in bytes. It must at least larger than sizeof(ChunkHeader). + \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(void *buffer, size_t size, size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(buffer), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + RAPIDJSON_ASSERT(buffer != 0); + RAPIDJSON_ASSERT(size > sizeof(ChunkHeader)); + chunkHead_ = reinterpret_cast(buffer); + chunkHead_->capacity = size - sizeof(ChunkHeader); + chunkHead_->size = 0; + chunkHead_->next = 0; + } + + //! Destructor. + /*! This deallocates all memory chunks, excluding the user-supplied buffer. + */ + ~MemoryPoolAllocator() { + Clear(); + RAPIDJSON_DELETE(ownBaseAllocator_); + } + + //! Deallocates all memory chunks, excluding the user-supplied buffer. + void Clear() { + while (chunkHead_ && chunkHead_ != userBuffer_) { + ChunkHeader* next = chunkHead_->next; + baseAllocator_->Free(chunkHead_); + chunkHead_ = next; + } + if (chunkHead_ && chunkHead_ == userBuffer_) + chunkHead_->size = 0; // Clear user buffer + } + + //! Computes the total capacity of allocated memory chunks. + /*! \return total capacity in bytes. + */ + size_t Capacity() const { + size_t capacity = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + capacity += c->capacity; + return capacity; + } + + //! Computes the memory blocks allocated. + /*! \return total used bytes. + */ + size_t Size() const { + size_t size = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + size += c->size; + return size; + } + + //! Allocates a memory block. (concept Allocator) + void* Malloc(size_t size) { + if (!size) + return NULL; + + size = RAPIDJSON_ALIGN(size); + if (chunkHead_ == 0 || chunkHead_->size + size > chunkHead_->capacity) + if (!AddChunk(chunk_capacity_ > size ? chunk_capacity_ : size)) + return NULL; + + void *buffer = reinterpret_cast(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size; + chunkHead_->size += size; + return buffer; + } + + //! Resizes a memory block (concept Allocator) + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + if (originalPtr == 0) + return Malloc(newSize); + + if (newSize == 0) + return NULL; + + originalSize = RAPIDJSON_ALIGN(originalSize); + newSize = RAPIDJSON_ALIGN(newSize); + + // Do not shrink if new size is smaller than original + if (originalSize >= newSize) + return originalPtr; + + // Simply expand it if it is the last allocation and there is sufficient space + if (originalPtr == reinterpret_cast(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size - originalSize) { + size_t increment = static_cast(newSize - originalSize); + if (chunkHead_->size + increment <= chunkHead_->capacity) { + chunkHead_->size += increment; + return originalPtr; + } + } + + // Realloc process: allocate and copy memory, do not free original buffer. + if (void* newBuffer = Malloc(newSize)) { + if (originalSize) + std::memcpy(newBuffer, originalPtr, originalSize); + return newBuffer; + } + else + return NULL; + } + + //! Frees a memory block (concept Allocator) + static void Free(void *ptr) { (void)ptr; } // Do nothing + +private: + //! Copy constructor is not permitted. + MemoryPoolAllocator(const MemoryPoolAllocator& rhs) /* = delete */; + //! Copy assignment operator is not permitted. + MemoryPoolAllocator& operator=(const MemoryPoolAllocator& rhs) /* = delete */; + + //! Creates a new chunk. + /*! \param capacity Capacity of the chunk in bytes. + \return true if success. + */ + bool AddChunk(size_t capacity) { + if (!baseAllocator_) + ownBaseAllocator_ = baseAllocator_ = RAPIDJSON_NEW(BaseAllocator)(); + if (ChunkHeader* chunk = reinterpret_cast(baseAllocator_->Malloc(RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + capacity))) { + chunk->capacity = capacity; + chunk->size = 0; + chunk->next = chunkHead_; + chunkHead_ = chunk; + return true; + } + else + return false; + } + + static const int kDefaultChunkCapacity = RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY; //!< Default chunk capacity. + + //! Chunk header for perpending to each chunk. + /*! Chunks are stored as a singly linked list. + */ + struct ChunkHeader { + size_t capacity; //!< Capacity of the chunk in bytes (excluding the header itself). + size_t size; //!< Current size of allocated memory in bytes. + ChunkHeader *next; //!< Next chunk in the linked list. + }; + + ChunkHeader *chunkHead_; //!< Head of the chunk linked-list. Only the head chunk serves allocation. + size_t chunk_capacity_; //!< The minimum capacity of chunk when they are allocated. + void *userBuffer_; //!< User supplied buffer. + BaseAllocator* baseAllocator_; //!< base allocator for allocating memory chunks. + BaseAllocator* ownBaseAllocator_; //!< base allocator created by this object. +}; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ENCODINGS_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/cursorstreamwrapper.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/cursorstreamwrapper.h new file mode 100755 index 000000000..52c11a7c0 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/cursorstreamwrapper.h @@ -0,0 +1,78 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_CURSORSTREAMWRAPPER_H_ +#define RAPIDJSON_CURSORSTREAMWRAPPER_H_ + +#include "stream.h" + +#if defined(__GNUC__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4702) // unreachable code +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + + +//! Cursor stream wrapper for counting line and column number if error exists. +/*! + \tparam InputStream Any stream that implements Stream Concept +*/ +template > +class CursorStreamWrapper : public GenericStreamWrapper { +public: + typedef typename Encoding::Ch Ch; + + CursorStreamWrapper(InputStream& is): + GenericStreamWrapper(is), line_(1), col_(0) {} + + // counting line and column number + Ch Take() { + Ch ch = this->is_.Take(); + if(ch == '\n') { + line_ ++; + col_ = 0; + } else { + col_ ++; + } + return ch; + } + + //! Get the error line number, if error exists. + size_t GetLine() const { return line_; } + //! Get the error column number, if error exists. + size_t GetColumn() const { return col_; } + +private: + size_t line_; //!< Current Line + size_t col_; //!< Current Column +}; + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_POP +#endif + +#if defined(__GNUC__) +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_CURSORSTREAMWRAPPER_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/document.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/document.h new file mode 100755 index 000000000..d1b90eb0b --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/document.h @@ -0,0 +1,2652 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_DOCUMENT_H_ +#define RAPIDJSON_DOCUMENT_H_ + +/*! \file document.h */ + +#include "reader.h" +#include "internal/meta.h" +#include "internal/strfunc.h" +#include "memorystream.h" +#include "encodedstream.h" +#include // placement new +#include + +RAPIDJSON_DIAG_PUSH +#ifdef __clang__ +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(c++98-compat) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +RAPIDJSON_DIAG_OFF(4244) // conversion from kXxxFlags to 'uint16_t', possible loss of data +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_OFF(effc++) +#endif // __GNUC__ + +#ifndef RAPIDJSON_NOMEMBERITERATORCLASS +#include // std::random_access_iterator_tag +#endif + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +#include // std::move +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +// Forward declaration. +template +class GenericValue; + +template +class GenericDocument; + +//! Name-value pair in a JSON object value. +/*! + This class was internal to GenericValue. It used to be a inner struct. + But a compiler (IBM XL C/C++ for AIX) have reported to have problem with that so it moved as a namespace scope struct. + https://code.google.com/p/rapidjson/issues/detail?id=64 +*/ +template +struct GenericMember { + GenericValue name; //!< name of member (must be a string) + GenericValue value; //!< value of member. + + // swap() for std::sort() and other potential use in STL. + friend inline void swap(GenericMember& a, GenericMember& b) RAPIDJSON_NOEXCEPT { + a.name.Swap(b.name); + a.value.Swap(b.value); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericMemberIterator + +#ifndef RAPIDJSON_NOMEMBERITERATORCLASS + +//! (Constant) member iterator for a JSON object value +/*! + \tparam Const Is this a constant iterator? + \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) + \tparam Allocator Allocator type for allocating memory of object, array and string. + + This class implements a Random Access Iterator for GenericMember elements + of a GenericValue, see ISO/IEC 14882:2003(E) C++ standard, 24.1 [lib.iterator.requirements]. + + \note This iterator implementation is mainly intended to avoid implicit + conversions from iterator values to \c NULL, + e.g. from GenericValue::FindMember. + + \note Define \c RAPIDJSON_NOMEMBERITERATORCLASS to fall back to a + pointer-based implementation, if your platform doesn't provide + the C++ header. + + \see GenericMember, GenericValue::MemberIterator, GenericValue::ConstMemberIterator + */ +template +class GenericMemberIterator { + + friend class GenericValue; + template friend class GenericMemberIterator; + + typedef GenericMember PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + +public: + //! Iterator type itself + typedef GenericMemberIterator Iterator; + //! Constant iterator type + typedef GenericMemberIterator ConstIterator; + //! Non-constant iterator type + typedef GenericMemberIterator NonConstIterator; + + /** \name std::iterator_traits support */ + //@{ + typedef ValueType value_type; + typedef ValueType * pointer; + typedef ValueType & reference; + typedef std::ptrdiff_t difference_type; + typedef std::random_access_iterator_tag iterator_category; + //@} + + //! Pointer to (const) GenericMember + typedef pointer Pointer; + //! Reference to (const) GenericMember + typedef reference Reference; + //! Signed integer type (e.g. \c ptrdiff_t) + typedef difference_type DifferenceType; + + //! Default constructor (singular value) + /*! Creates an iterator pointing to no element. + \note All operations, except for comparisons, are undefined on such values. + */ + GenericMemberIterator() : ptr_() {} + + //! Iterator conversions to more const + /*! + \param it (Non-const) iterator to copy from + + Allows the creation of an iterator from another GenericMemberIterator + that is "less const". Especially, creating a non-constant iterator + from a constant iterator are disabled: + \li const -> non-const (not ok) + \li const -> const (ok) + \li non-const -> const (ok) + \li non-const -> non-const (ok) + + \note If the \c Const template parameter is already \c false, this + constructor effectively defines a regular copy-constructor. + Otherwise, the copy constructor is implicitly defined. + */ + GenericMemberIterator(const NonConstIterator & it) : ptr_(it.ptr_) {} + Iterator& operator=(const NonConstIterator & it) { ptr_ = it.ptr_; return *this; } + + //! @name stepping + //@{ + Iterator& operator++(){ ++ptr_; return *this; } + Iterator& operator--(){ --ptr_; return *this; } + Iterator operator++(int){ Iterator old(*this); ++ptr_; return old; } + Iterator operator--(int){ Iterator old(*this); --ptr_; return old; } + //@} + + //! @name increment/decrement + //@{ + Iterator operator+(DifferenceType n) const { return Iterator(ptr_+n); } + Iterator operator-(DifferenceType n) const { return Iterator(ptr_-n); } + + Iterator& operator+=(DifferenceType n) { ptr_+=n; return *this; } + Iterator& operator-=(DifferenceType n) { ptr_-=n; return *this; } + //@} + + //! @name relations + //@{ + bool operator==(ConstIterator that) const { return ptr_ == that.ptr_; } + bool operator!=(ConstIterator that) const { return ptr_ != that.ptr_; } + bool operator<=(ConstIterator that) const { return ptr_ <= that.ptr_; } + bool operator>=(ConstIterator that) const { return ptr_ >= that.ptr_; } + bool operator< (ConstIterator that) const { return ptr_ < that.ptr_; } + bool operator> (ConstIterator that) const { return ptr_ > that.ptr_; } + //@} + + //! @name dereference + //@{ + Reference operator*() const { return *ptr_; } + Pointer operator->() const { return ptr_; } + Reference operator[](DifferenceType n) const { return ptr_[n]; } + //@} + + //! Distance + DifferenceType operator-(ConstIterator that) const { return ptr_-that.ptr_; } + +private: + //! Internal constructor from plain pointer + explicit GenericMemberIterator(Pointer p) : ptr_(p) {} + + Pointer ptr_; //!< raw pointer +}; + +#else // RAPIDJSON_NOMEMBERITERATORCLASS + +// class-based member iterator implementation disabled, use plain pointers + +template +struct GenericMemberIterator; + +//! non-const GenericMemberIterator +template +struct GenericMemberIterator { + //! use plain pointer as iterator type + typedef GenericMember* Iterator; +}; +//! const GenericMemberIterator +template +struct GenericMemberIterator { + //! use plain const pointer as iterator type + typedef const GenericMember* Iterator; +}; + +#endif // RAPIDJSON_NOMEMBERITERATORCLASS + +/////////////////////////////////////////////////////////////////////////////// +// GenericStringRef + +//! Reference to a constant string (not taking a copy) +/*! + \tparam CharType character type of the string + + This helper class is used to automatically infer constant string + references for string literals, especially from \c const \b (!) + character arrays. + + The main use is for creating JSON string values without copying the + source string via an \ref Allocator. This requires that the referenced + string pointers have a sufficient lifetime, which exceeds the lifetime + of the associated GenericValue. + + \b Example + \code + Value v("foo"); // ok, no need to copy & calculate length + const char foo[] = "foo"; + v.SetString(foo); // ok + + const char* bar = foo; + // Value x(bar); // not ok, can't rely on bar's lifetime + Value x(StringRef(bar)); // lifetime explicitly guaranteed by user + Value y(StringRef(bar, 3)); // ok, explicitly pass length + \endcode + + \see StringRef, GenericValue::SetString +*/ +template +struct GenericStringRef { + typedef CharType Ch; //!< character type of the string + + //! Create string reference from \c const character array +#ifndef __clang__ // -Wdocumentation + /*! + This constructor implicitly creates a constant string reference from + a \c const character array. It has better performance than + \ref StringRef(const CharType*) by inferring the string \ref length + from the array length, and also supports strings containing null + characters. + + \tparam N length of the string, automatically inferred + + \param str Constant character array, lifetime assumed to be longer + than the use of the string in e.g. a GenericValue + + \post \ref s == str + + \note Constant complexity. + \note There is a hidden, private overload to disallow references to + non-const character arrays to be created via this constructor. + By this, e.g. function-scope arrays used to be filled via + \c snprintf are excluded from consideration. + In such cases, the referenced string should be \b copied to the + GenericValue instead. + */ +#endif + template + GenericStringRef(const CharType (&str)[N]) RAPIDJSON_NOEXCEPT + : s(str), length(N-1) {} + + //! Explicitly create string reference from \c const character pointer +#ifndef __clang__ // -Wdocumentation + /*! + This constructor can be used to \b explicitly create a reference to + a constant string pointer. + + \see StringRef(const CharType*) + + \param str Constant character pointer, lifetime assumed to be longer + than the use of the string in e.g. a GenericValue + + \post \ref s == str + + \note There is a hidden, private overload to disallow references to + non-const character arrays to be created via this constructor. + By this, e.g. function-scope arrays used to be filled via + \c snprintf are excluded from consideration. + In such cases, the referenced string should be \b copied to the + GenericValue instead. + */ +#endif + explicit GenericStringRef(const CharType* str) + : s(str), length(NotNullStrLen(str)) {} + + //! Create constant string reference from pointer and length +#ifndef __clang__ // -Wdocumentation + /*! \param str constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \param len length of the string, excluding the trailing NULL terminator + + \post \ref s == str && \ref length == len + \note Constant complexity. + */ +#endif + GenericStringRef(const CharType* str, SizeType len) + : s(RAPIDJSON_LIKELY(str) ? str : emptyString), length(len) { RAPIDJSON_ASSERT(str != 0 || len == 0u); } + + GenericStringRef(const GenericStringRef& rhs) : s(rhs.s), length(rhs.length) {} + + //! implicit conversion to plain CharType pointer + operator const Ch *() const { return s; } + + const Ch* const s; //!< plain CharType pointer + const SizeType length; //!< length of the string (excluding the trailing NULL terminator) + +private: + SizeType NotNullStrLen(const CharType* str) { + RAPIDJSON_ASSERT(str != 0); + return internal::StrLen(str); + } + + /// Empty string - used when passing in a NULL pointer + static const Ch emptyString[]; + + //! Disallow construction from non-const array + template + GenericStringRef(CharType (&str)[N]) /* = delete */; + //! Copy assignment operator not permitted - immutable type + GenericStringRef& operator=(const GenericStringRef& rhs) /* = delete */; +}; + +template +const CharType GenericStringRef::emptyString[] = { CharType() }; + +//! Mark a character pointer as constant string +/*! Mark a plain character pointer as a "string literal". This function + can be used to avoid copying a character string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + \tparam CharType Character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \return GenericStringRef string reference object + \relatesalso GenericStringRef + + \see GenericValue::GenericValue(StringRefType), GenericValue::operator=(StringRefType), GenericValue::SetString(StringRefType), GenericValue::PushBack(StringRefType, Allocator&), GenericValue::AddMember +*/ +template +inline GenericStringRef StringRef(const CharType* str) { + return GenericStringRef(str); +} + +//! Mark a character pointer as constant string +/*! Mark a plain character pointer as a "string literal". This function + can be used to avoid copying a character string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + + This version has better performance with supplied length, and also + supports string containing null characters. + + \tparam CharType character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \param length The length of source string. + \return GenericStringRef string reference object + \relatesalso GenericStringRef +*/ +template +inline GenericStringRef StringRef(const CharType* str, size_t length) { + return GenericStringRef(str, SizeType(length)); +} + +#if RAPIDJSON_HAS_STDSTRING +//! Mark a string object as constant string +/*! Mark a string object (e.g. \c std::string) as a "string literal". + This function can be used to avoid copying a string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + + \tparam CharType character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \return GenericStringRef string reference object + \relatesalso GenericStringRef + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. +*/ +template +inline GenericStringRef StringRef(const std::basic_string& str) { + return GenericStringRef(str.data(), SizeType(str.size())); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +// GenericValue type traits +namespace internal { + +template +struct IsGenericValueImpl : FalseType {}; + +// select candidates according to nested encoding and allocator types +template struct IsGenericValueImpl::Type, typename Void::Type> + : IsBaseOf, T>::Type {}; + +// helper to match arbitrary GenericValue instantiations, including derived classes +template struct IsGenericValue : IsGenericValueImpl::Type {}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// TypeHelper + +namespace internal { + +template +struct TypeHelper {}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsBool(); } + static bool Get(const ValueType& v) { return v.GetBool(); } + static ValueType& Set(ValueType& v, bool data) { return v.SetBool(data); } + static ValueType& Set(ValueType& v, bool data, typename ValueType::AllocatorType&) { return v.SetBool(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsInt(); } + static int Get(const ValueType& v) { return v.GetInt(); } + static ValueType& Set(ValueType& v, int data) { return v.SetInt(data); } + static ValueType& Set(ValueType& v, int data, typename ValueType::AllocatorType&) { return v.SetInt(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsUint(); } + static unsigned Get(const ValueType& v) { return v.GetUint(); } + static ValueType& Set(ValueType& v, unsigned data) { return v.SetUint(data); } + static ValueType& Set(ValueType& v, unsigned data, typename ValueType::AllocatorType&) { return v.SetUint(data); } +}; + +#ifdef _MSC_VER +RAPIDJSON_STATIC_ASSERT(sizeof(long) == sizeof(int)); +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsInt(); } + static long Get(const ValueType& v) { return v.GetInt(); } + static ValueType& Set(ValueType& v, long data) { return v.SetInt(data); } + static ValueType& Set(ValueType& v, long data, typename ValueType::AllocatorType&) { return v.SetInt(data); } +}; + +RAPIDJSON_STATIC_ASSERT(sizeof(unsigned long) == sizeof(unsigned)); +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsUint(); } + static unsigned long Get(const ValueType& v) { return v.GetUint(); } + static ValueType& Set(ValueType& v, unsigned long data) { return v.SetUint(data); } + static ValueType& Set(ValueType& v, unsigned long data, typename ValueType::AllocatorType&) { return v.SetUint(data); } +}; +#endif + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsInt64(); } + static int64_t Get(const ValueType& v) { return v.GetInt64(); } + static ValueType& Set(ValueType& v, int64_t data) { return v.SetInt64(data); } + static ValueType& Set(ValueType& v, int64_t data, typename ValueType::AllocatorType&) { return v.SetInt64(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsUint64(); } + static uint64_t Get(const ValueType& v) { return v.GetUint64(); } + static ValueType& Set(ValueType& v, uint64_t data) { return v.SetUint64(data); } + static ValueType& Set(ValueType& v, uint64_t data, typename ValueType::AllocatorType&) { return v.SetUint64(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsDouble(); } + static double Get(const ValueType& v) { return v.GetDouble(); } + static ValueType& Set(ValueType& v, double data) { return v.SetDouble(data); } + static ValueType& Set(ValueType& v, double data, typename ValueType::AllocatorType&) { return v.SetDouble(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsFloat(); } + static float Get(const ValueType& v) { return v.GetFloat(); } + static ValueType& Set(ValueType& v, float data) { return v.SetFloat(data); } + static ValueType& Set(ValueType& v, float data, typename ValueType::AllocatorType&) { return v.SetFloat(data); } +}; + +template +struct TypeHelper { + typedef const typename ValueType::Ch* StringType; + static bool Is(const ValueType& v) { return v.IsString(); } + static StringType Get(const ValueType& v) { return v.GetString(); } + static ValueType& Set(ValueType& v, const StringType data) { return v.SetString(typename ValueType::StringRefType(data)); } + static ValueType& Set(ValueType& v, const StringType data, typename ValueType::AllocatorType& a) { return v.SetString(data, a); } +}; + +#if RAPIDJSON_HAS_STDSTRING +template +struct TypeHelper > { + typedef std::basic_string StringType; + static bool Is(const ValueType& v) { return v.IsString(); } + static StringType Get(const ValueType& v) { return StringType(v.GetString(), v.GetStringLength()); } + static ValueType& Set(ValueType& v, const StringType& data, typename ValueType::AllocatorType& a) { return v.SetString(data, a); } +}; +#endif + +template +struct TypeHelper { + typedef typename ValueType::Array ArrayType; + static bool Is(const ValueType& v) { return v.IsArray(); } + static ArrayType Get(ValueType& v) { return v.GetArray(); } + static ValueType& Set(ValueType& v, ArrayType data) { return v = data; } + static ValueType& Set(ValueType& v, ArrayType data, typename ValueType::AllocatorType&) { return v = data; } +}; + +template +struct TypeHelper { + typedef typename ValueType::ConstArray ArrayType; + static bool Is(const ValueType& v) { return v.IsArray(); } + static ArrayType Get(const ValueType& v) { return v.GetArray(); } +}; + +template +struct TypeHelper { + typedef typename ValueType::Object ObjectType; + static bool Is(const ValueType& v) { return v.IsObject(); } + static ObjectType Get(ValueType& v) { return v.GetObject(); } + static ValueType& Set(ValueType& v, ObjectType data) { return v = data; } + static ValueType& Set(ValueType& v, ObjectType data, typename ValueType::AllocatorType&) { return v = data; } +}; + +template +struct TypeHelper { + typedef typename ValueType::ConstObject ObjectType; + static bool Is(const ValueType& v) { return v.IsObject(); } + static ObjectType Get(const ValueType& v) { return v.GetObject(); } +}; + +} // namespace internal + +// Forward declarations +template class GenericArray; +template class GenericObject; + +/////////////////////////////////////////////////////////////////////////////// +// GenericValue + +//! Represents a JSON value. Use Value for UTF8 encoding and default allocator. +/*! + A JSON value can be one of 7 types. This class is a variant type supporting + these types. + + Use the Value if UTF8 and default allocator + + \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) + \tparam Allocator Allocator type for allocating memory of object, array and string. +*/ +template > +class GenericValue { +public: + //! Name-value pair in an object. + typedef GenericMember Member; + typedef Encoding EncodingType; //!< Encoding type from template parameter. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericStringRef StringRefType; //!< Reference to a constant string + typedef typename GenericMemberIterator::Iterator MemberIterator; //!< Member iterator for iterating in object. + typedef typename GenericMemberIterator::Iterator ConstMemberIterator; //!< Constant member iterator for iterating in object. + typedef GenericValue* ValueIterator; //!< Value iterator for iterating in array. + typedef const GenericValue* ConstValueIterator; //!< Constant value iterator for iterating in array. + typedef GenericValue ValueType; //!< Value type of itself. + typedef GenericArray Array; + typedef GenericArray ConstArray; + typedef GenericObject Object; + typedef GenericObject ConstObject; + + //!@name Constructors and destructor. + //@{ + + //! Default constructor creates a null value. + GenericValue() RAPIDJSON_NOEXCEPT : data_() { data_.f.flags = kNullFlag; } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericValue(GenericValue&& rhs) RAPIDJSON_NOEXCEPT : data_(rhs.data_) { + rhs.data_.f.flags = kNullFlag; // give up contents + } +#endif + +private: + //! Copy constructor is not permitted. + GenericValue(const GenericValue& rhs); + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Moving from a GenericDocument is not permitted. + template + GenericValue(GenericDocument&& rhs); + + //! Move assignment from a GenericDocument is not permitted. + template + GenericValue& operator=(GenericDocument&& rhs); +#endif + +public: + + //! Constructor with JSON value type. + /*! This creates a Value of specified type with default content. + \param type Type of the value. + \note Default content for number is zero. + */ + explicit GenericValue(Type type) RAPIDJSON_NOEXCEPT : data_() { + static const uint16_t defaultFlags[] = { + kNullFlag, kFalseFlag, kTrueFlag, kObjectFlag, kArrayFlag, kShortStringFlag, + kNumberAnyFlag + }; + RAPIDJSON_NOEXCEPT_ASSERT(type >= kNullType && type <= kNumberType); + data_.f.flags = defaultFlags[type]; + + // Use ShortString to store empty string. + if (type == kStringType) + data_.ss.SetLength(0); + } + + //! Explicit copy constructor (with allocator) + /*! Creates a copy of a Value by using the given Allocator + \tparam SourceAllocator allocator of \c rhs + \param rhs Value to copy from (read-only) + \param allocator Allocator for allocating copied elements and buffers. Commonly use GenericDocument::GetAllocator(). + \param copyConstStrings Force copying of constant strings (e.g. referencing an in-situ buffer) + \see CopyFrom() + */ + template + GenericValue(const GenericValue& rhs, Allocator& allocator, bool copyConstStrings = false) { + switch (rhs.GetType()) { + case kObjectType: { + SizeType count = rhs.data_.o.size; + Member* lm = reinterpret_cast(allocator.Malloc(count * sizeof(Member))); + const typename GenericValue::Member* rm = rhs.GetMembersPointer(); + for (SizeType i = 0; i < count; i++) { + new (&lm[i].name) GenericValue(rm[i].name, allocator, copyConstStrings); + new (&lm[i].value) GenericValue(rm[i].value, allocator, copyConstStrings); + } + data_.f.flags = kObjectFlag; + data_.o.size = data_.o.capacity = count; + SetMembersPointer(lm); + } + break; + case kArrayType: { + SizeType count = rhs.data_.a.size; + GenericValue* le = reinterpret_cast(allocator.Malloc(count * sizeof(GenericValue))); + const GenericValue* re = rhs.GetElementsPointer(); + for (SizeType i = 0; i < count; i++) + new (&le[i]) GenericValue(re[i], allocator, copyConstStrings); + data_.f.flags = kArrayFlag; + data_.a.size = data_.a.capacity = count; + SetElementsPointer(le); + } + break; + case kStringType: + if (rhs.data_.f.flags == kConstStringFlag && !copyConstStrings) { + data_.f.flags = rhs.data_.f.flags; + data_ = *reinterpret_cast(&rhs.data_); + } + else + SetStringRaw(StringRef(rhs.GetString(), rhs.GetStringLength()), allocator); + break; + default: + data_.f.flags = rhs.data_.f.flags; + data_ = *reinterpret_cast(&rhs.data_); + break; + } + } + + //! Constructor for boolean value. + /*! \param b Boolean value + \note This constructor is limited to \em real boolean values and rejects + implicitly converted types like arbitrary pointers. Use an explicit cast + to \c bool, if you want to construct a boolean JSON value in such cases. + */ +#ifndef RAPIDJSON_DOXYGEN_RUNNING // hide SFINAE from Doxygen + template + explicit GenericValue(T b, RAPIDJSON_ENABLEIF((internal::IsSame))) RAPIDJSON_NOEXCEPT // See #472 +#else + explicit GenericValue(bool b) RAPIDJSON_NOEXCEPT +#endif + : data_() { + // safe-guard against failing SFINAE + RAPIDJSON_STATIC_ASSERT((internal::IsSame::Value)); + data_.f.flags = b ? kTrueFlag : kFalseFlag; + } + + //! Constructor for int value. + explicit GenericValue(int i) RAPIDJSON_NOEXCEPT : data_() { + data_.n.i64 = i; + data_.f.flags = (i >= 0) ? (kNumberIntFlag | kUintFlag | kUint64Flag) : kNumberIntFlag; + } + + //! Constructor for unsigned value. + explicit GenericValue(unsigned u) RAPIDJSON_NOEXCEPT : data_() { + data_.n.u64 = u; + data_.f.flags = (u & 0x80000000) ? kNumberUintFlag : (kNumberUintFlag | kIntFlag | kInt64Flag); + } + + //! Constructor for int64_t value. + explicit GenericValue(int64_t i64) RAPIDJSON_NOEXCEPT : data_() { + data_.n.i64 = i64; + data_.f.flags = kNumberInt64Flag; + if (i64 >= 0) { + data_.f.flags |= kNumberUint64Flag; + if (!(static_cast(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) + data_.f.flags |= kUintFlag; + if (!(static_cast(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + else if (i64 >= static_cast(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + + //! Constructor for uint64_t value. + explicit GenericValue(uint64_t u64) RAPIDJSON_NOEXCEPT : data_() { + data_.n.u64 = u64; + data_.f.flags = kNumberUint64Flag; + if (!(u64 & RAPIDJSON_UINT64_C2(0x80000000, 0x00000000))) + data_.f.flags |= kInt64Flag; + if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) + data_.f.flags |= kUintFlag; + if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + + //! Constructor for double value. + explicit GenericValue(double d) RAPIDJSON_NOEXCEPT : data_() { data_.n.d = d; data_.f.flags = kNumberDoubleFlag; } + + //! Constructor for float value. + explicit GenericValue(float f) RAPIDJSON_NOEXCEPT : data_() { data_.n.d = static_cast(f); data_.f.flags = kNumberDoubleFlag; } + + //! Constructor for constant string (i.e. do not make a copy of string) + GenericValue(const Ch* s, SizeType length) RAPIDJSON_NOEXCEPT : data_() { SetStringRaw(StringRef(s, length)); } + + //! Constructor for constant string (i.e. do not make a copy of string) + explicit GenericValue(StringRefType s) RAPIDJSON_NOEXCEPT : data_() { SetStringRaw(s); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch* s, SizeType length, Allocator& allocator) : data_() { SetStringRaw(StringRef(s, length), allocator); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch*s, Allocator& allocator) : data_() { SetStringRaw(StringRef(s), allocator); } + +#if RAPIDJSON_HAS_STDSTRING + //! Constructor for copy-string from a string object (i.e. do make a copy of string) + /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + GenericValue(const std::basic_string& s, Allocator& allocator) : data_() { SetStringRaw(StringRef(s), allocator); } +#endif + + //! Constructor for Array. + /*! + \param a An array obtained by \c GetArray(). + \note \c Array is always pass-by-value. + \note the source array is moved into this value and the sourec array becomes empty. + */ + GenericValue(Array a) RAPIDJSON_NOEXCEPT : data_(a.value_.data_) { + a.value_.data_ = Data(); + a.value_.data_.f.flags = kArrayFlag; + } + + //! Constructor for Object. + /*! + \param o An object obtained by \c GetObject(). + \note \c Object is always pass-by-value. + \note the source object is moved into this value and the sourec object becomes empty. + */ + GenericValue(Object o) RAPIDJSON_NOEXCEPT : data_(o.value_.data_) { + o.value_.data_ = Data(); + o.value_.data_.f.flags = kObjectFlag; + } + + //! Destructor. + /*! Need to destruct elements of array, members of object, or copy-string. + */ + ~GenericValue() { + if (Allocator::kNeedFree) { // Shortcut by Allocator's trait + switch(data_.f.flags) { + case kArrayFlag: + { + GenericValue* e = GetElementsPointer(); + for (GenericValue* v = e; v != e + data_.a.size; ++v) + v->~GenericValue(); + Allocator::Free(e); + } + break; + + case kObjectFlag: + for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) + m->~Member(); + Allocator::Free(GetMembersPointer()); + break; + + case kCopyStringFlag: + Allocator::Free(const_cast(GetStringPointer())); + break; + + default: + break; // Do nothing for other types. + } + } + } + + //@} + + //!@name Assignment operators + //@{ + + //! Assignment with move semantics. + /*! \param rhs Source of the assignment. It will become a null value after assignment. + */ + GenericValue& operator=(GenericValue& rhs) RAPIDJSON_NOEXCEPT { + if (RAPIDJSON_LIKELY(this != &rhs)) { + this->~GenericValue(); + RawAssign(rhs); + } + return *this; + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move assignment in C++11 + GenericValue& operator=(GenericValue&& rhs) RAPIDJSON_NOEXCEPT { + return *this = rhs.Move(); + } +#endif + + //! Assignment of constant string reference (no copy) + /*! \param str Constant string reference to be assigned + \note This overload is needed to avoid clashes with the generic primitive type assignment overload below. + \see GenericStringRef, operator=(T) + */ + GenericValue& operator=(StringRefType str) RAPIDJSON_NOEXCEPT { + GenericValue s(str); + return *this = s; + } + + //! Assignment with primitive types. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param value The value to be assigned. + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref SetString(const Ch*, Allocator&) (for copying) or + \ref StringRef() (to explicitly mark the pointer as constant) instead. + All other pointer types would implicitly convert to \c bool, + use \ref SetBool() instead. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::IsPointer), (GenericValue&)) + operator=(T value) { + GenericValue v(value); + return *this = v; + } + + //! Deep-copy assignment from Value + /*! Assigns a \b copy of the Value to the current Value object + \tparam SourceAllocator Allocator type of \c rhs + \param rhs Value to copy from (read-only) + \param allocator Allocator to use for copying + \param copyConstStrings Force copying of constant strings (e.g. referencing an in-situ buffer) + */ + template + GenericValue& CopyFrom(const GenericValue& rhs, Allocator& allocator, bool copyConstStrings = false) { + RAPIDJSON_ASSERT(static_cast(this) != static_cast(&rhs)); + this->~GenericValue(); + new (this) GenericValue(rhs, allocator, copyConstStrings); + return *this; + } + + //! Exchange the contents of this value with those of other. + /*! + \param other Another value. + \note Constant complexity. + */ + GenericValue& Swap(GenericValue& other) RAPIDJSON_NOEXCEPT { + GenericValue temp; + temp.RawAssign(*this); + RawAssign(other); + other.RawAssign(temp); + return *this; + } + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.value, b.value); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericValue& a, GenericValue& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //! Prepare Value for move semantics + /*! \return *this */ + GenericValue& Move() RAPIDJSON_NOEXCEPT { return *this; } + //@} + + //!@name Equal-to and not-equal-to operators + //@{ + //! Equal-to operator + /*! + \note If an object contains duplicated named member, comparing equality with any object is always \c false. + \note Complexity is quadratic in Object's member number and linear for the rest (number of all values in the subtree and total lengths of all strings). + */ + template + bool operator==(const GenericValue& rhs) const { + typedef GenericValue RhsType; + if (GetType() != rhs.GetType()) + return false; + + switch (GetType()) { + case kObjectType: // Warning: O(n^2) inner-loop + if (data_.o.size != rhs.data_.o.size) + return false; + for (ConstMemberIterator lhsMemberItr = MemberBegin(); lhsMemberItr != MemberEnd(); ++lhsMemberItr) { + typename RhsType::ConstMemberIterator rhsMemberItr = rhs.FindMember(lhsMemberItr->name); + if (rhsMemberItr == rhs.MemberEnd() || lhsMemberItr->value != rhsMemberItr->value) + return false; + } + return true; + + case kArrayType: + if (data_.a.size != rhs.data_.a.size) + return false; + for (SizeType i = 0; i < data_.a.size; i++) + if ((*this)[i] != rhs[i]) + return false; + return true; + + case kStringType: + return StringEqual(rhs); + + case kNumberType: + if (IsDouble() || rhs.IsDouble()) { + double a = GetDouble(); // May convert from integer to double. + double b = rhs.GetDouble(); // Ditto + return a >= b && a <= b; // Prevent -Wfloat-equal + } + else + return data_.n.u64 == rhs.data_.n.u64; + + default: + return true; + } + } + + //! Equal-to operator with const C-string pointer + bool operator==(const Ch* rhs) const { return *this == GenericValue(StringRef(rhs)); } + +#if RAPIDJSON_HAS_STDSTRING + //! Equal-to operator with string object + /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + bool operator==(const std::basic_string& rhs) const { return *this == GenericValue(StringRef(rhs)); } +#endif + + //! Equal-to operator with primitive types + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c true, \c false + */ + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr,internal::IsGenericValue >), (bool)) operator==(const T& rhs) const { return *this == GenericValue(rhs); } + + //! Not-equal-to operator + /*! \return !(*this == rhs) + */ + template + bool operator!=(const GenericValue& rhs) const { return !(*this == rhs); } + + //! Not-equal-to operator with const C-string pointer + bool operator!=(const Ch* rhs) const { return !(*this == rhs); } + + //! Not-equal-to operator with arbitrary types + /*! \return !(*this == rhs) + */ + template RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& rhs) const { return !(*this == rhs); } + + //! Equal-to operator with arbitrary types (symmetric version) + /*! \return (rhs == lhs) + */ + template friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator==(const T& lhs, const GenericValue& rhs) { return rhs == lhs; } + + //! Not-Equal-to operator with arbitrary types (symmetric version) + /*! \return !(rhs == lhs) + */ + template friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& lhs, const GenericValue& rhs) { return !(rhs == lhs); } + //@} + + //!@name Type + //@{ + + Type GetType() const { return static_cast(data_.f.flags & kTypeMask); } + bool IsNull() const { return data_.f.flags == kNullFlag; } + bool IsFalse() const { return data_.f.flags == kFalseFlag; } + bool IsTrue() const { return data_.f.flags == kTrueFlag; } + bool IsBool() const { return (data_.f.flags & kBoolFlag) != 0; } + bool IsObject() const { return data_.f.flags == kObjectFlag; } + bool IsArray() const { return data_.f.flags == kArrayFlag; } + bool IsNumber() const { return (data_.f.flags & kNumberFlag) != 0; } + bool IsInt() const { return (data_.f.flags & kIntFlag) != 0; } + bool IsUint() const { return (data_.f.flags & kUintFlag) != 0; } + bool IsInt64() const { return (data_.f.flags & kInt64Flag) != 0; } + bool IsUint64() const { return (data_.f.flags & kUint64Flag) != 0; } + bool IsDouble() const { return (data_.f.flags & kDoubleFlag) != 0; } + bool IsString() const { return (data_.f.flags & kStringFlag) != 0; } + + // Checks whether a number can be losslessly converted to a double. + bool IsLosslessDouble() const { + if (!IsNumber()) return false; + if (IsUint64()) { + uint64_t u = GetUint64(); + volatile double d = static_cast(u); + return (d >= 0.0) + && (d < static_cast((std::numeric_limits::max)())) + && (u == static_cast(d)); + } + if (IsInt64()) { + int64_t i = GetInt64(); + volatile double d = static_cast(i); + return (d >= static_cast((std::numeric_limits::min)())) + && (d < static_cast((std::numeric_limits::max)())) + && (i == static_cast(d)); + } + return true; // double, int, uint are always lossless + } + + // Checks whether a number is a float (possible lossy). + bool IsFloat() const { + if ((data_.f.flags & kDoubleFlag) == 0) + return false; + double d = GetDouble(); + return d >= -3.4028234e38 && d <= 3.4028234e38; + } + // Checks whether a number can be losslessly converted to a float. + bool IsLosslessFloat() const { + if (!IsNumber()) return false; + double a = GetDouble(); + if (a < static_cast(-(std::numeric_limits::max)()) + || a > static_cast((std::numeric_limits::max)())) + return false; + double b = static_cast(static_cast(a)); + return a >= b && a <= b; // Prevent -Wfloat-equal + } + + //@} + + //!@name Null + //@{ + + GenericValue& SetNull() { this->~GenericValue(); new (this) GenericValue(); return *this; } + + //@} + + //!@name Bool + //@{ + + bool GetBool() const { RAPIDJSON_ASSERT(IsBool()); return data_.f.flags == kTrueFlag; } + //!< Set boolean value + /*! \post IsBool() == true */ + GenericValue& SetBool(bool b) { this->~GenericValue(); new (this) GenericValue(b); return *this; } + + //@} + + //!@name Object + //@{ + + //! Set this value as an empty object. + /*! \post IsObject() == true */ + GenericValue& SetObject() { this->~GenericValue(); new (this) GenericValue(kObjectType); return *this; } + + //! Get the number of members in the object. + SizeType MemberCount() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size; } + + //! Get the capacity of object. + SizeType MemberCapacity() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.capacity; } + + //! Check whether the object is empty. + bool ObjectEmpty() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size == 0; } + + //! Get a value from an object associated with the name. + /*! \pre IsObject() == true + \tparam T Either \c Ch or \c const \c Ch (template used for disambiguation with \ref operator[](SizeType)) + \note In version 0.1x, if the member is not found, this function returns a null value. This makes issue 7. + Since 0.2, if the name is not correct, it will assert. + If user is unsure whether a member exists, user should use HasMember() first. + A better approach is to use FindMember(). + \note Linear time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >),(GenericValue&)) operator[](T* name) { + GenericValue n(StringRef(name)); + return (*this)[n]; + } + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >),(const GenericValue&)) operator[](T* name) const { return const_cast(*this)[name]; } + + //! Get a value from an object associated with the name. + /*! \pre IsObject() == true + \tparam SourceAllocator Allocator of the \c name value + + \note Compared to \ref operator[](T*), this version is faster because it does not need a StrLen(). + And it can also handle strings with embedded null characters. + + \note Linear time complexity. + */ + template + GenericValue& operator[](const GenericValue& name) { + MemberIterator member = FindMember(name); + if (member != MemberEnd()) + return member->value; + else { + RAPIDJSON_ASSERT(false); // see above note + + // This will generate -Wexit-time-destructors in clang + // static GenericValue NullValue; + // return NullValue; + + // Use static buffer and placement-new to prevent destruction + static char buffer[sizeof(GenericValue)]; + return *new (buffer) GenericValue(); + } + } + template + const GenericValue& operator[](const GenericValue& name) const { return const_cast(*this)[name]; } + +#if RAPIDJSON_HAS_STDSTRING + //! Get a value from an object associated with name (string object). + GenericValue& operator[](const std::basic_string& name) { return (*this)[GenericValue(StringRef(name))]; } + const GenericValue& operator[](const std::basic_string& name) const { return (*this)[GenericValue(StringRef(name))]; } +#endif + + //! Const member iterator + /*! \pre IsObject() == true */ + ConstMemberIterator MemberBegin() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(GetMembersPointer()); } + //! Const \em past-the-end member iterator + /*! \pre IsObject() == true */ + ConstMemberIterator MemberEnd() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(GetMembersPointer() + data_.o.size); } + //! Member iterator + /*! \pre IsObject() == true */ + MemberIterator MemberBegin() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(GetMembersPointer()); } + //! \em Past-the-end member iterator + /*! \pre IsObject() == true */ + MemberIterator MemberEnd() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(GetMembersPointer() + data_.o.size); } + + //! Request the object to have enough capacity to store members. + /*! \param newCapacity The capacity that the object at least need to have. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note Linear time complexity. + */ + GenericValue& MemberReserve(SizeType newCapacity, Allocator &allocator) { + RAPIDJSON_ASSERT(IsObject()); + if (newCapacity > data_.o.capacity) { + SetMembersPointer(reinterpret_cast(allocator.Realloc(GetMembersPointer(), data_.o.capacity * sizeof(Member), newCapacity * sizeof(Member)))); + data_.o.capacity = newCapacity; + } + return *this; + } + + //! Check whether a member exists in the object. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + bool HasMember(const Ch* name) const { return FindMember(name) != MemberEnd(); } + +#if RAPIDJSON_HAS_STDSTRING + //! Check whether a member exists in the object with string object. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + bool HasMember(const std::basic_string& name) const { return FindMember(name) != MemberEnd(); } +#endif + + //! Check whether a member exists in the object with GenericValue name. + /*! + This version is faster because it does not need a StrLen(). It can also handle string with null character. + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + template + bool HasMember(const GenericValue& name) const { return FindMember(name) != MemberEnd(); } + + //! Find member by name. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + + \note Earlier versions of Rapidjson returned a \c NULL pointer, in case + the requested member doesn't exist. For consistency with e.g. + \c std::map, this has been changed to MemberEnd() now. + \note Linear time complexity. + */ + MemberIterator FindMember(const Ch* name) { + GenericValue n(StringRef(name)); + return FindMember(n); + } + + ConstMemberIterator FindMember(const Ch* name) const { return const_cast(*this).FindMember(name); } + + //! Find member by name. + /*! + This version is faster because it does not need a StrLen(). It can also handle string with null character. + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + + \note Earlier versions of Rapidjson returned a \c NULL pointer, in case + the requested member doesn't exist. For consistency with e.g. + \c std::map, this has been changed to MemberEnd() now. + \note Linear time complexity. + */ + template + MemberIterator FindMember(const GenericValue& name) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(name.IsString()); + MemberIterator member = MemberBegin(); + for ( ; member != MemberEnd(); ++member) + if (name.StringEqual(member->name)) + break; + return member; + } + template ConstMemberIterator FindMember(const GenericValue& name) const { return const_cast(*this).FindMember(name); } + +#if RAPIDJSON_HAS_STDSTRING + //! Find member by string object name. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + */ + MemberIterator FindMember(const std::basic_string& name) { return FindMember(GenericValue(StringRef(name))); } + ConstMemberIterator FindMember(const std::basic_string& name) const { return FindMember(GenericValue(StringRef(name))); } +#endif + + //! Add a member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value Value of any type. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note The ownership of \c name and \c value will be transferred to this object on success. + \pre IsObject() && name.IsString() + \post name.IsNull() && value.IsNull() + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(name.IsString()); + + ObjectData& o = data_.o; + if (o.size >= o.capacity) + MemberReserve(o.capacity == 0 ? kDefaultObjectCapacity : (o.capacity + (o.capacity + 1) / 2), allocator); + Member* members = GetMembersPointer(); + members[o.size].name.RawAssign(name); + members[o.size].value.RawAssign(value); + o.size++; + return *this; + } + + //! Add a constant string value as member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, StringRefType value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Add a string object as member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, std::basic_string& value, Allocator& allocator) { + GenericValue v(value, allocator); + return AddMember(name, v, allocator); + } +#endif + + //! Add any primitive value as member (name-value pair) to the object. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param name A string value as name of member. + \param value Value of primitive type \c T as value of member + \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref + AddMember(StringRefType, StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized Constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + AddMember(GenericValue& name, T value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericValue& AddMember(GenericValue&& name, GenericValue&& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(GenericValue&& name, GenericValue& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(GenericValue& name, GenericValue&& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(StringRefType name, GenericValue&& value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + + + //! Add a member (name-value pair) to the object. + /*! \param name A constant string reference as name of member. + \param value Value of any type. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note The ownership of \c value will be transferred to this object on success. + \pre IsObject() + \post value.IsNull() + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(StringRefType name, GenericValue& value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } + + //! Add a constant string value as member (name-value pair) to the object. + /*! \param name A constant string reference as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(StringRefType,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(StringRefType name, StringRefType value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + + //! Add any primitive value as member (name-value pair) to the object. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param name A constant string reference as name of member. + \param value Value of primitive type \c T as value of member + \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref + AddMember(StringRefType, StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized Constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + AddMember(StringRefType name, T value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } + + //! Remove all members in the object. + /*! This function do not deallocate memory in the object, i.e. the capacity is unchanged. + \note Linear time complexity. + */ + void RemoveAllMembers() { + RAPIDJSON_ASSERT(IsObject()); + for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) + m->~Member(); + data_.o.size = 0; + } + + //! Remove a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note This function may reorder the object members. Use \ref + EraseMember(ConstMemberIterator) if you need to preserve the + relative order of the remaining members. + \note Linear time complexity. + */ + bool RemoveMember(const Ch* name) { + GenericValue n(StringRef(name)); + return RemoveMember(n); + } + +#if RAPIDJSON_HAS_STDSTRING + bool RemoveMember(const std::basic_string& name) { return RemoveMember(GenericValue(StringRef(name))); } +#endif + + template + bool RemoveMember(const GenericValue& name) { + MemberIterator m = FindMember(name); + if (m != MemberEnd()) { + RemoveMember(m); + return true; + } + else + return false; + } + + //! Remove a member in object by iterator. + /*! \param m member iterator (obtained by FindMember() or MemberBegin()). + \return the new iterator after removal. + \note This function may reorder the object members. Use \ref + EraseMember(ConstMemberIterator) if you need to preserve the + relative order of the remaining members. + \note Constant time complexity. + */ + MemberIterator RemoveMember(MemberIterator m) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(data_.o.size > 0); + RAPIDJSON_ASSERT(GetMembersPointer() != 0); + RAPIDJSON_ASSERT(m >= MemberBegin() && m < MemberEnd()); + + MemberIterator last(GetMembersPointer() + (data_.o.size - 1)); + if (data_.o.size > 1 && m != last) + *m = *last; // Move the last one to this place + else + m->~Member(); // Only one left, just destroy + --data_.o.size; + return m; + } + + //! Remove a member from an object by iterator. + /*! \param pos iterator to the member to remove + \pre IsObject() == true && \ref MemberBegin() <= \c pos < \ref MemberEnd() + \return Iterator following the removed element. + If the iterator \c pos refers to the last element, the \ref MemberEnd() iterator is returned. + \note This function preserves the relative order of the remaining object + members. If you do not need this, use the more efficient \ref RemoveMember(MemberIterator). + \note Linear time complexity. + */ + MemberIterator EraseMember(ConstMemberIterator pos) { + return EraseMember(pos, pos +1); + } + + //! Remove members in the range [first, last) from an object. + /*! \param first iterator to the first member to remove + \param last iterator following the last member to remove + \pre IsObject() == true && \ref MemberBegin() <= \c first <= \c last <= \ref MemberEnd() + \return Iterator following the last removed element. + \note This function preserves the relative order of the remaining object + members. + \note Linear time complexity. + */ + MemberIterator EraseMember(ConstMemberIterator first, ConstMemberIterator last) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(data_.o.size > 0); + RAPIDJSON_ASSERT(GetMembersPointer() != 0); + RAPIDJSON_ASSERT(first >= MemberBegin()); + RAPIDJSON_ASSERT(first <= last); + RAPIDJSON_ASSERT(last <= MemberEnd()); + + MemberIterator pos = MemberBegin() + (first - MemberBegin()); + for (MemberIterator itr = pos; itr != last; ++itr) + itr->~Member(); + std::memmove(static_cast(&*pos), &*last, static_cast(MemberEnd() - last) * sizeof(Member)); + data_.o.size -= static_cast(last - first); + return pos; + } + + //! Erase a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note Linear time complexity. + */ + bool EraseMember(const Ch* name) { + GenericValue n(StringRef(name)); + return EraseMember(n); + } + +#if RAPIDJSON_HAS_STDSTRING + bool EraseMember(const std::basic_string& name) { return EraseMember(GenericValue(StringRef(name))); } +#endif + + template + bool EraseMember(const GenericValue& name) { + MemberIterator m = FindMember(name); + if (m != MemberEnd()) { + EraseMember(m); + return true; + } + else + return false; + } + + Object GetObject() { RAPIDJSON_ASSERT(IsObject()); return Object(*this); } + ConstObject GetObject() const { RAPIDJSON_ASSERT(IsObject()); return ConstObject(*this); } + + //@} + + //!@name Array + //@{ + + //! Set this value as an empty array. + /*! \post IsArray == true */ + GenericValue& SetArray() { this->~GenericValue(); new (this) GenericValue(kArrayType); return *this; } + + //! Get the number of elements in array. + SizeType Size() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size; } + + //! Get the capacity of array. + SizeType Capacity() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.capacity; } + + //! Check whether the array is empty. + bool Empty() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size == 0; } + + //! Remove all elements in the array. + /*! This function do not deallocate memory in the array, i.e. the capacity is unchanged. + \note Linear time complexity. + */ + void Clear() { + RAPIDJSON_ASSERT(IsArray()); + GenericValue* e = GetElementsPointer(); + for (GenericValue* v = e; v != e + data_.a.size; ++v) + v->~GenericValue(); + data_.a.size = 0; + } + + //! Get an element from array by index. + /*! \pre IsArray() == true + \param index Zero-based index of element. + \see operator[](T*) + */ + GenericValue& operator[](SizeType index) { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(index < data_.a.size); + return GetElementsPointer()[index]; + } + const GenericValue& operator[](SizeType index) const { return const_cast(*this)[index]; } + + //! Element iterator + /*! \pre IsArray() == true */ + ValueIterator Begin() { RAPIDJSON_ASSERT(IsArray()); return GetElementsPointer(); } + //! \em Past-the-end element iterator + /*! \pre IsArray() == true */ + ValueIterator End() { RAPIDJSON_ASSERT(IsArray()); return GetElementsPointer() + data_.a.size; } + //! Constant element iterator + /*! \pre IsArray() == true */ + ConstValueIterator Begin() const { return const_cast(*this).Begin(); } + //! Constant \em past-the-end element iterator + /*! \pre IsArray() == true */ + ConstValueIterator End() const { return const_cast(*this).End(); } + + //! Request the array to have enough capacity to store elements. + /*! \param newCapacity The capacity that the array at least need to have. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note Linear time complexity. + */ + GenericValue& Reserve(SizeType newCapacity, Allocator &allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (newCapacity > data_.a.capacity) { + SetElementsPointer(reinterpret_cast(allocator.Realloc(GetElementsPointer(), data_.a.capacity * sizeof(GenericValue), newCapacity * sizeof(GenericValue)))); + data_.a.capacity = newCapacity; + } + return *this; + } + + //! Append a GenericValue at the end of the array. + /*! \param value Value to be appended. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \post value.IsNull() == true + \return The value itself for fluent API. + \note The ownership of \c value will be transferred to this array on success. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + \note Amortized constant time complexity. + */ + GenericValue& PushBack(GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (data_.a.size >= data_.a.capacity) + Reserve(data_.a.capacity == 0 ? kDefaultArrayCapacity : (data_.a.capacity + (data_.a.capacity + 1) / 2), allocator); + GetElementsPointer()[data_.a.size++].RawAssign(value); + return *this; + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericValue& PushBack(GenericValue&& value, Allocator& allocator) { + return PushBack(value, allocator); + } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + + //! Append a constant string reference at the end of the array. + /*! \param value Constant string reference to be appended. + \param allocator Allocator for reallocating memory. It must be the same one used previously. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \return The value itself for fluent API. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + \note Amortized constant time complexity. + \see GenericStringRef + */ + GenericValue& PushBack(StringRefType value, Allocator& allocator) { + return (*this).template PushBack(value, allocator); + } + + //! Append a primitive value at the end of the array. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param value Value of primitive type T to be appended. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \return The value itself for fluent API. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref PushBack(GenericValue&, Allocator&) or \ref + PushBack(StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + PushBack(T value, Allocator& allocator) { + GenericValue v(value); + return PushBack(v, allocator); + } + + //! Remove the last element in the array. + /*! + \note Constant time complexity. + */ + GenericValue& PopBack() { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(!Empty()); + GetElementsPointer()[--data_.a.size].~GenericValue(); + return *this; + } + + //! Remove an element of array by iterator. + /*! + \param pos iterator to the element to remove + \pre IsArray() == true && \ref Begin() <= \c pos < \ref End() + \return Iterator following the removed element. If the iterator pos refers to the last element, the End() iterator is returned. + \note Linear time complexity. + */ + ValueIterator Erase(ConstValueIterator pos) { + return Erase(pos, pos + 1); + } + + //! Remove elements in the range [first, last) of the array. + /*! + \param first iterator to the first element to remove + \param last iterator following the last element to remove + \pre IsArray() == true && \ref Begin() <= \c first <= \c last <= \ref End() + \return Iterator following the last removed element. + \note Linear time complexity. + */ + ValueIterator Erase(ConstValueIterator first, ConstValueIterator last) { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(data_.a.size > 0); + RAPIDJSON_ASSERT(GetElementsPointer() != 0); + RAPIDJSON_ASSERT(first >= Begin()); + RAPIDJSON_ASSERT(first <= last); + RAPIDJSON_ASSERT(last <= End()); + ValueIterator pos = Begin() + (first - Begin()); + for (ValueIterator itr = pos; itr != last; ++itr) + itr->~GenericValue(); + std::memmove(static_cast(pos), last, static_cast(End() - last) * sizeof(GenericValue)); + data_.a.size -= static_cast(last - first); + return pos; + } + + Array GetArray() { RAPIDJSON_ASSERT(IsArray()); return Array(*this); } + ConstArray GetArray() const { RAPIDJSON_ASSERT(IsArray()); return ConstArray(*this); } + + //@} + + //!@name Number + //@{ + + int GetInt() const { RAPIDJSON_ASSERT(data_.f.flags & kIntFlag); return data_.n.i.i; } + unsigned GetUint() const { RAPIDJSON_ASSERT(data_.f.flags & kUintFlag); return data_.n.u.u; } + int64_t GetInt64() const { RAPIDJSON_ASSERT(data_.f.flags & kInt64Flag); return data_.n.i64; } + uint64_t GetUint64() const { RAPIDJSON_ASSERT(data_.f.flags & kUint64Flag); return data_.n.u64; } + + //! Get the value as double type. + /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessDouble() to check whether the converison is lossless. + */ + double GetDouble() const { + RAPIDJSON_ASSERT(IsNumber()); + if ((data_.f.flags & kDoubleFlag) != 0) return data_.n.d; // exact type, no conversion. + if ((data_.f.flags & kIntFlag) != 0) return data_.n.i.i; // int -> double + if ((data_.f.flags & kUintFlag) != 0) return data_.n.u.u; // unsigned -> double + if ((data_.f.flags & kInt64Flag) != 0) return static_cast(data_.n.i64); // int64_t -> double (may lose precision) + RAPIDJSON_ASSERT((data_.f.flags & kUint64Flag) != 0); return static_cast(data_.n.u64); // uint64_t -> double (may lose precision) + } + + //! Get the value as float type. + /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessFloat() to check whether the converison is lossless. + */ + float GetFloat() const { + return static_cast(GetDouble()); + } + + GenericValue& SetInt(int i) { this->~GenericValue(); new (this) GenericValue(i); return *this; } + GenericValue& SetUint(unsigned u) { this->~GenericValue(); new (this) GenericValue(u); return *this; } + GenericValue& SetInt64(int64_t i64) { this->~GenericValue(); new (this) GenericValue(i64); return *this; } + GenericValue& SetUint64(uint64_t u64) { this->~GenericValue(); new (this) GenericValue(u64); return *this; } + GenericValue& SetDouble(double d) { this->~GenericValue(); new (this) GenericValue(d); return *this; } + GenericValue& SetFloat(float f) { this->~GenericValue(); new (this) GenericValue(static_cast(f)); return *this; } + + //@} + + //!@name String + //@{ + + const Ch* GetString() const { RAPIDJSON_ASSERT(IsString()); return (data_.f.flags & kInlineStrFlag) ? data_.ss.str : GetStringPointer(); } + + //! Get the length of string. + /*! Since rapidjson permits "\\u0000" in the json string, strlen(v.GetString()) may not equal to v.GetStringLength(). + */ + SizeType GetStringLength() const { RAPIDJSON_ASSERT(IsString()); return ((data_.f.flags & kInlineStrFlag) ? (data_.ss.GetLength()) : data_.s.length); } + + //! Set this value as a string without copying source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string pointer. + \param length The length of source string, excluding the trailing null terminator. + \return The value itself for fluent API. + \post IsString() == true && GetString() == s && GetStringLength() == length + \see SetString(StringRefType) + */ + GenericValue& SetString(const Ch* s, SizeType length) { return SetString(StringRef(s, length)); } + + //! Set this value as a string without copying source string. + /*! \param s source string reference + \return The value itself for fluent API. + \post IsString() == true && GetString() == s && GetStringLength() == s.length + */ + GenericValue& SetString(StringRefType s) { this->~GenericValue(); SetStringRaw(s); return *this; } + + //! Set this value as a string by copying from source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string. + \param length The length of source string, excluding the trailing null terminator. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(const Ch* s, SizeType length, Allocator& allocator) { return SetString(StringRef(s, length), allocator); } + + //! Set this value as a string by copying from source string. + /*! \param s source string. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(const Ch* s, Allocator& allocator) { return SetString(StringRef(s), allocator); } + + //! Set this value as a string by copying from source string. + /*! \param s source string reference + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s.s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(StringRefType s, Allocator& allocator) { this->~GenericValue(); SetStringRaw(s, allocator); return *this; } + +#if RAPIDJSON_HAS_STDSTRING + //! Set this value as a string by copying from source string. + /*! \param s source string. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s.data() && strcmp(GetString(),s.data() == 0 && GetStringLength() == s.size() + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + GenericValue& SetString(const std::basic_string& s, Allocator& allocator) { return SetString(StringRef(s), allocator); } +#endif + + //@} + + //!@name Array + //@{ + + //! Templated version for checking whether this value is type T. + /*! + \tparam T Either \c bool, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c float, \c const \c char*, \c std::basic_string + */ + template + bool Is() const { return internal::TypeHelper::Is(*this); } + + template + T Get() const { return internal::TypeHelper::Get(*this); } + + template + T Get() { return internal::TypeHelper::Get(*this); } + + template + ValueType& Set(const T& data) { return internal::TypeHelper::Set(*this, data); } + + template + ValueType& Set(const T& data, AllocatorType& allocator) { return internal::TypeHelper::Set(*this, data, allocator); } + + //@} + + //! Generate events of this value to a Handler. + /*! This function adopts the GoF visitor pattern. + Typical usage is to output this JSON value as JSON text via Writer, which is a Handler. + It can also be used to deep clone this value via GenericDocument, which is also a Handler. + \tparam Handler type of handler. + \param handler An object implementing concept Handler. + */ + template + bool Accept(Handler& handler) const { + switch(GetType()) { + case kNullType: return handler.Null(); + case kFalseType: return handler.Bool(false); + case kTrueType: return handler.Bool(true); + + case kObjectType: + if (RAPIDJSON_UNLIKELY(!handler.StartObject())) + return false; + for (ConstMemberIterator m = MemberBegin(); m != MemberEnd(); ++m) { + RAPIDJSON_ASSERT(m->name.IsString()); // User may change the type of name by MemberIterator. + if (RAPIDJSON_UNLIKELY(!handler.Key(m->name.GetString(), m->name.GetStringLength(), (m->name.data_.f.flags & kCopyFlag) != 0))) + return false; + if (RAPIDJSON_UNLIKELY(!m->value.Accept(handler))) + return false; + } + return handler.EndObject(data_.o.size); + + case kArrayType: + if (RAPIDJSON_UNLIKELY(!handler.StartArray())) + return false; + for (const GenericValue* v = Begin(); v != End(); ++v) + if (RAPIDJSON_UNLIKELY(!v->Accept(handler))) + return false; + return handler.EndArray(data_.a.size); + + case kStringType: + return handler.String(GetString(), GetStringLength(), (data_.f.flags & kCopyFlag) != 0); + + default: + RAPIDJSON_ASSERT(GetType() == kNumberType); + if (IsDouble()) return handler.Double(data_.n.d); + else if (IsInt()) return handler.Int(data_.n.i.i); + else if (IsUint()) return handler.Uint(data_.n.u.u); + else if (IsInt64()) return handler.Int64(data_.n.i64); + else return handler.Uint64(data_.n.u64); + } + } + +private: + template friend class GenericValue; + template friend class GenericDocument; + + enum { + kBoolFlag = 0x0008, + kNumberFlag = 0x0010, + kIntFlag = 0x0020, + kUintFlag = 0x0040, + kInt64Flag = 0x0080, + kUint64Flag = 0x0100, + kDoubleFlag = 0x0200, + kStringFlag = 0x0400, + kCopyFlag = 0x0800, + kInlineStrFlag = 0x1000, + + // Initial flags of different types. + kNullFlag = kNullType, + kTrueFlag = kTrueType | kBoolFlag, + kFalseFlag = kFalseType | kBoolFlag, + kNumberIntFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag, + kNumberUintFlag = kNumberType | kNumberFlag | kUintFlag | kUint64Flag | kInt64Flag, + kNumberInt64Flag = kNumberType | kNumberFlag | kInt64Flag, + kNumberUint64Flag = kNumberType | kNumberFlag | kUint64Flag, + kNumberDoubleFlag = kNumberType | kNumberFlag | kDoubleFlag, + kNumberAnyFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag | kUintFlag | kUint64Flag | kDoubleFlag, + kConstStringFlag = kStringType | kStringFlag, + kCopyStringFlag = kStringType | kStringFlag | kCopyFlag, + kShortStringFlag = kStringType | kStringFlag | kCopyFlag | kInlineStrFlag, + kObjectFlag = kObjectType, + kArrayFlag = kArrayType, + + kTypeMask = 0x07 + }; + + static const SizeType kDefaultArrayCapacity = 16; + static const SizeType kDefaultObjectCapacity = 16; + + struct Flag { +#if RAPIDJSON_48BITPOINTER_OPTIMIZATION + char payload[sizeof(SizeType) * 2 + 6]; // 2 x SizeType + lower 48-bit pointer +#elif RAPIDJSON_64BIT + char payload[sizeof(SizeType) * 2 + sizeof(void*) + 6]; // 6 padding bytes +#else + char payload[sizeof(SizeType) * 2 + sizeof(void*) + 2]; // 2 padding bytes +#endif + uint16_t flags; + }; + + struct String { + SizeType length; + SizeType hashcode; //!< reserved + const Ch* str; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // implementation detail: ShortString can represent zero-terminated strings up to MaxSize chars + // (excluding the terminating zero) and store a value to determine the length of the contained + // string in the last character str[LenPos] by storing "MaxSize - length" there. If the string + // to store has the maximal length of MaxSize then str[LenPos] will be 0 and therefore act as + // the string terminator as well. For getting the string length back from that value just use + // "MaxSize - str[LenPos]". + // This allows to store 13-chars strings in 32-bit mode, 21-chars strings in 64-bit mode, + // 13-chars strings for RAPIDJSON_48BITPOINTER_OPTIMIZATION=1 inline (for `UTF8`-encoded strings). + struct ShortString { + enum { MaxChars = sizeof(static_cast(0)->payload) / sizeof(Ch), MaxSize = MaxChars - 1, LenPos = MaxSize }; + Ch str[MaxChars]; + + inline static bool Usable(SizeType len) { return (MaxSize >= len); } + inline void SetLength(SizeType len) { str[LenPos] = static_cast(MaxSize - len); } + inline SizeType GetLength() const { return static_cast(MaxSize - str[LenPos]); } + }; // at most as many bytes as "String" above => 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // By using proper binary layout, retrieval of different integer types do not need conversions. + union Number { +#if RAPIDJSON_ENDIAN == RAPIDJSON_LITTLEENDIAN + struct I { + int i; + char padding[4]; + }i; + struct U { + unsigned u; + char padding2[4]; + }u; +#else + struct I { + char padding[4]; + int i; + }i; + struct U { + char padding2[4]; + unsigned u; + }u; +#endif + int64_t i64; + uint64_t u64; + double d; + }; // 8 bytes + + struct ObjectData { + SizeType size; + SizeType capacity; + Member* members; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + struct ArrayData { + SizeType size; + SizeType capacity; + GenericValue* elements; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + union Data { + String s; + ShortString ss; + Number n; + ObjectData o; + ArrayData a; + Flag f; + }; // 16 bytes in 32-bit mode, 24 bytes in 64-bit mode, 16 bytes in 64-bit with RAPIDJSON_48BITPOINTER_OPTIMIZATION + + RAPIDJSON_FORCEINLINE const Ch* GetStringPointer() const { return RAPIDJSON_GETPOINTER(Ch, data_.s.str); } + RAPIDJSON_FORCEINLINE const Ch* SetStringPointer(const Ch* str) { return RAPIDJSON_SETPOINTER(Ch, data_.s.str, str); } + RAPIDJSON_FORCEINLINE GenericValue* GetElementsPointer() const { return RAPIDJSON_GETPOINTER(GenericValue, data_.a.elements); } + RAPIDJSON_FORCEINLINE GenericValue* SetElementsPointer(GenericValue* elements) { return RAPIDJSON_SETPOINTER(GenericValue, data_.a.elements, elements); } + RAPIDJSON_FORCEINLINE Member* GetMembersPointer() const { return RAPIDJSON_GETPOINTER(Member, data_.o.members); } + RAPIDJSON_FORCEINLINE Member* SetMembersPointer(Member* members) { return RAPIDJSON_SETPOINTER(Member, data_.o.members, members); } + + // Initialize this value as array with initial data, without calling destructor. + void SetArrayRaw(GenericValue* values, SizeType count, Allocator& allocator) { + data_.f.flags = kArrayFlag; + if (count) { + GenericValue* e = static_cast(allocator.Malloc(count * sizeof(GenericValue))); + SetElementsPointer(e); + std::memcpy(static_cast(e), values, count * sizeof(GenericValue)); + } + else + SetElementsPointer(0); + data_.a.size = data_.a.capacity = count; + } + + //! Initialize this value as object with initial data, without calling destructor. + void SetObjectRaw(Member* members, SizeType count, Allocator& allocator) { + data_.f.flags = kObjectFlag; + if (count) { + Member* m = static_cast(allocator.Malloc(count * sizeof(Member))); + SetMembersPointer(m); + std::memcpy(static_cast(m), members, count * sizeof(Member)); + } + else + SetMembersPointer(0); + data_.o.size = data_.o.capacity = count; + } + + //! Initialize this value as constant string, without calling destructor. + void SetStringRaw(StringRefType s) RAPIDJSON_NOEXCEPT { + data_.f.flags = kConstStringFlag; + SetStringPointer(s); + data_.s.length = s.length; + } + + //! Initialize this value as copy string with initial data, without calling destructor. + void SetStringRaw(StringRefType s, Allocator& allocator) { + Ch* str = 0; + if (ShortString::Usable(s.length)) { + data_.f.flags = kShortStringFlag; + data_.ss.SetLength(s.length); + str = data_.ss.str; + } else { + data_.f.flags = kCopyStringFlag; + data_.s.length = s.length; + str = static_cast(allocator.Malloc((s.length + 1) * sizeof(Ch))); + SetStringPointer(str); + } + std::memcpy(str, s, s.length * sizeof(Ch)); + str[s.length] = '\0'; + } + + //! Assignment without calling destructor + void RawAssign(GenericValue& rhs) RAPIDJSON_NOEXCEPT { + data_ = rhs.data_; + // data_.f.flags = rhs.data_.f.flags; + rhs.data_.f.flags = kNullFlag; + } + + template + bool StringEqual(const GenericValue& rhs) const { + RAPIDJSON_ASSERT(IsString()); + RAPIDJSON_ASSERT(rhs.IsString()); + + const SizeType len1 = GetStringLength(); + const SizeType len2 = rhs.GetStringLength(); + if(len1 != len2) { return false; } + + const Ch* const str1 = GetString(); + const Ch* const str2 = rhs.GetString(); + if(str1 == str2) { return true; } // fast path for constant string + + return (std::memcmp(str1, str2, sizeof(Ch) * len1) == 0); + } + + Data data_; +}; + +//! GenericValue with UTF8 encoding +typedef GenericValue > Value; + +/////////////////////////////////////////////////////////////////////////////// +// GenericDocument + +//! A document for parsing JSON text as DOM. +/*! + \note implements Handler concept + \tparam Encoding Encoding for both parsing and string storage. + \tparam Allocator Allocator for allocating memory for the DOM + \tparam StackAllocator Allocator for allocating memory for stack during parsing. + \warning Although GenericDocument inherits from GenericValue, the API does \b not provide any virtual functions, especially no virtual destructor. To avoid memory leaks, do not \c delete a GenericDocument object via a pointer to a GenericValue. +*/ +template , typename StackAllocator = CrtAllocator> +class GenericDocument : public GenericValue { +public: + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericValue ValueType; //!< Value type of the document. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + + //! Constructor + /*! Creates an empty document of specified type. + \param type Mandatory type of object to create. + \param allocator Optional allocator for allocating memory. + \param stackCapacity Optional initial capacity of stack in bytes. + \param stackAllocator Optional allocator for allocating memory for stack. + */ + explicit GenericDocument(Type type, Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) : + GenericValue(type), allocator_(allocator), ownAllocator_(0), stack_(stackAllocator, stackCapacity), parseResult_() + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + } + + //! Constructor + /*! Creates an empty document which type is Null. + \param allocator Optional allocator for allocating memory. + \param stackCapacity Optional initial capacity of stack in bytes. + \param stackAllocator Optional allocator for allocating memory for stack. + */ + GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) : + allocator_(allocator), ownAllocator_(0), stack_(stackAllocator, stackCapacity), parseResult_() + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericDocument(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT + : ValueType(std::forward(rhs)), // explicit cast to avoid prohibited move from Document + allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + stack_(std::move(rhs.stack_)), + parseResult_(rhs.parseResult_) + { + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.parseResult_ = ParseResult(); + } +#endif + + ~GenericDocument() { + Destroy(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move assignment in C++11 + GenericDocument& operator=(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT + { + // The cast to ValueType is necessary here, because otherwise it would + // attempt to call GenericValue's templated assignment operator. + ValueType::operator=(std::forward(rhs)); + + // Calling the destructor here would prematurely call stack_'s destructor + Destroy(); + + allocator_ = rhs.allocator_; + ownAllocator_ = rhs.ownAllocator_; + stack_ = std::move(rhs.stack_); + parseResult_ = rhs.parseResult_; + + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.parseResult_ = ParseResult(); + + return *this; + } +#endif + + //! Exchange the contents of this document with those of another. + /*! + \param rhs Another document. + \note Constant complexity. + \see GenericValue::Swap + */ + GenericDocument& Swap(GenericDocument& rhs) RAPIDJSON_NOEXCEPT { + ValueType::Swap(rhs); + stack_.Swap(rhs.stack_); + internal::Swap(allocator_, rhs.allocator_); + internal::Swap(ownAllocator_, rhs.ownAllocator_); + internal::Swap(parseResult_, rhs.parseResult_); + return *this; + } + + // Allow Swap with ValueType. + // Refer to Effective C++ 3rd Edition/Item 33: Avoid hiding inherited names. + using ValueType::Swap; + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.doc, b.doc); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericDocument& a, GenericDocument& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //! Populate this document by a generator which produces SAX events. + /*! \tparam Generator A functor with bool f(Handler) prototype. + \param g Generator functor which sends SAX events to the parameter. + \return The document itself for fluent API. + */ + template + GenericDocument& Populate(Generator& g) { + ClearStackOnExit scope(*this); + if (g(*this)) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + ValueType::operator=(*stack_.template Pop(1));// Move value from stack to document + } + return *this; + } + + //!@name Parse from stream + //!@{ + + //! Parse JSON text from an input stream (with Encoding conversion) + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam SourceEncoding Encoding of input stream + \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + GenericReader reader( + stack_.HasAllocator() ? &stack_.GetAllocator() : 0); + ClearStackOnExit scope(*this); + parseResult_ = reader.template Parse(is, *this); + if (parseResult_) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + ValueType::operator=(*stack_.template Pop(1));// Move value from stack to document + } + return *this; + } + + //! Parse JSON text from an input stream + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + return ParseStream(is); + } + + //! Parse JSON text from an input stream (with \ref kParseDefaultFlags) + /*! \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + return ParseStream(is); + } + //!@} + + //!@name Parse in-place from mutable string + //!@{ + + //! Parse JSON text from a mutable string + /*! \tparam parseFlags Combination of \ref ParseFlag. + \param str Mutable zero-terminated string to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseInsitu(Ch* str) { + GenericInsituStringStream s(str); + return ParseStream(s); + } + + //! Parse JSON text from a mutable string (with \ref kParseDefaultFlags) + /*! \param str Mutable zero-terminated string to be parsed. + \return The document itself for fluent API. + */ + GenericDocument& ParseInsitu(Ch* str) { + return ParseInsitu(str); + } + //!@} + + //!@name Parse from read-only string + //!@{ + + //! Parse JSON text from a read-only string (with Encoding conversion) + /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). + \tparam SourceEncoding Transcoding from input Encoding + \param str Read-only zero-terminated string to be parsed. + */ + template + GenericDocument& Parse(const typename SourceEncoding::Ch* str) { + RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); + GenericStringStream s(str); + return ParseStream(s); + } + + //! Parse JSON text from a read-only string + /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). + \param str Read-only zero-terminated string to be parsed. + */ + template + GenericDocument& Parse(const Ch* str) { + return Parse(str); + } + + //! Parse JSON text from a read-only string (with \ref kParseDefaultFlags) + /*! \param str Read-only zero-terminated string to be parsed. + */ + GenericDocument& Parse(const Ch* str) { + return Parse(str); + } + + template + GenericDocument& Parse(const typename SourceEncoding::Ch* str, size_t length) { + RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); + MemoryStream ms(reinterpret_cast(str), length * sizeof(typename SourceEncoding::Ch)); + EncodedInputStream is(ms); + ParseStream(is); + return *this; + } + + template + GenericDocument& Parse(const Ch* str, size_t length) { + return Parse(str, length); + } + + GenericDocument& Parse(const Ch* str, size_t length) { + return Parse(str, length); + } + +#if RAPIDJSON_HAS_STDSTRING + template + GenericDocument& Parse(const std::basic_string& str) { + // c_str() is constant complexity according to standard. Should be faster than Parse(const char*, size_t) + return Parse(str.c_str()); + } + + template + GenericDocument& Parse(const std::basic_string& str) { + return Parse(str.c_str()); + } + + GenericDocument& Parse(const std::basic_string& str) { + return Parse(str); + } +#endif // RAPIDJSON_HAS_STDSTRING + + //!@} + + //!@name Handling parse errors + //!@{ + + //! Whether a parse error has occurred in the last parsing. + bool HasParseError() const { return parseResult_.IsError(); } + + //! Get the \ref ParseErrorCode of last parsing. + ParseErrorCode GetParseError() const { return parseResult_.Code(); } + + //! Get the position of last parsing error in input, 0 otherwise. + size_t GetErrorOffset() const { return parseResult_.Offset(); } + + //! Implicit conversion to get the last parse result +#ifndef __clang // -Wdocumentation + /*! \return \ref ParseResult of the last parse operation + + \code + Document doc; + ParseResult ok = doc.Parse(json); + if (!ok) + printf( "JSON parse error: %s (%u)\n", GetParseError_En(ok.Code()), ok.Offset()); + \endcode + */ +#endif + operator ParseResult() const { return parseResult_; } + //!@} + + //! Get the allocator of this document. + Allocator& GetAllocator() { + RAPIDJSON_ASSERT(allocator_); + return *allocator_; + } + + //! Get the capacity of stack in bytes. + size_t GetStackCapacity() const { return stack_.GetCapacity(); } + +private: + // clear stack on any exit from ParseStream, e.g. due to exception + struct ClearStackOnExit { + explicit ClearStackOnExit(GenericDocument& d) : d_(d) {} + ~ClearStackOnExit() { d_.ClearStack(); } + private: + ClearStackOnExit(const ClearStackOnExit&); + ClearStackOnExit& operator=(const ClearStackOnExit&); + GenericDocument& d_; + }; + + // callers of the following private Handler functions + // template friend class GenericReader; // for parsing + template friend class GenericValue; // for deep copying + +public: + // Implementation of Handler + bool Null() { new (stack_.template Push()) ValueType(); return true; } + bool Bool(bool b) { new (stack_.template Push()) ValueType(b); return true; } + bool Int(int i) { new (stack_.template Push()) ValueType(i); return true; } + bool Uint(unsigned i) { new (stack_.template Push()) ValueType(i); return true; } + bool Int64(int64_t i) { new (stack_.template Push()) ValueType(i); return true; } + bool Uint64(uint64_t i) { new (stack_.template Push()) ValueType(i); return true; } + bool Double(double d) { new (stack_.template Push()) ValueType(d); return true; } + + bool RawNumber(const Ch* str, SizeType length, bool copy) { + if (copy) + new (stack_.template Push()) ValueType(str, length, GetAllocator()); + else + new (stack_.template Push()) ValueType(str, length); + return true; + } + + bool String(const Ch* str, SizeType length, bool copy) { + if (copy) + new (stack_.template Push()) ValueType(str, length, GetAllocator()); + else + new (stack_.template Push()) ValueType(str, length); + return true; + } + + bool StartObject() { new (stack_.template Push()) ValueType(kObjectType); return true; } + + bool Key(const Ch* str, SizeType length, bool copy) { return String(str, length, copy); } + + bool EndObject(SizeType memberCount) { + typename ValueType::Member* members = stack_.template Pop(memberCount); + stack_.template Top()->SetObjectRaw(members, memberCount, GetAllocator()); + return true; + } + + bool StartArray() { new (stack_.template Push()) ValueType(kArrayType); return true; } + + bool EndArray(SizeType elementCount) { + ValueType* elements = stack_.template Pop(elementCount); + stack_.template Top()->SetArrayRaw(elements, elementCount, GetAllocator()); + return true; + } + +private: + //! Prohibit copying + GenericDocument(const GenericDocument&); + //! Prohibit assignment + GenericDocument& operator=(const GenericDocument&); + + void ClearStack() { + if (Allocator::kNeedFree) + while (stack_.GetSize() > 0) // Here assumes all elements in stack array are GenericValue (Member is actually 2 GenericValue objects) + (stack_.template Pop(1))->~ValueType(); + else + stack_.Clear(); + stack_.ShrinkToFit(); + } + + void Destroy() { + RAPIDJSON_DELETE(ownAllocator_); + } + + static const size_t kDefaultStackCapacity = 1024; + Allocator* allocator_; + Allocator* ownAllocator_; + internal::Stack stack_; + ParseResult parseResult_; +}; + +//! GenericDocument with UTF8 encoding +typedef GenericDocument > Document; + +//! Helper class for accessing Value of array type. +/*! + Instance of this helper class is obtained by \c GenericValue::GetArray(). + In addition to all APIs for array type, it provides range-based for loop if \c RAPIDJSON_HAS_CXX11_RANGE_FOR=1. +*/ +template +class GenericArray { +public: + typedef GenericArray ConstArray; + typedef GenericArray Array; + typedef ValueT PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + typedef ValueType* ValueIterator; // This may be const or non-const iterator + typedef const ValueT* ConstValueIterator; + typedef typename ValueType::AllocatorType AllocatorType; + typedef typename ValueType::StringRefType StringRefType; + + template + friend class GenericValue; + + GenericArray(const GenericArray& rhs) : value_(rhs.value_) {} + GenericArray& operator=(const GenericArray& rhs) { value_ = rhs.value_; return *this; } + ~GenericArray() {} + + SizeType Size() const { return value_.Size(); } + SizeType Capacity() const { return value_.Capacity(); } + bool Empty() const { return value_.Empty(); } + void Clear() const { value_.Clear(); } + ValueType& operator[](SizeType index) const { return value_[index]; } + ValueIterator Begin() const { return value_.Begin(); } + ValueIterator End() const { return value_.End(); } + GenericArray Reserve(SizeType newCapacity, AllocatorType &allocator) const { value_.Reserve(newCapacity, allocator); return *this; } + GenericArray PushBack(ValueType& value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericArray PushBack(ValueType&& value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericArray PushBack(StringRefType value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (const GenericArray&)) PushBack(T value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } + GenericArray PopBack() const { value_.PopBack(); return *this; } + ValueIterator Erase(ConstValueIterator pos) const { return value_.Erase(pos); } + ValueIterator Erase(ConstValueIterator first, ConstValueIterator last) const { return value_.Erase(first, last); } + +#if RAPIDJSON_HAS_CXX11_RANGE_FOR + ValueIterator begin() const { return value_.Begin(); } + ValueIterator end() const { return value_.End(); } +#endif + +private: + GenericArray(); + GenericArray(ValueType& value) : value_(value) {} + ValueType& value_; +}; + +//! Helper class for accessing Value of object type. +/*! + Instance of this helper class is obtained by \c GenericValue::GetObject(). + In addition to all APIs for array type, it provides range-based for loop if \c RAPIDJSON_HAS_CXX11_RANGE_FOR=1. +*/ +template +class GenericObject { +public: + typedef GenericObject ConstObject; + typedef GenericObject Object; + typedef ValueT PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + typedef GenericMemberIterator MemberIterator; // This may be const or non-const iterator + typedef GenericMemberIterator ConstMemberIterator; + typedef typename ValueType::AllocatorType AllocatorType; + typedef typename ValueType::StringRefType StringRefType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename ValueType::Ch Ch; + + template + friend class GenericValue; + + GenericObject(const GenericObject& rhs) : value_(rhs.value_) {} + GenericObject& operator=(const GenericObject& rhs) { value_ = rhs.value_; return *this; } + ~GenericObject() {} + + SizeType MemberCount() const { return value_.MemberCount(); } + SizeType MemberCapacity() const { return value_.MemberCapacity(); } + bool ObjectEmpty() const { return value_.ObjectEmpty(); } + template ValueType& operator[](T* name) const { return value_[name]; } + template ValueType& operator[](const GenericValue& name) const { return value_[name]; } +#if RAPIDJSON_HAS_STDSTRING + ValueType& operator[](const std::basic_string& name) const { return value_[name]; } +#endif + MemberIterator MemberBegin() const { return value_.MemberBegin(); } + MemberIterator MemberEnd() const { return value_.MemberEnd(); } + GenericObject MemberReserve(SizeType newCapacity, AllocatorType &allocator) const { value_.MemberReserve(newCapacity, allocator); return *this; } + bool HasMember(const Ch* name) const { return value_.HasMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool HasMember(const std::basic_string& name) const { return value_.HasMember(name); } +#endif + template bool HasMember(const GenericValue& name) const { return value_.HasMember(name); } + MemberIterator FindMember(const Ch* name) const { return value_.FindMember(name); } + template MemberIterator FindMember(const GenericValue& name) const { return value_.FindMember(name); } +#if RAPIDJSON_HAS_STDSTRING + MemberIterator FindMember(const std::basic_string& name) const { return value_.FindMember(name); } +#endif + GenericObject AddMember(ValueType& name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType& name, StringRefType value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#if RAPIDJSON_HAS_STDSTRING + GenericObject AddMember(ValueType& name, std::basic_string& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) AddMember(ValueType& name, T value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericObject AddMember(ValueType&& name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType&& name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType& name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(StringRefType name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericObject AddMember(StringRefType name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(StringRefType name, StringRefType value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericObject)) AddMember(StringRefType name, T value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + void RemoveAllMembers() { value_.RemoveAllMembers(); } + bool RemoveMember(const Ch* name) const { return value_.RemoveMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool RemoveMember(const std::basic_string& name) const { return value_.RemoveMember(name); } +#endif + template bool RemoveMember(const GenericValue& name) const { return value_.RemoveMember(name); } + MemberIterator RemoveMember(MemberIterator m) const { return value_.RemoveMember(m); } + MemberIterator EraseMember(ConstMemberIterator pos) const { return value_.EraseMember(pos); } + MemberIterator EraseMember(ConstMemberIterator first, ConstMemberIterator last) const { return value_.EraseMember(first, last); } + bool EraseMember(const Ch* name) const { return value_.EraseMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool EraseMember(const std::basic_string& name) const { return EraseMember(ValueType(StringRef(name))); } +#endif + template bool EraseMember(const GenericValue& name) const { return value_.EraseMember(name); } + +#if RAPIDJSON_HAS_CXX11_RANGE_FOR + MemberIterator begin() const { return value_.MemberBegin(); } + MemberIterator end() const { return value_.MemberEnd(); } +#endif + +private: + GenericObject(); + GenericObject(ValueType& value) : value_(value) {} + ValueType& value_; +}; + +RAPIDJSON_NAMESPACE_END +RAPIDJSON_DIAG_POP + +#endif // RAPIDJSON_DOCUMENT_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/encodedstream.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/encodedstream.h new file mode 100755 index 000000000..223601c05 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/encodedstream.h @@ -0,0 +1,299 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ENCODEDSTREAM_H_ +#define RAPIDJSON_ENCODEDSTREAM_H_ + +#include "stream.h" +#include "memorystream.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Input byte stream wrapper with a statically bound encoding. +/*! + \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. + \tparam InputByteStream Type of input byte stream. For example, FileReadStream. +*/ +template +class EncodedInputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); +public: + typedef typename Encoding::Ch Ch; + + EncodedInputStream(InputByteStream& is) : is_(is) { + current_ = Encoding::TakeBOM(is_); + } + + Ch Peek() const { return current_; } + Ch Take() { Ch c = current_; current_ = Encoding::Take(is_); return c; } + size_t Tell() const { return is_.Tell(); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + EncodedInputStream(const EncodedInputStream&); + EncodedInputStream& operator=(const EncodedInputStream&); + + InputByteStream& is_; + Ch current_; +}; + +//! Specialized for UTF8 MemoryStream. +template <> +class EncodedInputStream, MemoryStream> { +public: + typedef UTF8<>::Ch Ch; + + EncodedInputStream(MemoryStream& is) : is_(is) { + if (static_cast(is_.Peek()) == 0xEFu) is_.Take(); + if (static_cast(is_.Peek()) == 0xBBu) is_.Take(); + if (static_cast(is_.Peek()) == 0xBFu) is_.Take(); + } + Ch Peek() const { return is_.Peek(); } + Ch Take() { return is_.Take(); } + size_t Tell() const { return is_.Tell(); } + + // Not implemented + void Put(Ch) {} + void Flush() {} + Ch* PutBegin() { return 0; } + size_t PutEnd(Ch*) { return 0; } + + MemoryStream& is_; + +private: + EncodedInputStream(const EncodedInputStream&); + EncodedInputStream& operator=(const EncodedInputStream&); +}; + +//! Output byte stream wrapper with statically bound encoding. +/*! + \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. + \tparam OutputByteStream Type of input byte stream. For example, FileWriteStream. +*/ +template +class EncodedOutputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); +public: + typedef typename Encoding::Ch Ch; + + EncodedOutputStream(OutputByteStream& os, bool putBOM = true) : os_(os) { + if (putBOM) + Encoding::PutBOM(os_); + } + + void Put(Ch c) { Encoding::Put(os_, c); } + void Flush() { os_.Flush(); } + + // Not implemented + Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;} + Ch Take() { RAPIDJSON_ASSERT(false); return 0;} + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + EncodedOutputStream(const EncodedOutputStream&); + EncodedOutputStream& operator=(const EncodedOutputStream&); + + OutputByteStream& os_; +}; + +#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8::x, UTF16LE::x, UTF16BE::x, UTF32LE::x, UTF32BE::x + +//! Input stream wrapper with dynamically bound encoding and automatic encoding detection. +/*! + \tparam CharType Type of character for reading. + \tparam InputByteStream type of input byte stream to be wrapped. +*/ +template +class AutoUTFInputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); +public: + typedef CharType Ch; + + //! Constructor. + /*! + \param is input stream to be wrapped. + \param type UTF encoding type if it is not detected from the stream. + */ + AutoUTFInputStream(InputByteStream& is, UTFType type = kUTF8) : is_(&is), type_(type), hasBOM_(false) { + RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); + DetectType(); + static const TakeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Take) }; + takeFunc_ = f[type_]; + current_ = takeFunc_(*is_); + } + + UTFType GetType() const { return type_; } + bool HasBOM() const { return hasBOM_; } + + Ch Peek() const { return current_; } + Ch Take() { Ch c = current_; current_ = takeFunc_(*is_); return c; } + size_t Tell() const { return is_->Tell(); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + AutoUTFInputStream(const AutoUTFInputStream&); + AutoUTFInputStream& operator=(const AutoUTFInputStream&); + + // Detect encoding type with BOM or RFC 4627 + void DetectType() { + // BOM (Byte Order Mark): + // 00 00 FE FF UTF-32BE + // FF FE 00 00 UTF-32LE + // FE FF UTF-16BE + // FF FE UTF-16LE + // EF BB BF UTF-8 + + const unsigned char* c = reinterpret_cast(is_->Peek4()); + if (!c) + return; + + unsigned bom = static_cast(c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24)); + hasBOM_ = false; + if (bom == 0xFFFE0000) { type_ = kUTF32BE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } + else if (bom == 0x0000FEFF) { type_ = kUTF32LE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } + else if ((bom & 0xFFFF) == 0xFFFE) { type_ = kUTF16BE; hasBOM_ = true; is_->Take(); is_->Take(); } + else if ((bom & 0xFFFF) == 0xFEFF) { type_ = kUTF16LE; hasBOM_ = true; is_->Take(); is_->Take(); } + else if ((bom & 0xFFFFFF) == 0xBFBBEF) { type_ = kUTF8; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); } + + // RFC 4627: Section 3 + // "Since the first two characters of a JSON text will always be ASCII + // characters [RFC0020], it is possible to determine whether an octet + // stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking + // at the pattern of nulls in the first four octets." + // 00 00 00 xx UTF-32BE + // 00 xx 00 xx UTF-16BE + // xx 00 00 00 UTF-32LE + // xx 00 xx 00 UTF-16LE + // xx xx xx xx UTF-8 + + if (!hasBOM_) { + int pattern = (c[0] ? 1 : 0) | (c[1] ? 2 : 0) | (c[2] ? 4 : 0) | (c[3] ? 8 : 0); + switch (pattern) { + case 0x08: type_ = kUTF32BE; break; + case 0x0A: type_ = kUTF16BE; break; + case 0x01: type_ = kUTF32LE; break; + case 0x05: type_ = kUTF16LE; break; + case 0x0F: type_ = kUTF8; break; + default: break; // Use type defined by user. + } + } + + // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. + if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); + if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); + } + + typedef Ch (*TakeFunc)(InputByteStream& is); + InputByteStream* is_; + UTFType type_; + Ch current_; + TakeFunc takeFunc_; + bool hasBOM_; +}; + +//! Output stream wrapper with dynamically bound encoding and automatic encoding detection. +/*! + \tparam CharType Type of character for writing. + \tparam OutputByteStream type of output byte stream to be wrapped. +*/ +template +class AutoUTFOutputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); +public: + typedef CharType Ch; + + //! Constructor. + /*! + \param os output stream to be wrapped. + \param type UTF encoding type. + \param putBOM Whether to write BOM at the beginning of the stream. + */ + AutoUTFOutputStream(OutputByteStream& os, UTFType type, bool putBOM) : os_(&os), type_(type) { + RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); + + // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. + if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); + if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); + + static const PutFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Put) }; + putFunc_ = f[type_]; + + if (putBOM) + PutBOM(); + } + + UTFType GetType() const { return type_; } + + void Put(Ch c) { putFunc_(*os_, c); } + void Flush() { os_->Flush(); } + + // Not implemented + Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;} + Ch Take() { RAPIDJSON_ASSERT(false); return 0;} + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + AutoUTFOutputStream(const AutoUTFOutputStream&); + AutoUTFOutputStream& operator=(const AutoUTFOutputStream&); + + void PutBOM() { + typedef void (*PutBOMFunc)(OutputByteStream&); + static const PutBOMFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(PutBOM) }; + f[type_](*os_); + } + + typedef void (*PutFunc)(OutputByteStream&, Ch); + + OutputByteStream* os_; + UTFType type_; + PutFunc putFunc_; +}; + +#undef RAPIDJSON_ENCODINGS_FUNC + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/encodings.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/encodings.h new file mode 100755 index 000000000..0b2446795 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/encodings.h @@ -0,0 +1,716 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ENCODINGS_H_ +#define RAPIDJSON_ENCODINGS_H_ + +#include "rapidjson.h" + +#if defined(_MSC_VER) && !defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4244) // conversion from 'type1' to 'type2', possible loss of data +RAPIDJSON_DIAG_OFF(4702) // unreachable code +#elif defined(__GNUC__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +RAPIDJSON_DIAG_OFF(overflow) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Encoding + +/*! \class rapidjson::Encoding + \brief Concept for encoding of Unicode characters. + +\code +concept Encoding { + typename Ch; //! Type of character. A "character" is actually a code unit in unicode's definition. + + enum { supportUnicode = 1 }; // or 0 if not supporting unicode + + //! \brief Encode a Unicode codepoint to an output stream. + //! \param os Output stream. + //! \param codepoint An unicode codepoint, ranging from 0x0 to 0x10FFFF inclusively. + template + static void Encode(OutputStream& os, unsigned codepoint); + + //! \brief Decode a Unicode codepoint from an input stream. + //! \param is Input stream. + //! \param codepoint Output of the unicode codepoint. + //! \return true if a valid codepoint can be decoded from the stream. + template + static bool Decode(InputStream& is, unsigned* codepoint); + + //! \brief Validate one Unicode codepoint from an encoded stream. + //! \param is Input stream to obtain codepoint. + //! \param os Output for copying one codepoint. + //! \return true if it is valid. + //! \note This function just validating and copying the codepoint without actually decode it. + template + static bool Validate(InputStream& is, OutputStream& os); + + // The following functions are deal with byte streams. + + //! Take a character from input byte stream, skip BOM if exist. + template + static CharType TakeBOM(InputByteStream& is); + + //! Take a character from input byte stream. + template + static Ch Take(InputByteStream& is); + + //! Put BOM to output byte stream. + template + static void PutBOM(OutputByteStream& os); + + //! Put a character to output byte stream. + template + static void Put(OutputByteStream& os, Ch c); +}; +\endcode +*/ + +/////////////////////////////////////////////////////////////////////////////// +// UTF8 + +//! UTF-8 encoding. +/*! http://en.wikipedia.org/wiki/UTF-8 + http://tools.ietf.org/html/rfc3629 + \tparam CharType Code unit for storing 8-bit UTF-8 data. Default is char. + \note implements Encoding concept +*/ +template +struct UTF8 { + typedef CharType Ch; + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + if (codepoint <= 0x7F) + os.Put(static_cast(codepoint & 0xFF)); + else if (codepoint <= 0x7FF) { + os.Put(static_cast(0xC0 | ((codepoint >> 6) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint & 0x3F)))); + } + else if (codepoint <= 0xFFFF) { + os.Put(static_cast(0xE0 | ((codepoint >> 12) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + os.Put(static_cast(0x80 | (codepoint & 0x3F))); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + os.Put(static_cast(0xF0 | ((codepoint >> 18) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + os.Put(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + os.Put(static_cast(0x80 | (codepoint & 0x3F))); + } + } + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + if (codepoint <= 0x7F) + PutUnsafe(os, static_cast(codepoint & 0xFF)); + else if (codepoint <= 0x7FF) { + PutUnsafe(os, static_cast(0xC0 | ((codepoint >> 6) & 0xFF))); + PutUnsafe(os, static_cast(0x80 | ((codepoint & 0x3F)))); + } + else if (codepoint <= 0xFFFF) { + PutUnsafe(os, static_cast(0xE0 | ((codepoint >> 12) & 0xFF))); + PutUnsafe(os, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + PutUnsafe(os, static_cast(0x80 | (codepoint & 0x3F))); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + PutUnsafe(os, static_cast(0xF0 | ((codepoint >> 18) & 0xFF))); + PutUnsafe(os, static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + PutUnsafe(os, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + PutUnsafe(os, static_cast(0x80 | (codepoint & 0x3F))); + } + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { +#define RAPIDJSON_COPY() c = is.Take(); *codepoint = (*codepoint << 6) | (static_cast(c) & 0x3Fu) +#define RAPIDJSON_TRANS(mask) result &= ((GetRange(static_cast(c)) & mask) != 0) +#define RAPIDJSON_TAIL() RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x70) + typename InputStream::Ch c = is.Take(); + if (!(c & 0x80)) { + *codepoint = static_cast(c); + return true; + } + + unsigned char type = GetRange(static_cast(c)); + if (type >= 32) { + *codepoint = 0; + } else { + *codepoint = (0xFFu >> type) & static_cast(c); + } + bool result = true; + switch (type) { + case 2: RAPIDJSON_TAIL(); return result; + case 3: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 4: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x50); RAPIDJSON_TAIL(); return result; + case 5: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x10); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 6: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 10: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x20); RAPIDJSON_TAIL(); return result; + case 11: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x60); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + default: return false; + } +#undef RAPIDJSON_COPY +#undef RAPIDJSON_TRANS +#undef RAPIDJSON_TAIL + } + + template + static bool Validate(InputStream& is, OutputStream& os) { +#define RAPIDJSON_COPY() os.Put(c = is.Take()) +#define RAPIDJSON_TRANS(mask) result &= ((GetRange(static_cast(c)) & mask) != 0) +#define RAPIDJSON_TAIL() RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x70) + Ch c; + RAPIDJSON_COPY(); + if (!(c & 0x80)) + return true; + + bool result = true; + switch (GetRange(static_cast(c))) { + case 2: RAPIDJSON_TAIL(); return result; + case 3: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 4: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x50); RAPIDJSON_TAIL(); return result; + case 5: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x10); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 6: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 10: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x20); RAPIDJSON_TAIL(); return result; + case 11: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x60); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + default: return false; + } +#undef RAPIDJSON_COPY +#undef RAPIDJSON_TRANS +#undef RAPIDJSON_TAIL + } + + static unsigned char GetRange(unsigned char c) { + // Referring to DFA of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + // With new mapping 1 -> 0x10, 7 -> 0x20, 9 -> 0x40, such that AND operation can test multiple types. + static const unsigned char type[] = { + 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, + 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, + 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, + 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, + 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, + 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + }; + return type[c]; + } + + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + typename InputByteStream::Ch c = Take(is); + if (static_cast(c) != 0xEFu) return c; + c = is.Take(); + if (static_cast(c) != 0xBBu) return c; + c = is.Take(); + if (static_cast(c) != 0xBFu) return c; + c = is.Take(); + return c; + } + + template + static Ch Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + return static_cast(is.Take()); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xEFu)); + os.Put(static_cast(0xBBu)); + os.Put(static_cast(0xBFu)); + } + + template + static void Put(OutputByteStream& os, Ch c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF16 + +//! UTF-16 encoding. +/*! http://en.wikipedia.org/wiki/UTF-16 + http://tools.ietf.org/html/rfc2781 + \tparam CharType Type for storing 16-bit UTF-16 data. Default is wchar_t. C++11 may use char16_t instead. + \note implements Encoding concept + + \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. + For streaming, use UTF16LE and UTF16BE, which handle endianness. +*/ +template +struct UTF16 { + typedef CharType Ch; + RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 2); + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + if (codepoint <= 0xFFFF) { + RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair + os.Put(static_cast(codepoint)); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + unsigned v = codepoint - 0x10000; + os.Put(static_cast((v >> 10) | 0xD800)); + os.Put(static_cast((v & 0x3FF) | 0xDC00)); + } + } + + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + if (codepoint <= 0xFFFF) { + RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair + PutUnsafe(os, static_cast(codepoint)); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + unsigned v = codepoint - 0x10000; + PutUnsafe(os, static_cast((v >> 10) | 0xD800)); + PutUnsafe(os, static_cast((v & 0x3FF) | 0xDC00)); + } + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); + typename InputStream::Ch c = is.Take(); + if (c < 0xD800 || c > 0xDFFF) { + *codepoint = static_cast(c); + return true; + } + else if (c <= 0xDBFF) { + *codepoint = (static_cast(c) & 0x3FF) << 10; + c = is.Take(); + *codepoint |= (static_cast(c) & 0x3FF); + *codepoint += 0x10000; + return c >= 0xDC00 && c <= 0xDFFF; + } + return false; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + typename InputStream::Ch c; + os.Put(static_cast(c = is.Take())); + if (c < 0xD800 || c > 0xDFFF) + return true; + else if (c <= 0xDBFF) { + os.Put(c = is.Take()); + return c >= 0xDC00 && c <= 0xDFFF; + } + return false; + } +}; + +//! UTF-16 little endian encoding. +template +struct UTF16LE : UTF16 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0xFEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(is.Take()); + c |= static_cast(static_cast(is.Take())) << 8; + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xFFu)); + os.Put(static_cast(0xFEu)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(static_cast(c) & 0xFFu)); + os.Put(static_cast((static_cast(c) >> 8) & 0xFFu)); + } +}; + +//! UTF-16 big endian encoding. +template +struct UTF16BE : UTF16 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0xFEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(static_cast(is.Take())) << 8; + c |= static_cast(static_cast(is.Take())); + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xFEu)); + os.Put(static_cast(0xFFu)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast((static_cast(c) >> 8) & 0xFFu)); + os.Put(static_cast(static_cast(c) & 0xFFu)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF32 + +//! UTF-32 encoding. +/*! http://en.wikipedia.org/wiki/UTF-32 + \tparam CharType Type for storing 32-bit UTF-32 data. Default is unsigned. C++11 may use char32_t instead. + \note implements Encoding concept + + \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. + For streaming, use UTF32LE and UTF32BE, which handle endianness. +*/ +template +struct UTF32 { + typedef CharType Ch; + RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 4); + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4); + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + os.Put(codepoint); + } + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4); + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + PutUnsafe(os, codepoint); + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); + Ch c = is.Take(); + *codepoint = c; + return c <= 0x10FFFF; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); + Ch c; + os.Put(c = is.Take()); + return c <= 0x10FFFF; + } +}; + +//! UTF-32 little endian enocoding. +template +struct UTF32LE : UTF32 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0x0000FEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(is.Take()); + c |= static_cast(static_cast(is.Take())) << 8; + c |= static_cast(static_cast(is.Take())) << 16; + c |= static_cast(static_cast(is.Take())) << 24; + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xFFu)); + os.Put(static_cast(0xFEu)); + os.Put(static_cast(0x00u)); + os.Put(static_cast(0x00u)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c & 0xFFu)); + os.Put(static_cast((c >> 8) & 0xFFu)); + os.Put(static_cast((c >> 16) & 0xFFu)); + os.Put(static_cast((c >> 24) & 0xFFu)); + } +}; + +//! UTF-32 big endian encoding. +template +struct UTF32BE : UTF32 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0x0000FEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(static_cast(is.Take())) << 24; + c |= static_cast(static_cast(is.Take())) << 16; + c |= static_cast(static_cast(is.Take())) << 8; + c |= static_cast(static_cast(is.Take())); + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0x00u)); + os.Put(static_cast(0x00u)); + os.Put(static_cast(0xFEu)); + os.Put(static_cast(0xFFu)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast((c >> 24) & 0xFFu)); + os.Put(static_cast((c >> 16) & 0xFFu)); + os.Put(static_cast((c >> 8) & 0xFFu)); + os.Put(static_cast(c & 0xFFu)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// ASCII + +//! ASCII encoding. +/*! http://en.wikipedia.org/wiki/ASCII + \tparam CharType Code unit for storing 7-bit ASCII data. Default is char. + \note implements Encoding concept +*/ +template +struct ASCII { + typedef CharType Ch; + + enum { supportUnicode = 0 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_ASSERT(codepoint <= 0x7F); + os.Put(static_cast(codepoint & 0xFF)); + } + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_ASSERT(codepoint <= 0x7F); + PutUnsafe(os, static_cast(codepoint & 0xFF)); + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + uint8_t c = static_cast(is.Take()); + *codepoint = c; + return c <= 0X7F; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + uint8_t c = static_cast(is.Take()); + os.Put(static_cast(c)); + return c <= 0x7F; + } + + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + uint8_t c = static_cast(Take(is)); + return static_cast(c); + } + + template + static Ch Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + return static_cast(is.Take()); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + (void)os; + } + + template + static void Put(OutputByteStream& os, Ch c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// AutoUTF + +//! Runtime-specified UTF encoding type of a stream. +enum UTFType { + kUTF8 = 0, //!< UTF-8. + kUTF16LE = 1, //!< UTF-16 little endian. + kUTF16BE = 2, //!< UTF-16 big endian. + kUTF32LE = 3, //!< UTF-32 little endian. + kUTF32BE = 4 //!< UTF-32 big endian. +}; + +//! Dynamically select encoding according to stream's runtime-specified UTF encoding type. +/*! \note This class can be used with AutoUTFInputtStream and AutoUTFOutputStream, which provides GetType(). +*/ +template +struct AutoUTF { + typedef CharType Ch; + + enum { supportUnicode = 1 }; + +#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8::x, UTF16LE::x, UTF16BE::x, UTF32LE::x, UTF32BE::x + + template + static RAPIDJSON_FORCEINLINE void Encode(OutputStream& os, unsigned codepoint) { + typedef void (*EncodeFunc)(OutputStream&, unsigned); + static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Encode) }; + (*f[os.GetType()])(os, codepoint); + } + + template + static RAPIDJSON_FORCEINLINE void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + typedef void (*EncodeFunc)(OutputStream&, unsigned); + static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(EncodeUnsafe) }; + (*f[os.GetType()])(os, codepoint); + } + + template + static RAPIDJSON_FORCEINLINE bool Decode(InputStream& is, unsigned* codepoint) { + typedef bool (*DecodeFunc)(InputStream&, unsigned*); + static const DecodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Decode) }; + return (*f[is.GetType()])(is, codepoint); + } + + template + static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) { + typedef bool (*ValidateFunc)(InputStream&, OutputStream&); + static const ValidateFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Validate) }; + return (*f[is.GetType()])(is, os); + } + +#undef RAPIDJSON_ENCODINGS_FUNC +}; + +/////////////////////////////////////////////////////////////////////////////// +// Transcoder + +//! Encoding conversion. +template +struct Transcoder { + //! Take one Unicode codepoint from source encoding, convert it to target encoding and put it to the output stream. + template + static RAPIDJSON_FORCEINLINE bool Transcode(InputStream& is, OutputStream& os) { + unsigned codepoint; + if (!SourceEncoding::Decode(is, &codepoint)) + return false; + TargetEncoding::Encode(os, codepoint); + return true; + } + + template + static RAPIDJSON_FORCEINLINE bool TranscodeUnsafe(InputStream& is, OutputStream& os) { + unsigned codepoint; + if (!SourceEncoding::Decode(is, &codepoint)) + return false; + TargetEncoding::EncodeUnsafe(os, codepoint); + return true; + } + + //! Validate one Unicode codepoint from an encoded stream. + template + static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) { + return Transcode(is, os); // Since source/target encoding is different, must transcode. + } +}; + +// Forward declaration. +template +inline void PutUnsafe(Stream& stream, typename Stream::Ch c); + +//! Specialization of Transcoder with same source and target encoding. +template +struct Transcoder { + template + static RAPIDJSON_FORCEINLINE bool Transcode(InputStream& is, OutputStream& os) { + os.Put(is.Take()); // Just copy one code unit. This semantic is different from primary template class. + return true; + } + + template + static RAPIDJSON_FORCEINLINE bool TranscodeUnsafe(InputStream& is, OutputStream& os) { + PutUnsafe(os, is.Take()); // Just copy one code unit. This semantic is different from primary template class. + return true; + } + + template + static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) { + return Encoding::Validate(is, os); // source/target encoding are the same + } +}; + +RAPIDJSON_NAMESPACE_END + +#if defined(__GNUC__) || (defined(_MSC_VER) && !defined(__clang__)) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ENCODINGS_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/error/en.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/error/en.h new file mode 100755 index 000000000..2db838bff --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/error/en.h @@ -0,0 +1,74 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ERROR_EN_H_ +#define RAPIDJSON_ERROR_EN_H_ + +#include "error.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(covered-switch-default) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Maps error code of parsing into error message. +/*! + \ingroup RAPIDJSON_ERRORS + \param parseErrorCode Error code obtained in parsing. + \return the error message. + \note User can make a copy of this function for localization. + Using switch-case is safer for future modification of error codes. +*/ +inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErrorCode) { + switch (parseErrorCode) { + case kParseErrorNone: return RAPIDJSON_ERROR_STRING("No error."); + + case kParseErrorDocumentEmpty: return RAPIDJSON_ERROR_STRING("The document is empty."); + case kParseErrorDocumentRootNotSingular: return RAPIDJSON_ERROR_STRING("The document root must not be followed by other values."); + + case kParseErrorValueInvalid: return RAPIDJSON_ERROR_STRING("Invalid value."); + + case kParseErrorObjectMissName: return RAPIDJSON_ERROR_STRING("Missing a name for object member."); + case kParseErrorObjectMissColon: return RAPIDJSON_ERROR_STRING("Missing a colon after a name of object member."); + case kParseErrorObjectMissCommaOrCurlyBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or '}' after an object member."); + + case kParseErrorArrayMissCommaOrSquareBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or ']' after an array element."); + + case kParseErrorStringUnicodeEscapeInvalidHex: return RAPIDJSON_ERROR_STRING("Incorrect hex digit after \\u escape in string."); + case kParseErrorStringUnicodeSurrogateInvalid: return RAPIDJSON_ERROR_STRING("The surrogate pair in string is invalid."); + case kParseErrorStringEscapeInvalid: return RAPIDJSON_ERROR_STRING("Invalid escape character in string."); + case kParseErrorStringMissQuotationMark: return RAPIDJSON_ERROR_STRING("Missing a closing quotation mark in string."); + case kParseErrorStringInvalidEncoding: return RAPIDJSON_ERROR_STRING("Invalid encoding in string."); + + case kParseErrorNumberTooBig: return RAPIDJSON_ERROR_STRING("Number too big to be stored in double."); + case kParseErrorNumberMissFraction: return RAPIDJSON_ERROR_STRING("Miss fraction part in number."); + case kParseErrorNumberMissExponent: return RAPIDJSON_ERROR_STRING("Miss exponent in number."); + + case kParseErrorTermination: return RAPIDJSON_ERROR_STRING("Terminate parsing due to Handler error."); + case kParseErrorUnspecificSyntaxError: return RAPIDJSON_ERROR_STRING("Unspecific syntax error."); + + default: return RAPIDJSON_ERROR_STRING("Unknown error."); + } +} + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ERROR_EN_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/error/error.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/error/error.h new file mode 100755 index 000000000..9311d2f03 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/error/error.h @@ -0,0 +1,161 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ERROR_ERROR_H_ +#define RAPIDJSON_ERROR_ERROR_H_ + +#include "../rapidjson.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +/*! \file error.h */ + +/*! \defgroup RAPIDJSON_ERRORS RapidJSON error handling */ + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_CHARTYPE + +//! Character type of error messages. +/*! \ingroup RAPIDJSON_ERRORS + The default character type is \c char. + On Windows, user can define this macro as \c TCHAR for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_CHARTYPE +#define RAPIDJSON_ERROR_CHARTYPE char +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_STRING + +//! Macro for converting string literial to \ref RAPIDJSON_ERROR_CHARTYPE[]. +/*! \ingroup RAPIDJSON_ERRORS + By default this conversion macro does nothing. + On Windows, user can define this macro as \c _T(x) for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_STRING +#define RAPIDJSON_ERROR_STRING(x) x +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// ParseErrorCode + +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericReader::Parse, GenericReader::GetParseErrorCode +*/ +enum ParseErrorCode { + kParseErrorNone = 0, //!< No error. + + kParseErrorDocumentEmpty, //!< The document is empty. + kParseErrorDocumentRootNotSingular, //!< The document root must not follow by other values. + + kParseErrorValueInvalid, //!< Invalid value. + + kParseErrorObjectMissName, //!< Missing a name for object member. + kParseErrorObjectMissColon, //!< Missing a colon after a name of object member. + kParseErrorObjectMissCommaOrCurlyBracket, //!< Missing a comma or '}' after an object member. + + kParseErrorArrayMissCommaOrSquareBracket, //!< Missing a comma or ']' after an array element. + + kParseErrorStringUnicodeEscapeInvalidHex, //!< Incorrect hex digit after \\u escape in string. + kParseErrorStringUnicodeSurrogateInvalid, //!< The surrogate pair in string is invalid. + kParseErrorStringEscapeInvalid, //!< Invalid escape character in string. + kParseErrorStringMissQuotationMark, //!< Missing a closing quotation mark in string. + kParseErrorStringInvalidEncoding, //!< Invalid encoding in string. + + kParseErrorNumberTooBig, //!< Number too big to be stored in double. + kParseErrorNumberMissFraction, //!< Miss fraction part in number. + kParseErrorNumberMissExponent, //!< Miss exponent in number. + + kParseErrorTermination, //!< Parsing was terminated. + kParseErrorUnspecificSyntaxError //!< Unspecific syntax error. +}; + +//! Result of parsing (wraps ParseErrorCode) +/*! + \ingroup RAPIDJSON_ERRORS + \code + Document doc; + ParseResult ok = doc.Parse("[42]"); + if (!ok) { + fprintf(stderr, "JSON parse error: %s (%u)", + GetParseError_En(ok.Code()), ok.Offset()); + exit(EXIT_FAILURE); + } + \endcode + \see GenericReader::Parse, GenericDocument::Parse +*/ +struct ParseResult { + //!! Unspecified boolean type + typedef bool (ParseResult::*BooleanType)() const; +public: + //! Default constructor, no error. + ParseResult() : code_(kParseErrorNone), offset_(0) {} + //! Constructor to set an error. + ParseResult(ParseErrorCode code, size_t offset) : code_(code), offset_(offset) {} + + //! Get the error code. + ParseErrorCode Code() const { return code_; } + //! Get the error offset, if \ref IsError(), 0 otherwise. + size_t Offset() const { return offset_; } + + //! Explicit conversion to \c bool, returns \c true, iff !\ref IsError(). + operator BooleanType() const { return !IsError() ? &ParseResult::IsError : NULL; } + //! Whether the result is an error. + bool IsError() const { return code_ != kParseErrorNone; } + + bool operator==(const ParseResult& that) const { return code_ == that.code_; } + bool operator==(ParseErrorCode code) const { return code_ == code; } + friend bool operator==(ParseErrorCode code, const ParseResult & err) { return code == err.code_; } + + bool operator!=(const ParseResult& that) const { return !(*this == that); } + bool operator!=(ParseErrorCode code) const { return !(*this == code); } + friend bool operator!=(ParseErrorCode code, const ParseResult & err) { return err != code; } + + //! Reset error code. + void Clear() { Set(kParseErrorNone); } + //! Update error code and offset. + void Set(ParseErrorCode code, size_t offset = 0) { code_ = code; offset_ = offset; } + +private: + ParseErrorCode code_; + size_t offset_; +}; + +//! Function pointer type of GetParseError(). +/*! \ingroup RAPIDJSON_ERRORS + + This is the prototype for \c GetParseError_X(), where \c X is a locale. + User can dynamically change locale in runtime, e.g.: +\code + GetParseErrorFunc GetParseError = GetParseError_En; // or whatever + const RAPIDJSON_ERROR_CHARTYPE* s = GetParseError(document.GetParseErrorCode()); +\endcode +*/ +typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetParseErrorFunc)(ParseErrorCode); + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ERROR_ERROR_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/filereadstream.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/filereadstream.h new file mode 100755 index 000000000..6b343707a --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/filereadstream.h @@ -0,0 +1,99 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FILEREADSTREAM_H_ +#define RAPIDJSON_FILEREADSTREAM_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(missing-noreturn) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! File byte stream for input using fread(). +/*! + \note implements Stream concept +*/ +class FileReadStream { +public: + typedef char Ch; //!< Character type (byte). + + //! Constructor. + /*! + \param fp File pointer opened for read. + \param buffer user-supplied buffer. + \param bufferSize size of buffer in bytes. Must >=4 bytes. + */ + FileReadStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferSize_(bufferSize), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { + RAPIDJSON_ASSERT(fp_ != 0); + RAPIDJSON_ASSERT(bufferSize >= 4); + Read(); + } + + Ch Peek() const { return *current_; } + Ch Take() { Ch c = *current_; Read(); return c; } + size_t Tell() const { return count_ + static_cast(current_ - buffer_); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return (current_ + 4 - !eof_ <= bufferLast_) ? current_ : 0; + } + +private: + void Read() { + if (current_ < bufferLast_) + ++current_; + else if (!eof_) { + count_ += readCount_; + readCount_ = std::fread(buffer_, 1, bufferSize_, fp_); + bufferLast_ = buffer_ + readCount_ - 1; + current_ = buffer_; + + if (readCount_ < bufferSize_) { + buffer_[readCount_] = '\0'; + ++bufferLast_; + eof_ = true; + } + } + } + + std::FILE* fp_; + Ch *buffer_; + size_t bufferSize_; + Ch *bufferLast_; + Ch *current_; + size_t readCount_; + size_t count_; //!< Number of characters read + bool eof_; +}; + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/filewritestream.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/filewritestream.h new file mode 100755 index 000000000..8b48fee19 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/filewritestream.h @@ -0,0 +1,104 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FILEWRITESTREAM_H_ +#define RAPIDJSON_FILEWRITESTREAM_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(unreachable-code) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of C file stream for output using fwrite(). +/*! + \note implements Stream concept +*/ +class FileWriteStream { +public: + typedef char Ch; //!< Character type. Only support char. + + FileWriteStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferEnd_(buffer + bufferSize), current_(buffer_) { + RAPIDJSON_ASSERT(fp_ != 0); + } + + void Put(char c) { + if (current_ >= bufferEnd_) + Flush(); + + *current_++ = c; + } + + void PutN(char c, size_t n) { + size_t avail = static_cast(bufferEnd_ - current_); + while (n > avail) { + std::memset(current_, c, avail); + current_ += avail; + Flush(); + n -= avail; + avail = static_cast(bufferEnd_ - current_); + } + + if (n > 0) { + std::memset(current_, c, n); + current_ += n; + } + } + + void Flush() { + if (current_ != buffer_) { + size_t result = std::fwrite(buffer_, 1, static_cast(current_ - buffer_), fp_); + if (result < static_cast(current_ - buffer_)) { + // failure deliberately ignored at this time + // added to avoid warn_unused_result build errors + } + current_ = buffer_; + } + } + + // Not implemented + char Peek() const { RAPIDJSON_ASSERT(false); return 0; } + char Take() { RAPIDJSON_ASSERT(false); return 0; } + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + // Prohibit copy constructor & assignment operator. + FileWriteStream(const FileWriteStream&); + FileWriteStream& operator=(const FileWriteStream&); + + std::FILE* fp_; + char *buffer_; + char *bufferEnd_; + char *current_; +}; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(FileWriteStream& stream, char c, size_t n) { + stream.PutN(c, n); +} + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/fwd.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/fwd.h new file mode 100755 index 000000000..e8104e841 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/fwd.h @@ -0,0 +1,151 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FWD_H_ +#define RAPIDJSON_FWD_H_ + +#include "rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN + +// encodings.h + +template struct UTF8; +template struct UTF16; +template struct UTF16BE; +template struct UTF16LE; +template struct UTF32; +template struct UTF32BE; +template struct UTF32LE; +template struct ASCII; +template struct AutoUTF; + +template +struct Transcoder; + +// allocators.h + +class CrtAllocator; + +template +class MemoryPoolAllocator; + +// stream.h + +template +struct GenericStringStream; + +typedef GenericStringStream > StringStream; + +template +struct GenericInsituStringStream; + +typedef GenericInsituStringStream > InsituStringStream; + +// stringbuffer.h + +template +class GenericStringBuffer; + +typedef GenericStringBuffer, CrtAllocator> StringBuffer; + +// filereadstream.h + +class FileReadStream; + +// filewritestream.h + +class FileWriteStream; + +// memorybuffer.h + +template +struct GenericMemoryBuffer; + +typedef GenericMemoryBuffer MemoryBuffer; + +// memorystream.h + +struct MemoryStream; + +// reader.h + +template +struct BaseReaderHandler; + +template +class GenericReader; + +typedef GenericReader, UTF8, CrtAllocator> Reader; + +// writer.h + +template +class Writer; + +// prettywriter.h + +template +class PrettyWriter; + +// document.h + +template +struct GenericMember; + +template +class GenericMemberIterator; + +template +struct GenericStringRef; + +template +class GenericValue; + +typedef GenericValue, MemoryPoolAllocator > Value; + +template +class GenericDocument; + +typedef GenericDocument, MemoryPoolAllocator, CrtAllocator> Document; + +// pointer.h + +template +class GenericPointer; + +typedef GenericPointer Pointer; + +// schema.h + +template +class IGenericRemoteSchemaDocumentProvider; + +template +class GenericSchemaDocument; + +typedef GenericSchemaDocument SchemaDocument; +typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProvider; + +template < + typename SchemaDocumentType, + typename OutputHandler, + typename StateAllocator> +class GenericSchemaValidator; + +typedef GenericSchemaValidator, void>, CrtAllocator> SchemaValidator; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_RAPIDJSONFWD_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/biginteger.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/biginteger.h new file mode 100755 index 000000000..a31c8a88d --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/biginteger.h @@ -0,0 +1,290 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_BIGINTEGER_H_ +#define RAPIDJSON_BIGINTEGER_H_ + +#include "../rapidjson.h" + +#if defined(_MSC_VER) && !__INTEL_COMPILER && defined(_M_AMD64) +#include // for _umul128 +#pragma intrinsic(_umul128) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +class BigInteger { +public: + typedef uint64_t Type; + + BigInteger(const BigInteger& rhs) : count_(rhs.count_) { + std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type)); + } + + explicit BigInteger(uint64_t u) : count_(1) { + digits_[0] = u; + } + + BigInteger(const char* decimals, size_t length) : count_(1) { + RAPIDJSON_ASSERT(length > 0); + digits_[0] = 0; + size_t i = 0; + const size_t kMaxDigitPerIteration = 19; // 2^64 = 18446744073709551616 > 10^19 + while (length >= kMaxDigitPerIteration) { + AppendDecimal64(decimals + i, decimals + i + kMaxDigitPerIteration); + length -= kMaxDigitPerIteration; + i += kMaxDigitPerIteration; + } + + if (length > 0) + AppendDecimal64(decimals + i, decimals + i + length); + } + + BigInteger& operator=(const BigInteger &rhs) + { + if (this != &rhs) { + count_ = rhs.count_; + std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type)); + } + return *this; + } + + BigInteger& operator=(uint64_t u) { + digits_[0] = u; + count_ = 1; + return *this; + } + + BigInteger& operator+=(uint64_t u) { + Type backup = digits_[0]; + digits_[0] += u; + for (size_t i = 0; i < count_ - 1; i++) { + if (digits_[i] >= backup) + return *this; // no carry + backup = digits_[i + 1]; + digits_[i + 1] += 1; + } + + // Last carry + if (digits_[count_ - 1] < backup) + PushBack(1); + + return *this; + } + + BigInteger& operator*=(uint64_t u) { + if (u == 0) return *this = 0; + if (u == 1) return *this; + if (*this == 1) return *this = u; + + uint64_t k = 0; + for (size_t i = 0; i < count_; i++) { + uint64_t hi; + digits_[i] = MulAdd64(digits_[i], u, k, &hi); + k = hi; + } + + if (k > 0) + PushBack(k); + + return *this; + } + + BigInteger& operator*=(uint32_t u) { + if (u == 0) return *this = 0; + if (u == 1) return *this; + if (*this == 1) return *this = u; + + uint64_t k = 0; + for (size_t i = 0; i < count_; i++) { + const uint64_t c = digits_[i] >> 32; + const uint64_t d = digits_[i] & 0xFFFFFFFF; + const uint64_t uc = u * c; + const uint64_t ud = u * d; + const uint64_t p0 = ud + k; + const uint64_t p1 = uc + (p0 >> 32); + digits_[i] = (p0 & 0xFFFFFFFF) | (p1 << 32); + k = p1 >> 32; + } + + if (k > 0) + PushBack(k); + + return *this; + } + + BigInteger& operator<<=(size_t shift) { + if (IsZero() || shift == 0) return *this; + + size_t offset = shift / kTypeBit; + size_t interShift = shift % kTypeBit; + RAPIDJSON_ASSERT(count_ + offset <= kCapacity); + + if (interShift == 0) { + std::memmove(digits_ + offset, digits_, count_ * sizeof(Type)); + count_ += offset; + } + else { + digits_[count_] = 0; + for (size_t i = count_; i > 0; i--) + digits_[i + offset] = (digits_[i] << interShift) | (digits_[i - 1] >> (kTypeBit - interShift)); + digits_[offset] = digits_[0] << interShift; + count_ += offset; + if (digits_[count_]) + count_++; + } + + std::memset(digits_, 0, offset * sizeof(Type)); + + return *this; + } + + bool operator==(const BigInteger& rhs) const { + return count_ == rhs.count_ && std::memcmp(digits_, rhs.digits_, count_ * sizeof(Type)) == 0; + } + + bool operator==(const Type rhs) const { + return count_ == 1 && digits_[0] == rhs; + } + + BigInteger& MultiplyPow5(unsigned exp) { + static const uint32_t kPow5[12] = { + 5, + 5 * 5, + 5 * 5 * 5, + 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 + }; + if (exp == 0) return *this; + for (; exp >= 27; exp -= 27) *this *= RAPIDJSON_UINT64_C2(0X6765C793, 0XFA10079D); // 5^27 + for (; exp >= 13; exp -= 13) *this *= static_cast(1220703125u); // 5^13 + if (exp > 0) *this *= kPow5[exp - 1]; + return *this; + } + + // Compute absolute difference of this and rhs. + // Assume this != rhs + bool Difference(const BigInteger& rhs, BigInteger* out) const { + int cmp = Compare(rhs); + RAPIDJSON_ASSERT(cmp != 0); + const BigInteger *a, *b; // Makes a > b + bool ret; + if (cmp < 0) { a = &rhs; b = this; ret = true; } + else { a = this; b = &rhs; ret = false; } + + Type borrow = 0; + for (size_t i = 0; i < a->count_; i++) { + Type d = a->digits_[i] - borrow; + if (i < b->count_) + d -= b->digits_[i]; + borrow = (d > a->digits_[i]) ? 1 : 0; + out->digits_[i] = d; + if (d != 0) + out->count_ = i + 1; + } + + return ret; + } + + int Compare(const BigInteger& rhs) const { + if (count_ != rhs.count_) + return count_ < rhs.count_ ? -1 : 1; + + for (size_t i = count_; i-- > 0;) + if (digits_[i] != rhs.digits_[i]) + return digits_[i] < rhs.digits_[i] ? -1 : 1; + + return 0; + } + + size_t GetCount() const { return count_; } + Type GetDigit(size_t index) const { RAPIDJSON_ASSERT(index < count_); return digits_[index]; } + bool IsZero() const { return count_ == 1 && digits_[0] == 0; } + +private: + void AppendDecimal64(const char* begin, const char* end) { + uint64_t u = ParseUint64(begin, end); + if (IsZero()) + *this = u; + else { + unsigned exp = static_cast(end - begin); + (MultiplyPow5(exp) <<= exp) += u; // *this = *this * 10^exp + u + } + } + + void PushBack(Type digit) { + RAPIDJSON_ASSERT(count_ < kCapacity); + digits_[count_++] = digit; + } + + static uint64_t ParseUint64(const char* begin, const char* end) { + uint64_t r = 0; + for (const char* p = begin; p != end; ++p) { + RAPIDJSON_ASSERT(*p >= '0' && *p <= '9'); + r = r * 10u + static_cast(*p - '0'); + } + return r; + } + + // Assume a * b + k < 2^128 + static uint64_t MulAdd64(uint64_t a, uint64_t b, uint64_t k, uint64_t* outHigh) { +#if defined(_MSC_VER) && defined(_M_AMD64) + uint64_t low = _umul128(a, b, outHigh) + k; + if (low < k) + (*outHigh)++; + return low; +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) + __extension__ typedef unsigned __int128 uint128; + uint128 p = static_cast(a) * static_cast(b); + p += k; + *outHigh = static_cast(p >> 64); + return static_cast(p); +#else + const uint64_t a0 = a & 0xFFFFFFFF, a1 = a >> 32, b0 = b & 0xFFFFFFFF, b1 = b >> 32; + uint64_t x0 = a0 * b0, x1 = a0 * b1, x2 = a1 * b0, x3 = a1 * b1; + x1 += (x0 >> 32); // can't give carry + x1 += x2; + if (x1 < x2) + x3 += (static_cast(1) << 32); + uint64_t lo = (x1 << 32) + (x0 & 0xFFFFFFFF); + uint64_t hi = x3 + (x1 >> 32); + + lo += k; + if (lo < k) + hi++; + *outHigh = hi; + return lo; +#endif + } + + static const size_t kBitCount = 3328; // 64bit * 54 > 10^1000 + static const size_t kCapacity = kBitCount / sizeof(Type); + static const size_t kTypeBit = sizeof(Type) * 8; + + Type digits_[kCapacity]; + size_t count_; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_BIGINTEGER_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/diyfp.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/diyfp.h new file mode 100755 index 000000000..b6c2cf561 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/diyfp.h @@ -0,0 +1,271 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// This is a C++ header-only implementation of Grisu2 algorithm from the publication: +// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with +// integers." ACM Sigplan Notices 45.6 (2010): 233-243. + +#ifndef RAPIDJSON_DIYFP_H_ +#define RAPIDJSON_DIYFP_H_ + +#include "../rapidjson.h" +#include + +#if defined(_MSC_VER) && defined(_M_AMD64) && !defined(__INTEL_COMPILER) +#include +#pragma intrinsic(_BitScanReverse64) +#pragma intrinsic(_umul128) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +struct DiyFp { + DiyFp() : f(), e() {} + + DiyFp(uint64_t fp, int exp) : f(fp), e(exp) {} + + explicit DiyFp(double d) { + union { + double d; + uint64_t u64; + } u = { d }; + + int biased_e = static_cast((u.u64 & kDpExponentMask) >> kDpSignificandSize); + uint64_t significand = (u.u64 & kDpSignificandMask); + if (biased_e != 0) { + f = significand + kDpHiddenBit; + e = biased_e - kDpExponentBias; + } + else { + f = significand; + e = kDpMinExponent + 1; + } + } + + DiyFp operator-(const DiyFp& rhs) const { + return DiyFp(f - rhs.f, e); + } + + DiyFp operator*(const DiyFp& rhs) const { +#if defined(_MSC_VER) && defined(_M_AMD64) + uint64_t h; + uint64_t l = _umul128(f, rhs.f, &h); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) + __extension__ typedef unsigned __int128 uint128; + uint128 p = static_cast(f) * static_cast(rhs.f); + uint64_t h = static_cast(p >> 64); + uint64_t l = static_cast(p); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#else + const uint64_t M32 = 0xFFFFFFFF; + const uint64_t a = f >> 32; + const uint64_t b = f & M32; + const uint64_t c = rhs.f >> 32; + const uint64_t d = rhs.f & M32; + const uint64_t ac = a * c; + const uint64_t bc = b * c; + const uint64_t ad = a * d; + const uint64_t bd = b * d; + uint64_t tmp = (bd >> 32) + (ad & M32) + (bc & M32); + tmp += 1U << 31; /// mult_round + return DiyFp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32), e + rhs.e + 64); +#endif + } + + DiyFp Normalize() const { + RAPIDJSON_ASSERT(f != 0); // https://stackoverflow.com/a/26809183/291737 +#if defined(_MSC_VER) && defined(_M_AMD64) + unsigned long index; + _BitScanReverse64(&index, f); + return DiyFp(f << (63 - index), e - (63 - index)); +#elif defined(__GNUC__) && __GNUC__ >= 4 + int s = __builtin_clzll(f); + return DiyFp(f << s, e - s); +#else + DiyFp res = *this; + while (!(res.f & (static_cast(1) << 63))) { + res.f <<= 1; + res.e--; + } + return res; +#endif + } + + DiyFp NormalizeBoundary() const { + DiyFp res = *this; + while (!(res.f & (kDpHiddenBit << 1))) { + res.f <<= 1; + res.e--; + } + res.f <<= (kDiySignificandSize - kDpSignificandSize - 2); + res.e = res.e - (kDiySignificandSize - kDpSignificandSize - 2); + return res; + } + + void NormalizedBoundaries(DiyFp* minus, DiyFp* plus) const { + DiyFp pl = DiyFp((f << 1) + 1, e - 1).NormalizeBoundary(); + DiyFp mi = (f == kDpHiddenBit) ? DiyFp((f << 2) - 1, e - 2) : DiyFp((f << 1) - 1, e - 1); + mi.f <<= mi.e - pl.e; + mi.e = pl.e; + *plus = pl; + *minus = mi; + } + + double ToDouble() const { + union { + double d; + uint64_t u64; + }u; + RAPIDJSON_ASSERT(f <= kDpHiddenBit + kDpSignificandMask); + if (e < kDpDenormalExponent) { + // Underflow. + return 0.0; + } + if (e >= kDpMaxExponent) { + // Overflow. + return std::numeric_limits::infinity(); + } + const uint64_t be = (e == kDpDenormalExponent && (f & kDpHiddenBit) == 0) ? 0 : + static_cast(e + kDpExponentBias); + u.u64 = (f & kDpSignificandMask) | (be << kDpSignificandSize); + return u.d; + } + + static const int kDiySignificandSize = 64; + static const int kDpSignificandSize = 52; + static const int kDpExponentBias = 0x3FF + kDpSignificandSize; + static const int kDpMaxExponent = 0x7FF - kDpExponentBias; + static const int kDpMinExponent = -kDpExponentBias; + static const int kDpDenormalExponent = -kDpExponentBias + 1; + static const uint64_t kDpExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kDpSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kDpHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); + + uint64_t f; + int e; +}; + +inline DiyFp GetCachedPowerByIndex(size_t index) { + // 10^-348, 10^-340, ..., 10^340 + static const uint64_t kCachedPowers_F[] = { + RAPIDJSON_UINT64_C2(0xfa8fd5a0, 0x081c0288), RAPIDJSON_UINT64_C2(0xbaaee17f, 0xa23ebf76), + RAPIDJSON_UINT64_C2(0x8b16fb20, 0x3055ac76), RAPIDJSON_UINT64_C2(0xcf42894a, 0x5dce35ea), + RAPIDJSON_UINT64_C2(0x9a6bb0aa, 0x55653b2d), RAPIDJSON_UINT64_C2(0xe61acf03, 0x3d1a45df), + RAPIDJSON_UINT64_C2(0xab70fe17, 0xc79ac6ca), RAPIDJSON_UINT64_C2(0xff77b1fc, 0xbebcdc4f), + RAPIDJSON_UINT64_C2(0xbe5691ef, 0x416bd60c), RAPIDJSON_UINT64_C2(0x8dd01fad, 0x907ffc3c), + RAPIDJSON_UINT64_C2(0xd3515c28, 0x31559a83), RAPIDJSON_UINT64_C2(0x9d71ac8f, 0xada6c9b5), + RAPIDJSON_UINT64_C2(0xea9c2277, 0x23ee8bcb), RAPIDJSON_UINT64_C2(0xaecc4991, 0x4078536d), + RAPIDJSON_UINT64_C2(0x823c1279, 0x5db6ce57), RAPIDJSON_UINT64_C2(0xc2109436, 0x4dfb5637), + RAPIDJSON_UINT64_C2(0x9096ea6f, 0x3848984f), RAPIDJSON_UINT64_C2(0xd77485cb, 0x25823ac7), + RAPIDJSON_UINT64_C2(0xa086cfcd, 0x97bf97f4), RAPIDJSON_UINT64_C2(0xef340a98, 0x172aace5), + RAPIDJSON_UINT64_C2(0xb23867fb, 0x2a35b28e), RAPIDJSON_UINT64_C2(0x84c8d4df, 0xd2c63f3b), + RAPIDJSON_UINT64_C2(0xc5dd4427, 0x1ad3cdba), RAPIDJSON_UINT64_C2(0x936b9fce, 0xbb25c996), + RAPIDJSON_UINT64_C2(0xdbac6c24, 0x7d62a584), RAPIDJSON_UINT64_C2(0xa3ab6658, 0x0d5fdaf6), + RAPIDJSON_UINT64_C2(0xf3e2f893, 0xdec3f126), RAPIDJSON_UINT64_C2(0xb5b5ada8, 0xaaff80b8), + RAPIDJSON_UINT64_C2(0x87625f05, 0x6c7c4a8b), RAPIDJSON_UINT64_C2(0xc9bcff60, 0x34c13053), + RAPIDJSON_UINT64_C2(0x964e858c, 0x91ba2655), RAPIDJSON_UINT64_C2(0xdff97724, 0x70297ebd), + RAPIDJSON_UINT64_C2(0xa6dfbd9f, 0xb8e5b88f), RAPIDJSON_UINT64_C2(0xf8a95fcf, 0x88747d94), + RAPIDJSON_UINT64_C2(0xb9447093, 0x8fa89bcf), RAPIDJSON_UINT64_C2(0x8a08f0f8, 0xbf0f156b), + RAPIDJSON_UINT64_C2(0xcdb02555, 0x653131b6), RAPIDJSON_UINT64_C2(0x993fe2c6, 0xd07b7fac), + RAPIDJSON_UINT64_C2(0xe45c10c4, 0x2a2b3b06), RAPIDJSON_UINT64_C2(0xaa242499, 0x697392d3), + RAPIDJSON_UINT64_C2(0xfd87b5f2, 0x8300ca0e), RAPIDJSON_UINT64_C2(0xbce50864, 0x92111aeb), + RAPIDJSON_UINT64_C2(0x8cbccc09, 0x6f5088cc), RAPIDJSON_UINT64_C2(0xd1b71758, 0xe219652c), + RAPIDJSON_UINT64_C2(0x9c400000, 0x00000000), RAPIDJSON_UINT64_C2(0xe8d4a510, 0x00000000), + RAPIDJSON_UINT64_C2(0xad78ebc5, 0xac620000), RAPIDJSON_UINT64_C2(0x813f3978, 0xf8940984), + RAPIDJSON_UINT64_C2(0xc097ce7b, 0xc90715b3), RAPIDJSON_UINT64_C2(0x8f7e32ce, 0x7bea5c70), + RAPIDJSON_UINT64_C2(0xd5d238a4, 0xabe98068), RAPIDJSON_UINT64_C2(0x9f4f2726, 0x179a2245), + RAPIDJSON_UINT64_C2(0xed63a231, 0xd4c4fb27), RAPIDJSON_UINT64_C2(0xb0de6538, 0x8cc8ada8), + RAPIDJSON_UINT64_C2(0x83c7088e, 0x1aab65db), RAPIDJSON_UINT64_C2(0xc45d1df9, 0x42711d9a), + RAPIDJSON_UINT64_C2(0x924d692c, 0xa61be758), RAPIDJSON_UINT64_C2(0xda01ee64, 0x1a708dea), + RAPIDJSON_UINT64_C2(0xa26da399, 0x9aef774a), RAPIDJSON_UINT64_C2(0xf209787b, 0xb47d6b85), + RAPIDJSON_UINT64_C2(0xb454e4a1, 0x79dd1877), RAPIDJSON_UINT64_C2(0x865b8692, 0x5b9bc5c2), + RAPIDJSON_UINT64_C2(0xc83553c5, 0xc8965d3d), RAPIDJSON_UINT64_C2(0x952ab45c, 0xfa97a0b3), + RAPIDJSON_UINT64_C2(0xde469fbd, 0x99a05fe3), RAPIDJSON_UINT64_C2(0xa59bc234, 0xdb398c25), + RAPIDJSON_UINT64_C2(0xf6c69a72, 0xa3989f5c), RAPIDJSON_UINT64_C2(0xb7dcbf53, 0x54e9bece), + RAPIDJSON_UINT64_C2(0x88fcf317, 0xf22241e2), RAPIDJSON_UINT64_C2(0xcc20ce9b, 0xd35c78a5), + RAPIDJSON_UINT64_C2(0x98165af3, 0x7b2153df), RAPIDJSON_UINT64_C2(0xe2a0b5dc, 0x971f303a), + RAPIDJSON_UINT64_C2(0xa8d9d153, 0x5ce3b396), RAPIDJSON_UINT64_C2(0xfb9b7cd9, 0xa4a7443c), + RAPIDJSON_UINT64_C2(0xbb764c4c, 0xa7a44410), RAPIDJSON_UINT64_C2(0x8bab8eef, 0xb6409c1a), + RAPIDJSON_UINT64_C2(0xd01fef10, 0xa657842c), RAPIDJSON_UINT64_C2(0x9b10a4e5, 0xe9913129), + RAPIDJSON_UINT64_C2(0xe7109bfb, 0xa19c0c9d), RAPIDJSON_UINT64_C2(0xac2820d9, 0x623bf429), + RAPIDJSON_UINT64_C2(0x80444b5e, 0x7aa7cf85), RAPIDJSON_UINT64_C2(0xbf21e440, 0x03acdd2d), + RAPIDJSON_UINT64_C2(0x8e679c2f, 0x5e44ff8f), RAPIDJSON_UINT64_C2(0xd433179d, 0x9c8cb841), + RAPIDJSON_UINT64_C2(0x9e19db92, 0xb4e31ba9), RAPIDJSON_UINT64_C2(0xeb96bf6e, 0xbadf77d9), + RAPIDJSON_UINT64_C2(0xaf87023b, 0x9bf0ee6b) + }; + static const int16_t kCachedPowers_E[] = { + -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, + -954, -927, -901, -874, -847, -821, -794, -768, -741, -715, + -688, -661, -635, -608, -582, -555, -529, -502, -475, -449, + -422, -396, -369, -343, -316, -289, -263, -236, -210, -183, + -157, -130, -103, -77, -50, -24, 3, 30, 56, 83, + 109, 136, 162, 189, 216, 242, 269, 295, 322, 348, + 375, 402, 428, 455, 481, 508, 534, 561, 588, 614, + 641, 667, 694, 720, 747, 774, 800, 827, 853, 880, + 907, 933, 960, 986, 1013, 1039, 1066 + }; + RAPIDJSON_ASSERT(index < 87); + return DiyFp(kCachedPowers_F[index], kCachedPowers_E[index]); +} + +inline DiyFp GetCachedPower(int e, int* K) { + + //int k = static_cast(ceil((-61 - e) * 0.30102999566398114)) + 374; + double dk = (-61 - e) * 0.30102999566398114 + 347; // dk must be positive, so can do ceiling in positive + int k = static_cast(dk); + if (dk - k > 0.0) + k++; + + unsigned index = static_cast((k >> 3) + 1); + *K = -(-348 + static_cast(index << 3)); // decimal exponent no need lookup table + + return GetCachedPowerByIndex(index); +} + +inline DiyFp GetCachedPower10(int exp, int *outExp) { + RAPIDJSON_ASSERT(exp >= -348); + unsigned index = static_cast(exp + 348) / 8u; + *outExp = -348 + static_cast(index) * 8; + return GetCachedPowerByIndex(index); +} + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +RAPIDJSON_DIAG_OFF(padded) +#endif + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_DIYFP_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/dtoa.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/dtoa.h new file mode 100755 index 000000000..bf2e9b2e5 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/dtoa.h @@ -0,0 +1,245 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// This is a C++ header-only implementation of Grisu2 algorithm from the publication: +// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with +// integers." ACM Sigplan Notices 45.6 (2010): 233-243. + +#ifndef RAPIDJSON_DTOA_ +#define RAPIDJSON_DTOA_ + +#include "itoa.h" // GetDigitsLut() +#include "diyfp.h" +#include "ieee754.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +RAPIDJSON_DIAG_OFF(array-bounds) // some gcc versions generate wrong warnings https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59124 +#endif + +inline void GrisuRound(char* buffer, int len, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t wp_w) { + while (rest < wp_w && delta - rest >= ten_kappa && + (rest + ten_kappa < wp_w || /// closer + wp_w - rest > rest + ten_kappa - wp_w)) { + buffer[len - 1]--; + rest += ten_kappa; + } +} + +inline int CountDecimalDigit32(uint32_t n) { + // Simple pure C++ implementation was faster than __builtin_clz version in this situation. + if (n < 10) return 1; + if (n < 100) return 2; + if (n < 1000) return 3; + if (n < 10000) return 4; + if (n < 100000) return 5; + if (n < 1000000) return 6; + if (n < 10000000) return 7; + if (n < 100000000) return 8; + // Will not reach 10 digits in DigitGen() + //if (n < 1000000000) return 9; + //return 10; + return 9; +} + +inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) { + static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + const DiyFp one(uint64_t(1) << -Mp.e, Mp.e); + const DiyFp wp_w = Mp - W; + uint32_t p1 = static_cast(Mp.f >> -one.e); + uint64_t p2 = Mp.f & (one.f - 1); + int kappa = CountDecimalDigit32(p1); // kappa in [0, 9] + *len = 0; + + while (kappa > 0) { + uint32_t d = 0; + switch (kappa) { + case 9: d = p1 / 100000000; p1 %= 100000000; break; + case 8: d = p1 / 10000000; p1 %= 10000000; break; + case 7: d = p1 / 1000000; p1 %= 1000000; break; + case 6: d = p1 / 100000; p1 %= 100000; break; + case 5: d = p1 / 10000; p1 %= 10000; break; + case 4: d = p1 / 1000; p1 %= 1000; break; + case 3: d = p1 / 100; p1 %= 100; break; + case 2: d = p1 / 10; p1 %= 10; break; + case 1: d = p1; p1 = 0; break; + default:; + } + if (d || *len) + buffer[(*len)++] = static_cast('0' + static_cast(d)); + kappa--; + uint64_t tmp = (static_cast(p1) << -one.e) + p2; + if (tmp <= delta) { + *K += kappa; + GrisuRound(buffer, *len, delta, tmp, static_cast(kPow10[kappa]) << -one.e, wp_w.f); + return; + } + } + + // kappa = 0 + for (;;) { + p2 *= 10; + delta *= 10; + char d = static_cast(p2 >> -one.e); + if (d || *len) + buffer[(*len)++] = static_cast('0' + d); + p2 &= one.f - 1; + kappa--; + if (p2 < delta) { + *K += kappa; + int index = -kappa; + GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * (index < 9 ? kPow10[index] : 0)); + return; + } + } +} + +inline void Grisu2(double value, char* buffer, int* length, int* K) { + const DiyFp v(value); + DiyFp w_m, w_p; + v.NormalizedBoundaries(&w_m, &w_p); + + const DiyFp c_mk = GetCachedPower(w_p.e, K); + const DiyFp W = v.Normalize() * c_mk; + DiyFp Wp = w_p * c_mk; + DiyFp Wm = w_m * c_mk; + Wm.f++; + Wp.f--; + DigitGen(W, Wp, Wp.f - Wm.f, buffer, length, K); +} + +inline char* WriteExponent(int K, char* buffer) { + if (K < 0) { + *buffer++ = '-'; + K = -K; + } + + if (K >= 100) { + *buffer++ = static_cast('0' + static_cast(K / 100)); + K %= 100; + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else if (K >= 10) { + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else + *buffer++ = static_cast('0' + static_cast(K)); + + return buffer; +} + +inline char* Prettify(char* buffer, int length, int k, int maxDecimalPlaces) { + const int kk = length + k; // 10^(kk-1) <= v < 10^kk + + if (0 <= k && kk <= 21) { + // 1234e7 -> 12340000000 + for (int i = length; i < kk; i++) + buffer[i] = '0'; + buffer[kk] = '.'; + buffer[kk + 1] = '0'; + return &buffer[kk + 2]; + } + else if (0 < kk && kk <= 21) { + // 1234e-2 -> 12.34 + std::memmove(&buffer[kk + 1], &buffer[kk], static_cast(length - kk)); + buffer[kk] = '.'; + if (0 > k + maxDecimalPlaces) { + // When maxDecimalPlaces = 2, 1.2345 -> 1.23, 1.102 -> 1.1 + // Remove extra trailing zeros (at least one) after truncation. + for (int i = kk + maxDecimalPlaces; i > kk + 1; i--) + if (buffer[i] != '0') + return &buffer[i + 1]; + return &buffer[kk + 2]; // Reserve one zero + } + else + return &buffer[length + 1]; + } + else if (-6 < kk && kk <= 0) { + // 1234e-6 -> 0.001234 + const int offset = 2 - kk; + std::memmove(&buffer[offset], &buffer[0], static_cast(length)); + buffer[0] = '0'; + buffer[1] = '.'; + for (int i = 2; i < offset; i++) + buffer[i] = '0'; + if (length - kk > maxDecimalPlaces) { + // When maxDecimalPlaces = 2, 0.123 -> 0.12, 0.102 -> 0.1 + // Remove extra trailing zeros (at least one) after truncation. + for (int i = maxDecimalPlaces + 1; i > 2; i--) + if (buffer[i] != '0') + return &buffer[i + 1]; + return &buffer[3]; // Reserve one zero + } + else + return &buffer[length + offset]; + } + else if (kk < -maxDecimalPlaces) { + // Truncate to zero + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + return &buffer[3]; + } + else if (length == 1) { + // 1e30 + buffer[1] = 'e'; + return WriteExponent(kk - 1, &buffer[2]); + } + else { + // 1234e30 -> 1.234e33 + std::memmove(&buffer[2], &buffer[1], static_cast(length - 1)); + buffer[1] = '.'; + buffer[length + 1] = 'e'; + return WriteExponent(kk - 1, &buffer[0 + length + 2]); + } +} + +inline char* dtoa(double value, char* buffer, int maxDecimalPlaces = 324) { + RAPIDJSON_ASSERT(maxDecimalPlaces >= 1); + Double d(value); + if (d.IsZero()) { + if (d.Sign()) + *buffer++ = '-'; // -0.0, Issue #289 + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + return &buffer[3]; + } + else { + if (value < 0) { + *buffer++ = '-'; + value = -value; + } + int length, K; + Grisu2(value, buffer, &length, &K); + return Prettify(buffer, length, K, maxDecimalPlaces); + } +} + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_DTOA_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/ieee754.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/ieee754.h new file mode 100755 index 000000000..c2684ba2a --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/ieee754.h @@ -0,0 +1,78 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_IEEE754_ +#define RAPIDJSON_IEEE754_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +class Double { +public: + Double() {} + Double(double d) : d_(d) {} + Double(uint64_t u) : u_(u) {} + + double Value() const { return d_; } + uint64_t Uint64Value() const { return u_; } + + double NextPositiveDouble() const { + RAPIDJSON_ASSERT(!Sign()); + return Double(u_ + 1).Value(); + } + + bool Sign() const { return (u_ & kSignMask) != 0; } + uint64_t Significand() const { return u_ & kSignificandMask; } + int Exponent() const { return static_cast(((u_ & kExponentMask) >> kSignificandSize) - kExponentBias); } + + bool IsNan() const { return (u_ & kExponentMask) == kExponentMask && Significand() != 0; } + bool IsInf() const { return (u_ & kExponentMask) == kExponentMask && Significand() == 0; } + bool IsNanOrInf() const { return (u_ & kExponentMask) == kExponentMask; } + bool IsNormal() const { return (u_ & kExponentMask) != 0 || Significand() == 0; } + bool IsZero() const { return (u_ & (kExponentMask | kSignificandMask)) == 0; } + + uint64_t IntegerSignificand() const { return IsNormal() ? Significand() | kHiddenBit : Significand(); } + int IntegerExponent() const { return (IsNormal() ? Exponent() : kDenormalExponent) - kSignificandSize; } + uint64_t ToBias() const { return (u_ & kSignMask) ? ~u_ + 1 : u_ | kSignMask; } + + static int EffectiveSignificandSize(int order) { + if (order >= -1021) + return 53; + else if (order <= -1074) + return 0; + else + return order + 1074; + } + +private: + static const int kSignificandSize = 52; + static const int kExponentBias = 0x3FF; + static const int kDenormalExponent = 1 - kExponentBias; + static const uint64_t kSignMask = RAPIDJSON_UINT64_C2(0x80000000, 0x00000000); + static const uint64_t kExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); + + union { + double d_; + uint64_t u_; + }; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_IEEE754_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/itoa.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/itoa.h new file mode 100755 index 000000000..9b1c45cc1 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/itoa.h @@ -0,0 +1,308 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ITOA_ +#define RAPIDJSON_ITOA_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline const char* GetDigitsLut() { + static const char cDigitsLut[200] = { + '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', + '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', + '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', + '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9', + '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9', + '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9', + '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9', + '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9', + '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9', + '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' + }; + return cDigitsLut; +} + +inline char* u32toa(uint32_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + + const char* cDigitsLut = GetDigitsLut(); + + if (value < 10000) { + const uint32_t d1 = (value / 100) << 1; + const uint32_t d2 = (value % 100) << 1; + + if (value >= 1000) + *buffer++ = cDigitsLut[d1]; + if (value >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else if (value < 100000000) { + // value = bbbbcccc + const uint32_t b = value / 10000; + const uint32_t c = value % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + else { + // value = aabbbbcccc in decimal + + const uint32_t a = value / 100000000; // 1 to 42 + value %= 100000000; + + if (a >= 10) { + const unsigned i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else + *buffer++ = static_cast('0' + static_cast(a)); + + const uint32_t b = value / 10000; // 0 to 9999 + const uint32_t c = value % 10000; // 0 to 9999 + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + return buffer; +} + +inline char* i32toa(int32_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + uint32_t u = static_cast(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u32toa(u, buffer); +} + +inline char* u64toa(uint64_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + const char* cDigitsLut = GetDigitsLut(); + const uint64_t kTen8 = 100000000; + const uint64_t kTen9 = kTen8 * 10; + const uint64_t kTen10 = kTen8 * 100; + const uint64_t kTen11 = kTen8 * 1000; + const uint64_t kTen12 = kTen8 * 10000; + const uint64_t kTen13 = kTen8 * 100000; + const uint64_t kTen14 = kTen8 * 1000000; + const uint64_t kTen15 = kTen8 * 10000000; + const uint64_t kTen16 = kTen8 * kTen8; + + if (value < kTen8) { + uint32_t v = static_cast(value); + if (v < 10000) { + const uint32_t d1 = (v / 100) << 1; + const uint32_t d2 = (v % 100) << 1; + + if (v >= 1000) + *buffer++ = cDigitsLut[d1]; + if (v >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (v >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else { + // value = bbbbcccc + const uint32_t b = v / 10000; + const uint32_t c = v % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + } + else if (value < kTen16) { + const uint32_t v0 = static_cast(value / kTen8); + const uint32_t v1 = static_cast(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + if (value >= kTen15) + *buffer++ = cDigitsLut[d1]; + if (value >= kTen14) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= kTen13) + *buffer++ = cDigitsLut[d2]; + if (value >= kTen12) + *buffer++ = cDigitsLut[d2 + 1]; + if (value >= kTen11) + *buffer++ = cDigitsLut[d3]; + if (value >= kTen10) + *buffer++ = cDigitsLut[d3 + 1]; + if (value >= kTen9) + *buffer++ = cDigitsLut[d4]; + + *buffer++ = cDigitsLut[d4 + 1]; + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + else { + const uint32_t a = static_cast(value / kTen16); // 1 to 1844 + value %= kTen16; + + if (a < 10) + *buffer++ = static_cast('0' + static_cast(a)); + else if (a < 100) { + const uint32_t i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else if (a < 1000) { + *buffer++ = static_cast('0' + static_cast(a / 100)); + + const uint32_t i = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else { + const uint32_t i = (a / 100) << 1; + const uint32_t j = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + *buffer++ = cDigitsLut[j]; + *buffer++ = cDigitsLut[j + 1]; + } + + const uint32_t v0 = static_cast(value / kTen8); + const uint32_t v1 = static_cast(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + + return buffer; +} + +inline char* i64toa(int64_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + uint64_t u = static_cast(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u64toa(u, buffer); +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ITOA_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/meta.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/meta.h new file mode 100755 index 000000000..d401edf85 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/meta.h @@ -0,0 +1,186 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_META_H_ +#define RAPIDJSON_INTERNAL_META_H_ + +#include "../rapidjson.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(6334) +#endif + +#if RAPIDJSON_HAS_CXX11_TYPETRAITS +#include +#endif + +//@cond RAPIDJSON_INTERNAL +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +// Helper to wrap/convert arbitrary types to void, useful for arbitrary type matching +template struct Void { typedef void Type; }; + +/////////////////////////////////////////////////////////////////////////////// +// BoolType, TrueType, FalseType +// +template struct BoolType { + static const bool Value = Cond; + typedef BoolType Type; +}; +typedef BoolType TrueType; +typedef BoolType FalseType; + + +/////////////////////////////////////////////////////////////////////////////// +// SelectIf, BoolExpr, NotExpr, AndExpr, OrExpr +// + +template struct SelectIfImpl { template struct Apply { typedef T1 Type; }; }; +template <> struct SelectIfImpl { template struct Apply { typedef T2 Type; }; }; +template struct SelectIfCond : SelectIfImpl::template Apply {}; +template struct SelectIf : SelectIfCond {}; + +template struct AndExprCond : FalseType {}; +template <> struct AndExprCond : TrueType {}; +template struct OrExprCond : TrueType {}; +template <> struct OrExprCond : FalseType {}; + +template struct BoolExpr : SelectIf::Type {}; +template struct NotExpr : SelectIf::Type {}; +template struct AndExpr : AndExprCond::Type {}; +template struct OrExpr : OrExprCond::Type {}; + + +/////////////////////////////////////////////////////////////////////////////// +// AddConst, MaybeAddConst, RemoveConst +template struct AddConst { typedef const T Type; }; +template struct MaybeAddConst : SelectIfCond {}; +template struct RemoveConst { typedef T Type; }; +template struct RemoveConst { typedef T Type; }; + + +/////////////////////////////////////////////////////////////////////////////// +// IsSame, IsConst, IsMoreConst, IsPointer +// +template struct IsSame : FalseType {}; +template struct IsSame : TrueType {}; + +template struct IsConst : FalseType {}; +template struct IsConst : TrueType {}; + +template +struct IsMoreConst + : AndExpr::Type, typename RemoveConst::Type>, + BoolType::Value >= IsConst::Value> >::Type {}; + +template struct IsPointer : FalseType {}; +template struct IsPointer : TrueType {}; + +/////////////////////////////////////////////////////////////////////////////// +// IsBaseOf +// +#if RAPIDJSON_HAS_CXX11_TYPETRAITS + +template struct IsBaseOf + : BoolType< ::std::is_base_of::value> {}; + +#else // simplified version adopted from Boost + +template struct IsBaseOfImpl { + RAPIDJSON_STATIC_ASSERT(sizeof(B) != 0); + RAPIDJSON_STATIC_ASSERT(sizeof(D) != 0); + + typedef char (&Yes)[1]; + typedef char (&No) [2]; + + template + static Yes Check(const D*, T); + static No Check(const B*, int); + + struct Host { + operator const B*() const; + operator const D*(); + }; + + enum { Value = (sizeof(Check(Host(), 0)) == sizeof(Yes)) }; +}; + +template struct IsBaseOf + : OrExpr, BoolExpr > >::Type {}; + +#endif // RAPIDJSON_HAS_CXX11_TYPETRAITS + + +////////////////////////////////////////////////////////////////////////// +// EnableIf / DisableIf +// +template struct EnableIfCond { typedef T Type; }; +template struct EnableIfCond { /* empty */ }; + +template struct DisableIfCond { typedef T Type; }; +template struct DisableIfCond { /* empty */ }; + +template +struct EnableIf : EnableIfCond {}; + +template +struct DisableIf : DisableIfCond {}; + +// SFINAE helpers +struct SfinaeTag {}; +template struct RemoveSfinaeTag; +template struct RemoveSfinaeTag { typedef T Type; }; + +#define RAPIDJSON_REMOVEFPTR_(type) \ + typename ::RAPIDJSON_NAMESPACE::internal::RemoveSfinaeTag \ + < ::RAPIDJSON_NAMESPACE::internal::SfinaeTag&(*) type>::Type + +#define RAPIDJSON_ENABLEIF(cond) \ + typename ::RAPIDJSON_NAMESPACE::internal::EnableIf \ + ::Type * = NULL + +#define RAPIDJSON_DISABLEIF(cond) \ + typename ::RAPIDJSON_NAMESPACE::internal::DisableIf \ + ::Type * = NULL + +#define RAPIDJSON_ENABLEIF_RETURN(cond,returntype) \ + typename ::RAPIDJSON_NAMESPACE::internal::EnableIf \ + ::Type + +#define RAPIDJSON_DISABLEIF_RETURN(cond,returntype) \ + typename ::RAPIDJSON_NAMESPACE::internal::DisableIf \ + ::Type + +} // namespace internal +RAPIDJSON_NAMESPACE_END +//@endcond + +#if defined(_MSC_VER) && !defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_META_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/pow10.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/pow10.h new file mode 100755 index 000000000..02f475d70 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/pow10.h @@ -0,0 +1,55 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_POW10_ +#define RAPIDJSON_POW10_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Computes integer powers of 10 in double (10.0^n). +/*! This function uses lookup table for fast and accurate results. + \param n non-negative exponent. Must <= 308. + \return 10.0^n +*/ +inline double Pow10(int n) { + static const double e[] = { // 1e-0...1e308: 309 * 8 bytes = 2472 bytes + 1e+0, + 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, + 1e+21, 1e+22, 1e+23, 1e+24, 1e+25, 1e+26, 1e+27, 1e+28, 1e+29, 1e+30, 1e+31, 1e+32, 1e+33, 1e+34, 1e+35, 1e+36, 1e+37, 1e+38, 1e+39, 1e+40, + 1e+41, 1e+42, 1e+43, 1e+44, 1e+45, 1e+46, 1e+47, 1e+48, 1e+49, 1e+50, 1e+51, 1e+52, 1e+53, 1e+54, 1e+55, 1e+56, 1e+57, 1e+58, 1e+59, 1e+60, + 1e+61, 1e+62, 1e+63, 1e+64, 1e+65, 1e+66, 1e+67, 1e+68, 1e+69, 1e+70, 1e+71, 1e+72, 1e+73, 1e+74, 1e+75, 1e+76, 1e+77, 1e+78, 1e+79, 1e+80, + 1e+81, 1e+82, 1e+83, 1e+84, 1e+85, 1e+86, 1e+87, 1e+88, 1e+89, 1e+90, 1e+91, 1e+92, 1e+93, 1e+94, 1e+95, 1e+96, 1e+97, 1e+98, 1e+99, 1e+100, + 1e+101,1e+102,1e+103,1e+104,1e+105,1e+106,1e+107,1e+108,1e+109,1e+110,1e+111,1e+112,1e+113,1e+114,1e+115,1e+116,1e+117,1e+118,1e+119,1e+120, + 1e+121,1e+122,1e+123,1e+124,1e+125,1e+126,1e+127,1e+128,1e+129,1e+130,1e+131,1e+132,1e+133,1e+134,1e+135,1e+136,1e+137,1e+138,1e+139,1e+140, + 1e+141,1e+142,1e+143,1e+144,1e+145,1e+146,1e+147,1e+148,1e+149,1e+150,1e+151,1e+152,1e+153,1e+154,1e+155,1e+156,1e+157,1e+158,1e+159,1e+160, + 1e+161,1e+162,1e+163,1e+164,1e+165,1e+166,1e+167,1e+168,1e+169,1e+170,1e+171,1e+172,1e+173,1e+174,1e+175,1e+176,1e+177,1e+178,1e+179,1e+180, + 1e+181,1e+182,1e+183,1e+184,1e+185,1e+186,1e+187,1e+188,1e+189,1e+190,1e+191,1e+192,1e+193,1e+194,1e+195,1e+196,1e+197,1e+198,1e+199,1e+200, + 1e+201,1e+202,1e+203,1e+204,1e+205,1e+206,1e+207,1e+208,1e+209,1e+210,1e+211,1e+212,1e+213,1e+214,1e+215,1e+216,1e+217,1e+218,1e+219,1e+220, + 1e+221,1e+222,1e+223,1e+224,1e+225,1e+226,1e+227,1e+228,1e+229,1e+230,1e+231,1e+232,1e+233,1e+234,1e+235,1e+236,1e+237,1e+238,1e+239,1e+240, + 1e+241,1e+242,1e+243,1e+244,1e+245,1e+246,1e+247,1e+248,1e+249,1e+250,1e+251,1e+252,1e+253,1e+254,1e+255,1e+256,1e+257,1e+258,1e+259,1e+260, + 1e+261,1e+262,1e+263,1e+264,1e+265,1e+266,1e+267,1e+268,1e+269,1e+270,1e+271,1e+272,1e+273,1e+274,1e+275,1e+276,1e+277,1e+278,1e+279,1e+280, + 1e+281,1e+282,1e+283,1e+284,1e+285,1e+286,1e+287,1e+288,1e+289,1e+290,1e+291,1e+292,1e+293,1e+294,1e+295,1e+296,1e+297,1e+298,1e+299,1e+300, + 1e+301,1e+302,1e+303,1e+304,1e+305,1e+306,1e+307,1e+308 + }; + RAPIDJSON_ASSERT(n >= 0 && n <= 308); + return e[n]; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_POW10_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/regex.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/regex.h new file mode 100755 index 000000000..16e355921 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/regex.h @@ -0,0 +1,740 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_REGEX_H_ +#define RAPIDJSON_INTERNAL_REGEX_H_ + +#include "../allocators.h" +#include "../stream.h" +#include "stack.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(implicit-fallthrough) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#if __GNUC__ >= 7 +RAPIDJSON_DIAG_OFF(implicit-fallthrough) +#endif +#endif + +#ifndef RAPIDJSON_REGEX_VERBOSE +#define RAPIDJSON_REGEX_VERBOSE 0 +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +/////////////////////////////////////////////////////////////////////////////// +// DecodedStream + +template +class DecodedStream { +public: + DecodedStream(SourceStream& ss) : ss_(ss), codepoint_() { Decode(); } + unsigned Peek() { return codepoint_; } + unsigned Take() { + unsigned c = codepoint_; + if (c) // No further decoding when '\0' + Decode(); + return c; + } + +private: + void Decode() { + if (!Encoding::Decode(ss_, &codepoint_)) + codepoint_ = 0; + } + + SourceStream& ss_; + unsigned codepoint_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericRegex + +static const SizeType kRegexInvalidState = ~SizeType(0); //!< Represents an invalid index in GenericRegex::State::out, out1 +static const SizeType kRegexInvalidRange = ~SizeType(0); + +template +class GenericRegexSearch; + +//! Regular expression engine with subset of ECMAscript grammar. +/*! + Supported regular expression syntax: + - \c ab Concatenation + - \c a|b Alternation + - \c a? Zero or one + - \c a* Zero or more + - \c a+ One or more + - \c a{3} Exactly 3 times + - \c a{3,} At least 3 times + - \c a{3,5} 3 to 5 times + - \c (ab) Grouping + - \c ^a At the beginning + - \c a$ At the end + - \c . Any character + - \c [abc] Character classes + - \c [a-c] Character class range + - \c [a-z0-9_] Character class combination + - \c [^abc] Negated character classes + - \c [^a-c] Negated character class range + - \c [\b] Backspace (U+0008) + - \c \\| \\\\ ... Escape characters + - \c \\f Form feed (U+000C) + - \c \\n Line feed (U+000A) + - \c \\r Carriage return (U+000D) + - \c \\t Tab (U+0009) + - \c \\v Vertical tab (U+000B) + + \note This is a Thompson NFA engine, implemented with reference to + Cox, Russ. "Regular Expression Matching Can Be Simple And Fast (but is slow in Java, Perl, PHP, Python, Ruby,...).", + https://swtch.com/~rsc/regexp/regexp1.html +*/ +template +class GenericRegex { +public: + typedef Encoding EncodingType; + typedef typename Encoding::Ch Ch; + template friend class GenericRegexSearch; + + GenericRegex(const Ch* source, Allocator* allocator = 0) : + ownAllocator_(allocator ? 0 : RAPIDJSON_NEW(Allocator)()), allocator_(allocator ? allocator : ownAllocator_), + states_(allocator_, 256), ranges_(allocator_, 256), root_(kRegexInvalidState), stateCount_(), rangeCount_(), + anchorBegin_(), anchorEnd_() + { + GenericStringStream ss(source); + DecodedStream, Encoding> ds(ss); + Parse(ds); + } + + ~GenericRegex() + { + RAPIDJSON_DELETE(ownAllocator_); + } + + bool IsValid() const { + return root_ != kRegexInvalidState; + } + +private: + enum Operator { + kZeroOrOne, + kZeroOrMore, + kOneOrMore, + kConcatenation, + kAlternation, + kLeftParenthesis + }; + + static const unsigned kAnyCharacterClass = 0xFFFFFFFF; //!< For '.' + static const unsigned kRangeCharacterClass = 0xFFFFFFFE; + static const unsigned kRangeNegationFlag = 0x80000000; + + struct Range { + unsigned start; // + unsigned end; + SizeType next; + }; + + struct State { + SizeType out; //!< Equals to kInvalid for matching state + SizeType out1; //!< Equals to non-kInvalid for split + SizeType rangeStart; + unsigned codepoint; + }; + + struct Frag { + Frag(SizeType s, SizeType o, SizeType m) : start(s), out(o), minIndex(m) {} + SizeType start; + SizeType out; //!< link-list of all output states + SizeType minIndex; + }; + + State& GetState(SizeType index) { + RAPIDJSON_ASSERT(index < stateCount_); + return states_.template Bottom()[index]; + } + + const State& GetState(SizeType index) const { + RAPIDJSON_ASSERT(index < stateCount_); + return states_.template Bottom()[index]; + } + + Range& GetRange(SizeType index) { + RAPIDJSON_ASSERT(index < rangeCount_); + return ranges_.template Bottom()[index]; + } + + const Range& GetRange(SizeType index) const { + RAPIDJSON_ASSERT(index < rangeCount_); + return ranges_.template Bottom()[index]; + } + + template + void Parse(DecodedStream& ds) { + Stack operandStack(allocator_, 256); // Frag + Stack operatorStack(allocator_, 256); // Operator + Stack atomCountStack(allocator_, 256); // unsigned (Atom per parenthesis) + + *atomCountStack.template Push() = 0; + + unsigned codepoint; + while (ds.Peek() != 0) { + switch (codepoint = ds.Take()) { + case '^': + anchorBegin_ = true; + break; + + case '$': + anchorEnd_ = true; + break; + + case '|': + while (!operatorStack.Empty() && *operatorStack.template Top() < kAlternation) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + *operatorStack.template Push() = kAlternation; + *atomCountStack.template Top() = 0; + break; + + case '(': + *operatorStack.template Push() = kLeftParenthesis; + *atomCountStack.template Push() = 0; + break; + + case ')': + while (!operatorStack.Empty() && *operatorStack.template Top() != kLeftParenthesis) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + if (operatorStack.Empty()) + return; + operatorStack.template Pop(1); + atomCountStack.template Pop(1); + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '?': + if (!Eval(operandStack, kZeroOrOne)) + return; + break; + + case '*': + if (!Eval(operandStack, kZeroOrMore)) + return; + break; + + case '+': + if (!Eval(operandStack, kOneOrMore)) + return; + break; + + case '{': + { + unsigned n, m; + if (!ParseUnsigned(ds, &n)) + return; + + if (ds.Peek() == ',') { + ds.Take(); + if (ds.Peek() == '}') + m = kInfinityQuantifier; + else if (!ParseUnsigned(ds, &m) || m < n) + return; + } + else + m = n; + + if (!EvalQuantifier(operandStack, n, m) || ds.Peek() != '}') + return; + ds.Take(); + } + break; + + case '.': + PushOperand(operandStack, kAnyCharacterClass); + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '[': + { + SizeType range; + if (!ParseRange(ds, &range)) + return; + SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, kRangeCharacterClass); + GetState(s).rangeStart = range; + *operandStack.template Push() = Frag(s, s, s); + } + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '\\': // Escape character + if (!CharacterEscape(ds, &codepoint)) + return; // Unsupported escape character + // fall through to default + + default: // Pattern character + PushOperand(operandStack, codepoint); + ImplicitConcatenation(atomCountStack, operatorStack); + } + } + + while (!operatorStack.Empty()) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + + // Link the operand to matching state. + if (operandStack.GetSize() == sizeof(Frag)) { + Frag* e = operandStack.template Pop(1); + Patch(e->out, NewState(kRegexInvalidState, kRegexInvalidState, 0)); + root_ = e->start; + +#if RAPIDJSON_REGEX_VERBOSE + printf("root: %d\n", root_); + for (SizeType i = 0; i < stateCount_ ; i++) { + State& s = GetState(i); + printf("[%2d] out: %2d out1: %2d c: '%c'\n", i, s.out, s.out1, (char)s.codepoint); + } + printf("\n"); +#endif + } + } + + SizeType NewState(SizeType out, SizeType out1, unsigned codepoint) { + State* s = states_.template Push(); + s->out = out; + s->out1 = out1; + s->codepoint = codepoint; + s->rangeStart = kRegexInvalidRange; + return stateCount_++; + } + + void PushOperand(Stack& operandStack, unsigned codepoint) { + SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, codepoint); + *operandStack.template Push() = Frag(s, s, s); + } + + void ImplicitConcatenation(Stack& atomCountStack, Stack& operatorStack) { + if (*atomCountStack.template Top()) + *operatorStack.template Push() = kConcatenation; + (*atomCountStack.template Top())++; + } + + SizeType Append(SizeType l1, SizeType l2) { + SizeType old = l1; + while (GetState(l1).out != kRegexInvalidState) + l1 = GetState(l1).out; + GetState(l1).out = l2; + return old; + } + + void Patch(SizeType l, SizeType s) { + for (SizeType next; l != kRegexInvalidState; l = next) { + next = GetState(l).out; + GetState(l).out = s; + } + } + + bool Eval(Stack& operandStack, Operator op) { + switch (op) { + case kConcatenation: + RAPIDJSON_ASSERT(operandStack.GetSize() >= sizeof(Frag) * 2); + { + Frag e2 = *operandStack.template Pop(1); + Frag e1 = *operandStack.template Pop(1); + Patch(e1.out, e2.start); + *operandStack.template Push() = Frag(e1.start, e2.out, Min(e1.minIndex, e2.minIndex)); + } + return true; + + case kAlternation: + if (operandStack.GetSize() >= sizeof(Frag) * 2) { + Frag e2 = *operandStack.template Pop(1); + Frag e1 = *operandStack.template Pop(1); + SizeType s = NewState(e1.start, e2.start, 0); + *operandStack.template Push() = Frag(s, Append(e1.out, e2.out), Min(e1.minIndex, e2.minIndex)); + return true; + } + return false; + + case kZeroOrOne: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + *operandStack.template Push() = Frag(s, Append(e.out, s), e.minIndex); + return true; + } + return false; + + case kZeroOrMore: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + Patch(e.out, s); + *operandStack.template Push() = Frag(s, s, e.minIndex); + return true; + } + return false; + + case kOneOrMore: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + Patch(e.out, s); + *operandStack.template Push() = Frag(e.start, s, e.minIndex); + return true; + } + return false; + + default: + // syntax error (e.g. unclosed kLeftParenthesis) + return false; + } + } + + bool EvalQuantifier(Stack& operandStack, unsigned n, unsigned m) { + RAPIDJSON_ASSERT(n <= m); + RAPIDJSON_ASSERT(operandStack.GetSize() >= sizeof(Frag)); + + if (n == 0) { + if (m == 0) // a{0} not support + return false; + else if (m == kInfinityQuantifier) + Eval(operandStack, kZeroOrMore); // a{0,} -> a* + else { + Eval(operandStack, kZeroOrOne); // a{0,5} -> a? + for (unsigned i = 0; i < m - 1; i++) + CloneTopOperand(operandStack); // a{0,5} -> a? a? a? a? a? + for (unsigned i = 0; i < m - 1; i++) + Eval(operandStack, kConcatenation); // a{0,5} -> a?a?a?a?a? + } + return true; + } + + for (unsigned i = 0; i < n - 1; i++) // a{3} -> a a a + CloneTopOperand(operandStack); + + if (m == kInfinityQuantifier) + Eval(operandStack, kOneOrMore); // a{3,} -> a a a+ + else if (m > n) { + CloneTopOperand(operandStack); // a{3,5} -> a a a a + Eval(operandStack, kZeroOrOne); // a{3,5} -> a a a a? + for (unsigned i = n; i < m - 1; i++) + CloneTopOperand(operandStack); // a{3,5} -> a a a a? a? + for (unsigned i = n; i < m; i++) + Eval(operandStack, kConcatenation); // a{3,5} -> a a aa?a? + } + + for (unsigned i = 0; i < n - 1; i++) + Eval(operandStack, kConcatenation); // a{3} -> aaa, a{3,} -> aaa+, a{3.5} -> aaaa?a? + + return true; + } + + static SizeType Min(SizeType a, SizeType b) { return a < b ? a : b; } + + void CloneTopOperand(Stack& operandStack) { + const Frag src = *operandStack.template Top(); // Copy constructor to prevent invalidation + SizeType count = stateCount_ - src.minIndex; // Assumes top operand contains states in [src->minIndex, stateCount_) + State* s = states_.template Push(count); + memcpy(s, &GetState(src.minIndex), count * sizeof(State)); + for (SizeType j = 0; j < count; j++) { + if (s[j].out != kRegexInvalidState) + s[j].out += count; + if (s[j].out1 != kRegexInvalidState) + s[j].out1 += count; + } + *operandStack.template Push() = Frag(src.start + count, src.out + count, src.minIndex + count); + stateCount_ += count; + } + + template + bool ParseUnsigned(DecodedStream& ds, unsigned* u) { + unsigned r = 0; + if (ds.Peek() < '0' || ds.Peek() > '9') + return false; + while (ds.Peek() >= '0' && ds.Peek() <= '9') { + if (r >= 429496729 && ds.Peek() > '5') // 2^32 - 1 = 4294967295 + return false; // overflow + r = r * 10 + (ds.Take() - '0'); + } + *u = r; + return true; + } + + template + bool ParseRange(DecodedStream& ds, SizeType* range) { + bool isBegin = true; + bool negate = false; + int step = 0; + SizeType start = kRegexInvalidRange; + SizeType current = kRegexInvalidRange; + unsigned codepoint; + while ((codepoint = ds.Take()) != 0) { + if (isBegin) { + isBegin = false; + if (codepoint == '^') { + negate = true; + continue; + } + } + + switch (codepoint) { + case ']': + if (start == kRegexInvalidRange) + return false; // Error: nothing inside [] + if (step == 2) { // Add trailing '-' + SizeType r = NewRange('-'); + RAPIDJSON_ASSERT(current != kRegexInvalidRange); + GetRange(current).next = r; + } + if (negate) + GetRange(start).start |= kRangeNegationFlag; + *range = start; + return true; + + case '\\': + if (ds.Peek() == 'b') { + ds.Take(); + codepoint = 0x0008; // Escape backspace character + } + else if (!CharacterEscape(ds, &codepoint)) + return false; + // fall through to default + + default: + switch (step) { + case 1: + if (codepoint == '-') { + step++; + break; + } + // fall through to step 0 for other characters + + case 0: + { + SizeType r = NewRange(codepoint); + if (current != kRegexInvalidRange) + GetRange(current).next = r; + if (start == kRegexInvalidRange) + start = r; + current = r; + } + step = 1; + break; + + default: + RAPIDJSON_ASSERT(step == 2); + GetRange(current).end = codepoint; + step = 0; + } + } + } + return false; + } + + SizeType NewRange(unsigned codepoint) { + Range* r = ranges_.template Push(); + r->start = r->end = codepoint; + r->next = kRegexInvalidRange; + return rangeCount_++; + } + + template + bool CharacterEscape(DecodedStream& ds, unsigned* escapedCodepoint) { + unsigned codepoint; + switch (codepoint = ds.Take()) { + case '^': + case '$': + case '|': + case '(': + case ')': + case '?': + case '*': + case '+': + case '.': + case '[': + case ']': + case '{': + case '}': + case '\\': + *escapedCodepoint = codepoint; return true; + case 'f': *escapedCodepoint = 0x000C; return true; + case 'n': *escapedCodepoint = 0x000A; return true; + case 'r': *escapedCodepoint = 0x000D; return true; + case 't': *escapedCodepoint = 0x0009; return true; + case 'v': *escapedCodepoint = 0x000B; return true; + default: + return false; // Unsupported escape character + } + } + + Allocator* ownAllocator_; + Allocator* allocator_; + Stack states_; + Stack ranges_; + SizeType root_; + SizeType stateCount_; + SizeType rangeCount_; + + static const unsigned kInfinityQuantifier = ~0u; + + // For SearchWithAnchoring() + bool anchorBegin_; + bool anchorEnd_; +}; + +template +class GenericRegexSearch { +public: + typedef typename RegexType::EncodingType Encoding; + typedef typename Encoding::Ch Ch; + + GenericRegexSearch(const RegexType& regex, Allocator* allocator = 0) : + regex_(regex), allocator_(allocator), ownAllocator_(0), + state0_(allocator, 0), state1_(allocator, 0), stateSet_() + { + RAPIDJSON_ASSERT(regex_.IsValid()); + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + stateSet_ = static_cast(allocator_->Malloc(GetStateSetSize())); + state0_.template Reserve(regex_.stateCount_); + state1_.template Reserve(regex_.stateCount_); + } + + ~GenericRegexSearch() { + Allocator::Free(stateSet_); + RAPIDJSON_DELETE(ownAllocator_); + } + + template + bool Match(InputStream& is) { + return SearchWithAnchoring(is, true, true); + } + + bool Match(const Ch* s) { + GenericStringStream is(s); + return Match(is); + } + + template + bool Search(InputStream& is) { + return SearchWithAnchoring(is, regex_.anchorBegin_, regex_.anchorEnd_); + } + + bool Search(const Ch* s) { + GenericStringStream is(s); + return Search(is); + } + +private: + typedef typename RegexType::State State; + typedef typename RegexType::Range Range; + + template + bool SearchWithAnchoring(InputStream& is, bool anchorBegin, bool anchorEnd) { + DecodedStream ds(is); + + state0_.Clear(); + Stack *current = &state0_, *next = &state1_; + const size_t stateSetSize = GetStateSetSize(); + std::memset(stateSet_, 0, stateSetSize); + + bool matched = AddState(*current, regex_.root_); + unsigned codepoint; + while (!current->Empty() && (codepoint = ds.Take()) != 0) { + std::memset(stateSet_, 0, stateSetSize); + next->Clear(); + matched = false; + for (const SizeType* s = current->template Bottom(); s != current->template End(); ++s) { + const State& sr = regex_.GetState(*s); + if (sr.codepoint == codepoint || + sr.codepoint == RegexType::kAnyCharacterClass || + (sr.codepoint == RegexType::kRangeCharacterClass && MatchRange(sr.rangeStart, codepoint))) + { + matched = AddState(*next, sr.out) || matched; + if (!anchorEnd && matched) + return true; + } + if (!anchorBegin) + AddState(*next, regex_.root_); + } + internal::Swap(current, next); + } + + return matched; + } + + size_t GetStateSetSize() const { + return (regex_.stateCount_ + 31) / 32 * 4; + } + + // Return whether the added states is a match state + bool AddState(Stack& l, SizeType index) { + RAPIDJSON_ASSERT(index != kRegexInvalidState); + + const State& s = regex_.GetState(index); + if (s.out1 != kRegexInvalidState) { // Split + bool matched = AddState(l, s.out); + return AddState(l, s.out1) || matched; + } + else if (!(stateSet_[index >> 5] & (1u << (index & 31)))) { + stateSet_[index >> 5] |= (1u << (index & 31)); + *l.template PushUnsafe() = index; + } + return s.out == kRegexInvalidState; // by using PushUnsafe() above, we can ensure s is not validated due to reallocation. + } + + bool MatchRange(SizeType rangeIndex, unsigned codepoint) const { + bool yes = (regex_.GetRange(rangeIndex).start & RegexType::kRangeNegationFlag) == 0; + while (rangeIndex != kRegexInvalidRange) { + const Range& r = regex_.GetRange(rangeIndex); + if (codepoint >= (r.start & ~RegexType::kRangeNegationFlag) && codepoint <= r.end) + return yes; + rangeIndex = r.next; + } + return !yes; + } + + const RegexType& regex_; + Allocator* allocator_; + Allocator* ownAllocator_; + Stack state0_; + Stack state1_; + uint32_t* stateSet_; +}; + +typedef GenericRegex > Regex; +typedef GenericRegexSearch RegexSearch; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_REGEX_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/stack.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/stack.h new file mode 100755 index 000000000..45dca6a8b --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/stack.h @@ -0,0 +1,232 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_STACK_H_ +#define RAPIDJSON_INTERNAL_STACK_H_ + +#include "../allocators.h" +#include "swap.h" +#include + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +/////////////////////////////////////////////////////////////////////////////// +// Stack + +//! A type-unsafe stack for storing different types of data. +/*! \tparam Allocator Allocator for allocating stack memory. +*/ +template +class Stack { +public: + // Optimization note: Do not allocate memory for stack_ in constructor. + // Do it lazily when first Push() -> Expand() -> Resize(). + Stack(Allocator* allocator, size_t stackCapacity) : allocator_(allocator), ownAllocator_(0), stack_(0), stackTop_(0), stackEnd_(0), initialCapacity_(stackCapacity) { + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Stack(Stack&& rhs) + : allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + stack_(rhs.stack_), + stackTop_(rhs.stackTop_), + stackEnd_(rhs.stackEnd_), + initialCapacity_(rhs.initialCapacity_) + { + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.stack_ = 0; + rhs.stackTop_ = 0; + rhs.stackEnd_ = 0; + rhs.initialCapacity_ = 0; + } +#endif + + ~Stack() { + Destroy(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Stack& operator=(Stack&& rhs) { + if (&rhs != this) + { + Destroy(); + + allocator_ = rhs.allocator_; + ownAllocator_ = rhs.ownAllocator_; + stack_ = rhs.stack_; + stackTop_ = rhs.stackTop_; + stackEnd_ = rhs.stackEnd_; + initialCapacity_ = rhs.initialCapacity_; + + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.stack_ = 0; + rhs.stackTop_ = 0; + rhs.stackEnd_ = 0; + rhs.initialCapacity_ = 0; + } + return *this; + } +#endif + + void Swap(Stack& rhs) RAPIDJSON_NOEXCEPT { + internal::Swap(allocator_, rhs.allocator_); + internal::Swap(ownAllocator_, rhs.ownAllocator_); + internal::Swap(stack_, rhs.stack_); + internal::Swap(stackTop_, rhs.stackTop_); + internal::Swap(stackEnd_, rhs.stackEnd_); + internal::Swap(initialCapacity_, rhs.initialCapacity_); + } + + void Clear() { stackTop_ = stack_; } + + void ShrinkToFit() { + if (Empty()) { + // If the stack is empty, completely deallocate the memory. + Allocator::Free(stack_); // NOLINT (+clang-analyzer-unix.Malloc) + stack_ = 0; + stackTop_ = 0; + stackEnd_ = 0; + } + else + Resize(GetSize()); + } + + // Optimization note: try to minimize the size of this function for force inline. + // Expansion is run very infrequently, so it is moved to another (probably non-inline) function. + template + RAPIDJSON_FORCEINLINE void Reserve(size_t count = 1) { + // Expand the stack if needed + if (RAPIDJSON_UNLIKELY(static_cast(sizeof(T) * count) > (stackEnd_ - stackTop_))) + Expand(count); + } + + template + RAPIDJSON_FORCEINLINE T* Push(size_t count = 1) { + Reserve(count); + return PushUnsafe(count); + } + + template + RAPIDJSON_FORCEINLINE T* PushUnsafe(size_t count = 1) { + RAPIDJSON_ASSERT(stackTop_); + RAPIDJSON_ASSERT(static_cast(sizeof(T) * count) <= (stackEnd_ - stackTop_)); + T* ret = reinterpret_cast(stackTop_); + stackTop_ += sizeof(T) * count; + return ret; + } + + template + T* Pop(size_t count) { + RAPIDJSON_ASSERT(GetSize() >= count * sizeof(T)); + stackTop_ -= count * sizeof(T); + return reinterpret_cast(stackTop_); + } + + template + T* Top() { + RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); + return reinterpret_cast(stackTop_ - sizeof(T)); + } + + template + const T* Top() const { + RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); + return reinterpret_cast(stackTop_ - sizeof(T)); + } + + template + T* End() { return reinterpret_cast(stackTop_); } + + template + const T* End() const { return reinterpret_cast(stackTop_); } + + template + T* Bottom() { return reinterpret_cast(stack_); } + + template + const T* Bottom() const { return reinterpret_cast(stack_); } + + bool HasAllocator() const { + return allocator_ != 0; + } + + Allocator& GetAllocator() { + RAPIDJSON_ASSERT(allocator_); + return *allocator_; + } + + bool Empty() const { return stackTop_ == stack_; } + size_t GetSize() const { return static_cast(stackTop_ - stack_); } + size_t GetCapacity() const { return static_cast(stackEnd_ - stack_); } + +private: + template + void Expand(size_t count) { + // Only expand the capacity if the current stack exists. Otherwise just create a stack with initial capacity. + size_t newCapacity; + if (stack_ == 0) { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + newCapacity = initialCapacity_; + } else { + newCapacity = GetCapacity(); + newCapacity += (newCapacity + 1) / 2; + } + size_t newSize = GetSize() + sizeof(T) * count; + if (newCapacity < newSize) + newCapacity = newSize; + + Resize(newCapacity); + } + + void Resize(size_t newCapacity) { + const size_t size = GetSize(); // Backup the current size + stack_ = static_cast(allocator_->Realloc(stack_, GetCapacity(), newCapacity)); + stackTop_ = stack_ + size; + stackEnd_ = stack_ + newCapacity; + } + + void Destroy() { + Allocator::Free(stack_); + RAPIDJSON_DELETE(ownAllocator_); // Only delete if it is owned by the stack + } + + // Prohibit copy constructor & assignment operator. + Stack(const Stack&); + Stack& operator=(const Stack&); + + Allocator* allocator_; + Allocator* ownAllocator_; + char *stack_; + char *stackTop_; + char *stackEnd_; + size_t initialCapacity_; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_STACK_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/strfunc.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/strfunc.h new file mode 100755 index 000000000..226439a76 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/strfunc.h @@ -0,0 +1,69 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_STRFUNC_H_ +#define RAPIDJSON_INTERNAL_STRFUNC_H_ + +#include "../stream.h" +#include + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Custom strlen() which works on different character types. +/*! \tparam Ch Character type (e.g. char, wchar_t, short) + \param s Null-terminated input string. + \return Number of characters in the string. + \note This has the same semantics as strlen(), the return value is not number of Unicode codepoints. +*/ +template +inline SizeType StrLen(const Ch* s) { + RAPIDJSON_ASSERT(s != 0); + const Ch* p = s; + while (*p) ++p; + return SizeType(p - s); +} + +template <> +inline SizeType StrLen(const char* s) { + return SizeType(std::strlen(s)); +} + +template <> +inline SizeType StrLen(const wchar_t* s) { + return SizeType(std::wcslen(s)); +} + +//! Returns number of code points in a encoded string. +template +bool CountStringCodePoint(const typename Encoding::Ch* s, SizeType length, SizeType* outCount) { + RAPIDJSON_ASSERT(s != 0); + RAPIDJSON_ASSERT(outCount != 0); + GenericStringStream is(s); + const typename Encoding::Ch* end = s + length; + SizeType count = 0; + while (is.src_ < end) { + unsigned codepoint; + if (!Encoding::Decode(is, &codepoint)) + return false; + count++; + } + *outCount = count; + return true; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_INTERNAL_STRFUNC_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/strtod.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/strtod.h new file mode 100755 index 000000000..dfca22b65 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/strtod.h @@ -0,0 +1,290 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_STRTOD_ +#define RAPIDJSON_STRTOD_ + +#include "ieee754.h" +#include "biginteger.h" +#include "diyfp.h" +#include "pow10.h" +#include +#include + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline double FastPath(double significand, int exp) { + if (exp < -308) + return 0.0; + else if (exp >= 0) + return significand * internal::Pow10(exp); + else + return significand / internal::Pow10(-exp); +} + +inline double StrtodNormalPrecision(double d, int p) { + if (p < -308) { + // Prevent expSum < -308, making Pow10(p) = 0 + d = FastPath(d, -308); + d = FastPath(d, p + 308); + } + else + d = FastPath(d, p); + return d; +} + +template +inline T Min3(T a, T b, T c) { + T m = a; + if (m > b) m = b; + if (m > c) m = c; + return m; +} + +inline int CheckWithinHalfULP(double b, const BigInteger& d, int dExp) { + const Double db(b); + const uint64_t bInt = db.IntegerSignificand(); + const int bExp = db.IntegerExponent(); + const int hExp = bExp - 1; + + int dS_Exp2 = 0, dS_Exp5 = 0, bS_Exp2 = 0, bS_Exp5 = 0, hS_Exp2 = 0, hS_Exp5 = 0; + + // Adjust for decimal exponent + if (dExp >= 0) { + dS_Exp2 += dExp; + dS_Exp5 += dExp; + } + else { + bS_Exp2 -= dExp; + bS_Exp5 -= dExp; + hS_Exp2 -= dExp; + hS_Exp5 -= dExp; + } + + // Adjust for binary exponent + if (bExp >= 0) + bS_Exp2 += bExp; + else { + dS_Exp2 -= bExp; + hS_Exp2 -= bExp; + } + + // Adjust for half ulp exponent + if (hExp >= 0) + hS_Exp2 += hExp; + else { + dS_Exp2 -= hExp; + bS_Exp2 -= hExp; + } + + // Remove common power of two factor from all three scaled values + int common_Exp2 = Min3(dS_Exp2, bS_Exp2, hS_Exp2); + dS_Exp2 -= common_Exp2; + bS_Exp2 -= common_Exp2; + hS_Exp2 -= common_Exp2; + + BigInteger dS = d; + dS.MultiplyPow5(static_cast(dS_Exp5)) <<= static_cast(dS_Exp2); + + BigInteger bS(bInt); + bS.MultiplyPow5(static_cast(bS_Exp5)) <<= static_cast(bS_Exp2); + + BigInteger hS(1); + hS.MultiplyPow5(static_cast(hS_Exp5)) <<= static_cast(hS_Exp2); + + BigInteger delta(0); + dS.Difference(bS, &delta); + + return delta.Compare(hS); +} + +inline bool StrtodFast(double d, int p, double* result) { + // Use fast path for string-to-double conversion if possible + // see http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ + if (p > 22 && p < 22 + 16) { + // Fast Path Cases In Disguise + d *= internal::Pow10(p - 22); + p = 22; + } + + if (p >= -22 && p <= 22 && d <= 9007199254740991.0) { // 2^53 - 1 + *result = FastPath(d, p); + return true; + } + else + return false; +} + +// Compute an approximation and see if it is within 1/2 ULP +inline bool StrtodDiyFp(const char* decimals, int dLen, int dExp, double* result) { + uint64_t significand = 0; + int i = 0; // 2^64 - 1 = 18446744073709551615, 1844674407370955161 = 0x1999999999999999 + for (; i < dLen; i++) { + if (significand > RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || + (significand == RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) && decimals[i] > '5')) + break; + significand = significand * 10u + static_cast(decimals[i] - '0'); + } + + if (i < dLen && decimals[i] >= '5') // Rounding + significand++; + + int remaining = dLen - i; + const int kUlpShift = 3; + const int kUlp = 1 << kUlpShift; + int64_t error = (remaining == 0) ? 0 : kUlp / 2; + + DiyFp v(significand, 0); + v = v.Normalize(); + error <<= -v.e; + + dExp += remaining; + + int actualExp; + DiyFp cachedPower = GetCachedPower10(dExp, &actualExp); + if (actualExp != dExp) { + static const DiyFp kPow10[] = { + DiyFp(RAPIDJSON_UINT64_C2(0xa0000000, 0x00000000), -60), // 10^1 + DiyFp(RAPIDJSON_UINT64_C2(0xc8000000, 0x00000000), -57), // 10^2 + DiyFp(RAPIDJSON_UINT64_C2(0xfa000000, 0x00000000), -54), // 10^3 + DiyFp(RAPIDJSON_UINT64_C2(0x9c400000, 0x00000000), -50), // 10^4 + DiyFp(RAPIDJSON_UINT64_C2(0xc3500000, 0x00000000), -47), // 10^5 + DiyFp(RAPIDJSON_UINT64_C2(0xf4240000, 0x00000000), -44), // 10^6 + DiyFp(RAPIDJSON_UINT64_C2(0x98968000, 0x00000000), -40) // 10^7 + }; + int adjustment = dExp - actualExp; + RAPIDJSON_ASSERT(adjustment >= 1 && adjustment < 8); + v = v * kPow10[adjustment - 1]; + if (dLen + adjustment > 19) // has more digits than decimal digits in 64-bit + error += kUlp / 2; + } + + v = v * cachedPower; + + error += kUlp + (error == 0 ? 0 : 1); + + const int oldExp = v.e; + v = v.Normalize(); + error <<= oldExp - v.e; + + const int effectiveSignificandSize = Double::EffectiveSignificandSize(64 + v.e); + int precisionSize = 64 - effectiveSignificandSize; + if (precisionSize + kUlpShift >= 64) { + int scaleExp = (precisionSize + kUlpShift) - 63; + v.f >>= scaleExp; + v.e += scaleExp; + error = (error >> scaleExp) + 1 + kUlp; + precisionSize -= scaleExp; + } + + DiyFp rounded(v.f >> precisionSize, v.e + precisionSize); + const uint64_t precisionBits = (v.f & ((uint64_t(1) << precisionSize) - 1)) * kUlp; + const uint64_t halfWay = (uint64_t(1) << (precisionSize - 1)) * kUlp; + if (precisionBits >= halfWay + static_cast(error)) { + rounded.f++; + if (rounded.f & (DiyFp::kDpHiddenBit << 1)) { // rounding overflows mantissa (issue #340) + rounded.f >>= 1; + rounded.e++; + } + } + + *result = rounded.ToDouble(); + + return halfWay - static_cast(error) >= precisionBits || precisionBits >= halfWay + static_cast(error); +} + +inline double StrtodBigInteger(double approx, const char* decimals, int dLen, int dExp) { + RAPIDJSON_ASSERT(dLen >= 0); + const BigInteger dInt(decimals, static_cast(dLen)); + Double a(approx); + int cmp = CheckWithinHalfULP(a.Value(), dInt, dExp); + if (cmp < 0) + return a.Value(); // within half ULP + else if (cmp == 0) { + // Round towards even + if (a.Significand() & 1) + return a.NextPositiveDouble(); + else + return a.Value(); + } + else // adjustment + return a.NextPositiveDouble(); +} + +inline double StrtodFullPrecision(double d, int p, const char* decimals, size_t length, size_t decimalPosition, int exp) { + RAPIDJSON_ASSERT(d >= 0.0); + RAPIDJSON_ASSERT(length >= 1); + + double result = 0.0; + if (StrtodFast(d, p, &result)) + return result; + + RAPIDJSON_ASSERT(length <= INT_MAX); + int dLen = static_cast(length); + + RAPIDJSON_ASSERT(length >= decimalPosition); + RAPIDJSON_ASSERT(length - decimalPosition <= INT_MAX); + int dExpAdjust = static_cast(length - decimalPosition); + + RAPIDJSON_ASSERT(exp >= INT_MIN + dExpAdjust); + int dExp = exp - dExpAdjust; + + // Make sure length+dExp does not overflow + RAPIDJSON_ASSERT(dExp <= INT_MAX - dLen); + + // Trim leading zeros + while (dLen > 0 && *decimals == '0') { + dLen--; + decimals++; + } + + // Trim trailing zeros + while (dLen > 0 && decimals[dLen - 1] == '0') { + dLen--; + dExp++; + } + + if (dLen == 0) { // Buffer only contains zeros. + return 0.0; + } + + // Trim right-most digits + const int kMaxDecimalDigit = 767 + 1; + if (dLen > kMaxDecimalDigit) { + dExp += dLen - kMaxDecimalDigit; + dLen = kMaxDecimalDigit; + } + + // If too small, underflow to zero. + // Any x <= 10^-324 is interpreted as zero. + if (dLen + dExp <= -324) + return 0.0; + + // If too large, overflow to infinity. + // Any x >= 10^309 is interpreted as +infinity. + if (dLen + dExp > 309) + return std::numeric_limits::infinity(); + + if (StrtodDiyFp(decimals, dLen, dExp, &result)) + return result; + + // Use approximation from StrtodDiyFp and make adjustment with BigInteger comparison + return StrtodBigInteger(result, decimals, dLen, dExp); +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_STRTOD_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/swap.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/swap.h new file mode 100755 index 000000000..666e49f97 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/internal/swap.h @@ -0,0 +1,46 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_SWAP_H_ +#define RAPIDJSON_INTERNAL_SWAP_H_ + +#include "../rapidjson.h" + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Custom swap() to avoid dependency on C++ header +/*! \tparam T Type of the arguments to swap, should be instantiated with primitive C++ types only. + \note This has the same semantics as std::swap(). +*/ +template +inline void Swap(T& a, T& b) RAPIDJSON_NOEXCEPT { + T tmp = a; + a = b; + b = tmp; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_SWAP_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/istreamwrapper.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/istreamwrapper.h new file mode 100755 index 000000000..c4950b9dc --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/istreamwrapper.h @@ -0,0 +1,128 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ISTREAMWRAPPER_H_ +#define RAPIDJSON_ISTREAMWRAPPER_H_ + +#include "stream.h" +#include +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4351) // new behavior: elements of array 'array' will be default initialized +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of \c std::basic_istream into RapidJSON's Stream concept. +/*! + The classes can be wrapped including but not limited to: + + - \c std::istringstream + - \c std::stringstream + - \c std::wistringstream + - \c std::wstringstream + - \c std::ifstream + - \c std::fstream + - \c std::wifstream + - \c std::wfstream + + \tparam StreamType Class derived from \c std::basic_istream. +*/ + +template +class BasicIStreamWrapper { +public: + typedef typename StreamType::char_type Ch; + + //! Constructor. + /*! + \param stream stream opened for read. + */ + BasicIStreamWrapper(StreamType &stream) : stream_(stream), buffer_(peekBuffer_), bufferSize_(4), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { + Read(); + } + + //! Constructor. + /*! + \param stream stream opened for read. + \param buffer user-supplied buffer. + \param bufferSize size of buffer in bytes. Must >=4 bytes. + */ + BasicIStreamWrapper(StreamType &stream, char* buffer, size_t bufferSize) : stream_(stream), buffer_(buffer), bufferSize_(bufferSize), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { + RAPIDJSON_ASSERT(bufferSize >= 4); + Read(); + } + + Ch Peek() const { return *current_; } + Ch Take() { Ch c = *current_; Read(); return c; } + size_t Tell() const { return count_ + static_cast(current_ - buffer_); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return (current_ + 4 - !eof_ <= bufferLast_) ? current_ : 0; + } + +private: + BasicIStreamWrapper(); + BasicIStreamWrapper(const BasicIStreamWrapper&); + BasicIStreamWrapper& operator=(const BasicIStreamWrapper&); + + void Read() { + if (current_ < bufferLast_) + ++current_; + else if (!eof_) { + count_ += readCount_; + readCount_ = bufferSize_; + bufferLast_ = buffer_ + readCount_ - 1; + current_ = buffer_; + + if (!stream_.read(buffer_, static_cast(bufferSize_))) { + readCount_ = static_cast(stream_.gcount()); + *(bufferLast_ = buffer_ + readCount_) = '\0'; + eof_ = true; + } + } + } + + StreamType &stream_; + Ch peekBuffer_[4], *buffer_; + size_t bufferSize_; + Ch *bufferLast_; + Ch *current_; + size_t readCount_; + size_t count_; //!< Number of characters read + bool eof_; +}; + +typedef BasicIStreamWrapper IStreamWrapper; +typedef BasicIStreamWrapper WIStreamWrapper; + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ISTREAMWRAPPER_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/memorybuffer.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/memorybuffer.h new file mode 100755 index 000000000..39bee1dec --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/memorybuffer.h @@ -0,0 +1,70 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_MEMORYBUFFER_H_ +#define RAPIDJSON_MEMORYBUFFER_H_ + +#include "stream.h" +#include "internal/stack.h" + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory output byte stream. +/*! + This class is mainly for being wrapped by EncodedOutputStream or AutoUTFOutputStream. + + It is similar to FileWriteBuffer but the destination is an in-memory buffer instead of a file. + + Differences between MemoryBuffer and StringBuffer: + 1. StringBuffer has Encoding but MemoryBuffer is only a byte buffer. + 2. StringBuffer::GetString() returns a null-terminated string. MemoryBuffer::GetBuffer() returns a buffer without terminator. + + \tparam Allocator type for allocating memory buffer. + \note implements Stream concept +*/ +template +struct GenericMemoryBuffer { + typedef char Ch; // byte + + GenericMemoryBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} + + void Put(Ch c) { *stack_.template Push() = c; } + void Flush() {} + + void Clear() { stack_.Clear(); } + void ShrinkToFit() { stack_.ShrinkToFit(); } + Ch* Push(size_t count) { return stack_.template Push(count); } + void Pop(size_t count) { stack_.template Pop(count); } + + const Ch* GetBuffer() const { + return stack_.template Bottom(); + } + + size_t GetSize() const { return stack_.GetSize(); } + + static const size_t kDefaultCapacity = 256; + mutable internal::Stack stack_; +}; + +typedef GenericMemoryBuffer<> MemoryBuffer; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(MemoryBuffer& memoryBuffer, char c, size_t n) { + std::memset(memoryBuffer.stack_.Push(n), c, n * sizeof(c)); +} + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_MEMORYBUFFER_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/memorystream.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/memorystream.h new file mode 100755 index 000000000..1d71d8a4f --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/memorystream.h @@ -0,0 +1,71 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_MEMORYSTREAM_H_ +#define RAPIDJSON_MEMORYSTREAM_H_ + +#include "stream.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(missing-noreturn) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory input byte stream. +/*! + This class is mainly for being wrapped by EncodedInputStream or AutoUTFInputStream. + + It is similar to FileReadBuffer but the source is an in-memory buffer instead of a file. + + Differences between MemoryStream and StringStream: + 1. StringStream has encoding but MemoryStream is a byte stream. + 2. MemoryStream needs size of the source buffer and the buffer don't need to be null terminated. StringStream assume null-terminated string as source. + 3. MemoryStream supports Peek4() for encoding detection. StringStream is specified with an encoding so it should not have Peek4(). + \note implements Stream concept +*/ +struct MemoryStream { + typedef char Ch; // byte + + MemoryStream(const Ch *src, size_t size) : src_(src), begin_(src), end_(src + size), size_(size) {} + + Ch Peek() const { return RAPIDJSON_UNLIKELY(src_ == end_) ? '\0' : *src_; } + Ch Take() { return RAPIDJSON_UNLIKELY(src_ == end_) ? '\0' : *src_++; } + size_t Tell() const { return static_cast(src_ - begin_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return Tell() + 4 <= size_ ? src_ : 0; + } + + const Ch* src_; //!< Current read position. + const Ch* begin_; //!< Original head of the string. + const Ch* end_; //!< End of stream. + size_t size_; //!< Size of the stream. +}; + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_MEMORYBUFFER_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/msinttypes/inttypes.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/msinttypes/inttypes.h new file mode 100755 index 000000000..18111286b --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/msinttypes/inttypes.h @@ -0,0 +1,316 @@ +// ISO C9x compliant inttypes.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +// The above software in this distribution may have been modified by +// THL A29 Limited ("Tencent Modifications"). +// All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_INTTYPES_H_ // [ +#define _MSC_INTTYPES_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include "stdint.h" + +// miloyip: VC supports inttypes.h since VC2013 +#if _MSC_VER >= 1800 +#include +#else + +// 7.8 Format conversion of integer types + +typedef struct { + intmax_t quot; + intmax_t rem; +} imaxdiv_t; + +// 7.8.1 Macros for format specifiers + +#if !defined(__cplusplus) || defined(__STDC_FORMAT_MACROS) // [ See footnote 185 at page 198 + +// The fprintf macros for signed integers are: +#define PRId8 "d" +#define PRIi8 "i" +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" + +#define PRId16 "hd" +#define PRIi16 "hi" +#define PRIdLEAST16 "hd" +#define PRIiLEAST16 "hi" +#define PRIdFAST16 "hd" +#define PRIiFAST16 "hi" + +#define PRId32 "I32d" +#define PRIi32 "I32i" +#define PRIdLEAST32 "I32d" +#define PRIiLEAST32 "I32i" +#define PRIdFAST32 "I32d" +#define PRIiFAST32 "I32i" + +#define PRId64 "I64d" +#define PRIi64 "I64i" +#define PRIdLEAST64 "I64d" +#define PRIiLEAST64 "I64i" +#define PRIdFAST64 "I64d" +#define PRIiFAST64 "I64i" + +#define PRIdMAX "I64d" +#define PRIiMAX "I64i" + +#define PRIdPTR "Id" +#define PRIiPTR "Ii" + +// The fprintf macros for unsigned integers are: +#define PRIo8 "o" +#define PRIu8 "u" +#define PRIx8 "x" +#define PRIX8 "X" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" + +#define PRIo16 "ho" +#define PRIu16 "hu" +#define PRIx16 "hx" +#define PRIX16 "hX" +#define PRIoLEAST16 "ho" +#define PRIuLEAST16 "hu" +#define PRIxLEAST16 "hx" +#define PRIXLEAST16 "hX" +#define PRIoFAST16 "ho" +#define PRIuFAST16 "hu" +#define PRIxFAST16 "hx" +#define PRIXFAST16 "hX" + +#define PRIo32 "I32o" +#define PRIu32 "I32u" +#define PRIx32 "I32x" +#define PRIX32 "I32X" +#define PRIoLEAST32 "I32o" +#define PRIuLEAST32 "I32u" +#define PRIxLEAST32 "I32x" +#define PRIXLEAST32 "I32X" +#define PRIoFAST32 "I32o" +#define PRIuFAST32 "I32u" +#define PRIxFAST32 "I32x" +#define PRIXFAST32 "I32X" + +#define PRIo64 "I64o" +#define PRIu64 "I64u" +#define PRIx64 "I64x" +#define PRIX64 "I64X" +#define PRIoLEAST64 "I64o" +#define PRIuLEAST64 "I64u" +#define PRIxLEAST64 "I64x" +#define PRIXLEAST64 "I64X" +#define PRIoFAST64 "I64o" +#define PRIuFAST64 "I64u" +#define PRIxFAST64 "I64x" +#define PRIXFAST64 "I64X" + +#define PRIoMAX "I64o" +#define PRIuMAX "I64u" +#define PRIxMAX "I64x" +#define PRIXMAX "I64X" + +#define PRIoPTR "Io" +#define PRIuPTR "Iu" +#define PRIxPTR "Ix" +#define PRIXPTR "IX" + +// The fscanf macros for signed integers are: +#define SCNd8 "d" +#define SCNi8 "i" +#define SCNdLEAST8 "d" +#define SCNiLEAST8 "i" +#define SCNdFAST8 "d" +#define SCNiFAST8 "i" + +#define SCNd16 "hd" +#define SCNi16 "hi" +#define SCNdLEAST16 "hd" +#define SCNiLEAST16 "hi" +#define SCNdFAST16 "hd" +#define SCNiFAST16 "hi" + +#define SCNd32 "ld" +#define SCNi32 "li" +#define SCNdLEAST32 "ld" +#define SCNiLEAST32 "li" +#define SCNdFAST32 "ld" +#define SCNiFAST32 "li" + +#define SCNd64 "I64d" +#define SCNi64 "I64i" +#define SCNdLEAST64 "I64d" +#define SCNiLEAST64 "I64i" +#define SCNdFAST64 "I64d" +#define SCNiFAST64 "I64i" + +#define SCNdMAX "I64d" +#define SCNiMAX "I64i" + +#ifdef _WIN64 // [ +# define SCNdPTR "I64d" +# define SCNiPTR "I64i" +#else // _WIN64 ][ +# define SCNdPTR "ld" +# define SCNiPTR "li" +#endif // _WIN64 ] + +// The fscanf macros for unsigned integers are: +#define SCNo8 "o" +#define SCNu8 "u" +#define SCNx8 "x" +#define SCNX8 "X" +#define SCNoLEAST8 "o" +#define SCNuLEAST8 "u" +#define SCNxLEAST8 "x" +#define SCNXLEAST8 "X" +#define SCNoFAST8 "o" +#define SCNuFAST8 "u" +#define SCNxFAST8 "x" +#define SCNXFAST8 "X" + +#define SCNo16 "ho" +#define SCNu16 "hu" +#define SCNx16 "hx" +#define SCNX16 "hX" +#define SCNoLEAST16 "ho" +#define SCNuLEAST16 "hu" +#define SCNxLEAST16 "hx" +#define SCNXLEAST16 "hX" +#define SCNoFAST16 "ho" +#define SCNuFAST16 "hu" +#define SCNxFAST16 "hx" +#define SCNXFAST16 "hX" + +#define SCNo32 "lo" +#define SCNu32 "lu" +#define SCNx32 "lx" +#define SCNX32 "lX" +#define SCNoLEAST32 "lo" +#define SCNuLEAST32 "lu" +#define SCNxLEAST32 "lx" +#define SCNXLEAST32 "lX" +#define SCNoFAST32 "lo" +#define SCNuFAST32 "lu" +#define SCNxFAST32 "lx" +#define SCNXFAST32 "lX" + +#define SCNo64 "I64o" +#define SCNu64 "I64u" +#define SCNx64 "I64x" +#define SCNX64 "I64X" +#define SCNoLEAST64 "I64o" +#define SCNuLEAST64 "I64u" +#define SCNxLEAST64 "I64x" +#define SCNXLEAST64 "I64X" +#define SCNoFAST64 "I64o" +#define SCNuFAST64 "I64u" +#define SCNxFAST64 "I64x" +#define SCNXFAST64 "I64X" + +#define SCNoMAX "I64o" +#define SCNuMAX "I64u" +#define SCNxMAX "I64x" +#define SCNXMAX "I64X" + +#ifdef _WIN64 // [ +# define SCNoPTR "I64o" +# define SCNuPTR "I64u" +# define SCNxPTR "I64x" +# define SCNXPTR "I64X" +#else // _WIN64 ][ +# define SCNoPTR "lo" +# define SCNuPTR "lu" +# define SCNxPTR "lx" +# define SCNXPTR "lX" +#endif // _WIN64 ] + +#endif // __STDC_FORMAT_MACROS ] + +// 7.8.2 Functions for greatest-width integer types + +// 7.8.2.1 The imaxabs function +#define imaxabs _abs64 + +// 7.8.2.2 The imaxdiv function + +// This is modified version of div() function from Microsoft's div.c found +// in %MSVC.NET%\crt\src\div.c +#ifdef STATIC_IMAXDIV // [ +static +#else // STATIC_IMAXDIV ][ +_inline +#endif // STATIC_IMAXDIV ] +imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) +{ + imaxdiv_t result; + + result.quot = numer / denom; + result.rem = numer % denom; + + if (numer < 0 && result.rem > 0) { + // did division wrong; must fix up + ++result.quot; + result.rem -= denom; + } + + return result; +} + +// 7.8.2.3 The strtoimax and strtoumax functions +#define strtoimax _strtoi64 +#define strtoumax _strtoui64 + +// 7.8.2.4 The wcstoimax and wcstoumax functions +#define wcstoimax _wcstoi64 +#define wcstoumax _wcstoui64 + +#endif // _MSC_VER >= 1800 + +#endif // _MSC_INTTYPES_H_ ] diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/msinttypes/stdint.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/msinttypes/stdint.h new file mode 100755 index 000000000..3d4477b9a --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/msinttypes/stdint.h @@ -0,0 +1,300 @@ +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +// The above software in this distribution may have been modified by +// THL A29 Limited ("Tencent Modifications"). +// All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +// miloyip: Originally Visual Studio 2010 uses its own stdint.h. However it generates warning with INT64_C(), so change to use this file for vs2010. +#if _MSC_VER >= 1600 // [ +#include + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +#undef INT8_C +#undef INT16_C +#undef INT32_C +#undef INT64_C +#undef UINT8_C +#undef UINT16_C +#undef UINT32_C +#undef UINT64_C + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with . +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#else // ] _MSC_VER >= 1700 [ + +#include + +// For Visual Studio 6 in C++ mode and for many Visual Studio versions when +// compiling for ARM we have to wrap include with 'extern "C++" {}' +// or compiler would give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#if defined(__cplusplus) && !defined(_M_ARM) +extern "C" { +#endif +# include +#if defined(__cplusplus) && !defined(_M_ARM) +} +#endif + +// Define _W64 macros to mark types changing their size, like intptr_t. +#ifndef _W64 +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif + + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types + +// Visual Studio 6 and Embedded Visual C++ 4 doesn't +// realize that, e.g. char has the same size as __int8 +// so we give up on __intX for them. +#if (_MSC_VER < 1300) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; +#else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +#endif +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef signed __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef _W64 signed int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with . +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#endif // _MSC_VER >= 1600 ] + +#endif // _MSC_STDINT_H_ ] diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/ostreamwrapper.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/ostreamwrapper.h new file mode 100755 index 000000000..6f4667c08 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/ostreamwrapper.h @@ -0,0 +1,81 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_OSTREAMWRAPPER_H_ +#define RAPIDJSON_OSTREAMWRAPPER_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of \c std::basic_ostream into RapidJSON's Stream concept. +/*! + The classes can be wrapped including but not limited to: + + - \c std::ostringstream + - \c std::stringstream + - \c std::wpstringstream + - \c std::wstringstream + - \c std::ifstream + - \c std::fstream + - \c std::wofstream + - \c std::wfstream + + \tparam StreamType Class derived from \c std::basic_ostream. +*/ + +template +class BasicOStreamWrapper { +public: + typedef typename StreamType::char_type Ch; + BasicOStreamWrapper(StreamType& stream) : stream_(stream) {} + + void Put(Ch c) { + stream_.put(c); + } + + void Flush() { + stream_.flush(); + } + + // Not implemented + char Peek() const { RAPIDJSON_ASSERT(false); return 0; } + char Take() { RAPIDJSON_ASSERT(false); return 0; } + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + BasicOStreamWrapper(const BasicOStreamWrapper&); + BasicOStreamWrapper& operator=(const BasicOStreamWrapper&); + + StreamType& stream_; +}; + +typedef BasicOStreamWrapper OStreamWrapper; +typedef BasicOStreamWrapper WOStreamWrapper; + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_OSTREAMWRAPPER_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/pointer.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/pointer.h new file mode 100755 index 000000000..063abab9a --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/pointer.h @@ -0,0 +1,1414 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_POINTER_H_ +#define RAPIDJSON_POINTER_H_ + +#include "document.h" +#include "internal/itoa.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(switch-enum) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +static const SizeType kPointerInvalidIndex = ~SizeType(0); //!< Represents an invalid index in GenericPointer::Token + +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericPointer::GenericPointer, GenericPointer::GetParseErrorCode +*/ +enum PointerParseErrorCode { + kPointerParseErrorNone = 0, //!< The parse is successful + + kPointerParseErrorTokenMustBeginWithSolidus, //!< A token must begin with a '/' + kPointerParseErrorInvalidEscape, //!< Invalid escape + kPointerParseErrorInvalidPercentEncoding, //!< Invalid percent encoding in URI fragment + kPointerParseErrorCharacterMustPercentEncode //!< A character must percent encoded in URI fragment +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericPointer + +//! Represents a JSON Pointer. Use Pointer for UTF8 encoding and default allocator. +/*! + This class implements RFC 6901 "JavaScript Object Notation (JSON) Pointer" + (https://tools.ietf.org/html/rfc6901). + + A JSON pointer is for identifying a specific value in a JSON document + (GenericDocument). It can simplify coding of DOM tree manipulation, because it + can access multiple-level depth of DOM tree with single API call. + + After it parses a string representation (e.g. "/foo/0" or URI fragment + representation (e.g. "#/foo/0") into its internal representation (tokens), + it can be used to resolve a specific value in multiple documents, or sub-tree + of documents. + + Contrary to GenericValue, Pointer can be copy constructed and copy assigned. + Apart from assignment, a Pointer cannot be modified after construction. + + Although Pointer is very convenient, please aware that constructing Pointer + involves parsing and dynamic memory allocation. A special constructor with user- + supplied tokens eliminates these. + + GenericPointer depends on GenericDocument and GenericValue. + + \tparam ValueType The value type of the DOM tree. E.g. GenericValue > + \tparam Allocator The allocator type for allocating memory for internal representation. + + \note GenericPointer uses same encoding of ValueType. + However, Allocator of GenericPointer is independent of Allocator of Value. +*/ +template +class GenericPointer { +public: + typedef typename ValueType::EncodingType EncodingType; //!< Encoding type from Value + typedef typename ValueType::Ch Ch; //!< Character type from Value + + //! A token is the basic units of internal representation. + /*! + A JSON pointer string representation "/foo/123" is parsed to two tokens: + "foo" and 123. 123 will be represented in both numeric form and string form. + They are resolved according to the actual value type (object or array). + + For token that are not numbers, or the numeric value is out of bound + (greater than limits of SizeType), they are only treated as string form + (i.e. the token's index will be equal to kPointerInvalidIndex). + + This struct is public so that user can create a Pointer without parsing and + allocation, using a special constructor. + */ + struct Token { + const Ch* name; //!< Name of the token. It has null character at the end but it can contain null character. + SizeType length; //!< Length of the name. + SizeType index; //!< A valid array index, if it is not equal to kPointerInvalidIndex. + }; + + //!@name Constructors and destructor. + //@{ + + //! Default constructor. + GenericPointer(Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A null-terminated, string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + */ + explicit GenericPointer(const Ch* source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source, internal::StrLen(source)); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + explicit GenericPointer(const std::basic_string& source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source.c_str(), source.size()); + } +#endif + + //! Constructor that parses a string or URI fragment representation, with length of the source string. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param length Length of source. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Slightly faster than the overload without length. + */ + GenericPointer(const Ch* source, size_t length, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source, length); + } + + //! Constructor with user-supplied tokens. + /*! + This constructor let user supplies const array of tokens. + This prevents the parsing process and eliminates allocation. + This is preferred for memory constrained environments. + + \param tokens An constant array of tokens representing the JSON pointer. + \param tokenCount Number of tokens. + + \b Example + \code + #define NAME(s) { s, sizeof(s) / sizeof(s[0]) - 1, kPointerInvalidIndex } + #define INDEX(i) { #i, sizeof(#i) - 1, i } + + static const Pointer::Token kTokens[] = { NAME("foo"), INDEX(123) }; + static const Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); + // Equivalent to static const Pointer p("/foo/123"); + + #undef NAME + #undef INDEX + \endcode + */ + GenericPointer(const Token* tokens, size_t tokenCount) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(const_cast(tokens)), tokenCount_(tokenCount), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + + //! Copy constructor. + GenericPointer(const GenericPointer& rhs) : allocator_(rhs.allocator_), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + *this = rhs; + } + + //! Copy constructor. + GenericPointer(const GenericPointer& rhs, Allocator* allocator) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + *this = rhs; + } + + //! Destructor. + ~GenericPointer() { + if (nameBuffer_) // If user-supplied tokens constructor is used, nameBuffer_ is nullptr and tokens_ are not deallocated. + Allocator::Free(tokens_); + RAPIDJSON_DELETE(ownAllocator_); + } + + //! Assignment operator. + GenericPointer& operator=(const GenericPointer& rhs) { + if (this != &rhs) { + // Do not delete ownAllcator + if (nameBuffer_) + Allocator::Free(tokens_); + + tokenCount_ = rhs.tokenCount_; + parseErrorOffset_ = rhs.parseErrorOffset_; + parseErrorCode_ = rhs.parseErrorCode_; + + if (rhs.nameBuffer_) + CopyFromRaw(rhs); // Normally parsed tokens. + else { + tokens_ = rhs.tokens_; // User supplied const tokens. + nameBuffer_ = 0; + } + } + return *this; + } + + //! Swap the content of this pointer with an other. + /*! + \param other The pointer to swap with. + \note Constant complexity. + */ + GenericPointer& Swap(GenericPointer& other) RAPIDJSON_NOEXCEPT { + internal::Swap(allocator_, other.allocator_); + internal::Swap(ownAllocator_, other.ownAllocator_); + internal::Swap(nameBuffer_, other.nameBuffer_); + internal::Swap(tokens_, other.tokens_); + internal::Swap(tokenCount_, other.tokenCount_); + internal::Swap(parseErrorOffset_, other.parseErrorOffset_); + internal::Swap(parseErrorCode_, other.parseErrorCode_); + return *this; + } + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.pointer, b.pointer); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericPointer& a, GenericPointer& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //@} + + //!@name Append token + //@{ + + //! Append a token and return a new Pointer + /*! + \param token Token to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Token& token, Allocator* allocator = 0) const { + GenericPointer r; + r.allocator_ = allocator; + Ch *p = r.CopyFromRaw(*this, 1, token.length + 1); + std::memcpy(p, token.name, (token.length + 1) * sizeof(Ch)); + r.tokens_[tokenCount_].name = p; + r.tokens_[tokenCount_].length = token.length; + r.tokens_[tokenCount_].index = token.index; + return r; + } + + //! Append a name token with length, and return a new Pointer + /*! + \param name Name to be appended. + \param length Length of name. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Ch* name, SizeType length, Allocator* allocator = 0) const { + Token token = { name, length, kPointerInvalidIndex }; + return Append(token, allocator); + } + + //! Append a name token without length, and return a new Pointer + /*! + \param name Name (const Ch*) to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >), (GenericPointer)) + Append(T* name, Allocator* allocator = 0) const { + return Append(name, internal::StrLen(name), allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Append a name token, and return a new Pointer + /*! + \param name Name to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const std::basic_string& name, Allocator* allocator = 0) const { + return Append(name.c_str(), static_cast(name.size()), allocator); + } +#endif + + //! Append a index token, and return a new Pointer + /*! + \param index Index to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(SizeType index, Allocator* allocator = 0) const { + char buffer[21]; + char* end = sizeof(SizeType) == 4 ? internal::u32toa(index, buffer) : internal::u64toa(index, buffer); + SizeType length = static_cast(end - buffer); + buffer[length] = '\0'; + + if (sizeof(Ch) == 1) { + Token token = { reinterpret_cast(buffer), length, index }; + return Append(token, allocator); + } + else { + Ch name[21]; + for (size_t i = 0; i <= length; i++) + name[i] = static_cast(buffer[i]); + Token token = { name, length, index }; + return Append(token, allocator); + } + } + + //! Append a token by value, and return a new Pointer + /*! + \param token token to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const ValueType& token, Allocator* allocator = 0) const { + if (token.IsString()) + return Append(token.GetString(), token.GetStringLength(), allocator); + else { + RAPIDJSON_ASSERT(token.IsUint64()); + RAPIDJSON_ASSERT(token.GetUint64() <= SizeType(~0)); + return Append(static_cast(token.GetUint64()), allocator); + } + } + + //!@name Handling Parse Error + //@{ + + //! Check whether this is a valid pointer. + bool IsValid() const { return parseErrorCode_ == kPointerParseErrorNone; } + + //! Get the parsing error offset in code unit. + size_t GetParseErrorOffset() const { return parseErrorOffset_; } + + //! Get the parsing error code. + PointerParseErrorCode GetParseErrorCode() const { return parseErrorCode_; } + + //@} + + //! Get the allocator of this pointer. + Allocator& GetAllocator() { return *allocator_; } + + //!@name Tokens + //@{ + + //! Get the token array (const version only). + const Token* GetTokens() const { return tokens_; } + + //! Get the number of tokens. + size_t GetTokenCount() const { return tokenCount_; } + + //@} + + //!@name Equality/inequality operators + //@{ + + //! Equality operator. + /*! + \note When any pointers are invalid, always returns false. + */ + bool operator==(const GenericPointer& rhs) const { + if (!IsValid() || !rhs.IsValid() || tokenCount_ != rhs.tokenCount_) + return false; + + for (size_t i = 0; i < tokenCount_; i++) { + if (tokens_[i].index != rhs.tokens_[i].index || + tokens_[i].length != rhs.tokens_[i].length || + (tokens_[i].length != 0 && std::memcmp(tokens_[i].name, rhs.tokens_[i].name, sizeof(Ch)* tokens_[i].length) != 0)) + { + return false; + } + } + + return true; + } + + //! Inequality operator. + /*! + \note When any pointers are invalid, always returns true. + */ + bool operator!=(const GenericPointer& rhs) const { return !(*this == rhs); } + + //! Less than operator. + /*! + \note Invalid pointers are always greater than valid ones. + */ + bool operator<(const GenericPointer& rhs) const { + if (!IsValid()) + return false; + if (!rhs.IsValid()) + return true; + + if (tokenCount_ != rhs.tokenCount_) + return tokenCount_ < rhs.tokenCount_; + + for (size_t i = 0; i < tokenCount_; i++) { + if (tokens_[i].index != rhs.tokens_[i].index) + return tokens_[i].index < rhs.tokens_[i].index; + + if (tokens_[i].length != rhs.tokens_[i].length) + return tokens_[i].length < rhs.tokens_[i].length; + + if (int cmp = std::memcmp(tokens_[i].name, rhs.tokens_[i].name, sizeof(Ch) * tokens_[i].length)) + return cmp < 0; + } + + return false; + } + + //@} + + //!@name Stringify + //@{ + + //! Stringify the pointer into string representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ + template + bool Stringify(OutputStream& os) const { + return Stringify(os); + } + + //! Stringify the pointer into URI fragment representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ + template + bool StringifyUriFragment(OutputStream& os) const { + return Stringify(os); + } + + //@} + + //!@name Create value + //@{ + + //! Create a value in a subtree. + /*! + If the value is not exist, it creates all parent values and a JSON Null value. + So it always succeed and return the newly created or existing value. + + Remind that it may change types of parents according to tokens, so it + potentially removes previously stored values. For example, if a document + was an array, and "/foo" is used to create a value, then the document + will be changed to an object, and all existing array elements are lost. + + \param root Root value of a DOM subtree to be resolved. It can be any value other than document root. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created (a JSON Null value), or already exists value. + */ + ValueType& Create(ValueType& root, typename ValueType::AllocatorType& allocator, bool* alreadyExist = 0) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + bool exist = true; + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + if (v->IsArray() && t->name[0] == '-' && t->length == 1) { + v->PushBack(ValueType().Move(), allocator); + v = &((*v)[v->Size() - 1]); + exist = false; + } + else { + if (t->index == kPointerInvalidIndex) { // must be object name + if (!v->IsObject()) + v->SetObject(); // Change to Object + } + else { // object name or array index + if (!v->IsArray() && !v->IsObject()) + v->SetArray(); // Change to Array + } + + if (v->IsArray()) { + if (t->index >= v->Size()) { + v->Reserve(t->index + 1, allocator); + while (t->index >= v->Size()) + v->PushBack(ValueType().Move(), allocator); + exist = false; + } + v = &((*v)[t->index]); + } + else { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) { + v->AddMember(ValueType(t->name, t->length, allocator).Move(), ValueType().Move(), allocator); + v = &(--v->MemberEnd())->value; // Assumes AddMember() appends at the end + exist = false; + } + else + v = &m->value; + } + } + } + + if (alreadyExist) + *alreadyExist = exist; + + return *v; + } + + //! Creates a value in a document. + /*! + \param document A document to be resolved. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created, or already exists value. + */ + template + ValueType& Create(GenericDocument& document, bool* alreadyExist = 0) const { + return Create(document, document.GetAllocator(), alreadyExist); + } + + //@} + + //!@name Query value + //@{ + + //! Query a value in a subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param unresolvedTokenIndex If the pointer cannot resolve a token in the pointer, this parameter can obtain the index of unresolved token. + \return Pointer to the value if it can be resolved. Otherwise null. + + \note + There are only 3 situations when a value cannot be resolved: + 1. A value in the path is not an array nor object. + 2. An object value does not contain the token. + 3. A token is out of range of an array value. + + Use unresolvedTokenIndex to retrieve the token index. + */ + ValueType* Get(ValueType& root, size_t* unresolvedTokenIndex = 0) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) + break; + v = &m->value; + } + continue; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + break; + v = &((*v)[t->index]); + continue; + default: + break; + } + + // Error: unresolved token + if (unresolvedTokenIndex) + *unresolvedTokenIndex = static_cast(t - tokens_); + return 0; + } + return v; + } + + //! Query a const value in a const subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Pointer to the value if it can be resolved. Otherwise null. + */ + const ValueType* Get(const ValueType& root, size_t* unresolvedTokenIndex = 0) const { + return Get(const_cast(root), unresolvedTokenIndex); + } + + //@} + + //!@name Query a value with default + //@{ + + //! Query a value in a subtree with default value. + /*! + Similar to Get(), but if the specified value do not exists, it creates all parents and clone the default value. + So that this function always succeed. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param defaultValue Default value to be cloned if the value was not exists. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& GetWithDefault(ValueType& root, const ValueType& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + ValueType& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.CopyFrom(defaultValue, allocator); + } + + //! Query a value in a subtree with default null-terminated string. + ValueType& GetWithDefault(ValueType& root, const Ch* defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + ValueType& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.SetString(defaultValue, allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Query a value in a subtree with default std::basic_string. + ValueType& GetWithDefault(ValueType& root, const std::basic_string& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + ValueType& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.SetString(defaultValue, allocator); + } +#endif + + //! Query a value in a subtree with default primitive value. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + GetWithDefault(ValueType& root, T defaultValue, typename ValueType::AllocatorType& allocator) const { + return GetWithDefault(root, ValueType(defaultValue).Move(), allocator); + } + + //! Query a value in a document with default value. + template + ValueType& GetWithDefault(GenericDocument& document, const ValueType& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + + //! Query a value in a document with default null-terminated string. + template + ValueType& GetWithDefault(GenericDocument& document, const Ch* defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Query a value in a document with default std::basic_string. + template + ValueType& GetWithDefault(GenericDocument& document, const std::basic_string& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } +#endif + + //! Query a value in a document with default primitive value. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + GetWithDefault(GenericDocument& document, T defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + + //@} + + //!@name Set a value + //@{ + + //! Set a value in a subtree, with move semantics. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be set. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& Set(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = value; + } + + //! Set a value in a subtree, with copy semantics. + ValueType& Set(ValueType& root, const ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).CopyFrom(value, allocator); + } + + //! Set a null-terminated string in a subtree. + ValueType& Set(ValueType& root, const Ch* value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value, allocator).Move(); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Set a std::basic_string in a subtree. + ValueType& Set(ValueType& root, const std::basic_string& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value, allocator).Move(); + } +#endif + + //! Set a primitive value in a subtree. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + Set(ValueType& root, T value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value).Move(); + } + + //! Set a value in a document, with move semantics. + template + ValueType& Set(GenericDocument& document, ValueType& value) const { + return Create(document) = value; + } + + //! Set a value in a document, with copy semantics. + template + ValueType& Set(GenericDocument& document, const ValueType& value) const { + return Create(document).CopyFrom(value, document.GetAllocator()); + } + + //! Set a null-terminated string in a document. + template + ValueType& Set(GenericDocument& document, const Ch* value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Sets a std::basic_string in a document. + template + ValueType& Set(GenericDocument& document, const std::basic_string& value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); + } +#endif + + //! Set a primitive value in a document. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + Set(GenericDocument& document, T value) const { + return Create(document) = value; + } + + //@} + + //!@name Swap a value + //@{ + + //! Swap a value with a value in a subtree. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be swapped. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& Swap(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).Swap(value); + } + + //! Swap a value with a value in a document. + template + ValueType& Swap(GenericDocument& document, ValueType& value) const { + return Create(document).Swap(value); + } + + //@} + + //! Erase a value in a subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Whether the resolved value is found and erased. + + \note Erasing with an empty pointer \c Pointer(""), i.e. the root, always fail and return false. + */ + bool Erase(ValueType& root) const { + RAPIDJSON_ASSERT(IsValid()); + if (tokenCount_ == 0) // Cannot erase the root + return false; + + ValueType* v = &root; + const Token* last = tokens_ + (tokenCount_ - 1); + for (const Token *t = tokens_; t != last; ++t) { + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) + return false; + v = &m->value; + } + break; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + return false; + v = &((*v)[t->index]); + break; + default: + return false; + } + } + + switch (v->GetType()) { + case kObjectType: + return v->EraseMember(GenericStringRef(last->name, last->length)); + case kArrayType: + if (last->index == kPointerInvalidIndex || last->index >= v->Size()) + return false; + v->Erase(v->Begin() + last->index); + return true; + default: + return false; + } + } + +private: + //! Clone the content from rhs to this. + /*! + \param rhs Source pointer. + \param extraToken Extra tokens to be allocated. + \param extraNameBufferSize Extra name buffer size (in number of Ch) to be allocated. + \return Start of non-occupied name buffer, for storing extra names. + */ + Ch* CopyFromRaw(const GenericPointer& rhs, size_t extraToken = 0, size_t extraNameBufferSize = 0) { + if (!allocator_) // allocator is independently owned. + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + + size_t nameBufferSize = rhs.tokenCount_; // null terminators for tokens + for (Token *t = rhs.tokens_; t != rhs.tokens_ + rhs.tokenCount_; ++t) + nameBufferSize += t->length; + + tokenCount_ = rhs.tokenCount_ + extraToken; + tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + (nameBufferSize + extraNameBufferSize) * sizeof(Ch))); + nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); + if (rhs.tokenCount_ > 0) { + std::memcpy(tokens_, rhs.tokens_, rhs.tokenCount_ * sizeof(Token)); + } + if (nameBufferSize > 0) { + std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize * sizeof(Ch)); + } + + // Adjust pointers to name buffer + std::ptrdiff_t diff = nameBuffer_ - rhs.nameBuffer_; + for (Token *t = tokens_; t != tokens_ + rhs.tokenCount_; ++t) + t->name += diff; + + return nameBuffer_ + nameBufferSize; + } + + //! Check whether a character should be percent-encoded. + /*! + According to RFC 3986 2.3 Unreserved Characters. + \param c The character (code unit) to be tested. + */ + bool NeedPercentEncode(Ch c) const { + return !((c >= '0' && c <= '9') || (c >= 'A' && c <='Z') || (c >= 'a' && c <= 'z') || c == '-' || c == '.' || c == '_' || c =='~'); + } + + //! Parse a JSON String or its URI fragment representation into tokens. +#ifndef __clang__ // -Wdocumentation + /*! + \param source Either a JSON Pointer string, or its URI fragment representation. Not need to be null terminated. + \param length Length of the source string. + \note Source cannot be JSON String Representation of JSON Pointer, e.g. In "/\u0000", \u0000 will not be unescaped. + */ +#endif + void Parse(const Ch* source, size_t length) { + RAPIDJSON_ASSERT(source != NULL); + RAPIDJSON_ASSERT(nameBuffer_ == 0); + RAPIDJSON_ASSERT(tokens_ == 0); + + // Create own allocator if user did not supply. + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + + // Count number of '/' as tokenCount + tokenCount_ = 0; + for (const Ch* s = source; s != source + length; s++) + if (*s == '/') + tokenCount_++; + + Token* token = tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + length * sizeof(Ch))); + Ch* name = nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); + size_t i = 0; + + // Detect if it is a URI fragment + bool uriFragment = false; + if (source[i] == '#') { + uriFragment = true; + i++; + } + + if (i != length && source[i] != '/') { + parseErrorCode_ = kPointerParseErrorTokenMustBeginWithSolidus; + goto error; + } + + while (i < length) { + RAPIDJSON_ASSERT(source[i] == '/'); + i++; // consumes '/' + + token->name = name; + bool isNumber = true; + + while (i < length && source[i] != '/') { + Ch c = source[i]; + if (uriFragment) { + // Decoding percent-encoding for URI fragment + if (c == '%') { + PercentDecodeStream is(&source[i], source + length); + GenericInsituStringStream os(name); + Ch* begin = os.PutBegin(); + if (!Transcoder, EncodingType>().Validate(is, os) || !is.IsValid()) { + parseErrorCode_ = kPointerParseErrorInvalidPercentEncoding; + goto error; + } + size_t len = os.PutEnd(begin); + i += is.Tell() - 1; + if (len == 1) + c = *name; + else { + name += len; + isNumber = false; + i++; + continue; + } + } + else if (NeedPercentEncode(c)) { + parseErrorCode_ = kPointerParseErrorCharacterMustPercentEncode; + goto error; + } + } + + i++; + + // Escaping "~0" -> '~', "~1" -> '/' + if (c == '~') { + if (i < length) { + c = source[i]; + if (c == '0') c = '~'; + else if (c == '1') c = '/'; + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; + goto error; + } + i++; + } + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; + goto error; + } + } + + // First check for index: all of characters are digit + if (c < '0' || c > '9') + isNumber = false; + + *name++ = c; + } + token->length = static_cast(name - token->name); + if (token->length == 0) + isNumber = false; + *name++ = '\0'; // Null terminator + + // Second check for index: more than one digit cannot have leading zero + if (isNumber && token->length > 1 && token->name[0] == '0') + isNumber = false; + + // String to SizeType conversion + SizeType n = 0; + if (isNumber) { + for (size_t j = 0; j < token->length; j++) { + SizeType m = n * 10 + static_cast(token->name[j] - '0'); + if (m < n) { // overflow detection + isNumber = false; + break; + } + n = m; + } + } + + token->index = isNumber ? n : kPointerInvalidIndex; + token++; + } + + RAPIDJSON_ASSERT(name <= nameBuffer_ + length); // Should not overflow buffer + parseErrorCode_ = kPointerParseErrorNone; + return; + + error: + Allocator::Free(tokens_); + nameBuffer_ = 0; + tokens_ = 0; + tokenCount_ = 0; + parseErrorOffset_ = i; + return; + } + + //! Stringify to string or URI fragment representation. + /*! + \tparam uriFragment True for stringifying to URI fragment representation. False for string representation. + \tparam OutputStream type of output stream. + \param os The output stream. + */ + template + bool Stringify(OutputStream& os) const { + RAPIDJSON_ASSERT(IsValid()); + + if (uriFragment) + os.Put('#'); + + for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + os.Put('/'); + for (size_t j = 0; j < t->length; j++) { + Ch c = t->name[j]; + if (c == '~') { + os.Put('~'); + os.Put('0'); + } + else if (c == '/') { + os.Put('~'); + os.Put('1'); + } + else if (uriFragment && NeedPercentEncode(c)) { + // Transcode to UTF8 sequence + GenericStringStream source(&t->name[j]); + PercentEncodeStream target(os); + if (!Transcoder >().Validate(source, target)) + return false; + j += source.Tell() - 1; + } + else + os.Put(c); + } + } + return true; + } + + //! A helper stream for decoding a percent-encoded sequence into code unit. + /*! + This stream decodes %XY triplet into code unit (0-255). + If it encounters invalid characters, it sets output code unit as 0 and + mark invalid, and to be checked by IsValid(). + */ + class PercentDecodeStream { + public: + typedef typename ValueType::Ch Ch; + + //! Constructor + /*! + \param source Start of the stream + \param end Past-the-end of the stream. + */ + PercentDecodeStream(const Ch* source, const Ch* end) : src_(source), head_(source), end_(end), valid_(true) {} + + Ch Take() { + if (*src_ != '%' || src_ + 3 > end_) { // %XY triplet + valid_ = false; + return 0; + } + src_++; + Ch c = 0; + for (int j = 0; j < 2; j++) { + c = static_cast(c << 4); + Ch h = *src_; + if (h >= '0' && h <= '9') c = static_cast(c + h - '0'); + else if (h >= 'A' && h <= 'F') c = static_cast(c + h - 'A' + 10); + else if (h >= 'a' && h <= 'f') c = static_cast(c + h - 'a' + 10); + else { + valid_ = false; + return 0; + } + src_++; + } + return c; + } + + size_t Tell() const { return static_cast(src_ - head_); } + bool IsValid() const { return valid_; } + + private: + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. + const Ch* end_; //!< Past-the-end position. + bool valid_; //!< Whether the parsing is valid. + }; + + //! A helper stream to encode character (UTF-8 code unit) into percent-encoded sequence. + template + class PercentEncodeStream { + public: + PercentEncodeStream(OutputStream& os) : os_(os) {} + void Put(char c) { // UTF-8 must be byte + unsigned char u = static_cast(c); + static const char hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + os_.Put('%'); + os_.Put(static_cast(hexDigits[u >> 4])); + os_.Put(static_cast(hexDigits[u & 15])); + } + private: + OutputStream& os_; + }; + + Allocator* allocator_; //!< The current allocator. It is either user-supplied or equal to ownAllocator_. + Allocator* ownAllocator_; //!< Allocator owned by this Pointer. + Ch* nameBuffer_; //!< A buffer containing all names in tokens. + Token* tokens_; //!< A list of tokens. + size_t tokenCount_; //!< Number of tokens in tokens_. + size_t parseErrorOffset_; //!< Offset in code unit when parsing fail. + PointerParseErrorCode parseErrorCode_; //!< Parsing error code. +}; + +//! GenericPointer for Value (UTF-8, default allocator). +typedef GenericPointer Pointer; + +//!@name Helper functions for GenericPointer +//@{ + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer& pointer, typename T::AllocatorType& a) { + return pointer.Create(root, a); +} + +template +typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N], typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Create(root, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const GenericPointer& pointer) { + return pointer.Create(document); +} + +template +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const CharType(&source)[N]) { + return GenericPointer(source, N - 1).Create(document); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType* GetValueByPointer(T& root, const GenericPointer& pointer, size_t* unresolvedTokenIndex = 0) { + return pointer.Get(root, unresolvedTokenIndex); +} + +template +const typename T::ValueType* GetValueByPointer(const T& root, const GenericPointer& pointer, size_t* unresolvedTokenIndex = 0) { + return pointer.Get(root, unresolvedTokenIndex); +} + +template +typename T::ValueType* GetValueByPointer(T& root, const CharType (&source)[N], size_t* unresolvedTokenIndex = 0) { + return GenericPointer(source, N - 1).Get(root, unresolvedTokenIndex); +} + +template +const typename T::ValueType* GetValueByPointer(const T& root, const CharType(&source)[N], size_t* unresolvedTokenIndex = 0) { + return GenericPointer(source, N - 1).Get(root, unresolvedTokenIndex); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const std::basic_string& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, T2 defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const std::basic_string& defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const CharType(&source)[N], T2 defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const std::basic_string& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, T2 defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const std::basic_string& defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], T2 defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::Ch* value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const std::basic_string& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const GenericPointer& pointer, T2 value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const std::basic_string& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const CharType(&source)[N], T2 value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* value) { + return pointer.Set(document, value); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const std::basic_string& value) { + return pointer.Set(document, value); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const GenericPointer& pointer, T2 value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const std::basic_string& value) { + return GenericPointer(source, N - 1).Set(document, value); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const CharType(&source)[N], T2 value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Swap(root, value, a); +} + +template +typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Swap(root, value, a); +} + +template +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { + return pointer.Swap(document, value); +} + +template +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Swap(document, value); +} + +////////////////////////////////////////////////////////////////////////////// + +template +bool EraseValueByPointer(T& root, const GenericPointer& pointer) { + return pointer.Erase(root); +} + +template +bool EraseValueByPointer(T& root, const CharType(&source)[N]) { + return GenericPointer(source, N - 1).Erase(root); +} + +//@} + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_POINTER_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/prettywriter.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/prettywriter.h new file mode 100755 index 000000000..45afb6949 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/prettywriter.h @@ -0,0 +1,277 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_PRETTYWRITER_H_ +#define RAPIDJSON_PRETTYWRITER_H_ + +#include "writer.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Combination of PrettyWriter format flags. +/*! \see PrettyWriter::SetFormatOptions + */ +enum PrettyFormatOptions { + kFormatDefault = 0, //!< Default pretty formatting. + kFormatSingleLineArray = 1 //!< Format arrays on a single line. +}; + +//! Writer with indentation and spacing. +/*! + \tparam OutputStream Type of output os. + \tparam SourceEncoding Encoding of source string. + \tparam TargetEncoding Encoding of output stream. + \tparam StackAllocator Type of allocator for allocating memory of stack. +*/ +template, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator, unsigned writeFlags = kWriteDefaultFlags> +class PrettyWriter : public Writer { +public: + typedef Writer Base; + typedef typename Base::Ch Ch; + + //! Constructor + /*! \param os Output stream. + \param allocator User supplied allocator. If it is null, it will create a private one. + \param levelDepth Initial capacity of stack. + */ + explicit PrettyWriter(OutputStream& os, StackAllocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : + Base(os, allocator, levelDepth), indentChar_(' '), indentCharCount_(4), formatOptions_(kFormatDefault) {} + + + explicit PrettyWriter(StackAllocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : + Base(allocator, levelDepth), indentChar_(' '), indentCharCount_(4) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + PrettyWriter(PrettyWriter&& rhs) : + Base(std::forward(rhs)), indentChar_(rhs.indentChar_), indentCharCount_(rhs.indentCharCount_), formatOptions_(rhs.formatOptions_) {} +#endif + + //! Set custom indentation. + /*! \param indentChar Character for indentation. Must be whitespace character (' ', '\\t', '\\n', '\\r'). + \param indentCharCount Number of indent characters for each indentation level. + \note The default indentation is 4 spaces. + */ + PrettyWriter& SetIndent(Ch indentChar, unsigned indentCharCount) { + RAPIDJSON_ASSERT(indentChar == ' ' || indentChar == '\t' || indentChar == '\n' || indentChar == '\r'); + indentChar_ = indentChar; + indentCharCount_ = indentCharCount; + return *this; + } + + //! Set pretty writer formatting options. + /*! \param options Formatting options. + */ + PrettyWriter& SetFormatOptions(PrettyFormatOptions options) { + formatOptions_ = options; + return *this; + } + + /*! @name Implementation of Handler + \see Handler + */ + //@{ + + bool Null() { PrettyPrefix(kNullType); return Base::EndValue(Base::WriteNull()); } + bool Bool(bool b) { PrettyPrefix(b ? kTrueType : kFalseType); return Base::EndValue(Base::WriteBool(b)); } + bool Int(int i) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteInt(i)); } + bool Uint(unsigned u) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteUint(u)); } + bool Int64(int64_t i64) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteInt64(i64)); } + bool Uint64(uint64_t u64) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteUint64(u64)); } + bool Double(double d) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteDouble(d)); } + + bool RawNumber(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + PrettyPrefix(kNumberType); + return Base::EndValue(Base::WriteString(str, length)); + } + + bool String(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + PrettyPrefix(kStringType); + return Base::EndValue(Base::WriteString(str, length)); + } + +#if RAPIDJSON_HAS_STDSTRING + bool String(const std::basic_string& str) { + return String(str.data(), SizeType(str.size())); + } +#endif + + bool StartObject() { + PrettyPrefix(kObjectType); + new (Base::level_stack_.template Push()) typename Base::Level(false); + return Base::WriteStartObject(); + } + + bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } + +#if RAPIDJSON_HAS_STDSTRING + bool Key(const std::basic_string& str) { + return Key(str.data(), SizeType(str.size())); + } +#endif + + bool EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); // not inside an Object + RAPIDJSON_ASSERT(!Base::level_stack_.template Top()->inArray); // currently inside an Array, not Object + RAPIDJSON_ASSERT(0 == Base::level_stack_.template Top()->valueCount % 2); // Object has a Key without a Value + + bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; + + if (!empty) { + Base::os_->Put('\n'); + WriteIndent(); + } + bool ret = Base::EndValue(Base::WriteEndObject()); + (void)ret; + RAPIDJSON_ASSERT(ret == true); + if (Base::level_stack_.Empty()) // end of json text + Base::Flush(); + return true; + } + + bool StartArray() { + PrettyPrefix(kArrayType); + new (Base::level_stack_.template Push()) typename Base::Level(true); + return Base::WriteStartArray(); + } + + bool EndArray(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); + RAPIDJSON_ASSERT(Base::level_stack_.template Top()->inArray); + bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; + + if (!empty && !(formatOptions_ & kFormatSingleLineArray)) { + Base::os_->Put('\n'); + WriteIndent(); + } + bool ret = Base::EndValue(Base::WriteEndArray()); + (void)ret; + RAPIDJSON_ASSERT(ret == true); + if (Base::level_stack_.Empty()) // end of json text + Base::Flush(); + return true; + } + + //@} + + /*! @name Convenience extensions */ + //@{ + + //! Simpler but slower overload. + bool String(const Ch* str) { return String(str, internal::StrLen(str)); } + bool Key(const Ch* str) { return Key(str, internal::StrLen(str)); } + + //@} + + //! Write a raw JSON value. + /*! + For user to write a stringified JSON as a value. + + \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. + \param length Length of the json. + \param type Type of the root of json. + \note When using PrettyWriter::RawValue(), the result json may not be indented correctly. + */ + bool RawValue(const Ch* json, size_t length, Type type) { + RAPIDJSON_ASSERT(json != 0); + PrettyPrefix(type); + return Base::EndValue(Base::WriteRawValue(json, length)); + } + +protected: + void PrettyPrefix(Type type) { + (void)type; + if (Base::level_stack_.GetSize() != 0) { // this value is not at root + typename Base::Level* level = Base::level_stack_.template Top(); + + if (level->inArray) { + if (level->valueCount > 0) { + Base::os_->Put(','); // add comma if it is not the first element in array + if (formatOptions_ & kFormatSingleLineArray) + Base::os_->Put(' '); + } + + if (!(formatOptions_ & kFormatSingleLineArray)) { + Base::os_->Put('\n'); + WriteIndent(); + } + } + else { // in object + if (level->valueCount > 0) { + if (level->valueCount % 2 == 0) { + Base::os_->Put(','); + Base::os_->Put('\n'); + } + else { + Base::os_->Put(':'); + Base::os_->Put(' '); + } + } + else + Base::os_->Put('\n'); + + if (level->valueCount % 2 == 0) + WriteIndent(); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else { + RAPIDJSON_ASSERT(!Base::hasRoot_); // Should only has one and only one root. + Base::hasRoot_ = true; + } + } + + void WriteIndent() { + size_t count = (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) * indentCharCount_; + PutN(*Base::os_, static_cast(indentChar_), count); + } + + Ch indentChar_; + unsigned indentCharCount_; + PrettyFormatOptions formatOptions_; + +private: + // Prohibit copy constructor & assignment operator. + PrettyWriter(const PrettyWriter&); + PrettyWriter& operator=(const PrettyWriter&); +}; + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/rapidjson.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/rapidjson.h new file mode 100755 index 000000000..0a6acd755 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/rapidjson.h @@ -0,0 +1,656 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_RAPIDJSON_H_ +#define RAPIDJSON_RAPIDJSON_H_ + +/*!\file rapidjson.h + \brief common definitions and configuration + + \see RAPIDJSON_CONFIG + */ + +/*! \defgroup RAPIDJSON_CONFIG RapidJSON configuration + \brief Configuration macros for library features + + Some RapidJSON features are configurable to adapt the library to a wide + variety of platforms, environments and usage scenarios. Most of the + features can be configured in terms of overridden or predefined + preprocessor macros at compile-time. + + Some additional customization is available in the \ref RAPIDJSON_ERRORS APIs. + + \note These macros should be given on the compiler command-line + (where applicable) to avoid inconsistent values when compiling + different translation units of a single application. + */ + +#include // malloc(), realloc(), free(), size_t +#include // memset(), memcpy(), memmove(), memcmp() + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_VERSION_STRING +// +// ALWAYS synchronize the following 3 macros with corresponding variables in /CMakeLists.txt. +// + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +// token stringification +#define RAPIDJSON_STRINGIFY(x) RAPIDJSON_DO_STRINGIFY(x) +#define RAPIDJSON_DO_STRINGIFY(x) #x + +// token concatenation +#define RAPIDJSON_JOIN(X, Y) RAPIDJSON_DO_JOIN(X, Y) +#define RAPIDJSON_DO_JOIN(X, Y) RAPIDJSON_DO_JOIN2(X, Y) +#define RAPIDJSON_DO_JOIN2(X, Y) X##Y +//!@endcond + +/*! \def RAPIDJSON_MAJOR_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Major version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_MINOR_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Minor version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_PATCH_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Patch version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_VERSION_STRING + \ingroup RAPIDJSON_CONFIG + \brief Version of RapidJSON in ".." string format. +*/ +#define RAPIDJSON_MAJOR_VERSION 1 +#define RAPIDJSON_MINOR_VERSION 1 +#define RAPIDJSON_PATCH_VERSION 0 +#define RAPIDJSON_VERSION_STRING \ + RAPIDJSON_STRINGIFY(RAPIDJSON_MAJOR_VERSION.RAPIDJSON_MINOR_VERSION.RAPIDJSON_PATCH_VERSION) + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NAMESPACE_(BEGIN|END) +/*! \def RAPIDJSON_NAMESPACE + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace + + In order to avoid symbol clashes and/or "One Definition Rule" errors + between multiple inclusions of (different versions of) RapidJSON in + a single binary, users can customize the name of the main RapidJSON + namespace. + + In case of a single nesting level, defining \c RAPIDJSON_NAMESPACE + to a custom name (e.g. \c MyRapidJSON) is sufficient. If multiple + levels are needed, both \ref RAPIDJSON_NAMESPACE_BEGIN and \ref + RAPIDJSON_NAMESPACE_END need to be defined as well: + + \code + // in some .cpp file + #define RAPIDJSON_NAMESPACE my::rapidjson + #define RAPIDJSON_NAMESPACE_BEGIN namespace my { namespace rapidjson { + #define RAPIDJSON_NAMESPACE_END } } + #include "rapidjson/..." + \endcode + + \see rapidjson + */ +/*! \def RAPIDJSON_NAMESPACE_BEGIN + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace (opening expression) + \see RAPIDJSON_NAMESPACE +*/ +/*! \def RAPIDJSON_NAMESPACE_END + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace (closing expression) + \see RAPIDJSON_NAMESPACE +*/ +#ifndef RAPIDJSON_NAMESPACE +#define RAPIDJSON_NAMESPACE rapidjson +#endif +#ifndef RAPIDJSON_NAMESPACE_BEGIN +#define RAPIDJSON_NAMESPACE_BEGIN namespace RAPIDJSON_NAMESPACE { +#endif +#ifndef RAPIDJSON_NAMESPACE_END +#define RAPIDJSON_NAMESPACE_END } +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_HAS_STDSTRING + +#ifndef RAPIDJSON_HAS_STDSTRING +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_HAS_STDSTRING 1 // force generation of documentation +#else +#define RAPIDJSON_HAS_STDSTRING 0 // no std::string support by default +#endif +/*! \def RAPIDJSON_HAS_STDSTRING + \ingroup RAPIDJSON_CONFIG + \brief Enable RapidJSON support for \c std::string + + By defining this preprocessor symbol to \c 1, several convenience functions for using + \ref rapidjson::GenericValue with \c std::string are enabled, especially + for construction and comparison. + + \hideinitializer +*/ +#endif // !defined(RAPIDJSON_HAS_STDSTRING) + +#if RAPIDJSON_HAS_STDSTRING +#include +#endif // RAPIDJSON_HAS_STDSTRING + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_INT64DEFINE + +/*! \def RAPIDJSON_NO_INT64DEFINE + \ingroup RAPIDJSON_CONFIG + \brief Use external 64-bit integer types. + + RapidJSON requires the 64-bit integer types \c int64_t and \c uint64_t types + to be available at global scope. + + If users have their own definition, define RAPIDJSON_NO_INT64DEFINE to + prevent RapidJSON from defining its own types. +*/ +#ifndef RAPIDJSON_NO_INT64DEFINE +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#if defined(_MSC_VER) && (_MSC_VER < 1800) // Visual Studio 2013 +#include "msinttypes/stdint.h" +#include "msinttypes/inttypes.h" +#else +// Other compilers should have this. +#include +#include +#endif +//!@endcond +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_NO_INT64DEFINE +#endif +#endif // RAPIDJSON_NO_INT64TYPEDEF + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_FORCEINLINE + +#ifndef RAPIDJSON_FORCEINLINE +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#if defined(_MSC_VER) && defined(NDEBUG) +#define RAPIDJSON_FORCEINLINE __forceinline +#elif defined(__GNUC__) && __GNUC__ >= 4 && defined(NDEBUG) +#define RAPIDJSON_FORCEINLINE __attribute__((always_inline)) +#else +#define RAPIDJSON_FORCEINLINE +#endif +//!@endcond +#endif // RAPIDJSON_FORCEINLINE + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ENDIAN +#define RAPIDJSON_LITTLEENDIAN 0 //!< Little endian machine +#define RAPIDJSON_BIGENDIAN 1 //!< Big endian machine + +//! Endianness of the machine. +/*! + \def RAPIDJSON_ENDIAN + \ingroup RAPIDJSON_CONFIG + + GCC 4.6 provided macro for detecting endianness of the target machine. But other + compilers may not have this. User can define RAPIDJSON_ENDIAN to either + \ref RAPIDJSON_LITTLEENDIAN or \ref RAPIDJSON_BIGENDIAN. + + Default detection implemented with reference to + \li https://gcc.gnu.org/onlinedocs/gcc-4.6.0/cpp/Common-Predefined-Macros.html + \li http://www.boost.org/doc/libs/1_42_0/boost/detail/endian.hpp +*/ +#ifndef RAPIDJSON_ENDIAN +// Detect with GCC 4.6's macro +# ifdef __BYTE_ORDER__ +# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# else +# error Unknown machine endianness detected. User needs to define RAPIDJSON_ENDIAN. +# endif // __BYTE_ORDER__ +// Detect with GLIBC's endian.h +# elif defined(__GLIBC__) +# include +# if (__BYTE_ORDER == __LITTLE_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif (__BYTE_ORDER == __BIG_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# else +# error Unknown machine endianness detected. User needs to define RAPIDJSON_ENDIAN. +# endif // __GLIBC__ +// Detect with _LITTLE_ENDIAN and _BIG_ENDIAN macro +# elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +// Detect with architecture macros +# elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || defined(__powerpc__) || defined(__ppc__) || defined(__hpux) || defined(__hppa) || defined(_MIPSEB) || defined(_POWER) || defined(__s390__) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# elif defined(__i386__) || defined(__alpha__) || defined(__ia64) || defined(__ia64__) || defined(_M_IX86) || defined(_M_IA64) || defined(_M_ALPHA) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || defined(__bfin__) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(RAPIDJSON_DOXYGEN_RUNNING) +# define RAPIDJSON_ENDIAN +# else +# error Unknown machine endianness detected. User needs to define RAPIDJSON_ENDIAN. +# endif +#endif // RAPIDJSON_ENDIAN + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_64BIT + +//! Whether using 64-bit architecture +#ifndef RAPIDJSON_64BIT +#if defined(__LP64__) || (defined(__x86_64__) && defined(__ILP32__)) || defined(_WIN64) || defined(__EMSCRIPTEN__) +#define RAPIDJSON_64BIT 1 +#else +#define RAPIDJSON_64BIT 0 +#endif +#endif // RAPIDJSON_64BIT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ALIGN + +//! Data alignment of the machine. +/*! \ingroup RAPIDJSON_CONFIG + \param x pointer to align + + Some machines require strict data alignment. The default is 8 bytes. + User can customize by defining the RAPIDJSON_ALIGN function macro. +*/ +#ifndef RAPIDJSON_ALIGN +#define RAPIDJSON_ALIGN(x) (((x) + static_cast(7u)) & ~static_cast(7u)) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_UINT64_C2 + +//! Construct a 64-bit literal by a pair of 32-bit integer. +/*! + 64-bit literal with or without ULL suffix is prone to compiler warnings. + UINT64_C() is C macro which cause compilation problems. + Use this macro to define 64-bit constants by a pair of 32-bit integer. +*/ +#ifndef RAPIDJSON_UINT64_C2 +#define RAPIDJSON_UINT64_C2(high32, low32) ((static_cast(high32) << 32) | static_cast(low32)) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_48BITPOINTER_OPTIMIZATION + +//! Use only lower 48-bit address for some pointers. +/*! + \ingroup RAPIDJSON_CONFIG + + This optimization uses the fact that current X86-64 architecture only implement lower 48-bit virtual address. + The higher 16-bit can be used for storing other data. + \c GenericValue uses this optimization to reduce its size form 24 bytes to 16 bytes in 64-bit architecture. +*/ +#ifndef RAPIDJSON_48BITPOINTER_OPTIMIZATION +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) +#define RAPIDJSON_48BITPOINTER_OPTIMIZATION 1 +#else +#define RAPIDJSON_48BITPOINTER_OPTIMIZATION 0 +#endif +#endif // RAPIDJSON_48BITPOINTER_OPTIMIZATION + +#if RAPIDJSON_48BITPOINTER_OPTIMIZATION == 1 +#if RAPIDJSON_64BIT != 1 +#error RAPIDJSON_48BITPOINTER_OPTIMIZATION can only be set to 1 when RAPIDJSON_64BIT=1 +#endif +#define RAPIDJSON_SETPOINTER(type, p, x) (p = reinterpret_cast((reinterpret_cast(p) & static_cast(RAPIDJSON_UINT64_C2(0xFFFF0000, 0x00000000))) | reinterpret_cast(reinterpret_cast(x)))) +#define RAPIDJSON_GETPOINTER(type, p) (reinterpret_cast(reinterpret_cast(p) & static_cast(RAPIDJSON_UINT64_C2(0x0000FFFF, 0xFFFFFFFF)))) +#else +#define RAPIDJSON_SETPOINTER(type, p, x) (p = (x)) +#define RAPIDJSON_GETPOINTER(type, p) (p) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_SSE2/RAPIDJSON_SSE42/RAPIDJSON_NEON/RAPIDJSON_SIMD + +/*! \def RAPIDJSON_SIMD + \ingroup RAPIDJSON_CONFIG + \brief Enable SSE2/SSE4.2/Neon optimization. + + RapidJSON supports optimized implementations for some parsing operations + based on the SSE2, SSE4.2 or NEon SIMD extensions on modern Intel + or ARM compatible processors. + + To enable these optimizations, three different symbols can be defined; + \code + // Enable SSE2 optimization. + #define RAPIDJSON_SSE2 + + // Enable SSE4.2 optimization. + #define RAPIDJSON_SSE42 + \endcode + + // Enable ARM Neon optimization. + #define RAPIDJSON_NEON + \endcode + + \c RAPIDJSON_SSE42 takes precedence over SSE2, if both are defined. + + If any of these symbols is defined, RapidJSON defines the macro + \c RAPIDJSON_SIMD to indicate the availability of the optimized code. +*/ +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) \ + || defined(RAPIDJSON_NEON) || defined(RAPIDJSON_DOXYGEN_RUNNING) +#define RAPIDJSON_SIMD +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_SIZETYPEDEFINE + +#ifndef RAPIDJSON_NO_SIZETYPEDEFINE +/*! \def RAPIDJSON_NO_SIZETYPEDEFINE + \ingroup RAPIDJSON_CONFIG + \brief User-provided \c SizeType definition. + + In order to avoid using 32-bit size types for indexing strings and arrays, + define this preprocessor symbol and provide the type rapidjson::SizeType + before including RapidJSON: + \code + #define RAPIDJSON_NO_SIZETYPEDEFINE + namespace rapidjson { typedef ::std::size_t SizeType; } + #include "rapidjson/..." + \endcode + + \see rapidjson::SizeType +*/ +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_NO_SIZETYPEDEFINE +#endif +RAPIDJSON_NAMESPACE_BEGIN +//! Size type (for string lengths, array sizes, etc.) +/*! RapidJSON uses 32-bit array/string indices even on 64-bit platforms, + instead of using \c size_t. Users may override the SizeType by defining + \ref RAPIDJSON_NO_SIZETYPEDEFINE. +*/ +typedef unsigned SizeType; +RAPIDJSON_NAMESPACE_END +#endif + +// always import std::size_t to rapidjson namespace +RAPIDJSON_NAMESPACE_BEGIN +using std::size_t; +RAPIDJSON_NAMESPACE_END + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ASSERT + +//! Assertion. +/*! \ingroup RAPIDJSON_CONFIG + By default, rapidjson uses C \c assert() for internal assertions. + User can override it by defining RAPIDJSON_ASSERT(x) macro. + + \note Parsing errors are handled and can be customized by the + \ref RAPIDJSON_ERRORS APIs. +*/ +#ifndef RAPIDJSON_ASSERT +#include +#define RAPIDJSON_ASSERT(x) /*assert(x)*/ +#endif // RAPIDJSON_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_STATIC_ASSERT + +// Prefer C++11 static_assert, if available +#ifndef RAPIDJSON_STATIC_ASSERT +#if __cplusplus >= 201103L || ( defined(_MSC_VER) && _MSC_VER >= 1800 ) +#define RAPIDJSON_STATIC_ASSERT(x) \ + static_assert(x, RAPIDJSON_STRINGIFY(x)) +#endif // C++11 +#endif // RAPIDJSON_STATIC_ASSERT + +// Adopt C++03 implementation from boost +#ifndef RAPIDJSON_STATIC_ASSERT +#ifndef __clang__ +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#endif +RAPIDJSON_NAMESPACE_BEGIN +template struct STATIC_ASSERTION_FAILURE; +template <> struct STATIC_ASSERTION_FAILURE { enum { value = 1 }; }; +template struct StaticAssertTest {}; +RAPIDJSON_NAMESPACE_END + +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE __attribute__((unused)) +#else +#define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE +#endif +#ifndef __clang__ +//!@endcond +#endif + +/*! \def RAPIDJSON_STATIC_ASSERT + \brief (Internal) macro to check for conditions at compile-time + \param x compile-time condition + \hideinitializer + */ +#define RAPIDJSON_STATIC_ASSERT(x) \ + typedef ::RAPIDJSON_NAMESPACE::StaticAssertTest< \ + sizeof(::RAPIDJSON_NAMESPACE::STATIC_ASSERTION_FAILURE)> \ + RAPIDJSON_JOIN(StaticAssertTypedef, __LINE__) RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE +#endif // RAPIDJSON_STATIC_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_LIKELY, RAPIDJSON_UNLIKELY + +//! Compiler branching hint for expression with high probability to be true. +/*! + \ingroup RAPIDJSON_CONFIG + \param x Boolean expression likely to be true. +*/ +#ifndef RAPIDJSON_LIKELY +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_LIKELY(x) __builtin_expect(!!(x), 1) +#else +#define RAPIDJSON_LIKELY(x) (x) +#endif +#endif + +//! Compiler branching hint for expression with low probability to be true. +/*! + \ingroup RAPIDJSON_CONFIG + \param x Boolean expression unlikely to be true. +*/ +#ifndef RAPIDJSON_UNLIKELY +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define RAPIDJSON_UNLIKELY(x) (x) +#endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Helpers + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN + +#define RAPIDJSON_MULTILINEMACRO_BEGIN do { +#define RAPIDJSON_MULTILINEMACRO_END \ +} while((void)0, 0) + +// adopted from Boost +#define RAPIDJSON_VERSION_CODE(x,y,z) \ + (((x)*100000) + ((y)*100) + (z)) + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_DIAG_PUSH/POP, RAPIDJSON_DIAG_OFF + +#if defined(__GNUC__) +#define RAPIDJSON_GNUC \ + RAPIDJSON_VERSION_CODE(__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__) +#endif + +#if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,2,0)) + +#define RAPIDJSON_PRAGMA(x) _Pragma(RAPIDJSON_STRINGIFY(x)) +#define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(GCC diagnostic x) +#define RAPIDJSON_DIAG_OFF(x) \ + RAPIDJSON_DIAG_PRAGMA(ignored RAPIDJSON_STRINGIFY(RAPIDJSON_JOIN(-W,x))) + +// push/pop support in Clang and GCC>=4.6 +#if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) +#define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) +#define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) +#else // GCC >= 4.2, < 4.6 +#define RAPIDJSON_DIAG_PUSH /* ignored */ +#define RAPIDJSON_DIAG_POP /* ignored */ +#endif + +#elif defined(_MSC_VER) + +// pragma (MSVC specific) +#define RAPIDJSON_PRAGMA(x) __pragma(x) +#define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(warning(x)) + +#define RAPIDJSON_DIAG_OFF(x) RAPIDJSON_DIAG_PRAGMA(disable: x) +#define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) +#define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) + +#else + +#define RAPIDJSON_DIAG_OFF(x) /* ignored */ +#define RAPIDJSON_DIAG_PUSH /* ignored */ +#define RAPIDJSON_DIAG_POP /* ignored */ + +#endif // RAPIDJSON_DIAG_* + +/////////////////////////////////////////////////////////////////////////////// +// C++11 features + +#ifndef RAPIDJSON_HAS_CXX11_RVALUE_REFS +#if defined(__clang__) +#if __has_feature(cxx_rvalue_references) && \ + (defined(_MSC_VER) || defined(_LIBCPP_VERSION) || defined(__GLIBCXX__) && __GLIBCXX__ >= 20080306) +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 +#else +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 +#endif +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,3,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1600) || \ + (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x5140 && defined(__GXX_EXPERIMENTAL_CXX0X__)) + +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 +#else +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 +#endif +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + +#ifndef RAPIDJSON_HAS_CXX11_NOEXCEPT +#if defined(__clang__) +#define RAPIDJSON_HAS_CXX11_NOEXCEPT __has_feature(cxx_noexcept) +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1900) || \ + (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x5140 && defined(__GXX_EXPERIMENTAL_CXX0X__)) +#define RAPIDJSON_HAS_CXX11_NOEXCEPT 1 +#else +#define RAPIDJSON_HAS_CXX11_NOEXCEPT 0 +#endif +#endif +#if RAPIDJSON_HAS_CXX11_NOEXCEPT +#define RAPIDJSON_NOEXCEPT noexcept +#else +#define RAPIDJSON_NOEXCEPT /* noexcept */ +#endif // RAPIDJSON_HAS_CXX11_NOEXCEPT + +// no automatic detection, yet +#ifndef RAPIDJSON_HAS_CXX11_TYPETRAITS +#if (defined(_MSC_VER) && _MSC_VER >= 1700) +#define RAPIDJSON_HAS_CXX11_TYPETRAITS 1 +#else +#define RAPIDJSON_HAS_CXX11_TYPETRAITS 0 +#endif +#endif + +#ifndef RAPIDJSON_HAS_CXX11_RANGE_FOR +#if defined(__clang__) +#define RAPIDJSON_HAS_CXX11_RANGE_FOR __has_feature(cxx_range_for) +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1700) || \ + (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x5140 && defined(__GXX_EXPERIMENTAL_CXX0X__)) +#define RAPIDJSON_HAS_CXX11_RANGE_FOR 1 +#else +#define RAPIDJSON_HAS_CXX11_RANGE_FOR 0 +#endif +#endif // RAPIDJSON_HAS_CXX11_RANGE_FOR + +//!@endcond + +//! Assertion (in non-throwing contexts). + /*! \ingroup RAPIDJSON_CONFIG + Some functions provide a \c noexcept guarantee, if the compiler supports it. + In these cases, the \ref RAPIDJSON_ASSERT macro cannot be overridden to + throw an exception. This macro adds a separate customization point for + such cases. + + Defaults to C \c assert() (as \ref RAPIDJSON_ASSERT), if \c noexcept is + supported, and to \ref RAPIDJSON_ASSERT otherwise. + */ + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NOEXCEPT_ASSERT + +#ifndef RAPIDJSON_NOEXCEPT_ASSERT +#ifdef RAPIDJSON_ASSERT_THROWS +#if RAPIDJSON_HAS_CXX11_NOEXCEPT +#define RAPIDJSON_NOEXCEPT_ASSERT(x) +#else +#define RAPIDJSON_NOEXCEPT_ASSERT(x) RAPIDJSON_ASSERT(x) +#endif // RAPIDJSON_HAS_CXX11_NOEXCEPT +#else +#define RAPIDJSON_NOEXCEPT_ASSERT(x) RAPIDJSON_ASSERT(x) +#endif // RAPIDJSON_ASSERT_THROWS +#endif // RAPIDJSON_NOEXCEPT_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// new/delete + +#ifndef RAPIDJSON_NEW +///! customization point for global \c new +#define RAPIDJSON_NEW(TypeName) new TypeName +#endif +#ifndef RAPIDJSON_DELETE +///! customization point for global \c delete +#define RAPIDJSON_DELETE(x) delete x +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Type + +/*! \namespace rapidjson + \brief main RapidJSON namespace + \see RAPIDJSON_NAMESPACE +*/ +RAPIDJSON_NAMESPACE_BEGIN + +//! Type of JSON value +enum Type { + kNullType = 0, //!< null + kFalseType = 1, //!< false + kTrueType = 2, //!< true + kObjectType = 3, //!< object + kArrayType = 4, //!< array + kStringType = 5, //!< string + kNumberType = 6 //!< number +}; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/reader.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/reader.h new file mode 100755 index 000000000..44a6bcd30 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/reader.h @@ -0,0 +1,2230 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_READER_H_ +#define RAPIDJSON_READER_H_ + +/*! \file reader.h */ + +#include "allocators.h" +#include "stream.h" +#include "encodedstream.h" +#include "internal/meta.h" +#include "internal/stack.h" +#include "internal/strtod.h" +#include + +#if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) +#include +#pragma intrinsic(_BitScanForward) +#endif +#ifdef RAPIDJSON_SSE42 +#include +#elif defined(RAPIDJSON_SSE2) +#include +#elif defined(RAPIDJSON_NEON) +#include +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(old-style-cast) +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +RAPIDJSON_DIAG_OFF(4702) // unreachable code +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define RAPIDJSON_NOTHING /* deliberately empty */ +#ifndef RAPIDJSON_PARSE_ERROR_EARLY_RETURN +#define RAPIDJSON_PARSE_ERROR_EARLY_RETURN(value) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + if (RAPIDJSON_UNLIKELY(HasParseError())) { return value; } \ + RAPIDJSON_MULTILINEMACRO_END +#endif +#define RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID \ + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(RAPIDJSON_NOTHING) +//!@endcond + +/*! \def RAPIDJSON_PARSE_ERROR_NORETURN + \ingroup RAPIDJSON_ERRORS + \brief Macro to indicate a parse error. + \param parseErrorCode \ref rapidjson::ParseErrorCode of the error + \param offset position of the error in JSON input (\c size_t) + + This macros can be used as a customization point for the internal + error handling mechanism of RapidJSON. + + A common usage model is to throw an exception instead of requiring the + caller to explicitly check the \ref rapidjson::GenericReader::Parse's + return value: + + \code + #define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode,offset) \ + throw ParseException(parseErrorCode, #parseErrorCode, offset) + + #include // std::runtime_error + #include "rapidjson/error/error.h" // rapidjson::ParseResult + + struct ParseException : std::runtime_error, rapidjson::ParseResult { + ParseException(rapidjson::ParseErrorCode code, const char* msg, size_t offset) + : std::runtime_error(msg), ParseResult(code, offset) {} + }; + + #include "rapidjson/reader.h" + \endcode + + \see RAPIDJSON_PARSE_ERROR, rapidjson::GenericReader::Parse + */ +#ifndef RAPIDJSON_PARSE_ERROR_NORETURN +#define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + RAPIDJSON_ASSERT(!HasParseError()); /* Error can only be assigned once */ \ + SetParseError(parseErrorCode, offset); \ + RAPIDJSON_MULTILINEMACRO_END +#endif + +/*! \def RAPIDJSON_PARSE_ERROR + \ingroup RAPIDJSON_ERRORS + \brief (Internal) macro to indicate and handle a parse error. + \param parseErrorCode \ref rapidjson::ParseErrorCode of the error + \param offset position of the error in JSON input (\c size_t) + + Invokes RAPIDJSON_PARSE_ERROR_NORETURN and stops the parsing. + + \see RAPIDJSON_PARSE_ERROR_NORETURN + \hideinitializer + */ +#ifndef RAPIDJSON_PARSE_ERROR +#define RAPIDJSON_PARSE_ERROR(parseErrorCode, offset) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset); \ + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; \ + RAPIDJSON_MULTILINEMACRO_END +#endif + +#include "error/error.h" // ParseErrorCode, ParseResult + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// ParseFlag + +/*! \def RAPIDJSON_PARSE_DEFAULT_FLAGS + \ingroup RAPIDJSON_CONFIG + \brief User-defined kParseDefaultFlags definition. + + User can define this as any \c ParseFlag combinations. +*/ +#ifndef RAPIDJSON_PARSE_DEFAULT_FLAGS +#define RAPIDJSON_PARSE_DEFAULT_FLAGS kParseNoFlags +#endif + +//! Combination of parseFlags +/*! \see Reader::Parse, Document::Parse, Document::ParseInsitu, Document::ParseStream + */ +enum ParseFlag { + kParseNoFlags = 0, //!< No flags are set. + kParseInsituFlag = 1, //!< In-situ(destructive) parsing. + kParseValidateEncodingFlag = 2, //!< Validate encoding of JSON strings. + kParseIterativeFlag = 4, //!< Iterative(constant complexity in terms of function call stack size) parsing. + kParseStopWhenDoneFlag = 8, //!< After parsing a complete JSON root from stream, stop further processing the rest of stream. When this flag is used, parser will not generate kParseErrorDocumentRootNotSingular error. + kParseFullPrecisionFlag = 16, //!< Parse number in full precision (but slower). + kParseCommentsFlag = 32, //!< Allow one-line (//) and multi-line (/**/) comments. + kParseNumbersAsStringsFlag = 64, //!< Parse all numbers (ints/doubles) as strings. + kParseTrailingCommasFlag = 128, //!< Allow trailing commas at the end of objects and arrays. + kParseNanAndInfFlag = 256, //!< Allow parsing NaN, Inf, Infinity, -Inf and -Infinity as doubles. + kParseDefaultFlags = RAPIDJSON_PARSE_DEFAULT_FLAGS //!< Default parse flags. Can be customized by defining RAPIDJSON_PARSE_DEFAULT_FLAGS +}; + +/////////////////////////////////////////////////////////////////////////////// +// Handler + +/*! \class rapidjson::Handler + \brief Concept for receiving events from GenericReader upon parsing. + The functions return true if no error occurs. If they return false, + the event publisher should terminate the process. +\code +concept Handler { + typename Ch; + + bool Null(); + bool Bool(bool b); + bool Int(int i); + bool Uint(unsigned i); + bool Int64(int64_t i); + bool Uint64(uint64_t i); + bool Double(double d); + /// enabled via kParseNumbersAsStringsFlag, string is not null-terminated (use length) + bool RawNumber(const Ch* str, SizeType length, bool copy); + bool String(const Ch* str, SizeType length, bool copy); + bool StartObject(); + bool Key(const Ch* str, SizeType length, bool copy); + bool EndObject(SizeType memberCount); + bool StartArray(); + bool EndArray(SizeType elementCount); +}; +\endcode +*/ +/////////////////////////////////////////////////////////////////////////////// +// BaseReaderHandler + +//! Default implementation of Handler. +/*! This can be used as base class of any reader handler. + \note implements Handler concept +*/ +template, typename Derived = void> +struct BaseReaderHandler { + typedef typename Encoding::Ch Ch; + + typedef typename internal::SelectIf, BaseReaderHandler, Derived>::Type Override; + + bool Default() { return true; } + bool Null() { return static_cast(*this).Default(); } + bool Bool(bool) { return static_cast(*this).Default(); } + bool Int(int) { return static_cast(*this).Default(); } + bool Uint(unsigned) { return static_cast(*this).Default(); } + bool Int64(int64_t) { return static_cast(*this).Default(); } + bool Uint64(uint64_t) { return static_cast(*this).Default(); } + bool Double(double) { return static_cast(*this).Default(); } + /// enabled via kParseNumbersAsStringsFlag, string is not null-terminated (use length) + bool RawNumber(const Ch* str, SizeType len, bool copy) { return static_cast(*this).String(str, len, copy); } + bool String(const Ch*, SizeType, bool) { return static_cast(*this).Default(); } + bool StartObject() { return static_cast(*this).Default(); } + bool Key(const Ch* str, SizeType len, bool copy) { return static_cast(*this).String(str, len, copy); } + bool EndObject(SizeType) { return static_cast(*this).Default(); } + bool StartArray() { return static_cast(*this).Default(); } + bool EndArray(SizeType) { return static_cast(*this).Default(); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamLocalCopy + +namespace internal { + +template::copyOptimization> +class StreamLocalCopy; + +//! Do copy optimization. +template +class StreamLocalCopy { +public: + StreamLocalCopy(Stream& original) : s(original), original_(original) {} + ~StreamLocalCopy() { original_ = s; } + + Stream s; + +private: + StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; + + Stream& original_; +}; + +//! Keep reference. +template +class StreamLocalCopy { +public: + StreamLocalCopy(Stream& original) : s(original) {} + + Stream& s; + +private: + StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; +}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// SkipWhitespace + +//! Skip the JSON white spaces in a stream. +/*! \param is A input stream for skipping white spaces. + \note This function has SSE2/SSE4.2 specialization. +*/ +template +void SkipWhitespace(InputStream& is) { + internal::StreamLocalCopy copy(is); + InputStream& s(copy.s); + + typename InputStream::Ch c; + while ((c = s.Peek()) == ' ' || c == '\n' || c == '\r' || c == '\t') + s.Take(); +} + +inline const char* SkipWhitespace(const char* p, const char* end) { + while (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + return p; +} + +#ifdef RAPIDJSON_SSE42 +//! Skip whitespace with SSE 4.2 pcmpistrm instruction, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // The rest of string using SIMD + static const char whitespace[16] = " \n\r\t"; + const __m128i w = _mm_loadu_si128(reinterpret_cast(&whitespace[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const int r = _mm_cmpistri(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_LEAST_SIGNIFICANT | _SIDD_NEGATIVE_POLARITY); + if (r != 16) // some of characters is non-whitespace + return p + r; + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + // The middle of string using SIMD + static const char whitespace[16] = " \n\r\t"; + const __m128i w = _mm_loadu_si128(reinterpret_cast(&whitespace[0])); + + for (; p <= end - 16; p += 16) { + const __m128i s = _mm_loadu_si128(reinterpret_cast(p)); + const int r = _mm_cmpistri(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_LEAST_SIGNIFICANT | _SIDD_NEGATIVE_POLARITY); + if (r != 16) // some of characters is non-whitespace + return p + r; + } + + return SkipWhitespace(p, end); +} + +#elif defined(RAPIDJSON_SSE2) + +//! Skip whitespace with SSE2 instructions, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // The rest of string + #define C16(c) { c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c } + static const char whitespaces[4][16] = { C16(' '), C16('\n'), C16('\r'), C16('\t') }; + #undef C16 + + const __m128i w0 = _mm_loadu_si128(reinterpret_cast(&whitespaces[0][0])); + const __m128i w1 = _mm_loadu_si128(reinterpret_cast(&whitespaces[1][0])); + const __m128i w2 = _mm_loadu_si128(reinterpret_cast(&whitespaces[2][0])); + const __m128i w3 = _mm_loadu_si128(reinterpret_cast(&whitespaces[3][0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + __m128i x = _mm_cmpeq_epi8(s, w0); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); + unsigned short r = static_cast(~_mm_movemask_epi8(x)); + if (r != 0) { // some of characters may be non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + // The rest of string + #define C16(c) { c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c } + static const char whitespaces[4][16] = { C16(' '), C16('\n'), C16('\r'), C16('\t') }; + #undef C16 + + const __m128i w0 = _mm_loadu_si128(reinterpret_cast(&whitespaces[0][0])); + const __m128i w1 = _mm_loadu_si128(reinterpret_cast(&whitespaces[1][0])); + const __m128i w2 = _mm_loadu_si128(reinterpret_cast(&whitespaces[2][0])); + const __m128i w3 = _mm_loadu_si128(reinterpret_cast(&whitespaces[3][0])); + + for (; p <= end - 16; p += 16) { + const __m128i s = _mm_loadu_si128(reinterpret_cast(p)); + __m128i x = _mm_cmpeq_epi8(s, w0); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); + unsigned short r = static_cast(~_mm_movemask_epi8(x)); + if (r != 0) { // some of characters may be non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } + + return SkipWhitespace(p, end); +} + +#elif defined(RAPIDJSON_NEON) + +//! Skip whitespace with ARM Neon instructions, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + const uint8x16_t w0 = vmovq_n_u8(' '); + const uint8x16_t w1 = vmovq_n_u8('\n'); + const uint8x16_t w2 = vmovq_n_u8('\r'); + const uint8x16_t w3 = vmovq_n_u8('\t'); + + for (;; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, w0); + x = vorrq_u8(x, vceqq_u8(s, w1)); + x = vorrq_u8(x, vceqq_u8(s, w2)); + x = vorrq_u8(x, vceqq_u8(s, w3)); + + x = vmvnq_u8(x); // Negate + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract + uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract + + if (low == 0) { + if (high != 0) { + int lz =__builtin_clzll(high);; + return p + 8 + (lz >> 3); + } + } else { + int lz = __builtin_clzll(low);; + return p + (lz >> 3); + } + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + const uint8x16_t w0 = vmovq_n_u8(' '); + const uint8x16_t w1 = vmovq_n_u8('\n'); + const uint8x16_t w2 = vmovq_n_u8('\r'); + const uint8x16_t w3 = vmovq_n_u8('\t'); + + for (; p <= end - 16; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, w0); + x = vorrq_u8(x, vceqq_u8(s, w1)); + x = vorrq_u8(x, vceqq_u8(s, w2)); + x = vorrq_u8(x, vceqq_u8(s, w3)); + + x = vmvnq_u8(x); // Negate + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract + uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract + + if (low == 0) { + if (high != 0) { + int lz = __builtin_clzll(high); + return p + 8 + (lz >> 3); + } + } else { + int lz = __builtin_clzll(low); + return p + (lz >> 3); + } + } + + return SkipWhitespace(p, end); +} + +#endif // RAPIDJSON_NEON + +#ifdef RAPIDJSON_SIMD +//! Template function specialization for InsituStringStream +template<> inline void SkipWhitespace(InsituStringStream& is) { + is.src_ = const_cast(SkipWhitespace_SIMD(is.src_)); +} + +//! Template function specialization for StringStream +template<> inline void SkipWhitespace(StringStream& is) { + is.src_ = SkipWhitespace_SIMD(is.src_); +} + +template<> inline void SkipWhitespace(EncodedInputStream, MemoryStream>& is) { + is.is_.src_ = SkipWhitespace_SIMD(is.is_.src_, is.is_.end_); +} +#endif // RAPIDJSON_SIMD + +/////////////////////////////////////////////////////////////////////////////// +// GenericReader + +//! SAX-style JSON parser. Use \ref Reader for UTF8 encoding and default allocator. +/*! GenericReader parses JSON text from a stream, and send events synchronously to an + object implementing Handler concept. + + It needs to allocate a stack for storing a single decoded string during + non-destructive parsing. + + For in-situ parsing, the decoded string is directly written to the source + text string, no temporary buffer is required. + + A GenericReader object can be reused for parsing multiple JSON text. + + \tparam SourceEncoding Encoding of the input stream. + \tparam TargetEncoding Encoding of the parse output. + \tparam StackAllocator Allocator type for stack. +*/ +template +class GenericReader { +public: + typedef typename SourceEncoding::Ch Ch; //!< SourceEncoding character type + + //! Constructor. + /*! \param stackAllocator Optional allocator for allocating stack memory. (Only use for non-destructive parsing) + \param stackCapacity stack capacity in bytes for storing a single decoded string. (Only use for non-destructive parsing) + */ + GenericReader(StackAllocator* stackAllocator = 0, size_t stackCapacity = kDefaultStackCapacity) : + stack_(stackAllocator, stackCapacity), parseResult_(), state_(IterativeParsingStartState) {} + + //! Parse JSON text. + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept. + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + ParseResult Parse(InputStream& is, Handler& handler) { + if (parseFlags & kParseIterativeFlag) + return IterativeParse(is, handler); + + parseResult_.Clear(); + + ClearStackOnExit scope(*this); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (RAPIDJSON_UNLIKELY(is.Peek() == '\0')) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentEmpty, is.Tell()); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + else { + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (!(parseFlags & kParseStopWhenDoneFlag)) { + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (RAPIDJSON_UNLIKELY(is.Peek() != '\0')) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentRootNotSingular, is.Tell()); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + } + } + + return parseResult_; + } + + //! Parse JSON text (with \ref kParseDefaultFlags) + /*! \tparam InputStream Type of input stream, implementing Stream concept + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + ParseResult Parse(InputStream& is, Handler& handler) { + return Parse(is, handler); + } + + //! Initialize JSON text token-by-token parsing + /*! + */ + void IterativeParseInit() { + parseResult_.Clear(); + state_ = IterativeParsingStartState; + } + + //! Parse one token from JSON text + /*! \tparam InputStream Type of input stream, implementing Stream concept + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + bool IterativeParseNext(InputStream& is, Handler& handler) { + while (RAPIDJSON_LIKELY(is.Peek() != '\0')) { + SkipWhitespaceAndComments(is); + + Token t = Tokenize(is.Peek()); + IterativeParsingState n = Predict(state_, t); + IterativeParsingState d = Transit(state_, t, n, is, handler); + + // If we've finished or hit an error... + if (RAPIDJSON_UNLIKELY(IsIterativeParsingCompleteState(d))) { + // Report errors. + if (d == IterativeParsingErrorState) { + HandleError(state_, is); + return false; + } + + // Transition to the finish state. + RAPIDJSON_ASSERT(d == IterativeParsingFinishState); + state_ = d; + + // If StopWhenDone is not set... + if (!(parseFlags & kParseStopWhenDoneFlag)) { + // ... and extra non-whitespace data is found... + SkipWhitespaceAndComments(is); + if (is.Peek() != '\0') { + // ... this is considered an error. + HandleError(state_, is); + return false; + } + } + + // Success! We are done! + return true; + } + + // Transition to the new state. + state_ = d; + + // If we parsed anything other than a delimiter, we invoked the handler, so we can return true now. + if (!IsIterativeParsingDelimiterState(n)) + return true; + } + + // We reached the end of file. + stack_.Clear(); + + if (state_ != IterativeParsingFinishState) { + HandleError(state_, is); + return false; + } + + return true; + } + + //! Check if token-by-token parsing JSON text is complete + /*! \return Whether the JSON has been fully decoded. + */ + RAPIDJSON_FORCEINLINE bool IterativeParseComplete() const { + return IsIterativeParsingCompleteState(state_); + } + + //! Whether a parse error has occurred in the last parsing. + bool HasParseError() const { return parseResult_.IsError(); } + + //! Get the \ref ParseErrorCode of last parsing. + ParseErrorCode GetParseErrorCode() const { return parseResult_.Code(); } + + //! Get the position of last parsing error in input, 0 otherwise. + size_t GetErrorOffset() const { return parseResult_.Offset(); } + +protected: + void SetParseError(ParseErrorCode code, size_t offset) { parseResult_.Set(code, offset); } + +private: + // Prohibit copy constructor & assignment operator. + GenericReader(const GenericReader&); + GenericReader& operator=(const GenericReader&); + + void ClearStack() { stack_.Clear(); } + + // clear stack on any exit from ParseStream, e.g. due to exception + struct ClearStackOnExit { + explicit ClearStackOnExit(GenericReader& r) : r_(r) {} + ~ClearStackOnExit() { r_.ClearStack(); } + private: + GenericReader& r_; + ClearStackOnExit(const ClearStackOnExit&); + ClearStackOnExit& operator=(const ClearStackOnExit&); + }; + + template + void SkipWhitespaceAndComments(InputStream& is) { + SkipWhitespace(is); + + if (parseFlags & kParseCommentsFlag) { + while (RAPIDJSON_UNLIKELY(Consume(is, '/'))) { + if (Consume(is, '*')) { + while (true) { + if (RAPIDJSON_UNLIKELY(is.Peek() == '\0')) + RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); + else if (Consume(is, '*')) { + if (Consume(is, '/')) + break; + } + else + is.Take(); + } + } + else if (RAPIDJSON_LIKELY(Consume(is, '/'))) + while (is.Peek() != '\0' && is.Take() != '\n') {} + else + RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); + + SkipWhitespace(is); + } + } + } + + // Parse object: { string : value, ... } + template + void ParseObject(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == '{'); + is.Take(); // Skip '{' + + if (RAPIDJSON_UNLIKELY(!handler.StartObject())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, '}')) { + if (RAPIDJSON_UNLIKELY(!handler.EndObject(0))) // empty object + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + + for (SizeType memberCount = 0;;) { + if (RAPIDJSON_UNLIKELY(is.Peek() != '"')) + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); + + ParseString(is, handler, true); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (RAPIDJSON_UNLIKELY(!Consume(is, ':'))) + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ++memberCount; + + switch (is.Peek()) { + case ',': + is.Take(); + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + break; + case '}': + is.Take(); + if (RAPIDJSON_UNLIKELY(!handler.EndObject(memberCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + default: + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); break; // This useless break is only for making warning and coverage happy + } + + if (parseFlags & kParseTrailingCommasFlag) { + if (is.Peek() == '}') { + if (RAPIDJSON_UNLIKELY(!handler.EndObject(memberCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + is.Take(); + return; + } + } + } + } + + // Parse array: [ value, ... ] + template + void ParseArray(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == '['); + is.Take(); // Skip '[' + + if (RAPIDJSON_UNLIKELY(!handler.StartArray())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, ']')) { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(0))) // empty array + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + + for (SizeType elementCount = 0;;) { + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ++elementCount; + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, ',')) { + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + } + else if (Consume(is, ']')) { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(elementCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); + + if (parseFlags & kParseTrailingCommasFlag) { + if (is.Peek() == ']') { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(elementCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + is.Take(); + return; + } + } + } + } + + template + void ParseNull(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 'n'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'u') && Consume(is, 'l') && Consume(is, 'l'))) { + if (RAPIDJSON_UNLIKELY(!handler.Null())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template + void ParseTrue(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 't'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'r') && Consume(is, 'u') && Consume(is, 'e'))) { + if (RAPIDJSON_UNLIKELY(!handler.Bool(true))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template + void ParseFalse(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 'f'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'a') && Consume(is, 'l') && Consume(is, 's') && Consume(is, 'e'))) { + if (RAPIDJSON_UNLIKELY(!handler.Bool(false))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template + RAPIDJSON_FORCEINLINE static bool Consume(InputStream& is, typename InputStream::Ch expect) { + if (RAPIDJSON_LIKELY(is.Peek() == expect)) { + is.Take(); + return true; + } + else + return false; + } + + // Helper function to parse four hexadecimal digits in \uXXXX in ParseString(). + template + unsigned ParseHex4(InputStream& is, size_t escapeOffset) { + unsigned codepoint = 0; + for (int i = 0; i < 4; i++) { + Ch c = is.Peek(); + codepoint <<= 4; + codepoint += static_cast(c); + if (c >= '0' && c <= '9') + codepoint -= '0'; + else if (c >= 'A' && c <= 'F') + codepoint -= 'A' - 10; + else if (c >= 'a' && c <= 'f') + codepoint -= 'a' - 10; + else { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorStringUnicodeEscapeInvalidHex, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(0); + } + is.Take(); + } + return codepoint; + } + + template + class StackStream { + public: + typedef CharType Ch; + + StackStream(internal::Stack& stack) : stack_(stack), length_(0) {} + RAPIDJSON_FORCEINLINE void Put(Ch c) { + *stack_.template Push() = c; + ++length_; + } + + RAPIDJSON_FORCEINLINE void* Push(SizeType count) { + length_ += count; + return stack_.template Push(count); + } + + size_t Length() const { return length_; } + + Ch* Pop() { + return stack_.template Pop(length_); + } + + private: + StackStream(const StackStream&); + StackStream& operator=(const StackStream&); + + internal::Stack& stack_; + SizeType length_; + }; + + // Parse string and generate String event. Different code paths for kParseInsituFlag. + template + void ParseString(InputStream& is, Handler& handler, bool isKey = false) { + internal::StreamLocalCopy copy(is); + InputStream& s(copy.s); + + RAPIDJSON_ASSERT(s.Peek() == '\"'); + s.Take(); // Skip '\"' + + bool success = false; + if (parseFlags & kParseInsituFlag) { + typename InputStream::Ch *head = s.PutBegin(); + ParseStringToStream(s, s); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + size_t length = s.PutEnd(head) - 1; + RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); + const typename TargetEncoding::Ch* const str = reinterpret_cast(head); + success = (isKey ? handler.Key(str, SizeType(length), false) : handler.String(str, SizeType(length), false)); + } + else { + StackStream stackStream(stack_); + ParseStringToStream(s, stackStream); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + SizeType length = static_cast(stackStream.Length()) - 1; + const typename TargetEncoding::Ch* const str = stackStream.Pop(); + success = (isKey ? handler.Key(str, length, true) : handler.String(str, length, true)); + } + if (RAPIDJSON_UNLIKELY(!success)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, s.Tell()); + } + + // Parse string to an output is + // This function handles the prefix/suffix double quotes, escaping, and optional encoding validation. + template + RAPIDJSON_FORCEINLINE void ParseStringToStream(InputStream& is, OutputStream& os) { +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + static const char escape[256] = { + Z16, Z16, 0, 0,'\"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'/', + Z16, Z16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, + 0, 0,'\b', 0, 0, 0,'\f', 0, 0, 0, 0, 0, 0, 0,'\n', 0, + 0, 0,'\r', 0,'\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 + }; +#undef Z16 +//!@endcond + + for (;;) { + // Scan and copy string before "\\\"" or < 0x20. This is an optional optimzation. + if (!(parseFlags & kParseValidateEncodingFlag)) + ScanCopyUnescapedString(is, os); + + Ch c = is.Peek(); + if (RAPIDJSON_UNLIKELY(c == '\\')) { // Escape + size_t escapeOffset = is.Tell(); // For invalid escaping, report the initial '\\' as error offset + is.Take(); + Ch e = is.Peek(); + if ((sizeof(Ch) == 1 || unsigned(e) < 256) && RAPIDJSON_LIKELY(escape[static_cast(e)])) { + is.Take(); + os.Put(static_cast(escape[static_cast(e)])); + } + else if (RAPIDJSON_LIKELY(e == 'u')) { // Unicode + is.Take(); + unsigned codepoint = ParseHex4(is, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (RAPIDJSON_UNLIKELY(codepoint >= 0xD800 && codepoint <= 0xDBFF)) { + // Handle UTF-16 surrogate pair + if (RAPIDJSON_UNLIKELY(!Consume(is, '\\') || !Consume(is, 'u'))) + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); + unsigned codepoint2 = ParseHex4(is, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (RAPIDJSON_UNLIKELY(codepoint2 < 0xDC00 || codepoint2 > 0xDFFF)) + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); + codepoint = (((codepoint - 0xD800) << 10) | (codepoint2 - 0xDC00)) + 0x10000; + } + TEncoding::Encode(os, codepoint); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorStringEscapeInvalid, escapeOffset); + } + else if (RAPIDJSON_UNLIKELY(c == '"')) { // Closing double quote + is.Take(); + os.Put('\0'); // null-terminate the string + return; + } + else if (RAPIDJSON_UNLIKELY(static_cast(c) < 0x20)) { // RFC 4627: unescaped = %x20-21 / %x23-5B / %x5D-10FFFF + if (c == '\0') + RAPIDJSON_PARSE_ERROR(kParseErrorStringMissQuotationMark, is.Tell()); + else + RAPIDJSON_PARSE_ERROR(kParseErrorStringInvalidEncoding, is.Tell()); + } + else { + size_t offset = is.Tell(); + if (RAPIDJSON_UNLIKELY((parseFlags & kParseValidateEncodingFlag ? + !Transcoder::Validate(is, os) : + !Transcoder::Transcode(is, os)))) + RAPIDJSON_PARSE_ERROR(kParseErrorStringInvalidEncoding, offset); + } + } + } + + template + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InputStream&, OutputStream&) { + // Do nothing for generic version + } + +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) + // StringStream -> StackStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(StringStream& is, StackStream& os) { + const char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + return; + } + else + os.Put(*p++); + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + SizeType length; + #ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; + #else + length = static_cast(__builtin_ffs(r) - 1); + #endif + if (length != 0) { + char* q = reinterpret_cast(os.Push(length)); + for (size_t i = 0; i < length; i++) + q[i] = p[i]; + + p += length; + } + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(os.Push(16)), s); + } + + is.src_ = p; + } + + // InsituStringStream -> InsituStringStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InsituStringStream& is, InsituStringStream& os) { + RAPIDJSON_ASSERT(&is == &os); + (void)os; + + if (is.src_ == is.dst_) { + SkipUnescapedString(is); + return; + } + + char* p = is.src_; + char *q = is.dst_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + is.dst_ = q; + return; + } + else + *q++ = *p++; + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (;; p += 16, q += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + size_t length; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; +#else + length = static_cast(__builtin_ffs(r) - 1); +#endif + for (const char* pend = p + length; p != pend; ) + *q++ = *p++; + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(q), s); + } + + is.src_ = p; + is.dst_ = q; + } + + // When read/write pointers are the same for insitu stream, just skip unescaped characters + static RAPIDJSON_FORCEINLINE void SkipUnescapedString(InsituStringStream& is) { + RAPIDJSON_ASSERT(is.src_ == is.dst_); + char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + for (; p != nextAligned; p++) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = is.dst_ = p; + return; + } + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + size_t length; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; +#else + length = static_cast(__builtin_ffs(r) - 1); +#endif + p += length; + break; + } + } + + is.src_ = is.dst_ = p; + } +#elif defined(RAPIDJSON_NEON) + // StringStream -> StackStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(StringStream& is, StackStream& os) { + const char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + return; + } + else + os.Put(*p++); + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (;; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract + uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract + + SizeType length = 0; + bool escaped = false; + if (low == 0) { + if (high != 0) { + unsigned lz = (unsigned)__builtin_clzll(high);; + length = 8 + (lz >> 3); + escaped = true; + } + } else { + unsigned lz = (unsigned)__builtin_clzll(low);; + length = lz >> 3; + escaped = true; + } + if (RAPIDJSON_UNLIKELY(escaped)) { // some of characters is escaped + if (length != 0) { + char* q = reinterpret_cast(os.Push(length)); + for (size_t i = 0; i < length; i++) + q[i] = p[i]; + + p += length; + } + break; + } + vst1q_u8(reinterpret_cast(os.Push(16)), s); + } + + is.src_ = p; + } + + // InsituStringStream -> InsituStringStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InsituStringStream& is, InsituStringStream& os) { + RAPIDJSON_ASSERT(&is == &os); + (void)os; + + if (is.src_ == is.dst_) { + SkipUnescapedString(is); + return; + } + + char* p = is.src_; + char *q = is.dst_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + is.dst_ = q; + return; + } + else + *q++ = *p++; + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (;; p += 16, q += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract + uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract + + SizeType length = 0; + bool escaped = false; + if (low == 0) { + if (high != 0) { + unsigned lz = (unsigned)__builtin_clzll(high); + length = 8 + (lz >> 3); + escaped = true; + } + } else { + unsigned lz = (unsigned)__builtin_clzll(low); + length = lz >> 3; + escaped = true; + } + if (RAPIDJSON_UNLIKELY(escaped)) { // some of characters is escaped + for (const char* pend = p + length; p != pend; ) { + *q++ = *p++; + } + break; + } + vst1q_u8(reinterpret_cast(q), s); + } + + is.src_ = p; + is.dst_ = q; + } + + // When read/write pointers are the same for insitu stream, just skip unescaped characters + static RAPIDJSON_FORCEINLINE void SkipUnescapedString(InsituStringStream& is) { + RAPIDJSON_ASSERT(is.src_ == is.dst_); + char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + for (; p != nextAligned; p++) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = is.dst_ = p; + return; + } + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (;; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract + uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract + + if (low == 0) { + if (high != 0) { + int lz = __builtin_clzll(high); + p += 8 + (lz >> 3); + break; + } + } else { + int lz = __builtin_clzll(low); + p += lz >> 3; + break; + } + } + + is.src_ = is.dst_ = p; + } +#endif // RAPIDJSON_NEON + + template + class NumberStream; + + template + class NumberStream { + public: + typedef typename InputStream::Ch Ch; + + NumberStream(GenericReader& reader, InputStream& s) : is(s) { (void)reader; } + + RAPIDJSON_FORCEINLINE Ch Peek() const { return is.Peek(); } + RAPIDJSON_FORCEINLINE Ch TakePush() { return is.Take(); } + RAPIDJSON_FORCEINLINE Ch Take() { return is.Take(); } + RAPIDJSON_FORCEINLINE void Push(char) {} + + size_t Tell() { return is.Tell(); } + size_t Length() { return 0; } + const char* Pop() { return 0; } + + protected: + NumberStream& operator=(const NumberStream&); + + InputStream& is; + }; + + template + class NumberStream : public NumberStream { + typedef NumberStream Base; + public: + NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is), stackStream(reader.stack_) {} + + RAPIDJSON_FORCEINLINE Ch TakePush() { + stackStream.Put(static_cast(Base::is.Peek())); + return Base::is.Take(); + } + + RAPIDJSON_FORCEINLINE void Push(char c) { + stackStream.Put(c); + } + + size_t Length() { return stackStream.Length(); } + + const char* Pop() { + stackStream.Put('\0'); + return stackStream.Pop(); + } + + private: + StackStream stackStream; + }; + + template + class NumberStream : public NumberStream { + typedef NumberStream Base; + public: + NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is) {} + + RAPIDJSON_FORCEINLINE Ch Take() { return Base::TakePush(); } + }; + + template + void ParseNumber(InputStream& is, Handler& handler) { + internal::StreamLocalCopy copy(is); + NumberStream s(*this, copy.s); + + size_t startOffset = s.Tell(); + double d = 0.0; + bool useNanOrInf = false; + + // Parse minus + bool minus = Consume(s, '-'); + + // Parse int: zero / ( digit1-9 *DIGIT ) + unsigned i = 0; + uint64_t i64 = 0; + bool use64bit = false; + int significandDigit = 0; + if (RAPIDJSON_UNLIKELY(s.Peek() == '0')) { + i = 0; + s.TakePush(); + } + else if (RAPIDJSON_LIKELY(s.Peek() >= '1' && s.Peek() <= '9')) { + i = static_cast(s.TakePush() - '0'); + + if (minus) + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i >= 214748364)) { // 2^31 = 2147483648 + if (RAPIDJSON_LIKELY(i != 214748364 || s.Peek() > '8')) { + i64 = i; + use64bit = true; + break; + } + } + i = i * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + else + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i >= 429496729)) { // 2^32 - 1 = 4294967295 + if (RAPIDJSON_LIKELY(i != 429496729 || s.Peek() > '5')) { + i64 = i; + use64bit = true; + break; + } + } + i = i * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + } + // Parse NaN or Infinity here + else if ((parseFlags & kParseNanAndInfFlag) && RAPIDJSON_LIKELY((s.Peek() == 'I' || s.Peek() == 'N'))) { + if (Consume(s, 'N')) { + if (Consume(s, 'a') && Consume(s, 'N')) { + d = std::numeric_limits::quiet_NaN(); + useNanOrInf = true; + } + } + else if (RAPIDJSON_LIKELY(Consume(s, 'I'))) { + if (Consume(s, 'n') && Consume(s, 'f')) { + d = (minus ? -std::numeric_limits::infinity() : std::numeric_limits::infinity()); + useNanOrInf = true; + + if (RAPIDJSON_UNLIKELY(s.Peek() == 'i' && !(Consume(s, 'i') && Consume(s, 'n') + && Consume(s, 'i') && Consume(s, 't') && Consume(s, 'y')))) { + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + } + } + } + + if (RAPIDJSON_UNLIKELY(!useNanOrInf)) { + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + } + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + + // Parse 64bit int + bool useDouble = false; + if (use64bit) { + if (minus) + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i64 >= RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC))) // 2^63 = 9223372036854775808 + if (RAPIDJSON_LIKELY(i64 != RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC) || s.Peek() > '8')) { + d = static_cast(i64); + useDouble = true; + break; + } + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + else + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i64 >= RAPIDJSON_UINT64_C2(0x19999999, 0x99999999))) // 2^64 - 1 = 18446744073709551615 + if (RAPIDJSON_LIKELY(i64 != RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || s.Peek() > '5')) { + d = static_cast(i64); + useDouble = true; + break; + } + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + } + + // Force double for big integer + if (useDouble) { + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + d = d * 10 + (s.TakePush() - '0'); + } + } + + // Parse frac = decimal-point 1*DIGIT + int expFrac = 0; + size_t decimalPosition; + if (Consume(s, '.')) { + decimalPosition = s.Length(); + + if (RAPIDJSON_UNLIKELY(!(s.Peek() >= '0' && s.Peek() <= '9'))) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissFraction, s.Tell()); + + if (!useDouble) { +#if RAPIDJSON_64BIT + // Use i64 to store significand in 64-bit architecture + if (!use64bit) + i64 = i; + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (i64 > RAPIDJSON_UINT64_C2(0x1FFFFF, 0xFFFFFFFF)) // 2^53 - 1 for fast path + break; + else { + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + --expFrac; + if (i64 != 0) + significandDigit++; + } + } + + d = static_cast(i64); +#else + // Use double to store significand in 32-bit architecture + d = static_cast(use64bit ? i64 : i); +#endif + useDouble = true; + } + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (significandDigit < 17) { + d = d * 10.0 + (s.TakePush() - '0'); + --expFrac; + if (RAPIDJSON_LIKELY(d > 0.0)) + significandDigit++; + } + else + s.TakePush(); + } + } + else + decimalPosition = s.Length(); // decimal position at the end of integer. + + // Parse exp = e [ minus / plus ] 1*DIGIT + int exp = 0; + if (Consume(s, 'e') || Consume(s, 'E')) { + if (!useDouble) { + d = static_cast(use64bit ? i64 : i); + useDouble = true; + } + + bool expMinus = false; + if (Consume(s, '+')) + ; + else if (Consume(s, '-')) + expMinus = true; + + if (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = static_cast(s.Take() - '0'); + if (expMinus) { + // (exp + expFrac) must not underflow int => we're detecting when -exp gets + // dangerously close to INT_MIN (a pessimistic next digit 9 would push it into + // underflow territory): + // + // -(exp * 10 + 9) + expFrac >= INT_MIN + // <=> exp <= (expFrac - INT_MIN - 9) / 10 + RAPIDJSON_ASSERT(expFrac <= 0); + int maxExp = (expFrac + 2147483639) / 10; + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = exp * 10 + static_cast(s.Take() - '0'); + if (RAPIDJSON_UNLIKELY(exp > maxExp)) { + while (RAPIDJSON_UNLIKELY(s.Peek() >= '0' && s.Peek() <= '9')) // Consume the rest of exponent + s.Take(); + } + } + } + else { // positive exp + int maxExp = 308 - expFrac; + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = exp * 10 + static_cast(s.Take() - '0'); + if (RAPIDJSON_UNLIKELY(exp > maxExp)) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, startOffset); + } + } + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissExponent, s.Tell()); + + if (expMinus) + exp = -exp; + } + + // Finish parsing, call event according to the type of number. + bool cont = true; + + if (parseFlags & kParseNumbersAsStringsFlag) { + if (parseFlags & kParseInsituFlag) { + s.Pop(); // Pop stack no matter if it will be used or not. + typename InputStream::Ch* head = is.PutBegin(); + const size_t length = s.Tell() - startOffset; + RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); + // unable to insert the \0 character here, it will erase the comma after this number + const typename TargetEncoding::Ch* const str = reinterpret_cast(head); + cont = handler.RawNumber(str, SizeType(length), false); + } + else { + SizeType numCharsToCopy = static_cast(s.Length()); + StringStream srcStream(s.Pop()); + StackStream dstStream(stack_); + while (numCharsToCopy--) { + Transcoder, TargetEncoding>::Transcode(srcStream, dstStream); + } + dstStream.Put('\0'); + const typename TargetEncoding::Ch* str = dstStream.Pop(); + const SizeType length = static_cast(dstStream.Length()) - 1; + cont = handler.RawNumber(str, SizeType(length), true); + } + } + else { + size_t length = s.Length(); + const char* decimal = s.Pop(); // Pop stack no matter if it will be used or not. + + if (useDouble) { + int p = exp + expFrac; + if (parseFlags & kParseFullPrecisionFlag) + d = internal::StrtodFullPrecision(d, p, decimal, length, decimalPosition, exp); + else + d = internal::StrtodNormalPrecision(d, p); + + // Use > max, instead of == inf, to fix bogus warning -Wfloat-equal + if (d > (std::numeric_limits::max)()) { + // Overflow + // TODO: internal::StrtodX should report overflow (or underflow) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, startOffset); + } + + cont = handler.Double(minus ? -d : d); + } + else if (useNanOrInf) { + cont = handler.Double(d); + } + else { + if (use64bit) { + if (minus) + cont = handler.Int64(static_cast(~i64 + 1)); + else + cont = handler.Uint64(i64); + } + else { + if (minus) + cont = handler.Int(static_cast(~i + 1)); + else + cont = handler.Uint(i); + } + } + } + if (RAPIDJSON_UNLIKELY(!cont)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, startOffset); + } + + // Parse any JSON value + template + void ParseValue(InputStream& is, Handler& handler) { + switch (is.Peek()) { + case 'n': ParseNull (is, handler); break; + case 't': ParseTrue (is, handler); break; + case 'f': ParseFalse (is, handler); break; + case '"': ParseString(is, handler); break; + case '{': ParseObject(is, handler); break; + case '[': ParseArray (is, handler); break; + default : + ParseNumber(is, handler); + break; + + } + } + + // Iterative Parsing + + // States + enum IterativeParsingState { + IterativeParsingFinishState = 0, // sink states at top + IterativeParsingErrorState, // sink states at top + IterativeParsingStartState, + + // Object states + IterativeParsingObjectInitialState, + IterativeParsingMemberKeyState, + IterativeParsingMemberValueState, + IterativeParsingObjectFinishState, + + // Array states + IterativeParsingArrayInitialState, + IterativeParsingElementState, + IterativeParsingArrayFinishState, + + // Single value state + IterativeParsingValueState, + + // Delimiter states (at bottom) + IterativeParsingElementDelimiterState, + IterativeParsingMemberDelimiterState, + IterativeParsingKeyValueDelimiterState, + + cIterativeParsingStateCount + }; + + // Tokens + enum Token { + LeftBracketToken = 0, + RightBracketToken, + + LeftCurlyBracketToken, + RightCurlyBracketToken, + + CommaToken, + ColonToken, + + StringToken, + FalseToken, + TrueToken, + NullToken, + NumberToken, + + kTokenCount + }; + + RAPIDJSON_FORCEINLINE Token Tokenize(Ch c) const { + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define N NumberToken +#define N16 N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N + // Maps from ASCII to Token + static const unsigned char tokenMap[256] = { + N16, // 00~0F + N16, // 10~1F + N, N, StringToken, N, N, N, N, N, N, N, N, N, CommaToken, N, N, N, // 20~2F + N, N, N, N, N, N, N, N, N, N, ColonToken, N, N, N, N, N, // 30~3F + N16, // 40~4F + N, N, N, N, N, N, N, N, N, N, N, LeftBracketToken, N, RightBracketToken, N, N, // 50~5F + N, N, N, N, N, N, FalseToken, N, N, N, N, N, N, N, NullToken, N, // 60~6F + N, N, N, N, TrueToken, N, N, N, N, N, N, LeftCurlyBracketToken, N, RightCurlyBracketToken, N, N, // 70~7F + N16, N16, N16, N16, N16, N16, N16, N16 // 80~FF + }; +#undef N +#undef N16 +//!@endcond + + if (sizeof(Ch) == 1 || static_cast(c) < 256) + return static_cast(tokenMap[static_cast(c)]); + else + return NumberToken; + } + + RAPIDJSON_FORCEINLINE IterativeParsingState Predict(IterativeParsingState state, Token token) const { + // current state x one lookahead token -> new state + static const char G[cIterativeParsingStateCount][kTokenCount] = { + // Finish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Error(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Start + { + IterativeParsingArrayInitialState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingValueState, // String + IterativeParsingValueState, // False + IterativeParsingValueState, // True + IterativeParsingValueState, // Null + IterativeParsingValueState // Number + }, + // ObjectInitial + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberKeyState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // MemberKey + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingKeyValueDelimiterState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // MemberValue + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingMemberDelimiterState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // ObjectFinish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // ArrayInitial + { + IterativeParsingArrayInitialState, // Left bracket(push Element state) + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push Element state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingElementState, // String + IterativeParsingElementState, // False + IterativeParsingElementState, // True + IterativeParsingElementState, // Null + IterativeParsingElementState // Number + }, + // Element + { + IterativeParsingErrorState, // Left bracket + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingElementDelimiterState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // ArrayFinish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Single Value (sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // ElementDelimiter + { + IterativeParsingArrayInitialState, // Left bracket(push Element state) + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push Element state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingElementState, // String + IterativeParsingElementState, // False + IterativeParsingElementState, // True + IterativeParsingElementState, // Null + IterativeParsingElementState // Number + }, + // MemberDelimiter + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberKeyState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // KeyValueDelimiter + { + IterativeParsingArrayInitialState, // Left bracket(push MemberValue state) + IterativeParsingErrorState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push MemberValue state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberValueState, // String + IterativeParsingMemberValueState, // False + IterativeParsingMemberValueState, // True + IterativeParsingMemberValueState, // Null + IterativeParsingMemberValueState // Number + }, + }; // End of G + + return static_cast(G[state][token]); + } + + // Make an advance in the token stream and state based on the candidate destination state which was returned by Transit(). + // May return a new state on state pop. + template + RAPIDJSON_FORCEINLINE IterativeParsingState Transit(IterativeParsingState src, Token token, IterativeParsingState dst, InputStream& is, Handler& handler) { + (void)token; + + switch (dst) { + case IterativeParsingErrorState: + return dst; + + case IterativeParsingObjectInitialState: + case IterativeParsingArrayInitialState: + { + // Push the state(Element or MemeberValue) if we are nested in another array or value of member. + // In this way we can get the correct state on ObjectFinish or ArrayFinish by frame pop. + IterativeParsingState n = src; + if (src == IterativeParsingArrayInitialState || src == IterativeParsingElementDelimiterState) + n = IterativeParsingElementState; + else if (src == IterativeParsingKeyValueDelimiterState) + n = IterativeParsingMemberValueState; + // Push current state. + *stack_.template Push(1) = n; + // Initialize and push the member/element count. + *stack_.template Push(1) = 0; + // Call handler + bool hr = (dst == IterativeParsingObjectInitialState) ? handler.StartObject() : handler.StartArray(); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return dst; + } + } + + case IterativeParsingMemberKeyState: + ParseString(is, handler, true); + if (HasParseError()) + return IterativeParsingErrorState; + else + return dst; + + case IterativeParsingKeyValueDelimiterState: + RAPIDJSON_ASSERT(token == ColonToken); + is.Take(); + return dst; + + case IterativeParsingMemberValueState: + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return dst; + + case IterativeParsingElementState: + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return dst; + + case IterativeParsingMemberDelimiterState: + case IterativeParsingElementDelimiterState: + is.Take(); + // Update member/element count. + *stack_.template Top() = *stack_.template Top() + 1; + return dst; + + case IterativeParsingObjectFinishState: + { + // Transit from delimiter is only allowed when trailing commas are enabled + if (!(parseFlags & kParseTrailingCommasFlag) && src == IterativeParsingMemberDelimiterState) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorObjectMissName, is.Tell()); + return IterativeParsingErrorState; + } + // Get member count. + SizeType c = *stack_.template Pop(1); + // If the object is not empty, count the last member. + if (src == IterativeParsingMemberValueState) + ++c; + // Restore the state. + IterativeParsingState n = static_cast(*stack_.template Pop(1)); + // Transit to Finish state if this is the topmost scope. + if (n == IterativeParsingStartState) + n = IterativeParsingFinishState; + // Call handler + bool hr = handler.EndObject(c); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return n; + } + } + + case IterativeParsingArrayFinishState: + { + // Transit from delimiter is only allowed when trailing commas are enabled + if (!(parseFlags & kParseTrailingCommasFlag) && src == IterativeParsingElementDelimiterState) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorValueInvalid, is.Tell()); + return IterativeParsingErrorState; + } + // Get element count. + SizeType c = *stack_.template Pop(1); + // If the array is not empty, count the last element. + if (src == IterativeParsingElementState) + ++c; + // Restore the state. + IterativeParsingState n = static_cast(*stack_.template Pop(1)); + // Transit to Finish state if this is the topmost scope. + if (n == IterativeParsingStartState) + n = IterativeParsingFinishState; + // Call handler + bool hr = handler.EndArray(c); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return n; + } + } + + default: + // This branch is for IterativeParsingValueState actually. + // Use `default:` rather than + // `case IterativeParsingValueState:` is for code coverage. + + // The IterativeParsingStartState is not enumerated in this switch-case. + // It is impossible for that case. And it can be caught by following assertion. + + // The IterativeParsingFinishState is not enumerated in this switch-case either. + // It is a "derivative" state which cannot triggered from Predict() directly. + // Therefore it cannot happen here. And it can be caught by following assertion. + RAPIDJSON_ASSERT(dst == IterativeParsingValueState); + + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return IterativeParsingFinishState; + } + } + + template + void HandleError(IterativeParsingState src, InputStream& is) { + if (HasParseError()) { + // Error flag has been set. + return; + } + + switch (src) { + case IterativeParsingStartState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentEmpty, is.Tell()); return; + case IterativeParsingFinishState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentRootNotSingular, is.Tell()); return; + case IterativeParsingObjectInitialState: + case IterativeParsingMemberDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); return; + case IterativeParsingMemberKeyState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); return; + case IterativeParsingMemberValueState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); return; + case IterativeParsingKeyValueDelimiterState: + case IterativeParsingArrayInitialState: + case IterativeParsingElementDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); return; + default: RAPIDJSON_ASSERT(src == IterativeParsingElementState); RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); return; + } + } + + RAPIDJSON_FORCEINLINE bool IsIterativeParsingDelimiterState(IterativeParsingState s) const { + return s >= IterativeParsingElementDelimiterState; + } + + RAPIDJSON_FORCEINLINE bool IsIterativeParsingCompleteState(IterativeParsingState s) const { + return s <= IterativeParsingErrorState; + } + + template + ParseResult IterativeParse(InputStream& is, Handler& handler) { + parseResult_.Clear(); + ClearStackOnExit scope(*this); + IterativeParsingState state = IterativeParsingStartState; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + while (is.Peek() != '\0') { + Token t = Tokenize(is.Peek()); + IterativeParsingState n = Predict(state, t); + IterativeParsingState d = Transit(state, t, n, is, handler); + + if (d == IterativeParsingErrorState) { + HandleError(state, is); + break; + } + + state = d; + + // Do not further consume streams if a root JSON has been parsed. + if ((parseFlags & kParseStopWhenDoneFlag) && state == IterativeParsingFinishState) + break; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + + // Handle the end of file. + if (state != IterativeParsingFinishState) + HandleError(state, is); + + return parseResult_; + } + + static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string. + internal::Stack stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing. + ParseResult parseResult_; + IterativeParsingState state_; +}; // class GenericReader + +//! Reader with UTF8 encoding and default allocator. +typedef GenericReader, UTF8<> > Reader; + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_READER_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/schema.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/schema.h new file mode 100755 index 000000000..26ae94748 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/schema.h @@ -0,0 +1,2497 @@ +// Tencent is pleased to support the open source community by making RapidJSON available-> +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip-> All rights reserved-> +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License-> You may obtain a copy of the License at +// +// http://opensource->org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied-> See the License for the +// specific language governing permissions and limitations under the License-> + +#ifndef RAPIDJSON_SCHEMA_H_ +#define RAPIDJSON_SCHEMA_H_ + +#include "document.h" +#include "pointer.h" +#include "stringbuffer.h" +#include // abs, floor + +#if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX) +#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 1 +#else +#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 0 +#endif + +#if !RAPIDJSON_SCHEMA_USE_INTERNALREGEX && defined(RAPIDJSON_SCHEMA_USE_STDREGEX) && (__cplusplus >=201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)) +#define RAPIDJSON_SCHEMA_USE_STDREGEX 1 +#else +#define RAPIDJSON_SCHEMA_USE_STDREGEX 0 +#endif + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX +#include "internal/regex.h" +#elif RAPIDJSON_SCHEMA_USE_STDREGEX +#include +#endif + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX || RAPIDJSON_SCHEMA_USE_STDREGEX +#define RAPIDJSON_SCHEMA_HAS_REGEX 1 +#else +#define RAPIDJSON_SCHEMA_HAS_REGEX 0 +#endif + +#ifndef RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_SCHEMA_VERBOSE 0 +#endif + +#if RAPIDJSON_SCHEMA_VERBOSE +#include "stringbuffer.h" +#endif + +RAPIDJSON_DIAG_PUSH + +#if defined(__GNUC__) +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_OFF(weak-vtables) +RAPIDJSON_DIAG_OFF(exit-time-destructors) +RAPIDJSON_DIAG_OFF(c++98-compat-pedantic) +RAPIDJSON_DIAG_OFF(variadic-macros) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Verbose Utilities + +#if RAPIDJSON_SCHEMA_VERBOSE + +namespace internal { + +inline void PrintInvalidKeyword(const char* keyword) { + printf("Fail keyword: %s\n", keyword); +} + +inline void PrintInvalidKeyword(const wchar_t* keyword) { + wprintf(L"Fail keyword: %ls\n", keyword); +} + +inline void PrintInvalidDocument(const char* document) { + printf("Fail document: %s\n\n", document); +} + +inline void PrintInvalidDocument(const wchar_t* document) { + wprintf(L"Fail document: %ls\n\n", document); +} + +inline void PrintValidatorPointers(unsigned depth, const char* s, const char* d) { + printf("S: %*s%s\nD: %*s%s\n\n", depth * 4, " ", s, depth * 4, " ", d); +} + +inline void PrintValidatorPointers(unsigned depth, const wchar_t* s, const wchar_t* d) { + wprintf(L"S: %*ls%ls\nD: %*ls%ls\n\n", depth * 4, L" ", s, depth * 4, L" ", d); +} + +} // namespace internal + +#endif // RAPIDJSON_SCHEMA_VERBOSE + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_INVALID_KEYWORD_RETURN + +#if RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) internal::PrintInvalidKeyword(keyword) +#else +#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) +#endif + +#define RAPIDJSON_INVALID_KEYWORD_RETURN(keyword)\ +RAPIDJSON_MULTILINEMACRO_BEGIN\ + context.invalidKeyword = keyword.GetString();\ + RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword.GetString());\ + return false;\ +RAPIDJSON_MULTILINEMACRO_END + +/////////////////////////////////////////////////////////////////////////////// +// Forward declarations + +template +class GenericSchemaDocument; + +namespace internal { + +template +class Schema; + +/////////////////////////////////////////////////////////////////////////////// +// ISchemaValidator + +class ISchemaValidator { +public: + virtual ~ISchemaValidator() {} + virtual bool IsValid() const = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// ISchemaStateFactory + +template +class ISchemaStateFactory { +public: + virtual ~ISchemaStateFactory() {} + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType&) = 0; + virtual void DestroySchemaValidator(ISchemaValidator* validator) = 0; + virtual void* CreateHasher() = 0; + virtual uint64_t GetHashCode(void* hasher) = 0; + virtual void DestroryHasher(void* hasher) = 0; + virtual void* MallocState(size_t size) = 0; + virtual void FreeState(void* p) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// IValidationErrorHandler + +template +class IValidationErrorHandler { +public: + typedef typename SchemaType::Ch Ch; + typedef typename SchemaType::SValue SValue; + + virtual ~IValidationErrorHandler() {} + + virtual void NotMultipleOf(int64_t actual, const SValue& expected) = 0; + virtual void NotMultipleOf(uint64_t actual, const SValue& expected) = 0; + virtual void NotMultipleOf(double actual, const SValue& expected) = 0; + virtual void AboveMaximum(int64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void AboveMaximum(uint64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void AboveMaximum(double actual, const SValue& expected, bool exclusive) = 0; + virtual void BelowMinimum(int64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void BelowMinimum(uint64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void BelowMinimum(double actual, const SValue& expected, bool exclusive) = 0; + + virtual void TooLong(const Ch* str, SizeType length, SizeType expected) = 0; + virtual void TooShort(const Ch* str, SizeType length, SizeType expected) = 0; + virtual void DoesNotMatch(const Ch* str, SizeType length) = 0; + + virtual void DisallowedItem(SizeType index) = 0; + virtual void TooFewItems(SizeType actualCount, SizeType expectedCount) = 0; + virtual void TooManyItems(SizeType actualCount, SizeType expectedCount) = 0; + virtual void DuplicateItems(SizeType index1, SizeType index2) = 0; + + virtual void TooManyProperties(SizeType actualCount, SizeType expectedCount) = 0; + virtual void TooFewProperties(SizeType actualCount, SizeType expectedCount) = 0; + virtual void StartMissingProperties() = 0; + virtual void AddMissingProperty(const SValue& name) = 0; + virtual bool EndMissingProperties() = 0; + virtual void PropertyViolations(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void DisallowedProperty(const Ch* name, SizeType length) = 0; + + virtual void StartDependencyErrors() = 0; + virtual void StartMissingDependentProperties() = 0; + virtual void AddMissingDependentProperty(const SValue& targetName) = 0; + virtual void EndMissingDependentProperties(const SValue& sourceName) = 0; + virtual void AddDependencySchemaError(const SValue& souceName, ISchemaValidator* subvalidator) = 0; + virtual bool EndDependencyErrors() = 0; + + virtual void DisallowedValue() = 0; + virtual void StartDisallowedType() = 0; + virtual void AddExpectedType(const typename SchemaType::ValueType& expectedType) = 0; + virtual void EndDisallowedType(const typename SchemaType::ValueType& actualType) = 0; + virtual void NotAllOf(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void NoneOf(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void NotOneOf(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void Disallowed() = 0; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Hasher + +// For comparison of compound value +template +class Hasher { +public: + typedef typename Encoding::Ch Ch; + + Hasher(Allocator* allocator = 0, size_t stackCapacity = kDefaultSize) : stack_(allocator, stackCapacity) {} + + bool Null() { return WriteType(kNullType); } + bool Bool(bool b) { return WriteType(b ? kTrueType : kFalseType); } + bool Int(int i) { Number n; n.u.i = i; n.d = static_cast(i); return WriteNumber(n); } + bool Uint(unsigned u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } + bool Int64(int64_t i) { Number n; n.u.i = i; n.d = static_cast(i); return WriteNumber(n); } + bool Uint64(uint64_t u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } + bool Double(double d) { + Number n; + if (d < 0) n.u.i = static_cast(d); + else n.u.u = static_cast(d); + n.d = d; + return WriteNumber(n); + } + + bool RawNumber(const Ch* str, SizeType len, bool) { + WriteBuffer(kNumberType, str, len * sizeof(Ch)); + return true; + } + + bool String(const Ch* str, SizeType len, bool) { + WriteBuffer(kStringType, str, len * sizeof(Ch)); + return true; + } + + bool StartObject() { return true; } + bool Key(const Ch* str, SizeType len, bool copy) { return String(str, len, copy); } + bool EndObject(SizeType memberCount) { + uint64_t h = Hash(0, kObjectType); + uint64_t* kv = stack_.template Pop(memberCount * 2); + for (SizeType i = 0; i < memberCount; i++) + h ^= Hash(kv[i * 2], kv[i * 2 + 1]); // Use xor to achieve member order insensitive + *stack_.template Push() = h; + return true; + } + + bool StartArray() { return true; } + bool EndArray(SizeType elementCount) { + uint64_t h = Hash(0, kArrayType); + uint64_t* e = stack_.template Pop(elementCount); + for (SizeType i = 0; i < elementCount; i++) + h = Hash(h, e[i]); // Use hash to achieve element order sensitive + *stack_.template Push() = h; + return true; + } + + bool IsValid() const { return stack_.GetSize() == sizeof(uint64_t); } + + uint64_t GetHashCode() const { + RAPIDJSON_ASSERT(IsValid()); + return *stack_.template Top(); + } + +private: + static const size_t kDefaultSize = 256; + struct Number { + union U { + uint64_t u; + int64_t i; + }u; + double d; + }; + + bool WriteType(Type type) { return WriteBuffer(type, 0, 0); } + + bool WriteNumber(const Number& n) { return WriteBuffer(kNumberType, &n, sizeof(n)); } + + bool WriteBuffer(Type type, const void* data, size_t len) { + // FNV-1a from http://isthe.com/chongo/tech/comp/fnv/ + uint64_t h = Hash(RAPIDJSON_UINT64_C2(0x84222325, 0xcbf29ce4), type); + const unsigned char* d = static_cast(data); + for (size_t i = 0; i < len; i++) + h = Hash(h, d[i]); + *stack_.template Push() = h; + return true; + } + + static uint64_t Hash(uint64_t h, uint64_t d) { + static const uint64_t kPrime = RAPIDJSON_UINT64_C2(0x00000100, 0x000001b3); + h ^= d; + h *= kPrime; + return h; + } + + Stack stack_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// SchemaValidationContext + +template +struct SchemaValidationContext { + typedef Schema SchemaType; + typedef ISchemaStateFactory SchemaValidatorFactoryType; + typedef IValidationErrorHandler ErrorHandlerType; + typedef typename SchemaType::ValueType ValueType; + typedef typename ValueType::Ch Ch; + + enum PatternValidatorType { + kPatternValidatorOnly, + kPatternValidatorWithProperty, + kPatternValidatorWithAdditionalProperty + }; + + SchemaValidationContext(SchemaValidatorFactoryType& f, ErrorHandlerType& eh, const SchemaType* s) : + factory(f), + error_handler(eh), + schema(s), + valueSchema(), + invalidKeyword(), + hasher(), + arrayElementHashCodes(), + validators(), + validatorCount(), + patternPropertiesValidators(), + patternPropertiesValidatorCount(), + patternPropertiesSchemas(), + patternPropertiesSchemaCount(), + valuePatternValidatorType(kPatternValidatorOnly), + propertyExist(), + inArray(false), + valueUniqueness(false), + arrayUniqueness(false) + { + } + + ~SchemaValidationContext() { + if (hasher) + factory.DestroryHasher(hasher); + if (validators) { + for (SizeType i = 0; i < validatorCount; i++) + factory.DestroySchemaValidator(validators[i]); + factory.FreeState(validators); + } + if (patternPropertiesValidators) { + for (SizeType i = 0; i < patternPropertiesValidatorCount; i++) + factory.DestroySchemaValidator(patternPropertiesValidators[i]); + factory.FreeState(patternPropertiesValidators); + } + if (patternPropertiesSchemas) + factory.FreeState(patternPropertiesSchemas); + if (propertyExist) + factory.FreeState(propertyExist); + } + + SchemaValidatorFactoryType& factory; + ErrorHandlerType& error_handler; + const SchemaType* schema; + const SchemaType* valueSchema; + const Ch* invalidKeyword; + void* hasher; // Only validator access + void* arrayElementHashCodes; // Only validator access this + ISchemaValidator** validators; + SizeType validatorCount; + ISchemaValidator** patternPropertiesValidators; + SizeType patternPropertiesValidatorCount; + const SchemaType** patternPropertiesSchemas; + SizeType patternPropertiesSchemaCount; + PatternValidatorType valuePatternValidatorType; + PatternValidatorType objectPatternValidatorType; + SizeType arrayElementIndex; + bool* propertyExist; + bool inArray; + bool valueUniqueness; + bool arrayUniqueness; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Schema + +template +class Schema { +public: + typedef typename SchemaDocumentType::ValueType ValueType; + typedef typename SchemaDocumentType::AllocatorType AllocatorType; + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + typedef SchemaValidationContext Context; + typedef Schema SchemaType; + typedef GenericValue SValue; + typedef IValidationErrorHandler ErrorHandler; + friend class GenericSchemaDocument; + + Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator) : + allocator_(allocator), + uri_(schemaDocument->GetURI(), *allocator), + pointer_(p, allocator), + typeless_(schemaDocument->GetTypeless()), + enum_(), + enumCount_(), + not_(), + type_((1 << kTotalSchemaType) - 1), // typeless + validatorCount_(), + notValidatorIndex_(), + properties_(), + additionalPropertiesSchema_(), + patternProperties_(), + patternPropertyCount_(), + propertyCount_(), + minProperties_(), + maxProperties_(SizeType(~0)), + additionalProperties_(true), + hasDependencies_(), + hasRequired_(), + hasSchemaDependencies_(), + additionalItemsSchema_(), + itemsList_(), + itemsTuple_(), + itemsTupleCount_(), + minItems_(), + maxItems_(SizeType(~0)), + additionalItems_(true), + uniqueItems_(false), + pattern_(), + minLength_(0), + maxLength_(~SizeType(0)), + exclusiveMinimum_(false), + exclusiveMaximum_(false), + defaultValueLength_(0) + { + typedef typename SchemaDocumentType::ValueType ValueType; + typedef typename ValueType::ConstValueIterator ConstValueIterator; + typedef typename ValueType::ConstMemberIterator ConstMemberIterator; + + if (!value.IsObject()) + return; + + if (const ValueType* v = GetMember(value, GetTypeString())) { + type_ = 0; + if (v->IsString()) + AddType(*v); + else if (v->IsArray()) + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) + AddType(*itr); + } + + if (const ValueType* v = GetMember(value, GetEnumString())) + if (v->IsArray() && v->Size() > 0) { + enum_ = static_cast(allocator_->Malloc(sizeof(uint64_t) * v->Size())); + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) { + typedef Hasher > EnumHasherType; + char buffer[256u + 24]; + MemoryPoolAllocator<> hasherAllocator(buffer, sizeof(buffer)); + EnumHasherType h(&hasherAllocator, 256); + itr->Accept(h); + enum_[enumCount_++] = h.GetHashCode(); + } + } + + if (schemaDocument) { + AssignIfExist(allOf_, *schemaDocument, p, value, GetAllOfString(), document); + AssignIfExist(anyOf_, *schemaDocument, p, value, GetAnyOfString(), document); + AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document); + } + + if (const ValueType* v = GetMember(value, GetNotString())) { + schemaDocument->CreateSchema(¬_, p.Append(GetNotString(), allocator_), *v, document); + notValidatorIndex_ = validatorCount_; + validatorCount_++; + } + + // Object + + const ValueType* properties = GetMember(value, GetPropertiesString()); + const ValueType* required = GetMember(value, GetRequiredString()); + const ValueType* dependencies = GetMember(value, GetDependenciesString()); + { + // Gather properties from properties/required/dependencies + SValue allProperties(kArrayType); + + if (properties && properties->IsObject()) + for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) + AddUniqueElement(allProperties, itr->name); + + if (required && required->IsArray()) + for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) + if (itr->IsString()) + AddUniqueElement(allProperties, *itr); + + if (dependencies && dependencies->IsObject()) + for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { + AddUniqueElement(allProperties, itr->name); + if (itr->value.IsArray()) + for (ConstValueIterator i = itr->value.Begin(); i != itr->value.End(); ++i) + if (i->IsString()) + AddUniqueElement(allProperties, *i); + } + + if (allProperties.Size() > 0) { + propertyCount_ = allProperties.Size(); + properties_ = static_cast(allocator_->Malloc(sizeof(Property) * propertyCount_)); + for (SizeType i = 0; i < propertyCount_; i++) { + new (&properties_[i]) Property(); + properties_[i].name = allProperties[i]; + properties_[i].schema = typeless_; + } + } + } + + if (properties && properties->IsObject()) { + PointerType q = p.Append(GetPropertiesString(), allocator_); + for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) { + SizeType index; + if (FindPropertyIndex(itr->name, &index)) + schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document); + } + } + + if (const ValueType* v = GetMember(value, GetPatternPropertiesString())) { + PointerType q = p.Append(GetPatternPropertiesString(), allocator_); + patternProperties_ = static_cast(allocator_->Malloc(sizeof(PatternProperty) * v->MemberCount())); + patternPropertyCount_ = 0; + + for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) { + new (&patternProperties_[patternPropertyCount_]) PatternProperty(); + patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name); + schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document); + patternPropertyCount_++; + } + } + + if (required && required->IsArray()) + for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) + if (itr->IsString()) { + SizeType index; + if (FindPropertyIndex(*itr, &index)) { + properties_[index].required = true; + hasRequired_ = true; + } + } + + if (dependencies && dependencies->IsObject()) { + PointerType q = p.Append(GetDependenciesString(), allocator_); + hasDependencies_ = true; + for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { + SizeType sourceIndex; + if (FindPropertyIndex(itr->name, &sourceIndex)) { + if (itr->value.IsArray()) { + properties_[sourceIndex].dependencies = static_cast(allocator_->Malloc(sizeof(bool) * propertyCount_)); + std::memset(properties_[sourceIndex].dependencies, 0, sizeof(bool)* propertyCount_); + for (ConstValueIterator targetItr = itr->value.Begin(); targetItr != itr->value.End(); ++targetItr) { + SizeType targetIndex; + if (FindPropertyIndex(*targetItr, &targetIndex)) + properties_[sourceIndex].dependencies[targetIndex] = true; + } + } + else if (itr->value.IsObject()) { + hasSchemaDependencies_ = true; + schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document); + properties_[sourceIndex].dependenciesValidatorIndex = validatorCount_; + validatorCount_++; + } + } + } + } + + if (const ValueType* v = GetMember(value, GetAdditionalPropertiesString())) { + if (v->IsBool()) + additionalProperties_ = v->GetBool(); + else if (v->IsObject()) + schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document); + } + + AssignIfExist(minProperties_, value, GetMinPropertiesString()); + AssignIfExist(maxProperties_, value, GetMaxPropertiesString()); + + // Array + if (const ValueType* v = GetMember(value, GetItemsString())) { + PointerType q = p.Append(GetItemsString(), allocator_); + if (v->IsObject()) // List validation + schemaDocument->CreateSchema(&itemsList_, q, *v, document); + else if (v->IsArray()) { // Tuple validation + itemsTuple_ = static_cast(allocator_->Malloc(sizeof(const Schema*) * v->Size())); + SizeType index = 0; + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++) + schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document); + } + } + + AssignIfExist(minItems_, value, GetMinItemsString()); + AssignIfExist(maxItems_, value, GetMaxItemsString()); + + if (const ValueType* v = GetMember(value, GetAdditionalItemsString())) { + if (v->IsBool()) + additionalItems_ = v->GetBool(); + else if (v->IsObject()) + schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document); + } + + AssignIfExist(uniqueItems_, value, GetUniqueItemsString()); + + // String + AssignIfExist(minLength_, value, GetMinLengthString()); + AssignIfExist(maxLength_, value, GetMaxLengthString()); + + if (const ValueType* v = GetMember(value, GetPatternString())) + pattern_ = CreatePattern(*v); + + // Number + if (const ValueType* v = GetMember(value, GetMinimumString())) + if (v->IsNumber()) + minimum_.CopyFrom(*v, *allocator_); + + if (const ValueType* v = GetMember(value, GetMaximumString())) + if (v->IsNumber()) + maximum_.CopyFrom(*v, *allocator_); + + AssignIfExist(exclusiveMinimum_, value, GetExclusiveMinimumString()); + AssignIfExist(exclusiveMaximum_, value, GetExclusiveMaximumString()); + + if (const ValueType* v = GetMember(value, GetMultipleOfString())) + if (v->IsNumber() && v->GetDouble() > 0.0) + multipleOf_.CopyFrom(*v, *allocator_); + + // Default + if (const ValueType* v = GetMember(value, GetDefaultValueString())) + if (v->IsString()) + defaultValueLength_ = v->GetStringLength(); + + } + + ~Schema() { + AllocatorType::Free(enum_); + if (properties_) { + for (SizeType i = 0; i < propertyCount_; i++) + properties_[i].~Property(); + AllocatorType::Free(properties_); + } + if (patternProperties_) { + for (SizeType i = 0; i < patternPropertyCount_; i++) + patternProperties_[i].~PatternProperty(); + AllocatorType::Free(patternProperties_); + } + AllocatorType::Free(itemsTuple_); +#if RAPIDJSON_SCHEMA_HAS_REGEX + if (pattern_) { + pattern_->~RegexType(); + AllocatorType::Free(pattern_); + } +#endif + } + + const SValue& GetURI() const { + return uri_; + } + + const PointerType& GetPointer() const { + return pointer_; + } + + bool BeginValue(Context& context) const { + if (context.inArray) { + if (uniqueItems_) + context.valueUniqueness = true; + + if (itemsList_) + context.valueSchema = itemsList_; + else if (itemsTuple_) { + if (context.arrayElementIndex < itemsTupleCount_) + context.valueSchema = itemsTuple_[context.arrayElementIndex]; + else if (additionalItemsSchema_) + context.valueSchema = additionalItemsSchema_; + else if (additionalItems_) + context.valueSchema = typeless_; + else { + context.error_handler.DisallowedItem(context.arrayElementIndex); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetItemsString()); + } + } + else + context.valueSchema = typeless_; + + context.arrayElementIndex++; + } + return true; + } + + RAPIDJSON_FORCEINLINE bool EndValue(Context& context) const { + if (context.patternPropertiesValidatorCount > 0) { + bool otherValid = false; + SizeType count = context.patternPropertiesValidatorCount; + if (context.objectPatternValidatorType != Context::kPatternValidatorOnly) + otherValid = context.patternPropertiesValidators[--count]->IsValid(); + + bool patternValid = true; + for (SizeType i = 0; i < count; i++) + if (!context.patternPropertiesValidators[i]->IsValid()) { + patternValid = false; + break; + } + + if (context.objectPatternValidatorType == Context::kPatternValidatorOnly) { + if (!patternValid) { + context.error_handler.PropertyViolations(context.patternPropertiesValidators, count); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + } + else if (context.objectPatternValidatorType == Context::kPatternValidatorWithProperty) { + if (!patternValid || !otherValid) { + context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + } + else if (!patternValid && !otherValid) { // kPatternValidatorWithAdditionalProperty) + context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + } + + if (enum_) { + const uint64_t h = context.factory.GetHashCode(context.hasher); + for (SizeType i = 0; i < enumCount_; i++) + if (enum_[i] == h) + goto foundEnum; + context.error_handler.DisallowedValue(); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetEnumString()); + foundEnum:; + } + + if (allOf_.schemas) + for (SizeType i = allOf_.begin; i < allOf_.begin + allOf_.count; i++) + if (!context.validators[i]->IsValid()) { + context.error_handler.NotAllOf(&context.validators[allOf_.begin], allOf_.count); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAllOfString()); + } + + if (anyOf_.schemas) { + for (SizeType i = anyOf_.begin; i < anyOf_.begin + anyOf_.count; i++) + if (context.validators[i]->IsValid()) + goto foundAny; + context.error_handler.NoneOf(&context.validators[anyOf_.begin], anyOf_.count); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAnyOfString()); + foundAny:; + } + + if (oneOf_.schemas) { + bool oneValid = false; + for (SizeType i = oneOf_.begin; i < oneOf_.begin + oneOf_.count; i++) + if (context.validators[i]->IsValid()) { + if (oneValid) { + context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); + } else + oneValid = true; + } + if (!oneValid) { + context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); + } + } + + if (not_ && context.validators[notValidatorIndex_]->IsValid()) { + context.error_handler.Disallowed(); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetNotString()); + } + + return true; + } + + bool Null(Context& context) const { + if (!(type_ & (1 << kNullSchemaType))) { + DisallowedType(context, GetNullString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + return CreateParallelValidator(context); + } + + bool Bool(Context& context, bool) const { + if (!(type_ & (1 << kBooleanSchemaType))) { + DisallowedType(context, GetBooleanString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + return CreateParallelValidator(context); + } + + bool Int(Context& context, int i) const { + if (!CheckInt(context, i)) + return false; + return CreateParallelValidator(context); + } + + bool Uint(Context& context, unsigned u) const { + if (!CheckUint(context, u)) + return false; + return CreateParallelValidator(context); + } + + bool Int64(Context& context, int64_t i) const { + if (!CheckInt(context, i)) + return false; + return CreateParallelValidator(context); + } + + bool Uint64(Context& context, uint64_t u) const { + if (!CheckUint(context, u)) + return false; + return CreateParallelValidator(context); + } + + bool Double(Context& context, double d) const { + if (!(type_ & (1 << kNumberSchemaType))) { + DisallowedType(context, GetNumberString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + if (!minimum_.IsNull() && !CheckDoubleMinimum(context, d)) + return false; + + if (!maximum_.IsNull() && !CheckDoubleMaximum(context, d)) + return false; + + if (!multipleOf_.IsNull() && !CheckDoubleMultipleOf(context, d)) + return false; + + return CreateParallelValidator(context); + } + + bool String(Context& context, const Ch* str, SizeType length, bool) const { + if (!(type_ & (1 << kStringSchemaType))) { + DisallowedType(context, GetStringString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + if (minLength_ != 0 || maxLength_ != SizeType(~0)) { + SizeType count; + if (internal::CountStringCodePoint(str, length, &count)) { + if (count < minLength_) { + context.error_handler.TooShort(str, length, minLength_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinLengthString()); + } + if (count > maxLength_) { + context.error_handler.TooLong(str, length, maxLength_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxLengthString()); + } + } + } + + if (pattern_ && !IsPatternMatch(pattern_, str, length)) { + context.error_handler.DoesNotMatch(str, length); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternString()); + } + + return CreateParallelValidator(context); + } + + bool StartObject(Context& context) const { + if (!(type_ & (1 << kObjectSchemaType))) { + DisallowedType(context, GetObjectString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + if (hasDependencies_ || hasRequired_) { + context.propertyExist = static_cast(context.factory.MallocState(sizeof(bool) * propertyCount_)); + std::memset(context.propertyExist, 0, sizeof(bool) * propertyCount_); + } + + if (patternProperties_) { // pre-allocate schema array + SizeType count = patternPropertyCount_ + 1; // extra for valuePatternValidatorType + context.patternPropertiesSchemas = static_cast(context.factory.MallocState(sizeof(const SchemaType*) * count)); + context.patternPropertiesSchemaCount = 0; + std::memset(context.patternPropertiesSchemas, 0, sizeof(SchemaType*) * count); + } + + return CreateParallelValidator(context); + } + + bool Key(Context& context, const Ch* str, SizeType len, bool) const { + if (patternProperties_) { + context.patternPropertiesSchemaCount = 0; + for (SizeType i = 0; i < patternPropertyCount_; i++) + if (patternProperties_[i].pattern && IsPatternMatch(patternProperties_[i].pattern, str, len)) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = patternProperties_[i].schema; + context.valueSchema = typeless_; + } + } + + SizeType index; + if (FindPropertyIndex(ValueType(str, len).Move(), &index)) { + if (context.patternPropertiesSchemaCount > 0) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = properties_[index].schema; + context.valueSchema = typeless_; + context.valuePatternValidatorType = Context::kPatternValidatorWithProperty; + } + else + context.valueSchema = properties_[index].schema; + + if (context.propertyExist) + context.propertyExist[index] = true; + + return true; + } + + if (additionalPropertiesSchema_) { + if (additionalPropertiesSchema_ && context.patternPropertiesSchemaCount > 0) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = additionalPropertiesSchema_; + context.valueSchema = typeless_; + context.valuePatternValidatorType = Context::kPatternValidatorWithAdditionalProperty; + } + else + context.valueSchema = additionalPropertiesSchema_; + return true; + } + else if (additionalProperties_) { + context.valueSchema = typeless_; + return true; + } + + if (context.patternPropertiesSchemaCount == 0) { // patternProperties are not additional properties + context.error_handler.DisallowedProperty(str, len); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAdditionalPropertiesString()); + } + + return true; + } + + bool EndObject(Context& context, SizeType memberCount) const { + if (hasRequired_) { + context.error_handler.StartMissingProperties(); + for (SizeType index = 0; index < propertyCount_; index++) + if (properties_[index].required && !context.propertyExist[index]) + if (properties_[index].schema->defaultValueLength_ == 0 ) + context.error_handler.AddMissingProperty(properties_[index].name); + if (context.error_handler.EndMissingProperties()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetRequiredString()); + } + + if (memberCount < minProperties_) { + context.error_handler.TooFewProperties(memberCount, minProperties_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinPropertiesString()); + } + + if (memberCount > maxProperties_) { + context.error_handler.TooManyProperties(memberCount, maxProperties_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxPropertiesString()); + } + + if (hasDependencies_) { + context.error_handler.StartDependencyErrors(); + for (SizeType sourceIndex = 0; sourceIndex < propertyCount_; sourceIndex++) { + const Property& source = properties_[sourceIndex]; + if (context.propertyExist[sourceIndex]) { + if (source.dependencies) { + context.error_handler.StartMissingDependentProperties(); + for (SizeType targetIndex = 0; targetIndex < propertyCount_; targetIndex++) + if (source.dependencies[targetIndex] && !context.propertyExist[targetIndex]) + context.error_handler.AddMissingDependentProperty(properties_[targetIndex].name); + context.error_handler.EndMissingDependentProperties(source.name); + } + else if (source.dependenciesSchema) { + ISchemaValidator* dependenciesValidator = context.validators[source.dependenciesValidatorIndex]; + if (!dependenciesValidator->IsValid()) + context.error_handler.AddDependencySchemaError(source.name, dependenciesValidator); + } + } + } + if (context.error_handler.EndDependencyErrors()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString()); + } + + return true; + } + + bool StartArray(Context& context) const { + if (!(type_ & (1 << kArraySchemaType))) { + DisallowedType(context, GetArrayString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + context.arrayElementIndex = 0; + context.inArray = true; + + return CreateParallelValidator(context); + } + + bool EndArray(Context& context, SizeType elementCount) const { + context.inArray = false; + + if (elementCount < minItems_) { + context.error_handler.TooFewItems(elementCount, minItems_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinItemsString()); + } + + if (elementCount > maxItems_) { + context.error_handler.TooManyItems(elementCount, maxItems_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxItemsString()); + } + + return true; + } + + // Generate functions for string literal according to Ch +#define RAPIDJSON_STRING_(name, ...) \ + static const ValueType& Get##name##String() {\ + static const Ch s[] = { __VA_ARGS__, '\0' };\ + static const ValueType v(s, static_cast(sizeof(s) / sizeof(Ch) - 1));\ + return v;\ + } + + RAPIDJSON_STRING_(Null, 'n', 'u', 'l', 'l') + RAPIDJSON_STRING_(Boolean, 'b', 'o', 'o', 'l', 'e', 'a', 'n') + RAPIDJSON_STRING_(Object, 'o', 'b', 'j', 'e', 'c', 't') + RAPIDJSON_STRING_(Array, 'a', 'r', 'r', 'a', 'y') + RAPIDJSON_STRING_(String, 's', 't', 'r', 'i', 'n', 'g') + RAPIDJSON_STRING_(Number, 'n', 'u', 'm', 'b', 'e', 'r') + RAPIDJSON_STRING_(Integer, 'i', 'n', 't', 'e', 'g', 'e', 'r') + RAPIDJSON_STRING_(Type, 't', 'y', 'p', 'e') + RAPIDJSON_STRING_(Enum, 'e', 'n', 'u', 'm') + RAPIDJSON_STRING_(AllOf, 'a', 'l', 'l', 'O', 'f') + RAPIDJSON_STRING_(AnyOf, 'a', 'n', 'y', 'O', 'f') + RAPIDJSON_STRING_(OneOf, 'o', 'n', 'e', 'O', 'f') + RAPIDJSON_STRING_(Not, 'n', 'o', 't') + RAPIDJSON_STRING_(Properties, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(Required, 'r', 'e', 'q', 'u', 'i', 'r', 'e', 'd') + RAPIDJSON_STRING_(Dependencies, 'd', 'e', 'p', 'e', 'n', 'd', 'e', 'n', 'c', 'i', 'e', 's') + RAPIDJSON_STRING_(PatternProperties, 'p', 'a', 't', 't', 'e', 'r', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(AdditionalProperties, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(MinProperties, 'm', 'i', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(MaxProperties, 'm', 'a', 'x', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(Items, 'i', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MinItems, 'm', 'i', 'n', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MaxItems, 'm', 'a', 'x', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(AdditionalItems, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(UniqueItems, 'u', 'n', 'i', 'q', 'u', 'e', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MinLength, 'm', 'i', 'n', 'L', 'e', 'n', 'g', 't', 'h') + RAPIDJSON_STRING_(MaxLength, 'm', 'a', 'x', 'L', 'e', 'n', 'g', 't', 'h') + RAPIDJSON_STRING_(Pattern, 'p', 'a', 't', 't', 'e', 'r', 'n') + RAPIDJSON_STRING_(Minimum, 'm', 'i', 'n', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(Maximum, 'm', 'a', 'x', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(ExclusiveMinimum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'i', 'n', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f') + RAPIDJSON_STRING_(DefaultValue, 'd', 'e', 'f', 'a', 'u', 'l', 't') + +#undef RAPIDJSON_STRING_ + +private: + enum SchemaValueType { + kNullSchemaType, + kBooleanSchemaType, + kObjectSchemaType, + kArraySchemaType, + kStringSchemaType, + kNumberSchemaType, + kIntegerSchemaType, + kTotalSchemaType + }; + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX + typedef internal::GenericRegex RegexType; +#elif RAPIDJSON_SCHEMA_USE_STDREGEX + typedef std::basic_regex RegexType; +#else + typedef char RegexType; +#endif + + struct SchemaArray { + SchemaArray() : schemas(), count() {} + ~SchemaArray() { AllocatorType::Free(schemas); } + const SchemaType** schemas; + SizeType begin; // begin index of context.validators + SizeType count; + }; + + template + void AddUniqueElement(V1& a, const V2& v) { + for (typename V1::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr) + if (*itr == v) + return; + V1 c(v, *allocator_); + a.PushBack(c, *allocator_); + } + + static const ValueType* GetMember(const ValueType& value, const ValueType& name) { + typename ValueType::ConstMemberIterator itr = value.FindMember(name); + return itr != value.MemberEnd() ? &(itr->value) : 0; + } + + static void AssignIfExist(bool& out, const ValueType& value, const ValueType& name) { + if (const ValueType* v = GetMember(value, name)) + if (v->IsBool()) + out = v->GetBool(); + } + + static void AssignIfExist(SizeType& out, const ValueType& value, const ValueType& name) { + if (const ValueType* v = GetMember(value, name)) + if (v->IsUint64() && v->GetUint64() <= SizeType(~0)) + out = static_cast(v->GetUint64()); + } + + void AssignIfExist(SchemaArray& out, SchemaDocumentType& schemaDocument, const PointerType& p, const ValueType& value, const ValueType& name, const ValueType& document) { + if (const ValueType* v = GetMember(value, name)) { + if (v->IsArray() && v->Size() > 0) { + PointerType q = p.Append(name, allocator_); + out.count = v->Size(); + out.schemas = static_cast(allocator_->Malloc(out.count * sizeof(const Schema*))); + memset(out.schemas, 0, sizeof(Schema*)* out.count); + for (SizeType i = 0; i < out.count; i++) + schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document); + out.begin = validatorCount_; + validatorCount_ += out.count; + } + } + } + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX + template + RegexType* CreatePattern(const ValueType& value) { + if (value.IsString()) { + RegexType* r = new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString(), allocator_); + if (!r->IsValid()) { + r->~RegexType(); + AllocatorType::Free(r); + r = 0; + } + return r; + } + return 0; + } + + static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType) { + GenericRegexSearch rs(*pattern); + return rs.Search(str); + } +#elif RAPIDJSON_SCHEMA_USE_STDREGEX + template + RegexType* CreatePattern(const ValueType& value) { + if (value.IsString()) { + RegexType *r = static_cast(allocator_->Malloc(sizeof(RegexType))); + try { + return new (r) RegexType(value.GetString(), std::size_t(value.GetStringLength()), std::regex_constants::ECMAScript); + } + catch (const std::regex_error&) { + AllocatorType::Free(r); + } + } + return 0; + } + + static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType length) { + std::match_results r; + return std::regex_search(str, str + length, r, *pattern); + } +#else + template + RegexType* CreatePattern(const ValueType&) { return 0; } + + static bool IsPatternMatch(const RegexType*, const Ch *, SizeType) { return true; } +#endif // RAPIDJSON_SCHEMA_USE_STDREGEX + + void AddType(const ValueType& type) { + if (type == GetNullString() ) type_ |= 1 << kNullSchemaType; + else if (type == GetBooleanString()) type_ |= 1 << kBooleanSchemaType; + else if (type == GetObjectString() ) type_ |= 1 << kObjectSchemaType; + else if (type == GetArrayString() ) type_ |= 1 << kArraySchemaType; + else if (type == GetStringString() ) type_ |= 1 << kStringSchemaType; + else if (type == GetIntegerString()) type_ |= 1 << kIntegerSchemaType; + else if (type == GetNumberString() ) type_ |= (1 << kNumberSchemaType) | (1 << kIntegerSchemaType); + } + + bool CreateParallelValidator(Context& context) const { + if (enum_ || context.arrayUniqueness) + context.hasher = context.factory.CreateHasher(); + + if (validatorCount_) { + RAPIDJSON_ASSERT(context.validators == 0); + context.validators = static_cast(context.factory.MallocState(sizeof(ISchemaValidator*) * validatorCount_)); + context.validatorCount = validatorCount_; + + if (allOf_.schemas) + CreateSchemaValidators(context, allOf_); + + if (anyOf_.schemas) + CreateSchemaValidators(context, anyOf_); + + if (oneOf_.schemas) + CreateSchemaValidators(context, oneOf_); + + if (not_) + context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_); + + if (hasSchemaDependencies_) { + for (SizeType i = 0; i < propertyCount_; i++) + if (properties_[i].dependenciesSchema) + context.validators[properties_[i].dependenciesValidatorIndex] = context.factory.CreateSchemaValidator(*properties_[i].dependenciesSchema); + } + } + + return true; + } + + void CreateSchemaValidators(Context& context, const SchemaArray& schemas) const { + for (SizeType i = 0; i < schemas.count; i++) + context.validators[schemas.begin + i] = context.factory.CreateSchemaValidator(*schemas.schemas[i]); + } + + // O(n) + bool FindPropertyIndex(const ValueType& name, SizeType* outIndex) const { + SizeType len = name.GetStringLength(); + const Ch* str = name.GetString(); + for (SizeType index = 0; index < propertyCount_; index++) + if (properties_[index].name.GetStringLength() == len && + (std::memcmp(properties_[index].name.GetString(), str, sizeof(Ch) * len) == 0)) + { + *outIndex = index; + return true; + } + return false; + } + + bool CheckInt(Context& context, int64_t i) const { + if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) { + DisallowedType(context, GetIntegerString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + if (!minimum_.IsNull()) { + if (minimum_.IsInt64()) { + if (exclusiveMinimum_ ? i <= minimum_.GetInt64() : i < minimum_.GetInt64()) { + context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + } + } + else if (minimum_.IsUint64()) { + context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); // i <= max(int64_t) < minimum.GetUint64() + } + else if (!CheckDoubleMinimum(context, static_cast(i))) + return false; + } + + if (!maximum_.IsNull()) { + if (maximum_.IsInt64()) { + if (exclusiveMaximum_ ? i >= maximum_.GetInt64() : i > maximum_.GetInt64()) { + context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + } + } + else if (maximum_.IsUint64()) { } + /* do nothing */ // i <= max(int64_t) < maximum_.GetUint64() + else if (!CheckDoubleMaximum(context, static_cast(i))) + return false; + } + + if (!multipleOf_.IsNull()) { + if (multipleOf_.IsUint64()) { + if (static_cast(i >= 0 ? i : -i) % multipleOf_.GetUint64() != 0) { + context.error_handler.NotMultipleOf(i, multipleOf_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + } + } + else if (!CheckDoubleMultipleOf(context, static_cast(i))) + return false; + } + + return true; + } + + bool CheckUint(Context& context, uint64_t i) const { + if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) { + DisallowedType(context, GetIntegerString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + if (!minimum_.IsNull()) { + if (minimum_.IsUint64()) { + if (exclusiveMinimum_ ? i <= minimum_.GetUint64() : i < minimum_.GetUint64()) { + context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + } + } + else if (minimum_.IsInt64()) + /* do nothing */; // i >= 0 > minimum.Getint64() + else if (!CheckDoubleMinimum(context, static_cast(i))) + return false; + } + + if (!maximum_.IsNull()) { + if (maximum_.IsUint64()) { + if (exclusiveMaximum_ ? i >= maximum_.GetUint64() : i > maximum_.GetUint64()) { + context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + } + } + else if (maximum_.IsInt64()) { + context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); // i >= 0 > maximum_ + } + else if (!CheckDoubleMaximum(context, static_cast(i))) + return false; + } + + if (!multipleOf_.IsNull()) { + if (multipleOf_.IsUint64()) { + if (i % multipleOf_.GetUint64() != 0) { + context.error_handler.NotMultipleOf(i, multipleOf_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + } + } + else if (!CheckDoubleMultipleOf(context, static_cast(i))) + return false; + } + + return true; + } + + bool CheckDoubleMinimum(Context& context, double d) const { + if (exclusiveMinimum_ ? d <= minimum_.GetDouble() : d < minimum_.GetDouble()) { + context.error_handler.BelowMinimum(d, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + } + return true; + } + + bool CheckDoubleMaximum(Context& context, double d) const { + if (exclusiveMaximum_ ? d >= maximum_.GetDouble() : d > maximum_.GetDouble()) { + context.error_handler.AboveMaximum(d, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + } + return true; + } + + bool CheckDoubleMultipleOf(Context& context, double d) const { + double a = std::abs(d), b = std::abs(multipleOf_.GetDouble()); + double q = std::floor(a / b); + double r = a - q * b; + if (r > 0.0) { + context.error_handler.NotMultipleOf(d, multipleOf_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + } + return true; + } + + void DisallowedType(Context& context, const ValueType& actualType) const { + ErrorHandler& eh = context.error_handler; + eh.StartDisallowedType(); + + if (type_ & (1 << kNullSchemaType)) eh.AddExpectedType(GetNullString()); + if (type_ & (1 << kBooleanSchemaType)) eh.AddExpectedType(GetBooleanString()); + if (type_ & (1 << kObjectSchemaType)) eh.AddExpectedType(GetObjectString()); + if (type_ & (1 << kArraySchemaType)) eh.AddExpectedType(GetArrayString()); + if (type_ & (1 << kStringSchemaType)) eh.AddExpectedType(GetStringString()); + + if (type_ & (1 << kNumberSchemaType)) eh.AddExpectedType(GetNumberString()); + else if (type_ & (1 << kIntegerSchemaType)) eh.AddExpectedType(GetIntegerString()); + + eh.EndDisallowedType(actualType); + } + + struct Property { + Property() : schema(), dependenciesSchema(), dependenciesValidatorIndex(), dependencies(), required(false) {} + ~Property() { AllocatorType::Free(dependencies); } + SValue name; + const SchemaType* schema; + const SchemaType* dependenciesSchema; + SizeType dependenciesValidatorIndex; + bool* dependencies; + bool required; + }; + + struct PatternProperty { + PatternProperty() : schema(), pattern() {} + ~PatternProperty() { + if (pattern) { + pattern->~RegexType(); + AllocatorType::Free(pattern); + } + } + const SchemaType* schema; + RegexType* pattern; + }; + + AllocatorType* allocator_; + SValue uri_; + PointerType pointer_; + const SchemaType* typeless_; + uint64_t* enum_; + SizeType enumCount_; + SchemaArray allOf_; + SchemaArray anyOf_; + SchemaArray oneOf_; + const SchemaType* not_; + unsigned type_; // bitmask of kSchemaType + SizeType validatorCount_; + SizeType notValidatorIndex_; + + Property* properties_; + const SchemaType* additionalPropertiesSchema_; + PatternProperty* patternProperties_; + SizeType patternPropertyCount_; + SizeType propertyCount_; + SizeType minProperties_; + SizeType maxProperties_; + bool additionalProperties_; + bool hasDependencies_; + bool hasRequired_; + bool hasSchemaDependencies_; + + const SchemaType* additionalItemsSchema_; + const SchemaType* itemsList_; + const SchemaType** itemsTuple_; + SizeType itemsTupleCount_; + SizeType minItems_; + SizeType maxItems_; + bool additionalItems_; + bool uniqueItems_; + + RegexType* pattern_; + SizeType minLength_; + SizeType maxLength_; + + SValue minimum_; + SValue maximum_; + SValue multipleOf_; + bool exclusiveMinimum_; + bool exclusiveMaximum_; + + SizeType defaultValueLength_; +}; + +template +struct TokenHelper { + RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { + *documentStack.template Push() = '/'; + char buffer[21]; + size_t length = static_cast((sizeof(SizeType) == 4 ? u32toa(index, buffer) : u64toa(index, buffer)) - buffer); + for (size_t i = 0; i < length; i++) + *documentStack.template Push() = static_cast(buffer[i]); + } +}; + +// Partial specialized version for char to prevent buffer copying. +template +struct TokenHelper { + RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { + if (sizeof(SizeType) == 4) { + char *buffer = documentStack.template Push(1 + 10); // '/' + uint + *buffer++ = '/'; + const char* end = internal::u32toa(index, buffer); + documentStack.template Pop(static_cast(10 - (end - buffer))); + } + else { + char *buffer = documentStack.template Push(1 + 20); // '/' + uint64 + *buffer++ = '/'; + const char* end = internal::u64toa(index, buffer); + documentStack.template Pop(static_cast(20 - (end - buffer))); + } + } +}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// IGenericRemoteSchemaDocumentProvider + +template +class IGenericRemoteSchemaDocumentProvider { +public: + typedef typename SchemaDocumentType::Ch Ch; + + virtual ~IGenericRemoteSchemaDocumentProvider() {} + virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericSchemaDocument + +//! JSON schema document. +/*! + A JSON schema document is a compiled version of a JSON schema. + It is basically a tree of internal::Schema. + + \note This is an immutable class (i.e. its instance cannot be modified after construction). + \tparam ValueT Type of JSON value (e.g. \c Value ), which also determine the encoding. + \tparam Allocator Allocator type for allocating memory of this document. +*/ +template +class GenericSchemaDocument { +public: + typedef ValueT ValueType; + typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProviderType; + typedef Allocator AllocatorType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + typedef internal::Schema SchemaType; + typedef GenericPointer PointerType; + typedef GenericValue URIType; + friend class internal::Schema; + template + friend class GenericSchemaValidator; + + //! Constructor. + /*! + Compile a JSON document into schema document. + + \param document A JSON document as source. + \param uri The base URI of this schema document for purposes of violation reporting. + \param uriLength Length of \c name, in code points. + \param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null. + \param allocator An optional allocator instance for allocating memory. Can be null. + */ + explicit GenericSchemaDocument(const ValueType& document, const Ch* uri = 0, SizeType uriLength = 0, + IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0) : + remoteProvider_(remoteProvider), + allocator_(allocator), + ownAllocator_(), + root_(), + typeless_(), + schemaMap_(allocator, kInitialSchemaMapSize), + schemaRef_(allocator, kInitialSchemaRefSize) + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + + Ch noUri[1] = {0}; + uri_.SetString(uri ? uri : noUri, uriLength, *allocator_); + + typeless_ = static_cast(allocator_->Malloc(sizeof(SchemaType))); + new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_); + + // Generate root schema, it will call CreateSchema() to create sub-schemas, + // And call AddRefSchema() if there are $ref. + CreateSchemaRecursive(&root_, PointerType(), document, document); + + // Resolve $ref + while (!schemaRef_.Empty()) { + SchemaRefEntry* refEntry = schemaRef_.template Pop(1); + if (const SchemaType* s = GetSchema(refEntry->target)) { + if (refEntry->schema) + *refEntry->schema = s; + + // Create entry in map if not exist + if (!GetSchema(refEntry->source)) { + new (schemaMap_.template Push()) SchemaEntry(refEntry->source, const_cast(s), false, allocator_); + } + } + else if (refEntry->schema) + *refEntry->schema = typeless_; + + refEntry->~SchemaRefEntry(); + } + + RAPIDJSON_ASSERT(root_ != 0); + + schemaRef_.ShrinkToFit(); // Deallocate all memory for ref + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericSchemaDocument(GenericSchemaDocument&& rhs) RAPIDJSON_NOEXCEPT : + remoteProvider_(rhs.remoteProvider_), + allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + root_(rhs.root_), + typeless_(rhs.typeless_), + schemaMap_(std::move(rhs.schemaMap_)), + schemaRef_(std::move(rhs.schemaRef_)), + uri_(std::move(rhs.uri_)) + { + rhs.remoteProvider_ = 0; + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.typeless_ = 0; + } +#endif + + //! Destructor + ~GenericSchemaDocument() { + while (!schemaMap_.Empty()) + schemaMap_.template Pop(1)->~SchemaEntry(); + + if (typeless_) { + typeless_->~SchemaType(); + Allocator::Free(typeless_); + } + + RAPIDJSON_DELETE(ownAllocator_); + } + + const URIType& GetURI() const { return uri_; } + + //! Get the root schema. + const SchemaType& GetRoot() const { return *root_; } + +private: + //! Prohibit copying + GenericSchemaDocument(const GenericSchemaDocument&); + //! Prohibit assignment + GenericSchemaDocument& operator=(const GenericSchemaDocument&); + + struct SchemaRefEntry { + SchemaRefEntry(const PointerType& s, const PointerType& t, const SchemaType** outSchema, Allocator *allocator) : source(s, allocator), target(t, allocator), schema(outSchema) {} + PointerType source; + PointerType target; + const SchemaType** schema; + }; + + struct SchemaEntry { + SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {} + ~SchemaEntry() { + if (owned) { + schema->~SchemaType(); + Allocator::Free(schema); + } + } + PointerType pointer; + SchemaType* schema; + bool owned; + }; + + void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { + if (schema) + *schema = typeless_; + + if (v.GetType() == kObjectType) { + const SchemaType* s = GetSchema(pointer); + if (!s) + CreateSchema(schema, pointer, v, document); + + for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) + CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document); + } + else if (v.GetType() == kArrayType) + for (SizeType i = 0; i < v.Size(); i++) + CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document); + } + + void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { + RAPIDJSON_ASSERT(pointer.IsValid()); + if (v.IsObject()) { + if (!HandleRefSchema(pointer, schema, v, document)) { + SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_); + new (schemaMap_.template Push()) SchemaEntry(pointer, s, true, allocator_); + if (schema) + *schema = s; + } + } + } + + bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document) { + static const Ch kRefString[] = { '$', 'r', 'e', 'f', '\0' }; + static const ValueType kRefValue(kRefString, 4); + + typename ValueType::ConstMemberIterator itr = v.FindMember(kRefValue); + if (itr == v.MemberEnd()) + return false; + + if (itr->value.IsString()) { + SizeType len = itr->value.GetStringLength(); + if (len > 0) { + const Ch* s = itr->value.GetString(); + SizeType i = 0; + while (i < len && s[i] != '#') // Find the first # + i++; + + if (i > 0) { // Remote reference, resolve immediately + if (remoteProvider_) { + if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(s, i)) { + PointerType pointer(&s[i], len - i, allocator_); + if (pointer.IsValid()) { + if (const SchemaType* sc = remoteDocument->GetSchema(pointer)) { + if (schema) + *schema = sc; + new (schemaMap_.template Push()) SchemaEntry(source, const_cast(sc), false, allocator_); + return true; + } + } + } + } + } + else if (s[i] == '#') { // Local reference, defer resolution + PointerType pointer(&s[i], len - i, allocator_); + if (pointer.IsValid()) { + if (const ValueType* nv = pointer.Get(document)) + if (HandleRefSchema(source, schema, *nv, document)) + return true; + + new (schemaRef_.template Push()) SchemaRefEntry(source, pointer, schema, allocator_); + return true; + } + } + } + } + return false; + } + + const SchemaType* GetSchema(const PointerType& pointer) const { + for (const SchemaEntry* target = schemaMap_.template Bottom(); target != schemaMap_.template End(); ++target) + if (pointer == target->pointer) + return target->schema; + return 0; + } + + PointerType GetPointer(const SchemaType* schema) const { + for (const SchemaEntry* target = schemaMap_.template Bottom(); target != schemaMap_.template End(); ++target) + if (schema == target->schema) + return target->pointer; + return PointerType(); + } + + const SchemaType* GetTypeless() const { return typeless_; } + + static const size_t kInitialSchemaMapSize = 64; + static const size_t kInitialSchemaRefSize = 64; + + IRemoteSchemaDocumentProviderType* remoteProvider_; + Allocator *allocator_; + Allocator *ownAllocator_; + const SchemaType* root_; //!< Root schema. + SchemaType* typeless_; + internal::Stack schemaMap_; // Stores created Pointer -> Schemas + internal::Stack schemaRef_; // Stores Pointer from $ref and schema which holds the $ref + URIType uri_; +}; + +//! GenericSchemaDocument using Value type. +typedef GenericSchemaDocument SchemaDocument; +//! IGenericRemoteSchemaDocumentProvider using SchemaDocument. +typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProvider; + +/////////////////////////////////////////////////////////////////////////////// +// GenericSchemaValidator + +//! JSON Schema Validator. +/*! + A SAX style JSON schema validator. + It uses a \c GenericSchemaDocument to validate SAX events. + It delegates the incoming SAX events to an output handler. + The default output handler does nothing. + It can be reused multiple times by calling \c Reset(). + + \tparam SchemaDocumentType Type of schema document. + \tparam OutputHandler Type of output handler. Default handler does nothing. + \tparam StateAllocator Allocator for storing the internal validation states. +*/ +template < + typename SchemaDocumentType, + typename OutputHandler = BaseReaderHandler, + typename StateAllocator = CrtAllocator> +class GenericSchemaValidator : + public internal::ISchemaStateFactory, + public internal::ISchemaValidator, + public internal::IValidationErrorHandler +{ +public: + typedef typename SchemaDocumentType::SchemaType SchemaType; + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename SchemaType::EncodingType EncodingType; + typedef typename SchemaType::SValue SValue; + typedef typename EncodingType::Ch Ch; + typedef GenericStringRef StringRefType; + typedef GenericValue ValueType; + + //! Constructor without output handler. + /*! + \param schemaDocument The schema document to conform to. + \param allocator Optional allocator for storing internal validation states. + \param schemaStackCapacity Optional initial capacity of schema path stack. + \param documentStackCapacity Optional initial capacity of document path stack. + */ + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(schemaDocument.GetRoot()), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + outputHandler_(0), + error_(kObjectType), + currentError_(), + missingDependents_(), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(0) +#endif + { + } + + //! Constructor with output handler. + /*! + \param schemaDocument The schema document to conform to. + \param allocator Optional allocator for storing internal validation states. + \param schemaStackCapacity Optional initial capacity of schema path stack. + \param documentStackCapacity Optional initial capacity of document path stack. + */ + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + OutputHandler& outputHandler, + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(schemaDocument.GetRoot()), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + outputHandler_(&outputHandler), + error_(kObjectType), + currentError_(), + missingDependents_(), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(0) +#endif + { + } + + //! Destructor. + ~GenericSchemaValidator() { + Reset(); + RAPIDJSON_DELETE(ownStateAllocator_); + } + + //! Reset the internal states. + void Reset() { + while (!schemaStack_.Empty()) + PopSchema(); + documentStack_.Clear(); + error_.SetObject(); + currentError_.SetNull(); + missingDependents_.SetNull(); + valid_ = true; + } + + //! Checks whether the current state is valid. + // Implementation of ISchemaValidator + virtual bool IsValid() const { return valid_; } + + //! Gets the error object. + ValueType& GetError() { return error_; } + const ValueType& GetError() const { return error_; } + + //! Gets the JSON pointer pointed to the invalid schema. + PointerType GetInvalidSchemaPointer() const { + return schemaStack_.Empty() ? PointerType() : CurrentSchema().GetPointer(); + } + + //! Gets the keyword of invalid schema. + const Ch* GetInvalidSchemaKeyword() const { + return schemaStack_.Empty() ? 0 : CurrentContext().invalidKeyword; + } + + //! Gets the JSON pointer pointed to the invalid value. + PointerType GetInvalidDocumentPointer() const { + if (documentStack_.Empty()) { + return PointerType(); + } + else { + return PointerType(documentStack_.template Bottom(), documentStack_.GetSize() / sizeof(Ch)); + } + } + + void NotMultipleOf(int64_t actual, const SValue& expected) { + AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected); + } + void NotMultipleOf(uint64_t actual, const SValue& expected) { + AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected); + } + void NotMultipleOf(double actual, const SValue& expected) { + AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected); + } + void AboveMaximum(int64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMaximumString : 0); + } + void AboveMaximum(uint64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMaximumString : 0); + } + void AboveMaximum(double actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMaximumString : 0); + } + void BelowMinimum(int64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMinimumString : 0); + } + void BelowMinimum(uint64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMinimumString : 0); + } + void BelowMinimum(double actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMinimumString : 0); + } + + void TooLong(const Ch* str, SizeType length, SizeType expected) { + AddNumberError(SchemaType::GetMaxLengthString(), + ValueType(str, length, GetStateAllocator()).Move(), SValue(expected).Move()); + } + void TooShort(const Ch* str, SizeType length, SizeType expected) { + AddNumberError(SchemaType::GetMinLengthString(), + ValueType(str, length, GetStateAllocator()).Move(), SValue(expected).Move()); + } + void DoesNotMatch(const Ch* str, SizeType length) { + currentError_.SetObject(); + currentError_.AddMember(GetActualString(), ValueType(str, length, GetStateAllocator()).Move(), GetStateAllocator()); + AddCurrentError(SchemaType::GetPatternString()); + } + + void DisallowedItem(SizeType index) { + currentError_.SetObject(); + currentError_.AddMember(GetDisallowedString(), ValueType(index).Move(), GetStateAllocator()); + AddCurrentError(SchemaType::GetAdditionalItemsString(), true); + } + void TooFewItems(SizeType actualCount, SizeType expectedCount) { + AddNumberError(SchemaType::GetMinItemsString(), + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void TooManyItems(SizeType actualCount, SizeType expectedCount) { + AddNumberError(SchemaType::GetMaxItemsString(), + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void DuplicateItems(SizeType index1, SizeType index2) { + ValueType duplicates(kArrayType); + duplicates.PushBack(index1, GetStateAllocator()); + duplicates.PushBack(index2, GetStateAllocator()); + currentError_.SetObject(); + currentError_.AddMember(GetDuplicatesString(), duplicates, GetStateAllocator()); + AddCurrentError(SchemaType::GetUniqueItemsString(), true); + } + + void TooManyProperties(SizeType actualCount, SizeType expectedCount) { + AddNumberError(SchemaType::GetMaxPropertiesString(), + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void TooFewProperties(SizeType actualCount, SizeType expectedCount) { + AddNumberError(SchemaType::GetMinPropertiesString(), + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void StartMissingProperties() { + currentError_.SetArray(); + } + void AddMissingProperty(const SValue& name) { + currentError_.PushBack(ValueType(name, GetStateAllocator()).Move(), GetStateAllocator()); + } + bool EndMissingProperties() { + if (currentError_.Empty()) + return false; + ValueType error(kObjectType); + error.AddMember(GetMissingString(), currentError_, GetStateAllocator()); + currentError_ = error; + AddCurrentError(SchemaType::GetRequiredString()); + return true; + } + void PropertyViolations(ISchemaValidator** subvalidators, SizeType count) { + for (SizeType i = 0; i < count; ++i) + MergeError(static_cast(subvalidators[i])->GetError()); + } + void DisallowedProperty(const Ch* name, SizeType length) { + currentError_.SetObject(); + currentError_.AddMember(GetDisallowedString(), ValueType(name, length, GetStateAllocator()).Move(), GetStateAllocator()); + AddCurrentError(SchemaType::GetAdditionalPropertiesString(), true); + } + + void StartDependencyErrors() { + currentError_.SetObject(); + } + void StartMissingDependentProperties() { + missingDependents_.SetArray(); + } + void AddMissingDependentProperty(const SValue& targetName) { + missingDependents_.PushBack(ValueType(targetName, GetStateAllocator()).Move(), GetStateAllocator()); + } + void EndMissingDependentProperties(const SValue& sourceName) { + if (!missingDependents_.Empty()) + currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(), + missingDependents_, GetStateAllocator()); + } + void AddDependencySchemaError(const SValue& sourceName, ISchemaValidator* subvalidator) { + currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(), + static_cast(subvalidator)->GetError(), GetStateAllocator()); + } + bool EndDependencyErrors() { + if (currentError_.ObjectEmpty()) + return false; + ValueType error(kObjectType); + error.AddMember(GetErrorsString(), currentError_, GetStateAllocator()); + currentError_ = error; + AddCurrentError(SchemaType::GetDependenciesString()); + return true; + } + + void DisallowedValue() { + currentError_.SetObject(); + AddCurrentError(SchemaType::GetEnumString()); + } + void StartDisallowedType() { + currentError_.SetArray(); + } + void AddExpectedType(const typename SchemaType::ValueType& expectedType) { + currentError_.PushBack(ValueType(expectedType, GetStateAllocator()).Move(), GetStateAllocator()); + } + void EndDisallowedType(const typename SchemaType::ValueType& actualType) { + ValueType error(kObjectType); + error.AddMember(GetExpectedString(), currentError_, GetStateAllocator()); + error.AddMember(GetActualString(), ValueType(actualType, GetStateAllocator()).Move(), GetStateAllocator()); + currentError_ = error; + AddCurrentError(SchemaType::GetTypeString()); + } + void NotAllOf(ISchemaValidator** subvalidators, SizeType count) { + for (SizeType i = 0; i < count; ++i) { + MergeError(static_cast(subvalidators[i])->GetError()); + } + } + void NoneOf(ISchemaValidator** subvalidators, SizeType count) { + AddErrorArray(SchemaType::GetAnyOfString(), subvalidators, count); + } + void NotOneOf(ISchemaValidator** subvalidators, SizeType count) { + AddErrorArray(SchemaType::GetOneOfString(), subvalidators, count); + } + void Disallowed() { + currentError_.SetObject(); + AddCurrentError(SchemaType::GetNotString()); + } + +#define RAPIDJSON_STRING_(name, ...) \ + static const StringRefType& Get##name##String() {\ + static const Ch s[] = { __VA_ARGS__, '\0' };\ + static const StringRefType v(s, static_cast(sizeof(s) / sizeof(Ch) - 1)); \ + return v;\ + } + + RAPIDJSON_STRING_(InstanceRef, 'i', 'n', 's', 't', 'a', 'n', 'c', 'e', 'R', 'e', 'f') + RAPIDJSON_STRING_(SchemaRef, 's', 'c', 'h', 'e', 'm', 'a', 'R', 'e', 'f') + RAPIDJSON_STRING_(Expected, 'e', 'x', 'p', 'e', 'c', 't', 'e', 'd') + RAPIDJSON_STRING_(Actual, 'a', 'c', 't', 'u', 'a', 'l') + RAPIDJSON_STRING_(Disallowed, 'd', 'i', 's', 'a', 'l', 'l', 'o', 'w', 'e', 'd') + RAPIDJSON_STRING_(Missing, 'm', 'i', 's', 's', 'i', 'n', 'g') + RAPIDJSON_STRING_(Errors, 'e', 'r', 'r', 'o', 'r', 's') + RAPIDJSON_STRING_(Duplicates, 'd', 'u', 'p', 'l', 'i', 'c', 'a', 't', 'e', 's') + +#undef RAPIDJSON_STRING_ + +#if RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() \ +RAPIDJSON_MULTILINEMACRO_BEGIN\ + *documentStack_.template Push() = '\0';\ + documentStack_.template Pop(1);\ + internal::PrintInvalidDocument(documentStack_.template Bottom());\ +RAPIDJSON_MULTILINEMACRO_END +#else +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() +#endif + +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_(method, arg1)\ + if (!valid_) return false; \ + if (!BeginValue() || !CurrentSchema().method arg1) {\ + RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_();\ + return valid_ = false;\ + } + +#define RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2)\ + for (Context* context = schemaStack_.template Bottom(); context != schemaStack_.template End(); context++) {\ + if (context->hasher)\ + static_cast(context->hasher)->method arg2;\ + if (context->validators)\ + for (SizeType i_ = 0; i_ < context->validatorCount; i_++)\ + static_cast(context->validators[i_])->method arg2;\ + if (context->patternPropertiesValidators)\ + for (SizeType i_ = 0; i_ < context->patternPropertiesValidatorCount; i_++)\ + static_cast(context->patternPropertiesValidators[i_])->method arg2;\ + } + +#define RAPIDJSON_SCHEMA_HANDLE_END_(method, arg2)\ + return valid_ = EndValue() && (!outputHandler_ || outputHandler_->method arg2) + +#define RAPIDJSON_SCHEMA_HANDLE_VALUE_(method, arg1, arg2) \ + RAPIDJSON_SCHEMA_HANDLE_BEGIN_ (method, arg1);\ + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2);\ + RAPIDJSON_SCHEMA_HANDLE_END_ (method, arg2) + + bool Null() { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Null, (CurrentContext()), ( )); } + bool Bool(bool b) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Bool, (CurrentContext(), b), (b)); } + bool Int(int i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int, (CurrentContext(), i), (i)); } + bool Uint(unsigned u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint, (CurrentContext(), u), (u)); } + bool Int64(int64_t i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int64, (CurrentContext(), i), (i)); } + bool Uint64(uint64_t u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint64, (CurrentContext(), u), (u)); } + bool Double(double d) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Double, (CurrentContext(), d), (d)); } + bool RawNumber(const Ch* str, SizeType length, bool copy) + { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } + bool String(const Ch* str, SizeType length, bool copy) + { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } + + bool StartObject() { + RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartObject, (CurrentContext())); + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartObject, ()); + return valid_ = !outputHandler_ || outputHandler_->StartObject(); + } + + bool Key(const Ch* str, SizeType len, bool copy) { + if (!valid_) return false; + AppendToken(str, len); + if (!CurrentSchema().Key(CurrentContext(), str, len, copy)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(Key, (str, len, copy)); + return valid_ = !outputHandler_ || outputHandler_->Key(str, len, copy); + } + + bool EndObject(SizeType memberCount) { + if (!valid_) return false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndObject, (memberCount)); + if (!CurrentSchema().EndObject(CurrentContext(), memberCount)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_END_(EndObject, (memberCount)); + } + + bool StartArray() { + RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext())); + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ()); + return valid_ = !outputHandler_ || outputHandler_->StartArray(); + } + + bool EndArray(SizeType elementCount) { + if (!valid_) return false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndArray, (elementCount)); + if (!CurrentSchema().EndArray(CurrentContext(), elementCount)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount)); + } + +#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_ +#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_ +#undef RAPIDJSON_SCHEMA_HANDLE_PARALLEL_ +#undef RAPIDJSON_SCHEMA_HANDLE_VALUE_ + + // Implementation of ISchemaStateFactory + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType& root) { + return new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, documentStack_.template Bottom(), documentStack_.GetSize(), +#if RAPIDJSON_SCHEMA_VERBOSE + depth_ + 1, +#endif + &GetStateAllocator()); + } + + virtual void DestroySchemaValidator(ISchemaValidator* validator) { + GenericSchemaValidator* v = static_cast(validator); + v->~GenericSchemaValidator(); + StateAllocator::Free(v); + } + + virtual void* CreateHasher() { + return new (GetStateAllocator().Malloc(sizeof(HasherType))) HasherType(&GetStateAllocator()); + } + + virtual uint64_t GetHashCode(void* hasher) { + return static_cast(hasher)->GetHashCode(); + } + + virtual void DestroryHasher(void* hasher) { + HasherType* h = static_cast(hasher); + h->~HasherType(); + StateAllocator::Free(h); + } + + virtual void* MallocState(size_t size) { + return GetStateAllocator().Malloc(size); + } + + virtual void FreeState(void* p) { + StateAllocator::Free(p); + } + +private: + typedef typename SchemaType::Context Context; + typedef GenericValue, StateAllocator> HashCodeArray; + typedef internal::Hasher HasherType; + + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + const SchemaType& root, + const char* basePath, size_t basePathSize, +#if RAPIDJSON_SCHEMA_VERBOSE + unsigned depth, +#endif + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(root), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + outputHandler_(0), + error_(kObjectType), + currentError_(), + missingDependents_(), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(depth) +#endif + { + if (basePath && basePathSize) + memcpy(documentStack_.template Push(basePathSize), basePath, basePathSize); + } + + StateAllocator& GetStateAllocator() { + if (!stateAllocator_) + stateAllocator_ = ownStateAllocator_ = RAPIDJSON_NEW(StateAllocator)(); + return *stateAllocator_; + } + + bool BeginValue() { + if (schemaStack_.Empty()) + PushSchema(root_); + else { + if (CurrentContext().inArray) + internal::TokenHelper, Ch>::AppendIndexToken(documentStack_, CurrentContext().arrayElementIndex); + + if (!CurrentSchema().BeginValue(CurrentContext())) + return false; + + SizeType count = CurrentContext().patternPropertiesSchemaCount; + const SchemaType** sa = CurrentContext().patternPropertiesSchemas; + typename Context::PatternValidatorType patternValidatorType = CurrentContext().valuePatternValidatorType; + bool valueUniqueness = CurrentContext().valueUniqueness; + RAPIDJSON_ASSERT(CurrentContext().valueSchema); + PushSchema(*CurrentContext().valueSchema); + + if (count > 0) { + CurrentContext().objectPatternValidatorType = patternValidatorType; + ISchemaValidator**& va = CurrentContext().patternPropertiesValidators; + SizeType& validatorCount = CurrentContext().patternPropertiesValidatorCount; + va = static_cast(MallocState(sizeof(ISchemaValidator*) * count)); + for (SizeType i = 0; i < count; i++) + va[validatorCount++] = CreateSchemaValidator(*sa[i]); + } + + CurrentContext().arrayUniqueness = valueUniqueness; + } + return true; + } + + bool EndValue() { + if (!CurrentSchema().EndValue(CurrentContext())) + return false; + +#if RAPIDJSON_SCHEMA_VERBOSE + GenericStringBuffer sb; + schemaDocument_->GetPointer(&CurrentSchema()).Stringify(sb); + + *documentStack_.template Push() = '\0'; + documentStack_.template Pop(1); + internal::PrintValidatorPointers(depth_, sb.GetString(), documentStack_.template Bottom()); +#endif + + uint64_t h = CurrentContext().arrayUniqueness ? static_cast(CurrentContext().hasher)->GetHashCode() : 0; + + PopSchema(); + + if (!schemaStack_.Empty()) { + Context& context = CurrentContext(); + if (context.valueUniqueness) { + HashCodeArray* a = static_cast(context.arrayElementHashCodes); + if (!a) + CurrentContext().arrayElementHashCodes = a = new (GetStateAllocator().Malloc(sizeof(HashCodeArray))) HashCodeArray(kArrayType); + for (typename HashCodeArray::ConstValueIterator itr = a->Begin(); itr != a->End(); ++itr) + if (itr->GetUint64() == h) { + DuplicateItems(static_cast(itr - a->Begin()), a->Size()); + RAPIDJSON_INVALID_KEYWORD_RETURN(SchemaType::GetUniqueItemsString()); + } + a->PushBack(h, GetStateAllocator()); + } + } + + // Remove the last token of document pointer + while (!documentStack_.Empty() && *documentStack_.template Pop(1) != '/') + ; + + return true; + } + + void AppendToken(const Ch* str, SizeType len) { + documentStack_.template Reserve(1 + len * 2); // worst case all characters are escaped as two characters + *documentStack_.template PushUnsafe() = '/'; + for (SizeType i = 0; i < len; i++) { + if (str[i] == '~') { + *documentStack_.template PushUnsafe() = '~'; + *documentStack_.template PushUnsafe() = '0'; + } + else if (str[i] == '/') { + *documentStack_.template PushUnsafe() = '~'; + *documentStack_.template PushUnsafe() = '1'; + } + else + *documentStack_.template PushUnsafe() = str[i]; + } + } + + RAPIDJSON_FORCEINLINE void PushSchema(const SchemaType& schema) { new (schemaStack_.template Push()) Context(*this, *this, &schema); } + + RAPIDJSON_FORCEINLINE void PopSchema() { + Context* c = schemaStack_.template Pop(1); + if (HashCodeArray* a = static_cast(c->arrayElementHashCodes)) { + a->~HashCodeArray(); + StateAllocator::Free(a); + } + c->~Context(); + } + + void AddErrorLocation(ValueType& result, bool parent) { + GenericStringBuffer sb; + PointerType instancePointer = GetInvalidDocumentPointer(); + ((parent && instancePointer.GetTokenCount() > 0) + ? PointerType(instancePointer.GetTokens(), instancePointer.GetTokenCount() - 1) + : instancePointer).StringifyUriFragment(sb); + ValueType instanceRef(sb.GetString(), static_cast(sb.GetSize() / sizeof(Ch)), + GetStateAllocator()); + result.AddMember(GetInstanceRefString(), instanceRef, GetStateAllocator()); + sb.Clear(); + memcpy(sb.Push(CurrentSchema().GetURI().GetStringLength()), + CurrentSchema().GetURI().GetString(), + CurrentSchema().GetURI().GetStringLength() * sizeof(Ch)); + GetInvalidSchemaPointer().StringifyUriFragment(sb); + ValueType schemaRef(sb.GetString(), static_cast(sb.GetSize() / sizeof(Ch)), + GetStateAllocator()); + result.AddMember(GetSchemaRefString(), schemaRef, GetStateAllocator()); + } + + void AddError(ValueType& keyword, ValueType& error) { + typename ValueType::MemberIterator member = error_.FindMember(keyword); + if (member == error_.MemberEnd()) + error_.AddMember(keyword, error, GetStateAllocator()); + else { + if (member->value.IsObject()) { + ValueType errors(kArrayType); + errors.PushBack(member->value, GetStateAllocator()); + member->value = errors; + } + member->value.PushBack(error, GetStateAllocator()); + } + } + + void AddCurrentError(const typename SchemaType::ValueType& keyword, bool parent = false) { + AddErrorLocation(currentError_, parent); + AddError(ValueType(keyword, GetStateAllocator(), false).Move(), currentError_); + } + + void MergeError(ValueType& other) { + for (typename ValueType::MemberIterator it = other.MemberBegin(), end = other.MemberEnd(); it != end; ++it) { + AddError(it->name, it->value); + } + } + + void AddNumberError(const typename SchemaType::ValueType& keyword, ValueType& actual, const SValue& expected, + const typename SchemaType::ValueType& (*exclusive)() = 0) { + currentError_.SetObject(); + currentError_.AddMember(GetActualString(), actual, GetStateAllocator()); + currentError_.AddMember(GetExpectedString(), ValueType(expected, GetStateAllocator()).Move(), GetStateAllocator()); + if (exclusive) + currentError_.AddMember(ValueType(exclusive(), GetStateAllocator()).Move(), true, GetStateAllocator()); + AddCurrentError(keyword); + } + + void AddErrorArray(const typename SchemaType::ValueType& keyword, + ISchemaValidator** subvalidators, SizeType count) { + ValueType errors(kArrayType); + for (SizeType i = 0; i < count; ++i) + errors.PushBack(static_cast(subvalidators[i])->GetError(), GetStateAllocator()); + currentError_.SetObject(); + currentError_.AddMember(GetErrorsString(), errors, GetStateAllocator()); + AddCurrentError(keyword); + } + + const SchemaType& CurrentSchema() const { return *schemaStack_.template Top()->schema; } + Context& CurrentContext() { return *schemaStack_.template Top(); } + const Context& CurrentContext() const { return *schemaStack_.template Top(); } + + static const size_t kDefaultSchemaStackCapacity = 1024; + static const size_t kDefaultDocumentStackCapacity = 256; + const SchemaDocumentType* schemaDocument_; + const SchemaType& root_; + StateAllocator* stateAllocator_; + StateAllocator* ownStateAllocator_; + internal::Stack schemaStack_; //!< stack to store the current path of schema (BaseSchemaType *) + internal::Stack documentStack_; //!< stack to store the current path of validating document (Ch) + OutputHandler* outputHandler_; + ValueType error_; + ValueType currentError_; + ValueType missingDependents_; + bool valid_; +#if RAPIDJSON_SCHEMA_VERBOSE + unsigned depth_; +#endif +}; + +typedef GenericSchemaValidator SchemaValidator; + +/////////////////////////////////////////////////////////////////////////////// +// SchemaValidatingReader + +//! A helper class for parsing with validation. +/*! + This helper class is a functor, designed as a parameter of \ref GenericDocument::Populate(). + + \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept. + \tparam SourceEncoding Encoding of the input stream. + \tparam SchemaDocumentType Type of schema document. + \tparam StackAllocator Allocator type for stack. +*/ +template < + unsigned parseFlags, + typename InputStream, + typename SourceEncoding, + typename SchemaDocumentType = SchemaDocument, + typename StackAllocator = CrtAllocator> +class SchemaValidatingReader { +public: + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename InputStream::Ch Ch; + typedef GenericValue ValueType; + + //! Constructor + /*! + \param is Input stream. + \param sd Schema document. + */ + SchemaValidatingReader(InputStream& is, const SchemaDocumentType& sd) : is_(is), sd_(sd), invalidSchemaKeyword_(), error_(kObjectType), isValid_(true) {} + + template + bool operator()(Handler& handler) { + GenericReader reader; + GenericSchemaValidator validator(sd_, handler); + parseResult_ = reader.template Parse(is_, validator); + + isValid_ = validator.IsValid(); + if (isValid_) { + invalidSchemaPointer_ = PointerType(); + invalidSchemaKeyword_ = 0; + invalidDocumentPointer_ = PointerType(); + error_.SetObject(); + } + else { + invalidSchemaPointer_ = validator.GetInvalidSchemaPointer(); + invalidSchemaKeyword_ = validator.GetInvalidSchemaKeyword(); + invalidDocumentPointer_ = validator.GetInvalidDocumentPointer(); + error_.CopyFrom(validator.GetError(), allocator_); + } + + return parseResult_; + } + + const ParseResult& GetParseResult() const { return parseResult_; } + bool IsValid() const { return isValid_; } + const PointerType& GetInvalidSchemaPointer() const { return invalidSchemaPointer_; } + const Ch* GetInvalidSchemaKeyword() const { return invalidSchemaKeyword_; } + const PointerType& GetInvalidDocumentPointer() const { return invalidDocumentPointer_; } + const ValueType& GetError() const { return error_; } + +private: + InputStream& is_; + const SchemaDocumentType& sd_; + + ParseResult parseResult_; + PointerType invalidSchemaPointer_; + const Ch* invalidSchemaKeyword_; + PointerType invalidDocumentPointer_; + StackAllocator allocator_; + ValueType error_; + bool isValid_; +}; + +RAPIDJSON_NAMESPACE_END +RAPIDJSON_DIAG_POP + +#endif // RAPIDJSON_SCHEMA_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/stream.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/stream.h new file mode 100755 index 000000000..c08a87e18 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/stream.h @@ -0,0 +1,250 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "rapidjson.h" + +#ifndef RAPIDJSON_STREAM_H_ +#define RAPIDJSON_STREAM_H_ + +#include "encodings.h" + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Stream + +/*! \class rapidjson::Stream + \brief Concept for reading and writing characters. + + For read-only stream, no need to implement PutBegin(), Put(), Flush() and PutEnd(). + + For write-only stream, only need to implement Put() and Flush(). + +\code +concept Stream { + typename Ch; //!< Character type of the stream. + + //! Read the current character from stream without moving the read cursor. + Ch Peek() const; + + //! Read the current character from stream and moving the read cursor to next character. + Ch Take(); + + //! Get the current read cursor. + //! \return Number of characters read from start. + size_t Tell(); + + //! Begin writing operation at the current read pointer. + //! \return The begin writer pointer. + Ch* PutBegin(); + + //! Write a character. + void Put(Ch c); + + //! Flush the buffer. + void Flush(); + + //! End the writing operation. + //! \param begin The begin write pointer returned by PutBegin(). + //! \return Number of characters written. + size_t PutEnd(Ch* begin); +} +\endcode +*/ + +//! Provides additional information for stream. +/*! + By using traits pattern, this type provides a default configuration for stream. + For custom stream, this type can be specialized for other configuration. + See TEST(Reader, CustomStringStream) in readertest.cpp for example. +*/ +template +struct StreamTraits { + //! Whether to make local copy of stream for optimization during parsing. + /*! + By default, for safety, streams do not use local copy optimization. + Stream that can be copied fast should specialize this, like StreamTraits. + */ + enum { copyOptimization = 0 }; +}; + +//! Reserve n characters for writing to a stream. +template +inline void PutReserve(Stream& stream, size_t count) { + (void)stream; + (void)count; +} + +//! Write character to a stream, presuming buffer is reserved. +template +inline void PutUnsafe(Stream& stream, typename Stream::Ch c) { + stream.Put(c); +} + +//! Put N copies of a character to a stream. +template +inline void PutN(Stream& stream, Ch c, size_t n) { + PutReserve(stream, n); + for (size_t i = 0; i < n; i++) + PutUnsafe(stream, c); +} + +/////////////////////////////////////////////////////////////////////////////// +// GenericStreamWrapper + +//! A Stream Wrapper +/*! \tThis string stream is a wrapper for any stream by just forwarding any + \treceived message to the origin stream. + \note implements Stream concept +*/ + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4702) // unreachable code +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +template > +class GenericStreamWrapper { +public: + typedef typename Encoding::Ch Ch; + GenericStreamWrapper(InputStream& is): is_(is) {} + + Ch Peek() const { return is_.Peek(); } + Ch Take() { return is_.Take(); } + size_t Tell() { return is_.Tell(); } + Ch* PutBegin() { return is_.PutBegin(); } + void Put(Ch ch) { is_.Put(ch); } + void Flush() { is_.Flush(); } + size_t PutEnd(Ch* ch) { return is_.PutEnd(ch); } + + // wrapper for MemoryStream + const Ch* Peek4() const { return is_.Peek4(); } + + // wrapper for AutoUTFInputStream + UTFType GetType() const { return is_.GetType(); } + bool HasBOM() const { return is_.HasBOM(); } + +protected: + InputStream& is_; +}; + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_POP +#endif + +/////////////////////////////////////////////////////////////////////////////// +// StringStream + +//! Read-only string stream. +/*! \note implements Stream concept +*/ +template +struct GenericStringStream { + typedef typename Encoding::Ch Ch; + + GenericStringStream(const Ch *src) : src_(src), head_(src) {} + + Ch Peek() const { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() const { return static_cast(src_ - head_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. +}; + +template +struct StreamTraits > { + enum { copyOptimization = 1 }; +}; + +//! String stream with UTF8 encoding. +typedef GenericStringStream > StringStream; + +/////////////////////////////////////////////////////////////////////////////// +// InsituStringStream + +//! A read-write string stream. +/*! This string stream is particularly designed for in-situ parsing. + \note implements Stream concept +*/ +template +struct GenericInsituStringStream { + typedef typename Encoding::Ch Ch; + + GenericInsituStringStream(Ch *src) : src_(src), dst_(0), head_(src) { + SkipBOM(); + } + + // Read + Ch Peek() { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() { return static_cast(src_ - head_); } + + // Write + void Put(Ch c) { RAPIDJSON_ASSERT(dst_ != 0); *dst_++ = c; } + + Ch* PutBegin() { return dst_ = src_; } + size_t PutEnd(Ch* begin) { return static_cast(dst_ - begin); } + void Flush() {} + + Ch* Push(size_t count) { Ch* begin = dst_; dst_ += count; return begin; } + void Pop(size_t count) { dst_ -= count; } + + /* + Detect encoding type with BOM or RFC 4627 + BOM (Byte Order Mark): + 00 00 FE FF UTF-32BE + FF FE 00 00 UTF-32LE + FE FF UTF-16BE + FF FE UTF-16LE + EF BB BF UTF-8 + */ + void SkipBOM() { + + unsigned char *c = reinterpret_cast(src_); + if (!c) return; + + unsigned bom = (c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24)); + + if (bom == 0xFFFE0000) { Take(); Take(); Take(); Take(); } + else if (bom == 0x0000FEFF) { Take(); Take(); Take(); Take(); } + else if ((bom & 0xFFFF) == 0xFFFE) { Take(); Take(); } + else if ((bom & 0xFFFF) == 0xFEFF) { Take(); Take(); } + else if ((bom & 0xFFFFFF) == 0xBFBBEF) { Take(); Take(); Take(); } + + //It might need to clarify this file is a type of RFC 4627? + } + + Ch* src_; + Ch* dst_; + Ch* head_; +}; + +template +struct StreamTraits > { + enum { copyOptimization = 1 }; +}; + +//! Insitu string stream with UTF8 encoding. +typedef GenericInsituStringStream > InsituStringStream; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_STREAM_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/stringbuffer.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/stringbuffer.h new file mode 100755 index 000000000..4e38b82c3 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/stringbuffer.h @@ -0,0 +1,121 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_STRINGBUFFER_H_ +#define RAPIDJSON_STRINGBUFFER_H_ + +#include "stream.h" +#include "internal/stack.h" + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +#include // std::move +#endif + +#include "internal/stack.h" + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory output stream. +/*! + \tparam Encoding Encoding of the stream. + \tparam Allocator type for allocating memory buffer. + \note implements Stream concept +*/ +template +class GenericStringBuffer { +public: + typedef typename Encoding::Ch Ch; + + GenericStringBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericStringBuffer(GenericStringBuffer&& rhs) : stack_(std::move(rhs.stack_)) {} + GenericStringBuffer& operator=(GenericStringBuffer&& rhs) { + if (&rhs != this) + stack_ = std::move(rhs.stack_); + return *this; + } +#endif + + void Put(Ch c) { *stack_.template Push() = c; } + void PutUnsafe(Ch c) { *stack_.template PushUnsafe() = c; } + void Flush() {} + + void Clear() { stack_.Clear(); } + void ShrinkToFit() { + // Push and pop a null terminator. This is safe. + *stack_.template Push() = '\0'; + stack_.ShrinkToFit(); + stack_.template Pop(1); + } + + void Reserve(size_t count) { stack_.template Reserve(count); } + Ch* Push(size_t count) { return stack_.template Push(count); } + Ch* PushUnsafe(size_t count) { return stack_.template PushUnsafe(count); } + void Pop(size_t count) { stack_.template Pop(count); } + + const Ch* GetString() const { + // Push and pop a null terminator. This is safe. + *stack_.template Push() = '\0'; + stack_.template Pop(1); + + return stack_.template Bottom(); + } + + //! Get the size of string in bytes in the string buffer. + size_t GetSize() const { return stack_.GetSize(); } + + //! Get the length of string in Ch in the string buffer. + size_t GetLength() const { return stack_.GetSize() / sizeof(Ch); } + + static const size_t kDefaultCapacity = 256; + mutable internal::Stack stack_; + +private: + // Prohibit copy constructor & assignment operator. + GenericStringBuffer(const GenericStringBuffer&); + GenericStringBuffer& operator=(const GenericStringBuffer&); +}; + +//! String buffer with UTF8 encoding +typedef GenericStringBuffer > StringBuffer; + +template +inline void PutReserve(GenericStringBuffer& stream, size_t count) { + stream.Reserve(count); +} + +template +inline void PutUnsafe(GenericStringBuffer& stream, typename Encoding::Ch c) { + stream.PutUnsafe(c); +} + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(GenericStringBuffer >& stream, char c, size_t n) { + std::memset(stream.stack_.Push(n), c, n * sizeof(c)); +} + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_STRINGBUFFER_H_ diff --git a/TMessagesProj/jni/rlottie/src/lottie/rapidjson/writer.h b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/writer.h new file mode 100755 index 000000000..6f5b69034 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/lottie/rapidjson/writer.h @@ -0,0 +1,709 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_WRITER_H_ +#define RAPIDJSON_WRITER_H_ + +#include "stream.h" +#include "internal/meta.h" +#include "internal/stack.h" +#include "internal/strfunc.h" +#include "internal/dtoa.h" +#include "internal/itoa.h" +#include "stringbuffer.h" +#include // placement new + +#if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) +#include +#pragma intrinsic(_BitScanForward) +#endif +#ifdef RAPIDJSON_SSE42 +#include +#elif defined(RAPIDJSON_SSE2) +#include +#elif defined(RAPIDJSON_NEON) +#include +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(c++98-compat) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// WriteFlag + +/*! \def RAPIDJSON_WRITE_DEFAULT_FLAGS + \ingroup RAPIDJSON_CONFIG + \brief User-defined kWriteDefaultFlags definition. + + User can define this as any \c WriteFlag combinations. +*/ +#ifndef RAPIDJSON_WRITE_DEFAULT_FLAGS +#define RAPIDJSON_WRITE_DEFAULT_FLAGS kWriteNoFlags +#endif + +//! Combination of writeFlags +enum WriteFlag { + kWriteNoFlags = 0, //!< No flags are set. + kWriteValidateEncodingFlag = 1, //!< Validate encoding of JSON strings. + kWriteNanAndInfFlag = 2, //!< Allow writing of Infinity, -Infinity and NaN. + kWriteDefaultFlags = RAPIDJSON_WRITE_DEFAULT_FLAGS //!< Default write flags. Can be customized by defining RAPIDJSON_WRITE_DEFAULT_FLAGS +}; + +//! JSON writer +/*! Writer implements the concept Handler. + It generates JSON text by events to an output os. + + User may programmatically calls the functions of a writer to generate JSON text. + + On the other side, a writer can also be passed to objects that generates events, + + for example Reader::Parse() and Document::Accept(). + + \tparam OutputStream Type of output stream. + \tparam SourceEncoding Encoding of source string. + \tparam TargetEncoding Encoding of output stream. + \tparam StackAllocator Type of allocator for allocating memory of stack. + \note implements Handler concept +*/ +template, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator, unsigned writeFlags = kWriteDefaultFlags> +class Writer { +public: + typedef typename SourceEncoding::Ch Ch; + + static const int kDefaultMaxDecimalPlaces = 324; + + //! Constructor + /*! \param os Output stream. + \param stackAllocator User supplied allocator. If it is null, it will create a private one. + \param levelDepth Initial capacity of stack. + */ + explicit + Writer(OutputStream& os, StackAllocator* stackAllocator = 0, size_t levelDepth = kDefaultLevelDepth) : + os_(&os), level_stack_(stackAllocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} + + explicit + Writer(StackAllocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) : + os_(0), level_stack_(allocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Writer(Writer&& rhs) : + os_(rhs.os_), level_stack_(std::move(rhs.level_stack_)), maxDecimalPlaces_(rhs.maxDecimalPlaces_), hasRoot_(rhs.hasRoot_) { + rhs.os_ = 0; + } +#endif + + //! Reset the writer with a new stream. + /*! + This function reset the writer with a new stream and default settings, + in order to make a Writer object reusable for output multiple JSONs. + + \param os New output stream. + \code + Writer writer(os1); + writer.StartObject(); + // ... + writer.EndObject(); + + writer.Reset(os2); + writer.StartObject(); + // ... + writer.EndObject(); + \endcode + */ + void Reset(OutputStream& os) { + os_ = &os; + hasRoot_ = false; + level_stack_.Clear(); + } + + //! Checks whether the output is a complete JSON. + /*! + A complete JSON has a complete root object or array. + */ + bool IsComplete() const { + return hasRoot_ && level_stack_.Empty(); + } + + int GetMaxDecimalPlaces() const { + return maxDecimalPlaces_; + } + + //! Sets the maximum number of decimal places for double output. + /*! + This setting truncates the output with specified number of decimal places. + + For example, + + \code + writer.SetMaxDecimalPlaces(3); + writer.StartArray(); + writer.Double(0.12345); // "0.123" + writer.Double(0.0001); // "0.0" + writer.Double(1.234567890123456e30); // "1.234567890123456e30" (do not truncate significand for positive exponent) + writer.Double(1.23e-4); // "0.0" (do truncate significand for negative exponent) + writer.EndArray(); + \endcode + + The default setting does not truncate any decimal places. You can restore to this setting by calling + \code + writer.SetMaxDecimalPlaces(Writer::kDefaultMaxDecimalPlaces); + \endcode + */ + void SetMaxDecimalPlaces(int maxDecimalPlaces) { + maxDecimalPlaces_ = maxDecimalPlaces; + } + + /*!@name Implementation of Handler + \see Handler + */ + //@{ + + bool Null() { Prefix(kNullType); return EndValue(WriteNull()); } + bool Bool(bool b) { Prefix(b ? kTrueType : kFalseType); return EndValue(WriteBool(b)); } + bool Int(int i) { Prefix(kNumberType); return EndValue(WriteInt(i)); } + bool Uint(unsigned u) { Prefix(kNumberType); return EndValue(WriteUint(u)); } + bool Int64(int64_t i64) { Prefix(kNumberType); return EndValue(WriteInt64(i64)); } + bool Uint64(uint64_t u64) { Prefix(kNumberType); return EndValue(WriteUint64(u64)); } + + //! Writes the given \c double value to the stream + /*! + \param d The value to be written. + \return Whether it is succeed. + */ + bool Double(double d) { Prefix(kNumberType); return EndValue(WriteDouble(d)); } + + bool RawNumber(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + Prefix(kNumberType); + return EndValue(WriteString(str, length)); + } + + bool String(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + Prefix(kStringType); + return EndValue(WriteString(str, length)); + } + +#if RAPIDJSON_HAS_STDSTRING + bool String(const std::basic_string& str) { + return String(str.data(), SizeType(str.size())); + } +#endif + + bool StartObject() { + Prefix(kObjectType); + new (level_stack_.template Push()) Level(false); + return WriteStartObject(); + } + + bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } + +#if RAPIDJSON_HAS_STDSTRING + bool Key(const std::basic_string& str) + { + return Key(str.data(), SizeType(str.size())); + } +#endif + + bool EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); // not inside an Object + RAPIDJSON_ASSERT(!level_stack_.template Top()->inArray); // currently inside an Array, not Object + RAPIDJSON_ASSERT(0 == level_stack_.template Top()->valueCount % 2); // Object has a Key without a Value + level_stack_.template Pop(1); + return EndValue(WriteEndObject()); + } + + bool StartArray() { + Prefix(kArrayType); + new (level_stack_.template Push()) Level(true); + return WriteStartArray(); + } + + bool EndArray(SizeType elementCount = 0) { + (void)elementCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); + RAPIDJSON_ASSERT(level_stack_.template Top()->inArray); + level_stack_.template Pop(1); + return EndValue(WriteEndArray()); + } + //@} + + /*! @name Convenience extensions */ + //@{ + + //! Simpler but slower overload. + bool String(const Ch* const& str) { return String(str, internal::StrLen(str)); } + bool Key(const Ch* const& str) { return Key(str, internal::StrLen(str)); } + + //@} + + //! Write a raw JSON value. + /*! + For user to write a stringified JSON as a value. + + \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. + \param length Length of the json. + \param type Type of the root of json. + */ + bool RawValue(const Ch* json, size_t length, Type type) { + RAPIDJSON_ASSERT(json != 0); + Prefix(type); + return EndValue(WriteRawValue(json, length)); + } + + //! Flush the output stream. + /*! + Allows the user to flush the output stream immediately. + */ + void Flush() { + os_->Flush(); + } + +protected: + //! Information for each nested level + struct Level { + Level(bool inArray_) : valueCount(0), inArray(inArray_) {} + size_t valueCount; //!< number of values in this level + bool inArray; //!< true if in array, otherwise in object + }; + + static const size_t kDefaultLevelDepth = 32; + + bool WriteNull() { + PutReserve(*os_, 4); + PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 'l'); return true; + } + + bool WriteBool(bool b) { + if (b) { + PutReserve(*os_, 4); + PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'r'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'e'); + } + else { + PutReserve(*os_, 5); + PutUnsafe(*os_, 'f'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 's'); PutUnsafe(*os_, 'e'); + } + return true; + } + + bool WriteInt(int i) { + char buffer[11]; + const char* end = internal::i32toa(i, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteUint(unsigned u) { + char buffer[10]; + const char* end = internal::u32toa(u, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteInt64(int64_t i64) { + char buffer[21]; + const char* end = internal::i64toa(i64, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteUint64(uint64_t u64) { + char buffer[20]; + char* end = internal::u64toa(u64, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteDouble(double d) { + if (internal::Double(d).IsNanOrInf()) { + if (!(writeFlags & kWriteNanAndInfFlag)) + return false; + if (internal::Double(d).IsNan()) { + PutReserve(*os_, 3); + PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N'); + return true; + } + if (internal::Double(d).Sign()) { + PutReserve(*os_, 9); + PutUnsafe(*os_, '-'); + } + else + PutReserve(*os_, 8); + PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f'); + PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y'); + return true; + } + + char buffer[25]; + char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); + PutReserve(*os_, static_cast(end - buffer)); + for (char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteString(const Ch* str, SizeType length) { + static const typename OutputStream::Ch hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + static const char escape[256] = { +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', 'u', 'f', 'r', 'u', 'u', // 00 + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', // 10 + 0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 + Z16, Z16, // 30~4F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, // 50 + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 // 60~FF +#undef Z16 + }; + + if (TargetEncoding::supportUnicode) + PutReserve(*os_, 2 + length * 6); // "\uxxxx..." + else + PutReserve(*os_, 2 + length * 12); // "\uxxxx\uyyyy..." + + PutUnsafe(*os_, '\"'); + GenericStringStream is(str); + while (ScanWriteUnescapedString(is, length)) { + const Ch c = is.Peek(); + if (!TargetEncoding::supportUnicode && static_cast(c) >= 0x80) { + // Unicode escaping + unsigned codepoint; + if (RAPIDJSON_UNLIKELY(!SourceEncoding::Decode(is, &codepoint))) + return false; + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, 'u'); + if (codepoint <= 0xD7FF || (codepoint >= 0xE000 && codepoint <= 0xFFFF)) { + PutUnsafe(*os_, hexDigits[(codepoint >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint ) & 15]); + } + else { + RAPIDJSON_ASSERT(codepoint >= 0x010000 && codepoint <= 0x10FFFF); + // Surrogate pair + unsigned s = codepoint - 0x010000; + unsigned lead = (s >> 10) + 0xD800; + unsigned trail = (s & 0x3FF) + 0xDC00; + PutUnsafe(*os_, hexDigits[(lead >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(lead >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(lead >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(lead ) & 15]); + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, 'u'); + PutUnsafe(*os_, hexDigits[(trail >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(trail >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(trail >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(trail ) & 15]); + } + } + else if ((sizeof(Ch) == 1 || static_cast(c) < 256) && RAPIDJSON_UNLIKELY(escape[static_cast(c)])) { + is.Take(); + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, static_cast(escape[static_cast(c)])); + if (escape[static_cast(c)] == 'u') { + PutUnsafe(*os_, '0'); + PutUnsafe(*os_, '0'); + PutUnsafe(*os_, hexDigits[static_cast(c) >> 4]); + PutUnsafe(*os_, hexDigits[static_cast(c) & 0xF]); + } + } + else if (RAPIDJSON_UNLIKELY(!(writeFlags & kWriteValidateEncodingFlag ? + Transcoder::Validate(is, *os_) : + Transcoder::TranscodeUnsafe(is, *os_)))) + return false; + } + PutUnsafe(*os_, '\"'); + return true; + } + + bool ScanWriteUnescapedString(GenericStringStream& is, size_t length) { + return RAPIDJSON_LIKELY(is.Tell() < length); + } + + bool WriteStartObject() { os_->Put('{'); return true; } + bool WriteEndObject() { os_->Put('}'); return true; } + bool WriteStartArray() { os_->Put('['); return true; } + bool WriteEndArray() { os_->Put(']'); return true; } + + bool WriteRawValue(const Ch* json, size_t length) { + PutReserve(*os_, length); + GenericStringStream is(json); + while (RAPIDJSON_LIKELY(is.Tell() < length)) { + RAPIDJSON_ASSERT(is.Peek() != '\0'); + if (RAPIDJSON_UNLIKELY(!(writeFlags & kWriteValidateEncodingFlag ? + Transcoder::Validate(is, *os_) : + Transcoder::TranscodeUnsafe(is, *os_)))) + return false; + } + return true; + } + + void Prefix(Type type) { + (void)type; + if (RAPIDJSON_LIKELY(level_stack_.GetSize() != 0)) { // this value is not at root + Level* level = level_stack_.template Top(); + if (level->valueCount > 0) { + if (level->inArray) + os_->Put(','); // add comma if it is not the first element in array + else // in object + os_->Put((level->valueCount % 2 == 0) ? ',' : ':'); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else { + RAPIDJSON_ASSERT(!hasRoot_); // Should only has one and only one root. + hasRoot_ = true; + } + } + + // Flush the value if it is the top level one. + bool EndValue(bool ret) { + if (RAPIDJSON_UNLIKELY(level_stack_.Empty())) // end of json text + Flush(); + return ret; + } + + OutputStream* os_; + internal::Stack level_stack_; + int maxDecimalPlaces_; + bool hasRoot_; + +private: + // Prohibit copy constructor & assignment operator. + Writer(const Writer&); + Writer& operator=(const Writer&); +}; + +// Full specialization for StringStream to prevent memory copying + +template<> +inline bool Writer::WriteInt(int i) { + char *buffer = os_->Push(11); + const char* end = internal::i32toa(i, buffer); + os_->Pop(static_cast(11 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteUint(unsigned u) { + char *buffer = os_->Push(10); + const char* end = internal::u32toa(u, buffer); + os_->Pop(static_cast(10 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteInt64(int64_t i64) { + char *buffer = os_->Push(21); + const char* end = internal::i64toa(i64, buffer); + os_->Pop(static_cast(21 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteUint64(uint64_t u) { + char *buffer = os_->Push(20); + const char* end = internal::u64toa(u, buffer); + os_->Pop(static_cast(20 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteDouble(double d) { + if (internal::Double(d).IsNanOrInf()) { + // Note: This code path can only be reached if (RAPIDJSON_WRITE_DEFAULT_FLAGS & kWriteNanAndInfFlag). + if (!(kWriteDefaultFlags & kWriteNanAndInfFlag)) + return false; + if (internal::Double(d).IsNan()) { + PutReserve(*os_, 3); + PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N'); + return true; + } + if (internal::Double(d).Sign()) { + PutReserve(*os_, 9); + PutUnsafe(*os_, '-'); + } + else + PutReserve(*os_, 8); + PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f'); + PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y'); + return true; + } + + char *buffer = os_->Push(25); + char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); + os_->Pop(static_cast(25 - (end - buffer))); + return true; +} + +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) +template<> +inline bool Writer::ScanWriteUnescapedString(StringStream& is, size_t length) { + if (length < 16) + return RAPIDJSON_LIKELY(is.Tell() < length); + + if (!RAPIDJSON_LIKELY(is.Tell() < length)) + return false; + + const char* p = is.src_; + const char* end = is.head_ + length; + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + const char* endAligned = reinterpret_cast(reinterpret_cast(end) & static_cast(~15)); + if (nextAligned > end) + return true; + + while (p != nextAligned) + if (*p < 0x20 || *p == '\"' || *p == '\\') { + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); + } + else + os_->PutUnsafe(*p++); + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (; p != endAligned; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + SizeType len; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + len = offset; +#else + len = static_cast(__builtin_ffs(r) - 1); +#endif + char* q = reinterpret_cast(os_->PushUnsafe(len)); + for (size_t i = 0; i < len; i++) + q[i] = p[i]; + + p += len; + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(os_->PushUnsafe(16)), s); + } + + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); +} +#elif defined(RAPIDJSON_NEON) +template<> +inline bool Writer::ScanWriteUnescapedString(StringStream& is, size_t length) { + if (length < 16) + return RAPIDJSON_LIKELY(is.Tell() < length); + + if (!RAPIDJSON_LIKELY(is.Tell() < length)) + return false; + + const char* p = is.src_; + const char* end = is.head_ + length; + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + const char* endAligned = reinterpret_cast(reinterpret_cast(end) & static_cast(~15)); + if (nextAligned > end) + return true; + + while (p != nextAligned) + if (*p < 0x20 || *p == '\"' || *p == '\\') { + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); + } + else + os_->PutUnsafe(*p++); + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (; p != endAligned; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract + uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract + + SizeType len = 0; + bool escaped = false; + if (low == 0) { + if (high != 0) { + unsigned lz = (unsigned)__builtin_clzll(high); + len = 8 + (lz >> 3); + escaped = true; + } + } else { + unsigned lz = (unsigned)__builtin_clzll(low); + len = lz >> 3; + escaped = true; + } + if (RAPIDJSON_UNLIKELY(escaped)) { // some of characters is escaped + char* q = reinterpret_cast(os_->PushUnsafe(len)); + for (size_t i = 0; i < len; i++) + q[i] = p[i]; + + p += len; + break; + } + vst1q_u8(reinterpret_cast(os_->PushUnsafe(16)), s); + } + + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); +} +#endif // RAPIDJSON_NEON + +RAPIDJSON_NAMESPACE_END + +#if defined(_MSC_VER) || defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/TMessagesProj/jni/rlottie/src/vector/config.h b/TMessagesProj/jni/rlottie/src/vector/config.h new file mode 100755 index 000000000..116e029e1 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/config.h @@ -0,0 +1,13 @@ +#ifndef CONFIG_H +#define CONFIG_H + +//enable logging +//#define LOTTIE_LOGGING_SUPPORT + +//enable static building of image loader +//#define LOTTIE_STATIC_IMAGE_LOADER + +//enable lottie model caching +//#define LOTTIE_CACHE_SUPPORT + +#endif // CONFIG_H diff --git a/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_math.cpp b/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_math.cpp new file mode 100755 index 000000000..5a71e70e3 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_math.cpp @@ -0,0 +1,456 @@ +/***************************************************************************/ +/* */ +/* fttrigon.c */ +/* */ +/* FreeType trigonometric functions (body). */ +/* */ +/* Copyright 2001-2005, 2012-2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "v_ft_math.h" +#include + +//form https://github.com/chromium/chromium/blob/59afd8336009c9d97c22854c52e0382b62b3aa5e/third_party/abseil-cpp/absl/base/internal/bits.h + +#if defined(_MSC_VER) +#include +static unsigned int __inline clz(unsigned int x) { + unsigned long r = 0; + if (x != 0) + { + _BitScanReverse(&r, x); + } + return r; +} +#define SW_FT_MSB(x) (clz(x)) +#elif defined(__GNUC__) +#define SW_FT_MSB(x) (31 - __builtin_clz(x)) +#else +static unsigned int __inline clz(unsigned int x) { + int c = 31; + x &= ~x + 1; + if (n & 0x0000FFFF) c -= 16; + if (n & 0x00FF00FF) c -= 8; + if (n & 0x0F0F0F0F) c -= 4; + if (n & 0x33333333) c -= 2; + if (n & 0x55555555) c -= 1; + return c; +} +#define SW_FT_MSB(x) (clz(x)) +#endif + + + + + +#define SW_FT_PAD_FLOOR(x, n) ((x) & ~((n)-1)) +#define SW_FT_PAD_ROUND(x, n) SW_FT_PAD_FLOOR((x) + ((n) / 2), n) +#define SW_FT_PAD_CEIL(x, n) SW_FT_PAD_FLOOR((x) + ((n)-1), n) + +#define SW_FT_BEGIN_STMNT do { +#define SW_FT_END_STMNT \ + } \ + while (0) +/* transfer sign leaving a positive number */ +#define SW_FT_MOVE_SIGN(x, s) \ + SW_FT_BEGIN_STMNT \ + if (x < 0) { \ + x = -x; \ + s = -s; \ + } \ + SW_FT_END_STMNT + +SW_FT_Long SW_FT_MulFix(SW_FT_Long a, SW_FT_Long b) +{ + SW_FT_Int s = 1; + SW_FT_Long c; + + SW_FT_MOVE_SIGN(a, s); + SW_FT_MOVE_SIGN(b, s); + + c = (SW_FT_Long)(((SW_FT_Int64)a * b + 0x8000L) >> 16); + + return (s > 0) ? c : -c; +} + +SW_FT_Long SW_FT_MulDiv(SW_FT_Long a, SW_FT_Long b, SW_FT_Long c) +{ + SW_FT_Int s = 1; + SW_FT_Long d; + + SW_FT_MOVE_SIGN(a, s); + SW_FT_MOVE_SIGN(b, s); + SW_FT_MOVE_SIGN(c, s); + + d = (SW_FT_Long)(c > 0 ? ((SW_FT_Int64)a * b + (c >> 1)) / c : 0x7FFFFFFFL); + + return (s > 0) ? d : -d; +} + +SW_FT_Long SW_FT_DivFix(SW_FT_Long a, SW_FT_Long b) +{ + SW_FT_Int s = 1; + SW_FT_Long q; + + SW_FT_MOVE_SIGN(a, s); + SW_FT_MOVE_SIGN(b, s); + + q = (SW_FT_Long)(b > 0 ? (((SW_FT_UInt64)a << 16) + (b >> 1)) / b + : 0x7FFFFFFFL); + + return (s < 0 ? -q : q); +} + +/*************************************************************************/ +/* */ +/* This is a fixed-point CORDIC implementation of trigonometric */ +/* functions as well as transformations between Cartesian and polar */ +/* coordinates. The angles are represented as 16.16 fixed-point values */ +/* in degrees, i.e., the angular resolution is 2^-16 degrees. Note that */ +/* only vectors longer than 2^16*180/pi (or at least 22 bits) on a */ +/* discrete Cartesian grid can have the same or better angular */ +/* resolution. Therefore, to maintain this precision, some functions */ +/* require an interim upscaling of the vectors, whereas others operate */ +/* with 24-bit long vectors directly. */ +/* */ +/*************************************************************************/ + +/* the Cordic shrink factor 0.858785336480436 * 2^32 */ +#define SW_FT_TRIG_SCALE 0xDBD95B16UL + +/* the highest bit in overflow-safe vector components, */ +/* MSB of 0.858785336480436 * sqrt(0.5) * 2^30 */ +#define SW_FT_TRIG_SAFE_MSB 29 + +/* this table was generated for SW_FT_PI = 180L << 16, i.e. degrees */ +#define SW_FT_TRIG_MAX_ITERS 23 + +static const SW_FT_Fixed ft_trig_arctan_table[] = { + 1740967L, 919879L, 466945L, 234379L, 117304L, 58666L, 29335L, 14668L, + 7334L, 3667L, 1833L, 917L, 458L, 229L, 115L, 57L, + 29L, 14L, 7L, 4L, 2L, 1L}; + +/* multiply a given value by the CORDIC shrink factor */ +static SW_FT_Fixed ft_trig_downscale(SW_FT_Fixed val) +{ + SW_FT_Fixed s; + SW_FT_Int64 v; + + s = val; + val = SW_FT_ABS(val); + + v = (val * (SW_FT_Int64)SW_FT_TRIG_SCALE) + 0x100000000UL; + val = (SW_FT_Fixed)(v >> 32); + + return (s >= 0) ? val : -val; +} + +/* undefined and never called for zero vector */ +static SW_FT_Int ft_trig_prenorm(SW_FT_Vector* vec) +{ + SW_FT_Pos x, y; + SW_FT_Int shift; + + x = vec->x; + y = vec->y; + + shift = SW_FT_MSB(SW_FT_ABS(x) | SW_FT_ABS(y)); + + if (shift <= SW_FT_TRIG_SAFE_MSB) { + shift = SW_FT_TRIG_SAFE_MSB - shift; + vec->x = (SW_FT_Pos)((SW_FT_ULong)x << shift); + vec->y = (SW_FT_Pos)((SW_FT_ULong)y << shift); + } else { + shift -= SW_FT_TRIG_SAFE_MSB; + vec->x = x >> shift; + vec->y = y >> shift; + shift = -shift; + } + + return shift; +} + +static void ft_trig_pseudo_rotate(SW_FT_Vector* vec, SW_FT_Angle theta) +{ + SW_FT_Int i; + SW_FT_Fixed x, y, xtemp, b; + const SW_FT_Fixed* arctanptr; + + x = vec->x; + y = vec->y; + + /* Rotate inside [-PI/4,PI/4] sector */ + while (theta < -SW_FT_ANGLE_PI4) { + xtemp = y; + y = -x; + x = xtemp; + theta += SW_FT_ANGLE_PI2; + } + + while (theta > SW_FT_ANGLE_PI4) { + xtemp = -y; + y = x; + x = xtemp; + theta -= SW_FT_ANGLE_PI2; + } + + arctanptr = ft_trig_arctan_table; + + /* Pseudorotations, with right shifts */ + for (i = 1, b = 1; i < SW_FT_TRIG_MAX_ITERS; b <<= 1, i++) { + if (theta < 0) { + xtemp = x + ((y + b) >> i); + y = y - ((x + b) >> i); + x = xtemp; + theta += *arctanptr++; + } else { + xtemp = x - ((y + b) >> i); + y = y + ((x + b) >> i); + x = xtemp; + theta -= *arctanptr++; + } + } + + vec->x = x; + vec->y = y; +} + +static void ft_trig_pseudo_polarize(SW_FT_Vector* vec) +{ + SW_FT_Angle theta; + SW_FT_Int i; + SW_FT_Fixed x, y, xtemp, b; + const SW_FT_Fixed* arctanptr; + + x = vec->x; + y = vec->y; + + /* Get the vector into [-PI/4,PI/4] sector */ + if (y > x) { + if (y > -x) { + theta = SW_FT_ANGLE_PI2; + xtemp = y; + y = -x; + x = xtemp; + } else { + theta = y > 0 ? SW_FT_ANGLE_PI : -SW_FT_ANGLE_PI; + x = -x; + y = -y; + } + } else { + if (y < -x) { + theta = -SW_FT_ANGLE_PI2; + xtemp = -y; + y = x; + x = xtemp; + } else { + theta = 0; + } + } + + arctanptr = ft_trig_arctan_table; + + /* Pseudorotations, with right shifts */ + for (i = 1, b = 1; i < SW_FT_TRIG_MAX_ITERS; b <<= 1, i++) { + if (y > 0) { + xtemp = x + ((y + b) >> i); + y = y - ((x + b) >> i); + x = xtemp; + theta += *arctanptr++; + } else { + xtemp = x - ((y + b) >> i); + y = y + ((x + b) >> i); + x = xtemp; + theta -= *arctanptr++; + } + } + + /* round theta */ + if (theta >= 0) + theta = SW_FT_PAD_ROUND(theta, 32); + else + theta = -SW_FT_PAD_ROUND(-theta, 32); + + vec->x = x; + vec->y = theta; +} + +/* documentation is in fttrigon.h */ + +SW_FT_Fixed SW_FT_Cos(SW_FT_Angle angle) +{ + SW_FT_Vector v; + + v.x = SW_FT_TRIG_SCALE >> 8; + v.y = 0; + ft_trig_pseudo_rotate(&v, angle); + + return (v.x + 0x80L) >> 8; +} + +/* documentation is in fttrigon.h */ + +SW_FT_Fixed SW_FT_Sin(SW_FT_Angle angle) +{ + return SW_FT_Cos(SW_FT_ANGLE_PI2 - angle); +} + +/* documentation is in fttrigon.h */ + +SW_FT_Fixed SW_FT_Tan(SW_FT_Angle angle) +{ + SW_FT_Vector v; + + v.x = SW_FT_TRIG_SCALE >> 8; + v.y = 0; + ft_trig_pseudo_rotate(&v, angle); + + return SW_FT_DivFix(v.y, v.x); +} + +/* documentation is in fttrigon.h */ + +SW_FT_Angle SW_FT_Atan2(SW_FT_Fixed dx, SW_FT_Fixed dy) +{ + SW_FT_Vector v; + + if (dx == 0 && dy == 0) return 0; + + v.x = dx; + v.y = dy; + ft_trig_prenorm(&v); + ft_trig_pseudo_polarize(&v); + + return v.y; +} + +/* documentation is in fttrigon.h */ + +void SW_FT_Vector_Unit(SW_FT_Vector* vec, SW_FT_Angle angle) +{ + vec->x = SW_FT_TRIG_SCALE >> 8; + vec->y = 0; + ft_trig_pseudo_rotate(vec, angle); + vec->x = (vec->x + 0x80L) >> 8; + vec->y = (vec->y + 0x80L) >> 8; +} + +/* these macros return 0 for positive numbers, + and -1 for negative ones */ +#define SW_FT_SIGN_LONG(x) ((x) >> (SW_FT_SIZEOF_LONG * 8 - 1)) +#define SW_FT_SIGN_INT(x) ((x) >> (SW_FT_SIZEOF_INT * 8 - 1)) +#define SW_FT_SIGN_INT32(x) ((x) >> 31) +#define SW_FT_SIGN_INT16(x) ((x) >> 15) + +/* documentation is in fttrigon.h */ + +void SW_FT_Vector_Rotate(SW_FT_Vector* vec, SW_FT_Angle angle) +{ + SW_FT_Int shift; + SW_FT_Vector v; + + v.x = vec->x; + v.y = vec->y; + + if (angle && (v.x != 0 || v.y != 0)) { + shift = ft_trig_prenorm(&v); + ft_trig_pseudo_rotate(&v, angle); + v.x = ft_trig_downscale(v.x); + v.y = ft_trig_downscale(v.y); + + if (shift > 0) { + SW_FT_Int32 half = (SW_FT_Int32)1L << (shift - 1); + + vec->x = (v.x + half + SW_FT_SIGN_LONG(v.x)) >> shift; + vec->y = (v.y + half + SW_FT_SIGN_LONG(v.y)) >> shift; + } else { + shift = -shift; + vec->x = (SW_FT_Pos)((SW_FT_ULong)v.x << shift); + vec->y = (SW_FT_Pos)((SW_FT_ULong)v.y << shift); + } + } +} + +/* documentation is in fttrigon.h */ + +SW_FT_Fixed SW_FT_Vector_Length(SW_FT_Vector* vec) +{ + SW_FT_Int shift; + SW_FT_Vector v; + + v = *vec; + + /* handle trivial cases */ + if (v.x == 0) { + return SW_FT_ABS(v.y); + } else if (v.y == 0) { + return SW_FT_ABS(v.x); + } + + /* general case */ + shift = ft_trig_prenorm(&v); + ft_trig_pseudo_polarize(&v); + + v.x = ft_trig_downscale(v.x); + + if (shift > 0) return (v.x + (1 << (shift - 1))) >> shift; + + return (SW_FT_Fixed)((SW_FT_UInt32)v.x << -shift); +} + +/* documentation is in fttrigon.h */ + +void SW_FT_Vector_Polarize(SW_FT_Vector* vec, SW_FT_Fixed* length, + SW_FT_Angle* angle) +{ + SW_FT_Int shift; + SW_FT_Vector v; + + v = *vec; + + if (v.x == 0 && v.y == 0) return; + + shift = ft_trig_prenorm(&v); + ft_trig_pseudo_polarize(&v); + + v.x = ft_trig_downscale(v.x); + + *length = (shift >= 0) ? (v.x >> shift) + : (SW_FT_Fixed)((SW_FT_UInt32)v.x << -shift); + *angle = v.y; +} + +/* documentation is in fttrigon.h */ + +void SW_FT_Vector_From_Polar(SW_FT_Vector* vec, SW_FT_Fixed length, + SW_FT_Angle angle) +{ + vec->x = length; + vec->y = 0; + + SW_FT_Vector_Rotate(vec, angle); +} + +/* documentation is in fttrigon.h */ + +SW_FT_Angle SW_FT_Angle_Diff(SW_FT_Angle angle1, SW_FT_Angle angle2) +{ + SW_FT_Angle delta = angle2 - angle1; + + delta %= SW_FT_ANGLE_2PI; + if (delta < 0) delta += SW_FT_ANGLE_2PI; + + if (delta > SW_FT_ANGLE_PI) delta -= SW_FT_ANGLE_2PI; + + return delta; +} + +/* END */ diff --git a/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_math.h b/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_math.h new file mode 100755 index 000000000..b4611d8d4 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_math.h @@ -0,0 +1,438 @@ +#ifndef V_FT_MATH_H +#define V_FT_MATH_H + +/***************************************************************************/ +/* */ +/* fttrigon.h */ +/* */ +/* FreeType trigonometric functions (specification). */ +/* */ +/* Copyright 2001, 2003, 2005, 2007, 2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "v_ft_types.h" + + +/*************************************************************************/ +/* */ +/* The min and max functions missing in C. As usual, be careful not to */ +/* write things like SW_FT_MIN( a++, b++ ) to avoid side effects. */ +/* */ +#define SW_FT_MIN( a, b ) ( (a) < (b) ? (a) : (b) ) +#define SW_FT_MAX( a, b ) ( (a) > (b) ? (a) : (b) ) + +#define SW_FT_ABS( a ) ( (a) < 0 ? -(a) : (a) ) + +/* + * Approximate sqrt(x*x+y*y) using the `alpha max plus beta min' + * algorithm. We use alpha = 1, beta = 3/8, giving us results with a + * largest error less than 7% compared to the exact value. + */ +#define SW_FT_HYPOT( x, y ) \ + ( x = SW_FT_ABS( x ), \ + y = SW_FT_ABS( y ), \ + x > y ? x + ( 3 * y >> 3 ) \ + : y + ( 3 * x >> 3 ) ) + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_MulFix */ +/* */ +/* */ +/* A very simple function used to perform the computation */ +/* `(a*b)/0x10000' with maximum accuracy. Most of the time this is */ +/* used to multiply a given value by a 16.16 fixed-point factor. */ +/* */ +/* */ +/* a :: The first multiplier. */ +/* b :: The second multiplier. Use a 16.16 factor here whenever */ +/* possible (see note below). */ +/* */ +/* */ +/* The result of `(a*b)/0x10000'. */ +/* */ +/* */ +/* This function has been optimized for the case where the absolute */ +/* value of `a' is less than 2048, and `b' is a 16.16 scaling factor. */ +/* As this happens mainly when scaling from notional units to */ +/* fractional pixels in FreeType, it resulted in noticeable speed */ +/* improvements between versions 2.x and 1.x. */ +/* */ +/* As a conclusion, always try to place a 16.16 factor as the */ +/* _second_ argument of this function; this can make a great */ +/* difference. */ +/* */ +SW_FT_Long +SW_FT_MulFix( SW_FT_Long a, + SW_FT_Long b ); + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_MulDiv */ +/* */ +/* */ +/* A very simple function used to perform the computation `(a*b)/c' */ +/* with maximum accuracy (it uses a 64-bit intermediate integer */ +/* whenever necessary). */ +/* */ +/* This function isn't necessarily as fast as some processor specific */ +/* operations, but is at least completely portable. */ +/* */ +/* */ +/* a :: The first multiplier. */ +/* b :: The second multiplier. */ +/* c :: The divisor. */ +/* */ +/* */ +/* The result of `(a*b)/c'. This function never traps when trying to */ +/* divide by zero; it simply returns `MaxInt' or `MinInt' depending */ +/* on the signs of `a' and `b'. */ +/* */ +SW_FT_Long +SW_FT_MulDiv( SW_FT_Long a, + SW_FT_Long b, + SW_FT_Long c ); + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_DivFix */ +/* */ +/* */ +/* A very simple function used to perform the computation */ +/* `(a*0x10000)/b' with maximum accuracy. Most of the time, this is */ +/* used to divide a given value by a 16.16 fixed-point factor. */ +/* */ +/* */ +/* a :: The numerator. */ +/* b :: The denominator. Use a 16.16 factor here. */ +/* */ +/* */ +/* The result of `(a*0x10000)/b'. */ +/* */ +SW_FT_Long +SW_FT_DivFix( SW_FT_Long a, + SW_FT_Long b ); + + + + /*************************************************************************/ + /* */ + /*
*/ + /* computations */ + /* */ + /*************************************************************************/ + + + /************************************************************************* + * + * @type: + * SW_FT_Angle + * + * @description: + * This type is used to model angle values in FreeType. Note that the + * angle is a 16.16 fixed-point value expressed in degrees. + * + */ + typedef SW_FT_Fixed SW_FT_Angle; + + + /************************************************************************* + * + * @macro: + * SW_FT_ANGLE_PI + * + * @description: + * The angle pi expressed in @SW_FT_Angle units. + * + */ +#define SW_FT_ANGLE_PI ( 180L << 16 ) + + + /************************************************************************* + * + * @macro: + * SW_FT_ANGLE_2PI + * + * @description: + * The angle 2*pi expressed in @SW_FT_Angle units. + * + */ +#define SW_FT_ANGLE_2PI ( SW_FT_ANGLE_PI * 2 ) + + + /************************************************************************* + * + * @macro: + * SW_FT_ANGLE_PI2 + * + * @description: + * The angle pi/2 expressed in @SW_FT_Angle units. + * + */ +#define SW_FT_ANGLE_PI2 ( SW_FT_ANGLE_PI / 2 ) + + + /************************************************************************* + * + * @macro: + * SW_FT_ANGLE_PI4 + * + * @description: + * The angle pi/4 expressed in @SW_FT_Angle units. + * + */ +#define SW_FT_ANGLE_PI4 ( SW_FT_ANGLE_PI / 4 ) + + + /************************************************************************* + * + * @function: + * SW_FT_Sin + * + * @description: + * Return the sinus of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The sinus value. + * + * @note: + * If you need both the sinus and cosinus for a given angle, use the + * function @SW_FT_Vector_Unit. + * + */ + SW_FT_Fixed + SW_FT_Sin( SW_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Cos + * + * @description: + * Return the cosinus of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The cosinus value. + * + * @note: + * If you need both the sinus and cosinus for a given angle, use the + * function @SW_FT_Vector_Unit. + * + */ + SW_FT_Fixed + SW_FT_Cos( SW_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Tan + * + * @description: + * Return the tangent of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The tangent value. + * + */ + SW_FT_Fixed + SW_FT_Tan( SW_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Atan2 + * + * @description: + * Return the arc-tangent corresponding to a given vector (x,y) in + * the 2d plane. + * + * @input: + * x :: + * The horizontal vector coordinate. + * + * y :: + * The vertical vector coordinate. + * + * @return: + * The arc-tangent value (i.e. angle). + * + */ + SW_FT_Angle + SW_FT_Atan2( SW_FT_Fixed x, + SW_FT_Fixed y ); + + + /************************************************************************* + * + * @function: + * SW_FT_Angle_Diff + * + * @description: + * Return the difference between two angles. The result is always + * constrained to the ]-PI..PI] interval. + * + * @input: + * angle1 :: + * First angle. + * + * angle2 :: + * Second angle. + * + * @return: + * Constrained value of `value2-value1'. + * + */ + SW_FT_Angle + SW_FT_Angle_Diff( SW_FT_Angle angle1, + SW_FT_Angle angle2 ); + + + /************************************************************************* + * + * @function: + * SW_FT_Vector_Unit + * + * @description: + * Return the unit vector corresponding to a given angle. After the + * call, the value of `vec.x' will be `sin(angle)', and the value of + * `vec.y' will be `cos(angle)'. + * + * This function is useful to retrieve both the sinus and cosinus of a + * given angle quickly. + * + * @output: + * vec :: + * The address of target vector. + * + * @input: + * angle :: + * The input angle. + * + */ + void + SW_FT_Vector_Unit( SW_FT_Vector* vec, + SW_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Vector_Rotate + * + * @description: + * Rotate a vector by a given angle. + * + * @inout: + * vec :: + * The address of target vector. + * + * @input: + * angle :: + * The input angle. + * + */ + void + SW_FT_Vector_Rotate( SW_FT_Vector* vec, + SW_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Vector_Length + * + * @description: + * Return the length of a given vector. + * + * @input: + * vec :: + * The address of target vector. + * + * @return: + * The vector length, expressed in the same units that the original + * vector coordinates. + * + */ + SW_FT_Fixed + SW_FT_Vector_Length( SW_FT_Vector* vec ); + + + /************************************************************************* + * + * @function: + * SW_FT_Vector_Polarize + * + * @description: + * Compute both the length and angle of a given vector. + * + * @input: + * vec :: + * The address of source vector. + * + * @output: + * length :: + * The vector length. + * + * angle :: + * The vector angle. + * + */ + void + SW_FT_Vector_Polarize( SW_FT_Vector* vec, + SW_FT_Fixed *length, + SW_FT_Angle *angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Vector_From_Polar + * + * @description: + * Compute vector coordinates from a length and angle. + * + * @output: + * vec :: + * The address of source vector. + * + * @input: + * length :: + * The vector length. + * + * angle :: + * The vector angle. + * + */ + void + SW_FT_Vector_From_Polar( SW_FT_Vector* vec, + SW_FT_Fixed length, + SW_FT_Angle angle ); + + +#endif // V_FT_MATH_H diff --git a/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_raster.cpp b/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_raster.cpp new file mode 100755 index 000000000..639eaaa9f --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_raster.cpp @@ -0,0 +1,1456 @@ +/***************************************************************************/ +/* */ +/* ftgrays.c */ +/* */ +/* A new `perfect' anti-aliasing renderer (body). */ +/* */ +/* Copyright 2000-2003, 2005-2014 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +/*************************************************************************/ +/* */ +/* This is a new anti-aliasing scan-converter for FreeType 2. The */ +/* algorithm used here is _very_ different from the one in the standard */ +/* `ftraster' module. Actually, `ftgrays' computes the _exact_ */ +/* coverage of the outline on each pixel cell. */ +/* */ +/* It is based on ideas that I initially found in Raph Levien's */ +/* excellent LibArt graphics library (see http://www.levien.com/libart */ +/* for more information, though the web pages do not tell anything */ +/* about the renderer; you'll have to dive into the source code to */ +/* understand how it works). */ +/* */ +/* Note, however, that this is a _very_ different implementation */ +/* compared to Raph's. Coverage information is stored in a very */ +/* different way, and I don't use sorted vector paths. Also, it doesn't */ +/* use floating point values. */ +/* */ +/* This renderer has the following advantages: */ +/* */ +/* - It doesn't need an intermediate bitmap. Instead, one can supply a */ +/* callback function that will be called by the renderer to draw gray */ +/* spans on any target surface. You can thus do direct composition on */ +/* any kind of bitmap, provided that you give the renderer the right */ +/* callback. */ +/* */ +/* - A perfect anti-aliaser, i.e., it computes the _exact_ coverage on */ +/* each pixel cell. */ +/* */ +/* - It performs a single pass on the outline (the `standard' FT2 */ +/* renderer makes two passes). */ +/* */ +/* - It can easily be modified to render to _any_ number of gray levels */ +/* cheaply. */ +/* */ +/* - For small (< 20) pixel sizes, it is faster than the standard */ +/* renderer. */ +/* */ +/*************************************************************************/ + +#include "v_ft_raster.h" +#include "v_ft_math.h" + +/* Auxiliary macros for token concatenation. */ +#define SW_FT_ERR_XCAT(x, y) x##y +#define SW_FT_ERR_CAT(x, y) SW_FT_ERR_XCAT(x, y) + +#define SW_FT_BEGIN_STMNT do { +#define SW_FT_END_STMNT \ + } \ + while (0) + +#include +#include +#include +#include +#define SW_FT_UINT_MAX UINT_MAX +#define SW_FT_INT_MAX INT_MAX +#define SW_FT_ULONG_MAX ULONG_MAX +#define SW_FT_CHAR_BIT CHAR_BIT + +#define ft_memset memset + +#define ft_setjmp setjmp +#define ft_longjmp longjmp +#define ft_jmp_buf jmp_buf + +typedef ptrdiff_t SW_FT_PtrDist; + +#define ErrRaster_Invalid_Mode -2 +#define ErrRaster_Invalid_Outline -1 +#define ErrRaster_Invalid_Argument -3 +#define ErrRaster_Memory_Overflow -4 + +#define SW_FT_BEGIN_HEADER +#define SW_FT_END_HEADER + +/* This macro is used to indicate that a function parameter is unused. */ +/* Its purpose is simply to reduce compiler warnings. Note also that */ +/* simply defining it as `(void)x' doesn't avoid warnings with certain */ +/* ANSI compilers (e.g. LCC). */ +#define SW_FT_UNUSED(x) (x) = (x) + +#define SW_FT_THROW(e) SW_FT_ERR_CAT(ErrRaster_, e) + +/* The size in bytes of the render pool used by the scan-line converter */ +/* to do all of its work. */ +#define SW_FT_RENDER_POOL_SIZE 16384L + +typedef int (*SW_FT_Outline_MoveToFunc)(const SW_FT_Vector* to, void* user); + +#define SW_FT_Outline_MoveTo_Func SW_FT_Outline_MoveToFunc + +typedef int (*SW_FT_Outline_LineToFunc)(const SW_FT_Vector* to, void* user); + +#define SW_FT_Outline_LineTo_Func SW_FT_Outline_LineToFunc + +typedef int (*SW_FT_Outline_ConicToFunc)(const SW_FT_Vector* control, + const SW_FT_Vector* to, void* user); + +#define SW_FT_Outline_ConicTo_Func SW_FT_Outline_ConicToFunc + +typedef int (*SW_FT_Outline_CubicToFunc)(const SW_FT_Vector* control1, + const SW_FT_Vector* control2, + const SW_FT_Vector* to, void* user); + +#define SW_FT_Outline_CubicTo_Func SW_FT_Outline_CubicToFunc + +typedef struct SW_FT_Outline_Funcs_ { + SW_FT_Outline_MoveToFunc move_to; + SW_FT_Outline_LineToFunc line_to; + SW_FT_Outline_ConicToFunc conic_to; + SW_FT_Outline_CubicToFunc cubic_to; + + int shift; + SW_FT_Pos delta; + +} SW_FT_Outline_Funcs; + +#define SW_FT_DEFINE_OUTLINE_FUNCS(class_, move_to_, line_to_, conic_to_, \ + cubic_to_, shift_, delta_) \ + static const SW_FT_Outline_Funcs class_ = {move_to_, line_to_, conic_to_, \ + cubic_to_, shift_, delta_}; + +#define SW_FT_DEFINE_RASTER_FUNCS(class_, raster_new_, raster_reset_, \ + raster_render_, raster_done_) \ + const SW_FT_Raster_Funcs class_ = {raster_new_, raster_reset_, \ + raster_render_, raster_done_}; + +#ifndef SW_FT_MEM_SET +#define SW_FT_MEM_SET(d, s, c) ft_memset(d, s, c) +#endif + +#ifndef SW_FT_MEM_ZERO +#define SW_FT_MEM_ZERO(dest, count) SW_FT_MEM_SET(dest, 0, count) +#endif + +/* as usual, for the speed hungry :-) */ + +#undef RAS_ARG +#undef RAS_ARG_ +#undef RAS_VAR +#undef RAS_VAR_ + +#ifndef SW_FT_STATIC_RASTER + +#define RAS_ARG gray_PWorker worker +#define RAS_ARG_ gray_PWorker worker, + +#define RAS_VAR worker +#define RAS_VAR_ worker, + +#else /* SW_FT_STATIC_RASTER */ + +#define RAS_ARG /* empty */ +#define RAS_ARG_ /* empty */ +#define RAS_VAR /* empty */ +#define RAS_VAR_ /* empty */ + +#endif /* SW_FT_STATIC_RASTER */ + +/* must be at least 6 bits! */ +#define PIXEL_BITS 8 + +#undef FLOOR +#undef CEILING +#undef TRUNC +#undef SCALED + +#define ONE_PIXEL (1L << PIXEL_BITS) +#define PIXEL_MASK (-1L << PIXEL_BITS) +#define TRUNC(x) ((TCoord)((x) >> PIXEL_BITS)) +#define SUBPIXELS(x) ((TPos)(x) << PIXEL_BITS) +#define FLOOR(x) ((x) & -ONE_PIXEL) +#define CEILING(x) (((x) + ONE_PIXEL - 1) & -ONE_PIXEL) +#define ROUND(x) (((x) + ONE_PIXEL / 2) & -ONE_PIXEL) + +#if PIXEL_BITS >= 6 +#define UPSCALE(x) ((x) << (PIXEL_BITS - 6)) +#define DOWNSCALE(x) ((x) >> (PIXEL_BITS - 6)) +#else +#define UPSCALE(x) ((x) >> (6 - PIXEL_BITS)) +#define DOWNSCALE(x) ((x) << (6 - PIXEL_BITS)) +#endif + +/* Compute `dividend / divisor' and return both its quotient and */ +/* remainder, cast to a specific type. This macro also ensures that */ +/* the remainder is always positive. */ +#define SW_FT_DIV_MOD(type, dividend, divisor, quotient, remainder) \ + SW_FT_BEGIN_STMNT(quotient) = (type)((dividend) / (divisor)); \ + (remainder) = (type)((dividend) % (divisor)); \ + if ((remainder) < 0) { \ + (quotient)--; \ + (remainder) += (type)(divisor); \ + } \ + SW_FT_END_STMNT + +#ifdef __arm__ +/* Work around a bug specific to GCC which make the compiler fail to */ +/* optimize a division and modulo operation on the same parameters */ +/* into a single call to `__aeabi_idivmod'. See */ +/* */ +/* http://gcc.gnu.org/bugzilla/show_bug.cgi?id=43721 */ +#undef SW_FT_DIV_MOD +#define SW_FT_DIV_MOD(type, dividend, divisor, quotient, remainder) \ + SW_FT_BEGIN_STMNT(quotient) = (type)((dividend) / (divisor)); \ + (remainder) = (type)((dividend) - (quotient) * (divisor)); \ + if ((remainder) < 0) { \ + (quotient)--; \ + (remainder) += (type)(divisor); \ + } \ + SW_FT_END_STMNT +#endif /* __arm__ */ + +/* These macros speed up repetitive divisions by replacing them */ +/* with multiplications and right shifts. */ +#define SW_FT_UDIVPREP(b) \ + long b##_r = (long)(SW_FT_ULONG_MAX >> PIXEL_BITS) / (b) +#define SW_FT_UDIV(a, b) \ + (((unsigned long)(a) * (unsigned long)(b##_r)) >> \ + (sizeof(long) * SW_FT_CHAR_BIT - PIXEL_BITS)) + +/*************************************************************************/ +/* */ +/* TYPE DEFINITIONS */ +/* */ + +/* don't change the following types to SW_FT_Int or SW_FT_Pos, since we might */ +/* need to define them to "float" or "double" when experimenting with */ +/* new algorithms */ + +typedef long TCoord; /* integer scanline/pixel coordinate */ +typedef long TPos; /* sub-pixel coordinate */ + +/* determine the type used to store cell areas. This normally takes at */ +/* least PIXEL_BITS*2 + 1 bits. On 16-bit systems, we need to use */ +/* `long' instead of `int', otherwise bad things happen */ + +#if PIXEL_BITS <= 7 + +typedef int TArea; + +#else /* PIXEL_BITS >= 8 */ + +/* approximately determine the size of integers using an ANSI-C header */ +#if SW_FT_UINT_MAX == 0xFFFFU +typedef long TArea; +#else +typedef int TArea; +#endif + +#endif /* PIXEL_BITS >= 8 */ + +/* maximum number of gray spans in a call to the span callback */ +#define SW_FT_MAX_GRAY_SPANS 256 + +typedef struct TCell_* PCell; + +typedef struct TCell_ { + TPos x; /* same with gray_TWorker.ex */ + TCoord cover; /* same with gray_TWorker.cover */ + TArea area; + PCell next; + +} TCell; + +#if defined(_MSC_VER) /* Visual C++ (and Intel C++) */ +/* We disable the warning `structure was padded due to */ +/* __declspec(align())' in order to compile cleanly with */ +/* the maximum level of warnings. */ +#pragma warning(push) +#pragma warning(disable : 4324) +#endif /* _MSC_VER */ + +typedef struct gray_TWorker_ { + TCoord ex, ey; + TPos min_ex, max_ex; + TPos min_ey, max_ey; + TPos count_ex, count_ey; + + TArea area; + TCoord cover; + int invalid; + + PCell cells; + SW_FT_PtrDist max_cells; + SW_FT_PtrDist num_cells; + + TPos x, y; + + SW_FT_Vector bez_stack[32 * 3 + 1]; + int lev_stack[32]; + + SW_FT_Outline outline; + SW_FT_BBox clip_box; + + int bound_left; + int bound_top; + int bound_right; + int bound_bottom; + + SW_FT_Span gray_spans[SW_FT_MAX_GRAY_SPANS]; + int num_gray_spans; + + SW_FT_Raster_Span_Func render_span; + void* render_span_data; + + int band_size; + int band_shoot; + + ft_jmp_buf jump_buffer; + + void* buffer; + long buffer_size; + + PCell* ycells; + TPos ycount; + +} gray_TWorker, *gray_PWorker; + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#ifndef SW_FT_STATIC_RASTER +#define ras (*worker) +#else +static gray_TWorker ras; +#endif + +typedef struct gray_TRaster_ { + void* memory; + +} gray_TRaster, *gray_PRaster; + +/*************************************************************************/ +/* */ +/* Initialize the cells table. */ +/* */ +static void gray_init_cells(RAS_ARG_ void* buffer, long byte_size) +{ + ras.buffer = buffer; + ras.buffer_size = byte_size; + + ras.ycells = (PCell*)buffer; + ras.cells = NULL; + ras.max_cells = 0; + ras.num_cells = 0; + ras.area = 0; + ras.cover = 0; + ras.invalid = 1; + + ras.bound_left = INT_MAX; + ras.bound_top = INT_MAX; + ras.bound_right = INT_MIN; + ras.bound_bottom = INT_MIN; +} + +/*************************************************************************/ +/* */ +/* Compute the outline bounding box. */ +/* */ +static void gray_compute_cbox(RAS_ARG) +{ + SW_FT_Outline* outline = &ras.outline; + SW_FT_Vector* vec = outline->points; + SW_FT_Vector* limit = vec + outline->n_points; + + if (outline->n_points <= 0) { + ras.min_ex = ras.max_ex = 0; + ras.min_ey = ras.max_ey = 0; + return; + } + + ras.min_ex = ras.max_ex = vec->x; + ras.min_ey = ras.max_ey = vec->y; + + vec++; + + for (; vec < limit; vec++) { + TPos x = vec->x; + TPos y = vec->y; + + if (x < ras.min_ex) ras.min_ex = x; + if (x > ras.max_ex) ras.max_ex = x; + if (y < ras.min_ey) ras.min_ey = y; + if (y > ras.max_ey) ras.max_ey = y; + } + + /* truncate the bounding box to integer pixels */ + ras.min_ex = ras.min_ex >> 6; + ras.min_ey = ras.min_ey >> 6; + ras.max_ex = (ras.max_ex + 63) >> 6; + ras.max_ey = (ras.max_ey + 63) >> 6; +} + +/*************************************************************************/ +/* */ +/* Record the current cell in the table. */ +/* */ +static PCell gray_find_cell(RAS_ARG) +{ + PCell *pcell, cell; + TPos x = ras.ex; + + if (x > ras.count_ex) x = ras.count_ex; + + pcell = &ras.ycells[ras.ey]; + for (;;) { + cell = *pcell; + if (cell == NULL || cell->x > x) break; + + if (cell->x == x) goto Exit; + + pcell = &cell->next; + } + + if (ras.num_cells >= ras.max_cells) ft_longjmp(ras.jump_buffer, 1); + + cell = ras.cells + ras.num_cells++; + cell->x = x; + cell->area = 0; + cell->cover = 0; + + cell->next = *pcell; + *pcell = cell; + +Exit: + return cell; +} + +static void gray_record_cell(RAS_ARG) +{ + if (ras.area | ras.cover) { + PCell cell = gray_find_cell(RAS_VAR); + + cell->area += ras.area; + cell->cover += ras.cover; + } +} + +/*************************************************************************/ +/* */ +/* Set the current cell to a new position. */ +/* */ +static void gray_set_cell(RAS_ARG_ TCoord ex, TCoord ey) +{ + /* Move the cell pointer to a new position. We set the `invalid' */ + /* flag to indicate that the cell isn't part of those we're interested */ + /* in during the render phase. This means that: */ + /* */ + /* . the new vertical position must be within min_ey..max_ey-1. */ + /* . the new horizontal position must be strictly less than max_ex */ + /* */ + /* Note that if a cell is to the left of the clipping region, it is */ + /* actually set to the (min_ex-1) horizontal position. */ + + /* All cells that are on the left of the clipping region go to the */ + /* min_ex - 1 horizontal position. */ + ey -= ras.min_ey; + + if (ex > ras.max_ex) ex = ras.max_ex; + + ex -= ras.min_ex; + if (ex < 0) ex = -1; + + /* are we moving to a different cell ? */ + if (ex != ras.ex || ey != ras.ey) { + /* record the current one if it is valid */ + if (!ras.invalid) gray_record_cell(RAS_VAR); + + ras.area = 0; + ras.cover = 0; + ras.ex = ex; + ras.ey = ey; + } + + ras.invalid = + ((unsigned)ey >= (unsigned)ras.count_ey || ex >= ras.count_ex); +} + +/*************************************************************************/ +/* */ +/* Start a new contour at a given cell. */ +/* */ +static void gray_start_cell(RAS_ARG_ TCoord ex, TCoord ey) +{ + if (ex > ras.max_ex) ex = (TCoord)(ras.max_ex); + + if (ex < ras.min_ex) ex = (TCoord)(ras.min_ex - 1); + + ras.area = 0; + ras.cover = 0; + ras.ex = ex - ras.min_ex; + ras.ey = ey - ras.min_ey; + ras.invalid = 0; + + gray_set_cell(RAS_VAR_ ex, ey); +} + +/*************************************************************************/ +/* */ +/* Render a straight line across multiple cells in any direction. */ +/* */ +static void gray_render_line(RAS_ARG_ TPos to_x, TPos to_y) +{ + TPos dx, dy, fx1, fy1, fx2, fy2; + TCoord ex1, ex2, ey1, ey2; + + ex1 = TRUNC(ras.x); + ex2 = TRUNC(to_x); + ey1 = TRUNC(ras.y); + ey2 = TRUNC(to_y); + + /* perform vertical clipping */ + if ((ey1 >= ras.max_ey && ey2 >= ras.max_ey) || + (ey1 < ras.min_ey && ey2 < ras.min_ey)) + goto End; + + dx = to_x - ras.x; + dy = to_y - ras.y; + + fx1 = ras.x - SUBPIXELS(ex1); + fy1 = ras.y - SUBPIXELS(ey1); + + if (ex1 == ex2 && ey1 == ey2) /* inside one cell */ + ; + else if (dy == 0) /* ex1 != ex2 */ /* any horizontal line */ + { + ex1 = ex2; + gray_set_cell(RAS_VAR_ ex1, ey1); + } else if (dx == 0) { + if (dy > 0) /* vertical line up */ + do { + fy2 = ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * fx1 * 2; + fy1 = 0; + ey1++; + gray_set_cell(RAS_VAR_ ex1, ey1); + } while (ey1 != ey2); + else /* vertical line down */ + do { + fy2 = 0; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * fx1 * 2; + fy1 = ONE_PIXEL; + ey1--; + gray_set_cell(RAS_VAR_ ex1, ey1); + } while (ey1 != ey2); + } else /* any other line */ + { + TArea prod = dx * fy1 - dy * fx1; + SW_FT_UDIVPREP(dx); + SW_FT_UDIVPREP(dy); + + /* The fundamental value `prod' determines which side and the */ + /* exact coordinate where the line exits current cell. It is */ + /* also easily updated when moving from one cell to the next. */ + do { + if (prod <= 0 && prod - dx * ONE_PIXEL > 0) /* left */ + { + fx2 = 0; + fy2 = (TPos)SW_FT_UDIV(-prod, -dx); + prod -= dy * ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = ONE_PIXEL; + fy1 = fy2; + ex1--; + } else if (prod - dx * ONE_PIXEL <= 0 && + prod - dx * ONE_PIXEL + dy * ONE_PIXEL > 0) /* up */ + { + prod -= dx * ONE_PIXEL; + fx2 = (TPos)SW_FT_UDIV(-prod, dy); + fy2 = ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = fx2; + fy1 = 0; + ey1++; + } else if (prod - dx * ONE_PIXEL + dy * ONE_PIXEL <= 0 && + prod + dy * ONE_PIXEL >= 0) /* right */ + { + prod += dy * ONE_PIXEL; + fx2 = ONE_PIXEL; + fy2 = (TPos)SW_FT_UDIV(prod, dx); + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = 0; + fy1 = fy2; + ex1++; + } else /* ( prod + dy * ONE_PIXEL < 0 && + prod > 0 ) down */ + { + fx2 = (TPos)SW_FT_UDIV(prod, -dy); + fy2 = 0; + prod += dx * ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = fx2; + fy1 = ONE_PIXEL; + ey1--; + } + + gray_set_cell(RAS_VAR_ ex1, ey1); + } while (ex1 != ex2 || ey1 != ey2); + } + + fx2 = to_x - SUBPIXELS(ex2); + fy2 = to_y - SUBPIXELS(ey2); + + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + +End: + ras.x = to_x; + ras.y = to_y; +} + +static void gray_split_conic(SW_FT_Vector* base) +{ + TPos a, b; + + base[4].x = base[2].x; + b = base[1].x; + a = base[3].x = (base[2].x + b) / 2; + b = base[1].x = (base[0].x + b) / 2; + base[2].x = (a + b) / 2; + + base[4].y = base[2].y; + b = base[1].y; + a = base[3].y = (base[2].y + b) / 2; + b = base[1].y = (base[0].y + b) / 2; + base[2].y = (a + b) / 2; +} + +static void gray_render_conic(RAS_ARG_ const SW_FT_Vector* control, + const SW_FT_Vector* to) +{ + TPos dx, dy; + TPos min, max, y; + int top, level; + int* levels; + SW_FT_Vector* arc; + + levels = ras.lev_stack; + + arc = ras.bez_stack; + arc[0].x = UPSCALE(to->x); + arc[0].y = UPSCALE(to->y); + arc[1].x = UPSCALE(control->x); + arc[1].y = UPSCALE(control->y); + arc[2].x = ras.x; + arc[2].y = ras.y; + top = 0; + + dx = SW_FT_ABS(arc[2].x + arc[0].x - 2 * arc[1].x); + dy = SW_FT_ABS(arc[2].y + arc[0].y - 2 * arc[1].y); + if (dx < dy) dx = dy; + + if (dx < ONE_PIXEL / 4) goto Draw; + + /* short-cut the arc that crosses the current band */ + min = max = arc[0].y; + + y = arc[1].y; + if (y < min) min = y; + if (y > max) max = y; + + y = arc[2].y; + if (y < min) min = y; + if (y > max) max = y; + + if (TRUNC(min) >= ras.max_ey || TRUNC(max) < ras.min_ey) goto Draw; + + level = 0; + do { + dx >>= 2; + level++; + } while (dx > ONE_PIXEL / 4); + + levels[0] = level; + + do { + level = levels[top]; + if (level > 0) { + gray_split_conic(arc); + arc += 2; + top++; + levels[top] = levels[top - 1] = level - 1; + continue; + } + + Draw: + gray_render_line(RAS_VAR_ arc[0].x, arc[0].y); + top--; + arc -= 2; + + } while (top >= 0); +} + +static void gray_split_cubic(SW_FT_Vector* base) +{ + TPos a, b, c, d; + + base[6].x = base[3].x; + c = base[1].x; + d = base[2].x; + base[1].x = a = (base[0].x + c) / 2; + base[5].x = b = (base[3].x + d) / 2; + c = (c + d) / 2; + base[2].x = a = (a + c) / 2; + base[4].x = b = (b + c) / 2; + base[3].x = (a + b) / 2; + + base[6].y = base[3].y; + c = base[1].y; + d = base[2].y; + base[1].y = a = (base[0].y + c) / 2; + base[5].y = b = (base[3].y + d) / 2; + c = (c + d) / 2; + base[2].y = a = (a + c) / 2; + base[4].y = b = (b + c) / 2; + base[3].y = (a + b) / 2; +} + +static void gray_render_cubic(RAS_ARG_ const SW_FT_Vector* control1, + const SW_FT_Vector* control2, + const SW_FT_Vector* to) +{ + SW_FT_Vector* arc; + TPos min, max, y; + + arc = ras.bez_stack; + arc[0].x = UPSCALE(to->x); + arc[0].y = UPSCALE(to->y); + arc[1].x = UPSCALE(control2->x); + arc[1].y = UPSCALE(control2->y); + arc[2].x = UPSCALE(control1->x); + arc[2].y = UPSCALE(control1->y); + arc[3].x = ras.x; + arc[3].y = ras.y; + + /* Short-cut the arc that crosses the current band. */ + min = max = arc[0].y; + + y = arc[1].y; + if (y < min) min = y; + if (y > max) max = y; + + y = arc[2].y; + if (y < min) min = y; + if (y > max) max = y; + + y = arc[3].y; + if (y < min) min = y; + if (y > max) max = y; + + if (TRUNC(min) >= ras.max_ey || TRUNC(max) < ras.min_ey) goto Draw; + + for (;;) { + /* Decide whether to split or draw. See `Rapid Termination */ + /* Evaluation for Recursive Subdivision of Bezier Curves' by Thomas */ + /* F. Hain, at */ + /* http://www.cis.southalabama.edu/~hain/general/Publications/Bezier/Camera-ready%20CISST02%202.pdf + */ + + { + TPos dx, dy, dx_, dy_; + TPos dx1, dy1, dx2, dy2; + TPos L, s, s_limit; + + /* dx and dy are x and y components of the P0-P3 chord vector. */ + dx = dx_ = arc[3].x - arc[0].x; + dy = dy_ = arc[3].y - arc[0].y; + + L = SW_FT_HYPOT(dx_, dy_); + + /* Avoid possible arithmetic overflow below by splitting. */ + if (L > 32767) goto Split; + + /* Max deviation may be as much as (s/L) * 3/4 (if Hain's v = 1). */ + s_limit = L * (TPos)(ONE_PIXEL / 6); + + /* s is L * the perpendicular distance from P1 to the line P0-P3. */ + dx1 = arc[1].x - arc[0].x; + dy1 = arc[1].y - arc[0].y; + s = SW_FT_ABS(dy * dx1 - dx * dy1); + + if (s > s_limit) goto Split; + + /* s is L * the perpendicular distance from P2 to the line P0-P3. */ + dx2 = arc[2].x - arc[0].x; + dy2 = arc[2].y - arc[0].y; + s = SW_FT_ABS(dy * dx2 - dx * dy2); + + if (s > s_limit) goto Split; + + /* Split super curvy segments where the off points are so far + from the chord that the angles P0-P1-P3 or P0-P2-P3 become + acute as detected by appropriate dot products. */ + if (dx1 * (dx1 - dx) + dy1 * (dy1 - dy) > 0 || + dx2 * (dx2 - dx) + dy2 * (dy2 - dy) > 0) + goto Split; + + /* No reason to split. */ + goto Draw; + } + + Split: + gray_split_cubic(arc); + arc += 3; + continue; + + Draw: + gray_render_line(RAS_VAR_ arc[0].x, arc[0].y); + + if (arc == ras.bez_stack) return; + + arc -= 3; + } +} + +static int gray_move_to(const SW_FT_Vector* to, gray_PWorker worker) +{ + TPos x, y; + + /* record current cell, if any */ + if (!ras.invalid) gray_record_cell(RAS_VAR); + + /* start to a new position */ + x = UPSCALE(to->x); + y = UPSCALE(to->y); + + gray_start_cell(RAS_VAR_ TRUNC(x), TRUNC(y)); + + worker->x = x; + worker->y = y; + return 0; +} + +static int gray_line_to(const SW_FT_Vector* to, gray_PWorker worker) +{ + gray_render_line(RAS_VAR_ UPSCALE(to->x), UPSCALE(to->y)); + return 0; +} + +static int gray_conic_to(const SW_FT_Vector* control, const SW_FT_Vector* to, + gray_PWorker worker) +{ + gray_render_conic(RAS_VAR_ control, to); + return 0; +} + +static int gray_cubic_to(const SW_FT_Vector* control1, + const SW_FT_Vector* control2, const SW_FT_Vector* to, + gray_PWorker worker) +{ + gray_render_cubic(RAS_VAR_ control1, control2, to); + return 0; +} + +static void gray_hline(RAS_ARG_ TCoord x, TCoord y, TPos area, TCoord acount) +{ + int coverage; + + /* compute the coverage line's coverage, depending on the */ + /* outline fill rule */ + /* */ + /* the coverage percentage is area/(PIXEL_BITS*PIXEL_BITS*2) */ + /* */ + coverage = (int)(area >> (PIXEL_BITS * 2 + 1 - 8)); + /* use range 0..256 */ + if (coverage < 0) coverage = -coverage; + + if (ras.outline.flags & SW_FT_OUTLINE_EVEN_ODD_FILL) { + coverage &= 511; + + if (coverage > 256) + coverage = 512 - coverage; + else if (coverage == 256) + coverage = 255; + } else { + /* normal non-zero winding rule */ + if (coverage >= 256) coverage = 255; + } + + y += (TCoord)ras.min_ey; + x += (TCoord)ras.min_ex; + + /* SW_FT_Span.x is a 16-bit short, so limit our coordinates appropriately */ + if (x >= 32767) x = 32767; + + /* SW_FT_Span.y is an integer, so limit our coordinates appropriately */ + if (y >= SW_FT_INT_MAX) y = SW_FT_INT_MAX; + + if (coverage) { + SW_FT_Span* span; + int count; + + // update bounding box. + if (x < ras.bound_left) ras.bound_left = x; + if (y < ras.bound_top) ras.bound_top = y; + if (y > ras.bound_bottom) ras.bound_bottom = y; + if (x + acount > ras.bound_right) ras.bound_right = x + acount; + + /* see whether we can add this span to the current list */ + count = ras.num_gray_spans; + span = ras.gray_spans + count - 1; + if (count > 0 && span->y == y && (int)span->x + span->len == (int)x && + span->coverage == coverage) { + span->len = (unsigned short)(span->len + acount); + return; + } + + if (count >= SW_FT_MAX_GRAY_SPANS) { + if (ras.render_span && count > 0) + ras.render_span(count, ras.gray_spans, ras.render_span_data); + +#ifdef DEBUG_GRAYS + + if (1) { + int n; + + fprintf(stderr, "count = %3d ", count); + span = ras.gray_spans; + for (n = 0; n < count; n++, span++) + fprintf(stderr, "[%d , %d..%d] : %d ", span->y, span->x, + span->x + span->len - 1, span->coverage); + fprintf(stderr, "\n"); + } + +#endif /* DEBUG_GRAYS */ + + ras.num_gray_spans = 0; + + span = ras.gray_spans; + } else + span++; + + /* add a gray span to the current list */ + span->x = (short)x; + span->y = (short)y; + span->len = (unsigned short)acount; + span->coverage = (unsigned char)coverage; + + ras.num_gray_spans++; + } +} + +static void gray_sweep(RAS_ARG) +{ + int yindex; + + if (ras.num_cells == 0) return; + + ras.num_gray_spans = 0; + + for (yindex = 0; yindex < ras.ycount; yindex++) { + PCell cell = ras.ycells[yindex]; + TCoord cover = 0; + TCoord x = 0; + + for (; cell != NULL; cell = cell->next) { + TPos area; + + if (cell->x > x && cover != 0) + gray_hline(RAS_VAR_ x, yindex, cover * (ONE_PIXEL * 2), + cell->x - x); + + cover += cell->cover; + area = cover * (ONE_PIXEL * 2) - cell->area; + + if (area != 0 && cell->x >= 0) + gray_hline(RAS_VAR_ cell->x, yindex, area, 1); + + x = cell->x + 1; + } + + if (cover != 0) + gray_hline(RAS_VAR_ x, yindex, cover * (ONE_PIXEL * 2), + ras.count_ex - x); + } + + if (ras.render_span && ras.num_gray_spans > 0) + ras.render_span(ras.num_gray_spans, ras.gray_spans, + ras.render_span_data); +} + +/*************************************************************************/ +/* */ +/* The following function should only compile in stand-alone mode, */ +/* i.e., when building this component without the rest of FreeType. */ +/* */ +/*************************************************************************/ + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Outline_Decompose */ +/* */ +/* */ +/* Walk over an outline's structure to decompose it into individual */ +/* segments and Bézier arcs. This function is also able to emit */ +/* `move to' and `close to' operations to indicate the start and end */ +/* of new contours in the outline. */ +/* */ +/* */ +/* outline :: A pointer to the source target. */ +/* */ +/* func_interface :: A table of `emitters', i.e., function pointers */ +/* called during decomposition to indicate path */ +/* operations. */ +/* */ +/* */ +/* user :: A typeless pointer which is passed to each */ +/* emitter during the decomposition. It can be */ +/* used to store the state during the */ +/* decomposition. */ +/* */ +/* */ +/* Error code. 0 means success. */ +/* */ +static int SW_FT_Outline_Decompose(const SW_FT_Outline* outline, + const SW_FT_Outline_Funcs* func_interface, + void* user) +{ +#undef SCALED +#define SCALED(x) (((x) << shift) - delta) + + SW_FT_Vector v_last; + SW_FT_Vector v_control; + SW_FT_Vector v_start; + + SW_FT_Vector* point; + SW_FT_Vector* limit; + char* tags; + + int error; + + int n; /* index of contour in outline */ + int first; /* index of first point in contour */ + char tag; /* current point's state */ + + int shift; + TPos delta; + + if (!outline || !func_interface) return SW_FT_THROW(Invalid_Argument); + + shift = func_interface->shift; + delta = func_interface->delta; + first = 0; + + for (n = 0; n < outline->n_contours; n++) { + int last; /* index of last point in contour */ + + last = outline->contours[n]; + if (last < 0) goto Invalid_Outline; + limit = outline->points + last; + + v_start = outline->points[first]; + v_start.x = SCALED(v_start.x); + v_start.y = SCALED(v_start.y); + + v_last = outline->points[last]; + v_last.x = SCALED(v_last.x); + v_last.y = SCALED(v_last.y); + + v_control = v_start; + + point = outline->points + first; + tags = outline->tags + first; + tag = SW_FT_CURVE_TAG(tags[0]); + + /* A contour cannot start with a cubic control point! */ + if (tag == SW_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; + + /* check first point to determine origin */ + if (tag == SW_FT_CURVE_TAG_CONIC) { + /* first point is conic control. Yes, this happens. */ + if (SW_FT_CURVE_TAG(outline->tags[last]) == SW_FT_CURVE_TAG_ON) { + /* start at last point if it is on the curve */ + v_start = v_last; + limit--; + } else { + /* if both first and last points are conic, */ + /* start at their middle and record its position */ + /* for closure */ + v_start.x = (v_start.x + v_last.x) / 2; + v_start.y = (v_start.y + v_last.y) / 2; + } + point--; + tags--; + } + + error = func_interface->move_to(&v_start, user); + if (error) goto Exit; + + while (point < limit) { + point++; + tags++; + + tag = SW_FT_CURVE_TAG(tags[0]); + switch (tag) { + case SW_FT_CURVE_TAG_ON: /* emit a single line_to */ + { + SW_FT_Vector vec; + + vec.x = SCALED(point->x); + vec.y = SCALED(point->y); + + error = func_interface->line_to(&vec, user); + if (error) goto Exit; + continue; + } + + case SW_FT_CURVE_TAG_CONIC: /* consume conic arcs */ + v_control.x = SCALED(point->x); + v_control.y = SCALED(point->y); + + Do_Conic: + if (point < limit) { + SW_FT_Vector vec; + SW_FT_Vector v_middle; + + point++; + tags++; + tag = SW_FT_CURVE_TAG(tags[0]); + + vec.x = SCALED(point->x); + vec.y = SCALED(point->y); + + if (tag == SW_FT_CURVE_TAG_ON) { + error = + func_interface->conic_to(&v_control, &vec, user); + if (error) goto Exit; + continue; + } + + if (tag != SW_FT_CURVE_TAG_CONIC) goto Invalid_Outline; + + v_middle.x = (v_control.x + vec.x) / 2; + v_middle.y = (v_control.y + vec.y) / 2; + + error = + func_interface->conic_to(&v_control, &v_middle, user); + if (error) goto Exit; + + v_control = vec; + goto Do_Conic; + } + + error = func_interface->conic_to(&v_control, &v_start, user); + goto Close; + + default: /* SW_FT_CURVE_TAG_CUBIC */ + { + SW_FT_Vector vec1, vec2; + + if (point + 1 > limit || + SW_FT_CURVE_TAG(tags[1]) != SW_FT_CURVE_TAG_CUBIC) + goto Invalid_Outline; + + point += 2; + tags += 2; + + vec1.x = SCALED(point[-2].x); + vec1.y = SCALED(point[-2].y); + + vec2.x = SCALED(point[-1].x); + vec2.y = SCALED(point[-1].y); + + if (point <= limit) { + SW_FT_Vector vec; + + vec.x = SCALED(point->x); + vec.y = SCALED(point->y); + + error = func_interface->cubic_to(&vec1, &vec2, &vec, user); + if (error) goto Exit; + continue; + } + + error = func_interface->cubic_to(&vec1, &vec2, &v_start, user); + goto Close; + } + } + } + + /* close the contour with a line segment */ + error = func_interface->line_to(&v_start, user); + + Close: + if (error) goto Exit; + + first = last + 1; + } + + return 0; + +Exit: + return error; + +Invalid_Outline: + return SW_FT_THROW(Invalid_Outline); +} + +typedef struct gray_TBand_ { + TPos min, max; + +} gray_TBand; + +SW_FT_DEFINE_OUTLINE_FUNCS(func_interface, + (SW_FT_Outline_MoveTo_Func)gray_move_to, + (SW_FT_Outline_LineTo_Func)gray_line_to, + (SW_FT_Outline_ConicTo_Func)gray_conic_to, + (SW_FT_Outline_CubicTo_Func)gray_cubic_to, 0, 0) + +static int gray_convert_glyph_inner(RAS_ARG) +{ + volatile int error = 0; + + if (ft_setjmp(ras.jump_buffer) == 0) { + error = SW_FT_Outline_Decompose(&ras.outline, &func_interface, &ras); + if (!ras.invalid) gray_record_cell(RAS_VAR); + } else + error = SW_FT_THROW(Memory_Overflow); + + return error; +} + +static int gray_convert_glyph(RAS_ARG) +{ + gray_TBand bands[40]; + gray_TBand* volatile band; + int volatile n, num_bands; + TPos volatile min, max, max_y; + SW_FT_BBox* clip; + + /* Set up state in the raster object */ + gray_compute_cbox(RAS_VAR); + + /* clip to target bitmap, exit if nothing to do */ + clip = &ras.clip_box; + + if (ras.max_ex <= clip->xMin || ras.min_ex >= clip->xMax || + ras.max_ey <= clip->yMin || ras.min_ey >= clip->yMax) + return 0; + + if (ras.min_ex < clip->xMin) ras.min_ex = clip->xMin; + if (ras.min_ey < clip->yMin) ras.min_ey = clip->yMin; + + if (ras.max_ex > clip->xMax) ras.max_ex = clip->xMax; + if (ras.max_ey > clip->yMax) ras.max_ey = clip->yMax; + + ras.count_ex = ras.max_ex - ras.min_ex; + ras.count_ey = ras.max_ey - ras.min_ey; + + /* set up vertical bands */ + num_bands = (int)((ras.max_ey - ras.min_ey) / ras.band_size); + if (num_bands == 0) num_bands = 1; + if (num_bands >= 39) num_bands = 39; + + ras.band_shoot = 0; + + min = ras.min_ey; + max_y = ras.max_ey; + + for (n = 0; n < num_bands; n++, min = max) { + max = min + ras.band_size; + if (n == num_bands - 1 || max > max_y) max = max_y; + + bands[0].min = min; + bands[0].max = max; + band = bands; + + while (band >= bands) { + TPos bottom, top, middle; + int error; + + { + PCell cells_max; + int yindex; + long cell_start, cell_end, cell_mod; + + ras.ycells = (PCell*)ras.buffer; + ras.ycount = band->max - band->min; + + cell_start = sizeof(PCell) * ras.ycount; + cell_mod = cell_start % sizeof(TCell); + if (cell_mod > 0) cell_start += sizeof(TCell) - cell_mod; + + cell_end = ras.buffer_size; + cell_end -= cell_end % sizeof(TCell); + + cells_max = (PCell)((char*)ras.buffer + cell_end); + ras.cells = (PCell)((char*)ras.buffer + cell_start); + if (ras.cells >= cells_max) goto ReduceBands; + + ras.max_cells = cells_max - ras.cells; + if (ras.max_cells < 2) goto ReduceBands; + + for (yindex = 0; yindex < ras.ycount; yindex++) + ras.ycells[yindex] = NULL; + } + + ras.num_cells = 0; + ras.invalid = 1; + ras.min_ey = band->min; + ras.max_ey = band->max; + ras.count_ey = band->max - band->min; + + error = gray_convert_glyph_inner(RAS_VAR); + + if (!error) { + gray_sweep(RAS_VAR); + band--; + continue; + } else if (error != ErrRaster_Memory_Overflow) + return 1; + + ReduceBands: + /* render pool overflow; we will reduce the render band by half */ + bottom = band->min; + top = band->max; + middle = bottom + ((top - bottom) >> 1); + + /* This is too complex for a single scanline; there must */ + /* be some problems. */ + if (middle == bottom) { + return 1; + } + + if (bottom - top >= ras.band_size) ras.band_shoot++; + + band[1].min = bottom; + band[1].max = middle; + band[0].min = middle; + band[0].max = top; + band++; + } + } + + if (ras.band_shoot > 8 && ras.band_size > 16) + ras.band_size = ras.band_size / 2; + + return 0; +} + +static int gray_raster_render(gray_PRaster raster, + const SW_FT_Raster_Params* params) +{ + SW_FT_UNUSED(raster); + const SW_FT_Outline* outline = (const SW_FT_Outline*)params->source; + + gray_TWorker worker[1]; + + TCell buffer[SW_FT_RENDER_POOL_SIZE / sizeof(TCell)]; + long buffer_size = sizeof(buffer); + int band_size = (int)(buffer_size / (long)(sizeof(TCell) * 8)); + + if (!outline) return SW_FT_THROW(Invalid_Outline); + + /* return immediately if the outline is empty */ + if (outline->n_points == 0 || outline->n_contours <= 0) return 0; + + if (!outline->contours || !outline->points) + return SW_FT_THROW(Invalid_Outline); + + if (outline->n_points != outline->contours[outline->n_contours - 1] + 1) + return SW_FT_THROW(Invalid_Outline); + + /* this version does not support monochrome rendering */ + if (!(params->flags & SW_FT_RASTER_FLAG_AA)) + return SW_FT_THROW(Invalid_Mode); + + if (params->flags & SW_FT_RASTER_FLAG_CLIP) + ras.clip_box = params->clip_box; + else { + ras.clip_box.xMin = -32768L; + ras.clip_box.yMin = -32768L; + ras.clip_box.xMax = 32767L; + ras.clip_box.yMax = 32767L; + } + + gray_init_cells(RAS_VAR_ buffer, buffer_size); + + ras.outline = *outline; + ras.num_cells = 0; + ras.invalid = 1; + ras.band_size = band_size; + ras.num_gray_spans = 0; + + ras.render_span = (SW_FT_Raster_Span_Func)params->gray_spans; + ras.render_span_data = params->user; + + gray_convert_glyph(RAS_VAR); + params->bbox_cb(ras.bound_left, ras.bound_top, + ras.bound_right - ras.bound_left, + ras.bound_bottom - ras.bound_top + 1, params->user); + return 1; +} + +/**** RASTER OBJECT CREATION: In stand-alone mode, we simply use *****/ +/**** a static object. *****/ + +static int gray_raster_new(SW_FT_Raster* araster) +{ + static gray_TRaster the_raster; + + *araster = (SW_FT_Raster)&the_raster; + SW_FT_MEM_ZERO(&the_raster, sizeof(the_raster)); + + return 0; +} + +static void gray_raster_done(SW_FT_Raster raster) +{ + /* nothing */ + SW_FT_UNUSED(raster); +} + +static void gray_raster_reset(SW_FT_Raster raster, char* pool_base, + long pool_size) +{ + SW_FT_UNUSED(raster); + SW_FT_UNUSED(pool_base); + SW_FT_UNUSED(pool_size); +} + +SW_FT_DEFINE_RASTER_FUNCS(sw_ft_grays_raster, + + (SW_FT_Raster_New_Func)gray_raster_new, + (SW_FT_Raster_Reset_Func)gray_raster_reset, + (SW_FT_Raster_Render_Func)gray_raster_render, + (SW_FT_Raster_Done_Func)gray_raster_done) + +/* END */ diff --git a/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_raster.h b/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_raster.h new file mode 100755 index 000000000..bab40efd9 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_raster.h @@ -0,0 +1,613 @@ +#ifndef V_FT_IMG_H +#define V_FT_IMG_H +/***************************************************************************/ +/* */ +/* ftimage.h */ +/* */ +/* FreeType glyph image formats and default raster interface */ +/* (specification). */ +/* */ +/* Copyright 1996-2010, 2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + + /*************************************************************************/ + /* */ + /* Note: A `raster' is simply a scan-line converter, used to render */ + /* SW_FT_Outlines into SW_FT_Bitmaps. */ + /* */ + /*************************************************************************/ + +#include "v_ft_types.h" + + /*************************************************************************/ + /* */ + /* */ + /* FT_BBox */ + /* */ + /* */ + /* A structure used to hold an outline's bounding box, i.e., the */ + /* coordinates of its extrema in the horizontal and vertical */ + /* directions. */ + /* */ + /* */ + /* xMin :: The horizontal minimum (left-most). */ + /* */ + /* yMin :: The vertical minimum (bottom-most). */ + /* */ + /* xMax :: The horizontal maximum (right-most). */ + /* */ + /* yMax :: The vertical maximum (top-most). */ + /* */ + /* */ + /* The bounding box is specified with the coordinates of the lower */ + /* left and the upper right corner. In PostScript, those values are */ + /* often called (llx,lly) and (urx,ury), respectively. */ + /* */ + /* If `yMin' is negative, this value gives the glyph's descender. */ + /* Otherwise, the glyph doesn't descend below the baseline. */ + /* Similarly, if `ymax' is positive, this value gives the glyph's */ + /* ascender. */ + /* */ + /* `xMin' gives the horizontal distance from the glyph's origin to */ + /* the left edge of the glyph's bounding box. If `xMin' is negative, */ + /* the glyph extends to the left of the origin. */ + /* */ + typedef struct SW_FT_BBox_ + { + SW_FT_Pos xMin, yMin; + SW_FT_Pos xMax, yMax; + + } SW_FT_BBox; + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Outline */ +/* */ +/* */ +/* This structure is used to describe an outline to the scan-line */ +/* converter. */ +/* */ +/* */ +/* n_contours :: The number of contours in the outline. */ +/* */ +/* n_points :: The number of points in the outline. */ +/* */ +/* points :: A pointer to an array of `n_points' @SW_FT_Vector */ +/* elements, giving the outline's point coordinates. */ +/* */ +/* tags :: A pointer to an array of `n_points' chars, giving */ +/* each outline point's type. */ +/* */ +/* If bit~0 is unset, the point is `off' the curve, */ +/* i.e., a Bézier control point, while it is `on' if */ +/* set. */ +/* */ +/* Bit~1 is meaningful for `off' points only. If set, */ +/* it indicates a third-order Bézier arc control point; */ +/* and a second-order control point if unset. */ +/* */ +/* If bit~2 is set, bits 5-7 contain the drop-out mode */ +/* (as defined in the OpenType specification; the value */ +/* is the same as the argument to the SCANMODE */ +/* instruction). */ +/* */ +/* Bits 3 and~4 are reserved for internal purposes. */ +/* */ +/* contours :: An array of `n_contours' shorts, giving the end */ +/* point of each contour within the outline. For */ +/* example, the first contour is defined by the points */ +/* `0' to `contours[0]', the second one is defined by */ +/* the points `contours[0]+1' to `contours[1]', etc. */ +/* */ +/* flags :: A set of bit flags used to characterize the outline */ +/* and give hints to the scan-converter and hinter on */ +/* how to convert/grid-fit it. See @SW_FT_OUTLINE_FLAGS.*/ +/* */ +typedef struct SW_FT_Outline_ +{ + short n_contours; /* number of contours in glyph */ + short n_points; /* number of points in the glyph */ + + SW_FT_Vector* points; /* the outline's points */ + char* tags; /* the points flags */ + short* contours; /* the contour end points */ + char* contours_flag; /* the contour open flags */ + + int flags; /* outline masks */ + +} SW_FT_Outline; + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_OUTLINE_FLAGS */ + /* */ + /* */ + /* A list of bit-field constants use for the flags in an outline's */ + /* `flags' field. */ + /* */ + /* */ + /* SW_FT_OUTLINE_NONE :: */ + /* Value~0 is reserved. */ + /* */ + /* SW_FT_OUTLINE_OWNER :: */ + /* If set, this flag indicates that the outline's field arrays */ + /* (i.e., `points', `flags', and `contours') are `owned' by the */ + /* outline object, and should thus be freed when it is destroyed. */ + /* */ + /* SW_FT_OUTLINE_EVEN_ODD_FILL :: */ + /* By default, outlines are filled using the non-zero winding rule. */ + /* If set to 1, the outline will be filled using the even-odd fill */ + /* rule (only works with the smooth rasterizer). */ + /* */ + /* SW_FT_OUTLINE_REVERSE_FILL :: */ + /* By default, outside contours of an outline are oriented in */ + /* clock-wise direction, as defined in the TrueType specification. */ + /* This flag is set if the outline uses the opposite direction */ + /* (typically for Type~1 fonts). This flag is ignored by the scan */ + /* converter. */ + /* */ + /* */ + /* */ + /* There exists a second mechanism to pass the drop-out mode to the */ + /* B/W rasterizer; see the `tags' field in @SW_FT_Outline. */ + /* */ + /* Please refer to the description of the `SCANTYPE' instruction in */ + /* the OpenType specification (in file `ttinst1.doc') how simple */ + /* drop-outs, smart drop-outs, and stubs are defined. */ + /* */ +#define SW_FT_OUTLINE_NONE 0x0 +#define SW_FT_OUTLINE_OWNER 0x1 +#define SW_FT_OUTLINE_EVEN_ODD_FILL 0x2 +#define SW_FT_OUTLINE_REVERSE_FILL 0x4 + + /* */ + +#define SW_FT_CURVE_TAG( flag ) ( flag & 3 ) + +#define SW_FT_CURVE_TAG_ON 1 +#define SW_FT_CURVE_TAG_CONIC 0 +#define SW_FT_CURVE_TAG_CUBIC 2 + + +#define SW_FT_Curve_Tag_On SW_FT_CURVE_TAG_ON +#define SW_FT_Curve_Tag_Conic SW_FT_CURVE_TAG_CONIC +#define SW_FT_Curve_Tag_Cubic SW_FT_CURVE_TAG_CUBIC + + /*************************************************************************/ + /* */ + /* A raster is a scan converter, in charge of rendering an outline into */ + /* a a bitmap. This section contains the public API for rasters. */ + /* */ + /* Note that in FreeType 2, all rasters are now encapsulated within */ + /* specific modules called `renderers'. See `ftrender.h' for more */ + /* details on renderers. */ + /* */ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster */ + /* */ + /* */ + /* A handle (pointer) to a raster object. Each object can be used */ + /* independently to convert an outline into a bitmap or pixmap. */ + /* */ + typedef struct SW_FT_RasterRec_* SW_FT_Raster; + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Span */ + /* */ + /* */ + /* A structure used to model a single span of gray (or black) pixels */ + /* when rendering a monochrome or anti-aliased bitmap. */ + /* */ + /* */ + /* x :: The span's horizontal start position. */ + /* */ + /* len :: The span's length in pixels. */ + /* */ + /* coverage :: The span color/coverage, ranging from 0 (background) */ + /* to 255 (foreground). Only used for anti-aliased */ + /* rendering. */ + /* */ + /* */ + /* This structure is used by the span drawing callback type named */ + /* @SW_FT_SpanFunc that takes the y~coordinate of the span as a */ + /* parameter. */ + /* */ + /* The coverage value is always between 0 and 255. If you want less */ + /* gray values, the callback function has to reduce them. */ + /* */ + typedef struct SW_FT_Span_ + { + short x; + short y; + unsigned short len; + unsigned char coverage; + + } SW_FT_Span; + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_SpanFunc */ + /* */ + /* */ + /* A function used as a call-back by the anti-aliased renderer in */ + /* order to let client applications draw themselves the gray pixel */ + /* spans on each scan line. */ + /* */ + /* */ + /* y :: The scanline's y~coordinate. */ + /* */ + /* count :: The number of spans to draw on this scanline. */ + /* */ + /* spans :: A table of `count' spans to draw on the scanline. */ + /* */ + /* user :: User-supplied data that is passed to the callback. */ + /* */ + /* */ + /* This callback allows client applications to directly render the */ + /* gray spans of the anti-aliased bitmap to any kind of surfaces. */ + /* */ + /* This can be used to write anti-aliased outlines directly to a */ + /* given background bitmap, and even perform translucency. */ + /* */ + /* Note that the `count' field cannot be greater than a fixed value */ + /* defined by the `SW_FT_MAX_GRAY_SPANS' configuration macro in */ + /* `ftoption.h'. By default, this value is set to~32, which means */ + /* that if there are more than 32~spans on a given scanline, the */ + /* callback is called several times with the same `y' parameter in */ + /* order to draw all callbacks. */ + /* */ + /* Otherwise, the callback is only called once per scan-line, and */ + /* only for those scanlines that do have `gray' pixels on them. */ + /* */ + typedef void + (*SW_FT_SpanFunc)( int count, + const SW_FT_Span* spans, + void* user ); + + typedef void + (*SW_FT_BboxFunc)( int x, int y, int w, int h, + void* user); + +#define SW_FT_Raster_Span_Func SW_FT_SpanFunc + + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_RASTER_FLAG_XXX */ + /* */ + /* */ + /* A list of bit flag constants as used in the `flags' field of a */ + /* @SW_FT_Raster_Params structure. */ + /* */ + /* */ + /* SW_FT_RASTER_FLAG_DEFAULT :: This value is 0. */ + /* */ + /* SW_FT_RASTER_FLAG_AA :: This flag is set to indicate that an */ + /* anti-aliased glyph image should be */ + /* generated. Otherwise, it will be */ + /* monochrome (1-bit). */ + /* */ + /* SW_FT_RASTER_FLAG_DIRECT :: This flag is set to indicate direct */ + /* rendering. In this mode, client */ + /* applications must provide their own span */ + /* callback. This lets them directly */ + /* draw or compose over an existing bitmap. */ + /* If this bit is not set, the target */ + /* pixmap's buffer _must_ be zeroed before */ + /* rendering. */ + /* */ + /* Note that for now, direct rendering is */ + /* only possible with anti-aliased glyphs. */ + /* */ + /* SW_FT_RASTER_FLAG_CLIP :: This flag is only used in direct */ + /* rendering mode. If set, the output will */ + /* be clipped to a box specified in the */ + /* `clip_box' field of the */ + /* @SW_FT_Raster_Params structure. */ + /* */ + /* Note that by default, the glyph bitmap */ + /* is clipped to the target pixmap, except */ + /* in direct rendering mode where all spans */ + /* are generated if no clipping box is set. */ + /* */ +#define SW_FT_RASTER_FLAG_DEFAULT 0x0 +#define SW_FT_RASTER_FLAG_AA 0x1 +#define SW_FT_RASTER_FLAG_DIRECT 0x2 +#define SW_FT_RASTER_FLAG_CLIP 0x4 + + /* deprecated */ +#define ft_raster_flag_default SW_FT_RASTER_FLAG_DEFAULT +#define ft_raster_flag_aa SW_FT_RASTER_FLAG_AA +#define ft_raster_flag_direct SW_FT_RASTER_FLAG_DIRECT +#define ft_raster_flag_clip SW_FT_RASTER_FLAG_CLIP + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_Params */ + /* */ + /* */ + /* A structure to hold the arguments used by a raster's render */ + /* function. */ + /* */ + /* */ + /* target :: The target bitmap. */ + /* */ + /* source :: A pointer to the source glyph image (e.g., an */ + /* @SW_FT_Outline). */ + /* */ + /* flags :: The rendering flags. */ + /* */ + /* gray_spans :: The gray span drawing callback. */ + /* */ + /* black_spans :: The black span drawing callback. UNIMPLEMENTED! */ + /* */ + /* bit_test :: The bit test callback. UNIMPLEMENTED! */ + /* */ + /* bit_set :: The bit set callback. UNIMPLEMENTED! */ + /* */ + /* user :: User-supplied data that is passed to each drawing */ + /* callback. */ + /* */ + /* clip_box :: An optional clipping box. It is only used in */ + /* direct rendering mode. Note that coordinates here */ + /* should be expressed in _integer_ pixels (and not in */ + /* 26.6 fixed-point units). */ + /* */ + /* */ + /* An anti-aliased glyph bitmap is drawn if the @SW_FT_RASTER_FLAG_AA */ + /* bit flag is set in the `flags' field, otherwise a monochrome */ + /* bitmap is generated. */ + /* */ + /* If the @SW_FT_RASTER_FLAG_DIRECT bit flag is set in `flags', the */ + /* raster will call the `gray_spans' callback to draw gray pixel */ + /* spans, in the case of an aa glyph bitmap, it will call */ + /* `black_spans', and `bit_test' and `bit_set' in the case of a */ + /* monochrome bitmap. This allows direct composition over a */ + /* pre-existing bitmap through user-provided callbacks to perform the */ + /* span drawing/composition. */ + /* */ + /* Note that the `bit_test' and `bit_set' callbacks are required when */ + /* rendering a monochrome bitmap, as they are crucial to implement */ + /* correct drop-out control as defined in the TrueType specification. */ + /* */ + typedef struct SW_FT_Raster_Params_ + { + const void* source; + int flags; + SW_FT_SpanFunc gray_spans; + SW_FT_BboxFunc bbox_cb; + void* user; + SW_FT_BBox clip_box; + + } SW_FT_Raster_Params; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Outline_Check */ +/* */ +/* */ +/* Check the contents of an outline descriptor. */ +/* */ +/* */ +/* outline :: A handle to a source outline. */ +/* */ +/* */ +/* FreeType error code. 0~means success. */ +/* */ +SW_FT_Error +SW_FT_Outline_Check( SW_FT_Outline* outline ); + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Outline_Get_CBox */ +/* */ +/* */ +/* Return an outline's `control box'. The control box encloses all */ +/* the outline's points, including Bézier control points. Though it */ +/* coincides with the exact bounding box for most glyphs, it can be */ +/* slightly larger in some situations (like when rotating an outline */ +/* that contains Bézier outside arcs). */ +/* */ +/* Computing the control box is very fast, while getting the bounding */ +/* box can take much more time as it needs to walk over all segments */ +/* and arcs in the outline. To get the latter, you can use the */ +/* `ftbbox' component, which is dedicated to this single task. */ +/* */ +/* */ +/* outline :: A pointer to the source outline descriptor. */ +/* */ +/* */ +/* acbox :: The outline's control box. */ +/* */ +/* */ +/* See @SW_FT_Glyph_Get_CBox for a discussion of tricky fonts. */ +/* */ +void +SW_FT_Outline_Get_CBox( const SW_FT_Outline* outline, + SW_FT_BBox *acbox ); + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_NewFunc */ + /* */ + /* */ + /* A function used to create a new raster object. */ + /* */ + /* */ + /* memory :: A handle to the memory allocator. */ + /* */ + /* */ + /* raster :: A handle to the new raster object. */ + /* */ + /* */ + /* Error code. 0~means success. */ + /* */ + /* */ + /* The `memory' parameter is a typeless pointer in order to avoid */ + /* un-wanted dependencies on the rest of the FreeType code. In */ + /* practice, it is an @SW_FT_Memory object, i.e., a handle to the */ + /* standard FreeType memory allocator. However, this field can be */ + /* completely ignored by a given raster implementation. */ + /* */ + typedef int + (*SW_FT_Raster_NewFunc)( SW_FT_Raster* raster ); + +#define SW_FT_Raster_New_Func SW_FT_Raster_NewFunc + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_DoneFunc */ + /* */ + /* */ + /* A function used to destroy a given raster object. */ + /* */ + /* */ + /* raster :: A handle to the raster object. */ + /* */ + typedef void + (*SW_FT_Raster_DoneFunc)( SW_FT_Raster raster ); + +#define SW_FT_Raster_Done_Func SW_FT_Raster_DoneFunc + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_ResetFunc */ + /* */ + /* */ + /* FreeType provides an area of memory called the `render pool', */ + /* available to all registered rasters. This pool can be freely used */ + /* during a given scan-conversion but is shared by all rasters. Its */ + /* content is thus transient. */ + /* */ + /* This function is called each time the render pool changes, or just */ + /* after a new raster object is created. */ + /* */ + /* */ + /* raster :: A handle to the new raster object. */ + /* */ + /* pool_base :: The address in memory of the render pool. */ + /* */ + /* pool_size :: The size in bytes of the render pool. */ + /* */ + /* */ + /* Rasters can ignore the render pool and rely on dynamic memory */ + /* allocation if they want to (a handle to the memory allocator is */ + /* passed to the raster constructor). However, this is not */ + /* recommended for efficiency purposes. */ + /* */ + typedef void + (*SW_FT_Raster_ResetFunc)( SW_FT_Raster raster, + unsigned char* pool_base, + unsigned long pool_size ); + +#define SW_FT_Raster_Reset_Func SW_FT_Raster_ResetFunc + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_RenderFunc */ + /* */ + /* */ + /* Invoke a given raster to scan-convert a given glyph image into a */ + /* target bitmap. */ + /* */ + /* */ + /* raster :: A handle to the raster object. */ + /* */ + /* params :: A pointer to an @SW_FT_Raster_Params structure used to */ + /* store the rendering parameters. */ + /* */ + /* */ + /* Error code. 0~means success. */ + /* */ + /* */ + /* The exact format of the source image depends on the raster's glyph */ + /* format defined in its @SW_FT_Raster_Funcs structure. It can be an */ + /* @SW_FT_Outline or anything else in order to support a large array of */ + /* glyph formats. */ + /* */ + /* Note also that the render function can fail and return a */ + /* `SW_FT_Err_Unimplemented_Feature' error code if the raster used does */ + /* not support direct composition. */ + /* */ + /* XXX: For now, the standard raster doesn't support direct */ + /* composition but this should change for the final release (see */ + /* the files `demos/src/ftgrays.c' and `demos/src/ftgrays2.c' */ + /* for examples of distinct implementations that support direct */ + /* composition). */ + /* */ + typedef int + (*SW_FT_Raster_RenderFunc)( SW_FT_Raster raster, + const SW_FT_Raster_Params* params ); + +#define SW_FT_Raster_Render_Func SW_FT_Raster_RenderFunc + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_Funcs */ + /* */ + /* */ + /* A structure used to describe a given raster class to the library. */ + /* */ + /* */ + /* glyph_format :: The supported glyph format for this raster. */ + /* */ + /* raster_new :: The raster constructor. */ + /* */ + /* raster_reset :: Used to reset the render pool within the raster. */ + /* */ + /* raster_render :: A function to render a glyph into a given bitmap. */ + /* */ + /* raster_done :: The raster destructor. */ + /* */ + typedef struct SW_FT_Raster_Funcs_ + { + SW_FT_Raster_NewFunc raster_new; + SW_FT_Raster_ResetFunc raster_reset; + SW_FT_Raster_RenderFunc raster_render; + SW_FT_Raster_DoneFunc raster_done; + + } SW_FT_Raster_Funcs; + + +extern const SW_FT_Raster_Funcs sw_ft_grays_raster; + +#endif // V_FT_IMG_H diff --git a/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_stroker.cpp b/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_stroker.cpp new file mode 100755 index 000000000..2a857079b --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_stroker.cpp @@ -0,0 +1,1917 @@ + +/***************************************************************************/ +/* */ +/* ftstroke.c */ +/* */ +/* FreeType path stroker (body). */ +/* */ +/* Copyright 2002-2006, 2008-2011, 2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "v_ft_stroker.h" +#include +#include +#include +#include "v_ft_math.h" + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** BEZIER COMPUTATIONS *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +#define SW_FT_SMALL_CONIC_THRESHOLD (SW_FT_ANGLE_PI / 6) +#define SW_FT_SMALL_CUBIC_THRESHOLD (SW_FT_ANGLE_PI / 8) + +#define SW_FT_EPSILON 2 + +#define SW_FT_IS_SMALL(x) ((x) > -SW_FT_EPSILON && (x) < SW_FT_EPSILON) + +static SW_FT_Pos ft_pos_abs(SW_FT_Pos x) +{ + return x >= 0 ? x : -x; +} + +static void ft_conic_split(SW_FT_Vector* base) +{ + SW_FT_Pos a, b; + + base[4].x = base[2].x; + b = base[1].x; + a = base[3].x = (base[2].x + b) / 2; + b = base[1].x = (base[0].x + b) / 2; + base[2].x = (a + b) / 2; + + base[4].y = base[2].y; + b = base[1].y; + a = base[3].y = (base[2].y + b) / 2; + b = base[1].y = (base[0].y + b) / 2; + base[2].y = (a + b) / 2; +} + +static SW_FT_Bool ft_conic_is_small_enough(SW_FT_Vector* base, + SW_FT_Angle* angle_in, + SW_FT_Angle* angle_out) +{ + SW_FT_Vector d1, d2; + SW_FT_Angle theta; + SW_FT_Int close1, close2; + + d1.x = base[1].x - base[2].x; + d1.y = base[1].y - base[2].y; + d2.x = base[0].x - base[1].x; + d2.y = base[0].y - base[1].y; + + close1 = SW_FT_IS_SMALL(d1.x) && SW_FT_IS_SMALL(d1.y); + close2 = SW_FT_IS_SMALL(d2.x) && SW_FT_IS_SMALL(d2.y); + + if (close1) { + if (close2) { + /* basically a point; */ + /* do nothing to retain original direction */ + } else { + *angle_in = *angle_out = SW_FT_Atan2(d2.x, d2.y); + } + } else /* !close1 */ + { + if (close2) { + *angle_in = *angle_out = SW_FT_Atan2(d1.x, d1.y); + } else { + *angle_in = SW_FT_Atan2(d1.x, d1.y); + *angle_out = SW_FT_Atan2(d2.x, d2.y); + } + } + + theta = ft_pos_abs(SW_FT_Angle_Diff(*angle_in, *angle_out)); + + return SW_FT_BOOL(theta < SW_FT_SMALL_CONIC_THRESHOLD); +} + +static void ft_cubic_split(SW_FT_Vector* base) +{ + SW_FT_Pos a, b, c, d; + + base[6].x = base[3].x; + c = base[1].x; + d = base[2].x; + base[1].x = a = (base[0].x + c) / 2; + base[5].x = b = (base[3].x + d) / 2; + c = (c + d) / 2; + base[2].x = a = (a + c) / 2; + base[4].x = b = (b + c) / 2; + base[3].x = (a + b) / 2; + + base[6].y = base[3].y; + c = base[1].y; + d = base[2].y; + base[1].y = a = (base[0].y + c) / 2; + base[5].y = b = (base[3].y + d) / 2; + c = (c + d) / 2; + base[2].y = a = (a + c) / 2; + base[4].y = b = (b + c) / 2; + base[3].y = (a + b) / 2; +} + +/* Return the average of `angle1' and `angle2'. */ +/* This gives correct result even if `angle1' and `angle2' */ +/* have opposite signs. */ +static SW_FT_Angle ft_angle_mean(SW_FT_Angle angle1, SW_FT_Angle angle2) +{ + return angle1 + SW_FT_Angle_Diff(angle1, angle2) / 2; +} + +static SW_FT_Bool ft_cubic_is_small_enough(SW_FT_Vector* base, + SW_FT_Angle* angle_in, + SW_FT_Angle* angle_mid, + SW_FT_Angle* angle_out) +{ + SW_FT_Vector d1, d2, d3; + SW_FT_Angle theta1, theta2; + SW_FT_Int close1, close2, close3; + + d1.x = base[2].x - base[3].x; + d1.y = base[2].y - base[3].y; + d2.x = base[1].x - base[2].x; + d2.y = base[1].y - base[2].y; + d3.x = base[0].x - base[1].x; + d3.y = base[0].y - base[1].y; + + close1 = SW_FT_IS_SMALL(d1.x) && SW_FT_IS_SMALL(d1.y); + close2 = SW_FT_IS_SMALL(d2.x) && SW_FT_IS_SMALL(d2.y); + close3 = SW_FT_IS_SMALL(d3.x) && SW_FT_IS_SMALL(d3.y); + + if (close1) { + if (close2) { + if (close3) { + /* basically a point; */ + /* do nothing to retain original direction */ + } else /* !close3 */ + { + *angle_in = *angle_mid = *angle_out = SW_FT_Atan2(d3.x, d3.y); + } + } else /* !close2 */ + { + if (close3) { + *angle_in = *angle_mid = *angle_out = SW_FT_Atan2(d2.x, d2.y); + } else /* !close3 */ + { + *angle_in = *angle_mid = SW_FT_Atan2(d2.x, d2.y); + *angle_out = SW_FT_Atan2(d3.x, d3.y); + } + } + } else /* !close1 */ + { + if (close2) { + if (close3) { + *angle_in = *angle_mid = *angle_out = SW_FT_Atan2(d1.x, d1.y); + } else /* !close3 */ + { + *angle_in = SW_FT_Atan2(d1.x, d1.y); + *angle_out = SW_FT_Atan2(d3.x, d3.y); + *angle_mid = ft_angle_mean(*angle_in, *angle_out); + } + } else /* !close2 */ + { + if (close3) { + *angle_in = SW_FT_Atan2(d1.x, d1.y); + *angle_mid = *angle_out = SW_FT_Atan2(d2.x, d2.y); + } else /* !close3 */ + { + *angle_in = SW_FT_Atan2(d1.x, d1.y); + *angle_mid = SW_FT_Atan2(d2.x, d2.y); + *angle_out = SW_FT_Atan2(d3.x, d3.y); + } + } + } + + theta1 = ft_pos_abs(SW_FT_Angle_Diff(*angle_in, *angle_mid)); + theta2 = ft_pos_abs(SW_FT_Angle_Diff(*angle_mid, *angle_out)); + + return SW_FT_BOOL(theta1 < SW_FT_SMALL_CUBIC_THRESHOLD && + theta2 < SW_FT_SMALL_CUBIC_THRESHOLD); +} + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** STROKE BORDERS *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +typedef enum SW_FT_StrokeTags_ { + SW_FT_STROKE_TAG_ON = 1, /* on-curve point */ + SW_FT_STROKE_TAG_CUBIC = 2, /* cubic off-point */ + SW_FT_STROKE_TAG_BEGIN = 4, /* sub-path start */ + SW_FT_STROKE_TAG_END = 8 /* sub-path end */ + +} SW_FT_StrokeTags; + +#define SW_FT_STROKE_TAG_BEGIN_END \ + (SW_FT_STROKE_TAG_BEGIN | SW_FT_STROKE_TAG_END) + +typedef struct SW_FT_StrokeBorderRec_ { + SW_FT_UInt num_points; + SW_FT_UInt max_points; + SW_FT_Vector* points; + SW_FT_Byte* tags; + SW_FT_Bool movable; /* TRUE for ends of lineto borders */ + SW_FT_Int start; /* index of current sub-path start point */ + SW_FT_Bool valid; + +} SW_FT_StrokeBorderRec, *SW_FT_StrokeBorder; + +SW_FT_Error SW_FT_Outline_Check(SW_FT_Outline* outline) +{ + if (outline) { + SW_FT_Int n_points = outline->n_points; + SW_FT_Int n_contours = outline->n_contours; + SW_FT_Int end0, end; + SW_FT_Int n; + + /* empty glyph? */ + if (n_points == 0 && n_contours == 0) return 0; + + /* check point and contour counts */ + if (n_points <= 0 || n_contours <= 0) goto Bad; + + end0 = end = -1; + for (n = 0; n < n_contours; n++) { + end = outline->contours[n]; + + /* note that we don't accept empty contours */ + if (end <= end0 || end >= n_points) goto Bad; + + end0 = end; + } + + if (end != n_points - 1) goto Bad; + + /* XXX: check the tags array */ + return 0; + } + +Bad: + return -1; // SW_FT_THROW( Invalid_Argument ); +} + +void SW_FT_Outline_Get_CBox(const SW_FT_Outline* outline, SW_FT_BBox* acbox) +{ + SW_FT_Pos xMin, yMin, xMax, yMax; + + if (outline && acbox) { + if (outline->n_points == 0) { + xMin = 0; + yMin = 0; + xMax = 0; + yMax = 0; + } else { + SW_FT_Vector* vec = outline->points; + SW_FT_Vector* limit = vec + outline->n_points; + + xMin = xMax = vec->x; + yMin = yMax = vec->y; + vec++; + + for (; vec < limit; vec++) { + SW_FT_Pos x, y; + + x = vec->x; + if (x < xMin) xMin = x; + if (x > xMax) xMax = x; + + y = vec->y; + if (y < yMin) yMin = y; + if (y > yMax) yMax = y; + } + } + acbox->xMin = xMin; + acbox->xMax = xMax; + acbox->yMin = yMin; + acbox->yMax = yMax; + } +} + +static SW_FT_Error ft_stroke_border_grow(SW_FT_StrokeBorder border, + SW_FT_UInt new_points) +{ + SW_FT_UInt old_max = border->max_points; + SW_FT_UInt new_max = border->num_points + new_points; + SW_FT_Error error = 0; + + if (new_max > old_max) { + SW_FT_UInt cur_max = old_max; + + while (cur_max < new_max) cur_max += (cur_max >> 1) + 16; + + border->points = (SW_FT_Vector*)realloc(border->points, + cur_max * sizeof(SW_FT_Vector)); + border->tags = + (SW_FT_Byte*)realloc(border->tags, cur_max * sizeof(SW_FT_Byte)); + + if (!border->points || !border->tags) goto Exit; + + border->max_points = cur_max; + } + +Exit: + return error; +} + +static void ft_stroke_border_close(SW_FT_StrokeBorder border, + SW_FT_Bool reverse) +{ + SW_FT_UInt start = border->start; + SW_FT_UInt count = border->num_points; + + assert(border->start >= 0); + + /* don't record empty paths! */ + if (count <= start + 1U) + border->num_points = start; + else { + /* copy the last point to the start of this sub-path, since */ + /* it contains the `adjusted' starting coordinates */ + border->num_points = --count; + border->points[start] = border->points[count]; + + if (reverse) { + /* reverse the points */ + { + SW_FT_Vector* vec1 = border->points + start + 1; + SW_FT_Vector* vec2 = border->points + count - 1; + + for (; vec1 < vec2; vec1++, vec2--) { + SW_FT_Vector tmp; + + tmp = *vec1; + *vec1 = *vec2; + *vec2 = tmp; + } + } + + /* then the tags */ + { + SW_FT_Byte* tag1 = border->tags + start + 1; + SW_FT_Byte* tag2 = border->tags + count - 1; + + for (; tag1 < tag2; tag1++, tag2--) { + SW_FT_Byte tmp; + + tmp = *tag1; + *tag1 = *tag2; + *tag2 = tmp; + } + } + } + + border->tags[start] |= SW_FT_STROKE_TAG_BEGIN; + border->tags[count - 1] |= SW_FT_STROKE_TAG_END; + } + + border->start = -1; + border->movable = FALSE; +} + +static SW_FT_Error ft_stroke_border_lineto(SW_FT_StrokeBorder border, + SW_FT_Vector* to, SW_FT_Bool movable) +{ + SW_FT_Error error = 0; + + assert(border->start >= 0); + + if (border->movable) { + /* move last point */ + border->points[border->num_points - 1] = *to; + } else { + /* don't add zero-length lineto */ + if (border->num_points > 0 && + SW_FT_IS_SMALL(border->points[border->num_points - 1].x - to->x) && + SW_FT_IS_SMALL(border->points[border->num_points - 1].y - to->y)) + return error; + + /* add one point */ + error = ft_stroke_border_grow(border, 1); + if (!error) { + SW_FT_Vector* vec = border->points + border->num_points; + SW_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *to; + tag[0] = SW_FT_STROKE_TAG_ON; + + border->num_points += 1; + } + } + border->movable = movable; + return error; +} + +static SW_FT_Error ft_stroke_border_conicto(SW_FT_StrokeBorder border, + SW_FT_Vector* control, + SW_FT_Vector* to) +{ + SW_FT_Error error; + + assert(border->start >= 0); + + error = ft_stroke_border_grow(border, 2); + if (!error) { + SW_FT_Vector* vec = border->points + border->num_points; + SW_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *control; + vec[1] = *to; + + tag[0] = 0; + tag[1] = SW_FT_STROKE_TAG_ON; + + border->num_points += 2; + } + + border->movable = FALSE; + + return error; +} + +static SW_FT_Error ft_stroke_border_cubicto(SW_FT_StrokeBorder border, + SW_FT_Vector* control1, + SW_FT_Vector* control2, + SW_FT_Vector* to) +{ + SW_FT_Error error; + + assert(border->start >= 0); + + error = ft_stroke_border_grow(border, 3); + if (!error) { + SW_FT_Vector* vec = border->points + border->num_points; + SW_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *control1; + vec[1] = *control2; + vec[2] = *to; + + tag[0] = SW_FT_STROKE_TAG_CUBIC; + tag[1] = SW_FT_STROKE_TAG_CUBIC; + tag[2] = SW_FT_STROKE_TAG_ON; + + border->num_points += 3; + } + + border->movable = FALSE; + + return error; +} + +#define SW_FT_ARC_CUBIC_ANGLE (SW_FT_ANGLE_PI / 2) + +static SW_FT_Error ft_stroke_border_arcto(SW_FT_StrokeBorder border, + SW_FT_Vector* center, + SW_FT_Fixed radius, + SW_FT_Angle angle_start, + SW_FT_Angle angle_diff) +{ + SW_FT_Angle total, angle, step, rotate, next, theta; + SW_FT_Vector a, b, a2, b2; + SW_FT_Fixed length; + SW_FT_Error error = 0; + + /* compute start point */ + SW_FT_Vector_From_Polar(&a, radius, angle_start); + a.x += center->x; + a.y += center->y; + + total = angle_diff; + angle = angle_start; + rotate = (angle_diff >= 0) ? SW_FT_ANGLE_PI2 : -SW_FT_ANGLE_PI2; + + while (total != 0) { + step = total; + if (step > SW_FT_ARC_CUBIC_ANGLE) + step = SW_FT_ARC_CUBIC_ANGLE; + + else if (step < -SW_FT_ARC_CUBIC_ANGLE) + step = -SW_FT_ARC_CUBIC_ANGLE; + + next = angle + step; + theta = step; + if (theta < 0) theta = -theta; + + theta >>= 1; + + /* compute end point */ + SW_FT_Vector_From_Polar(&b, radius, next); + b.x += center->x; + b.y += center->y; + + /* compute first and second control points */ + length = SW_FT_MulDiv(radius, SW_FT_Sin(theta) * 4, + (0x10000L + SW_FT_Cos(theta)) * 3); + + SW_FT_Vector_From_Polar(&a2, length, angle + rotate); + a2.x += a.x; + a2.y += a.y; + + SW_FT_Vector_From_Polar(&b2, length, next - rotate); + b2.x += b.x; + b2.y += b.y; + + /* add cubic arc */ + error = ft_stroke_border_cubicto(border, &a2, &b2, &b); + if (error) break; + + /* process the rest of the arc ?? */ + a = b; + total -= step; + angle = next; + } + + return error; +} + +static SW_FT_Error ft_stroke_border_moveto(SW_FT_StrokeBorder border, + SW_FT_Vector* to) +{ + /* close current open path if any ? */ + if (border->start >= 0) ft_stroke_border_close(border, FALSE); + + border->start = border->num_points; + border->movable = FALSE; + + return ft_stroke_border_lineto(border, to, FALSE); +} + +static void ft_stroke_border_init(SW_FT_StrokeBorder border) +{ + border->points = NULL; + border->tags = NULL; + + border->num_points = 0; + border->max_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static void ft_stroke_border_reset(SW_FT_StrokeBorder border) +{ + border->num_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static void ft_stroke_border_done(SW_FT_StrokeBorder border) +{ + free(border->points); + free(border->tags); + + border->num_points = 0; + border->max_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static SW_FT_Error ft_stroke_border_get_counts(SW_FT_StrokeBorder border, + SW_FT_UInt* anum_points, + SW_FT_UInt* anum_contours) +{ + SW_FT_Error error = 0; + SW_FT_UInt num_points = 0; + SW_FT_UInt num_contours = 0; + + SW_FT_UInt count = border->num_points; + SW_FT_Vector* point = border->points; + SW_FT_Byte* tags = border->tags; + SW_FT_Int in_contour = 0; + + for (; count > 0; count--, num_points++, point++, tags++) { + if (tags[0] & SW_FT_STROKE_TAG_BEGIN) { + if (in_contour != 0) goto Fail; + + in_contour = 1; + } else if (in_contour == 0) + goto Fail; + + if (tags[0] & SW_FT_STROKE_TAG_END) { + in_contour = 0; + num_contours++; + } + } + + if (in_contour != 0) goto Fail; + + border->valid = TRUE; + +Exit: + *anum_points = num_points; + *anum_contours = num_contours; + return error; + +Fail: + num_points = 0; + num_contours = 0; + goto Exit; +} + +static void ft_stroke_border_export(SW_FT_StrokeBorder border, + SW_FT_Outline* outline) +{ + /* copy point locations */ + memcpy(outline->points + outline->n_points, border->points, + border->num_points * sizeof(SW_FT_Vector)); + + /* copy tags */ + { + SW_FT_UInt count = border->num_points; + SW_FT_Byte* read = border->tags; + SW_FT_Byte* write = (SW_FT_Byte*)outline->tags + outline->n_points; + + for (; count > 0; count--, read++, write++) { + if (*read & SW_FT_STROKE_TAG_ON) + *write = SW_FT_CURVE_TAG_ON; + else if (*read & SW_FT_STROKE_TAG_CUBIC) + *write = SW_FT_CURVE_TAG_CUBIC; + else + *write = SW_FT_CURVE_TAG_CONIC; + } + } + + /* copy contours */ + { + SW_FT_UInt count = border->num_points; + SW_FT_Byte* tags = border->tags; + SW_FT_Short* write = outline->contours + outline->n_contours; + SW_FT_Short idx = (SW_FT_Short)outline->n_points; + + for (; count > 0; count--, tags++, idx++) { + if (*tags & SW_FT_STROKE_TAG_END) { + *write++ = idx; + outline->n_contours++; + } + } + } + + outline->n_points = (short)(outline->n_points + border->num_points); + + assert(SW_FT_Outline_Check(outline) == 0); +} + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** STROKER *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +#define SW_FT_SIDE_TO_ROTATE(s) (SW_FT_ANGLE_PI2 - (s)*SW_FT_ANGLE_PI) + +typedef struct SW_FT_StrokerRec_ { + SW_FT_Angle angle_in; /* direction into curr join */ + SW_FT_Angle angle_out; /* direction out of join */ + SW_FT_Vector center; /* current position */ + SW_FT_Fixed line_length; /* length of last lineto */ + SW_FT_Bool first_point; /* is this the start? */ + SW_FT_Bool subpath_open; /* is the subpath open? */ + SW_FT_Angle subpath_angle; /* subpath start direction */ + SW_FT_Vector subpath_start; /* subpath start position */ + SW_FT_Fixed subpath_line_length; /* subpath start lineto len */ + SW_FT_Bool handle_wide_strokes; /* use wide strokes logic? */ + + SW_FT_Stroker_LineCap line_cap; + SW_FT_Stroker_LineJoin line_join; + SW_FT_Stroker_LineJoin line_join_saved; + SW_FT_Fixed miter_limit; + SW_FT_Fixed radius; + + SW_FT_StrokeBorderRec borders[2]; +} SW_FT_StrokerRec; + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_New(SW_FT_Stroker* astroker) +{ + SW_FT_Error error = 0; /* assigned in SW_FT_NEW */ + SW_FT_Stroker stroker = NULL; + + stroker = (SW_FT_StrokerRec*)calloc(1, sizeof(SW_FT_StrokerRec)); + if (stroker) { + ft_stroke_border_init(&stroker->borders[0]); + ft_stroke_border_init(&stroker->borders[1]); + } + + *astroker = stroker; + + return error; +} + +void SW_FT_Stroker_Rewind(SW_FT_Stroker stroker) +{ + if (stroker) { + ft_stroke_border_reset(&stroker->borders[0]); + ft_stroke_border_reset(&stroker->borders[1]); + } +} + +/* documentation is in ftstroke.h */ + +void SW_FT_Stroker_Set(SW_FT_Stroker stroker, SW_FT_Fixed radius, + SW_FT_Stroker_LineCap line_cap, + SW_FT_Stroker_LineJoin line_join, + SW_FT_Fixed miter_limit) +{ + stroker->radius = radius; + stroker->line_cap = line_cap; + stroker->line_join = line_join; + stroker->miter_limit = miter_limit; + + /* ensure miter limit has sensible value */ + if (stroker->miter_limit < 0x10000) stroker->miter_limit = 0x10000; + + /* save line join style: */ + /* line join style can be temporarily changed when stroking curves */ + stroker->line_join_saved = line_join; + + SW_FT_Stroker_Rewind(stroker); +} + +/* documentation is in ftstroke.h */ + +void SW_FT_Stroker_Done(SW_FT_Stroker stroker) +{ + if (stroker) { + ft_stroke_border_done(&stroker->borders[0]); + ft_stroke_border_done(&stroker->borders[1]); + + free(stroker); + } +} + +/* create a circular arc at a corner or cap */ +static SW_FT_Error ft_stroker_arcto(SW_FT_Stroker stroker, SW_FT_Int side) +{ + SW_FT_Angle total, rotate; + SW_FT_Fixed radius = stroker->radius; + SW_FT_Error error = 0; + SW_FT_StrokeBorder border = stroker->borders + side; + + rotate = SW_FT_SIDE_TO_ROTATE(side); + + total = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + if (total == SW_FT_ANGLE_PI) total = -rotate * 2; + + error = ft_stroke_border_arcto(border, &stroker->center, radius, + stroker->angle_in + rotate, total); + border->movable = FALSE; + return error; +} + +/* add a cap at the end of an opened path */ +static SW_FT_Error ft_stroker_cap(SW_FT_Stroker stroker, SW_FT_Angle angle, + SW_FT_Int side) +{ + SW_FT_Error error = 0; + + if (stroker->line_cap == SW_FT_STROKER_LINECAP_ROUND) { + /* add a round cap */ + stroker->angle_in = angle; + stroker->angle_out = angle + SW_FT_ANGLE_PI; + + error = ft_stroker_arcto(stroker, side); + } else if (stroker->line_cap == SW_FT_STROKER_LINECAP_SQUARE) { + /* add a square cap */ + SW_FT_Vector delta, delta2; + SW_FT_Angle rotate = SW_FT_SIDE_TO_ROTATE(side); + SW_FT_Fixed radius = stroker->radius; + SW_FT_StrokeBorder border = stroker->borders + side; + + SW_FT_Vector_From_Polar(&delta2, radius, angle + rotate); + SW_FT_Vector_From_Polar(&delta, radius, angle); + + delta.x += stroker->center.x + delta2.x; + delta.y += stroker->center.y + delta2.y; + + error = ft_stroke_border_lineto(border, &delta, FALSE); + if (error) goto Exit; + + SW_FT_Vector_From_Polar(&delta2, radius, angle - rotate); + SW_FT_Vector_From_Polar(&delta, radius, angle); + + delta.x += delta2.x + stroker->center.x; + delta.y += delta2.y + stroker->center.y; + + error = ft_stroke_border_lineto(border, &delta, FALSE); + } else if (stroker->line_cap == SW_FT_STROKER_LINECAP_BUTT) { + /* add a butt ending */ + SW_FT_Vector delta; + SW_FT_Angle rotate = SW_FT_SIDE_TO_ROTATE(side); + SW_FT_Fixed radius = stroker->radius; + SW_FT_StrokeBorder border = stroker->borders + side; + + SW_FT_Vector_From_Polar(&delta, radius, angle + rotate); + + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + error = ft_stroke_border_lineto(border, &delta, FALSE); + if (error) goto Exit; + + SW_FT_Vector_From_Polar(&delta, radius, angle - rotate); + + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + error = ft_stroke_border_lineto(border, &delta, FALSE); + } + +Exit: + return error; +} + +/* process an inside corner, i.e. compute intersection */ +static SW_FT_Error ft_stroker_inside(SW_FT_Stroker stroker, SW_FT_Int side, + SW_FT_Fixed line_length) +{ + SW_FT_StrokeBorder border = stroker->borders + side; + SW_FT_Angle phi, theta, rotate; + SW_FT_Fixed length, thcos; + SW_FT_Vector delta; + SW_FT_Error error = 0; + SW_FT_Bool intersect; /* use intersection of lines? */ + + rotate = SW_FT_SIDE_TO_ROTATE(side); + + theta = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out) / 2; + + /* Only intersect borders if between two lineto's and both */ + /* lines are long enough (line_length is zero for curves). */ + if (!border->movable || line_length == 0) + intersect = FALSE; + else { + /* compute minimum required length of lines */ + SW_FT_Fixed min_length = + ft_pos_abs(SW_FT_MulFix(stroker->radius, SW_FT_Tan(theta))); + + intersect = SW_FT_BOOL(stroker->line_length >= min_length && + line_length >= min_length); + } + + if (!intersect) { + SW_FT_Vector_From_Polar(&delta, stroker->radius, + stroker->angle_out + rotate); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + border->movable = FALSE; + } else { + /* compute median angle */ + phi = stroker->angle_in + theta; + + thcos = SW_FT_Cos(theta); + + length = SW_FT_DivFix(stroker->radius, thcos); + + SW_FT_Vector_From_Polar(&delta, length, phi + rotate); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + } + + error = ft_stroke_border_lineto(border, &delta, FALSE); + + return error; +} + +/* process an outside corner, i.e. compute bevel/miter/round */ +static SW_FT_Error ft_stroker_outside(SW_FT_Stroker stroker, SW_FT_Int side, + SW_FT_Fixed line_length) +{ + SW_FT_StrokeBorder border = stroker->borders + side; + SW_FT_Error error; + SW_FT_Angle rotate; + + if (stroker->line_join == SW_FT_STROKER_LINEJOIN_ROUND) + error = ft_stroker_arcto(stroker, side); + else { + /* this is a mitered (pointed) or beveled (truncated) corner */ + SW_FT_Fixed sigma = 0, radius = stroker->radius; + SW_FT_Angle theta = 0, phi = 0; + SW_FT_Fixed thcos = 0; + SW_FT_Bool bevel, fixed_bevel; + + rotate = SW_FT_SIDE_TO_ROTATE(side); + + bevel = SW_FT_BOOL(stroker->line_join == SW_FT_STROKER_LINEJOIN_BEVEL); + + fixed_bevel = SW_FT_BOOL(stroker->line_join != + SW_FT_STROKER_LINEJOIN_MITER_VARIABLE); + + if (!bevel) { + theta = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + + if (theta == SW_FT_ANGLE_PI) { + theta = rotate; + phi = stroker->angle_in; + } else { + theta /= 2; + phi = stroker->angle_in + theta + rotate; + } + + thcos = SW_FT_Cos(theta); + sigma = SW_FT_MulFix(stroker->miter_limit, thcos); + + /* is miter limit exceeded? */ + if (sigma < 0x10000L) { + /* don't create variable bevels for very small deviations; */ + /* SW_FT_Sin(x) = 0 for x <= 57 */ + if (fixed_bevel || ft_pos_abs(theta) > 57) bevel = TRUE; + } + } + + if (bevel) /* this is a bevel (broken angle) */ + { + if (fixed_bevel) { + /* the outer corners are simply joined together */ + SW_FT_Vector delta; + + /* add bevel */ + SW_FT_Vector_From_Polar(&delta, radius, + stroker->angle_out + rotate); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + border->movable = FALSE; + error = ft_stroke_border_lineto(border, &delta, FALSE); + } else /* variable bevel */ + { + /* the miter is truncated */ + SW_FT_Vector middle, delta; + SW_FT_Fixed length; + + /* compute middle point */ + SW_FT_Vector_From_Polar( + &middle, SW_FT_MulFix(radius, stroker->miter_limit), phi); + middle.x += stroker->center.x; + middle.y += stroker->center.y; + + /* compute first angle point */ + length = SW_FT_MulDiv(radius, 0x10000L - sigma, + ft_pos_abs(SW_FT_Sin(theta))); + + SW_FT_Vector_From_Polar(&delta, length, phi + rotate); + delta.x += middle.x; + delta.y += middle.y; + + error = ft_stroke_border_lineto(border, &delta, FALSE); + if (error) goto Exit; + + /* compute second angle point */ + SW_FT_Vector_From_Polar(&delta, length, phi - rotate); + delta.x += middle.x; + delta.y += middle.y; + + error = ft_stroke_border_lineto(border, &delta, FALSE); + if (error) goto Exit; + + /* finally, add an end point; only needed if not lineto */ + /* (line_length is zero for curves) */ + if (line_length == 0) { + SW_FT_Vector_From_Polar(&delta, radius, + stroker->angle_out + rotate); + + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + error = ft_stroke_border_lineto(border, &delta, FALSE); + } + } + } else /* this is a miter (intersection) */ + { + SW_FT_Fixed length; + SW_FT_Vector delta; + + length = SW_FT_DivFix(stroker->radius, thcos); + + SW_FT_Vector_From_Polar(&delta, length, phi); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + error = ft_stroke_border_lineto(border, &delta, FALSE); + if (error) goto Exit; + + /* now add an end point; only needed if not lineto */ + /* (line_length is zero for curves) */ + if (line_length == 0) { + SW_FT_Vector_From_Polar(&delta, stroker->radius, + stroker->angle_out + rotate); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + error = ft_stroke_border_lineto(border, &delta, FALSE); + } + } + } + +Exit: + return error; +} + +static SW_FT_Error ft_stroker_process_corner(SW_FT_Stroker stroker, + SW_FT_Fixed line_length) +{ + SW_FT_Error error = 0; + SW_FT_Angle turn; + SW_FT_Int inside_side; + + turn = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + + /* no specific corner processing is required if the turn is 0 */ + if (turn == 0) goto Exit; + + /* when we turn to the right, the inside side is 0 */ + inside_side = 0; + + /* otherwise, the inside side is 1 */ + if (turn < 0) inside_side = 1; + + /* process the inside side */ + error = ft_stroker_inside(stroker, inside_side, line_length); + if (error) goto Exit; + + /* process the outside side */ + error = ft_stroker_outside(stroker, 1 - inside_side, line_length); + +Exit: + return error; +} + +/* add two points to the left and right borders corresponding to the */ +/* start of the subpath */ +static SW_FT_Error ft_stroker_subpath_start(SW_FT_Stroker stroker, + SW_FT_Angle start_angle, + SW_FT_Fixed line_length) +{ + SW_FT_Vector delta; + SW_FT_Vector point; + SW_FT_Error error; + SW_FT_StrokeBorder border; + + SW_FT_Vector_From_Polar(&delta, stroker->radius, + start_angle + SW_FT_ANGLE_PI2); + + point.x = stroker->center.x + delta.x; + point.y = stroker->center.y + delta.y; + + border = stroker->borders; + error = ft_stroke_border_moveto(border, &point); + if (error) goto Exit; + + point.x = stroker->center.x - delta.x; + point.y = stroker->center.y - delta.y; + + border++; + error = ft_stroke_border_moveto(border, &point); + + /* save angle, position, and line length for last join */ + /* (line_length is zero for curves) */ + stroker->subpath_angle = start_angle; + stroker->first_point = FALSE; + stroker->subpath_line_length = line_length; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_LineTo(SW_FT_Stroker stroker, SW_FT_Vector* to) +{ + SW_FT_Error error = 0; + SW_FT_StrokeBorder border; + SW_FT_Vector delta; + SW_FT_Angle angle; + SW_FT_Int side; + SW_FT_Fixed line_length; + + delta.x = to->x - stroker->center.x; + delta.y = to->y - stroker->center.y; + + /* a zero-length lineto is a no-op; avoid creating a spurious corner */ + if (delta.x == 0 && delta.y == 0) goto Exit; + + /* compute length of line */ + line_length = SW_FT_Vector_Length(&delta); + + angle = SW_FT_Atan2(delta.x, delta.y); + SW_FT_Vector_From_Polar(&delta, stroker->radius, angle + SW_FT_ANGLE_PI2); + + /* process corner if necessary */ + if (stroker->first_point) { + /* This is the first segment of a subpath. We need to */ + /* add a point to each border at their respective starting */ + /* point locations. */ + error = ft_stroker_subpath_start(stroker, angle, line_length); + if (error) goto Exit; + } else { + /* process the current corner */ + stroker->angle_out = angle; + error = ft_stroker_process_corner(stroker, line_length); + if (error) goto Exit; + } + + /* now add a line segment to both the `inside' and `outside' paths */ + for (border = stroker->borders, side = 1; side >= 0; side--, border++) { + SW_FT_Vector point; + + point.x = to->x + delta.x; + point.y = to->y + delta.y; + + /* the ends of lineto borders are movable */ + error = ft_stroke_border_lineto(border, &point, TRUE); + if (error) goto Exit; + + delta.x = -delta.x; + delta.y = -delta.y; + } + + stroker->angle_in = angle; + stroker->center = *to; + stroker->line_length = line_length; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_ConicTo(SW_FT_Stroker stroker, SW_FT_Vector* control, + SW_FT_Vector* to) +{ + SW_FT_Error error = 0; + SW_FT_Vector bez_stack[34]; + SW_FT_Vector* arc; + SW_FT_Vector* limit = bez_stack + 30; + SW_FT_Bool first_arc = TRUE; + + /* if all control points are coincident, this is a no-op; */ + /* avoid creating a spurious corner */ + if (SW_FT_IS_SMALL(stroker->center.x - control->x) && + SW_FT_IS_SMALL(stroker->center.y - control->y) && + SW_FT_IS_SMALL(control->x - to->x) && + SW_FT_IS_SMALL(control->y - to->y)) { + stroker->center = *to; + goto Exit; + } + + arc = bez_stack; + arc[0] = *to; + arc[1] = *control; + arc[2] = stroker->center; + + while (arc >= bez_stack) { + SW_FT_Angle angle_in, angle_out; + + /* initialize with current direction */ + angle_in = angle_out = stroker->angle_in; + + if (arc < limit && + !ft_conic_is_small_enough(arc, &angle_in, &angle_out)) { + if (stroker->first_point) stroker->angle_in = angle_in; + + ft_conic_split(arc); + arc += 2; + continue; + } + + if (first_arc) { + first_arc = FALSE; + + /* process corner if necessary */ + if (stroker->first_point) + error = ft_stroker_subpath_start(stroker, angle_in, 0); + else { + stroker->angle_out = angle_in; + error = ft_stroker_process_corner(stroker, 0); + } + } else if (ft_pos_abs(SW_FT_Angle_Diff(stroker->angle_in, angle_in)) > + SW_FT_SMALL_CONIC_THRESHOLD / 4) { + /* if the deviation from one arc to the next is too great, */ + /* add a round corner */ + stroker->center = arc[2]; + stroker->angle_out = angle_in; + stroker->line_join = SW_FT_STROKER_LINEJOIN_ROUND; + + error = ft_stroker_process_corner(stroker, 0); + + /* reinstate line join style */ + stroker->line_join = stroker->line_join_saved; + } + + if (error) goto Exit; + + /* the arc's angle is small enough; we can add it directly to each */ + /* border */ + { + SW_FT_Vector ctrl, end; + SW_FT_Angle theta, phi, rotate, alpha0 = 0; + SW_FT_Fixed length; + SW_FT_StrokeBorder border; + SW_FT_Int side; + + theta = SW_FT_Angle_Diff(angle_in, angle_out) / 2; + phi = angle_in + theta; + length = SW_FT_DivFix(stroker->radius, SW_FT_Cos(theta)); + + /* compute direction of original arc */ + if (stroker->handle_wide_strokes) + alpha0 = SW_FT_Atan2(arc[0].x - arc[2].x, arc[0].y - arc[2].y); + + for (border = stroker->borders, side = 0; side <= 1; + side++, border++) { + rotate = SW_FT_SIDE_TO_ROTATE(side); + + /* compute control point */ + SW_FT_Vector_From_Polar(&ctrl, length, phi + rotate); + ctrl.x += arc[1].x; + ctrl.y += arc[1].y; + + /* compute end point */ + SW_FT_Vector_From_Polar(&end, stroker->radius, + angle_out + rotate); + end.x += arc[0].x; + end.y += arc[0].y; + + if (stroker->handle_wide_strokes) { + SW_FT_Vector start; + SW_FT_Angle alpha1; + + /* determine whether the border radius is greater than the + */ + /* radius of curvature of the original arc */ + start = border->points[border->num_points - 1]; + + alpha1 = SW_FT_Atan2(end.x - start.x, end.y - start.y); + + /* is the direction of the border arc opposite to */ + /* that of the original arc? */ + if (ft_pos_abs(SW_FT_Angle_Diff(alpha0, alpha1)) > + SW_FT_ANGLE_PI / 2) { + SW_FT_Angle beta, gamma; + SW_FT_Vector bvec, delta; + SW_FT_Fixed blen, sinA, sinB, alen; + + /* use the sine rule to find the intersection point */ + beta = + SW_FT_Atan2(arc[2].x - start.x, arc[2].y - start.y); + gamma = SW_FT_Atan2(arc[0].x - end.x, arc[0].y - end.y); + + bvec.x = end.x - start.x; + bvec.y = end.y - start.y; + + blen = SW_FT_Vector_Length(&bvec); + + sinA = ft_pos_abs(SW_FT_Sin(alpha1 - gamma)); + sinB = ft_pos_abs(SW_FT_Sin(beta - gamma)); + + alen = SW_FT_MulDiv(blen, sinA, sinB); + + SW_FT_Vector_From_Polar(&delta, alen, beta); + delta.x += start.x; + delta.y += start.y; + + /* circumnavigate the negative sector backwards */ + border->movable = FALSE; + error = ft_stroke_border_lineto(border, &delta, FALSE); + if (error) goto Exit; + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + error = ft_stroke_border_conicto(border, &ctrl, &start); + if (error) goto Exit; + /* and then move to the endpoint */ + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + + continue; + } + + /* else fall through */ + } + + /* simply add an arc */ + error = ft_stroke_border_conicto(border, &ctrl, &end); + if (error) goto Exit; + } + } + + arc -= 2; + + stroker->angle_in = angle_out; + } + + stroker->center = *to; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_CubicTo(SW_FT_Stroker stroker, SW_FT_Vector* control1, + SW_FT_Vector* control2, SW_FT_Vector* to) +{ + SW_FT_Error error = 0; + SW_FT_Vector bez_stack[37]; + SW_FT_Vector* arc; + SW_FT_Vector* limit = bez_stack + 32; + SW_FT_Bool first_arc = TRUE; + + /* if all control points are coincident, this is a no-op; */ + /* avoid creating a spurious corner */ + if (SW_FT_IS_SMALL(stroker->center.x - control1->x) && + SW_FT_IS_SMALL(stroker->center.y - control1->y) && + SW_FT_IS_SMALL(control1->x - control2->x) && + SW_FT_IS_SMALL(control1->y - control2->y) && + SW_FT_IS_SMALL(control2->x - to->x) && + SW_FT_IS_SMALL(control2->y - to->y)) { + stroker->center = *to; + goto Exit; + } + + arc = bez_stack; + arc[0] = *to; + arc[1] = *control2; + arc[2] = *control1; + arc[3] = stroker->center; + + while (arc >= bez_stack) { + SW_FT_Angle angle_in, angle_mid, angle_out; + + /* initialize with current direction */ + angle_in = angle_out = angle_mid = stroker->angle_in; + + if (arc < limit && + !ft_cubic_is_small_enough(arc, &angle_in, &angle_mid, &angle_out)) { + if (stroker->first_point) stroker->angle_in = angle_in; + + ft_cubic_split(arc); + arc += 3; + continue; + } + + if (first_arc) { + first_arc = FALSE; + + /* process corner if necessary */ + if (stroker->first_point) + error = ft_stroker_subpath_start(stroker, angle_in, 0); + else { + stroker->angle_out = angle_in; + error = ft_stroker_process_corner(stroker, 0); + } + } else if (ft_pos_abs(SW_FT_Angle_Diff(stroker->angle_in, angle_in)) > + SW_FT_SMALL_CUBIC_THRESHOLD / 4) { + /* if the deviation from one arc to the next is too great, */ + /* add a round corner */ + stroker->center = arc[3]; + stroker->angle_out = angle_in; + stroker->line_join = SW_FT_STROKER_LINEJOIN_ROUND; + + error = ft_stroker_process_corner(stroker, 0); + + /* reinstate line join style */ + stroker->line_join = stroker->line_join_saved; + } + + if (error) goto Exit; + + /* the arc's angle is small enough; we can add it directly to each */ + /* border */ + { + SW_FT_Vector ctrl1, ctrl2, end; + SW_FT_Angle theta1, phi1, theta2, phi2, rotate, alpha0 = 0; + SW_FT_Fixed length1, length2; + SW_FT_StrokeBorder border; + SW_FT_Int side; + + theta1 = SW_FT_Angle_Diff(angle_in, angle_mid) / 2; + theta2 = SW_FT_Angle_Diff(angle_mid, angle_out) / 2; + phi1 = ft_angle_mean(angle_in, angle_mid); + phi2 = ft_angle_mean(angle_mid, angle_out); + length1 = SW_FT_DivFix(stroker->radius, SW_FT_Cos(theta1)); + length2 = SW_FT_DivFix(stroker->radius, SW_FT_Cos(theta2)); + + /* compute direction of original arc */ + if (stroker->handle_wide_strokes) + alpha0 = SW_FT_Atan2(arc[0].x - arc[3].x, arc[0].y - arc[3].y); + + for (border = stroker->borders, side = 0; side <= 1; + side++, border++) { + rotate = SW_FT_SIDE_TO_ROTATE(side); + + /* compute control points */ + SW_FT_Vector_From_Polar(&ctrl1, length1, phi1 + rotate); + ctrl1.x += arc[2].x; + ctrl1.y += arc[2].y; + + SW_FT_Vector_From_Polar(&ctrl2, length2, phi2 + rotate); + ctrl2.x += arc[1].x; + ctrl2.y += arc[1].y; + + /* compute end point */ + SW_FT_Vector_From_Polar(&end, stroker->radius, + angle_out + rotate); + end.x += arc[0].x; + end.y += arc[0].y; + + if (stroker->handle_wide_strokes) { + SW_FT_Vector start; + SW_FT_Angle alpha1; + + /* determine whether the border radius is greater than the + */ + /* radius of curvature of the original arc */ + start = border->points[border->num_points - 1]; + + alpha1 = SW_FT_Atan2(end.x - start.x, end.y - start.y); + + /* is the direction of the border arc opposite to */ + /* that of the original arc? */ + if (ft_pos_abs(SW_FT_Angle_Diff(alpha0, alpha1)) > + SW_FT_ANGLE_PI / 2) { + SW_FT_Angle beta, gamma; + SW_FT_Vector bvec, delta; + SW_FT_Fixed blen, sinA, sinB, alen; + + /* use the sine rule to find the intersection point */ + beta = + SW_FT_Atan2(arc[3].x - start.x, arc[3].y - start.y); + gamma = SW_FT_Atan2(arc[0].x - end.x, arc[0].y - end.y); + + bvec.x = end.x - start.x; + bvec.y = end.y - start.y; + + blen = SW_FT_Vector_Length(&bvec); + + sinA = ft_pos_abs(SW_FT_Sin(alpha1 - gamma)); + sinB = ft_pos_abs(SW_FT_Sin(beta - gamma)); + + alen = SW_FT_MulDiv(blen, sinA, sinB); + + SW_FT_Vector_From_Polar(&delta, alen, beta); + delta.x += start.x; + delta.y += start.y; + + /* circumnavigate the negative sector backwards */ + border->movable = FALSE; + error = ft_stroke_border_lineto(border, &delta, FALSE); + if (error) goto Exit; + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + error = ft_stroke_border_cubicto(border, &ctrl2, &ctrl1, + &start); + if (error) goto Exit; + /* and then move to the endpoint */ + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + + continue; + } + + /* else fall through */ + } + + /* simply add an arc */ + error = ft_stroke_border_cubicto(border, &ctrl1, &ctrl2, &end); + if (error) goto Exit; + } + } + + arc -= 3; + + stroker->angle_in = angle_out; + } + + stroker->center = *to; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_BeginSubPath(SW_FT_Stroker stroker, SW_FT_Vector* to, + SW_FT_Bool open) +{ + /* We cannot process the first point, because there is not enough */ + /* information regarding its corner/cap. The latter will be processed */ + /* in the `SW_FT_Stroker_EndSubPath' routine. */ + /* */ + stroker->first_point = TRUE; + stroker->center = *to; + stroker->subpath_open = open; + + /* Determine if we need to check whether the border radius is greater */ + /* than the radius of curvature of a curve, to handle this case */ + /* specially. This is only required if bevel joins or butt caps may */ + /* be created, because round & miter joins and round & square caps */ + /* cover the negative sector created with wide strokes. */ + stroker->handle_wide_strokes = + SW_FT_BOOL(stroker->line_join != SW_FT_STROKER_LINEJOIN_ROUND || + (stroker->subpath_open && + stroker->line_cap == SW_FT_STROKER_LINECAP_BUTT)); + + /* record the subpath start point for each border */ + stroker->subpath_start = *to; + + stroker->angle_in = 0; + + return 0; +} + +static SW_FT_Error ft_stroker_add_reverse_left(SW_FT_Stroker stroker, + SW_FT_Bool open) +{ + SW_FT_StrokeBorder right = stroker->borders + 0; + SW_FT_StrokeBorder left = stroker->borders + 1; + SW_FT_Int new_points; + SW_FT_Error error = 0; + + assert(left->start >= 0); + + new_points = left->num_points - left->start; + if (new_points > 0) { + error = ft_stroke_border_grow(right, (SW_FT_UInt)new_points); + if (error) goto Exit; + + { + SW_FT_Vector* dst_point = right->points + right->num_points; + SW_FT_Byte* dst_tag = right->tags + right->num_points; + SW_FT_Vector* src_point = left->points + left->num_points - 1; + SW_FT_Byte* src_tag = left->tags + left->num_points - 1; + + while (src_point >= left->points + left->start) { + *dst_point = *src_point; + *dst_tag = *src_tag; + + if (open) + dst_tag[0] &= ~SW_FT_STROKE_TAG_BEGIN_END; + else { + SW_FT_Byte ttag = + (SW_FT_Byte)(dst_tag[0] & SW_FT_STROKE_TAG_BEGIN_END); + + /* switch begin/end tags if necessary */ + if (ttag == SW_FT_STROKE_TAG_BEGIN || + ttag == SW_FT_STROKE_TAG_END) + dst_tag[0] ^= SW_FT_STROKE_TAG_BEGIN_END; + } + + src_point--; + src_tag--; + dst_point++; + dst_tag++; + } + } + + left->num_points = left->start; + right->num_points += new_points; + + right->movable = FALSE; + left->movable = FALSE; + } + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +/* there's a lot of magic in this function! */ +SW_FT_Error SW_FT_Stroker_EndSubPath(SW_FT_Stroker stroker) +{ + SW_FT_Error error = 0; + + if (stroker->subpath_open) { + SW_FT_StrokeBorder right = stroker->borders; + + /* All right, this is an opened path, we need to add a cap between */ + /* right & left, add the reverse of left, then add a final cap */ + /* between left & right. */ + error = ft_stroker_cap(stroker, stroker->angle_in, 0); + if (error) goto Exit; + + /* add reversed points from `left' to `right' */ + error = ft_stroker_add_reverse_left(stroker, TRUE); + if (error) goto Exit; + + /* now add the final cap */ + stroker->center = stroker->subpath_start; + error = + ft_stroker_cap(stroker, stroker->subpath_angle + SW_FT_ANGLE_PI, 0); + if (error) goto Exit; + + /* Now end the right subpath accordingly. The left one is */ + /* rewind and doesn't need further processing. */ + ft_stroke_border_close(right, FALSE); + } else { + SW_FT_Angle turn; + SW_FT_Int inside_side; + + /* close the path if needed */ + if (stroker->center.x != stroker->subpath_start.x || + stroker->center.y != stroker->subpath_start.y) { + error = SW_FT_Stroker_LineTo(stroker, &stroker->subpath_start); + if (error) goto Exit; + } + + /* process the corner */ + stroker->angle_out = stroker->subpath_angle; + turn = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + + /* no specific corner processing is required if the turn is 0 */ + if (turn != 0) { + /* when we turn to the right, the inside side is 0 */ + inside_side = 0; + + /* otherwise, the inside side is 1 */ + if (turn < 0) inside_side = 1; + + error = ft_stroker_inside(stroker, inside_side, + stroker->subpath_line_length); + if (error) goto Exit; + + /* process the outside side */ + error = ft_stroker_outside(stroker, 1 - inside_side, + stroker->subpath_line_length); + if (error) goto Exit; + } + + /* then end our two subpaths */ + ft_stroke_border_close(stroker->borders + 0, FALSE); + ft_stroke_border_close(stroker->borders + 1, TRUE); + } + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_GetBorderCounts(SW_FT_Stroker stroker, + SW_FT_StrokerBorder border, + SW_FT_UInt* anum_points, + SW_FT_UInt* anum_contours) +{ + SW_FT_UInt num_points = 0, num_contours = 0; + SW_FT_Error error; + + if (!stroker || border > 1) { + error = -1; // SW_FT_THROW( Invalid_Argument ); + goto Exit; + } + + error = ft_stroke_border_get_counts(stroker->borders + border, &num_points, + &num_contours); +Exit: + if (anum_points) *anum_points = num_points; + + if (anum_contours) *anum_contours = num_contours; + + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_GetCounts(SW_FT_Stroker stroker, + SW_FT_UInt* anum_points, + SW_FT_UInt* anum_contours) +{ + SW_FT_UInt count1, count2, num_points = 0; + SW_FT_UInt count3, count4, num_contours = 0; + SW_FT_Error error; + + error = ft_stroke_border_get_counts(stroker->borders + 0, &count1, &count2); + if (error) goto Exit; + + error = ft_stroke_border_get_counts(stroker->borders + 1, &count3, &count4); + if (error) goto Exit; + + num_points = count1 + count3; + num_contours = count2 + count4; + +Exit: + *anum_points = num_points; + *anum_contours = num_contours; + return error; +} + +/* documentation is in ftstroke.h */ + +void SW_FT_Stroker_ExportBorder(SW_FT_Stroker stroker, + SW_FT_StrokerBorder border, + SW_FT_Outline* outline) +{ + if (border == SW_FT_STROKER_BORDER_LEFT || + border == SW_FT_STROKER_BORDER_RIGHT) { + SW_FT_StrokeBorder sborder = &stroker->borders[border]; + + if (sborder->valid) ft_stroke_border_export(sborder, outline); + } +} + +/* documentation is in ftstroke.h */ + +void SW_FT_Stroker_Export(SW_FT_Stroker stroker, SW_FT_Outline* outline) +{ + SW_FT_Stroker_ExportBorder(stroker, SW_FT_STROKER_BORDER_LEFT, outline); + SW_FT_Stroker_ExportBorder(stroker, SW_FT_STROKER_BORDER_RIGHT, outline); +} + +/* documentation is in ftstroke.h */ + +/* + * The following is very similar to SW_FT_Outline_Decompose, except + * that we do support opened paths, and do not scale the outline. + */ +SW_FT_Error SW_FT_Stroker_ParseOutline(SW_FT_Stroker stroker, + const SW_FT_Outline* outline) +{ + SW_FT_Vector v_last; + SW_FT_Vector v_control; + SW_FT_Vector v_start; + + SW_FT_Vector* point; + SW_FT_Vector* limit; + char* tags; + + SW_FT_Error error; + + SW_FT_Int n; /* index of contour in outline */ + SW_FT_UInt first; /* index of first point in contour */ + SW_FT_Int tag; /* current point's state */ + + if (!outline || !stroker) return -1; // SW_FT_THROW( Invalid_Argument ); + + SW_FT_Stroker_Rewind(stroker); + + first = 0; + + for (n = 0; n < outline->n_contours; n++) { + SW_FT_UInt last; /* index of last point in contour */ + + last = outline->contours[n]; + limit = outline->points + last; + + /* skip empty points; we don't stroke these */ + if (last <= first) { + first = last + 1; + continue; + } + + v_start = outline->points[first]; + v_last = outline->points[last]; + + v_control = v_start; + + point = outline->points + first; + tags = outline->tags + first; + tag = SW_FT_CURVE_TAG(tags[0]); + + /* A contour cannot start with a cubic control point! */ + if (tag == SW_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; + + /* check first point to determine origin */ + if (tag == SW_FT_CURVE_TAG_CONIC) { + /* First point is conic control. Yes, this happens. */ + if (SW_FT_CURVE_TAG(outline->tags[last]) == SW_FT_CURVE_TAG_ON) { + /* start at last point if it is on the curve */ + v_start = v_last; + limit--; + } else { + /* if both first and last points are conic, */ + /* start at their middle */ + v_start.x = (v_start.x + v_last.x) / 2; + v_start.y = (v_start.y + v_last.y) / 2; + } + point--; + tags--; + } + + error = SW_FT_Stroker_BeginSubPath(stroker, &v_start, outline->contours_flag[n]); + if (error) goto Exit; + + while (point < limit) { + point++; + tags++; + + tag = SW_FT_CURVE_TAG(tags[0]); + switch (tag) { + case SW_FT_CURVE_TAG_ON: /* emit a single line_to */ + { + SW_FT_Vector vec; + + vec.x = point->x; + vec.y = point->y; + + error = SW_FT_Stroker_LineTo(stroker, &vec); + if (error) goto Exit; + continue; + } + + case SW_FT_CURVE_TAG_CONIC: /* consume conic arcs */ + v_control.x = point->x; + v_control.y = point->y; + + Do_Conic: + if (point < limit) { + SW_FT_Vector vec; + SW_FT_Vector v_middle; + + point++; + tags++; + tag = SW_FT_CURVE_TAG(tags[0]); + + vec = point[0]; + + if (tag == SW_FT_CURVE_TAG_ON) { + error = + SW_FT_Stroker_ConicTo(stroker, &v_control, &vec); + if (error) goto Exit; + continue; + } + + if (tag != SW_FT_CURVE_TAG_CONIC) goto Invalid_Outline; + + v_middle.x = (v_control.x + vec.x) / 2; + v_middle.y = (v_control.y + vec.y) / 2; + + error = + SW_FT_Stroker_ConicTo(stroker, &v_control, &v_middle); + if (error) goto Exit; + + v_control = vec; + goto Do_Conic; + } + + error = SW_FT_Stroker_ConicTo(stroker, &v_control, &v_start); + goto Close; + + default: /* SW_FT_CURVE_TAG_CUBIC */ + { + SW_FT_Vector vec1, vec2; + + if (point + 1 > limit || + SW_FT_CURVE_TAG(tags[1]) != SW_FT_CURVE_TAG_CUBIC) + goto Invalid_Outline; + + point += 2; + tags += 2; + + vec1 = point[-2]; + vec2 = point[-1]; + + if (point <= limit) { + SW_FT_Vector vec; + + vec = point[0]; + + error = SW_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &vec); + if (error) goto Exit; + continue; + } + + error = SW_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &v_start); + goto Close; + } + } + } + + Close: + if (error) goto Exit; + + /* don't try to end the path if no segments have been generated */ + if (!stroker->first_point) { + error = SW_FT_Stroker_EndSubPath(stroker); + if (error) goto Exit; + } + + first = last + 1; + } + + return 0; + +Exit: + return error; + +Invalid_Outline: + return -2; // SW_FT_THROW( Invalid_Outline ); +} + +/* END */ diff --git a/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_stroker.h b/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_stroker.h new file mode 100755 index 000000000..1514cf3d0 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_stroker.h @@ -0,0 +1,319 @@ +#ifndef V_FT_STROKER_H +#define V_FT_STROKER_H +/***************************************************************************/ +/* */ +/* ftstroke.h */ +/* */ +/* FreeType path stroker (specification). */ +/* */ +/* Copyright 2002-2006, 2008, 2009, 2011-2012 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "v_ft_raster.h" + + /************************************************************** + * + * @type: + * SW_FT_Stroker + * + * @description: + * Opaque handler to a path stroker object. + */ + typedef struct SW_FT_StrokerRec_* SW_FT_Stroker; + + + /************************************************************** + * + * @enum: + * SW_FT_Stroker_LineJoin + * + * @description: + * These values determine how two joining lines are rendered + * in a stroker. + * + * @values: + * SW_FT_STROKER_LINEJOIN_ROUND :: + * Used to render rounded line joins. Circular arcs are used + * to join two lines smoothly. + * + * SW_FT_STROKER_LINEJOIN_BEVEL :: + * Used to render beveled line joins. The outer corner of + * the joined lines is filled by enclosing the triangular + * region of the corner with a straight line between the + * outer corners of each stroke. + * + * SW_FT_STROKER_LINEJOIN_MITER_FIXED :: + * Used to render mitered line joins, with fixed bevels if the + * miter limit is exceeded. The outer edges of the strokes + * for the two segments are extended until they meet at an + * angle. If the segments meet at too sharp an angle (such + * that the miter would extend from the intersection of the + * segments a distance greater than the product of the miter + * limit value and the border radius), then a bevel join (see + * above) is used instead. This prevents long spikes being + * created. SW_FT_STROKER_LINEJOIN_MITER_FIXED generates a miter + * line join as used in PostScript and PDF. + * + * SW_FT_STROKER_LINEJOIN_MITER_VARIABLE :: + * SW_FT_STROKER_LINEJOIN_MITER :: + * Used to render mitered line joins, with variable bevels if + * the miter limit is exceeded. The intersection of the + * strokes is clipped at a line perpendicular to the bisector + * of the angle between the strokes, at the distance from the + * intersection of the segments equal to the product of the + * miter limit value and the border radius. This prevents + * long spikes being created. + * SW_FT_STROKER_LINEJOIN_MITER_VARIABLE generates a mitered line + * join as used in XPS. SW_FT_STROKER_LINEJOIN_MITER is an alias + * for SW_FT_STROKER_LINEJOIN_MITER_VARIABLE, retained for + * backwards compatibility. + */ + typedef enum SW_FT_Stroker_LineJoin_ + { + SW_FT_STROKER_LINEJOIN_ROUND = 0, + SW_FT_STROKER_LINEJOIN_BEVEL = 1, + SW_FT_STROKER_LINEJOIN_MITER_VARIABLE = 2, + SW_FT_STROKER_LINEJOIN_MITER = SW_FT_STROKER_LINEJOIN_MITER_VARIABLE, + SW_FT_STROKER_LINEJOIN_MITER_FIXED = 3 + + } SW_FT_Stroker_LineJoin; + + + /************************************************************** + * + * @enum: + * SW_FT_Stroker_LineCap + * + * @description: + * These values determine how the end of opened sub-paths are + * rendered in a stroke. + * + * @values: + * SW_FT_STROKER_LINECAP_BUTT :: + * The end of lines is rendered as a full stop on the last + * point itself. + * + * SW_FT_STROKER_LINECAP_ROUND :: + * The end of lines is rendered as a half-circle around the + * last point. + * + * SW_FT_STROKER_LINECAP_SQUARE :: + * The end of lines is rendered as a square around the + * last point. + */ + typedef enum SW_FT_Stroker_LineCap_ + { + SW_FT_STROKER_LINECAP_BUTT = 0, + SW_FT_STROKER_LINECAP_ROUND, + SW_FT_STROKER_LINECAP_SQUARE + + } SW_FT_Stroker_LineCap; + + + /************************************************************** + * + * @enum: + * SW_FT_StrokerBorder + * + * @description: + * These values are used to select a given stroke border + * in @SW_FT_Stroker_GetBorderCounts and @SW_FT_Stroker_ExportBorder. + * + * @values: + * SW_FT_STROKER_BORDER_LEFT :: + * Select the left border, relative to the drawing direction. + * + * SW_FT_STROKER_BORDER_RIGHT :: + * Select the right border, relative to the drawing direction. + * + * @note: + * Applications are generally interested in the `inside' and `outside' + * borders. However, there is no direct mapping between these and the + * `left' and `right' ones, since this really depends on the glyph's + * drawing orientation, which varies between font formats. + * + * You can however use @SW_FT_Outline_GetInsideBorder and + * @SW_FT_Outline_GetOutsideBorder to get these. + */ + typedef enum SW_FT_StrokerBorder_ + { + SW_FT_STROKER_BORDER_LEFT = 0, + SW_FT_STROKER_BORDER_RIGHT + + } SW_FT_StrokerBorder; + + + /************************************************************** + * + * @function: + * SW_FT_Stroker_New + * + * @description: + * Create a new stroker object. + * + * @input: + * library :: + * FreeType library handle. + * + * @output: + * astroker :: + * A new stroker object handle. NULL in case of error. + * + * @return: + * FreeType error code. 0~means success. + */ + SW_FT_Error + SW_FT_Stroker_New( SW_FT_Stroker *astroker ); + + + /************************************************************** + * + * @function: + * SW_FT_Stroker_Set + * + * @description: + * Reset a stroker object's attributes. + * + * @input: + * stroker :: + * The target stroker handle. + * + * radius :: + * The border radius. + * + * line_cap :: + * The line cap style. + * + * line_join :: + * The line join style. + * + * miter_limit :: + * The miter limit for the SW_FT_STROKER_LINEJOIN_MITER_FIXED and + * SW_FT_STROKER_LINEJOIN_MITER_VARIABLE line join styles, + * expressed as 16.16 fixed-point value. + * + * @note: + * The radius is expressed in the same units as the outline + * coordinates. + */ + void + SW_FT_Stroker_Set( SW_FT_Stroker stroker, + SW_FT_Fixed radius, + SW_FT_Stroker_LineCap line_cap, + SW_FT_Stroker_LineJoin line_join, + SW_FT_Fixed miter_limit ); + + /************************************************************** + * + * @function: + * SW_FT_Stroker_ParseOutline + * + * @description: + * A convenience function used to parse a whole outline with + * the stroker. The resulting outline(s) can be retrieved + * later by functions like @SW_FT_Stroker_GetCounts and @SW_FT_Stroker_Export. + * + * @input: + * stroker :: + * The target stroker handle. + * + * outline :: + * The source outline. + * + * + * @return: + * FreeType error code. 0~means success. + * + * @note: + * If `opened' is~0 (the default), the outline is treated as a closed + * path, and the stroker generates two distinct `border' outlines. + * + * + * This function calls @SW_FT_Stroker_Rewind automatically. + */ + SW_FT_Error + SW_FT_Stroker_ParseOutline( SW_FT_Stroker stroker, + const SW_FT_Outline* outline); + + + /************************************************************** + * + * @function: + * SW_FT_Stroker_GetCounts + * + * @description: + * Call this function once you have finished parsing your paths + * with the stroker. It returns the number of points and + * contours necessary to export all points/borders from the stroked + * outline/path. + * + * @input: + * stroker :: + * The target stroker handle. + * + * @output: + * anum_points :: + * The number of points. + * + * anum_contours :: + * The number of contours. + * + * @return: + * FreeType error code. 0~means success. + */ + SW_FT_Error + SW_FT_Stroker_GetCounts( SW_FT_Stroker stroker, + SW_FT_UInt *anum_points, + SW_FT_UInt *anum_contours ); + + + /************************************************************** + * + * @function: + * SW_FT_Stroker_Export + * + * @description: + * Call this function after @SW_FT_Stroker_GetBorderCounts to + * export all borders to your own @SW_FT_Outline structure. + * + * Note that this function appends the border points and + * contours to your outline, but does not try to resize its + * arrays. + * + * @input: + * stroker :: + * The target stroker handle. + * + * outline :: + * The target outline handle. + */ + void + SW_FT_Stroker_Export( SW_FT_Stroker stroker, + SW_FT_Outline* outline ); + + + /************************************************************** + * + * @function: + * SW_FT_Stroker_Done + * + * @description: + * Destroy a stroker object. + * + * @input: + * stroker :: + * A stroker handle. Can be NULL. + */ + void + SW_FT_Stroker_Done( SW_FT_Stroker stroker ); + + +#endif // V_FT_STROKER_H diff --git a/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_types.h b/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_types.h new file mode 100755 index 000000000..a01c4f287 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/freetype/v_ft_types.h @@ -0,0 +1,160 @@ +#ifndef V_FT_TYPES_H +#define V_FT_TYPES_H + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Fixed */ +/* */ +/* */ +/* This type is used to store 16.16 fixed-point values, like scaling */ +/* values or matrix coefficients. */ +/* */ +typedef signed long SW_FT_Fixed; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Int */ +/* */ +/* */ +/* A typedef for the int type. */ +/* */ +typedef signed int SW_FT_Int; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_UInt */ +/* */ +/* */ +/* A typedef for the unsigned int type. */ +/* */ +typedef unsigned int SW_FT_UInt; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Long */ +/* */ +/* */ +/* A typedef for signed long. */ +/* */ +typedef signed long SW_FT_Long; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_ULong */ +/* */ +/* */ +/* A typedef for unsigned long. */ +/* */ +typedef unsigned long SW_FT_ULong; + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Short */ +/* */ +/* */ +/* A typedef for signed short. */ +/* */ +typedef signed short SW_FT_Short; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Byte */ +/* */ +/* */ +/* A simple typedef for the _unsigned_ char type. */ +/* */ +typedef unsigned char SW_FT_Byte; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Bool */ +/* */ +/* */ +/* A typedef of unsigned char, used for simple booleans. As usual, */ +/* values 1 and~0 represent true and false, respectively. */ +/* */ +typedef unsigned char SW_FT_Bool; + + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Error */ +/* */ +/* */ +/* The FreeType error code type. A value of~0 is always interpreted */ +/* as a successful operation. */ +/* */ +typedef int SW_FT_Error; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Pos */ +/* */ +/* */ +/* The type SW_FT_Pos is used to store vectorial coordinates. Depending */ +/* on the context, these can represent distances in integer font */ +/* units, or 16.16, or 26.6 fixed-point pixel coordinates. */ +/* */ +typedef signed long SW_FT_Pos; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Vector */ +/* */ +/* */ +/* A simple structure used to store a 2D vector; coordinates are of */ +/* the SW_FT_Pos type. */ +/* */ +/* */ +/* x :: The horizontal coordinate. */ +/* y :: The vertical coordinate. */ +/* */ +typedef struct SW_FT_Vector_ +{ + SW_FT_Pos x; + SW_FT_Pos y; + +} SW_FT_Vector; + + +typedef long long int SW_FT_Int64; +typedef unsigned long long int SW_FT_UInt64; + +typedef signed int SW_FT_Int32; +typedef unsigned int SW_FT_UInt32; + + +#define SW_FT_BOOL( x ) ( (SW_FT_Bool)( x ) ) + +#define SW_FT_SIZEOF_LONG 4 + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + + +#endif // V_FT_TYPES_H diff --git a/TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arm-neon-asm.S b/TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arm-neon-asm.S new file mode 100755 index 000000000..f2203c21c --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arm-neon-asm.S @@ -0,0 +1,497 @@ +/* + * Copyright © 2009 Nokia Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Author: Siarhei Siamashka (siarhei.siamashka@nokia.com) + */ + +/* + * This file contains implementations of NEON optimized pixel processing + * functions. There is no full and detailed tutorial, but some functions + * (those which are exposing some new or interesting features) are + * extensively commented and can be used as examples. + * + * You may want to have a look at the comments for following functions: + * - pixman_composite_over_8888_0565_asm_neon + * - pixman_composite_over_n_8_0565_asm_neon + */ + +/* Prevent the stack from becoming executable for no reason... */ +#if defined(__linux__) && defined(__ELF__) +.section .note.GNU-stack,"",%progbits +#endif + + .text + .fpu neon + .arch armv7a + .object_arch armv4 + .eabi_attribute 10, 0 /* suppress Tag_FP_arch */ + .eabi_attribute 12, 0 /* suppress Tag_Advanced_SIMD_arch */ + .arm + .altmacro + .p2align 2 + + +//#include "pixman-arm-asm.h" +/* Supplementary macro for setting function attributes */ +.macro pixman_asm_function fname + .func fname + .global fname +#ifdef __ELF__ + .hidden fname + .type fname, %function +#endif +fname: +.endm + +//#include "pixman-private.h" +/* + * The defines which are shared between C and assembly code + */ + +/* bilinear interpolation precision (must be < 8) */ +#define BILINEAR_INTERPOLATION_BITS 7 +#define BILINEAR_INTERPOLATION_RANGE (1 << BILINEAR_INTERPOLATION_BITS) + +#include "pixman-arm-neon-asm.h" + +/* Global configuration options and preferences */ + +/* + * The code can optionally make use of unaligned memory accesses to improve + * performance of handling leading/trailing pixels for each scanline. + * Configuration variable RESPECT_STRICT_ALIGNMENT can be set to 0 for + * example in linux if unaligned memory accesses are not configured to + * generate.exceptions. + */ +.set RESPECT_STRICT_ALIGNMENT, 1 + +/* + * Set default prefetch type. There is a choice between the following options: + * + * PREFETCH_TYPE_NONE (may be useful for the ARM cores where PLD is set to work + * as NOP to workaround some HW bugs or for whatever other reason) + * + * PREFETCH_TYPE_SIMPLE (may be useful for simple single-issue ARM cores where + * advanced prefetch intruduces heavy overhead) + * + * PREFETCH_TYPE_ADVANCED (useful for superscalar cores such as ARM Cortex-A8 + * which can run ARM and NEON instructions simultaneously so that extra ARM + * instructions do not add (many) extra cycles, but improve prefetch efficiency) + * + * Note: some types of function can't support advanced prefetch and fallback + * to simple one (those which handle 24bpp pixels) + */ +.set PREFETCH_TYPE_DEFAULT, PREFETCH_TYPE_ADVANCED + +/* Prefetch distance in pixels for simple prefetch */ +.set PREFETCH_DISTANCE_SIMPLE, 64 + +/* + * Implementation of pixman_composite_over_8888_0565_asm_neon + * + * This function takes a8r8g8b8 source buffer, r5g6b5 destination buffer and + * performs OVER compositing operation. Function fast_composite_over_8888_0565 + * from pixman-fast-path.c does the same in C and can be used as a reference. + * + * First we need to have some NEON assembly code which can do the actual + * operation on the pixels and provide it to the template macro. + * + * Template macro quite conveniently takes care of emitting all the necessary + * code for memory reading and writing (including quite tricky cases of + * handling unaligned leading/trailing pixels), so we only need to deal with + * the data in NEON registers. + * + * NEON registers allocation in general is recommented to be the following: + * d0, d1, d2, d3 - contain loaded source pixel data + * d4, d5, d6, d7 - contain loaded destination pixels (if they are needed) + * d24, d25, d26, d27 - contain loading mask pixel data (if mask is used) + * d28, d29, d30, d31 - place for storing the result (destination pixels) + * + * As can be seen above, four 64-bit NEON registers are used for keeping + * intermediate pixel data and up to 8 pixels can be processed in one step + * for 32bpp formats (16 pixels for 16bpp, 32 pixels for 8bpp). + * + * This particular function uses the following registers allocation: + * d0, d1, d2, d3 - contain loaded source pixel data + * d4, d5 - contain loaded destination pixels (they are needed) + * d28, d29 - place for storing the result (destination pixels) + */ + +/* + * Step one. We need to have some code to do some arithmetics on pixel data. + * This is implemented as a pair of macros: '*_head' and '*_tail'. When used + * back-to-back, they take pixel data from {d0, d1, d2, d3} and {d4, d5}, + * perform all the needed calculations and write the result to {d28, d29}. + * The rationale for having two macros and not just one will be explained + * later. In practice, any single monolitic function which does the work can + * be split into two parts in any arbitrary way without affecting correctness. + * + * There is one special trick here too. Common template macro can optionally + * make our life a bit easier by doing R, G, B, A color components + * deinterleaving for 32bpp pixel formats (and this feature is used in + * 'pixman_composite_over_8888_0565_asm_neon' function). So it means that + * instead of having 8 packed pixels in {d0, d1, d2, d3} registers, we + * actually use d0 register for blue channel (a vector of eight 8-bit + * values), d1 register for green, d2 for red and d3 for alpha. This + * simple conversion can be also done with a few NEON instructions: + * + * Packed to planar conversion: + * vuzp.8 d0, d1 + * vuzp.8 d2, d3 + * vuzp.8 d1, d3 + * vuzp.8 d0, d2 + * + * Planar to packed conversion: + * vzip.8 d0, d2 + * vzip.8 d1, d3 + * vzip.8 d2, d3 + * vzip.8 d0, d1 + * + * But pixel can be loaded directly in planar format using VLD4.8 NEON + * instruction. It is 1 cycle slower than VLD1.32, so this is not always + * desirable, that's why deinterleaving is optional. + * + * But anyway, here is the code: + */ + +/* + * OK, now we got almost everything that we need. Using the above two + * macros, the work can be done right. But now we want to optimize + * it a bit. ARM Cortex-A8 is an in-order core, and benefits really + * a lot from good code scheduling and software pipelining. + * + * Let's construct some code, which will run in the core main loop. + * Some pseudo-code of the main loop will look like this: + * head + * while (...) { + * tail + * head + * } + * tail + * + * It may look a bit weird, but this setup allows to hide instruction + * latencies better and also utilize dual-issue capability more + * efficiently (make pairs of load-store and ALU instructions). + * + * So what we need now is a '*_tail_head' macro, which will be used + * in the core main loop. A trivial straightforward implementation + * of this macro would look like this: + * + * pixman_composite_over_8888_0565_process_pixblock_tail + * vst1.16 {d28, d29}, [DST_W, :128]! + * vld1.16 {d4, d5}, [DST_R, :128]! + * vld4.32 {d0, d1, d2, d3}, [SRC]! + * pixman_composite_over_8888_0565_process_pixblock_head + * cache_preload 8, 8 + * + * Now it also got some VLD/VST instructions. We simply can't move from + * processing one block of pixels to the other one with just arithmetics. + * The previously processed data needs to be written to memory and new + * data needs to be fetched. Fortunately, this main loop does not deal + * with partial leading/trailing pixels and can load/store a full block + * of pixels in a bulk. Additionally, destination buffer is already + * 16 bytes aligned here (which is good for performance). + * + * New things here are DST_R, DST_W, SRC and MASK identifiers. These + * are the aliases for ARM registers which are used as pointers for + * accessing data. We maintain separate pointers for reading and writing + * destination buffer (DST_R and DST_W). + * + * Another new thing is 'cache_preload' macro. It is used for prefetching + * data into CPU L2 cache and improve performance when dealing with large + * images which are far larger than cache size. It uses one argument + * (actually two, but they need to be the same here) - number of pixels + * in a block. Looking into 'pixman-arm-neon-asm.h' can provide some + * details about this macro. Moreover, if good performance is needed + * the code from this macro needs to be copied into '*_tail_head' macro + * and mixed with the rest of code for optimal instructions scheduling. + * We are actually doing it below. + * + * Now after all the explanations, here is the optimized code. + * Different instruction streams (originaling from '*_head', '*_tail' + * and 'cache_preload' macro) use different indentation levels for + * better readability. Actually taking the code from one of these + * indentation levels and ignoring a few VLD/VST instructions would + * result in exactly the code from '*_head', '*_tail' or 'cache_preload' + * macro! + */ + +/* + * And now the final part. We are using 'generate_composite_function' macro + * to put all the stuff together. We are specifying the name of the function + * which we want to get, number of bits per pixel for the source, mask and + * destination (0 if unused, like mask in this case). Next come some bit + * flags: + * FLAG_DST_READWRITE - tells that the destination buffer is both read + * and written, for write-only buffer we would use + * FLAG_DST_WRITEONLY flag instead + * FLAG_DEINTERLEAVE_32BPP - tells that we prefer to work with planar data + * and separate color channels for 32bpp format. + * The next things are: + * - the number of pixels processed per iteration (8 in this case, because + * that's the maximum what can fit into four 64-bit NEON registers). + * - prefetch distance, measured in pixel blocks. In this case it is 5 times + * by 8 pixels. That would be 40 pixels, or up to 160 bytes. Optimal + * prefetch distance can be selected by running some benchmarks. + * + * After that we specify some macros, these are 'default_init', + * 'default_cleanup' here which are empty (but it is possible to have custom + * init/cleanup macros to be able to save/restore some extra NEON registers + * like d8-d15 or do anything else) followed by + * 'pixman_composite_over_8888_0565_process_pixblock_head', + * 'pixman_composite_over_8888_0565_process_pixblock_tail' and + * 'pixman_composite_over_8888_0565_process_pixblock_tail_head' + * which we got implemented above. + * + * The last part is the NEON registers allocation scheme. + */ + +/******************************************************************************/ + +/******************************************************************************/ + .macro pixman_composite_out_reverse_8888_8888_process_pixblock_head + vmvn.8 d24, d3 /* get inverted alpha */ + /* do alpha blending */ + vmull.u8 q8, d24, d4 + vmull.u8 q9, d24, d5 + vmull.u8 q10, d24, d6 + vmull.u8 q11, d24, d7 + .endm + + .macro pixman_composite_out_reverse_8888_8888_process_pixblock_tail + vrshr.u16 q14, q8, #8 + vrshr.u16 q15, q9, #8 + vrshr.u16 q12, q10, #8 + vrshr.u16 q13, q11, #8 + vraddhn.u16 d28, q14, q8 + vraddhn.u16 d29, q15, q9 + vraddhn.u16 d30, q12, q10 + vraddhn.u16 d31, q13, q11 + .endm + +/******************************************************************************/ + +.macro pixman_composite_over_8888_8888_process_pixblock_head + pixman_composite_out_reverse_8888_8888_process_pixblock_head +.endm + +.macro pixman_composite_over_8888_8888_process_pixblock_tail + pixman_composite_out_reverse_8888_8888_process_pixblock_tail + vqadd.u8 q14, q0, q14 + vqadd.u8 q15, q1, q15 +.endm + +.macro pixman_composite_over_8888_8888_process_pixblock_tail_head + vld4.8 {d4, d5, d6, d7}, [DST_R, :128]! + vrshr.u16 q14, q8, #8 + PF add PF_X, PF_X, #8 + PF tst PF_CTL, #0xF + vrshr.u16 q15, q9, #8 + vrshr.u16 q12, q10, #8 + vrshr.u16 q13, q11, #8 + PF addne PF_X, PF_X, #8 + PF subne PF_CTL, PF_CTL, #1 + vraddhn.u16 d28, q14, q8 + vraddhn.u16 d29, q15, q9 + PF cmp PF_X, ORIG_W + vraddhn.u16 d30, q12, q10 + vraddhn.u16 d31, q13, q11 + vqadd.u8 q14, q0, q14 + vqadd.u8 q15, q1, q15 + fetch_src_pixblock + PF pld, [PF_SRC, PF_X, lsl #src_bpp_shift] + vmvn.8 d22, d3 + PF pld, [PF_DST, PF_X, lsl #dst_bpp_shift] + vst4.8 {d28, d29, d30, d31}, [DST_W, :128]! + PF subge PF_X, PF_X, ORIG_W + vmull.u8 q8, d22, d4 + PF subges PF_CTL, PF_CTL, #0x10 + vmull.u8 q9, d22, d5 + PF ldrgeb DUMMY, [PF_SRC, SRC_STRIDE, lsl #src_bpp_shift]! + vmull.u8 q10, d22, d6 + PF ldrgeb DUMMY, [PF_DST, DST_STRIDE, lsl #dst_bpp_shift]! + vmull.u8 q11, d22, d7 +.endm + +generate_composite_function \ + pixman_composite_over_8888_8888_asm_neon, 32, 0, 32, \ + FLAG_DST_READWRITE | FLAG_DEINTERLEAVE_32BPP, \ + 8, /* number of pixels, processed in a single block */ \ + 5, /* prefetch distance */ \ + default_init, \ + default_cleanup, \ + pixman_composite_over_8888_8888_process_pixblock_head, \ + pixman_composite_over_8888_8888_process_pixblock_tail, \ + pixman_composite_over_8888_8888_process_pixblock_tail_head + +generate_composite_function_single_scanline \ + pixman_composite_scanline_over_asm_neon, 32, 0, 32, \ + FLAG_DST_READWRITE | FLAG_DEINTERLEAVE_32BPP, \ + 8, /* number of pixels, processed in a single block */ \ + default_init, \ + default_cleanup, \ + pixman_composite_over_8888_8888_process_pixblock_head, \ + pixman_composite_over_8888_8888_process_pixblock_tail, \ + pixman_composite_over_8888_8888_process_pixblock_tail_head + +/******************************************************************************/ + +.macro pixman_composite_over_n_8888_process_pixblock_head + /* deinterleaved source pixels in {d0, d1, d2, d3} */ + /* inverted alpha in {d24} */ + /* destination pixels in {d4, d5, d6, d7} */ + vmull.u8 q8, d24, d4 + vmull.u8 q9, d24, d5 + vmull.u8 q10, d24, d6 + vmull.u8 q11, d24, d7 +.endm + +.macro pixman_composite_over_n_8888_process_pixblock_tail + vrshr.u16 q14, q8, #8 + vrshr.u16 q15, q9, #8 + vrshr.u16 q2, q10, #8 + vrshr.u16 q3, q11, #8 + vraddhn.u16 d28, q14, q8 + vraddhn.u16 d29, q15, q9 + vraddhn.u16 d30, q2, q10 + vraddhn.u16 d31, q3, q11 + vqadd.u8 q14, q0, q14 + vqadd.u8 q15, q1, q15 +.endm + +.macro pixman_composite_over_n_8888_process_pixblock_tail_head + vrshr.u16 q14, q8, #8 + vrshr.u16 q15, q9, #8 + vrshr.u16 q2, q10, #8 + vrshr.u16 q3, q11, #8 + vraddhn.u16 d28, q14, q8 + vraddhn.u16 d29, q15, q9 + vraddhn.u16 d30, q2, q10 + vraddhn.u16 d31, q3, q11 + vld4.8 {d4, d5, d6, d7}, [DST_R, :128]! + vqadd.u8 q14, q0, q14 + PF add PF_X, PF_X, #8 + PF tst PF_CTL, #0x0F + PF addne PF_X, PF_X, #8 + PF subne PF_CTL, PF_CTL, #1 + vqadd.u8 q15, q1, q15 + PF cmp PF_X, ORIG_W + vmull.u8 q8, d24, d4 + PF pld, [PF_DST, PF_X, lsl #dst_bpp_shift] + vmull.u8 q9, d24, d5 + PF subge PF_X, PF_X, ORIG_W + vmull.u8 q10, d24, d6 + PF subges PF_CTL, PF_CTL, #0x10 + vmull.u8 q11, d24, d7 + PF ldrgeb DUMMY, [PF_DST, DST_STRIDE, lsl #dst_bpp_shift]! + vst4.8 {d28, d29, d30, d31}, [DST_W, :128]! +.endm + +.macro pixman_composite_over_n_8888_init + add DUMMY, sp, #ARGS_STACK_OFFSET + vld1.32 {d3[0]}, [DUMMY] + vdup.8 d0, d3[0] + vdup.8 d1, d3[1] + vdup.8 d2, d3[2] + vdup.8 d3, d3[3] + vmvn.8 d24, d3 /* get inverted alpha */ +.endm + +generate_composite_function \ + pixman_composite_over_n_8888_asm_neon, 0, 0, 32, \ + FLAG_DST_READWRITE | FLAG_DEINTERLEAVE_32BPP, \ + 8, /* number of pixels, processed in a single block */ \ + 5, /* prefetch distance */ \ + pixman_composite_over_n_8888_init, \ + default_cleanup, \ + pixman_composite_over_8888_8888_process_pixblock_head, \ + pixman_composite_over_8888_8888_process_pixblock_tail, \ + pixman_composite_over_n_8888_process_pixblock_tail_head + +/******************************************************************************/ + +.macro pixman_composite_src_n_8888_process_pixblock_head +.endm + +.macro pixman_composite_src_n_8888_process_pixblock_tail +.endm + +.macro pixman_composite_src_n_8888_process_pixblock_tail_head + vst1.32 {d0, d1, d2, d3}, [DST_W, :128]! +.endm + +.macro pixman_composite_src_n_8888_init + add DUMMY, sp, #ARGS_STACK_OFFSET + vld1.32 {d0[0]}, [DUMMY] + vsli.u64 d0, d0, #32 + vorr d1, d0, d0 + vorr q1, q0, q0 +.endm + +.macro pixman_composite_src_n_8888_cleanup +.endm + +generate_composite_function \ + pixman_composite_src_n_8888_asm_neon, 0, 0, 32, \ + FLAG_DST_WRITEONLY, \ + 8, /* number of pixels, processed in a single block */ \ + 0, /* prefetch distance */ \ + pixman_composite_src_n_8888_init, \ + pixman_composite_src_n_8888_cleanup, \ + pixman_composite_src_n_8888_process_pixblock_head, \ + pixman_composite_src_n_8888_process_pixblock_tail, \ + pixman_composite_src_n_8888_process_pixblock_tail_head, \ + 0, /* dst_w_basereg */ \ + 0, /* dst_r_basereg */ \ + 0, /* src_basereg */ \ + 0 /* mask_basereg */ + +/******************************************************************************/ + +.macro pixman_composite_src_8888_8888_process_pixblock_head +.endm + +.macro pixman_composite_src_8888_8888_process_pixblock_tail +.endm + +.macro pixman_composite_src_8888_8888_process_pixblock_tail_head + vst1.32 {d0, d1, d2, d3}, [DST_W, :128]! + fetch_src_pixblock + cache_preload 8, 8 +.endm + +generate_composite_function \ + pixman_composite_src_8888_8888_asm_neon, 32, 0, 32, \ + FLAG_DST_WRITEONLY, \ + 8, /* number of pixels, processed in a single block */ \ + 10, /* prefetch distance */ \ + default_init, \ + default_cleanup, \ + pixman_composite_src_8888_8888_process_pixblock_head, \ + pixman_composite_src_8888_8888_process_pixblock_tail, \ + pixman_composite_src_8888_8888_process_pixblock_tail_head, \ + 0, /* dst_w_basereg */ \ + 0, /* dst_r_basereg */ \ + 0, /* src_basereg */ \ + 0 /* mask_basereg */ + +/******************************************************************************/ diff --git a/TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arm-neon-asm.h b/TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arm-neon-asm.h new file mode 100755 index 000000000..6add220a1 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arm-neon-asm.h @@ -0,0 +1,1126 @@ +/* + * Copyright © 2009 Nokia Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Author: Siarhei Siamashka (siarhei.siamashka@nokia.com) + */ + +/* + * This file contains a macro ('generate_composite_function') which can + * construct 2D image processing functions, based on a common template. + * Any combinations of source, destination and mask images with 8bpp, + * 16bpp, 24bpp, 32bpp color formats are supported. + * + * This macro takes care of: + * - handling of leading and trailing unaligned pixels + * - doing most of the work related to L2 cache preload + * - encourages the use of software pipelining for better instructions + * scheduling + * + * The user of this macro has to provide some configuration parameters + * (bit depths for the images, prefetch distance, etc.) and a set of + * macros, which should implement basic code chunks responsible for + * pixels processing. See 'pixman-arm-neon-asm.S' file for the usage + * examples. + * + * TODO: + * - try overlapped pixel method (from Ian Rickards) when processing + * exactly two blocks of pixels + * - maybe add an option to do reverse scanline processing + */ + +/* + * Bit flags for 'generate_composite_function' macro which are used + * to tune generated functions behavior. + */ +.set FLAG_DST_WRITEONLY, 0 +.set FLAG_DST_READWRITE, 1 +.set FLAG_DEINTERLEAVE_32BPP, 2 + +/* + * Offset in stack where mask and source pointer/stride can be accessed + * from 'init' macro. This is useful for doing special handling for solid mask. + */ +.set ARGS_STACK_OFFSET, 40 + +/* + * Constants for selecting preferable prefetch type. + */ +.set PREFETCH_TYPE_NONE, 0 /* No prefetch at all */ +.set PREFETCH_TYPE_SIMPLE, 1 /* A simple, fixed-distance-ahead prefetch */ +.set PREFETCH_TYPE_ADVANCED, 2 /* Advanced fine-grained prefetch */ + +/* + * Definitions of supplementary pixld/pixst macros (for partial load/store of + * pixel data). + */ + +.macro pixldst1 op, elem_size, reg1, mem_operand, abits +.if abits > 0 + op&.&elem_size {d®1}, [&mem_operand&, :&abits&]! +.else + op&.&elem_size {d®1}, [&mem_operand&]! +.endif +.endm + +.macro pixldst2 op, elem_size, reg1, reg2, mem_operand, abits +.if abits > 0 + op&.&elem_size {d®1, d®2}, [&mem_operand&, :&abits&]! +.else + op&.&elem_size {d®1, d®2}, [&mem_operand&]! +.endif +.endm + +.macro pixldst4 op, elem_size, reg1, reg2, reg3, reg4, mem_operand, abits +.if abits > 0 + op&.&elem_size {d®1, d®2, d®3, d®4}, [&mem_operand&, :&abits&]! +.else + op&.&elem_size {d®1, d®2, d®3, d®4}, [&mem_operand&]! +.endif +.endm + +.macro pixldst0 op, elem_size, reg1, idx, mem_operand, abits + op&.&elem_size {d®1[idx]}, [&mem_operand&]! +.endm + +.macro pixldst3 op, elem_size, reg1, reg2, reg3, mem_operand + op&.&elem_size {d®1, d®2, d®3}, [&mem_operand&]! +.endm + +.macro pixldst30 op, elem_size, reg1, reg2, reg3, idx, mem_operand + op&.&elem_size {d®1[idx], d®2[idx], d®3[idx]}, [&mem_operand&]! +.endm + +.macro pixldst numbytes, op, elem_size, basereg, mem_operand, abits +.if numbytes == 32 + pixldst4 op, elem_size, %(basereg+4), %(basereg+5), \ + %(basereg+6), %(basereg+7), mem_operand, abits +.elseif numbytes == 16 + pixldst2 op, elem_size, %(basereg+2), %(basereg+3), mem_operand, abits +.elseif numbytes == 8 + pixldst1 op, elem_size, %(basereg+1), mem_operand, abits +.elseif numbytes == 4 + .if !RESPECT_STRICT_ALIGNMENT || (elem_size == 32) + pixldst0 op, 32, %(basereg+0), 1, mem_operand, abits + .elseif elem_size == 16 + pixldst0 op, 16, %(basereg+0), 2, mem_operand, abits + pixldst0 op, 16, %(basereg+0), 3, mem_operand, abits + .else + pixldst0 op, 8, %(basereg+0), 4, mem_operand, abits + pixldst0 op, 8, %(basereg+0), 5, mem_operand, abits + pixldst0 op, 8, %(basereg+0), 6, mem_operand, abits + pixldst0 op, 8, %(basereg+0), 7, mem_operand, abits + .endif +.elseif numbytes == 2 + .if !RESPECT_STRICT_ALIGNMENT || (elem_size == 16) + pixldst0 op, 16, %(basereg+0), 1, mem_operand, abits + .else + pixldst0 op, 8, %(basereg+0), 2, mem_operand, abits + pixldst0 op, 8, %(basereg+0), 3, mem_operand, abits + .endif +.elseif numbytes == 1 + pixldst0 op, 8, %(basereg+0), 1, mem_operand, abits +.else + .error "unsupported size: numbytes" +.endif +.endm + +.macro pixld numpix, bpp, basereg, mem_operand, abits=0 +.if bpp > 0 +.if (bpp == 32) && (numpix == 8) && (DEINTERLEAVE_32BPP_ENABLED != 0) + pixldst4 vld4, 8, %(basereg+4), %(basereg+5), \ + %(basereg+6), %(basereg+7), mem_operand, abits +.elseif (bpp == 24) && (numpix == 8) + pixldst3 vld3, 8, %(basereg+3), %(basereg+4), %(basereg+5), mem_operand +.elseif (bpp == 24) && (numpix == 4) + pixldst30 vld3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 4, mem_operand + pixldst30 vld3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 5, mem_operand + pixldst30 vld3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 6, mem_operand + pixldst30 vld3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 7, mem_operand +.elseif (bpp == 24) && (numpix == 2) + pixldst30 vld3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 2, mem_operand + pixldst30 vld3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 3, mem_operand +.elseif (bpp == 24) && (numpix == 1) + pixldst30 vld3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 1, mem_operand +.else + pixldst %(numpix * bpp / 8), vld1, %(bpp), basereg, mem_operand, abits +.endif +.endif +.endm + +.macro pixst numpix, bpp, basereg, mem_operand, abits=0 +.if bpp > 0 +.if (bpp == 32) && (numpix == 8) && (DEINTERLEAVE_32BPP_ENABLED != 0) + pixldst4 vst4, 8, %(basereg+4), %(basereg+5), \ + %(basereg+6), %(basereg+7), mem_operand, abits +.elseif (bpp == 24) && (numpix == 8) + pixldst3 vst3, 8, %(basereg+3), %(basereg+4), %(basereg+5), mem_operand +.elseif (bpp == 24) && (numpix == 4) + pixldst30 vst3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 4, mem_operand + pixldst30 vst3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 5, mem_operand + pixldst30 vst3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 6, mem_operand + pixldst30 vst3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 7, mem_operand +.elseif (bpp == 24) && (numpix == 2) + pixldst30 vst3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 2, mem_operand + pixldst30 vst3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 3, mem_operand +.elseif (bpp == 24) && (numpix == 1) + pixldst30 vst3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 1, mem_operand +.else + pixldst %(numpix * bpp / 8), vst1, %(bpp), basereg, mem_operand, abits +.endif +.endif +.endm + +.macro pixld_a numpix, bpp, basereg, mem_operand +.if (bpp * numpix) <= 128 + pixld numpix, bpp, basereg, mem_operand, %(bpp * numpix) +.else + pixld numpix, bpp, basereg, mem_operand, 128 +.endif +.endm + +.macro pixst_a numpix, bpp, basereg, mem_operand +.if (bpp * numpix) <= 128 + pixst numpix, bpp, basereg, mem_operand, %(bpp * numpix) +.else + pixst numpix, bpp, basereg, mem_operand, 128 +.endif +.endm + +/* + * Pixel fetcher for nearest scaling (needs TMP1, TMP2, VX, UNIT_X register + * aliases to be defined) + */ +.macro pixld1_s elem_size, reg1, mem_operand +.if elem_size == 16 + mov TMP1, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP1, mem_operand, TMP1, asl #1 + mov TMP2, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP2, mem_operand, TMP2, asl #1 + vld1.16 {d®1&[0]}, [TMP1, :16] + mov TMP1, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP1, mem_operand, TMP1, asl #1 + vld1.16 {d®1&[1]}, [TMP2, :16] + mov TMP2, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP2, mem_operand, TMP2, asl #1 + vld1.16 {d®1&[2]}, [TMP1, :16] + vld1.16 {d®1&[3]}, [TMP2, :16] +.elseif elem_size == 32 + mov TMP1, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP1, mem_operand, TMP1, asl #2 + mov TMP2, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP2, mem_operand, TMP2, asl #2 + vld1.32 {d®1&[0]}, [TMP1, :32] + vld1.32 {d®1&[1]}, [TMP2, :32] +.else + .error "unsupported" +.endif +.endm + +.macro pixld2_s elem_size, reg1, reg2, mem_operand +.if 0 /* elem_size == 32 */ + mov TMP1, VX, asr #16 + add VX, VX, UNIT_X, asl #1 + add TMP1, mem_operand, TMP1, asl #2 + mov TMP2, VX, asr #16 + sub VX, VX, UNIT_X + add TMP2, mem_operand, TMP2, asl #2 + vld1.32 {d®1&[0]}, [TMP1, :32] + mov TMP1, VX, asr #16 + add VX, VX, UNIT_X, asl #1 + add TMP1, mem_operand, TMP1, asl #2 + vld1.32 {d®2&[0]}, [TMP2, :32] + mov TMP2, VX, asr #16 + add VX, VX, UNIT_X + add TMP2, mem_operand, TMP2, asl #2 + vld1.32 {d®1&[1]}, [TMP1, :32] + vld1.32 {d®2&[1]}, [TMP2, :32] +.else + pixld1_s elem_size, reg1, mem_operand + pixld1_s elem_size, reg2, mem_operand +.endif +.endm + +.macro pixld0_s elem_size, reg1, idx, mem_operand +.if elem_size == 16 + mov TMP1, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP1, mem_operand, TMP1, asl #1 + vld1.16 {d®1&[idx]}, [TMP1, :16] +.elseif elem_size == 32 + mov TMP1, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP1, mem_operand, TMP1, asl #2 + vld1.32 {d®1&[idx]}, [TMP1, :32] +.endif +.endm + +.macro pixld_s_internal numbytes, elem_size, basereg, mem_operand +.if numbytes == 32 + pixld2_s elem_size, %(basereg+4), %(basereg+5), mem_operand + pixld2_s elem_size, %(basereg+6), %(basereg+7), mem_operand + pixdeinterleave elem_size, %(basereg+4) +.elseif numbytes == 16 + pixld2_s elem_size, %(basereg+2), %(basereg+3), mem_operand +.elseif numbytes == 8 + pixld1_s elem_size, %(basereg+1), mem_operand +.elseif numbytes == 4 + .if elem_size == 32 + pixld0_s elem_size, %(basereg+0), 1, mem_operand + .elseif elem_size == 16 + pixld0_s elem_size, %(basereg+0), 2, mem_operand + pixld0_s elem_size, %(basereg+0), 3, mem_operand + .else + pixld0_s elem_size, %(basereg+0), 4, mem_operand + pixld0_s elem_size, %(basereg+0), 5, mem_operand + pixld0_s elem_size, %(basereg+0), 6, mem_operand + pixld0_s elem_size, %(basereg+0), 7, mem_operand + .endif +.elseif numbytes == 2 + .if elem_size == 16 + pixld0_s elem_size, %(basereg+0), 1, mem_operand + .else + pixld0_s elem_size, %(basereg+0), 2, mem_operand + pixld0_s elem_size, %(basereg+0), 3, mem_operand + .endif +.elseif numbytes == 1 + pixld0_s elem_size, %(basereg+0), 1, mem_operand +.else + .error "unsupported size: numbytes" +.endif +.endm + +.macro pixld_s numpix, bpp, basereg, mem_operand +.if bpp > 0 + pixld_s_internal %(numpix * bpp / 8), %(bpp), basereg, mem_operand +.endif +.endm + +.macro vuzp8 reg1, reg2 + vuzp.8 d®1, d®2 +.endm + +.macro vzip8 reg1, reg2 + vzip.8 d®1, d®2 +.endm + +/* deinterleave B, G, R, A channels for eight 32bpp pixels in 4 registers */ +.macro pixdeinterleave bpp, basereg +.if (bpp == 32) && (DEINTERLEAVE_32BPP_ENABLED != 0) + vuzp8 %(basereg+0), %(basereg+1) + vuzp8 %(basereg+2), %(basereg+3) + vuzp8 %(basereg+1), %(basereg+3) + vuzp8 %(basereg+0), %(basereg+2) +.endif +.endm + +/* interleave B, G, R, A channels for eight 32bpp pixels in 4 registers */ +.macro pixinterleave bpp, basereg +.if (bpp == 32) && (DEINTERLEAVE_32BPP_ENABLED != 0) + vzip8 %(basereg+0), %(basereg+2) + vzip8 %(basereg+1), %(basereg+3) + vzip8 %(basereg+2), %(basereg+3) + vzip8 %(basereg+0), %(basereg+1) +.endif +.endm + +/* + * This is a macro for implementing cache preload. The main idea is that + * cache preload logic is mostly independent from the rest of pixels + * processing code. It starts at the top left pixel and moves forward + * across pixels and can jump across scanlines. Prefetch distance is + * handled in an 'incremental' way: it starts from 0 and advances to the + * optimal distance over time. After reaching optimal prefetch distance, + * it is kept constant. There are some checks which prevent prefetching + * unneeded pixel lines below the image (but it still can prefetch a bit + * more data on the right side of the image - not a big issue and may + * be actually helpful when rendering text glyphs). Additional trick is + * the use of LDR instruction for prefetch instead of PLD when moving to + * the next line, the point is that we have a high chance of getting TLB + * miss in this case, and PLD would be useless. + * + * This sounds like it may introduce a noticeable overhead (when working with + * fully cached data). But in reality, due to having a separate pipeline and + * instruction queue for NEON unit in ARM Cortex-A8, normal ARM code can + * execute simultaneously with NEON and be completely shadowed by it. Thus + * we get no performance overhead at all (*). This looks like a very nice + * feature of Cortex-A8, if used wisely. We don't have a hardware prefetcher, + * but still can implement some rather advanced prefetch logic in software + * for almost zero cost! + * + * (*) The overhead of the prefetcher is visible when running some trivial + * pixels processing like simple copy. Anyway, having prefetch is a must + * when working with the graphics data. + */ +.macro PF a, x:vararg +.if (PREFETCH_TYPE_CURRENT == PREFETCH_TYPE_ADVANCED) + a x +.endif +.endm + +.macro cache_preload std_increment, boost_increment +.if (src_bpp_shift >= 0) || (dst_r_bpp != 0) || (mask_bpp_shift >= 0) +.if regs_shortage + PF ldr ORIG_W, [sp] /* If we are short on regs, ORIG_W is kept on stack */ +.endif +.if std_increment != 0 + PF add PF_X, PF_X, #std_increment +.endif + PF tst PF_CTL, #0xF + PF addne PF_X, PF_X, #boost_increment + PF subne PF_CTL, PF_CTL, #1 + PF cmp PF_X, ORIG_W +.if src_bpp_shift >= 0 + PF pld, [PF_SRC, PF_X, lsl #src_bpp_shift] +.endif +.if dst_r_bpp != 0 + PF pld, [PF_DST, PF_X, lsl #dst_bpp_shift] +.endif +.if mask_bpp_shift >= 0 + PF pld, [PF_MASK, PF_X, lsl #mask_bpp_shift] +.endif + PF subge PF_X, PF_X, ORIG_W + PF subges PF_CTL, PF_CTL, #0x10 +.if src_bpp_shift >= 0 + PF ldrgeb DUMMY, [PF_SRC, SRC_STRIDE, lsl #src_bpp_shift]! +.endif +.if dst_r_bpp != 0 + PF ldrgeb DUMMY, [PF_DST, DST_STRIDE, lsl #dst_bpp_shift]! +.endif +.if mask_bpp_shift >= 0 + PF ldrgeb DUMMY, [PF_MASK, MASK_STRIDE, lsl #mask_bpp_shift]! +.endif +.endif +.endm + +.macro cache_preload_simple +.if (PREFETCH_TYPE_CURRENT == PREFETCH_TYPE_SIMPLE) +.if src_bpp > 0 + pld [SRC, #(PREFETCH_DISTANCE_SIMPLE * src_bpp / 8)] +.endif +.if dst_r_bpp > 0 + pld [DST_R, #(PREFETCH_DISTANCE_SIMPLE * dst_r_bpp / 8)] +.endif +.if mask_bpp > 0 + pld [MASK, #(PREFETCH_DISTANCE_SIMPLE * mask_bpp / 8)] +.endif +.endif +.endm + +.macro fetch_mask_pixblock + pixld pixblock_size, mask_bpp, \ + (mask_basereg - pixblock_size * mask_bpp / 64), MASK +.endm + +/* + * Macro which is used to process leading pixels until destination + * pointer is properly aligned (at 16 bytes boundary). When destination + * buffer uses 16bpp format, this is unnecessary, or even pointless. + */ +.macro ensure_destination_ptr_alignment process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head +.if dst_w_bpp != 24 + tst DST_R, #0xF + beq 2f + +.irp lowbit, 1, 2, 4, 8, 16 +local skip1 +.if (dst_w_bpp <= (lowbit * 8)) && ((lowbit * 8) < (pixblock_size * dst_w_bpp)) +.if lowbit < 16 /* we don't need more than 16-byte alignment */ + tst DST_R, #lowbit + beq 1f +.endif + pixld_src (lowbit * 8 / dst_w_bpp), src_bpp, src_basereg, SRC + pixld (lowbit * 8 / dst_w_bpp), mask_bpp, mask_basereg, MASK +.if dst_r_bpp > 0 + pixld_a (lowbit * 8 / dst_r_bpp), dst_r_bpp, dst_r_basereg, DST_R +.else + add DST_R, DST_R, #lowbit +.endif + PF add PF_X, PF_X, #(lowbit * 8 / dst_w_bpp) + sub W, W, #(lowbit * 8 / dst_w_bpp) +1: +.endif +.endr + pixdeinterleave src_bpp, src_basereg + pixdeinterleave mask_bpp, mask_basereg + pixdeinterleave dst_r_bpp, dst_r_basereg + + process_pixblock_head + cache_preload 0, pixblock_size + cache_preload_simple + process_pixblock_tail + + pixinterleave dst_w_bpp, dst_w_basereg +.irp lowbit, 1, 2, 4, 8, 16 +.if (dst_w_bpp <= (lowbit * 8)) && ((lowbit * 8) < (pixblock_size * dst_w_bpp)) +.if lowbit < 16 /* we don't need more than 16-byte alignment */ + tst DST_W, #lowbit + beq 1f +.endif + pixst_a (lowbit * 8 / dst_w_bpp), dst_w_bpp, dst_w_basereg, DST_W +1: +.endif +.endr +.endif +2: +.endm + +/* + * Special code for processing up to (pixblock_size - 1) remaining + * trailing pixels. As SIMD processing performs operation on + * pixblock_size pixels, anything smaller than this has to be loaded + * and stored in a special way. Loading and storing of pixel data is + * performed in such a way that we fill some 'slots' in the NEON + * registers (some slots naturally are unused), then perform compositing + * operation as usual. In the end, the data is taken from these 'slots' + * and saved to memory. + * + * cache_preload_flag - allows to suppress prefetch if + * set to 0 + * dst_aligned_flag - selects whether destination buffer + * is aligned + */ +.macro process_trailing_pixels cache_preload_flag, \ + dst_aligned_flag, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + tst W, #(pixblock_size - 1) + beq 2f +.irp chunk_size, 16, 8, 4, 2, 1 +.if pixblock_size > chunk_size + tst W, #chunk_size + beq 1f + pixld_src chunk_size, src_bpp, src_basereg, SRC + pixld chunk_size, mask_bpp, mask_basereg, MASK +.if dst_aligned_flag != 0 + pixld_a chunk_size, dst_r_bpp, dst_r_basereg, DST_R +.else + pixld chunk_size, dst_r_bpp, dst_r_basereg, DST_R +.endif +.if cache_preload_flag != 0 + PF add PF_X, PF_X, #chunk_size +.endif +1: +.endif +.endr + pixdeinterleave src_bpp, src_basereg + pixdeinterleave mask_bpp, mask_basereg + pixdeinterleave dst_r_bpp, dst_r_basereg + + process_pixblock_head +.if cache_preload_flag != 0 + cache_preload 0, pixblock_size + cache_preload_simple +.endif + process_pixblock_tail + pixinterleave dst_w_bpp, dst_w_basereg +.irp chunk_size, 16, 8, 4, 2, 1 +.if pixblock_size > chunk_size + tst W, #chunk_size + beq 1f +.if dst_aligned_flag != 0 + pixst_a chunk_size, dst_w_bpp, dst_w_basereg, DST_W +.else + pixst chunk_size, dst_w_bpp, dst_w_basereg, DST_W +.endif +1: +.endif +.endr +2: +.endm + +/* + * Macro, which performs all the needed operations to switch to the next + * scanline and start the next loop iteration unless all the scanlines + * are already processed. + */ +.macro advance_to_next_scanline start_of_loop_label +.if regs_shortage + ldrd W, [sp] /* load W and H (width and height) from stack */ +.else + mov W, ORIG_W +.endif + add DST_W, DST_W, DST_STRIDE, lsl #dst_bpp_shift +.if src_bpp != 0 + add SRC, SRC, SRC_STRIDE, lsl #src_bpp_shift +.endif +.if mask_bpp != 0 + add MASK, MASK, MASK_STRIDE, lsl #mask_bpp_shift +.endif +.if (dst_w_bpp != 24) + sub DST_W, DST_W, W, lsl #dst_bpp_shift +.endif +.if (src_bpp != 24) && (src_bpp != 0) + sub SRC, SRC, W, lsl #src_bpp_shift +.endif +.if (mask_bpp != 24) && (mask_bpp != 0) + sub MASK, MASK, W, lsl #mask_bpp_shift +.endif + subs H, H, #1 + mov DST_R, DST_W +.if regs_shortage + str H, [sp, #4] /* save updated height to stack */ +.endif + bge start_of_loop_label +.endm + +/* + * Registers are allocated in the following way by default: + * d0, d1, d2, d3 - reserved for loading source pixel data + * d4, d5, d6, d7 - reserved for loading destination pixel data + * d24, d25, d26, d27 - reserved for loading mask pixel data + * d28, d29, d30, d31 - final destination pixel data for writeback to memory + */ +.macro generate_composite_function fname, \ + src_bpp_, \ + mask_bpp_, \ + dst_w_bpp_, \ + flags, \ + pixblock_size_, \ + prefetch_distance, \ + init, \ + cleanup, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head, \ + dst_w_basereg_ = 28, \ + dst_r_basereg_ = 4, \ + src_basereg_ = 0, \ + mask_basereg_ = 24 + + pixman_asm_function fname + + push {r4-r12, lr} /* save all registers */ + +/* + * Select prefetch type for this function. If prefetch distance is + * set to 0 or one of the color formats is 24bpp, SIMPLE prefetch + * has to be used instead of ADVANCED. + */ + .set PREFETCH_TYPE_CURRENT, PREFETCH_TYPE_DEFAULT +.if prefetch_distance == 0 + .set PREFETCH_TYPE_CURRENT, PREFETCH_TYPE_NONE +.elseif (PREFETCH_TYPE_CURRENT > PREFETCH_TYPE_SIMPLE) && \ + ((src_bpp_ == 24) || (mask_bpp_ == 24) || (dst_w_bpp_ == 24)) + .set PREFETCH_TYPE_CURRENT, PREFETCH_TYPE_SIMPLE +.endif + +/* + * Make some macro arguments globally visible and accessible + * from other macros + */ + .set src_bpp, src_bpp_ + .set mask_bpp, mask_bpp_ + .set dst_w_bpp, dst_w_bpp_ + .set pixblock_size, pixblock_size_ + .set dst_w_basereg, dst_w_basereg_ + .set dst_r_basereg, dst_r_basereg_ + .set src_basereg, src_basereg_ + .set mask_basereg, mask_basereg_ + + .macro pixld_src x:vararg + pixld x + .endm + .macro fetch_src_pixblock + pixld_src pixblock_size, src_bpp, \ + (src_basereg - pixblock_size * src_bpp / 64), SRC + .endm +/* + * Assign symbolic names to registers + */ + W .req r0 /* width (is updated during processing) */ + H .req r1 /* height (is updated during processing) */ + DST_W .req r2 /* destination buffer pointer for writes */ + DST_STRIDE .req r3 /* destination image stride */ + SRC .req r4 /* source buffer pointer */ + SRC_STRIDE .req r5 /* source image stride */ + DST_R .req r6 /* destination buffer pointer for reads */ + + MASK .req r7 /* mask pointer */ + MASK_STRIDE .req r8 /* mask stride */ + + PF_CTL .req r9 /* combined lines counter and prefetch */ + /* distance increment counter */ + PF_X .req r10 /* pixel index in a scanline for current */ + /* pretetch position */ + PF_SRC .req r11 /* pointer to source scanline start */ + /* for prefetch purposes */ + PF_DST .req r12 /* pointer to destination scanline start */ + /* for prefetch purposes */ + PF_MASK .req r14 /* pointer to mask scanline start */ + /* for prefetch purposes */ +/* + * Check whether we have enough registers for all the local variables. + * If we don't have enough registers, original width and height are + * kept on top of stack (and 'regs_shortage' variable is set to indicate + * this for the rest of code). Even if there are enough registers, the + * allocation scheme may be a bit different depending on whether source + * or mask is not used. + */ +.if (PREFETCH_TYPE_CURRENT < PREFETCH_TYPE_ADVANCED) + ORIG_W .req r10 /* saved original width */ + DUMMY .req r12 /* temporary register */ + .set regs_shortage, 0 +.elseif mask_bpp == 0 + ORIG_W .req r7 /* saved original width */ + DUMMY .req r8 /* temporary register */ + .set regs_shortage, 0 +.elseif src_bpp == 0 + ORIG_W .req r4 /* saved original width */ + DUMMY .req r5 /* temporary register */ + .set regs_shortage, 0 +.else + ORIG_W .req r1 /* saved original width */ + DUMMY .req r1 /* temporary register */ + .set regs_shortage, 1 +.endif + + .set mask_bpp_shift, -1 +.if src_bpp == 32 + .set src_bpp_shift, 2 +.elseif src_bpp == 24 + .set src_bpp_shift, 0 +.elseif src_bpp == 16 + .set src_bpp_shift, 1 +.elseif src_bpp == 8 + .set src_bpp_shift, 0 +.elseif src_bpp == 0 + .set src_bpp_shift, -1 +.else + .error "requested src bpp (src_bpp) is not supported" +.endif +.if mask_bpp == 32 + .set mask_bpp_shift, 2 +.elseif mask_bpp == 24 + .set mask_bpp_shift, 0 +.elseif mask_bpp == 8 + .set mask_bpp_shift, 0 +.elseif mask_bpp == 0 + .set mask_bpp_shift, -1 +.else + .error "requested mask bpp (mask_bpp) is not supported" +.endif +.if dst_w_bpp == 32 + .set dst_bpp_shift, 2 +.elseif dst_w_bpp == 24 + .set dst_bpp_shift, 0 +.elseif dst_w_bpp == 16 + .set dst_bpp_shift, 1 +.elseif dst_w_bpp == 8 + .set dst_bpp_shift, 0 +.else + .error "requested dst bpp (dst_w_bpp) is not supported" +.endif + +.if (((flags) & FLAG_DST_READWRITE) != 0) + .set dst_r_bpp, dst_w_bpp +.else + .set dst_r_bpp, 0 +.endif +.if (((flags) & FLAG_DEINTERLEAVE_32BPP) != 0) + .set DEINTERLEAVE_32BPP_ENABLED, 1 +.else + .set DEINTERLEAVE_32BPP_ENABLED, 0 +.endif + +.if prefetch_distance < 0 || prefetch_distance > 15 + .error "invalid prefetch distance (prefetch_distance)" +.endif + +.if src_bpp > 0 + ldr SRC, [sp, #40] +.endif +.if mask_bpp > 0 + ldr MASK, [sp, #48] +.endif + PF mov PF_X, #0 +.if src_bpp > 0 + ldr SRC_STRIDE, [sp, #44] +.endif +.if mask_bpp > 0 + ldr MASK_STRIDE, [sp, #52] +.endif + mov DST_R, DST_W + +.if src_bpp == 24 + sub SRC_STRIDE, SRC_STRIDE, W + sub SRC_STRIDE, SRC_STRIDE, W, lsl #1 +.endif +.if mask_bpp == 24 + sub MASK_STRIDE, MASK_STRIDE, W + sub MASK_STRIDE, MASK_STRIDE, W, lsl #1 +.endif +.if dst_w_bpp == 24 + sub DST_STRIDE, DST_STRIDE, W + sub DST_STRIDE, DST_STRIDE, W, lsl #1 +.endif + +/* + * Setup advanced prefetcher initial state + */ + PF mov PF_SRC, SRC + PF mov PF_DST, DST_R + PF mov PF_MASK, MASK + /* PF_CTL = prefetch_distance | ((h - 1) << 4) */ + PF mov PF_CTL, H, lsl #4 + PF add PF_CTL, #(prefetch_distance - 0x10) + + init +.if regs_shortage + push {r0, r1} +.endif + subs H, H, #1 +.if regs_shortage + str H, [sp, #4] /* save updated height to stack */ +.else + mov ORIG_W, W +.endif + blt 9f + cmp W, #(pixblock_size * 2) + blt 8f +/* + * This is the start of the pipelined loop, which if optimized for + * long scanlines + */ +0: + ensure_destination_ptr_alignment process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + + /* Implement "head (tail_head) ... (tail_head) tail" loop pattern */ + pixld_a pixblock_size, dst_r_bpp, \ + (dst_r_basereg - pixblock_size * dst_r_bpp / 64), DST_R + fetch_src_pixblock + pixld pixblock_size, mask_bpp, \ + (mask_basereg - pixblock_size * mask_bpp / 64), MASK + PF add PF_X, PF_X, #pixblock_size + process_pixblock_head + cache_preload 0, pixblock_size + cache_preload_simple + subs W, W, #(pixblock_size * 2) + blt 2f +1: + process_pixblock_tail_head + cache_preload_simple + subs W, W, #pixblock_size + bge 1b +2: + process_pixblock_tail + pixst_a pixblock_size, dst_w_bpp, \ + (dst_w_basereg - pixblock_size * dst_w_bpp / 64), DST_W + + /* Process the remaining trailing pixels in the scanline */ + process_trailing_pixels 1, 1, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + advance_to_next_scanline 0b + +.if regs_shortage + pop {r0, r1} +.endif + cleanup + pop {r4-r12, pc} /* exit */ +/* + * This is the start of the loop, designed to process images with small width + * (less than pixblock_size * 2 pixels). In this case neither pipelining + * nor prefetch are used. + */ +8: + /* Process exactly pixblock_size pixels if needed */ + tst W, #pixblock_size + beq 1f + pixld pixblock_size, dst_r_bpp, \ + (dst_r_basereg - pixblock_size * dst_r_bpp / 64), DST_R + fetch_src_pixblock + pixld pixblock_size, mask_bpp, \ + (mask_basereg - pixblock_size * mask_bpp / 64), MASK + process_pixblock_head + process_pixblock_tail + pixst pixblock_size, dst_w_bpp, \ + (dst_w_basereg - pixblock_size * dst_w_bpp / 64), DST_W +1: + /* Process the remaining trailing pixels in the scanline */ + process_trailing_pixels 0, 0, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + advance_to_next_scanline 8b +9: +.if regs_shortage + pop {r0, r1} +.endif + cleanup + pop {r4-r12, pc} /* exit */ + + .purgem fetch_src_pixblock + .purgem pixld_src + + .unreq SRC + .unreq MASK + .unreq DST_R + .unreq DST_W + .unreq ORIG_W + .unreq W + .unreq H + .unreq SRC_STRIDE + .unreq DST_STRIDE + .unreq MASK_STRIDE + .unreq PF_CTL + .unreq PF_X + .unreq PF_SRC + .unreq PF_DST + .unreq PF_MASK + .unreq DUMMY + .endfunc +.endm + +/* + * A simplified variant of function generation template for a single + * scanline processing (for implementing pixman combine functions) + */ +.macro generate_composite_function_scanline use_nearest_scaling, \ + fname, \ + src_bpp_, \ + mask_bpp_, \ + dst_w_bpp_, \ + flags, \ + pixblock_size_, \ + init, \ + cleanup, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head, \ + dst_w_basereg_ = 28, \ + dst_r_basereg_ = 4, \ + src_basereg_ = 0, \ + mask_basereg_ = 24 + + pixman_asm_function fname + + .set PREFETCH_TYPE_CURRENT, PREFETCH_TYPE_NONE +/* + * Make some macro arguments globally visible and accessible + * from other macros + */ + .set src_bpp, src_bpp_ + .set mask_bpp, mask_bpp_ + .set dst_w_bpp, dst_w_bpp_ + .set pixblock_size, pixblock_size_ + .set dst_w_basereg, dst_w_basereg_ + .set dst_r_basereg, dst_r_basereg_ + .set src_basereg, src_basereg_ + .set mask_basereg, mask_basereg_ + +.if use_nearest_scaling != 0 + /* + * Assign symbolic names to registers for nearest scaling + */ + W .req r0 + DST_W .req r1 + SRC .req r2 + VX .req r3 + UNIT_X .req ip + MASK .req lr + TMP1 .req r4 + TMP2 .req r5 + DST_R .req r6 + SRC_WIDTH_FIXED .req r7 + + .macro pixld_src x:vararg + pixld_s x + .endm + + ldr UNIT_X, [sp] + push {r4-r8, lr} + ldr SRC_WIDTH_FIXED, [sp, #(24 + 4)] + .if mask_bpp != 0 + ldr MASK, [sp, #(24 + 8)] + .endif +.else + /* + * Assign symbolic names to registers + */ + W .req r0 /* width (is updated during processing) */ + DST_W .req r1 /* destination buffer pointer for writes */ + SRC .req r2 /* source buffer pointer */ + DST_R .req ip /* destination buffer pointer for reads */ + MASK .req r3 /* mask pointer */ + + .macro pixld_src x:vararg + pixld x + .endm +.endif + +.if (((flags) & FLAG_DST_READWRITE) != 0) + .set dst_r_bpp, dst_w_bpp +.else + .set dst_r_bpp, 0 +.endif +.if (((flags) & FLAG_DEINTERLEAVE_32BPP) != 0) + .set DEINTERLEAVE_32BPP_ENABLED, 1 +.else + .set DEINTERLEAVE_32BPP_ENABLED, 0 +.endif + + .macro fetch_src_pixblock + pixld_src pixblock_size, src_bpp, \ + (src_basereg - pixblock_size * src_bpp / 64), SRC + .endm + + init + mov DST_R, DST_W + + cmp W, #pixblock_size + blt 8f + + ensure_destination_ptr_alignment process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + + subs W, W, #pixblock_size + blt 7f + + /* Implement "head (tail_head) ... (tail_head) tail" loop pattern */ + pixld_a pixblock_size, dst_r_bpp, \ + (dst_r_basereg - pixblock_size * dst_r_bpp / 64), DST_R + fetch_src_pixblock + pixld pixblock_size, mask_bpp, \ + (mask_basereg - pixblock_size * mask_bpp / 64), MASK + process_pixblock_head + subs W, W, #pixblock_size + blt 2f +1: + process_pixblock_tail_head + subs W, W, #pixblock_size + bge 1b +2: + process_pixblock_tail + pixst_a pixblock_size, dst_w_bpp, \ + (dst_w_basereg - pixblock_size * dst_w_bpp / 64), DST_W +7: + /* Process the remaining trailing pixels in the scanline (dst aligned) */ + process_trailing_pixels 0, 1, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + + cleanup +.if use_nearest_scaling != 0 + pop {r4-r8, pc} /* exit */ +.else + bx lr /* exit */ +.endif +8: + /* Process the remaining trailing pixels in the scanline (dst unaligned) */ + process_trailing_pixels 0, 0, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + + cleanup + +.if use_nearest_scaling != 0 + pop {r4-r8, pc} /* exit */ + + .unreq DST_R + .unreq SRC + .unreq W + .unreq VX + .unreq UNIT_X + .unreq TMP1 + .unreq TMP2 + .unreq DST_W + .unreq MASK + .unreq SRC_WIDTH_FIXED + +.else + bx lr /* exit */ + + .unreq SRC + .unreq MASK + .unreq DST_R + .unreq DST_W + .unreq W +.endif + + .purgem fetch_src_pixblock + .purgem pixld_src + + .endfunc +.endm + +.macro generate_composite_function_single_scanline x:vararg + generate_composite_function_scanline 0, x +.endm + +.macro generate_composite_function_nearest_scanline x:vararg + generate_composite_function_scanline 1, x +.endm + +/* Default prologue/epilogue, nothing special needs to be done */ + +.macro default_init +.endm + +.macro default_cleanup +.endm + +/* + * Prologue/epilogue variant which additionally saves/restores d8-d15 + * registers (they need to be saved/restored by callee according to ABI). + * This is required if the code needs to use all the NEON registers. + */ + +.macro default_init_need_all_regs + vpush {d8-d15} +.endm + +.macro default_cleanup_need_all_regs + vpop {d8-d15} +.endm + +/******************************************************************************/ diff --git a/TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arma64-neon-asm.S b/TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arma64-neon-asm.S new file mode 100644 index 000000000..7705eee0a --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arma64-neon-asm.S @@ -0,0 +1,418 @@ +/* + * Copyright © 2009 Nokia Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Author: Siarhei Siamashka (siarhei.siamashka@nokia.com) + */ + +/* + * This file contains implementations of NEON optimized pixel processing + * functions. There is no full and detailed tutorial, but some functions + * (those which are exposing some new or interesting features) are + * extensively commented and can be used as examples. + * + * You may want to have a look at the comments for following functions: + * - pixman_composite_over_8888_0565_asm_neon + * - pixman_composite_over_n_8_0565_asm_neon + */ + +/* Prevent the stack from becoming executable for no reason... */ +#if defined(__linux__) && defined(__ELF__) +.section .note.GNU-stack,"",%progbits +#endif + +.text +.arch armv8-a +.altmacro +.p2align 2 + +/* Supplementary macro for setting function attributes */ +.macro pixman_asm_function fname +.func fname +.global fname +#ifdef __ELF__ +.hidden fname +.type fname, %function +#endif +fname: +.endm + +#include "pixman-arma64-neon-asm.h" + +/* Global configuration options and preferences */ + +/* + * The code can optionally make use of unaligned memory accesses to improve + * performance of handling leading/trailing pixels for each scanline. + * Configuration variable RESPECT_STRICT_ALIGNMENT can be set to 0 for + * example in linux if unaligned memory accesses are not configured to + * generate.exceptions. + */ +.set RESPECT_STRICT_ALIGNMENT, 1 + +/* + * Set default prefetch type. There is a choice between the following options: + * + * PREFETCH_TYPE_NONE (may be useful for the ARM cores where PLD is set to work + * as NOP to workaround some HW bugs or for whatever other reason) + * + * PREFETCH_TYPE_SIMPLE (may be useful for simple single-issue ARM cores where + * advanced prefetch intruduces heavy overhead) + * + * PREFETCH_TYPE_ADVANCED (useful for superscalar cores such as ARM Cortex-A8 + * which can run ARM and NEON instructions simultaneously so that extra ARM + * instructions do not add (many) extra cycles, but improve prefetch efficiency) + * + * Note: some types of function can't support advanced prefetch and fallback + * to simple one (those which handle 24bpp pixels) + */ +.set PREFETCH_TYPE_DEFAULT, PREFETCH_TYPE_ADVANCED + +/* Prefetch distance in pixels for simple prefetch */ +.set PREFETCH_DISTANCE_SIMPLE, 64 + +/* + * Implementation of pixman_composite_over_8888_0565_asm_neon + * + * This function takes a8r8g8b8 source buffer, r5g6b5 destination buffer and + * performs OVER compositing operation. Function fast_composite_over_8888_0565 + * from pixman-fast-path.c does the same in C and can be used as a reference. + * + * First we need to have some NEON assembly code which can do the actual + * operation on the pixels and provide it to the template macro. + * + * Template macro quite conveniently takes care of emitting all the necessary + * code for memory reading and writing (including quite tricky cases of + * handling unaligned leading/trailing pixels), so we only need to deal with + * the data in NEON registers. + * + * NEON registers allocation in general is recommented to be the following: + * v0, v1, v2, v3 - contain loaded source pixel data + * v4, v5, v6, v7 - contain loaded destination pixels (if they are needed) + * v24, v25, v26, v27 - contain loading mask pixel data (if mask is used) + * v28, v29, v30, v31 - place for storing the result (destination pixels) + * + * As can be seen above, four 64-bit NEON registers are used for keeping + * intermediate pixel data and up to 8 pixels can be processed in one step + * for 32bpp formats (16 pixels for 16bpp, 32 pixels for 8bpp). + * + * This particular function uses the following registers allocation: + * v0, v1, v2, v3 - contain loaded source pixel data + * v4, v5 - contain loaded destination pixels (they are needed) + * v28, v29 - place for storing the result (destination pixels) + */ + +/* + * Step one. We need to have some code to do some arithmetics on pixel data. + * This is implemented as a pair of macros: '*_head' and '*_tail'. When used + * back-to-back, they take pixel data from {v0, v1, v2, v3} and {v4, v5}, + * perform all the needed calculations and write the result to {v28, v29}. + * The rationale for having two macros and not just one will be explained + * later. In practice, any single monolitic function which does the work can + * be split into two parts in any arbitrary way without affecting correctness. + * + * There is one special trick here too. Common template macro can optionally + * make our life a bit easier by doing R, G, B, A color components + * deinterleaving for 32bpp pixel formats (and this feature is used in + * 'pixman_composite_over_8888_0565_asm_neon' function). So it means that + * instead of having 8 packed pixels in {v0, v1, v2, v3} registers, we + * actually use v0 register for blue channel (a vector of eight 8-bit + * values), v1 register for green, v2 for red and v3 for alpha. This + * simple conversion can be also done with a few NEON instructions: + * + * Packed to planar conversion: // vuzp8 is a wrapper macro + * vuzp8 v0, v1 + * vuzp8 v2, v3 + * vuzp8 v1, v3 + * vuzp8 v0, v2 + * + * Planar to packed conversion: // vzip8 is a wrapper macro + * vzip8 v0, v2 + * vzip8 v1, v3 + * vzip8 v2, v3 + * vzip8 v0, v1 + * + * But pixel can be loaded directly in planar format using LD4 / b NEON + * instruction. It is 1 cycle slower than LD1 / s, so this is not always + * desirable, that's why deinterleaving is optional. + * + * But anyway, here is the code: + */ + +.macro pixman_composite_out_reverse_8888_8888_process_pixblock_head + mvn v24.8b, v3.8b /* get inverted alpha */ + /* do alpha blending */ + umull v8.8h, v24.8b, v4.8b + umull v9.8h, v24.8b, v5.8b + umull v10.8h, v24.8b, v6.8b + umull v11.8h, v24.8b, v7.8b +.endm + +.macro pixman_composite_out_reverse_8888_8888_process_pixblock_tail + urshr v14.8h, v8.8h, #8 + urshr v15.8h, v9.8h, #8 + urshr v16.8h, v10.8h, #8 + urshr v17.8h, v11.8h, #8 + raddhn v28.8b, v14.8h, v8.8h + raddhn v29.8b, v15.8h, v9.8h + raddhn v30.8b, v16.8h, v10.8h + raddhn v31.8b, v17.8h, v11.8h +.endm + +/******************************************************************************/ + +.macro pixman_composite_over_8888_8888_process_pixblock_head + pixman_composite_out_reverse_8888_8888_process_pixblock_head +.endm + +.macro pixman_composite_over_8888_8888_process_pixblock_tail + pixman_composite_out_reverse_8888_8888_process_pixblock_tail + uqadd v28.8b, v0.8b, v28.8b + uqadd v29.8b, v1.8b, v29.8b + uqadd v30.8b, v2.8b, v30.8b + uqadd v31.8b, v3.8b, v31.8b +.endm + +.macro pixman_composite_over_8888_8888_process_pixblock_tail_head + ld4 {v4.8b, v5.8b, v6.8b, v7.8b}, [DST_R], #32 + urshr v14.8h, v8.8h, #8 + PF add PF_X, PF_X, #8 + PF tst PF_CTL, #0xF + urshr v15.8h, v9.8h, #8 + urshr v16.8h, v10.8h, #8 + urshr v17.8h, v11.8h, #8 + PF beq 10f + PF add PF_X, PF_X, #8 + PF sub PF_CTL, PF_CTL, #1 +10: + raddhn v28.8b, v14.8h, v8.8h + raddhn v29.8b, v15.8h, v9.8h + PF cmp PF_X, ORIG_W + raddhn v30.8b, v16.8h, v10.8h + raddhn v31.8b, v17.8h, v11.8h + uqadd v28.8b, v0.8b, v28.8b + uqadd v29.8b, v1.8b, v29.8b + uqadd v30.8b, v2.8b, v30.8b + uqadd v31.8b, v3.8b, v31.8b + fetch_src_pixblock + PF lsl DUMMY, PF_X, #src_bpp_shift + PF prfm PREFETCH_MODE, [PF_SRC, DUMMY] + mvn v22.8b, v3.8b + PF lsl DUMMY, PF_X, #dst_bpp_shift + PF prfm PREFETCH_MODE, [PF_DST, DUMMY] + st4 {v28.8b, v29.8b, v30.8b, v31.8b}, [DST_W], #32 + PF ble 10f + PF sub PF_X, PF_X, ORIG_W +10: + umull v8.8h, v22.8b, v4.8b + PF ble 10f + PF subs PF_CTL, PF_CTL, #0x10 +10: + umull v9.8h, v22.8b, v5.8b + PF ble 10f + PF lsl DUMMY, SRC_STRIDE, #src_bpp_shift + PF ldrsb DUMMY, [PF_SRC, DUMMY] + PF add PF_SRC, PF_SRC, #1 +10: + umull v10.8h, v22.8b, v6.8b + PF ble 10f + PF lsl DUMMY, DST_STRIDE, #dst_bpp_shift + PF ldrsb DUMMY, [PF_DST, DUMMY] + PF add PF_DST, PF_DST, #1 +10: + umull v11.8h, v22.8b, v7.8b +.endm + +generate_composite_function \ + pixman_composite_over_8888_8888_asm_neon, 32, 0, 32, \ + FLAG_DST_READWRITE | FLAG_DEINTERLEAVE_32BPP, \ + 8, /* number of pixels, processed in a single block */ \ + 5, /* prefetch distance */ \ + default_init, \ + default_cleanup, \ + pixman_composite_over_8888_8888_process_pixblock_head, \ + pixman_composite_over_8888_8888_process_pixblock_tail, \ + pixman_composite_over_8888_8888_process_pixblock_tail_head + +generate_composite_function_single_scanline \ + pixman_composite_scanline_over_asm_neon, 32, 0, 32, \ + FLAG_DST_READWRITE | FLAG_DEINTERLEAVE_32BPP, \ + 8, /* number of pixels, processed in a single block */ \ + default_init, \ + default_cleanup, \ + pixman_composite_over_8888_8888_process_pixblock_head, \ + pixman_composite_over_8888_8888_process_pixblock_tail, \ + pixman_composite_over_8888_8888_process_pixblock_tail_head + +/******************************************************************************/ + +.macro pixman_composite_over_n_8888_process_pixblock_head + /* deinterleaved source pixels in {v0, v1, v2, v3} */ + /* inverted alpha in {v24} */ + /* destination pixels in {v4, v5, v6, v7} */ + umull v8.8h, v24.8b, v4.8b + umull v9.8h, v24.8b, v5.8b + umull v10.8h, v24.8b, v6.8b + umull v11.8h, v24.8b, v7.8b +.endm + +.macro pixman_composite_over_n_8888_process_pixblock_tail + urshr v14.8h, v8.8h, #8 + urshr v15.8h, v9.8h, #8 + urshr v16.8h, v10.8h, #8 + urshr v17.8h, v11.8h, #8 + raddhn v28.8b, v14.8h, v8.8h + raddhn v29.8b, v15.8h, v9.8h + raddhn v30.8b, v16.8h, v10.8h + raddhn v31.8b, v17.8h, v11.8h + uqadd v28.8b, v0.8b, v28.8b + uqadd v29.8b, v1.8b, v29.8b + uqadd v30.8b, v2.8b, v30.8b + uqadd v31.8b, v3.8b, v31.8b +.endm + +.macro pixman_composite_over_n_8888_process_pixblock_tail_head + urshr v14.8h, v8.8h, #8 + urshr v15.8h, v9.8h, #8 + urshr v16.8h, v10.8h, #8 + urshr v17.8h, v11.8h, #8 + raddhn v28.8b, v14.8h, v8.8h + raddhn v29.8b, v15.8h, v9.8h + raddhn v30.8b, v16.8h, v10.8h + raddhn v31.8b, v17.8h, v11.8h + ld4 {v4.8b, v5.8b, v6.8b, v7.8b}, [DST_R], #32 + uqadd v28.8b, v0.8b, v28.8b + PF add PF_X, PF_X, #8 + PF tst PF_CTL, #0x0F + PF beq 10f + PF add PF_X, PF_X, #8 + PF sub PF_CTL, PF_CTL, #1 +10: + uqadd v29.8b, v1.8b, v29.8b + uqadd v30.8b, v2.8b, v30.8b + uqadd v31.8b, v3.8b, v31.8b + PF cmp PF_X, ORIG_W + umull v8.8h, v24.8b, v4.8b + PF lsl DUMMY, PF_X, #dst_bpp_shift + PF prfm PREFETCH_MODE, [PF_DST, DUMMY] + umull v9.8h, v24.8b, v5.8b + PF ble 10f + PF sub PF_X, PF_X, ORIG_W +10: + umull v10.8h, v24.8b, v6.8b + PF subs PF_CTL, PF_CTL, #0x10 + umull v11.8h, v24.8b, v7.8b + PF ble 10f + PF lsl DUMMY, DST_STRIDE, #dst_bpp_shift + PF ldrsb DUMMY, [PF_DST, DUMMY] + PF add PF_DST, PF_DST, #1 +10: + st4 {v28.8b, v29.8b, v30.8b, v31.8b}, [DST_W], #32 +.endm + +.macro pixman_composite_over_n_8888_init + mov v3.s[0], w4 + dup v0.8b, v3.b[0] + dup v1.8b, v3.b[1] + dup v2.8b, v3.b[2] + dup v3.8b, v3.b[3] + mvn v24.8b, v3.8b /* get inverted alpha */ +.endm + +generate_composite_function \ + pixman_composite_over_n_8888_asm_neon, 0, 0, 32, \ + FLAG_DST_READWRITE | FLAG_DEINTERLEAVE_32BPP, \ + 8, /* number of pixels, processed in a single block */ \ + 5, /* prefetch distance */ \ + pixman_composite_over_n_8888_init, \ + default_cleanup, \ + pixman_composite_over_8888_8888_process_pixblock_head, \ + pixman_composite_over_8888_8888_process_pixblock_tail, \ + pixman_composite_over_n_8888_process_pixblock_tail_head + +/******************************************************************************/ + +.macro pixman_composite_src_n_8888_process_pixblock_head +.endm + +.macro pixman_composite_src_n_8888_process_pixblock_tail +.endm + +.macro pixman_composite_src_n_8888_process_pixblock_tail_head + st1 {v0.2s, v1.2s, v2.2s, v3.2s}, [DST_W], #32 +.endm + +.macro pixman_composite_src_n_8888_init + mov v0.s[0], w4 + dup v3.2s, v0.s[0] + dup v2.2s, v0.s[0] + dup v1.2s, v0.s[0] + dup v0.2s, v0.s[0] +.endm + +.macro pixman_composite_src_n_8888_cleanup +.endm + +generate_composite_function \ + pixman_composite_src_n_8888_asm_neon, 0, 0, 32, \ + FLAG_DST_WRITEONLY, \ + 8, /* number of pixels, processed in a single block */ \ + 0, /* prefetch distance */ \ + pixman_composite_src_n_8888_init, \ + pixman_composite_src_n_8888_cleanup, \ + pixman_composite_src_n_8888_process_pixblock_head, \ + pixman_composite_src_n_8888_process_pixblock_tail, \ + pixman_composite_src_n_8888_process_pixblock_tail_head, \ + 0, /* dst_w_basereg */ \ + 0, /* dst_r_basereg */ \ + 0, /* src_basereg */ \ + 0 /* mask_basereg */ + +/******************************************************************************/ + +.macro pixman_composite_src_8888_8888_process_pixblock_head +.endm + +.macro pixman_composite_src_8888_8888_process_pixblock_tail +.endm + +.macro pixman_composite_src_8888_8888_process_pixblock_tail_head + st1 {v0.2s, v1.2s, v2.2s, v3.2s}, [DST_W], #32 + fetch_src_pixblock + cache_preload 8, 8 +.endm + +generate_composite_function \ + pixman_composite_src_8888_8888_asm_neon, 32, 0, 32, \ + FLAG_DST_WRITEONLY, \ + 8, /* number of pixels, processed in a single block */ \ + 10, /* prefetch distance */ \ + default_init, \ + default_cleanup, \ + pixman_composite_src_8888_8888_process_pixblock_head, \ + pixman_composite_src_8888_8888_process_pixblock_tail, \ + pixman_composite_src_8888_8888_process_pixblock_tail_head, \ + 0, /* dst_w_basereg */ \ + 0, /* dst_r_basereg */ \ + 0, /* src_basereg */ \ + 0 /* mask_basereg */ + +/******************************************************************************/ diff --git a/TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arma64-neon-asm.h b/TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arma64-neon-asm.h new file mode 100644 index 000000000..1103084a3 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/pixman/pixman-arma64-neon-asm.h @@ -0,0 +1,1223 @@ +/* + * Copyright © 2009 Nokia Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Author: Siarhei Siamashka (siarhei.siamashka@nokia.com) + */ + +/* + * This file contains a macro ('generate_composite_function') which can + * construct 2D image processing functions, based on a common template. + * Any combinations of source, destination and mask images with 8bpp, + * 16bpp, 24bpp, 32bpp color formats are supported. + * + * This macro takes care of: + * - handling of leading and trailing unaligned pixels + * - doing most of the work related to L2 cache preload + * - encourages the use of software pipelining for better instructions + * scheduling + * + * The user of this macro has to provide some configuration parameters + * (bit depths for the images, prefetch distance, etc.) and a set of + * macros, which should implement basic code chunks responsible for + * pixels processing. See 'pixman-armv8-neon-asm.S' file for the usage + * examples. + * + * TODO: + * - try overlapped pixel method (from Ian Rickards) when processing + * exactly two blocks of pixels + * - maybe add an option to do reverse scanline processing + */ + +/* + * Bit flags for 'generate_composite_function' macro which are used + * to tune generated functions behavior. + */ +.set FLAG_DST_WRITEONLY, 0 +.set FLAG_DST_READWRITE, 1 +.set FLAG_DEINTERLEAVE_32BPP, 2 + +/* + * Constants for selecting preferable prefetch type. + */ +.set PREFETCH_TYPE_NONE, 0 /* No prefetch at all */ +.set PREFETCH_TYPE_SIMPLE, 1 /* A simple, fixed-distance-ahead prefetch */ +.set PREFETCH_TYPE_ADVANCED, 2 /* Advanced fine-grained prefetch */ + +/* + * prefetch mode + * available modes are: + * pldl1keep + * pldl1strm + * pldl2keep + * pldl2strm + * pldl3keep + * pldl3strm + */ +#define PREFETCH_MODE pldl1keep + +/* + * Definitions of supplementary pixld/pixst macros (for partial load/store of + * pixel data). + */ + +.macro pixldst1 op, elem_size, reg1, mem_operand, abits + op {v®1&.&elem_size}, [&mem_operand&], #8 +.endm + +.macro pixldst2 op, elem_size, reg1, reg2, mem_operand, abits + op {v®1&.&elem_size, v®2&.&elem_size}, [&mem_operand&], #16 +.endm + +.macro pixldst4 op, elem_size, reg1, reg2, reg3, reg4, mem_operand, abits + op {v®1&.&elem_size, v®2&.&elem_size, v®3&.&elem_size, v®4&.&elem_size}, [&mem_operand&], #32 +.endm + +.macro pixldst0 op, elem_size, reg1, idx, mem_operand, abits, bytes + op {v®1&.&elem_size}[idx], [&mem_operand&], #&bytes& +.endm + +.macro pixldst3 op, elem_size, reg1, reg2, reg3, mem_operand + op {v®1&.&elem_size, v®2&.&elem_size, v®3&.&elem_size}, [&mem_operand&], #24 +.endm + +.macro pixldst30 op, elem_size, reg1, reg2, reg3, idx, mem_operand + op {v®1&.&elem_size, v®2&.&elem_size, v®3&.&elem_size}[idx], [&mem_operand&], #3 +.endm + +.macro pixldst numbytes, op, elem_size, basereg, mem_operand, abits +.if numbytes == 32 + .if elem_size==32 + pixldst4 op, 2s, %(basereg+4), %(basereg+5), \ + %(basereg+6), %(basereg+7), mem_operand, abits + .elseif elem_size==16 + pixldst4 op, 4h, %(basereg+4), %(basereg+5), \ + %(basereg+6), %(basereg+7), mem_operand, abits + .else + pixldst4 op, 8b, %(basereg+4), %(basereg+5), \ + %(basereg+6), %(basereg+7), mem_operand, abits + .endif +.elseif numbytes == 16 + .if elem_size==32 + pixldst2 op, 2s, %(basereg+2), %(basereg+3), mem_operand, abits + .elseif elem_size==16 + pixldst2 op, 4h, %(basereg+2), %(basereg+3), mem_operand, abits + .else + pixldst2 op, 8b, %(basereg+2), %(basereg+3), mem_operand, abits + .endif +.elseif numbytes == 8 + .if elem_size==32 + pixldst1 op, 2s, %(basereg+1), mem_operand, abits + .elseif elem_size==16 + pixldst1 op, 4h, %(basereg+1), mem_operand, abits + .else + pixldst1 op, 8b, %(basereg+1), mem_operand, abits + .endif +.elseif numbytes == 4 + .if !RESPECT_STRICT_ALIGNMENT || (elem_size == 32) + pixldst0 op, s, %(basereg+0), 1, mem_operand, abits, 4 + .elseif elem_size == 16 + pixldst0 op, h, %(basereg+0), 2, mem_operand, abits, 2 + pixldst0 op, h, %(basereg+0), 3, mem_operand, abits, 2 + .else + pixldst0 op, b, %(basereg+0), 4, mem_operand, abits, 1 + pixldst0 op, b, %(basereg+0), 5, mem_operand, abits, 1 + pixldst0 op, b, %(basereg+0), 6, mem_operand, abits, 1 + pixldst0 op, b, %(basereg+0), 7, mem_operand, abits, 1 + .endif +.elseif numbytes == 2 + .if !RESPECT_STRICT_ALIGNMENT || (elem_size == 16) + pixldst0 op, h, %(basereg+0), 1, mem_operand, abits, 2 + .else + pixldst0 op, b, %(basereg+0), 2, mem_operand, abits, 1 + pixldst0 op, b, %(basereg+0), 3, mem_operand, abits, 1 + .endif +.elseif numbytes == 1 + pixldst0 op, b, %(basereg+0), 1, mem_operand, abits, 1 +.else + .error "unsupported size: numbytes" +.endif +.endm + +.macro pixld numpix, bpp, basereg, mem_operand, abits=0 +.if bpp > 0 +.if (bpp == 32) && (numpix == 8) && (DEINTERLEAVE_32BPP_ENABLED != 0) + pixldst4 ld4, 8b, %(basereg+4), %(basereg+5), \ + %(basereg+6), %(basereg+7), mem_operand, abits +.elseif (bpp == 24) && (numpix == 8) + pixldst3 ld3, 8b, %(basereg+3), %(basereg+4), %(basereg+5), mem_operand +.elseif (bpp == 24) && (numpix == 4) + pixldst30 ld3, b, %(basereg+0), %(basereg+1), %(basereg+2), 4, mem_operand + pixldst30 ld3, b, %(basereg+0), %(basereg+1), %(basereg+2), 5, mem_operand + pixldst30 ld3, b, %(basereg+0), %(basereg+1), %(basereg+2), 6, mem_operand + pixldst30 ld3, b, %(basereg+0), %(basereg+1), %(basereg+2), 7, mem_operand +.elseif (bpp == 24) && (numpix == 2) + pixldst30 ld3, b, %(basereg+0), %(basereg+1), %(basereg+2), 2, mem_operand + pixldst30 ld3, b, %(basereg+0), %(basereg+1), %(basereg+2), 3, mem_operand +.elseif (bpp == 24) && (numpix == 1) + pixldst30 ld3, b, %(basereg+0), %(basereg+1), %(basereg+2), 1, mem_operand +.else + pixldst %(numpix * bpp / 8), ld1, %(bpp), basereg, mem_operand, abits +.endif +.endif +.endm + +.macro pixst numpix, bpp, basereg, mem_operand, abits=0 +.if bpp > 0 +.if (bpp == 32) && (numpix == 8) && (DEINTERLEAVE_32BPP_ENABLED != 0) + pixldst4 st4, 8b, %(basereg+4), %(basereg+5), \ + %(basereg+6), %(basereg+7), mem_operand, abits +.elseif (bpp == 24) && (numpix == 8) + pixldst3 st3, 8b, %(basereg+3), %(basereg+4), %(basereg+5), mem_operand +.elseif (bpp == 24) && (numpix == 4) + pixldst30 st3, b, %(basereg+0), %(basereg+1), %(basereg+2), 4, mem_operand + pixldst30 st3, b, %(basereg+0), %(basereg+1), %(basereg+2), 5, mem_operand + pixldst30 st3, b, %(basereg+0), %(basereg+1), %(basereg+2), 6, mem_operand + pixldst30 st3, b, %(basereg+0), %(basereg+1), %(basereg+2), 7, mem_operand +.elseif (bpp == 24) && (numpix == 2) + pixldst30 st3, b, %(basereg+0), %(basereg+1), %(basereg+2), 2, mem_operand + pixldst30 st3, b, %(basereg+0), %(basereg+1), %(basereg+2), 3, mem_operand +.elseif (bpp == 24) && (numpix == 1) + pixldst30 st3, b, %(basereg+0), %(basereg+1), %(basereg+2), 1, mem_operand +.else + pixldst %(numpix * bpp / 8), st1, %(bpp), basereg, mem_operand, abits +.endif +.endif +.endm + +.macro pixld_a numpix, bpp, basereg, mem_operand +.if (bpp * numpix) <= 128 + pixld numpix, bpp, basereg, mem_operand, %(bpp * numpix) +.else + pixld numpix, bpp, basereg, mem_operand, 128 +.endif +.endm + +.macro pixst_a numpix, bpp, basereg, mem_operand +.if (bpp * numpix) <= 128 + pixst numpix, bpp, basereg, mem_operand, %(bpp * numpix) +.else + pixst numpix, bpp, basereg, mem_operand, 128 +.endif +.endm + +/* + * Pixel fetcher for nearest scaling (needs TMP1, TMP2, VX, UNIT_X register + * aliases to be defined) + */ +.macro pixld1_s elem_size, reg1, mem_operand +.if elem_size == 16 + asr TMP1, VX, #16 + adds VX, VX, UNIT_X + bmi 55f +5: subs VX, VX, SRC_WIDTH_FIXED + bpl 5b +55: + add TMP1, mem_operand, TMP1, lsl #1 + asr TMP2, VX, #16 + adds VX, VX, UNIT_X + bmi 55f +5: subs VX, VX, SRC_WIDTH_FIXED + bpl 5b +55: + add TMP2, mem_operand, TMP2, lsl #1 + ld1 {v®1&.h}[0], [TMP1] + asr TMP1, VX, #16 + adds VX, VX, UNIT_X + bmi 55f +5: subs VX, VX, SRC_WIDTH_FIXED + bpl 5b +55: + add TMP1, mem_operand, TMP1, lsl #1 + ld1 {v®1&.h}[1], [TMP2] + asr TMP2, VX, #16 + adds VX, VX, UNIT_X + bmi 55f +5: subs VX, VX, SRC_WIDTH_FIXED + bpl 5b +55: + add TMP2, mem_operand, TMP2, lsl #1 + ld1 {v®1&.h}[2], [TMP1] + ld1 {v®1&.h}[3], [TMP2] +.elseif elem_size == 32 + asr TMP1, VX, #16 + adds VX, VX, UNIT_X + bmi 55f +5: subs VX, VX, SRC_WIDTH_FIXED + bpl 5b +55: + add TMP1, mem_operand, TMP1, lsl #2 + asr TMP2, VX, #16 + adds VX, VX, UNIT_X + bmi 55f +5: subs VX, VX, SRC_WIDTH_FIXED + bpl 5b +55: + add TMP2, mem_operand, TMP2, lsl #2 + ld1 {v®1&.s}[0], [TMP1] + ld1 {v®1&.s}[1], [TMP2] +.else + .error "unsupported" +.endif +.endm + +.macro pixld2_s elem_size, reg1, reg2, mem_operand +.if 0 /* elem_size == 32 */ + mov TMP1, VX, asr #16 + add VX, VX, UNIT_X, asl #1 + add TMP1, mem_operand, TMP1, asl #2 + mov TMP2, VX, asr #16 + sub VX, VX, UNIT_X + add TMP2, mem_operand, TMP2, asl #2 + ld1 {v®1&.s}[0], [TMP1] + mov TMP1, VX, asr #16 + add VX, VX, UNIT_X, asl #1 + add TMP1, mem_operand, TMP1, asl #2 + ld1 {v®2&.s}[0], [TMP2, :32] + mov TMP2, VX, asr #16 + add VX, VX, UNIT_X + add TMP2, mem_operand, TMP2, asl #2 + ld1 {v®1&.s}[1], [TMP1] + ld1 {v®2&.s}[1], [TMP2] +.else + pixld1_s elem_size, reg1, mem_operand + pixld1_s elem_size, reg2, mem_operand +.endif +.endm + +.macro pixld0_s elem_size, reg1, idx, mem_operand +.if elem_size == 16 + asr TMP1, VX, #16 + adds VX, VX, UNIT_X + bmi 55f +5: subs VX, VX, SRC_WIDTH_FIXED + bpl 5b +55: + add TMP1, mem_operand, TMP1, lsl #1 + ld1 {v®1&.h}[idx], [TMP1] +.elseif elem_size == 32 + asr DUMMY, VX, #16 + mov TMP1, DUMMY + adds VX, VX, UNIT_X + bmi 55f +5: subs VX, VX, SRC_WIDTH_FIXED + bpl 5b +55: + add TMP1, mem_operand, TMP1, lsl #2 + ld1 {v®1&.s}[idx], [TMP1] +.endif +.endm + +.macro pixld_s_internal numbytes, elem_size, basereg, mem_operand +.if numbytes == 32 + pixld2_s elem_size, %(basereg+4), %(basereg+5), mem_operand + pixld2_s elem_size, %(basereg+6), %(basereg+7), mem_operand + pixdeinterleave elem_size, %(basereg+4) +.elseif numbytes == 16 + pixld2_s elem_size, %(basereg+2), %(basereg+3), mem_operand +.elseif numbytes == 8 + pixld1_s elem_size, %(basereg+1), mem_operand +.elseif numbytes == 4 + .if elem_size == 32 + pixld0_s elem_size, %(basereg+0), 1, mem_operand + .elseif elem_size == 16 + pixld0_s elem_size, %(basereg+0), 2, mem_operand + pixld0_s elem_size, %(basereg+0), 3, mem_operand + .else + pixld0_s elem_size, %(basereg+0), 4, mem_operand + pixld0_s elem_size, %(basereg+0), 5, mem_operand + pixld0_s elem_size, %(basereg+0), 6, mem_operand + pixld0_s elem_size, %(basereg+0), 7, mem_operand + .endif +.elseif numbytes == 2 + .if elem_size == 16 + pixld0_s elem_size, %(basereg+0), 1, mem_operand + .else + pixld0_s elem_size, %(basereg+0), 2, mem_operand + pixld0_s elem_size, %(basereg+0), 3, mem_operand + .endif +.elseif numbytes == 1 + pixld0_s elem_size, %(basereg+0), 1, mem_operand +.else + .error "unsupported size: numbytes" +.endif +.endm + +.macro pixld_s numpix, bpp, basereg, mem_operand +.if bpp > 0 + pixld_s_internal %(numpix * bpp / 8), %(bpp), basereg, mem_operand +.endif +.endm + +.macro vuzp8 reg1, reg2 + umov DUMMY, v16.d[0] + uzp1 v16.8b, v®1&.8b, v®2&.8b + uzp2 v®2&.8b, v®1&.8b, v®2&.8b + mov v®1&.8b, v16.8b + mov v16.d[0], DUMMY +.endm + +.macro vzip8 reg1, reg2 + umov DUMMY, v16.d[0] + zip1 v16.8b, v®1&.8b, v®2&.8b + zip2 v®2&.8b, v®1&.8b, v®2&.8b + mov v®1&.8b, v16.8b + mov v16.d[0], DUMMY +.endm + +/* deinterleave B, G, R, A channels for eight 32bpp pixels in 4 registers */ +.macro pixdeinterleave bpp, basereg +.if (bpp == 32) && (DEINTERLEAVE_32BPP_ENABLED != 0) + vuzp8 %(basereg+0), %(basereg+1) + vuzp8 %(basereg+2), %(basereg+3) + vuzp8 %(basereg+1), %(basereg+3) + vuzp8 %(basereg+0), %(basereg+2) +.endif +.endm + +/* interleave B, G, R, A channels for eight 32bpp pixels in 4 registers */ +.macro pixinterleave bpp, basereg +.if (bpp == 32) && (DEINTERLEAVE_32BPP_ENABLED != 0) + vzip8 %(basereg+0), %(basereg+2) + vzip8 %(basereg+1), %(basereg+3) + vzip8 %(basereg+2), %(basereg+3) + vzip8 %(basereg+0), %(basereg+1) +.endif +.endm + +/* + * This is a macro for implementing cache preload. The main idea is that + * cache preload logic is mostly independent from the rest of pixels + * processing code. It starts at the top left pixel and moves forward + * across pixels and can jump across scanlines. Prefetch distance is + * handled in an 'incremental' way: it starts from 0 and advances to the + * optimal distance over time. After reaching optimal prefetch distance, + * it is kept constant. There are some checks which prevent prefetching + * unneeded pixel lines below the image (but it still can prefetch a bit + * more data on the right side of the image - not a big issue and may + * be actually helpful when rendering text glyphs). Additional trick is + * the use of LDR instruction for prefetch instead of PLD when moving to + * the next line, the point is that we have a high chance of getting TLB + * miss in this case, and PLD would be useless. + * + * This sounds like it may introduce a noticeable overhead (when working with + * fully cached data). But in reality, due to having a separate pipeline and + * instruction queue for NEON unit in ARM Cortex-A8, normal ARM code can + * execute simultaneously with NEON and be completely shadowed by it. Thus + * we get no performance overhead at all (*). This looks like a very nice + * feature of Cortex-A8, if used wisely. We don't have a hardware prefetcher, + * but still can implement some rather advanced prefetch logic in software + * for almost zero cost! + * + * (*) The overhead of the prefetcher is visible when running some trivial + * pixels processing like simple copy. Anyway, having prefetch is a must + * when working with the graphics data. + */ +.macro PF a, x:vararg +.if (PREFETCH_TYPE_CURRENT == PREFETCH_TYPE_ADVANCED) + a x +.endif +.endm + +.macro cache_preload std_increment, boost_increment +.if (src_bpp_shift >= 0) || (dst_r_bpp != 0) || (mask_bpp_shift >= 0) +.if std_increment != 0 + PF add PF_X, PF_X, #std_increment +.endif + PF tst PF_CTL, #0xF + PF beq 71f + PF add PF_X, PF_X, #boost_increment + PF sub PF_CTL, PF_CTL, #1 +71: + PF cmp PF_X, ORIG_W +.if src_bpp_shift >= 0 + PF lsl DUMMY, PF_X, #src_bpp_shift + PF prfm PREFETCH_MODE, [PF_SRC, DUMMY] +.endif +.if dst_r_bpp != 0 + PF lsl DUMMY, PF_X, #dst_bpp_shift + PF prfm PREFETCH_MODE, [PF_DST, DUMMY] +.endif +.if mask_bpp_shift >= 0 + PF lsl DUMMY, PF_X, #mask_bpp_shift + PF prfm PREFETCH_MODE, [PF_MASK, DUMMY] +.endif + PF ble 71f + PF sub PF_X, PF_X, ORIG_W + PF subs PF_CTL, PF_CTL, #0x10 +71: + PF ble 72f +.if src_bpp_shift >= 0 + PF lsl DUMMY, SRC_STRIDE, #src_bpp_shift + PF ldrsb DUMMY, [PF_SRC, DUMMY] + PF add PF_SRC, PF_SRC, #1 +.endif +.if dst_r_bpp != 0 + PF lsl DUMMY, DST_STRIDE, #dst_bpp_shift + PF ldrsb DUMMY, [PF_DST, DUMMY] + PF add PF_DST, PF_DST, #1 +.endif +.if mask_bpp_shift >= 0 + PF lsl DUMMY, MASK_STRIDE, #mask_bpp_shift + PF ldrsb DUMMY, [PF_MASK, DUMMY] + PF add PF_MASK, PF_MASK, #1 +.endif +72: +.endif +.endm + +.macro cache_preload_simple +.if (PREFETCH_TYPE_CURRENT == PREFETCH_TYPE_SIMPLE) +.if src_bpp > 0 + prfm PREFETCH_MODE, [SRC, #(PREFETCH_DISTANCE_SIMPLE * src_bpp / 8)] +.endif +.if dst_r_bpp > 0 + prfm PREFETCH_MODE, [DST_R, #(PREFETCH_DISTANCE_SIMPLE * dst_r_bpp / 8)] +.endif +.if mask_bpp > 0 + prfm PREFETCH_MODE, [MASK, #(PREFETCH_DISTANCE_SIMPLE * mask_bpp / 8)] +.endif +.endif +.endm + +.macro fetch_mask_pixblock + pixld pixblock_size, mask_bpp, \ + (mask_basereg - pixblock_size * mask_bpp / 64), MASK +.endm + +/* + * Macro which is used to process leading pixels until destination + * pointer is properly aligned (at 16 bytes boundary). When destination + * buffer uses 16bpp format, this is unnecessary, or even pointless. + */ +.macro ensure_destination_ptr_alignment process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head +.if dst_w_bpp != 24 + tst DST_R, #0xF + beq 52f +.irp lowbit, 1, 2, 4, 8, 16 +local skip1 +.if (dst_w_bpp <= (lowbit * 8)) && ((lowbit * 8) < (pixblock_size * dst_w_bpp)) +.if lowbit < 16 /* we don't need more than 16-byte alignment */ + tst DST_R, #lowbit + beq 51f +.endif + pixld_src (lowbit * 8 / dst_w_bpp), src_bpp, src_basereg, SRC + pixld (lowbit * 8 / dst_w_bpp), mask_bpp, mask_basereg, MASK +.if dst_r_bpp > 0 + pixld_a (lowbit * 8 / dst_r_bpp), dst_r_bpp, dst_r_basereg, DST_R +.else + add DST_R, DST_R, #lowbit +.endif + PF add PF_X, PF_X, #(lowbit * 8 / dst_w_bpp) + sub W, W, #(lowbit * 8 / dst_w_bpp) +51: +.endif +.endr + pixdeinterleave src_bpp, src_basereg + pixdeinterleave mask_bpp, mask_basereg + pixdeinterleave dst_r_bpp, dst_r_basereg + + process_pixblock_head + cache_preload 0, pixblock_size + cache_preload_simple + process_pixblock_tail + + pixinterleave dst_w_bpp, dst_w_basereg + +.irp lowbit, 1, 2, 4, 8, 16 +.if (dst_w_bpp <= (lowbit * 8)) && ((lowbit * 8) < (pixblock_size * dst_w_bpp)) +.if lowbit < 16 /* we don't need more than 16-byte alignment */ + tst DST_W, #lowbit + beq 51f +.endif + pixst_a (lowbit * 8 / dst_w_bpp), dst_w_bpp, dst_w_basereg, DST_W +51: +.endif +.endr +.endif +52: +.endm + +/* + * Special code for processing up to (pixblock_size - 1) remaining + * trailing pixels. As SIMD processing performs operation on + * pixblock_size pixels, anything smaller than this has to be loaded + * and stored in a special way. Loading and storing of pixel data is + * performed in such a way that we fill some 'slots' in the NEON + * registers (some slots naturally are unused), then perform compositing + * operation as usual. In the end, the data is taken from these 'slots' + * and saved to memory. + * + * cache_preload_flag - allows to suppress prefetch if + * set to 0 + * dst_aligned_flag - selects whether destination buffer + * is aligned + */ +.macro process_trailing_pixels cache_preload_flag, \ + dst_aligned_flag, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + tst W, #(pixblock_size - 1) + beq 52f +.irp chunk_size, 16, 8, 4, 2, 1 +.if pixblock_size > chunk_size + tst W, #chunk_size + beq 51f + pixld_src chunk_size, src_bpp, src_basereg, SRC + pixld chunk_size, mask_bpp, mask_basereg, MASK +.if dst_aligned_flag != 0 + pixld_a chunk_size, dst_r_bpp, dst_r_basereg, DST_R +.else + pixld chunk_size, dst_r_bpp, dst_r_basereg, DST_R +.endif +.if cache_preload_flag != 0 + PF add PF_X, PF_X, #chunk_size +.endif +51: +.endif +.endr + pixdeinterleave src_bpp, src_basereg + pixdeinterleave mask_bpp, mask_basereg + pixdeinterleave dst_r_bpp, dst_r_basereg + + process_pixblock_head +.if cache_preload_flag != 0 + cache_preload 0, pixblock_size + cache_preload_simple +.endif + process_pixblock_tail + pixinterleave dst_w_bpp, dst_w_basereg +.irp chunk_size, 16, 8, 4, 2, 1 +.if pixblock_size > chunk_size + tst W, #chunk_size + beq 51f +.if dst_aligned_flag != 0 + pixst_a chunk_size, dst_w_bpp, dst_w_basereg, DST_W +.else + pixst chunk_size, dst_w_bpp, dst_w_basereg, DST_W +.endif +51: +.endif +.endr +52: +.endm + +/* + * Macro, which performs all the needed operations to switch to the next + * scanline and start the next loop iteration unless all the scanlines + * are already processed. + */ +.macro advance_to_next_scanline start_of_loop_label + mov W, ORIG_W + add DST_W, DST_W, DST_STRIDE, lsl #dst_bpp_shift +.if src_bpp != 0 + add SRC, SRC, SRC_STRIDE, lsl #src_bpp_shift +.endif +.if mask_bpp != 0 + add MASK, MASK, MASK_STRIDE, lsl #mask_bpp_shift +.endif +.if (dst_w_bpp != 24) + sub DST_W, DST_W, W, lsl #dst_bpp_shift +.endif +.if (src_bpp != 24) && (src_bpp != 0) + sub SRC, SRC, W, lsl #src_bpp_shift +.endif +.if (mask_bpp != 24) && (mask_bpp != 0) + sub MASK, MASK, W, lsl #mask_bpp_shift +.endif + subs H, H, #1 + mov DST_R, DST_W + bge start_of_loop_label +.endm + +/* + * Registers are allocated in the following way by default: + * v0, v1, v2, v3 - reserved for loading source pixel data + * v4, v5, v6, v7 - reserved for loading destination pixel data + * v24, v25, v26, v27 - reserved for loading mask pixel data + * v28, v29, v30, v31 - final destination pixel data for writeback to memory + */ +.macro generate_composite_function fname, \ + src_bpp_, \ + mask_bpp_, \ + dst_w_bpp_, \ + flags, \ + pixblock_size_, \ + prefetch_distance, \ + init, \ + cleanup, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head, \ + dst_w_basereg_ = 28, \ + dst_r_basereg_ = 4, \ + src_basereg_ = 0, \ + mask_basereg_ = 24 + + pixman_asm_function fname + stp x29, x30, [sp, -16]! + mov x29, sp + sub sp, sp, 232 /* push all registers */ + sub x29, x29, 64 + st1 {v8.8b, v9.8b, v10.8b, v11.8b}, [x29], #32 + st1 {v12.8b, v13.8b, v14.8b, v15.8b}, [x29], #32 + stp x8, x9, [x29, -80] + stp x10, x11, [x29, -96] + stp x12, x13, [x29, -112] + stp x14, x15, [x29, -128] + stp x16, x17, [x29, -144] + stp x18, x19, [x29, -160] + stp x20, x21, [x29, -176] + stp x22, x23, [x29, -192] + stp x24, x25, [x29, -208] + stp x26, x27, [x29, -224] + str x28, [x29, -232] + +/* + * Select prefetch type for this function. If prefetch distance is + * set to 0 or one of the color formats is 24bpp, SIMPLE prefetch + * has to be used instead of ADVANCED. + */ + .set PREFETCH_TYPE_CURRENT, PREFETCH_TYPE_DEFAULT +.if prefetch_distance == 0 + .set PREFETCH_TYPE_CURRENT, PREFETCH_TYPE_NONE +.elseif (PREFETCH_TYPE_CURRENT > PREFETCH_TYPE_SIMPLE) && \ + ((src_bpp_ == 24) || (mask_bpp_ == 24) || (dst_w_bpp_ == 24)) + .set PREFETCH_TYPE_CURRENT, PREFETCH_TYPE_SIMPLE +.endif + +/* + * Make some macro arguments globally visible and accessible + * from other macros + */ + .set src_bpp, src_bpp_ + .set mask_bpp, mask_bpp_ + .set dst_w_bpp, dst_w_bpp_ + .set pixblock_size, pixblock_size_ + .set dst_w_basereg, dst_w_basereg_ + .set dst_r_basereg, dst_r_basereg_ + .set src_basereg, src_basereg_ + .set mask_basereg, mask_basereg_ + + .macro pixld_src x:vararg + pixld x + .endm + .macro fetch_src_pixblock + pixld_src pixblock_size, src_bpp, \ + (src_basereg - pixblock_size * src_bpp / 64), SRC + .endm +/* + * Assign symbolic names to registers + */ + W .req x0 /* width (is updated during processing) */ + H .req x1 /* height (is updated during processing) */ + DST_W .req x2 /* destination buffer pointer for writes */ + DST_STRIDE .req x3 /* destination image stride */ + SRC .req x4 /* source buffer pointer */ + SRC_STRIDE .req x5 /* source image stride */ + MASK .req x6 /* mask pointer */ + MASK_STRIDE .req x7 /* mask stride */ + + DST_R .req x8 /* destination buffer pointer for reads */ + + PF_CTL .req x9 /* combined lines counter and prefetch */ + /* distance increment counter */ + PF_X .req x10 /* pixel index in a scanline for current */ + /* pretetch position */ + PF_SRC .req x11 /* pointer to source scanline start */ + /* for prefetch purposes */ + PF_DST .req x12 /* pointer to destination scanline start */ + /* for prefetch purposes */ + PF_MASK .req x13 /* pointer to mask scanline start */ + /* for prefetch purposes */ + + ORIG_W .req x14 /* saved original width */ + DUMMY .req x15 /* temporary register */ + + sxtw x0, w0 + sxtw x1, w1 + sxtw x3, w3 + sxtw x5, w5 + sxtw x7, w7 + + .set mask_bpp_shift, -1 +.if src_bpp == 32 + .set src_bpp_shift, 2 +.elseif src_bpp == 24 + .set src_bpp_shift, 0 +.elseif src_bpp == 16 + .set src_bpp_shift, 1 +.elseif src_bpp == 8 + .set src_bpp_shift, 0 +.elseif src_bpp == 0 + .set src_bpp_shift, -1 +.else + .error "requested src bpp (src_bpp) is not supported" +.endif +.if mask_bpp == 32 + .set mask_bpp_shift, 2 +.elseif mask_bpp == 24 + .set mask_bpp_shift, 0 +.elseif mask_bpp == 8 + .set mask_bpp_shift, 0 +.elseif mask_bpp == 0 + .set mask_bpp_shift, -1 +.else + .error "requested mask bpp (mask_bpp) is not supported" +.endif +.if dst_w_bpp == 32 + .set dst_bpp_shift, 2 +.elseif dst_w_bpp == 24 + .set dst_bpp_shift, 0 +.elseif dst_w_bpp == 16 + .set dst_bpp_shift, 1 +.elseif dst_w_bpp == 8 + .set dst_bpp_shift, 0 +.else + .error "requested dst bpp (dst_w_bpp) is not supported" +.endif + +.if (((flags) & FLAG_DST_READWRITE) != 0) + .set dst_r_bpp, dst_w_bpp +.else + .set dst_r_bpp, 0 +.endif +.if (((flags) & FLAG_DEINTERLEAVE_32BPP) != 0) + .set DEINTERLEAVE_32BPP_ENABLED, 1 +.else + .set DEINTERLEAVE_32BPP_ENABLED, 0 +.endif + +.if prefetch_distance < 0 || prefetch_distance > 15 + .error "invalid prefetch distance (prefetch_distance)" +.endif + + PF mov PF_X, #0 + mov DST_R, DST_W + +.if src_bpp == 24 + sub SRC_STRIDE, SRC_STRIDE, W + sub SRC_STRIDE, SRC_STRIDE, W, lsl #1 +.endif +.if mask_bpp == 24 + sub MASK_STRIDE, MASK_STRIDE, W + sub MASK_STRIDE, MASK_STRIDE, W, lsl #1 +.endif +.if dst_w_bpp == 24 + sub DST_STRIDE, DST_STRIDE, W + sub DST_STRIDE, DST_STRIDE, W, lsl #1 +.endif + +/* + * Setup advanced prefetcher initial state + */ + PF mov PF_SRC, SRC + PF mov PF_DST, DST_R + PF mov PF_MASK, MASK + /* PF_CTL = prefetch_distance | ((h - 1) << 4) */ + PF lsl DUMMY, H, #4 + PF mov PF_CTL, DUMMY + PF add PF_CTL, PF_CTL, #(prefetch_distance - 0x10) + + init + subs H, H, #1 + mov ORIG_W, W + blt 9f + cmp W, #(pixblock_size * 2) + blt 800f +/* + * This is the start of the pipelined loop, which if optimized for + * long scanlines + */ +0: + ensure_destination_ptr_alignment process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + + /* Implement "head (tail_head) ... (tail_head) tail" loop pattern */ + pixld_a pixblock_size, dst_r_bpp, \ + (dst_r_basereg - pixblock_size * dst_r_bpp / 64), DST_R + fetch_src_pixblock + pixld pixblock_size, mask_bpp, \ + (mask_basereg - pixblock_size * mask_bpp / 64), MASK + PF add PF_X, PF_X, #pixblock_size + process_pixblock_head + cache_preload 0, pixblock_size + cache_preload_simple + subs W, W, #(pixblock_size * 2) + blt 200f + +100: + process_pixblock_tail_head + cache_preload_simple + subs W, W, #pixblock_size + bge 100b + +200: + process_pixblock_tail + pixst_a pixblock_size, dst_w_bpp, \ + (dst_w_basereg - pixblock_size * dst_w_bpp / 64), DST_W + + /* Process the remaining trailing pixels in the scanline */ + process_trailing_pixels 1, 1, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + advance_to_next_scanline 0b + + cleanup +1000: + /* pop all registers */ + sub x29, x29, 64 + ld1 {v8.8b, v9.8b, v10.8b, v11.8b}, [x29], 32 + ld1 {v12.8b, v13.8b, v14.8b, v15.8b}, [x29], 32 + ldp x8, x9, [x29, -80] + ldp x10, x11, [x29, -96] + ldp x12, x13, [x29, -112] + ldp x14, x15, [x29, -128] + ldp x16, x17, [x29, -144] + ldp x18, x19, [x29, -160] + ldp x20, x21, [x29, -176] + ldp x22, x23, [x29, -192] + ldp x24, x25, [x29, -208] + ldp x26, x27, [x29, -224] + ldr x28, [x29, -232] + mov sp, x29 + ldp x29, x30, [sp], 16 + ret /* exit */ +/* + * This is the start of the loop, designed to process images with small width + * (less than pixblock_size * 2 pixels). In this case neither pipelining + * nor prefetch are used. + */ +800: + /* Process exactly pixblock_size pixels if needed */ + tst W, #pixblock_size + beq 100f + pixld pixblock_size, dst_r_bpp, \ + (dst_r_basereg - pixblock_size * dst_r_bpp / 64), DST_R + fetch_src_pixblock + pixld pixblock_size, mask_bpp, \ + (mask_basereg - pixblock_size * mask_bpp / 64), MASK + process_pixblock_head + process_pixblock_tail + pixst pixblock_size, dst_w_bpp, \ + (dst_w_basereg - pixblock_size * dst_w_bpp / 64), DST_W +100: + /* Process the remaining trailing pixels in the scanline */ + process_trailing_pixels 0, 0, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + advance_to_next_scanline 800b +9: + cleanup + /* pop all registers */ + sub x29, x29, 64 + ld1 {v8.8b, v9.8b, v10.8b, v11.8b}, [x29], 32 + ld1 {v12.8b, v13.8b, v14.8b, v15.8b}, [x29], 32 + ldp x8, x9, [x29, -80] + ldp x10, x11, [x29, -96] + ldp x12, x13, [x29, -112] + ldp x14, x15, [x29, -128] + ldp x16, x17, [x29, -144] + ldp x18, x19, [x29, -160] + ldp x20, x21, [x29, -176] + ldp x22, x23, [x29, -192] + ldp x24, x25, [x29, -208] + ldp x26, x27, [x29, -224] + ldr x28, [x29, -232] + mov sp, x29 + ldp x29, x30, [sp], 16 + ret /* exit */ + + .purgem fetch_src_pixblock + .purgem pixld_src + + .unreq SRC + .unreq MASK + .unreq DST_R + .unreq DST_W + .unreq ORIG_W + .unreq W + .unreq H + .unreq SRC_STRIDE + .unreq DST_STRIDE + .unreq MASK_STRIDE + .unreq PF_CTL + .unreq PF_X + .unreq PF_SRC + .unreq PF_DST + .unreq PF_MASK + .unreq DUMMY + .endfunc +.endm + +/* + * A simplified variant of function generation template for a single + * scanline processing (for implementing pixman combine functions) + */ +.macro generate_composite_function_scanline use_nearest_scaling, \ + fname, \ + src_bpp_, \ + mask_bpp_, \ + dst_w_bpp_, \ + flags, \ + pixblock_size_, \ + init, \ + cleanup, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head, \ + dst_w_basereg_ = 28, \ + dst_r_basereg_ = 4, \ + src_basereg_ = 0, \ + mask_basereg_ = 24 + + pixman_asm_function fname + .set PREFETCH_TYPE_CURRENT, PREFETCH_TYPE_NONE + +/* + * Make some macro arguments globally visible and accessible + * from other macros + */ + .set src_bpp, src_bpp_ + .set mask_bpp, mask_bpp_ + .set dst_w_bpp, dst_w_bpp_ + .set pixblock_size, pixblock_size_ + .set dst_w_basereg, dst_w_basereg_ + .set dst_r_basereg, dst_r_basereg_ + .set src_basereg, src_basereg_ + .set mask_basereg, mask_basereg_ + +.if use_nearest_scaling != 0 + /* + * Assign symbolic names to registers for nearest scaling + */ + W .req x0 + DST_W .req x1 + SRC .req x2 + VX .req x3 + UNIT_X .req x4 + SRC_WIDTH_FIXED .req x5 + MASK .req x6 + TMP1 .req x8 + TMP2 .req x9 + DST_R .req x10 + DUMMY .req x30 + + .macro pixld_src x:vararg + pixld_s x + .endm + + sxtw x0, w0 + sxtw x3, w3 + sxtw x4, w4 + sxtw x5, w5 + + stp x29, x30, [sp, -16]! + mov x29, sp + sub sp, sp, 88 + sub x29, x29, 64 + st1 {v8.8b, v9.8b, v10.8b, v11.8b}, [x29], 32 + st1 {v12.8b, v13.8b, v14.8b, v15.8b}, [x29], 32 + stp x8, x9, [x29, -80] + str x10, [x29, -88] +.else + /* + * Assign symbolic names to registers + */ + W .req x0 /* width (is updated during processing) */ + DST_W .req x1 /* destination buffer pointer for writes */ + SRC .req x2 /* source buffer pointer */ + MASK .req x3 /* mask pointer */ + DST_R .req x4 /* destination buffer pointer for reads */ + DUMMY .req x30 + + .macro pixld_src x:vararg + pixld x + .endm + + sxtw x0, w0 + + stp x29, x30, [sp, -16]! + mov x29, sp + sub sp, sp, 64 + sub x29, x29, 64 + st1 {v8.8b, v9.8b, v10.8b, v11.8b}, [x29], 32 + st1 {v12.8b, v13.8b, v14.8b, v15.8b}, [x29], 32 +.endif + +.if (((flags) & FLAG_DST_READWRITE) != 0) + .set dst_r_bpp, dst_w_bpp +.else + .set dst_r_bpp, 0 +.endif +.if (((flags) & FLAG_DEINTERLEAVE_32BPP) != 0) + .set DEINTERLEAVE_32BPP_ENABLED, 1 +.else + .set DEINTERLEAVE_32BPP_ENABLED, 0 +.endif + + .macro fetch_src_pixblock + pixld_src pixblock_size, src_bpp, \ + (src_basereg - pixblock_size * src_bpp / 64), SRC + .endm + + init + mov DST_R, DST_W + + cmp W, #pixblock_size + blt 800f + + ensure_destination_ptr_alignment process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + + subs W, W, #pixblock_size + blt 700f + + /* Implement "head (tail_head) ... (tail_head) tail" loop pattern */ + pixld_a pixblock_size, dst_r_bpp, \ + (dst_r_basereg - pixblock_size * dst_r_bpp / 64), DST_R + fetch_src_pixblock + pixld pixblock_size, mask_bpp, \ + (mask_basereg - pixblock_size * mask_bpp / 64), MASK + process_pixblock_head + subs W, W, #pixblock_size + blt 200f +100: + process_pixblock_tail_head + subs W, W, #pixblock_size + bge 100b +200: + process_pixblock_tail + pixst_a pixblock_size, dst_w_bpp, \ + (dst_w_basereg - pixblock_size * dst_w_bpp / 64), DST_W +700: + /* Process the remaining trailing pixels in the scanline (dst aligned) */ + process_trailing_pixels 0, 1, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + + cleanup +.if use_nearest_scaling != 0 + sub x29, x29, 64 + ld1 {v8.8b, v9.8b, v10.8b, v11.8b}, [x29], 32 + ld1 {v12.8b, v13.8b, v14.8b, v15.8b}, [x29], 32 + ldp x8, x9, [x29, -80] + ldr x10, [x29, -96] + mov sp, x29 + ldp x29, x30, [sp], 16 + ret /* exit */ +.else + sub x29, x29, 64 + ld1 {v8.8b, v9.8b, v10.8b, v11.8b}, [x29], 32 + ld1 {v12.8b, v13.8b, v14.8b, v15.8b}, [x29], 32 + mov sp, x29 + ldp x29, x30, [sp], 16 + ret /* exit */ +.endif +800: + /* Process the remaining trailing pixels in the scanline (dst unaligned) */ + process_trailing_pixels 0, 0, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + + cleanup +.if use_nearest_scaling != 0 + sub x29, x29, 64 + ld1 {v8.8b, v9.8b, v10.8b, v11.8b}, [x29], 32 + ld1 {v12.8b, v13.8b, v14.8b, v15.8b}, [x29], 32 + ldp x8, x9, [x29, -80] + ldr x10, [x29, -88] + mov sp, x29 + ldp x29, x30, [sp], 16 + ret /* exit */ + + .unreq DUMMY + .unreq DST_R + .unreq SRC + .unreq W + .unreq VX + .unreq UNIT_X + .unreq TMP1 + .unreq TMP2 + .unreq DST_W + .unreq MASK + .unreq SRC_WIDTH_FIXED + +.else + sub x29, x29, 64 + ld1 {v8.8b, v9.8b, v10.8b, v11.8b}, [x29], 32 + ld1 {v12.8b, v13.8b, v14.8b, v15.8b}, [x29], 32 + mov sp, x29 + ldp x29, x30, [sp], 16 + ret /* exit */ + + .unreq DUMMY + .unreq SRC + .unreq MASK + .unreq DST_R + .unreq DST_W + .unreq W +.endif + + .purgem fetch_src_pixblock + .purgem pixld_src + + .endfunc +.endm + +.macro generate_composite_function_single_scanline x:vararg + generate_composite_function_scanline 0, x +.endm + +.macro generate_composite_function_nearest_scanline x:vararg + generate_composite_function_scanline 1, x +.endm + +/* Default prologue/epilogue, nothing special needs to be done */ + +.macro default_init +.endm + +.macro default_cleanup +.endm + +/* + * Prologue/epilogue variant which additionally saves/restores v8-v15 + * registers (they need to be saved/restored by callee according to ABI). + * This is required if the code needs to use all the NEON registers. + */ + +.macro default_init_need_all_regs +.endm + +.macro default_cleanup_need_all_regs +.endm + +/******************************************************************************/ diff --git a/TMessagesProj/jni/rlottie/src/vector/pixman/vregion.cpp b/TMessagesProj/jni/rlottie/src/vector/pixman/vregion.cpp new file mode 100755 index 000000000..5944b6347 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/pixman/vregion.cpp @@ -0,0 +1,2086 @@ +/* + * Copyright 1987, 1988, 1989, 1998 The Open Group + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name of The Open Group shall not be + * used in advertising or otherwise to promote the sale, use or other dealings + * in this Software without prior written authorization from The Open Group. + * + * Copyright 1987, 1988, 1989 by + * Digital Equipment Corporation, Maynard, Massachusetts. + * + * All Rights Reserved + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, + * provided that the above copyright notice appear in all copies and that + * both that copyright notice and this permission notice appear in + * supporting documentation, and that the name of Digital not be + * used in advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * + * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING + * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL + * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR + * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + * + * Copyright © 1998 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Keith Packard not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. Keith Packard makes no + * representations about the suitability of this software for any purpose. It + * is provided "as is" without express or implied warranty. + * + * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#define critical_if_fail assert +#define PIXMAN_EXPORT static +#define FALSE 0 +#define TRUE 1 +#define FUNC "" +#define MIN(a, b) (a) < (b) ? (a) : (b) +#define MAX(a, b) (a) > (b) ? (a) : (b) + +typedef int pixman_bool_t; + +typedef struct pixman_rectangle pixman_rectangle_t; + +typedef struct pixman_box box_type_t; +typedef struct pixman_region_data region_data_type_t; +typedef struct pixman_region region_type_t; +typedef int64_t overflow_int_t; + +#define PREFIX(x) pixman_region##x + +#define PIXMAN_REGION_MAX INT32_MAX +#define PIXMAN_REGION_MIN INT32_MIN + +typedef struct { + int x, y; +} point_type_t; + +struct pixman_region_data { + long size; + long numRects; + /* box_type_t rects[size]; in memory but not explicitly declared */ +}; + +struct pixman_rectangle { + int32_t x, y; + uint32_t width, height; +}; + +struct pixman_box { + int32_t x1, y1, x2, y2; +}; + +struct pixman_region { + box_type_t extents; + region_data_type_t *data; +}; + +typedef enum { + PIXMAN_REGION_OUT, + PIXMAN_REGION_IN, + PIXMAN_REGION_PART +} pixman_region_overlap_t; + +static void _pixman_log_error(const char *function, const char *message) +{ + fprintf(stderr, + "*** BUG ***\n" + "In %s: %s\n" + "Set a breakpoint on '_pixman_log_error' to debug\n\n", + function, message); +} + +#define PIXREGION_NIL(reg) ((reg)->data && !(reg)->data->numRects) +/* not a region */ +#define PIXREGION_NAR(reg) ((reg)->data == pixman_broken_data) +#define PIXREGION_NUMRECTS(reg) ((reg)->data ? (reg)->data->numRects : 1) +#define PIXREGION_SIZE(reg) ((reg)->data ? (reg)->data->size : 0) +#define PIXREGION_RECTS(reg) \ + ((reg)->data ? (box_type_t *)((reg)->data + 1) : &(reg)->extents) +#define PIXREGION_BOXPTR(reg) ((box_type_t *)((reg)->data + 1)) +#define PIXREGION_BOX(reg, i) (&PIXREGION_BOXPTR(reg)[i]) +#define PIXREGION_TOP(reg) PIXREGION_BOX(reg, (reg)->data->numRects) +#define PIXREGION_END(reg) PIXREGION_BOX(reg, (reg)->data->numRects - 1) + +#define GOOD_RECT(rect) ((rect)->x1 < (rect)->x2 && (rect)->y1 < (rect)->y2) +#define BAD_RECT(rect) ((rect)->x1 > (rect)->x2 || (rect)->y1 > (rect)->y2) + +#ifdef DEBUG + +#define GOOD(reg) \ + do { \ + if (!PREFIX(_selfcheck(reg))) \ + _pixman_log_error(FUNC, "Malformed region " #reg); \ + } while (0) + +#else + +#define GOOD(reg) + +#endif + +static const box_type_t PREFIX(_empty_box_) = {0, 0, 0, 0}; +static const region_data_type_t PREFIX(_empty_data_) = {0, 0}; +#if defined(__llvm__) && !defined(__clang__) +static const volatile region_data_type_t PREFIX(_broken_data_) = {0, 0}; +#else +static const region_data_type_t PREFIX(_broken_data_) = {0, 0}; +#endif + +static box_type_t *pixman_region_empty_box = (box_type_t *)&PREFIX(_empty_box_); +static region_data_type_t *pixman_region_empty_data = + (region_data_type_t *)&PREFIX(_empty_data_); +static region_data_type_t *pixman_broken_data = + (region_data_type_t *)&PREFIX(_broken_data_); + +static pixman_bool_t pixman_break(region_type_t *region); + +/* + * The functions in this file implement the Region abstraction used extensively + * throughout the X11 sample server. A Region is simply a set of disjoint + * (non-overlapping) rectangles, plus an "extent" rectangle which is the + * smallest single rectangle that contains all the non-overlapping rectangles. + * + * A Region is implemented as a "y-x-banded" array of rectangles. This array + * imposes two degrees of order. First, all rectangles are sorted by top side + * y coordinate first (y1), and then by left side x coordinate (x1). + * + * Furthermore, the rectangles are grouped into "bands". Each rectangle in a + * band has the same top y coordinate (y1), and each has the same bottom y + * coordinate (y2). Thus all rectangles in a band differ only in their left + * and right side (x1 and x2). Bands are implicit in the array of rectangles: + * there is no separate list of band start pointers. + * + * The y-x band representation does not minimize rectangles. In particular, + * if a rectangle vertically crosses a band (the rectangle has scanlines in + * the y1 to y2 area spanned by the band), then the rectangle may be broken + * down into two or more smaller rectangles stacked one atop the other. + * + * ----------- ----------- + * | | | | band 0 + * | | -------- ----------- -------- + * | | | | in y-x banded | | | | band 1 + * | | | | form is | | | | + * ----------- | | ----------- -------- + * | | | | band 2 + * -------- -------- + * + * An added constraint on the rectangles is that they must cover as much + * horizontal area as possible: no two rectangles within a band are allowed + * to touch. + * + * Whenever possible, bands will be merged together to cover a greater vertical + * distance (and thus reduce the number of rectangles). Two bands can be merged + * only if the bottom of one touches the top of the other and they have + * rectangles in the same places (of the same width, of course). + * + * Adam de Boor wrote most of the original region code. Joel McCormack + * substantially modified or rewrote most of the core arithmetic routines, and + * added pixman_region_validate in order to support several speed improvements + * to pixman_region_validate_tree. Bob Scheifler changed the representation + * to be more compact when empty or a single rectangle, and did a bunch of + * gratuitous reformatting. Carl Worth did further gratuitous reformatting + * while re-merging the server and client region code into libpixregion. + * Soren Sandmann did even more gratuitous reformatting. + */ + +/* true iff two Boxes overlap */ +#define EXTENTCHECK(r1, r2) \ + (!(((r1)->x2 <= (r2)->x1) || ((r1)->x1 >= (r2)->x2) || \ + ((r1)->y2 <= (r2)->y1) || ((r1)->y1 >= (r2)->y2))) + +/* true iff (x,y) is in Box */ +#define INBOX(r, x, y) \ + (((r)->x2 > x) && ((r)->x1 <= x) && ((r)->y2 > y) && ((r)->y1 <= y)) + +/* true iff Box r1 contains Box r2 */ +#define SUBSUMES(r1, r2) \ + (((r1)->x1 <= (r2)->x1) && ((r1)->x2 >= (r2)->x2) && \ + ((r1)->y1 <= (r2)->y1) && ((r1)->y2 >= (r2)->y2)) + +static size_t PIXREGION_SZOF(size_t n) +{ + size_t size = n * sizeof(box_type_t); + + if (n > UINT32_MAX / sizeof(box_type_t)) return 0; + + if (sizeof(region_data_type_t) > UINT32_MAX - size) return 0; + + return size + sizeof(region_data_type_t); +} + +static region_data_type_t *alloc_data(size_t n) +{ + size_t sz = PIXREGION_SZOF(n); + + if (!sz) return NULL; + + return (region_data_type_t *)malloc(sz); +} + +#define FREE_DATA(reg) \ + if ((reg)->data && (reg)->data->size) free((reg)->data) + +#define RECTALLOC_BAIL(region, n, bail) \ + do { \ + if (!(region)->data || \ + (((region)->data->numRects + (n)) > (region)->data->size)) { \ + if (!pixman_rect_alloc(region, n)) goto bail; \ + } \ + } while (0) + +#define RECTALLOC(region, n) \ + do { \ + if (!(region)->data || \ + (((region)->data->numRects + (n)) > (region)->data->size)) { \ + if (!pixman_rect_alloc(region, n)) { \ + return FALSE; \ + } \ + } \ + } while (0) + +#define ADDRECT(next_rect, nx1, ny1, nx2, ny2) \ + do { \ + next_rect->x1 = nx1; \ + next_rect->y1 = ny1; \ + next_rect->x2 = nx2; \ + next_rect->y2 = ny2; \ + next_rect++; \ + } while (0) + +#define NEWRECT(region, next_rect, nx1, ny1, nx2, ny2) \ + do { \ + if (!(region)->data || \ + ((region)->data->numRects == (region)->data->size)) { \ + if (!pixman_rect_alloc(region, 1)) return FALSE; \ + next_rect = PIXREGION_TOP(region); \ + } \ + ADDRECT(next_rect, nx1, ny1, nx2, ny2); \ + region->data->numRects++; \ + critical_if_fail(region->data->numRects <= region->data->size); \ + } while (0) + +#define DOWNSIZE(reg, numRects) \ + do { \ + if (((numRects) < ((reg)->data->size >> 1)) && \ + ((reg)->data->size > 50)) { \ + region_data_type_t *new_data; \ + size_t data_size = PIXREGION_SZOF(numRects); \ + \ + if (!data_size) { \ + new_data = NULL; \ + } else { \ + new_data = \ + (region_data_type_t *)realloc((reg)->data, data_size); \ + } \ + \ + if (new_data) { \ + new_data->size = (numRects); \ + (reg)->data = new_data; \ + } \ + } \ + } while (0) + +PIXMAN_EXPORT pixman_bool_t PREFIX(_equal)(region_type_t *reg1, + region_type_t *reg2) +{ + int i; + box_type_t *rects1; + box_type_t *rects2; + + if (reg1->extents.x1 != reg2->extents.x1) return FALSE; + + if (reg1->extents.x2 != reg2->extents.x2) return FALSE; + + if (reg1->extents.y1 != reg2->extents.y1) return FALSE; + + if (reg1->extents.y2 != reg2->extents.y2) return FALSE; + + if (PIXREGION_NUMRECTS(reg1) != PIXREGION_NUMRECTS(reg2)) return FALSE; + + rects1 = PIXREGION_RECTS(reg1); + rects2 = PIXREGION_RECTS(reg2); + + for (i = 0; i != PIXREGION_NUMRECTS(reg1); i++) { + if (rects1[i].x1 != rects2[i].x1) return FALSE; + + if (rects1[i].x2 != rects2[i].x2) return FALSE; + + if (rects1[i].y1 != rects2[i].y1) return FALSE; + + if (rects1[i].y2 != rects2[i].y2) return FALSE; + } + + return TRUE; +} + +// returns true if both region intersects +PIXMAN_EXPORT pixman_bool_t PREFIX(_intersects)(region_type_t *reg1, + region_type_t *reg2) +{ + box_type_t *rects1 = PIXREGION_RECTS(reg1); + box_type_t *rects2 = PIXREGION_RECTS(reg2); + for (int i = 0; i != PIXREGION_NUMRECTS(reg1); i++) { + for (int j = 0; j != PIXREGION_NUMRECTS(reg2); j++) { + if (EXTENTCHECK(rects1 + i, rects2 + j)) return TRUE; + } + } + return FALSE; +} + +int PREFIX(_print)(region_type_t *rgn) +{ + int num, size; + int i; + box_type_t *rects; + + num = PIXREGION_NUMRECTS(rgn); + size = PIXREGION_SIZE(rgn); + rects = PIXREGION_RECTS(rgn); + + fprintf(stderr, "num: %d size: %d\n", num, size); + fprintf(stderr, "extents: %d %d %d %d\n", rgn->extents.x1, rgn->extents.y1, + rgn->extents.x2, rgn->extents.y2); + + for (i = 0; i < num; i++) { + fprintf(stderr, "%d %d %d %d \n", rects[i].x1, rects[i].y1, rects[i].x2, + rects[i].y2); + } + + fprintf(stderr, "\n"); + + return (num); +} + +PIXMAN_EXPORT void PREFIX(_init)(region_type_t *region) +{ + region->extents = *pixman_region_empty_box; + region->data = pixman_region_empty_data; +} + +PIXMAN_EXPORT pixman_bool_t PREFIX(_union_rect)(region_type_t *dest, + region_type_t *source, int x, + int y, unsigned int width, + unsigned int height); +PIXMAN_EXPORT void PREFIX(_init_rect)(region_type_t *region, int x, int y, + unsigned int width, unsigned int height) +{ + PREFIX(_init)(region); + PREFIX(_union_rect)(region, region, x, y, width, height); +} + +PIXMAN_EXPORT void PREFIX(_fini)(region_type_t *region) +{ + GOOD(region); + FREE_DATA(region); +} + +PIXMAN_EXPORT int PREFIX(_n_rects)(region_type_t *region) +{ + return PIXREGION_NUMRECTS(region); +} + +static pixman_bool_t pixman_break(region_type_t *region) +{ + FREE_DATA(region); + + region->extents = *pixman_region_empty_box; + region->data = pixman_broken_data; + + return FALSE; +} + +static pixman_bool_t pixman_rect_alloc(region_type_t *region, int n) +{ + region_data_type_t *data; + + if (!region->data) { + n++; + region->data = alloc_data(n); + + if (!region->data) return pixman_break(region); + + region->data->numRects = 1; + *PIXREGION_BOXPTR(region) = region->extents; + } else if (!region->data->size) { + region->data = alloc_data(n); + + if (!region->data) return pixman_break(region); + + region->data->numRects = 0; + } else { + size_t data_size; + + if (n == 1) { + n = region->data->numRects; + if (n > 500) /* XXX pick numbers out of a hat */ + n = 250; + } + + n += region->data->numRects; + data_size = PIXREGION_SZOF(n); + + if (!data_size) { + data = NULL; + } else { + data = + (region_data_type_t *)realloc(region->data, PIXREGION_SZOF(n)); + } + + if (!data) return pixman_break(region); + + region->data = data; + } + + region->data->size = n; + + return TRUE; +} + +PIXMAN_EXPORT pixman_bool_t PREFIX(_copy)(region_type_t *dst, + region_type_t *src) +{ + GOOD(dst); + GOOD(src); + + if (dst == src) return TRUE; + + dst->extents = src->extents; + + if (!src->data || !src->data->size) { + FREE_DATA(dst); + dst->data = src->data; + return TRUE; + } + + if (!dst->data || (dst->data->size < src->data->numRects)) { + FREE_DATA(dst); + + dst->data = alloc_data(src->data->numRects); + + if (!dst->data) return pixman_break(dst); + + dst->data->size = src->data->numRects; + } + + dst->data->numRects = src->data->numRects; + + memmove((char *)PIXREGION_BOXPTR(dst), (char *)PIXREGION_BOXPTR(src), + dst->data->numRects * sizeof(box_type_t)); + + return TRUE; +} + +/*====================================================================== + * Generic Region Operator + *====================================================================*/ + +/*- + *----------------------------------------------------------------------- + * pixman_coalesce -- + * Attempt to merge the boxes in the current band with those in the + * previous one. We are guaranteed that the current band extends to + * the end of the rects array. Used only by pixman_op. + * + * Results: + * The new index for the previous band. + * + * Side Effects: + * If coalescing takes place: + * - rectangles in the previous band will have their y2 fields + * altered. + * - region->data->numRects will be decreased. + * + *----------------------------------------------------------------------- + */ +static inline int pixman_coalesce( + region_type_t *region, /* Region to coalesce */ + int prev_start, /* Index of start of previous band */ + int cur_start) /* Index of start of current band */ +{ + box_type_t *prev_box; /* Current box in previous band */ + box_type_t *cur_box; /* Current box in current band */ + int numRects; /* Number rectangles in both bands */ + int y2; /* Bottom of current band */ + + /* + * Figure out how many rectangles are in the band. + */ + numRects = cur_start - prev_start; + critical_if_fail(numRects == region->data->numRects - cur_start); + + if (!numRects) return cur_start; + + /* + * The bands may only be coalesced if the bottom of the previous + * matches the top scanline of the current. + */ + prev_box = PIXREGION_BOX(region, prev_start); + cur_box = PIXREGION_BOX(region, cur_start); + if (prev_box->y2 != cur_box->y1) return cur_start; + + /* + * Make sure the bands have boxes in the same places. This + * assumes that boxes have been added in such a way that they + * cover the most area possible. I.e. two boxes in a band must + * have some horizontal space between them. + */ + y2 = cur_box->y2; + + do { + if ((prev_box->x1 != cur_box->x1) || (prev_box->x2 != cur_box->x2)) + return (cur_start); + + prev_box++; + cur_box++; + numRects--; + } while (numRects); + + /* + * The bands may be merged, so set the bottom y of each box + * in the previous band to the bottom y of the current band. + */ + numRects = cur_start - prev_start; + region->data->numRects -= numRects; + + do { + prev_box--; + prev_box->y2 = y2; + numRects--; + } while (numRects); + + return prev_start; +} + +/* Quicky macro to avoid trivial reject procedure calls to pixman_coalesce */ + +#define COALESCE(new_reg, prev_band, cur_band) \ + do { \ + if (cur_band - prev_band == new_reg->data->numRects - cur_band) \ + prev_band = pixman_coalesce(new_reg, prev_band, cur_band); \ + else \ + prev_band = cur_band; \ + } while (0) + +/*- + *----------------------------------------------------------------------- + * pixman_region_append_non_o -- + * Handle a non-overlapping band for the union and subtract operations. + * Just adds the (top/bottom-clipped) rectangles into the region. + * Doesn't have to check for subsumption or anything. + * + * Results: + * None. + * + * Side Effects: + * region->data->numRects is incremented and the rectangles overwritten + * with the rectangles we're passed. + * + *----------------------------------------------------------------------- + */ +static inline pixman_bool_t pixman_region_append_non_o(region_type_t *region, + box_type_t * r, + box_type_t * r_end, + int y1, int y2) +{ + box_type_t *next_rect; + int new_rects; + + new_rects = r_end - r; + + critical_if_fail(y1 < y2); + critical_if_fail(new_rects != 0); + + /* Make sure we have enough space for all rectangles to be added */ + RECTALLOC(region, new_rects); + next_rect = PIXREGION_TOP(region); + region->data->numRects += new_rects; + + do { + critical_if_fail(r->x1 < r->x2); + ADDRECT(next_rect, r->x1, y1, r->x2, y2); + r++; + } while (r != r_end); + + return TRUE; +} + +#define FIND_BAND(r, r_band_end, r_end, ry1) \ + do { \ + ry1 = r->y1; \ + r_band_end = r + 1; \ + while ((r_band_end != r_end) && (r_band_end->y1 == ry1)) { \ + r_band_end++; \ + } \ + } while (0) + +#define APPEND_REGIONS(new_reg, r, r_end) \ + do { \ + int new_rects; \ + if ((new_rects = r_end - r)) { \ + RECTALLOC_BAIL(new_reg, new_rects, bail); \ + memmove((char *)PIXREGION_TOP(new_reg), (char *)r, \ + new_rects * sizeof(box_type_t)); \ + new_reg->data->numRects += new_rects; \ + } \ + } while (0) + +/*- + *----------------------------------------------------------------------- + * pixman_op -- + * Apply an operation to two regions. Called by pixman_region_union, + *pixman_region_inverse, pixman_region_subtract, pixman_region_intersect.... + *Both regions MUST have at least one rectangle, and cannot be the same object. + * + * Results: + * TRUE if successful. + * + * Side Effects: + * The new region is overwritten. + * overlap set to TRUE if overlap_func ever returns TRUE. + * + * Notes: + * The idea behind this function is to view the two regions as sets. + * Together they cover a rectangle of area that this function divides + * into horizontal bands where points are covered only by one region + * or by both. For the first case, the non_overlap_func is called with + * each the band and the band's upper and lower extents. For the + * second, the overlap_func is called to process the entire band. It + * is responsible for clipping the rectangles in the band, though + * this function provides the boundaries. + * At the end of each band, the new region is coalesced, if possible, + * to reduce the number of rectangles in the region. + * + *----------------------------------------------------------------------- + */ + +typedef pixman_bool_t (*overlap_proc_ptr)(region_type_t *region, box_type_t *r1, + box_type_t *r1_end, box_type_t *r2, + box_type_t *r2_end, int y1, int y2); + +static pixman_bool_t pixman_op( + region_type_t * new_reg, /* Place to store result */ + region_type_t * reg1, /* First region in operation */ + region_type_t * reg2, /* 2d region in operation */ + overlap_proc_ptr overlap_func, /* Function to call for over- + * lapping bands */ + int append_non1, /* Append non-overlapping bands + * in region 1 ? + */ + int append_non2 /* Append non-overlapping bands + * in region 2 ? + */ +) +{ + box_type_t * r1; /* Pointer into first region */ + box_type_t * r2; /* Pointer into 2d region */ + box_type_t * r1_end; /* End of 1st region */ + box_type_t * r2_end; /* End of 2d region */ + int ybot; /* Bottom of intersection */ + int ytop; /* Top of intersection */ + region_data_type_t *old_data; /* Old data for new_reg */ + int prev_band; /* Index of start of + * previous band in new_reg */ + int cur_band; /* Index of start of current + * band in new_reg */ + box_type_t *r1_band_end; /* End of current band in r1 */ + box_type_t *r2_band_end; /* End of current band in r2 */ + int top; /* Top of non-overlapping band */ + int bot; /* Bottom of non-overlapping band*/ + int r1y1; /* Temps for r1->y1 and r2->y1 */ + int r2y1; + int new_size; + int numRects; + + /* + * Break any region computed from a broken region + */ + if (PIXREGION_NAR(reg1) || PIXREGION_NAR(reg2)) + return pixman_break(new_reg); + + /* + * Initialization: + * set r1, r2, r1_end and r2_end appropriately, save the rectangles + * of the destination region until the end in case it's one of + * the two source regions, then mark the "new" region empty, allocating + * another array of rectangles for it to use. + */ + + r1 = PIXREGION_RECTS(reg1); + new_size = PIXREGION_NUMRECTS(reg1); + r1_end = r1 + new_size; + + numRects = PIXREGION_NUMRECTS(reg2); + r2 = PIXREGION_RECTS(reg2); + r2_end = r2 + numRects; + + critical_if_fail(r1 != r1_end); + critical_if_fail(r2 != r2_end); + + old_data = (region_data_type_t *)NULL; + + if (((new_reg == reg1) && (new_size > 1)) || + ((new_reg == reg2) && (numRects > 1))) { + old_data = new_reg->data; + new_reg->data = pixman_region_empty_data; + } + + /* guess at new size */ + if (numRects > new_size) new_size = numRects; + + new_size <<= 1; + + if (!new_reg->data) + new_reg->data = pixman_region_empty_data; + else if (new_reg->data->size) + new_reg->data->numRects = 0; + + if (new_size > new_reg->data->size) { + if (!pixman_rect_alloc(new_reg, new_size)) { + free(old_data); + return FALSE; + } + } + + /* + * Initialize ybot. + * In the upcoming loop, ybot and ytop serve different functions depending + * on whether the band being handled is an overlapping or non-overlapping + * band. + * In the case of a non-overlapping band (only one of the regions + * has points in the band), ybot is the bottom of the most recent + * intersection and thus clips the top of the rectangles in that band. + * ytop is the top of the next intersection between the two regions and + * serves to clip the bottom of the rectangles in the current band. + * For an overlapping band (where the two regions intersect), ytop clips + * the top of the rectangles of both regions and ybot clips the bottoms. + */ + + ybot = MIN(r1->y1, r2->y1); + + /* + * prev_band serves to mark the start of the previous band so rectangles + * can be coalesced into larger rectangles. qv. pixman_coalesce, above. + * In the beginning, there is no previous band, so prev_band == cur_band + * (cur_band is set later on, of course, but the first band will always + * start at index 0). prev_band and cur_band must be indices because of + * the possible expansion, and resultant moving, of the new region's + * array of rectangles. + */ + prev_band = 0; + + do { + /* + * This algorithm proceeds one source-band (as opposed to a + * destination band, which is determined by where the two regions + * intersect) at a time. r1_band_end and r2_band_end serve to mark the + * rectangle after the last one in the current band for their + * respective regions. + */ + critical_if_fail(r1 != r1_end); + critical_if_fail(r2 != r2_end); + + FIND_BAND(r1, r1_band_end, r1_end, r1y1); + FIND_BAND(r2, r2_band_end, r2_end, r2y1); + + /* + * First handle the band that doesn't intersect, if any. + * + * Note that attention is restricted to one band in the + * non-intersecting region at once, so if a region has n + * bands between the current position and the next place it overlaps + * the other, this entire loop will be passed through n times. + */ + if (r1y1 < r2y1) { + if (append_non1) { + top = MAX(r1y1, ybot); + bot = MIN(r1->y2, r2y1); + if (top != bot) { + cur_band = new_reg->data->numRects; + if (!pixman_region_append_non_o(new_reg, r1, r1_band_end, + top, bot)) + goto bail; + COALESCE(new_reg, prev_band, cur_band); + } + } + ytop = r2y1; + } else if (r2y1 < r1y1) { + if (append_non2) { + top = MAX(r2y1, ybot); + bot = MIN(r2->y2, r1y1); + + if (top != bot) { + cur_band = new_reg->data->numRects; + + if (!pixman_region_append_non_o(new_reg, r2, r2_band_end, + top, bot)) + goto bail; + + COALESCE(new_reg, prev_band, cur_band); + } + } + ytop = r1y1; + } else { + ytop = r1y1; + } + + /* + * Now see if we've hit an intersecting band. The two bands only + * intersect if ybot > ytop + */ + ybot = MIN(r1->y2, r2->y2); + if (ybot > ytop) { + cur_band = new_reg->data->numRects; + + if (!(*overlap_func)(new_reg, r1, r1_band_end, r2, r2_band_end, + ytop, ybot)) { + goto bail; + } + + COALESCE(new_reg, prev_band, cur_band); + } + + /* + * If we've finished with a band (y2 == ybot) we skip forward + * in the region to the next band. + */ + if (r1->y2 == ybot) r1 = r1_band_end; + + if (r2->y2 == ybot) r2 = r2_band_end; + + } while (r1 != r1_end && r2 != r2_end); + + /* + * Deal with whichever region (if any) still has rectangles left. + * + * We only need to worry about banding and coalescing for the very first + * band left. After that, we can just group all remaining boxes, + * regardless of how many bands, into one final append to the list. + */ + + if ((r1 != r1_end) && append_non1) { + /* Do first non_overlap1Func call, which may be able to coalesce */ + FIND_BAND(r1, r1_band_end, r1_end, r1y1); + + cur_band = new_reg->data->numRects; + + if (!pixman_region_append_non_o(new_reg, r1, r1_band_end, + MAX(r1y1, ybot), r1->y2)) { + goto bail; + } + + COALESCE(new_reg, prev_band, cur_band); + + /* Just append the rest of the boxes */ + APPEND_REGIONS(new_reg, r1_band_end, r1_end); + } else if ((r2 != r2_end) && append_non2) { + /* Do first non_overlap2Func call, which may be able to coalesce */ + FIND_BAND(r2, r2_band_end, r2_end, r2y1); + + cur_band = new_reg->data->numRects; + + if (!pixman_region_append_non_o(new_reg, r2, r2_band_end, + MAX(r2y1, ybot), r2->y2)) { + goto bail; + } + + COALESCE(new_reg, prev_band, cur_band); + + /* Append rest of boxes */ + APPEND_REGIONS(new_reg, r2_band_end, r2_end); + } + + free(old_data); + + if (!(numRects = new_reg->data->numRects)) { + FREE_DATA(new_reg); + new_reg->data = pixman_region_empty_data; + } else if (numRects == 1) { + new_reg->extents = *PIXREGION_BOXPTR(new_reg); + FREE_DATA(new_reg); + new_reg->data = (region_data_type_t *)NULL; + } else { + DOWNSIZE(new_reg, numRects); + } + + return TRUE; + +bail: + free(old_data); + + return pixman_break(new_reg); +} + +/*- + *----------------------------------------------------------------------- + * pixman_set_extents -- + * Reset the extents of a region to what they should be. Called by + * pixman_region_subtract and pixman_region_intersect as they can't + * figure it out along the way or do so easily, as pixman_region_union can. + * + * Results: + * None. + * + * Side Effects: + * The region's 'extents' structure is overwritten. + * + *----------------------------------------------------------------------- + */ +static void pixman_set_extents(region_type_t *region) +{ + box_type_t *box, *box_end; + + if (!region->data) return; + + if (!region->data->size) { + region->extents.x2 = region->extents.x1; + region->extents.y2 = region->extents.y1; + return; + } + + box = PIXREGION_BOXPTR(region); + box_end = PIXREGION_END(region); + + /* + * Since box is the first rectangle in the region, it must have the + * smallest y1 and since box_end is the last rectangle in the region, + * it must have the largest y2, because of banding. Initialize x1 and + * x2 from box and box_end, resp., as good things to initialize them + * to... + */ + region->extents.x1 = box->x1; + region->extents.y1 = box->y1; + region->extents.x2 = box_end->x2; + region->extents.y2 = box_end->y2; + + critical_if_fail(region->extents.y1 < region->extents.y2); + + while (box <= box_end) { + if (box->x1 < region->extents.x1) region->extents.x1 = box->x1; + if (box->x2 > region->extents.x2) region->extents.x2 = box->x2; + box++; + } + + critical_if_fail(region->extents.x1 < region->extents.x2); +} + +/*====================================================================== + * Region Intersection + *====================================================================*/ +/*- + *----------------------------------------------------------------------- + * pixman_region_intersect_o -- + * Handle an overlapping band for pixman_region_intersect. + * + * Results: + * TRUE if successful. + * + * Side Effects: + * Rectangles may be added to the region. + * + *----------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static pixman_bool_t pixman_region_intersect_o( + region_type_t *region, box_type_t *r1, box_type_t *r1_end, box_type_t *r2, + box_type_t *r2_end, int y1, int y2) +{ + int x1; + int x2; + box_type_t *next_rect; + + next_rect = PIXREGION_TOP(region); + + critical_if_fail(y1 < y2); + critical_if_fail(r1 != r1_end && r2 != r2_end); + + do { + x1 = MAX(r1->x1, r2->x1); + x2 = MIN(r1->x2, r2->x2); + + /* + * If there's any overlap between the two rectangles, add that + * overlap to the new region. + */ + if (x1 < x2) NEWRECT(region, next_rect, x1, y1, x2, y2); + + /* + * Advance the pointer(s) with the leftmost right side, since the next + * rectangle on that list may still overlap the other region's + * current rectangle. + */ + if (r1->x2 == x2) { + r1++; + } + if (r2->x2 == x2) { + r2++; + } + } while ((r1 != r1_end) && (r2 != r2_end)); + + return TRUE; +} + +PIXMAN_EXPORT pixman_bool_t PREFIX(_intersect)(region_type_t *new_reg, + region_type_t *reg1, + region_type_t *reg2) +{ + GOOD(reg1); + GOOD(reg2); + GOOD(new_reg); + + /* check for trivial reject */ + if (PIXREGION_NIL(reg1) || PIXREGION_NIL(reg2) || + !EXTENTCHECK(®1->extents, ®2->extents)) { + /* Covers about 20% of all cases */ + FREE_DATA(new_reg); + new_reg->extents.x2 = new_reg->extents.x1; + new_reg->extents.y2 = new_reg->extents.y1; + if (PIXREGION_NAR(reg1) || PIXREGION_NAR(reg2)) { + new_reg->data = pixman_broken_data; + return FALSE; + } else { + new_reg->data = pixman_region_empty_data; + } + } else if (!reg1->data && !reg2->data) { + /* Covers about 80% of cases that aren't trivially rejected */ + new_reg->extents.x1 = MAX(reg1->extents.x1, reg2->extents.x1); + new_reg->extents.y1 = MAX(reg1->extents.y1, reg2->extents.y1); + new_reg->extents.x2 = MIN(reg1->extents.x2, reg2->extents.x2); + new_reg->extents.y2 = MIN(reg1->extents.y2, reg2->extents.y2); + + FREE_DATA(new_reg); + + new_reg->data = (region_data_type_t *)NULL; + } else if (!reg2->data && SUBSUMES(®2->extents, ®1->extents)) { + return PREFIX(_copy)(new_reg, reg1); + } else if (!reg1->data && SUBSUMES(®1->extents, ®2->extents)) { + return PREFIX(_copy)(new_reg, reg2); + } else if (reg1 == reg2) { + return PREFIX(_copy)(new_reg, reg1); + } else { + /* General purpose intersection */ + + if (!pixman_op(new_reg, reg1, reg2, pixman_region_intersect_o, FALSE, + FALSE)) + return FALSE; + + pixman_set_extents(new_reg); + } + + GOOD(new_reg); + return (TRUE); +} + +#define MERGERECT(r) \ + do { \ + if (r->x1 <= x2) { \ + /* Merge with current rectangle */ \ + if (x2 < r->x2) x2 = r->x2; \ + } else { \ + /* Add current rectangle, start new one */ \ + NEWRECT(region, next_rect, x1, y1, x2, y2); \ + x1 = r->x1; \ + x2 = r->x2; \ + } \ + r++; \ + } while (0) + +/*====================================================================== + * Region Union + *====================================================================*/ + +/*- + *----------------------------------------------------------------------- + * pixman_region_union_o -- + * Handle an overlapping band for the union operation. Picks the + * left-most rectangle each time and merges it into the region. + * + * Results: + * TRUE if successful. + * + * Side Effects: + * region is overwritten. + * overlap is set to TRUE if any boxes overlap. + * + *----------------------------------------------------------------------- + */ +static pixman_bool_t pixman_region_union_o(region_type_t *region, + box_type_t *r1, box_type_t *r1_end, + box_type_t *r2, box_type_t *r2_end, + int y1, int y2) +{ + box_type_t *next_rect; + int x1; /* left and right side of current union */ + int x2; + + critical_if_fail(y1 < y2); + critical_if_fail(r1 != r1_end && r2 != r2_end); + + next_rect = PIXREGION_TOP(region); + + /* Start off current rectangle */ + if (r1->x1 < r2->x1) { + x1 = r1->x1; + x2 = r1->x2; + r1++; + } else { + x1 = r2->x1; + x2 = r2->x2; + r2++; + } + while (r1 != r1_end && r2 != r2_end) { + if (r1->x1 < r2->x1) + MERGERECT(r1); + else + MERGERECT(r2); + } + + /* Finish off whoever (if any) is left */ + if (r1 != r1_end) { + do { + MERGERECT(r1); + } while (r1 != r1_end); + } else if (r2 != r2_end) { + do { + MERGERECT(r2); + } while (r2 != r2_end); + } + + /* Add current rectangle */ + NEWRECT(region, next_rect, x1, y1, x2, y2); + + return TRUE; +} + +PIXMAN_EXPORT pixman_bool_t PREFIX(_intersect_rect)(region_type_t *dest, + region_type_t *source, + int x, int y, + unsigned int width, + unsigned int height) +{ + region_type_t region; + + region.data = NULL; + region.extents.x1 = x; + region.extents.y1 = y; + region.extents.x2 = x + width; + region.extents.y2 = y + height; + + return PREFIX(_intersect)(dest, source, ®ion); +} + +PIXMAN_EXPORT pixman_bool_t PREFIX(_union)(region_type_t *new_reg, + region_type_t *reg1, + region_type_t *reg2); + +/* Convenience function for performing union of region with a + * single rectangle + */ +PIXMAN_EXPORT pixman_bool_t PREFIX(_union_rect)(region_type_t *dest, + region_type_t *source, int x, + int y, unsigned int width, + unsigned int height) +{ + region_type_t region; + + region.extents.x1 = x; + region.extents.y1 = y; + region.extents.x2 = x + width; + region.extents.y2 = y + height; + + if (!GOOD_RECT(®ion.extents)) { + if (BAD_RECT(®ion.extents)) + _pixman_log_error(FUNC, "Invalid rectangle passed"); + return PREFIX(_copy)(dest, source); + } + + region.data = NULL; + + return PREFIX(_union)(dest, source, ®ion); +} + +PIXMAN_EXPORT pixman_bool_t PREFIX(_union)(region_type_t *new_reg, + region_type_t *reg1, + region_type_t *reg2) +{ + /* Return TRUE if some overlap + * between reg1, reg2 + */ + GOOD(reg1); + GOOD(reg2); + GOOD(new_reg); + + /* checks all the simple cases */ + + /* + * Region 1 and 2 are the same + */ + if (reg1 == reg2) return PREFIX(_copy)(new_reg, reg1); + + /* + * Region 1 is empty + */ + if (PIXREGION_NIL(reg1)) { + if (PIXREGION_NAR(reg1)) return pixman_break(new_reg); + + if (new_reg != reg2) return PREFIX(_copy)(new_reg, reg2); + + return TRUE; + } + + /* + * Region 2 is empty + */ + if (PIXREGION_NIL(reg2)) { + if (PIXREGION_NAR(reg2)) return pixman_break(new_reg); + + if (new_reg != reg1) return PREFIX(_copy)(new_reg, reg1); + + return TRUE; + } + + /* + * Region 1 completely subsumes region 2 + */ + if (!reg1->data && SUBSUMES(®1->extents, ®2->extents)) { + if (new_reg != reg1) return PREFIX(_copy)(new_reg, reg1); + + return TRUE; + } + + /* + * Region 2 completely subsumes region 1 + */ + if (!reg2->data && SUBSUMES(®2->extents, ®1->extents)) { + if (new_reg != reg2) return PREFIX(_copy)(new_reg, reg2); + + return TRUE; + } + + if (!pixman_op(new_reg, reg1, reg2, pixman_region_union_o, TRUE, TRUE)) + return FALSE; + + new_reg->extents.x1 = MIN(reg1->extents.x1, reg2->extents.x1); + new_reg->extents.y1 = MIN(reg1->extents.y1, reg2->extents.y1); + new_reg->extents.x2 = MAX(reg1->extents.x2, reg2->extents.x2); + new_reg->extents.y2 = MAX(reg1->extents.y2, reg2->extents.y2); + + GOOD(new_reg); + + return TRUE; +} + +/*====================================================================== + * Region Subtraction + *====================================================================*/ + +/*- + *----------------------------------------------------------------------- + * pixman_region_subtract_o -- + * Overlapping band subtraction. x1 is the left-most point not yet + * checked. + * + * Results: + * TRUE if successful. + * + * Side Effects: + * region may have rectangles added to it. + * + *----------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static pixman_bool_t pixman_region_subtract_o( + region_type_t *region, box_type_t *r1, box_type_t *r1_end, box_type_t *r2, + box_type_t *r2_end, int y1, int y2) +{ + box_type_t *next_rect; + int x1; + + x1 = r1->x1; + + critical_if_fail(y1 < y2); + critical_if_fail(r1 != r1_end && r2 != r2_end); + + next_rect = PIXREGION_TOP(region); + + do { + if (r2->x2 <= x1) { + /* + * Subtrahend entirely to left of minuend: go to next subtrahend. + */ + r2++; + } else if (r2->x1 <= x1) { + /* + * Subtrahend precedes minuend: nuke left edge of minuend. + */ + x1 = r2->x2; + if (x1 >= r1->x2) { + /* + * Minuend completely covered: advance to next minuend and + * reset left fence to edge of new minuend. + */ + r1++; + if (r1 != r1_end) x1 = r1->x1; + } else { + /* + * Subtrahend now used up since it doesn't extend beyond + * minuend + */ + r2++; + } + } else if (r2->x1 < r1->x2) { + /* + * Left part of subtrahend covers part of minuend: add uncovered + * part of minuend to region and skip to next subtrahend. + */ + critical_if_fail(x1 < r2->x1); + NEWRECT(region, next_rect, x1, y1, r2->x1, y2); + + x1 = r2->x2; + if (x1 >= r1->x2) { + /* + * Minuend used up: advance to new... + */ + r1++; + if (r1 != r1_end) x1 = r1->x1; + } else { + /* + * Subtrahend used up + */ + r2++; + } + } else { + /* + * Minuend used up: add any remaining piece before advancing. + */ + if (r1->x2 > x1) NEWRECT(region, next_rect, x1, y1, r1->x2, y2); + + r1++; + + if (r1 != r1_end) x1 = r1->x1; + } + } while ((r1 != r1_end) && (r2 != r2_end)); + + /* + * Add remaining minuend rectangles to region. + */ + while (r1 != r1_end) { + critical_if_fail(x1 < r1->x2); + + NEWRECT(region, next_rect, x1, y1, r1->x2, y2); + + r1++; + if (r1 != r1_end) x1 = r1->x1; + } + return TRUE; +} + +/*- + *----------------------------------------------------------------------- + * pixman_region_subtract -- + * Subtract reg_s from reg_m and leave the result in reg_d. + * S stands for subtrahend, M for minuend and D for difference. + * + * Results: + * TRUE if successful. + * + * Side Effects: + * reg_d is overwritten. + * + *----------------------------------------------------------------------- + */ +PIXMAN_EXPORT pixman_bool_t PREFIX(_subtract)(region_type_t *reg_d, + region_type_t *reg_m, + region_type_t *reg_s) +{ + GOOD(reg_m); + GOOD(reg_s); + GOOD(reg_d); + + /* check for trivial rejects */ + if (PIXREGION_NIL(reg_m) || PIXREGION_NIL(reg_s) || + !EXTENTCHECK(®_m->extents, ®_s->extents)) { + if (PIXREGION_NAR(reg_s)) return pixman_break(reg_d); + + return PREFIX(_copy)(reg_d, reg_m); + } else if (reg_m == reg_s) { + FREE_DATA(reg_d); + reg_d->extents.x2 = reg_d->extents.x1; + reg_d->extents.y2 = reg_d->extents.y1; + reg_d->data = pixman_region_empty_data; + + return TRUE; + } + + /* Add those rectangles in region 1 that aren't in region 2, + do yucky subtraction for overlaps, and + just throw away rectangles in region 2 that aren't in region 1 */ + if (!pixman_op(reg_d, reg_m, reg_s, pixman_region_subtract_o, TRUE, FALSE)) + return FALSE; + + /* + * Can't alter reg_d's extents before we call pixman_op because + * it might be one of the source regions and pixman_op depends + * on the extents of those regions being unaltered. Besides, this + * way there's no checking against rectangles that will be nuked + * due to coalescing, so we have to examine fewer rectangles. + */ + pixman_set_extents(reg_d); + GOOD(reg_d); + return TRUE; +} +#if 0 +/*====================================================================== + * Region Inversion + *====================================================================*/ + +/*- + *----------------------------------------------------------------------- + * pixman_region_inverse -- + * Take a region and a box and return a region that is everything + * in the box but not in the region. The careful reader will note + * that this is the same as subtracting the region from the box... + * + * Results: + * TRUE. + * + * Side Effects: + * new_reg is overwritten. + * + *----------------------------------------------------------------------- + */ +PIXMAN_EXPORT pixman_bool_t +PREFIX (_inverse) (region_type_t *new_reg, /* Destination region */ + region_type_t *reg1, /* Region to invert */ + box_type_t * inv_rect) /* Bounding box for inversion */ +{ + region_type_t inv_reg; /* Quick and dirty region made from the + * bounding box */ + GOOD (reg1); + GOOD (new_reg); + + /* check for trivial rejects */ + if (PIXREGION_NIL (reg1) || !EXTENTCHECK (inv_rect, ®1->extents)) + { + if (PIXREGION_NAR (reg1)) + return pixman_break (new_reg); + + new_reg->extents = *inv_rect; + FREE_DATA (new_reg); + new_reg->data = (region_data_type_t *)NULL; + + return TRUE; + } + + /* Add those rectangles in region 1 that aren't in region 2, + * do yucky subtraction for overlaps, and + * just throw away rectangles in region 2 that aren't in region 1 + */ + inv_reg.extents = *inv_rect; + inv_reg.data = (region_data_type_t *)NULL; + if (!pixman_op (new_reg, &inv_reg, reg1, pixman_region_subtract_o, TRUE, FALSE)) + return FALSE; + + /* + * Can't alter new_reg's extents before we call pixman_op because + * it might be one of the source regions and pixman_op depends + * on the extents of those regions being unaltered. Besides, this + * way there's no checking against rectangles that will be nuked + * due to coalescing, so we have to examine fewer rectangles. + */ + pixman_set_extents (new_reg); + GOOD (new_reg); + return TRUE; +} +#endif +/* In time O(log n), locate the first box whose y2 is greater than y. + * Return @end if no such box exists. + */ +static box_type_t *find_box_for_y(box_type_t *begin, box_type_t *end, int y) +{ + box_type_t *mid; + + if (end == begin) return end; + + if (end - begin == 1) { + if (begin->y2 > y) + return begin; + else + return end; + } + + mid = begin + (end - begin) / 2; + if (mid->y2 > y) { + /* If no box is found in [begin, mid], the function + * will return @mid, which is then known to be the + * correct answer. + */ + return find_box_for_y(begin, mid, y); + } else { + return find_box_for_y(mid, end, y); + } +} + +/* + * rect_in(region, rect) + * This routine takes a pointer to a region and a pointer to a box + * and determines if the box is outside/inside/partly inside the region. + * + * The idea is to travel through the list of rectangles trying to cover the + * passed box with them. Anytime a piece of the rectangle isn't covered + * by a band of rectangles, part_out is set TRUE. Any time a rectangle in + * the region covers part of the box, part_in is set TRUE. The process ends + * when either the box has been completely covered (we reached a band that + * doesn't overlap the box, part_in is TRUE and part_out is false), the + * box has been partially covered (part_in == part_out == TRUE -- because of + * the banding, the first time this is true we know the box is only + * partially in the region) or is outside the region (we reached a band + * that doesn't overlap the box at all and part_in is false) + */ +PIXMAN_EXPORT pixman_region_overlap_t + PREFIX(_contains_rectangle)(region_type_t *region, box_type_t *prect) +{ + box_type_t *pbox; + box_type_t *pbox_end; + int part_in, part_out; + int numRects; + int x, y; + + GOOD(region); + + numRects = PIXREGION_NUMRECTS(region); + + /* useful optimization */ + if (!numRects || !EXTENTCHECK(®ion->extents, prect)) + return (PIXMAN_REGION_OUT); + + if (numRects == 1) { + /* We know that it must be PIXMAN_REGION_IN or PIXMAN_REGION_PART */ + if (SUBSUMES(®ion->extents, prect)) + return (PIXMAN_REGION_IN); + else + return (PIXMAN_REGION_PART); + } + + part_out = FALSE; + part_in = FALSE; + + /* (x,y) starts at upper left of rect, moving to the right and down */ + x = prect->x1; + y = prect->y1; + + /* can stop when both part_out and part_in are TRUE, or we reach prect->y2 + */ + for (pbox = PIXREGION_BOXPTR(region), pbox_end = pbox + numRects; + pbox != pbox_end; pbox++) { + /* getting up to speed or skipping remainder of band */ + if (pbox->y2 <= y) { + if ((pbox = find_box_for_y(pbox, pbox_end, y)) == pbox_end) break; + } + + if (pbox->y1 > y) { + part_out = TRUE; /* missed part of rectangle above */ + if (part_in || (pbox->y1 >= prect->y2)) break; + y = pbox->y1; /* x guaranteed to be == prect->x1 */ + } + + if (pbox->x2 <= x) continue; /* not far enough over yet */ + + if (pbox->x1 > x) { + part_out = TRUE; /* missed part of rectangle to left */ + if (part_in) break; + } + + if (pbox->x1 < prect->x2) { + part_in = TRUE; /* definitely overlap */ + if (part_out) break; + } + + if (pbox->x2 >= prect->x2) { + y = pbox->y2; /* finished with this band */ + if (y >= prect->y2) break; + x = prect->x1; /* reset x out to left again */ + } else { + /* + * Because boxes in a band are maximal width, if the first box + * to overlap the rectangle doesn't completely cover it in that + * band, the rectangle must be partially out, since some of it + * will be uncovered in that band. part_in will have been set true + * by now... + */ + part_out = TRUE; + break; + } + } + + if (part_in) { + if (y < prect->y2) + return PIXMAN_REGION_PART; + else + return PIXMAN_REGION_IN; + } else { + return PIXMAN_REGION_OUT; + } +} + +/* PREFIX(_translate) (region, x, y) + * translates in place + */ + +PIXMAN_EXPORT void PREFIX(_translate)(region_type_t *region, int x, int y) +{ + overflow_int_t x1, x2, y1, y2; + int nbox; + box_type_t * pbox; + + GOOD(region); + region->extents.x1 = x1 = region->extents.x1 + x; + region->extents.y1 = y1 = region->extents.y1 + y; + region->extents.x2 = x2 = region->extents.x2 + x; + region->extents.y2 = y2 = region->extents.y2 + y; + + if (((x1 - PIXMAN_REGION_MIN) | (y1 - PIXMAN_REGION_MIN) | + (PIXMAN_REGION_MAX - x2) | (PIXMAN_REGION_MAX - y2)) >= 0) { + if (region->data && (nbox = region->data->numRects)) { + for (pbox = PIXREGION_BOXPTR(region); nbox--; pbox++) { + pbox->x1 += x; + pbox->y1 += y; + pbox->x2 += x; + pbox->y2 += y; + } + } + return; + } + + if (((x2 - PIXMAN_REGION_MIN) | (y2 - PIXMAN_REGION_MIN) | + (PIXMAN_REGION_MAX - x1) | (PIXMAN_REGION_MAX - y1)) <= 0) { + region->extents.x2 = region->extents.x1; + region->extents.y2 = region->extents.y1; + FREE_DATA(region); + region->data = pixman_region_empty_data; + return; + } + + if (x1 < PIXMAN_REGION_MIN) + region->extents.x1 = PIXMAN_REGION_MIN; + else if (x2 > PIXMAN_REGION_MAX) + region->extents.x2 = PIXMAN_REGION_MAX; + + if (y1 < PIXMAN_REGION_MIN) + region->extents.y1 = PIXMAN_REGION_MIN; + else if (y2 > PIXMAN_REGION_MAX) + region->extents.y2 = PIXMAN_REGION_MAX; + + if (region->data && (nbox = region->data->numRects)) { + box_type_t *pbox_out; + + for (pbox_out = pbox = PIXREGION_BOXPTR(region); nbox--; pbox++) { + pbox_out->x1 = x1 = pbox->x1 + x; + pbox_out->y1 = y1 = pbox->y1 + y; + pbox_out->x2 = x2 = pbox->x2 + x; + pbox_out->y2 = y2 = pbox->y2 + y; + + if (((x2 - PIXMAN_REGION_MIN) | (y2 - PIXMAN_REGION_MIN) | + (PIXMAN_REGION_MAX - x1) | (PIXMAN_REGION_MAX - y1)) <= 0) { + region->data->numRects--; + continue; + } + + if (x1 < PIXMAN_REGION_MIN) + pbox_out->x1 = PIXMAN_REGION_MIN; + else if (x2 > PIXMAN_REGION_MAX) + pbox_out->x2 = PIXMAN_REGION_MAX; + + if (y1 < PIXMAN_REGION_MIN) + pbox_out->y1 = PIXMAN_REGION_MIN; + else if (y2 > PIXMAN_REGION_MAX) + pbox_out->y2 = PIXMAN_REGION_MAX; + + pbox_out++; + } + + if (pbox_out != pbox) { + if (region->data->numRects == 1) { + region->extents = *PIXREGION_BOXPTR(region); + FREE_DATA(region); + region->data = (region_data_type_t *)NULL; + } else { + pixman_set_extents(region); + } + } + } + + GOOD(region); +} + +PIXMAN_EXPORT int PREFIX(_not_empty)(region_type_t *region) +{ + GOOD(region); + + return (!PIXREGION_NIL(region)); +} + +PIXMAN_EXPORT box_type_t *PREFIX(_extents)(region_type_t *region) +{ + GOOD(region); + + return (®ion->extents); +} + +typedef region_type_t VRegionPrivate; + +#include "vregion.h" + +V_BEGIN_NAMESPACE + +static VRegionPrivate regionPrivate = {{0, 0, 0, 0}, NULL}; + +struct VRegionData { + VRegionData() : ref(-1), rgn(®ionPrivate) {} + RefCount ref; + VRegionPrivate *rgn; +}; + +const VRegionData shared_empty; + +inline VRect box_to_rect(box_type_t *box) +{ + return {box->x1, box->y1, box->x2 - box->x1, box->y2 - box->y1}; +} + +void VRegion::cleanUp(VRegionData *x) +{ + if (x->rgn) { + PREFIX(_fini)(x->rgn); + delete x->rgn; + } + delete x; +} + +void VRegion::detach() +{ + if (d->ref.isShared()) *this = copy(); +} + +VRegion VRegion::copy() const +{ + VRegion r; + + r.d = new VRegionData; + r.d->rgn = new VRegionPrivate; + r.d->ref.setOwned(); + PREFIX(_init)(r.d->rgn); + if (d != &shared_empty) PREFIX(_copy)(r.d->rgn, d->rgn); + return r; +} + +VRegion::VRegion() : d(const_cast(&shared_empty)) {} + +VRegion::VRegion(int x, int y, int w, int h) +{ + VRegion tmp(VRect(x, y, w, h)); + tmp.d->ref.ref(); + d = tmp.d; +} + +VRegion::VRegion(const VRect &r) +{ + if (r.empty()) { + d = const_cast(&shared_empty); + } else { + d = new VRegionData; + d->rgn = new VRegionPrivate; + d->ref.setOwned(); + PREFIX(_init_rect)(d->rgn, r.left(), r.top(), r.width(), r.height()); + } +} + +VRegion::VRegion(const VRegion &r) +{ + d = r.d; + d->ref.ref(); +} + +VRegion::VRegion(VRegion &&other) : d(other.d) +{ + other.d = const_cast(&shared_empty); +} + +VRegion &VRegion::operator=(const VRegion &r) +{ + r.d->ref.ref(); + if (!d->ref.deref()) cleanUp(d); + + d = r.d; + return *this; +} + +inline VRegion &VRegion::operator=(VRegion &&other) +{ + if (!d->ref.deref()) cleanUp(d); + d = other.d; + other.d = const_cast(&shared_empty); + return *this; +} + +VRegion::~VRegion() +{ + if (!d->ref.deref()) cleanUp(d); +} + +bool VRegion::empty() const +{ + return d == &shared_empty || !PREFIX(_not_empty)(d->rgn); +} + +void VRegion::translate(const VPoint &p) +{ + if (p == VPoint() || empty()) return; + + detach(); + PREFIX(_translate)(d->rgn, p.x(), p.y()); +} + +VRegion VRegion::translated(const VPoint &p) const +{ + VRegion ret(*this); + ret.translate(p); + return ret; +} + +/* + * Returns \c true if this region is guaranteed to be fully contained in r. + */ +bool VRegion::within(const VRect &r1) const +{ + box_type_t *r2 = PREFIX(_extents)(d->rgn); + + return r2->x1 >= r1.left() && r2->x2 <= r1.right() && r2->y1 >= r1.top() && + r2->y2 <= r1.bottom(); +} + +bool VRegion::contains(const VRect &r) const +{ + box_type_t box = {r.left(), r.top(), r.right(), r.bottom()}; + + pixman_region_overlap_t res = PREFIX(_contains_rectangle)(d->rgn, &box); + if (res == PIXMAN_REGION_IN) return true; + return false; +} + +VRegion VRegion::united(const VRect &r) const +{ + if (empty()) return r; + + if (contains(r)) { + return *this; + } else if (within(r)) { + return r; + } else { + VRegion result; + result.detach(); + PREFIX(_union_rect) + (result.d->rgn, d->rgn, r.left(), r.top(), r.width(), r.height()); + return result; + } +} + +VRegion VRegion::united(const VRegion &r) const +{ + if (empty()) return r; + if (r.empty()) return *this; + if (d == r.d || PREFIX(_equal)(d->rgn, r.d->rgn)) return *this; + VRegion result; + result.detach(); + PREFIX(_union)(result.d->rgn, d->rgn, r.d->rgn); + return result; +} + +VRegion VRegion::intersected(const VRect &r) const +{ + if (empty() || r.empty()) return VRegion(); + + /* this is fully contained in r */ + if (within(r)) return *this; + + /* r is fully contained in this */ + if (contains(r)) return r; + + VRegion result; + result.detach(); + PREFIX(_intersect_rect) + (result.d->rgn, d->rgn, r.left(), r.top(), r.width(), r.height()); + return result; +} + +VRegion VRegion::intersected(const VRegion &r) const +{ + if (empty() || r.empty()) return VRegion(); + + VRegion result; + result.detach(); + PREFIX(_intersect)(result.d->rgn, d->rgn, r.d->rgn); + + return result; +} + +VRegion VRegion::subtracted(const VRegion &r) const +{ + if (empty() || r.empty()) return *this; + if (d == r.d || PREFIX(_equal)(d->rgn, r.d->rgn)) return VRegion(); + + VRegion result; + result.detach(); + PREFIX(_subtract)(result.d->rgn, d->rgn, r.d->rgn); + return result; +} + +int VRegion::rectCount() const +{ + if (empty()) return 0; + return PREFIX(_n_rects)(d->rgn); +} + +VRect VRegion::rectAt(int index) const +{ + VRegionPrivate *reg = d->rgn; + if (!reg) return {}; + + box_type_t *box = PIXREGION_RECTS(reg) + index; + + return box_to_rect(box); +} + +VRegion VRegion::operator+(const VRect &r) const +{ + return united(r); +} + +VRegion VRegion::operator+(const VRegion &r) const +{ + return united(r); +} + +VRegion VRegion::operator-(const VRegion &r) const +{ + return subtracted(r); +} + +VRegion &VRegion::operator+=(const VRect &r) +{ + if (empty()) return *this = r; + if (r.empty()) return *this; + + if (contains(r)) { + return *this; + } else if (within(r)) { + return *this = r; + } else { + detach(); + PREFIX(_union_rect) + (d->rgn, d->rgn, r.left(), r.top(), r.width(), r.height()); + return *this; + } +} + +VRegion &VRegion::operator+=(const VRegion &r) +{ + if (empty()) return *this = r; + if (r.empty()) return *this; + if (d == r.d || PREFIX(_equal)(d->rgn, r.d->rgn)) return *this; + + detach(); + PREFIX(_union)(d->rgn, d->rgn, r.d->rgn); + return *this; +} + +VRegion &VRegion::operator-=(const VRegion &r) +{ + return *this = *this - r; +} + +bool VRegion::operator==(const VRegion &r) const +{ + if (empty()) return r.empty(); + if (r.empty()) return empty(); + + if (d == r.d) + return true; + else + return PREFIX(_equal)(d->rgn, r.d->rgn); +} + +VRect VRegion::boundingRect() const noexcept +{ + if (empty()) return {}; + return box_to_rect(&d->rgn->extents); +} + +inline bool rect_intersects(const VRect &r1, const VRect &r2) +{ + return (r1.right() >= r2.left() && r1.left() <= r2.right() && + r1.bottom() >= r2.top() && r1.top() <= r2.bottom()); +} + +bool VRegion::intersects(const VRegion &r) const +{ + if (empty() || r.empty()) return false; + + return PREFIX(_intersects)(d->rgn, r.d->rgn); +} + +VDebug &operator<<(VDebug &os, const VRegion &o) +{ + os << "[REGION: " + << "[bbox = " << o.boundingRect() << "]"; + os << "[rectCount = " << o.rectCount() << "]"; + os << "[rects = "; + for (int i = 0; i < o.rectCount(); i++) { + os << o.rectAt(i); + } + os << "]" + << "]"; + return os; +} + +V_END_NAMESPACE diff --git a/TMessagesProj/jni/rlottie/src/vector/pixman/vregion.h b/TMessagesProj/jni/rlottie/src/vector/pixman/vregion.h new file mode 100755 index 000000000..ecb17f253 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/pixman/vregion.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VREGION_H +#define VREGION_H +#include +#include +#include +#include +#include "vdebug.h" + +V_BEGIN_NAMESPACE + +struct VRegionData; + +class VRegion { +public: + VRegion(); + VRegion(int x, int y, int w, int h); + VRegion(const VRect &r); + VRegion(const VRegion ®ion); + VRegion(VRegion &&other); + ~VRegion(); + VRegion & operator=(const VRegion &); + VRegion & operator=(VRegion &&); + bool empty() const; + bool contains(const VRect &r) const; + VRegion united(const VRect &r) const; + VRegion united(const VRegion &r) const; + VRegion intersected(const VRect &r) const; + VRegion intersected(const VRegion &r) const; + VRegion subtracted(const VRegion &r) const; + void translate(const VPoint &p); + inline void translate(int dx, int dy); + VRegion translated(const VPoint &p) const; + inline VRegion translated(int dx, int dy) const; + int rectCount() const; + VRect rectAt(int index) const; + + VRegion operator+(const VRect &r) const; + VRegion operator+(const VRegion &r) const; + VRegion operator-(const VRegion &r) const; + VRegion &operator+=(const VRect &r); + VRegion &operator+=(const VRegion &r); + VRegion &operator-=(const VRegion &r); + + VRect boundingRect() const noexcept; + bool intersects(const VRegion ®ion) const; + + bool operator==(const VRegion &r) const; + inline bool operator!=(const VRegion &r) const { return !(operator==(r)); } + friend VDebug &operator<<(VDebug &os, const VRegion &o); + +private: + bool within(const VRect &r) const; + VRegion copy() const; + void detach(); + void cleanUp(VRegionData *x); + + struct VRegionData *d; +}; +inline void VRegion::translate(int dx, int dy) +{ + translate(VPoint(dx, dy)); +} + +inline VRegion VRegion::translated(int dx, int dy) const +{ + return translated(VPoint(dx, dy)); +} + +V_END_NAMESPACE + +#endif // VREGION_H diff --git a/TMessagesProj/jni/rlottie/src/vector/stb/stb_image.cpp b/TMessagesProj/jni/rlottie/src/vector/stb/stb_image.cpp new file mode 100755 index 000000000..985b058a7 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/stb/stb_image.cpp @@ -0,0 +1,51 @@ +#ifdef _WIN32 +#ifdef LOT_BUILD +#ifdef DLL_EXPORT +#define LOT_EXPORT __declspec(dllexport) +#else +#define LOT_EXPORT +#endif +#else +#define LOT_EXPORT __declspec(dllimport) +#endif +#else +#ifdef __GNUC__ +#if __GNUC__ >= 4 +#define LOT_EXPORT __attribute__((visibility("default"))) +#else +#define LOT_EXPORT +#endif +#else +#define LOT_EXPORT +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * exported function wrapper from the library + */ + +LOT_EXPORT unsigned char *lottie_image_load(char const *filename, int *x, + int *y, int *comp, int req_comp) +{ + return nullptr; +} + +LOT_EXPORT unsigned char *lottie_image_load_from_data(const char *imageData, + int len, int *x, int *y, + int *comp, int req_comp) +{ + return nullptr; +} + +LOT_EXPORT void lottie_image_free(unsigned char *data) +{ + +} + +#ifdef __cplusplus +} +#endif diff --git a/TMessagesProj/jni/rlottie/src/vector/stb/stb_image.h b/TMessagesProj/jni/rlottie/src/vector/stb/stb_image.h new file mode 100755 index 000000000..b8ab05537 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/stb/stb_image.h @@ -0,0 +1,7468 @@ +/* stb_image - v2.19 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine + John-Mark Allen + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Phil Jordan + Dave Moore Roy Eltham Hayaki Saito Nathan Reed + Won Chun Luke Graham Johan Duparc Nick Verigakis + the Horde3D community Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Laurent Gomila Cort Stratton Sergio Gonzalez github:snagar + Aruelien Pocheville Thibault Reuille Cass Everitt github:Zelex + Ryamond Barbiero Paul Du Bois Engin Manap github:grim210 + Aldo Culquicondor Philipp Wiesemann Dale Weiler github:sammyhw + Oriol Ferrer Mesia Josh Tobin Matthew Gregan github:phprus + Julian Raschke Gregory Mullen Baldur Karlsson github:poppolopoppo + Christian Floisand Kevin Schmidt github:darealshinji + Blazej Dariusz Roszkowski github:Michaelangel007 +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy to use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// make more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. You can still load any file through the existing interface; +// if you attempt to load an HDR file, it will be automatically remapped to +// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB, even though +// they are internally encoded differently. You can disable this conversion +// by by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through (which +// is BGR stored in RGB). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// + + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// NOT THREADSAFE +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + fseek((FILE*) user, n, SEEK_CUR); +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +// this is not threadsafe +static const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load = flag_true_if_should_flip; +} + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int /*bpc*/) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + if (ri.bits_per_channel != 8) { + STBI_ASSERT(ri.bits_per_channel == 16); + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + if (ri.bits_per_channel != 16) { + STBI_ASSERT(ri.bits_per_channel == 8); + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) || !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} + +static void stbi__skip(stbi__context *s, int n) +{ + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} + +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} + +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + return z + (stbi__get16le(s) << 16); +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0], dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0], dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB + k = stbi_lrot(j->code_buffer, n); + STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & ~sgn); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << j->succ_low); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << shift); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) << shift); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4]; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) { + stbi__errpuc("outofmem", "Out of memory"); + return 0; + } + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[288]; + stbi__uint16 value[288]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) stbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = old_limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) + c = stbi__zreceive(a,3)+3; + else { + STBI_ASSERT(c == 18); + c = stbi__zreceive(a,7)+11; + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[288] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT(img_width_bytes <= x); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth < 8) + ri->bits_per_channel = 8; + else + ri->bits_per_channel = p->depth; + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v >= 0 && v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; +} stbi__bmp_data; + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - 14 - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - 14 - info.hsz) >> 2; + } + + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - 14 - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + g->background = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + g->history = (stbi_uc *) stbi__malloc(g->w * g->h); + if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "tranparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to teh color that was there the previous frame. + memset( g->out, 0x00, 4 * g->w * g->h ); + memset( g->background, 0x00, 4 * g->w * g->h ); // state of the background (starts transparent) + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispoase of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (o == NULL) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + out = (stbi_uc*) STBI_REALLOC( out, layers * stride ); + if (delays) { + *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers ); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + stbi__rewind( s ); + if (p == NULL) + return 0; + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) *comp = info.ma ? 4 : 3; + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + (void) stbi__get32be(s); + (void) stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) +// Does not support 16-bit-per-channel + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + return 0; + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + + if (maxv > 255) + return stbi__err("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/TMessagesProj/jni/rlottie/src/vector/vbezier.cpp b/TMessagesProj/jni/rlottie/src/vector/vbezier.cpp new file mode 100755 index 000000000..c5d7a173d --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vbezier.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "vbezier.h" +#include +#include "vline.h" + +V_BEGIN_NAMESPACE + +VBezier VBezier::fromPoints(const VPointF &p1, const VPointF &p2, + const VPointF &p3, const VPointF &p4) +{ + VBezier b; + b.x1 = p1.x(); + b.y1 = p1.y(); + b.x2 = p2.x(); + b.y2 = p2.y(); + b.x3 = p3.x(); + b.y3 = p3.y(); + b.x4 = p4.x(); + b.y4 = p4.y(); + return b; +} + +float VBezier::length() const +{ + VBezier left, right; /* bez poly splits */ + float len = 0.0; /* arc length */ + float chord; /* chord length */ + float length; + + len = len + VLine::length(x1, y1, x2, y2); + len = len + VLine::length(x2, y2, x3, y3); + len = len + VLine::length(x3, y3, x4, y4); + + chord = VLine::length(x1, y1, x4, y4); + + if ((len - chord) > 0.01) { + split(&left, &right); /* split in two */ + length = left.length() + /* try left side */ + right.length(); /* try right side */ + + return length; + } + + return len; +} + +VBezier VBezier::onInterval(float t0, float t1) const +{ + if (t0 == 0 && t1 == 1) return *this; + + VBezier bezier = *this; + + VBezier result; + bezier.parameterSplitLeft(t0, &result); + float trueT = (t1 - t0) / (1 - t0); + bezier.parameterSplitLeft(trueT, &result); + + return result; +} + +float VBezier::tAtLength(float l) const +{ + float len = length(); + float t = 1.0; + const float error = 0.01; + if (l > len || vCompare(l, len)) return t; + + t *= 0.5; + + float lastBigger = 1.0; + while (1) { + VBezier right = *this; + VBezier left; + right.parameterSplitLeft(t, &left); + float lLen = left.length(); + if (fabs(lLen - l) < error) break; + + if (lLen < l) { + t += (lastBigger - t) * 0.5; + } else { + lastBigger = t; + t -= t * 0.5; + } + } + return t; +} + +void VBezier::splitAtLength(float len, VBezier *left, VBezier *right) +{ + float t; + + *right = *this; + t = right->tAtLength(len); + right->parameterSplitLeft(t, left); +} + +VPointF VBezier::derivative(float t) const +{ + // p'(t) = 3 * (-(1-2t+t^2) * p0 + (1 - 4 * t + 3 * t^2) * p1 + (2 * t - 3 * + // t^2) * p2 + t^2 * p3) + + float m_t = 1. - t; + + float d = t * t; + float a = -m_t * m_t; + float b = 1 - 4 * t + 3 * d; + float c = 2 * t - 3 * d; + + return 3 * VPointF(a * x1 + b * x2 + c * x3 + d * x4, + a * y1 + b * y2 + c * y3 + d * y4); +} + +float VBezier::angleAt(float t) const +{ + if (t < 0 || t > 1) { + return 0; + } + return VLine({}, derivative(t)).angle(); +} + +V_END_NAMESPACE diff --git a/TMessagesProj/jni/rlottie/src/vector/vbezier.h b/TMessagesProj/jni/rlottie/src/vector/vbezier.h new file mode 100755 index 000000000..b07e7824d --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vbezier.h @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VBEZIER_H +#define VBEZIER_H + +#include + +V_BEGIN_NAMESPACE + +class VBezier { +public: + VBezier() = default; + VPointF pointAt(float t) const; + float angleAt(float t) const; + VBezier onInterval(float t0, float t1) const; + float length() const; + static void coefficients(float t, float &a, float &b, float &c, float &d); + static VBezier fromPoints(const VPointF &start, const VPointF &cp1, + const VPointF &cp2, const VPointF &end); + inline void parameterSplitLeft(float t, VBezier *left); + inline void split(VBezier *firstHalf, VBezier *secondHalf) const; + float tAtLength(float len) const; + void splitAtLength(float len, VBezier *left, VBezier *right); + VPointF pt1() const { return {x1, y1}; } + VPointF pt2() const { return {x2, y2}; } + VPointF pt3() const { return {x3, y3}; } + VPointF pt4() const { return {x4, y4}; } + +private: + VPointF derivative(float t) const; + float x1, y1, x2, y2, x3, y3, x4, y4; +}; + +inline void VBezier::coefficients(float t, float &a, float &b, float &c, + float &d) +{ + float m_t = 1. - t; + b = m_t * m_t; + c = t * t; + d = c * t; + a = b * m_t; + b *= 3. * t; + c *= 3. * m_t; +} + +inline VPointF VBezier::pointAt(float t) const +{ + // numerically more stable: + float x, y; + + float m_t = 1. - t; + { + float a = x1 * m_t + x2 * t; + float b = x2 * m_t + x3 * t; + float c = x3 * m_t + x4 * t; + a = a * m_t + b * t; + b = b * m_t + c * t; + x = a * m_t + b * t; + } + { + float a = y1 * m_t + y2 * t; + float b = y2 * m_t + y3 * t; + float c = y3 * m_t + y4 * t; + a = a * m_t + b * t; + b = b * m_t + c * t; + y = a * m_t + b * t; + } + return {x, y}; +} + +inline void VBezier::parameterSplitLeft(float t, VBezier *left) +{ + left->x1 = x1; + left->y1 = y1; + + left->x2 = x1 + t * (x2 - x1); + left->y2 = y1 + t * (y2 - y1); + + left->x3 = x2 + t * (x3 - x2); // temporary holding spot + left->y3 = y2 + t * (y3 - y2); // temporary holding spot + + x3 = x3 + t * (x4 - x3); + y3 = y3 + t * (y4 - y3); + + x2 = left->x3 + t * (x3 - left->x3); + y2 = left->y3 + t * (y3 - left->y3); + + left->x3 = left->x2 + t * (left->x3 - left->x2); + left->y3 = left->y2 + t * (left->y3 - left->y2); + + left->x4 = x1 = left->x3 + t * (x2 - left->x3); + left->y4 = y1 = left->y3 + t * (y2 - left->y3); +} + +inline void VBezier::split(VBezier *firstHalf, VBezier *secondHalf) const +{ + float c = (x2 + x3) * .5; + firstHalf->x2 = (x1 + x2) * .5; + secondHalf->x3 = (x3 + x4) * .5; + firstHalf->x1 = x1; + secondHalf->x4 = x4; + firstHalf->x3 = (firstHalf->x2 + c) * .5; + secondHalf->x2 = (secondHalf->x3 + c) * .5; + firstHalf->x4 = secondHalf->x1 = (firstHalf->x3 + secondHalf->x2) * .5; + + c = (y2 + y3) / 2; + firstHalf->y2 = (y1 + y2) * .5; + secondHalf->y3 = (y3 + y4) * .5; + firstHalf->y1 = y1; + secondHalf->y4 = y4; + firstHalf->y3 = (firstHalf->y2 + c) * .5; + secondHalf->y2 = (secondHalf->y3 + c) * .5; + firstHalf->y4 = secondHalf->y1 = (firstHalf->y3 + secondHalf->y2) * .5; +} + +V_END_NAMESPACE + +#endif // VBEZIER_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vbitmap.cpp b/TMessagesProj/jni/rlottie/src/vector/vbitmap.cpp new file mode 100755 index 000000000..20963a65e --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vbitmap.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "vbitmap.h" +#include +#include "vdrawhelper.h" +#include "vglobal.h" + +V_BEGIN_NAMESPACE + +struct VBitmap::Impl { + uchar * mData{nullptr}; + uint mWidth{0}; + uint mHeight{0}; + uint mStride{0}; + uint mBytes{0}; + uint mDepth{0}; + VBitmap::Format mFormat{VBitmap::Format::Invalid}; + bool mOwnData; + bool mRoData; + + Impl() = delete; + + Impl(uint width, uint height, VBitmap::Format format) + : mOwnData(true), mRoData(false) + { + reset(width, height, format); + } + + void reset(uint width, uint height, VBitmap::Format format) + { + if (mOwnData && mData) delete (mData); + + mDepth = depth(format); + uint stride = ((width * mDepth + 31) >> 5) + << 2; // bytes per scanline (must be multiple of 4) + + mWidth = width; + mHeight = height; + mFormat = format; + mStride = stride; + mBytes = mStride * mHeight; + mData = reinterpret_cast(::operator new(mBytes)); + } + + Impl(uchar *data, uint w, uint h, uint bytesPerLine, VBitmap::Format format) + : mOwnData(false), mRoData(false) + { + mWidth = w; + mHeight = h; + mFormat = format; + mStride = bytesPerLine; + mBytes = mStride * mHeight; + mData = data; + mDepth = depth(format); + } + + ~Impl() + { + if (mOwnData && mData) ::operator delete(mData); + } + + uint stride() const { return mStride; } + uint width() const { return mWidth; } + uint height() const { return mHeight; } + VBitmap::Format format() const { return mFormat; } + uchar * data() { return mData; } + + static uint depth(VBitmap::Format format) + { + uint depth = 1; + switch (format) { + case VBitmap::Format::Alpha8: + depth = 8; + break; + case VBitmap::Format::ARGB32: + case VBitmap::Format::ARGB32_Premultiplied: + depth = 32; + break; + default: + break; + } + return depth; + } + void fill(uint /*pixel*/) + { + //@TODO + } + + void updateLuma() + { + if (mFormat != VBitmap::Format::ARGB32_Premultiplied) return; + + for (uint col = 0; col < mHeight; col++) { + uint *pixel = (uint *)(mData + mStride * col); + for (uint row = 0; row < mWidth; row++) { + int alpha = vAlpha(*pixel); + if (alpha == 0) { + pixel++; + continue; + } + + int red = vRed(*pixel); + int green = vGreen(*pixel); + int blue = vBlue(*pixel); + + if (alpha != 255) { + // un multiply + red = (red * 255) / alpha; + green = (green * 255) / alpha; + blue = (blue * 255) / alpha; + } + int luminosity = (0.299 * red + 0.587 * green + 0.114 * blue); + *pixel = luminosity << 24; + pixel++; + } + } + } +}; + +VBitmap::VBitmap(uint width, uint height, VBitmap::Format format) +{ + if (width <= 0 || height <= 0 || format == Format::Invalid) return; + + mImpl = std::make_shared(width, height, format); +} + +VBitmap::VBitmap(uchar *data, uint width, uint height, uint bytesPerLine, + VBitmap::Format format) +{ + if (!data || width <= 0 || height <= 0 || bytesPerLine <= 0 || + format == Format::Invalid) + return; + + mImpl = std::make_shared(data, width, height, bytesPerLine, format); +} + +void VBitmap::reset(uint w, uint h, VBitmap::Format format) +{ + if (mImpl) { + if (w == mImpl->width() && h == mImpl->height() && + format == mImpl->format()) { + return; + } + mImpl->reset(w, h, format); + } else { + mImpl = std::make_shared(w, h, format); + } +} + +uint VBitmap::stride() const +{ + return mImpl ? mImpl->stride() : 0; +} + +uint VBitmap::width() const +{ + return mImpl ? mImpl->width() : 0; +} + +uint VBitmap::height() const +{ + return mImpl ? mImpl->height() : 0; +} + +uint VBitmap::depth() const +{ + return mImpl ? mImpl->mDepth : 0; +} + +uchar *VBitmap::data() +{ + return mImpl ? mImpl->data() : nullptr; +} + +uchar *VBitmap::data() const +{ + return mImpl ? mImpl->data() : nullptr; +} + +bool VBitmap::valid() const +{ + return mImpl ? true : false; +} + +VBitmap::Format VBitmap::format() const +{ + return mImpl ? mImpl->format() : VBitmap::Format::Invalid; +} + +void VBitmap::fill(uint pixel) +{ + if (mImpl) mImpl->fill(pixel); +} + +/* + * This is special function which converts + * RGB value to Luminosity and stores it in + * the Alpha component of the pixel. + * After this conversion the bitmap data is no more + * in RGB space. but the Alpha component contains the + * Luminosity value of the pixel in HSL color space. + * NOTE: this api has its own special usecase + * make sure you know what you are doing before using + * this api. + */ +void VBitmap::updateLuma() +{ + if (mImpl) mImpl->updateLuma(); +} + +V_END_NAMESPACE diff --git a/TMessagesProj/jni/rlottie/src/vector/vbitmap.h b/TMessagesProj/jni/rlottie/src/vector/vbitmap.h new file mode 100755 index 000000000..8c49102c2 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vbitmap.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VBITMAP_H +#define VBITMAP_H + +#include "vrect.h" +#include + +V_BEGIN_NAMESPACE + +class VBitmap { +public: + enum class Format: uchar { + Invalid, + Alpha8, + ARGB32, + ARGB32_Premultiplied + }; + + VBitmap() = default; + VBitmap(uint w, uint h, VBitmap::Format format); + VBitmap(uchar *data, uint w, uint h, uint bytesPerLine, VBitmap::Format format); + + void reset(uint w, uint h, VBitmap::Format format=Format::ARGB32); + uint stride() const; + uint width() const; + uint height() const; + uint depth() const; + VBitmap::Format format() const; + bool valid() const; + uchar * data(); + uchar * data() const; + + void fill(uint pixel); + void updateLuma(); +private: + struct Impl; + std::shared_ptr mImpl; +}; + +V_END_NAMESPACE + +#endif // VBITMAP_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vbrush.cpp b/TMessagesProj/jni/rlottie/src/vector/vbrush.cpp new file mode 100755 index 000000000..cdc114ecf --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vbrush.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "vbrush.h" + +V_BEGIN_NAMESPACE + +VGradient::VGradient(VGradient::Type type) + : mType(type), + mSpread(VGradient::Spread::Pad), + mMode(VGradient::Mode::Absolute) +{ +} + +void VGradient::setStops(const VGradientStops &stops) +{ + mStops = stops; +} + +VLinearGradient::VLinearGradient(const VPointF &start, const VPointF &stop) + : VGradient(VGradient::Type::Linear) +{ + linear.x1 = start.x(); + linear.y1 = start.y(); + linear.x1 = stop.x(); + linear.y1 = stop.y(); +} + +VLinearGradient::VLinearGradient(float xStart, float yStart, float xStop, + float yStop) + : VGradient(VGradient::Type::Linear) +{ + linear.x1 = xStart; + linear.y1 = yStart; + linear.x1 = xStop; + linear.y1 = yStop; +} + +VRadialGradient::VRadialGradient(const VPointF ¢er, float cradius, + const VPointF &focalPoint, float fradius) + : VGradient(VGradient::Type::Radial) +{ + radial.cx = center.x(); + radial.cy = center.y(); + radial.fx = focalPoint.x(); + radial.fy = focalPoint.y(); + radial.cradius = cradius; + radial.fradius = fradius; +} + +VRadialGradient::VRadialGradient(float cx, float cy, float cradius, float fx, + float fy, float fradius) + : VGradient(VGradient::Type::Radial) +{ + radial.cx = cx; + radial.cy = cy; + radial.fx = fx; + radial.fy = fy; + radial.cradius = cradius; + radial.fradius = fradius; +} + +VBrush::VBrush(const VColor &color) : mType(VBrush::Type::Solid), mColor(color) +{ +} + +VBrush::VBrush(int r, int g, int b, int a) + : mType(VBrush::Type::Solid), mColor(r, g, b, a) + +{ +} + +VBrush::VBrush(const VGradient *gradient) +{ + if (!gradient) return; + + mGradient = gradient; + + if (gradient->mType == VGradient::Type::Linear) { + mType = VBrush::Type::LinearGradient; + } else if (gradient->mType == VGradient::Type::Radial) { + mType = VBrush::Type::RadialGradient; + } +} + +VBrush::VBrush(const VBitmap &texture) +{ + if (!texture.valid()) return; + + mType = VBrush::Type::Texture; + mTexture = texture; +} + +void VBrush::setMatrix(const VMatrix &m) +{ + mMatrix = m; +} + +V_END_NAMESPACE diff --git a/TMessagesProj/jni/rlottie/src/vector/vbrush.h b/TMessagesProj/jni/rlottie/src/vector/vbrush.h new file mode 100755 index 000000000..348e2ec87 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vbrush.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VBRUSH_H +#define VBRUSH_H + +#include +#include "vglobal.h" +#include "vmatrix.h" +#include "vpoint.h" +#include "vbitmap.h" + +V_BEGIN_NAMESPACE + +typedef std::pair VGradientStop; +typedef std::vector VGradientStops; +class VGradient { +public: + enum class Mode { Absolute, Relative }; + enum class Spread { Pad, Repeat, Reflect }; + enum class Type { Linear, Radial }; + VGradient(VGradient::Type type); + void setStops(const VGradientStops &stops); + void setAlpha(float alpha) {mAlpha = alpha;} + float alpha() const {return mAlpha;} + VGradient() = default; + +public: + static constexpr int colorTableSize = 1024; + VGradient::Type mType; + VGradient::Spread mSpread; + VGradient::Mode mMode; + VGradientStops mStops; + float mAlpha{1.0}; + union { + struct { + float x1, y1, x2, y2; + } linear; + struct { + float cx, cy, fx, fy, cradius, fradius; + } radial; + }; + VMatrix mMatrix; +}; + +class VLinearGradient : public VGradient { +public: + VLinearGradient(const VPointF &start, const VPointF &stop); + VLinearGradient(float xStart, float yStart, float xStop, float yStop); +}; + +class VRadialGradient : public VGradient { +public: + VRadialGradient(const VPointF ¢er, float cradius, + const VPointF &focalPoint, float fradius); + VRadialGradient(float cx, float cy, float cradius, float fx, float fy, + float fradius); +}; + +class VBrush { +public: + enum class Type { NoBrush, Solid, LinearGradient, RadialGradient, Texture }; + VBrush() = default; + VBrush(const VColor &color); + VBrush(const VGradient *gradient); + VBrush(int r, int g, int b, int a); + VBrush(const VBitmap &texture); + inline VBrush::Type type() const { return mType; } + void setMatrix(const VMatrix &m); +public: + VBrush::Type mType{Type::NoBrush}; + VColor mColor; + const VGradient *mGradient{nullptr}; + VBitmap mTexture; + VMatrix mMatrix; +}; + +V_END_NAMESPACE + +#endif // VBRUSH_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vcompositionfunctions.cpp b/TMessagesProj/jni/rlottie/src/vector/vcompositionfunctions.cpp new file mode 100755 index 000000000..52ac0dbe0 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vcompositionfunctions.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "vdrawhelper.h" + +/* + result = s + dest = s * ca + d * cia +*/ +void comp_func_solid_Source(uint32_t *dest, int length, uint32_t color, + uint32_t const_alpha) +{ + int ialpha, i; + + if (const_alpha == 255) { + memfill32(dest, color, length); + } else { + ialpha = 255 - const_alpha; + color = BYTE_MUL(color, const_alpha); + for (i = 0; i < length; ++i) + dest[i] = color + BYTE_MUL(dest[i], ialpha); + } +} + +/* + r = s + d * sia + dest = r * ca + d * cia + = (s + d * sia) * ca + d * cia + = s * ca + d * (sia * ca + cia) + = s * ca + d * (1 - sa*ca) + = s' + d ( 1 - s'a) +*/ +void comp_func_solid_SourceOver(uint32_t *dest, int length, uint32_t color, + uint32_t const_alpha) +{ + int ialpha, i; + + if (const_alpha != 255) color = BYTE_MUL(color, const_alpha); + ialpha = 255 - vAlpha(color); + for (i = 0; i < length; ++i) dest[i] = color + BYTE_MUL(dest[i], ialpha); +} + +/* + result = d * sa + dest = d * sa * ca + d * cia + = d * (sa * ca + cia) +*/ +static void comp_func_solid_DestinationIn(uint *dest, int length, uint color, + uint const_alpha) +{ + uint a = vAlpha(color); + if (const_alpha != 255) { + a = BYTE_MUL(a, const_alpha) + 255 - const_alpha; + } + for (int i = 0; i < length; ++i) { + dest[i] = BYTE_MUL(dest[i], a); + } +} + +/* + result = d * sia + dest = d * sia * ca + d * cia + = d * (sia * ca + cia) +*/ +static void comp_func_solid_DestinationOut(uint *dest, int length, uint color, + uint const_alpha) +{ + uint a = vAlpha(~color); + if (const_alpha != 255) a = BYTE_MUL(a, const_alpha) + 255 - const_alpha; + for (int i = 0; i < length; ++i) { + dest[i] = BYTE_MUL(dest[i], a); + } +} + +void comp_func_Source(uint32_t *dest, const uint32_t *src, int length, + uint32_t const_alpha) +{ + if (const_alpha == 255) { + memcpy(dest, src, size_t(length) * sizeof(uint)); + } else { + uint ialpha = 255 - const_alpha; + for (int i = 0; i < length; ++i) { + dest[i] = + INTERPOLATE_PIXEL_255(src[i], const_alpha, dest[i], ialpha); + } + } +} + +/* s' = s * ca + * d' = s' + d (1 - s'a) + */ +void comp_func_SourceOver(uint32_t *dest, const uint32_t *src, int length, + uint32_t const_alpha) +{ + uint s, sia; + + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + s = src[i]; + if (s >= 0xff000000) + dest[i] = s; + else if (s != 0) { + sia = vAlpha(~s); + dest[i] = s + BYTE_MUL(dest[i], sia); + } + } + } else { + /* source' = source * const_alpha + * dest = source' + dest ( 1- source'a) + */ + for (int i = 0; i < length; ++i) { + uint s = BYTE_MUL(src[i], const_alpha); + sia = vAlpha(~s); + dest[i] = s + BYTE_MUL(dest[i], sia); + } + } +} + +void comp_func_DestinationIn(uint *dest, const uint *src, int length, + uint const_alpha) +{ + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + dest[i] = BYTE_MUL(dest[i], vAlpha(src[i])); + } + } else { + uint cia = 255 - const_alpha; + for (int i = 0; i < length; ++i) { + uint a = BYTE_MUL(vAlpha(src[i]), const_alpha) + cia; + dest[i] = BYTE_MUL(dest[i], a); + } + } +} + +void comp_func_DestinationOut(uint *dest, const uint *src, int length, + uint const_alpha) +{ + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + dest[i] = BYTE_MUL(dest[i], vAlpha(~src[i])); + } + } else { + uint cia = 255 - const_alpha; + for (int i = 0; i < length; ++i) { + uint sia = BYTE_MUL(vAlpha(~src[i]), const_alpha) + cia; + dest[i] = BYTE_MUL(dest[i], sia); + } + } +} + +CompositionFunctionSolid COMP_functionForModeSolid_C[] = { + comp_func_solid_Source, comp_func_solid_SourceOver, + comp_func_solid_DestinationIn, comp_func_solid_DestinationOut}; + +CompositionFunction COMP_functionForMode_C[] = { + comp_func_Source, comp_func_SourceOver, comp_func_DestinationIn, + comp_func_DestinationOut}; + +void vInitBlendFunctions() {} diff --git a/TMessagesProj/jni/rlottie/src/vector/vcowptr.h b/TMessagesProj/jni/rlottie/src/vector/vcowptr.h new file mode 100755 index 000000000..dcc283ad1 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vcowptr.h @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VCOWPTR_H +#define VCOWPTR_H + +#include +#include "vglobal.h" + +template +class vcow_ptr { + struct model { + std::atomic mRef{1}; + + model() = default; + + template + explicit model(Args&&... args) : mValue(std::forward(args)...) + { + } + + T mValue; + }; + model* mModel; + +public: + using element_type = T; + + vcow_ptr() + { + static model default_s; + mModel = &default_s; + ++mModel->mRef; + } + + ~vcow_ptr() + { + if (mModel && (--mModel->mRef == 0)) delete mModel; + } + + template + vcow_ptr(Args&&... args) : mModel(new model(std::forward(args)...)) + { + } + + vcow_ptr(const vcow_ptr& x) noexcept : mModel(x.mModel) + { + assert(mModel); + ++mModel->mRef; + } + vcow_ptr(vcow_ptr&& x) noexcept : mModel(x.mModel) + { + assert(mModel); + x.mModel = nullptr; + } + + auto operator=(const vcow_ptr& x) noexcept -> vcow_ptr& + { + return *this = vcow_ptr(x); + } + + auto operator=(vcow_ptr&& x) noexcept -> vcow_ptr& + { + auto tmp = std::move(x); + swap(*this, tmp); + return *this; + } + + auto operator*() const noexcept -> const element_type& { return read(); } + + auto operator-> () const noexcept -> const element_type* { return &read(); } + + int refCount() const noexcept + { + assert(mModel); + + return mModel->mRef; + } + + bool unique() const noexcept + { + assert(mModel); + + return mModel->mRef == 1; + } + + auto write() -> element_type& + { + if (!unique()) *this = vcow_ptr(read()); + + return mModel->mValue; + } + + auto read() const noexcept -> const element_type& + { + assert(mModel); + + return mModel->mValue; + } + + friend inline void swap(vcow_ptr& x, vcow_ptr& y) noexcept + { + std::swap(x.mModel, y.mModel); + } +}; + +#endif // VCOWPTR_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vdasher.cpp b/TMessagesProj/jni/rlottie/src/vector/vdasher.cpp new file mode 100755 index 000000000..a6ca9abce --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vdasher.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "vbezier.h" + +#include + +#include "vdasher.h" +#include "vline.h" + +V_BEGIN_NAMESPACE + +VDasher::VDasher(const float *dashArray, size_t size) +{ + mDashArray = reinterpret_cast(dashArray); + mArraySize = size / 2; + if (size % 2) mDashOffset = dashArray[size - 1]; + mIndex = 0; + mCurrentLength = 0; + mDiscard = false; +} + +void VDasher::moveTo(const VPointF &p) +{ + mDiscard = false; + mStartNewSegment = true; + mCurPt = p; + mIndex = 0; + + if (!vCompare(mDashOffset, 0.0f)) { + float totalLength = 0.0; + for (size_t i = 0; i < mArraySize; i++) { + totalLength = mDashArray[i].length + mDashArray[i].gap; + } + float normalizeLen = std::fmod(mDashOffset, totalLength); + if (normalizeLen < 0.0f) { + normalizeLen = totalLength + normalizeLen; + } + // now the length is less than total length and +ve + // findout the current dash index , dashlength and gap. + for (size_t i = 0; i < mArraySize; i++) { + if (normalizeLen < mDashArray[i].length) { + mIndex = i; + mCurrentLength = mDashArray[i].length - normalizeLen; + mDiscard = false; + break; + } + normalizeLen -= mDashArray[i].length; + if (normalizeLen < mDashArray[i].gap) { + mIndex = i; + mCurrentLength = mDashArray[i].gap - normalizeLen; + mDiscard = true; + break; + } + normalizeLen -= mDashArray[i].gap; + } + } else { + mCurrentLength = mDashArray[mIndex].length; + } + if (vIsZero(mCurrentLength)) updateActiveSegment(); +} + +void VDasher::addLine(const VPointF &p) +{ + if (mDiscard) return; + + if (mStartNewSegment) { + mResult.moveTo(mCurPt); + mStartNewSegment = false; + } + mResult.lineTo(p); +} + +void VDasher::updateActiveSegment() +{ + mStartNewSegment = true; + + if (mDiscard) { + mDiscard = false; + mIndex = (mIndex + 1) % mArraySize; + mCurrentLength = mDashArray[mIndex].length; + } else { + mDiscard = true; + mCurrentLength = mDashArray[mIndex].gap; + } + if (vIsZero(mCurrentLength)) updateActiveSegment(); +} + +void VDasher::lineTo(const VPointF &p) +{ + VLine left, right; + VLine line(mCurPt, p); + float length = line.length(); + + if (length <= mCurrentLength) { + mCurrentLength -= length; + addLine(p); + } else { + while (length > mCurrentLength) { + length -= mCurrentLength; + line.splitAtLength(mCurrentLength, left, right); + + addLine(left.p2()); + updateActiveSegment(); + + line = right; + mCurPt = line.p1(); + } + // handle remainder + if (length > 1.0f) { + mCurrentLength -= length; + addLine(line.p2()); + } + } + + if (mCurrentLength < 1.0f) updateActiveSegment(); + + mCurPt = p; +} + +void VDasher::addCubic(const VPointF &cp1, const VPointF &cp2, const VPointF &e) +{ + if (mDiscard) return; + + if (mStartNewSegment) { + mResult.moveTo(mCurPt); + mStartNewSegment = false; + } + mResult.cubicTo(cp1, cp2, e); +} + +void VDasher::cubicTo(const VPointF &cp1, const VPointF &cp2, const VPointF &e) +{ + VBezier left, right; + VBezier b = VBezier::fromPoints(mCurPt, cp1, cp2, e); + float bezLen = b.length(); + + if (bezLen <= mCurrentLength) { + mCurrentLength -= bezLen; + addCubic(cp1, cp2, e); + } else { + while (bezLen > mCurrentLength) { + bezLen -= mCurrentLength; + b.splitAtLength(mCurrentLength, &left, &right); + + addCubic(left.pt2(), left.pt3(), left.pt4()); + updateActiveSegment(); + + b = right; + mCurPt = b.pt1(); + } + // handle remainder + if (bezLen > 1.0f) { + mCurrentLength -= bezLen; + addCubic(b.pt2(), b.pt3(), b.pt4()); + } + } + + if (mCurrentLength < 1.0f) updateActiveSegment(); + + mCurPt = e; +} + +VPath VDasher::dashed(const VPath &path) +{ + if (path.empty()) return VPath(); + + mResult = {}; + mResult.reserve(path.points().size(), path.elements().size()); + mIndex = 0; + const std::vector &elms = path.elements(); + const std::vector & pts = path.points(); + const VPointF * ptPtr = pts.data(); + + for (auto &i : elms) { + switch (i) { + case VPath::Element::MoveTo: { + moveTo(*ptPtr++); + break; + } + case VPath::Element::LineTo: { + lineTo(*ptPtr++); + break; + } + case VPath::Element::CubicTo: { + cubicTo(*ptPtr, *(ptPtr + 1), *(ptPtr + 2)); + ptPtr += 3; + break; + } + case VPath::Element::Close: { + // The point is already joined to start point in VPath + // no need to do anything here. + break; + } + } + } + return std::move(mResult); +} + +V_END_NAMESPACE diff --git a/TMessagesProj/jni/rlottie/src/vector/vdasher.h b/TMessagesProj/jni/rlottie/src/vector/vdasher.h new file mode 100755 index 000000000..224a2a8f2 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vdasher.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VDASHER_H +#define VDASHER_H +#include "vpath.h" + +V_BEGIN_NAMESPACE + +class VDasher { +public: + VDasher(const float *dashArray, size_t size); + VPath dashed(const VPath &path); + +private: + void moveTo(const VPointF &p); + void lineTo(const VPointF &p); + void cubicTo(const VPointF &cp1, const VPointF &cp2, const VPointF &e); + void close(); + void addLine(const VPointF &p); + void addCubic(const VPointF &cp1, const VPointF &cp2, const VPointF &e); + void updateActiveSegment(); + +private: + struct Dash { + float length; + float gap; + }; + const VDasher::Dash *mDashArray; + size_t mArraySize{0}; + VPointF mCurPt; + size_t mIndex{0}; /* index to the dash Array */ + float mCurrentLength; + bool mDiscard; + float mDashOffset{0}; + VPath mResult; + bool mStartNewSegment=true; +}; + +V_END_NAMESPACE + +#endif // VDASHER_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vdebug.cpp b/TMessagesProj/jni/rlottie/src/vector/vdebug.cpp new file mode 100755 index 000000000..8a0c19cef --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vdebug.cpp @@ -0,0 +1,754 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "vdebug.h" + +#ifdef LOTTIE_LOGGING_SUPPORT + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +/* Returns microseconds since epoch */ +uint64_t timestamp_now() +{ + return std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); +} + +/* I want [2016-10-13 00:01:23.528514] */ +void format_timestamp(std::ostream& os, uint64_t timestamp) +{ + // The next 3 lines do not work on MSVC! + // auto duration = std::chrono::microseconds(timestamp); + // std::chrono::high_resolution_clock::time_point time_point(duration); + // std::time_t time_t = + // std::chrono::high_resolution_clock::to_time_t(time_point); + std::time_t time_t = timestamp / 1000000; + auto gmtime = std::gmtime(&time_t); + char buffer[32]; + strftime(buffer, 32, "%Y-%m-%d %T.", gmtime); + char microseconds[7]; + snprintf(microseconds, 7, "%06llu", + (long long unsigned int)timestamp % 1000000); + os << '[' << buffer << microseconds << ']'; +} + +std::thread::id this_thread_id() +{ + static thread_local const std::thread::id id = std::this_thread::get_id(); + return id; +} + +template +struct TupleIndex; + +template +struct TupleIndex > { + static constexpr const std::size_t value = 0; +}; + +template +struct TupleIndex > { + static constexpr const std::size_t value = + 1 + TupleIndex >::value; +}; + +} // anonymous namespace + +typedef std::tuple + SupportedTypes; + +char const* to_string(LogLevel loglevel) +{ + switch (loglevel) { + case LogLevel::OFF: + return "OFF"; + case LogLevel::INFO: + return "INFO"; + case LogLevel::WARN: + return "WARN"; + case LogLevel::CRIT: + return "CRIT"; + } + return "XXXX"; +} + +template +void VDebug::encode(Arg arg) +{ + *reinterpret_cast(buffer()) = arg; + m_bytes_used += sizeof(Arg); +} + +template +void VDebug::encode(Arg arg, uint8_t type_id) +{ + resize_buffer_if_needed(sizeof(Arg) + sizeof(uint8_t)); + encode(type_id); + encode(arg); +} + +VDebug::VDebug(LogLevel level, char const* file, char const* function, + uint32_t line) + : m_bytes_used(0), m_buffer_size(sizeof(m_stack_buffer)) +{ + encode(timestamp_now()); + encode(this_thread_id()); + encode(string_literal_t(file)); + encode(string_literal_t(function)); + encode(line); + encode(level); + if (level == LogLevel::INFO) { + m_logAll = false; + } else { + m_logAll = true; + } +} + +VDebug::~VDebug() = default; + +void VDebug::stringify(std::ostream& os) +{ + char* b = !m_heap_buffer ? m_stack_buffer : m_heap_buffer.get(); + char const* const end = b + m_bytes_used; + uint64_t timestamp = *reinterpret_cast(b); + b += sizeof(uint64_t); + std::thread::id threadid = *reinterpret_cast(b); + b += sizeof(std::thread::id); + string_literal_t file = *reinterpret_cast(b); + b += sizeof(string_literal_t); + string_literal_t function = *reinterpret_cast(b); + b += sizeof(string_literal_t); + uint32_t line = *reinterpret_cast(b); + b += sizeof(uint32_t); + LogLevel loglevel = *reinterpret_cast(b); + b += sizeof(LogLevel); + if (m_logAll) { + format_timestamp(os, timestamp); + + os << '[' << to_string(loglevel) << ']' << '[' << threadid << ']' << '[' + << file.m_s << ':' << function.m_s << ':' << line << "] "; + } + + stringify(os, b, end); + os << std::endl; + + if (loglevel >= LogLevel::CRIT) os.flush(); +} + +template +char* decode(std::ostream& os, char* b, Arg* /*dummy*/) +{ + Arg arg = *reinterpret_cast(b); + os << arg; + return b + sizeof(Arg); +} + +template <> +char* decode(std::ostream& os, char* b, VDebug::string_literal_t* /*dummy*/) +{ + VDebug::string_literal_t s = + *reinterpret_cast(b); + os << s.m_s; + return b + sizeof(VDebug::string_literal_t); +} + +template <> +char* decode(std::ostream& os, char* b, char** /*dummy*/) +{ + while (*b != '\0') { + os << *b; + ++b; + } + return ++b; +} + +void VDebug::stringify(std::ostream& os, char* start, char const* const end) +{ + if (start == end) return; + + int type_id = static_cast(*start); + start++; + + switch (type_id) { + case 0: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + case 1: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + case 2: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + case 3: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + case 4: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + case 5: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + case 6: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + case 7: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + } +} + +char* VDebug::buffer() +{ + return !m_heap_buffer ? &m_stack_buffer[m_bytes_used] + : &(m_heap_buffer.get())[m_bytes_used]; +} + +void VDebug::resize_buffer_if_needed(size_t additional_bytes) +{ + size_t const required_size = m_bytes_used + additional_bytes; + + if (required_size <= m_buffer_size) return; + + if (!m_heap_buffer) { + m_buffer_size = std::max(static_cast(512), required_size); + m_heap_buffer = std::make_unique(m_buffer_size); + memcpy(m_heap_buffer.get(), m_stack_buffer, m_bytes_used); + return; + } else { + m_buffer_size = + std::max(static_cast(2 * m_buffer_size), required_size); + std::unique_ptr new_heap_buffer(new char[m_buffer_size]); + memcpy(new_heap_buffer.get(), m_heap_buffer.get(), m_bytes_used); + m_heap_buffer.swap(new_heap_buffer); + } +} + +void VDebug::encode(char const* arg) +{ + if (arg != nullptr) encode_c_string(arg, strlen(arg)); +} + +void VDebug::encode(char* arg) +{ + if (arg != nullptr) encode_c_string(arg, strlen(arg)); +} + +void VDebug::encode_c_string(char const* arg, size_t length) +{ + if (length == 0) return; + + resize_buffer_if_needed(1 + length + 1); + char* b = buffer(); + auto type_id = TupleIndex::value; + *reinterpret_cast(b++) = static_cast(type_id); + memcpy(b, arg, length + 1); + m_bytes_used += 1 + length + 1; +} + +void VDebug::encode(string_literal_t arg) +{ + encode( + arg, TupleIndex::value); +} + +VDebug& VDebug::operator<<(std::string const& arg) +{ + encode_c_string(arg.c_str(), arg.length()); + return *this; +} + +VDebug& VDebug::operator<<(int32_t arg) +{ + encode(arg, TupleIndex::value); + return *this; +} + +VDebug& VDebug::operator<<(uint32_t arg) +{ + encode(arg, TupleIndex::value); + return *this; +} + +// VDebug& VDebug::operator<<(int64_t arg) +// { +// encode < int64_t >(arg, TupleIndex < int64_t, SupportedTypes >::value); +// return *this; +// } + +// VDebug& VDebug::operator<<(uint64_t arg) +// { +// encode < uint64_t >(arg, TupleIndex < uint64_t, SupportedTypes >::value); +// return *this; +// } +VDebug& VDebug::operator<<(unsigned long arg) +{ + encode(arg, TupleIndex::value); + return *this; +} + +VDebug& VDebug::operator<<(long arg) +{ + encode(arg, TupleIndex::value); + return *this; +} + +VDebug& VDebug::operator<<(double arg) +{ + encode(arg, TupleIndex::value); + return *this; +} + +VDebug& VDebug::operator<<(char arg) +{ + encode(arg, TupleIndex::value); + return *this; +} + +struct BufferBase { + virtual ~BufferBase() = default; + virtual void push(VDebug&& logline) = 0; + virtual bool try_pop(VDebug& logline) = 0; +}; + +struct SpinLock { + SpinLock(std::atomic_flag& flag) : m_flag(flag) + { + while (m_flag.test_and_set(std::memory_order_acquire)) + ; + } + + ~SpinLock() { m_flag.clear(std::memory_order_release); } + +private: + std::atomic_flag& m_flag; +}; + +/* Multi Producer Single Consumer Ring Buffer */ +class RingBuffer : public BufferBase { +public: + struct alignas(64) Item { + Item() + : flag(), written(0), logline(LogLevel::INFO, nullptr, nullptr, 0) + { + } + + std::atomic_flag flag; + char written; + char padding[256 - sizeof(std::atomic_flag) - sizeof(char) - + sizeof(VDebug)]; + VDebug logline; + }; + + RingBuffer(size_t const size) + : m_size(size), + m_ring(static_cast(std::malloc(size * sizeof(Item)))), + m_write_index(0), + m_read_index(0) + { + for (size_t i = 0; i < m_size; ++i) { + new (&m_ring[i]) Item(); + } + static_assert(sizeof(Item) == 256, "Unexpected size != 256"); + } + + ~RingBuffer() override + { + for (size_t i = 0; i < m_size; ++i) { + m_ring[i].~Item(); + } + std::free(m_ring); + } + + void push(VDebug&& logline) override + { + unsigned int write_index = + m_write_index.fetch_add(1, std::memory_order_relaxed) % m_size; + Item& item = m_ring[write_index]; + SpinLock spinlock(item.flag); + item.logline = std::move(logline); + item.written = 1; + } + + bool try_pop(VDebug& logline) override + { + Item& item = m_ring[m_read_index % m_size]; + SpinLock spinlock(item.flag); + if (item.written == 1) { + logline = std::move(item.logline); + item.written = 0; + ++m_read_index; + return true; + } + return false; + } + + RingBuffer(RingBuffer const&) = delete; + RingBuffer& operator=(RingBuffer const&) = delete; + +private: + size_t const m_size; + Item* m_ring; + std::atomic m_write_index; + +public: + char pad[64]; + +private: + unsigned int m_read_index; +}; + +class Buffer { +public: + struct Item { + Item(VDebug&& logline) : logline(std::move(logline)) {} + char padding[256 - sizeof(VDebug)]; + VDebug logline; + }; + + static constexpr const size_t size = + 32768; // 8MB. Helps reduce memory fragmentation + + Buffer() : m_buffer(static_cast(std::malloc(size * sizeof(Item)))) + { + for (size_t i = 0; i <= size; ++i) { + m_write_state[i].store(0, std::memory_order_relaxed); + } + static_assert(sizeof(Item) == 256, "Unexpected size != 256"); + } + + ~Buffer() + { + unsigned int write_count = m_write_state[size].load(); + for (size_t i = 0; i < write_count; ++i) { + m_buffer[i].~Item(); + } + std::free(m_buffer); + } + + // Returns true if we need to switch to next buffer + bool push(VDebug&& logline, unsigned int const write_index) + { + new (&m_buffer[write_index]) Item(std::move(logline)); + m_write_state[write_index].store(1, std::memory_order_release); + return m_write_state[size].fetch_add(1, std::memory_order_acquire) + + 1 == + size; + } + + bool try_pop(VDebug& logline, unsigned int const read_index) + { + if (m_write_state[read_index].load(std::memory_order_acquire)) { + Item& item = m_buffer[read_index]; + logline = std::move(item.logline); + return true; + } + return false; + } + + Buffer(Buffer const&) = delete; + Buffer& operator=(Buffer const&) = delete; + +private: + Item* m_buffer; + std::atomic m_write_state[size + 1]; +}; + +class QueueBuffer : public BufferBase { +public: + QueueBuffer(QueueBuffer const&) = delete; + QueueBuffer& operator=(QueueBuffer const&) = delete; + + QueueBuffer() + : m_current_read_buffer{nullptr}, + m_write_index(0), + m_flag(), + m_read_index(0) + { + setup_next_write_buffer(); + } + + void push(VDebug&& logline) override + { + unsigned int write_index = + m_write_index.fetch_add(1, std::memory_order_relaxed); + if (write_index < Buffer::size) { + if (m_current_write_buffer.load(std::memory_order_acquire) + ->push(std::move(logline), write_index)) { + setup_next_write_buffer(); + } + } else { + while (m_write_index.load(std::memory_order_acquire) >= + Buffer::size) + ; + push(std::move(logline)); + } + } + + bool try_pop(VDebug& logline) override + { + if (m_current_read_buffer == nullptr) + m_current_read_buffer = get_next_read_buffer(); + + Buffer* read_buffer = m_current_read_buffer; + + if (read_buffer == nullptr) return false; + + if (read_buffer->try_pop(logline, m_read_index)) { + m_read_index++; + if (m_read_index == Buffer::size) { + m_read_index = 0; + m_current_read_buffer = nullptr; + SpinLock spinlock(m_flag); + m_buffers.pop(); + } + return true; + } + + return false; + } + +private: + void setup_next_write_buffer() + { + std::unique_ptr next_write_buffer(new Buffer()); + m_current_write_buffer.store(next_write_buffer.get(), + std::memory_order_release); + SpinLock spinlock(m_flag); + m_buffers.push(std::move(next_write_buffer)); + m_write_index.store(0, std::memory_order_relaxed); + } + + Buffer* get_next_read_buffer() + { + SpinLock spinlock(m_flag); + return m_buffers.empty() ? nullptr : m_buffers.front().get(); + } + +private: + std::queue > m_buffers; + std::atomic m_current_write_buffer; + Buffer* m_current_read_buffer; + std::atomic m_write_index; + std::atomic_flag m_flag; + unsigned int m_read_index; +}; + +class FileWriter { +public: + FileWriter(std::string const& log_directory, + std::string const& log_file_name, uint32_t log_file_roll_size_mb) + : m_log_file_roll_size_bytes(log_file_roll_size_mb * 1024 * 1024), + m_name(log_directory + log_file_name) + { + roll_file(); + } + + void write(VDebug& logline) + { + auto pos = m_os->tellp(); + logline.stringify(*m_os); + m_bytes_written += m_os->tellp() - pos; + if (m_bytes_written > m_log_file_roll_size_bytes) { + roll_file(); + } + } + +private: + void roll_file() + { + if (m_os) { + m_os->flush(); + m_os->close(); + } + + m_bytes_written = 0; + m_os = std::make_unique(); + // TODO Optimize this part. Does it even matter ? + std::string log_file_name = m_name; + log_file_name.append("."); + log_file_name.append(std::to_string(++m_file_number)); + log_file_name.append(".txt"); + m_os->open(log_file_name, std::ofstream::out | std::ofstream::trunc); + } + +private: + uint32_t m_file_number = 0; + std::streamoff m_bytes_written = 0; + uint32_t const m_log_file_roll_size_bytes; + std::string const m_name; + std::unique_ptr m_os; +}; + +class NanoLogger { +public: + NanoLogger(NonGuaranteedLogger ngl, std::string const& log_directory, + std::string const& log_file_name, uint32_t log_file_roll_size_mb) + : m_state(State::INIT), + m_buffer_base( + new RingBuffer(std::max(1u, ngl.ring_buffer_size_mb) * 1024 * 4)), + m_file_writer(log_directory, log_file_name, + std::max(1u, log_file_roll_size_mb)), + m_thread(&NanoLogger::pop, this) + { + m_state.store(State::READY, std::memory_order_release); + } + + NanoLogger(GuaranteedLogger /*gl*/, std::string const& log_directory, + std::string const& log_file_name, uint32_t log_file_roll_size_mb) + : m_state(State::INIT), + m_buffer_base(new QueueBuffer()), + m_file_writer(log_directory, log_file_name, + std::max(1u, log_file_roll_size_mb)), + m_thread(&NanoLogger::pop, this) + { + m_state.store(State::READY, std::memory_order_release); + } + + ~NanoLogger() + { + m_state.store(State::SHUTDOWN); + m_thread.join(); + } + + void add(VDebug&& logline) { m_buffer_base->push(std::move(logline)); } + + void pop() + { + // Wait for constructor to complete and pull all stores done there to + // this thread / core. + while (m_state.load(std::memory_order_acquire) == State::INIT) + std::this_thread::sleep_for(std::chrono::microseconds(50)); + + VDebug logline(LogLevel::INFO, nullptr, nullptr, 0); + + while (m_state.load() == State::READY) { + if (m_buffer_base->try_pop(logline)) + m_file_writer.write(logline); + else + std::this_thread::sleep_for(std::chrono::microseconds(50)); + } + + // Pop and log all remaining entries + while (m_buffer_base->try_pop(logline)) { + m_file_writer.write(logline); + } + } + +private: + enum class State { INIT, READY, SHUTDOWN }; + + std::atomic m_state; + std::unique_ptr m_buffer_base; + FileWriter m_file_writer; + std::thread m_thread; +}; + +std::unique_ptr nanologger; +std::atomic atomic_nanologger; + +bool VDebugServer::operator==(VDebug& logline) +{ + atomic_nanologger.load(std::memory_order_acquire)->add(std::move(logline)); + return true; +} + +void initialize(NonGuaranteedLogger ngl, std::string const& log_directory, + std::string const& log_file_name, + uint32_t log_file_roll_size_mb) +{ + nanologger = std::make_unique(ngl, log_directory, log_file_name, + log_file_roll_size_mb); + atomic_nanologger.store(nanologger.get(), std::memory_order_seq_cst); +} + +void initialize(GuaranteedLogger gl, std::string const& log_directory, + std::string const& log_file_name, + uint32_t log_file_roll_size_mb) +{ + nanologger = std::make_unique(gl, log_directory, log_file_name, + log_file_roll_size_mb); + atomic_nanologger.store(nanologger.get(), std::memory_order_seq_cst); +} + +std::atomic loglevel = {0}; + +void set_log_level(LogLevel level) +{ + loglevel.store(static_cast(level), std::memory_order_release); +} + +bool is_logged(LogLevel level) +{ + return static_cast(level) >= + loglevel.load(std::memory_order_relaxed); +} + +#endif // LOTTIE_LOGGING_SUPPORT diff --git a/TMessagesProj/jni/rlottie/src/vector/vdebug.h b/TMessagesProj/jni/rlottie/src/vector/vdebug.h new file mode 100755 index 000000000..83ffdb028 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vdebug.h @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VDEBUG_H +#define VDEBUG_H + +#include "config.h" + +#ifdef LOTTIE_LOGGING_SUPPORT + +#include +#include +#include +#include +#include + +enum class LogLevel : uint8_t { INFO, WARN, CRIT, OFF }; + +class VDebug { +public: + VDebug(); + VDebug& debug() { return *this; } + VDebug(LogLevel level, char const* file, char const* function, + uint32_t line); + ~VDebug(); + + VDebug(VDebug&&) = default; + VDebug& operator=(VDebug&&) = default; + + void stringify(std::ostream& os); + + VDebug& operator<<(char arg); + VDebug& operator<<(int32_t arg); + VDebug& operator<<(uint32_t arg); + // VDebug& operator<<(int64_t arg); + // VDebug& operator<<(uint64_t arg); + + VDebug& operator<<(long arg); + VDebug& operator<<(unsigned long arg); + VDebug& operator<<(double arg); + VDebug& operator<<(std::string const& arg); + + template + VDebug& operator<<(const char (&arg)[N]) + { + encode(string_literal_t(arg)); + return *this; + } + + template + typename std::enable_if::value, + VDebug&>::type + operator<<(Arg const& arg) + { + encode(arg); + return *this; + } + + template + typename std::enable_if::value, VDebug&>::type + operator<<(Arg const& arg) + { + encode(arg); + return *this; + } + + struct string_literal_t { + explicit string_literal_t(char const* s) : m_s(s) {} + char const* m_s; + }; + +private: + char* buffer(); + + template + void encode(Arg arg); + + template + void encode(Arg arg, uint8_t type_id); + + void encode(char* arg); + void encode(char const* arg); + void encode(string_literal_t arg); + void encode_c_string(char const* arg, size_t length); + void resize_buffer_if_needed(size_t additional_bytes); + void stringify(std::ostream& os, char* start, char const* const end); + +private: + size_t m_bytes_used{0}; + size_t m_buffer_size{0}; + std::unique_ptr m_heap_buffer; + bool m_logAll; + char m_stack_buffer[256 - sizeof(bool) - 2 * sizeof(size_t) - + sizeof(decltype(m_heap_buffer)) - 8 /* Reserved */]; +}; + +struct VDebugServer { + /* + * Ideally this should have been operator+= + * Could not get that to compile, so here we are... + */ + bool operator==(VDebug&); +}; + +void set_log_level(LogLevel level); + +bool is_logged(LogLevel level); + +/* + * Non guaranteed logging. Uses a ring buffer to hold log lines. + * When the ring gets full, the previous log line in the slot will be dropped. + * Does not block producer even if the ring buffer is full. + * ring_buffer_size_mb - LogLines are pushed into a mpsc ring buffer whose size + * is determined by this parameter. Since each LogLine is 256 bytes, + * ring_buffer_size = ring_buffer_size_mb * 1024 * 1024 / 256 + */ +struct NonGuaranteedLogger { + NonGuaranteedLogger(uint32_t ring_buffer_size_mb_) + : ring_buffer_size_mb(ring_buffer_size_mb_) + { + } + uint32_t ring_buffer_size_mb; +}; + +/* + * Provides a guarantee log lines will not be dropped. + */ +struct GuaranteedLogger { +}; + +/* + * Ensure initialize() is called prior to any log statements. + * log_directory - where to create the logs. For example - "/tmp/" + * log_file_name - root of the file name. For example - "nanolog" + * This will create log files of the form - + * /tmp/nanolog.1.txt + * /tmp/nanolog.2.txt + * etc. + * log_file_roll_size_mb - mega bytes after which we roll to next log file. + */ +void initialize(GuaranteedLogger gl, std::string const& log_directory, + std::string const& log_file_name, + uint32_t log_file_roll_size_mb); +void initialize(NonGuaranteedLogger ngl, std::string const& log_directory, + std::string const& log_file_name, + uint32_t log_file_roll_size_mb); + +#define VDEBUG_LOG(LEVEL) \ + VDebugServer() == VDebug(LEVEL, __FILE__, __func__, __LINE__).debug() +#define vDebug is_logged(LogLevel::INFO) && VDEBUG_LOG(LogLevel::INFO) +#define vWarning is_logged(LogLevel::WARN) && VDEBUG_LOG(LogLevel::WARN) +#define vCritical is_logged(LogLevel::CRIT) && VDEBUG_LOG(LogLevel::CRIT) + +#else + +struct VDebug +{ + template + VDebug& operator<<(const Args &){return *this;} +}; + +#define vDebug VDebug() +#define vWarning VDebug() +#define vCritical VDebug() + +#endif //LOTTIE_LOGGING_SUPPORT + +#endif // VDEBUG_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vdrawable.cpp b/TMessagesProj/jni/rlottie/src/vector/vdrawable.cpp new file mode 100755 index 000000000..946054f2c --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vdrawable.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "vdrawable.h" +#include "vdasher.h" +#include "vraster.h" + +void VDrawable::preprocess(const VRect &clip) +{ + if (mFlag & (DirtyState::Path)) { + if (mStroke.enable) { + if (mStroke.mDash.size()) { + VDasher dasher(mStroke.mDash.data(), mStroke.mDash.size()); + mPath = dasher.dashed(mPath); + } + mRasterizer.rasterize(std::move(mPath), mStroke.cap, mStroke.join, + mStroke.width, mStroke.meterLimit, clip); + } else { + mRasterizer.rasterize(std::move(mPath), mFillRule, clip); + } + mPath = {}; + mFlag &= ~DirtyFlag(DirtyState::Path); + } +} + +VRle VDrawable::rle() +{ + return mRasterizer.rle(); +} + +void VDrawable::setStrokeInfo(CapStyle cap, JoinStyle join, float meterLimit, + float strokeWidth) +{ + if ((mStroke.cap == cap) && (mStroke.join == join) && + vCompare(mStroke.meterLimit, meterLimit) && + vCompare(mStroke.width, strokeWidth)) + return; + + mStroke.enable = true; + mStroke.cap = cap; + mStroke.join = join; + mStroke.meterLimit = meterLimit; + mStroke.width = strokeWidth; + mFlag |= DirtyState::Path; +} + +void VDrawable::setDashInfo(float *array, uint size) +{ + bool hasChanged = false; + + if (mStroke.mDash.size() == size) { + for (uint i = 0; i < size; i++) { + if (!vCompare(mStroke.mDash[i], array[i])) { + hasChanged = true; + break; + } + } + } else { + hasChanged = true; + } + + if (!hasChanged) return; + + mStroke.mDash.clear(); + + for (uint i = 0; i < size; i++) { + mStroke.mDash.push_back(array[i]); + } + mFlag |= DirtyState::Path; +} + +void VDrawable::setPath(const VPath &path) +{ + mPath = path; + mFlag |= DirtyState::Path; +} diff --git a/TMessagesProj/jni/rlottie/src/vector/vdrawable.h b/TMessagesProj/jni/rlottie/src/vector/vdrawable.h new file mode 100755 index 000000000..861d9be1a --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vdrawable.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VDRAWABLE_H +#define VDRAWABLE_H +#include +#include "vbrush.h" +#include "vpath.h" +#include "vrle.h" +#include "vraster.h" + +class VDrawable { +public: + enum class DirtyState { + None = 0x00000000, + Path = 0x00000001, + Stroke = 0x00000010, + Brush = 0x00000100, + All = (None | Path | Stroke | Brush) + }; + enum class Type : unsigned char{ + Fill, + Stroke, + }; + typedef vFlag DirtyFlag; + void setPath(const VPath &path); + void setFillRule(FillRule rule) { mFillRule = rule; } + void setBrush(const VBrush &brush) { mBrush = brush; } + void setStrokeInfo(CapStyle cap, JoinStyle join, float meterLimit, + float strokeWidth); + void setDashInfo(float *array, uint size); + void preprocess(const VRect &clip); + VRle rle(); + +public: + struct StrokeInfo { + std::vector mDash; + float width{0.0}; + float meterLimit{10}; + bool enable{false}; + CapStyle cap{CapStyle::Flat}; + JoinStyle join{JoinStyle::Bevel}; + }; + VRasterizer mRasterizer; + VBrush mBrush; + VPath mPath; + StrokeInfo mStroke; + DirtyFlag mFlag{DirtyState::All}; + FillRule mFillRule{FillRule::Winding}; + VDrawable::Type mType{Type::Fill}; +}; + +#endif // VDRAWABLE_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vdrawhelper.cpp b/TMessagesProj/jni/rlottie/src/vector/vdrawhelper.cpp new file mode 100755 index 000000000..5e6ef4dc9 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vdrawhelper.cpp @@ -0,0 +1,925 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "vdrawhelper.h" +#include +#include +#include +#include +#include + +class VGradientCache { +public: + struct CacheInfo : public VColorTable { + inline CacheInfo(VGradientStops s) : stops(std::move(s)) {} + VGradientStops stops; + }; + using VCacheData = std::shared_ptr; + using VCacheKey = int64_t; + using VGradientColorTableHash = + std::unordered_multimap; + + bool generateGradientColorTable(const VGradientStops &stops, float alpha, + uint32_t *colorTable, int size); + VCacheData getBuffer(const VGradient &gradient) + { + VCacheKey hash_val = 0; + VCacheData info; + const VGradientStops &stops = gradient.mStops; + for (uint i = 0; i < stops.size() && i <= 2; i++) + hash_val += (stops[i].second.premulARGB() * gradient.alpha()); + + { + std::lock_guard guard(mMutex); + + size_t count = mCache.count(hash_val); + if (!count) { + // key is not present in the hash + info = addCacheElement(hash_val, gradient); + } else if (count == 1) { + auto search = mCache.find(hash_val); + if (search->second->stops == stops) { + info = search->second; + } else { + // didn't find an exact match + info = addCacheElement(hash_val, gradient); + } + } else { + // we have a multiple data with same key + auto range = mCache.equal_range(hash_val); + for (auto it = range.first; it != range.second; ++it) { + if (it->second->stops == stops) { + info = it->second; + break; + } + } + if (!info) { + // didn't find an exact match + info = addCacheElement(hash_val, gradient); + } + } + } + return info; + } + +protected: + uint maxCacheSize() const { return 60; } + VCacheData addCacheElement(VCacheKey hash_val, const VGradient &gradient) + { + if (mCache.size() == maxCacheSize()) { + uint count = maxCacheSize() / 10; + while (count--) { + mCache.erase(mCache.begin()); + } + } + auto cache_entry = std::make_shared(gradient.mStops); + cache_entry->alpha = generateGradientColorTable( + gradient.mStops, gradient.alpha(), cache_entry->buffer32, + VGradient::colorTableSize); + mCache.insert(std::make_pair(hash_val, cache_entry)); + return cache_entry; + } + + VGradientColorTableHash mCache; + std::mutex mMutex; +}; + +bool VGradientCache::generateGradientColorTable(const VGradientStops &stops, + float opacity, + uint32_t *colorTable, int size) +{ + int dist, idist, pos = 0, i; + bool alpha = false; + int stopCount = stops.size(); + const VGradientStop *curr, *next, *start; + uint32_t curColor, nextColor; + float delta, t, incr, fpos; + + if (!vCompare(opacity, 1.0f)) alpha = true; + + start = stops.data(); + curr = start; + if (!curr->second.isOpaque()) alpha = true; + curColor = curr->second.premulARGB(opacity); + incr = 1.0 / (float)size; + fpos = 1.5 * incr; + + colorTable[pos++] = curColor; + + while (fpos <= curr->first) { + colorTable[pos] = colorTable[pos - 1]; + pos++; + fpos += incr; + } + + for (i = 0; i < stopCount - 1; ++i) { + curr = (start + i); + next = (start + i + 1); + delta = 1 / (next->first - curr->first); + if (!next->second.isOpaque()) alpha = true; + nextColor = next->second.premulARGB(opacity); + while (fpos < next->first && pos < size) { + t = (fpos - curr->first) * delta; + dist = (int)(255 * t); + idist = 255 - dist; + colorTable[pos] = + INTERPOLATE_PIXEL_255(curColor, idist, nextColor, dist); + ++pos; + fpos += incr; + } + curColor = nextColor; + } + + for (; pos < size; ++pos) colorTable[pos] = curColor; + + // Make sure the last color stop is represented at the end of the table + colorTable[size - 1] = curColor; + return alpha; +} + +static VGradientCache VGradientCacheInstance; + +void VRasterBuffer::clear() +{ + memset(mBuffer, 0, mHeight * mBytesPerLine); +} + +VBitmap::Format VRasterBuffer::prepare(VBitmap *image) +{ + mBuffer = image->data(); + mWidth = image->width(); + mHeight = image->height(); + mBytesPerPixel = 4; + mBytesPerLine = image->stride(); + + mFormat = image->format(); + return mFormat; +} + +void VSpanData::init(VRasterBuffer *image) +{ + mRasterBuffer = image; + setDrawRegion(VRect(0, 0, image->width(), image->height())); + mType = VSpanData::Type::None; + mBlendFunc = nullptr; + mUnclippedBlendFunc = nullptr; +} + +extern CompositionFunction COMP_functionForMode_C[]; +extern CompositionFunctionSolid COMP_functionForModeSolid_C[]; +static const CompositionFunction * functionForMode = COMP_functionForMode_C; +static const CompositionFunctionSolid *functionForModeSolid = + COMP_functionForModeSolid_C; + +/* + * Gradient Draw routines + * + */ + +#define FIXPT_BITS 8 +#define FIXPT_SIZE (1 << FIXPT_BITS) +static inline void getLinearGradientValues(LinearGradientValues *v, + const VSpanData * data) +{ + const VGradientData *grad = &data->mGradient; + v->dx = grad->linear.x2 - grad->linear.x1; + v->dy = grad->linear.y2 - grad->linear.y1; + v->l = v->dx * v->dx + v->dy * v->dy; + v->off = 0; + if (v->l != 0) { + v->dx /= v->l; + v->dy /= v->l; + v->off = -v->dx * grad->linear.x1 - v->dy * grad->linear.y1; + } +} + +static inline void getRadialGradientValues(RadialGradientValues *v, + const VSpanData * data) +{ + const VGradientData &gradient = data->mGradient; + v->dx = gradient.radial.cx - gradient.radial.fx; + v->dy = gradient.radial.cy - gradient.radial.fy; + + v->dr = gradient.radial.cradius - gradient.radial.fradius; + v->sqrfr = gradient.radial.fradius * gradient.radial.fradius; + + v->a = v->dr * v->dr - v->dx * v->dx - v->dy * v->dy; + v->inv2a = 1 / (2 * v->a); + + v->extended = !vIsZero(gradient.radial.fradius) || v->a <= 0; +} + +static inline int gradientClamp(const VGradientData *grad, int ipos) +{ + int limit; + + if (grad->mSpread == VGradient::Spread::Repeat) { + ipos = ipos % VGradient::colorTableSize; + ipos = ipos < 0 ? VGradient::colorTableSize + ipos : ipos; + } else if (grad->mSpread == VGradient::Spread::Reflect) { + limit = VGradient::colorTableSize * 2; + ipos = ipos % limit; + ipos = ipos < 0 ? limit + ipos : ipos; + ipos = ipos >= VGradient::colorTableSize ? limit - 1 - ipos : ipos; + } else { + if (ipos < 0) + ipos = 0; + else if (ipos >= VGradient::colorTableSize) + ipos = VGradient::colorTableSize - 1; + } + return ipos; +} + +static uint32_t gradientPixelFixed(const VGradientData *grad, int fixed_pos) +{ + int ipos = (fixed_pos + (FIXPT_SIZE / 2)) >> FIXPT_BITS; + + return grad->mColorTable[gradientClamp(grad, ipos)]; +} + +static inline uint32_t gradientPixel(const VGradientData *grad, float pos) +{ + int ipos = (int)(pos * (VGradient::colorTableSize - 1) + (float)(0.5)); + + return grad->mColorTable[gradientClamp(grad, ipos)]; +} + +void fetch_linear_gradient(uint32_t *buffer, const Operator *op, + const VSpanData *data, int y, int x, int length) +{ + float t, inc; + const VGradientData *gradient = &data->mGradient; + + bool affine = true; + float rx = 0, ry = 0; + if (op->linear.l == 0) { + t = inc = 0; + } else { + rx = data->m21 * (y + float(0.5)) + data->m11 * (x + float(0.5)) + + data->dx; + ry = data->m22 * (y + float(0.5)) + data->m12 * (x + float(0.5)) + + data->dy; + t = op->linear.dx * rx + op->linear.dy * ry + op->linear.off; + inc = op->linear.dx * data->m11 + op->linear.dy * data->m12; + affine = !data->m13 && !data->m23; + + if (affine) { + t *= (VGradient::colorTableSize - 1); + inc *= (VGradient::colorTableSize - 1); + } + } + + const uint32_t *end = buffer + length; + if (affine) { + if (inc > float(-1e-5) && inc < float(1e-5)) { + memfill32(buffer, gradientPixelFixed(gradient, int(t * FIXPT_SIZE)), + length); + } else { + if (t + inc * length < float(INT_MAX >> (FIXPT_BITS + 1)) && + t + inc * length > float(INT_MIN >> (FIXPT_BITS + 1))) { + // we can use fixed point math + int t_fixed = int(t * FIXPT_SIZE); + int inc_fixed = int(inc * FIXPT_SIZE); + while (buffer < end) { + *buffer = gradientPixelFixed(gradient, t_fixed); + t_fixed += inc_fixed; + ++buffer; + } + } else { + // we have to fall back to float math + while (buffer < end) { + *buffer = + gradientPixel(gradient, t / VGradient::colorTableSize); + t += inc; + ++buffer; + } + } + } + } else { // fall back to float math here as well + float rw = data->m23 * (y + float(0.5)) + data->m13 * (x + float(0.5)) + + data->m33; + while (buffer < end) { + float x = rx / rw; + float y = ry / rw; + t = (op->linear.dx * x + op->linear.dy * y) + op->linear.off; + + *buffer = gradientPixel(gradient, t); + rx += data->m11; + ry += data->m12; + rw += data->m13; + if (!rw) { + rw += data->m13; + } + ++buffer; + } + } +} + +static inline float radialDeterminant(float a, float b, float c) +{ + return (b * b) - (4 * a * c); +} + +static void fetch(uint32_t *buffer, uint32_t *end, const Operator *op, + const VSpanData *data, float det, float delta_det, + float delta_delta_det, float b, float delta_b) +{ + if (op->radial.extended) { + while (buffer < end) { + uint32_t result = 0; + if (det >= 0) { + float w = std::sqrt(det) - b; + if (data->mGradient.radial.fradius + op->radial.dr * w >= 0) + result = gradientPixel(&data->mGradient, w); + } + + *buffer = result; + + det += delta_det; + delta_det += delta_delta_det; + b += delta_b; + + ++buffer; + } + } else { + while (buffer < end) { + *buffer++ = gradientPixel(&data->mGradient, std::sqrt(det) - b); + + det += delta_det; + delta_det += delta_delta_det; + b += delta_b; + } + } +} + +void fetch_radial_gradient(uint32_t *buffer, const Operator *op, + const VSpanData *data, int y, int x, int length) +{ + // avoid division by zero + if (vIsZero(op->radial.a)) { + memfill32(buffer, 0, length); + return; + } + + float rx = + data->m21 * (y + float(0.5)) + data->dx + data->m11 * (x + float(0.5)); + float ry = + data->m22 * (y + float(0.5)) + data->dy + data->m12 * (x + float(0.5)); + bool affine = !data->m13 && !data->m23; + + uint32_t *end = buffer + length; + if (affine) { + rx -= data->mGradient.radial.fx; + ry -= data->mGradient.radial.fy; + + float inv_a = 1 / float(2 * op->radial.a); + + const float delta_rx = data->m11; + const float delta_ry = data->m12; + + float b = 2 * (op->radial.dr * data->mGradient.radial.fradius + + rx * op->radial.dx + ry * op->radial.dy); + float delta_b = + 2 * (delta_rx * op->radial.dx + delta_ry * op->radial.dy); + const float b_delta_b = 2 * b * delta_b; + const float delta_b_delta_b = 2 * delta_b * delta_b; + + const float bb = b * b; + const float delta_bb = delta_b * delta_b; + + b *= inv_a; + delta_b *= inv_a; + + const float rxrxryry = rx * rx + ry * ry; + const float delta_rxrxryry = delta_rx * delta_rx + delta_ry * delta_ry; + const float rx_plus_ry = 2 * (rx * delta_rx + ry * delta_ry); + const float delta_rx_plus_ry = 2 * delta_rxrxryry; + + inv_a *= inv_a; + + float det = + (bb - 4 * op->radial.a * (op->radial.sqrfr - rxrxryry)) * inv_a; + float delta_det = (b_delta_b + delta_bb + + 4 * op->radial.a * (rx_plus_ry + delta_rxrxryry)) * + inv_a; + const float delta_delta_det = + (delta_b_delta_b + 4 * op->radial.a * delta_rx_plus_ry) * inv_a; + + fetch(buffer, end, op, data, det, delta_det, delta_delta_det, b, + delta_b); + } else { + float rw = data->m23 * (y + float(0.5)) + data->m33 + + data->m13 * (x + float(0.5)); + + while (buffer < end) { + if (rw == 0) { + *buffer = 0; + } else { + float invRw = 1 / rw; + float gx = rx * invRw - data->mGradient.radial.fx; + float gy = ry * invRw - data->mGradient.radial.fy; + float b = 2 * (op->radial.dr * data->mGradient.radial.fradius + + gx * op->radial.dx + gy * op->radial.dy); + float det = radialDeterminant( + op->radial.a, b, op->radial.sqrfr - (gx * gx + gy * gy)); + + uint32_t result = 0; + if (det >= 0) { + float detSqrt = std::sqrt(det); + + float s0 = (-b - detSqrt) * op->radial.inv2a; + float s1 = (-b + detSqrt) * op->radial.inv2a; + + float s = vMax(s0, s1); + + if (data->mGradient.radial.fradius + op->radial.dr * s >= 0) + result = gradientPixel(&data->mGradient, s); + } + + *buffer = result; + } + + rx += data->m11; + ry += data->m12; + rw += data->m13; + + ++buffer; + } + } +} + +static inline Operator getOperator(const VSpanData *data, const VRle::Span *, + size_t) +{ + Operator op; + bool solidSource = false; + + switch (data->mType) { + case VSpanData::Type::Solid: + solidSource = (vAlpha(data->mSolid) == 255); + op.srcFetch = nullptr; + break; + case VSpanData::Type::LinearGradient: + solidSource = false; + getLinearGradientValues(&op.linear, data); + op.srcFetch = &fetch_linear_gradient; + break; + case VSpanData::Type::RadialGradient: + solidSource = false; + getRadialGradientValues(&op.radial, data); + op.srcFetch = &fetch_radial_gradient; + break; + default: + op.srcFetch = nullptr; + break; + } + + op.mode = data->mCompositionMode; + if (op.mode == VPainter::CompModeSrcOver && solidSource) + op.mode = VPainter::CompModeSrc; + + op.funcSolid = functionForModeSolid[op.mode]; + op.func = functionForMode[op.mode]; + + return op; +} + +static void blendColorARGB(size_t count, const VRle::Span *spans, + void *userData) +{ + VSpanData *data = (VSpanData *)(userData); + Operator op = getOperator(data, spans, count); + const uint color = data->mSolid; + + if (op.mode == VPainter::CompModeSrc) { + // inline for performance + while (count--) { + uint *target = data->buffer(spans->x, spans->y); + if (spans->coverage == 255) { + memfill32(target, color, spans->len); + } else { + uint c = BYTE_MUL(color, spans->coverage); + int ialpha = 255 - spans->coverage; + for (int i = 0; i < spans->len; ++i) + target[i] = c + BYTE_MUL(target[i], ialpha); + } + ++spans; + } + return; + } + + while (count--) { + uint *target = data->buffer(spans->x, spans->y); + op.funcSolid(target, spans->len, color, spans->coverage); + ++spans; + } +} + +#define BLEND_GRADIENT_BUFFER_SIZE 2048 +static void blendGradientARGB(size_t count, const VRle::Span *spans, + void *userData) +{ + VSpanData *data = (VSpanData *)(userData); + Operator op = getOperator(data, spans, count); + + unsigned int buffer[BLEND_GRADIENT_BUFFER_SIZE]; + + if (!op.srcFetch) return; + + while (count--) { + uint *target = data->buffer(spans->x, spans->y); + int length = spans->len; + while (length) { + int l = std::min(length, BLEND_GRADIENT_BUFFER_SIZE); + op.srcFetch(buffer, &op, data, spans->y, spans->x, l); + op.func(target, buffer, l, spans->coverage); + target += l; + length -= l; + } + ++spans; + } +} + +template +constexpr const T &clamp(const T &v, const T &lo, const T &hi) +{ + return v < lo ? lo : hi < v ? hi : v; +} + +static const int buffer_size = 1024; +static const int fixed_scale = 1 << 16; +static void blend_transformed_argb(size_t count, const VRle::Span *spans, + void *userData) +{ + VSpanData *data = reinterpret_cast(userData); + if (data->mBitmap.format != VBitmap::Format::ARGB32_Premultiplied && + data->mBitmap.format != VBitmap::Format::ARGB32) { + //@TODO other formats not yet handled. + return; + } + + Operator op = getOperator(data, spans, count); + uint buffer[buffer_size]; + + const int image_x1 = data->mBitmap.x1; + const int image_y1 = data->mBitmap.y1; + const int image_x2 = data->mBitmap.x2 - 1; + const int image_y2 = data->mBitmap.y2 - 1; + + if (data->fast_matrix) { + // The increment pr x in the scanline + int fdx = (int)(data->m11 * fixed_scale); + int fdy = (int)(data->m12 * fixed_scale); + + while (count--) { + uint *target = data->buffer(spans->x, spans->y); + + const float cx = spans->x + float(0.5); + const float cy = spans->y + float(0.5); + + int x = + int((data->m21 * cy + data->m11 * cx + data->dx) * fixed_scale); + int y = + int((data->m22 * cy + data->m12 * cx + data->dy) * fixed_scale); + + int length = spans->len; + const int coverage = + (spans->coverage * data->mBitmap.const_alpha) >> 8; + while (length) { + int l = std::min(length, buffer_size); + const uint *end = buffer + l; + uint * b = buffer; + while (b < end) { + int px = clamp(x >> 16, image_x1, image_x2); + int py = clamp(y >> 16, image_y1, image_y2); + *b = reinterpret_cast( + data->mBitmap.scanLine(py))[px]; + + x += fdx; + y += fdy; + ++b; + } + op.func(target, buffer, l, coverage); + target += l; + length -= l; + } + ++spans; + } + } else { + const float fdx = data->m11; + const float fdy = data->m12; + const float fdw = data->m13; + while (count--) { + uint *target = data->buffer(spans->x, spans->y); + + const float cx = spans->x + float(0.5); + const float cy = spans->y + float(0.5); + + float x = data->m21 * cy + data->m11 * cx + data->dx; + float y = data->m22 * cy + data->m12 * cx + data->dy; + float w = data->m23 * cy + data->m13 * cx + data->m33; + + int length = spans->len; + const int coverage = + (spans->coverage * data->mBitmap.const_alpha) >> 8; + while (length) { + int l = std::min(length, buffer_size); + const uint *end = buffer + l; + uint * b = buffer; + while (b < end) { + const float iw = w == 0 ? 1 : 1 / w; + const float tx = x * iw; + const float ty = y * iw; + const int px = + clamp(int(tx) - (tx < 0), image_x1, image_x2); + const int py = + clamp(int(ty) - (ty < 0), image_y1, image_y2); + + *b = reinterpret_cast( + data->mBitmap.scanLine(py))[px]; + x += fdx; + y += fdy; + w += fdw; + + ++b; + } + op.func(target, buffer, l, coverage); + target += l; + length -= l; + } + ++spans; + } + } +} + +static void blend_untransformed_argb(size_t count, const VRle::Span *spans, + void *userData) +{ + VSpanData *data = reinterpret_cast(userData); + if (data->mBitmap.format != VBitmap::Format::ARGB32_Premultiplied && + data->mBitmap.format != VBitmap::Format::ARGB32) { + //@TODO other formats not yet handled. + return; + } + + Operator op = getOperator(data, spans, count); + + const int image_width = data->mBitmap.width; + const int image_height = data->mBitmap.height; + + int xoff = data->dx; + int yoff = data->dy; + + while (count--) { + int x = spans->x; + int length = spans->len; + int sx = xoff + x; + int sy = yoff + spans->y; + if (sy >= 0 && sy < image_height && sx < image_width) { + if (sx < 0) { + x -= sx; + length += sx; + sx = 0; + } + if (sx + length > image_width) length = image_width - sx; + if (length > 0) { + const int coverage = + (spans->coverage * data->mBitmap.const_alpha) >> 8; + const uint *src = (const uint *)data->mBitmap.scanLine(sy) + sx; + uint * dest = data->buffer(x, spans->y); + op.func(dest, src, length, coverage); + } + } + ++spans; + } +} + +void VSpanData::setup(const VBrush &brush, VPainter::CompositionMode /*mode*/, + int /*alpha*/) +{ + transformType = VMatrix::MatrixType::None; + + switch (brush.type()) { + case VBrush::Type::NoBrush: + mType = VSpanData::Type::None; + break; + case VBrush::Type::Solid: + mType = VSpanData::Type::Solid; + mSolid = brush.mColor.premulARGB(); + break; + case VBrush::Type::LinearGradient: { + mType = VSpanData::Type::LinearGradient; + mColorTable = VGradientCacheInstance.getBuffer(*brush.mGradient); + mGradient.mColorTable = mColorTable->buffer32; + mGradient.mColorTableAlpha = mColorTable->alpha; + mGradient.linear.x1 = brush.mGradient->linear.x1; + mGradient.linear.y1 = brush.mGradient->linear.y1; + mGradient.linear.x2 = brush.mGradient->linear.x2; + mGradient.linear.y2 = brush.mGradient->linear.y2; + mGradient.mSpread = brush.mGradient->mSpread; + setupMatrix(brush.mGradient->mMatrix); + break; + } + case VBrush::Type::RadialGradient: { + mType = VSpanData::Type::RadialGradient; + mColorTable = VGradientCacheInstance.getBuffer(*brush.mGradient); + mGradient.mColorTable = mColorTable->buffer32; + mGradient.mColorTableAlpha = mColorTable->alpha; + mGradient.radial.cx = brush.mGradient->radial.cx; + mGradient.radial.cy = brush.mGradient->radial.cy; + mGradient.radial.fx = brush.mGradient->radial.fx; + mGradient.radial.fy = brush.mGradient->radial.fy; + mGradient.radial.cradius = brush.mGradient->radial.cradius; + mGradient.radial.fradius = brush.mGradient->radial.fradius; + mGradient.mSpread = brush.mGradient->mSpread; + setupMatrix(brush.mGradient->mMatrix); + break; + } + case VBrush::Type::Texture: { + mType = VSpanData::Type::Texture; + initTexture( + &brush.mTexture, 255, VBitmapData::Plain, + VRect(0, 0, brush.mTexture.width(), brush.mTexture.height())); + setupMatrix(brush.mMatrix); + break; + } + default: + break; + } + updateSpanFunc(); +} + +void VSpanData::setupMatrix(const VMatrix &matrix) +{ + VMatrix inv = matrix.inverted(); + m11 = inv.m11; + m12 = inv.m12; + m13 = inv.m13; + m21 = inv.m21; + m22 = inv.m22; + m23 = inv.m23; + m33 = inv.m33; + dx = inv.mtx; + dy = inv.mty; + transformType = inv.type(); + + const bool affine = inv.isAffine(); + const float f1 = m11 * m11 + m21 * m21; + const float f2 = m12 * m12 + m22 * m22; + fast_matrix = affine && f1 < 1e4 && f2 < 1e4 && f1 > (1.0 / 65536) && + f2 > (1.0 / 65536) && fabs(dx) < 1e4 && fabs(dy) < 1e4; +} + +void VSpanData::initTexture(const VBitmap *bitmap, int alpha, + VBitmapData::Type type, const VRect &sourceRect) +{ + mType = VSpanData::Type::Texture; + + mBitmap.imageData = bitmap->data(); + mBitmap.width = bitmap->width(); + mBitmap.height = bitmap->height(); + mBitmap.bytesPerLine = bitmap->stride(); + mBitmap.format = bitmap->format(); + mBitmap.x1 = sourceRect.x(); + mBitmap.y1 = sourceRect.y(); + mBitmap.x2 = std::min(mBitmap.x1 + sourceRect.width(), mBitmap.width); + mBitmap.y2 = std::min(mBitmap.y1 + sourceRect.height(), mBitmap.height); + + mBitmap.const_alpha = alpha; + mBitmap.type = type; + + updateSpanFunc(); +} + +void VSpanData::updateSpanFunc() +{ + switch (mType) { + case VSpanData::Type::None: + mUnclippedBlendFunc = nullptr; + break; + case VSpanData::Type::Solid: + mUnclippedBlendFunc = &blendColorARGB; + break; + case VSpanData::Type::LinearGradient: + case VSpanData::Type::RadialGradient: { + mUnclippedBlendFunc = &blendGradientARGB; + break; + } + case VSpanData::Type::Texture: { + //@TODO update proper image function. + if (transformType <= VMatrix::MatrixType::Translate) { + mUnclippedBlendFunc = &blend_untransformed_argb; + } else { + mUnclippedBlendFunc = &blend_transformed_argb; + } + break; + } + } +} + +#if !defined(__ARM_NEON__) && !defined(__ARM64_NEON__) + +void memfill32(uint32_t *dest, uint32_t value, int length) +{ + int n; + + if (length <= 0) return; + + // Cute hack to align future memcopy operation + // and do unroll the loop a bit. Not sure it is + // the most efficient, but will do for now. + n = (length + 7) / 8; + switch (length & 0x07) { + case 0: + do { + *dest++ = value; + VECTOR_FALLTHROUGH; + case 7: + *dest++ = value; + VECTOR_FALLTHROUGH; + case 6: + *dest++ = value; + VECTOR_FALLTHROUGH; + case 5: + *dest++ = value; + VECTOR_FALLTHROUGH; + case 4: + *dest++ = value; + VECTOR_FALLTHROUGH; + case 3: + *dest++ = value; + VECTOR_FALLTHROUGH; + case 2: + *dest++ = value; + VECTOR_FALLTHROUGH; + case 1: + *dest++ = value; + } while (--n > 0); + } +} +#endif + +void vInitDrawhelperFunctions() +{ + vInitBlendFunctions(); + +#if defined(__ARM_NEON__) || defined(__ARM64_NEON__) + // update fast path for NEON + extern void comp_func_solid_SourceOver_neon( + uint32_t * dest, int length, uint32_t color, uint32_t const_alpha); + + COMP_functionForModeSolid_C[VPainter::CompModeSrcOver] = + comp_func_solid_SourceOver_neon; +#endif +} + +V_CONSTRUCTOR_FUNCTION(vInitDrawhelperFunctions) diff --git a/TMessagesProj/jni/rlottie/src/vector/vdrawhelper.h b/TMessagesProj/jni/rlottie/src/vector/vdrawhelper.h new file mode 100755 index 000000000..55e182fa8 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vdrawhelper.h @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VDRAWHELPER_H +#define VDRAWHELPER_H + +#include +#include +#include "assert.h" +#include "vbitmap.h" +#include "vbrush.h" +#include "vpainter.h" +#include "vrect.h" +#include "vrle.h" + +V_USE_NAMESPACE + +struct VSpanData; +struct Operator; + +typedef void (*CompositionFunctionSolid)(uint32_t *dest, int length, + uint32_t color, uint32_t const_alpha); +typedef void (*CompositionFunction)(uint32_t *dest, const uint32_t *src, + int length, uint32_t const_alpha); +typedef void (*SourceFetchProc)(uint32_t *buffer, const Operator *o, + const VSpanData *data, int y, int x, + int length); +typedef void (*ProcessRleSpan)(size_t count, const VRle::Span *spans, + void *userData); + +extern void memfill32(uint32_t *dest, uint32_t value, int count); + +struct LinearGradientValues { + float dx; + float dy; + float l; + float off; +}; + +struct RadialGradientValues { + float dx; + float dy; + float dr; + float sqrfr; + float a; + float inv2a; + bool extended; +}; + +struct Operator { + VPainter::CompositionMode mode; + SourceFetchProc srcFetch; + CompositionFunctionSolid funcSolid; + CompositionFunction func; + union { + LinearGradientValues linear; + RadialGradientValues radial; + }; +}; + +class VRasterBuffer { +public: + VBitmap::Format prepare(VBitmap *image); + void clear(); + + void resetBuffer(int val = 0); + + inline uchar *scanLine(int y) + { + assert(y >= 0); + assert(y < mHeight); + return mBuffer + y * mBytesPerLine; + } + + int width() const { return mWidth; } + int height() const { return mHeight; } + int bytesPerLine() const { return mBytesPerLine; } + int bytesPerPixel() const { return mBytesPerPixel; } + + VBitmap::Format mFormat{VBitmap::Format::ARGB32}; +private: + int mWidth{0}; + int mHeight{0}; + int mBytesPerLine{0}; + int mBytesPerPixel{0}; + uchar *mBuffer{nullptr}; +}; + +struct VGradientData { + VGradient::Spread mSpread; + union { + struct { + float x1, y1, x2, y2; + } linear; + struct { + float cx, cy, fx, fy, cradius, fradius; + } radial; + }; + const uint32_t *mColorTable; + bool mColorTableAlpha; +}; + +struct VBitmapData +{ + const uchar *imageData; + const uchar *scanLine(int y) const { return imageData + y*bytesPerLine; } + + int width; + int height; + // clip rect + int x1; + int y1; + int x2; + int y2; + uint bytesPerLine; + VBitmap::Format format; + bool hasAlpha; + enum Type { + Plain, + Tiled + }; + Type type; + int const_alpha; +}; + +struct VColorTable +{ + uint32_t buffer32[VGradient::colorTableSize]; + bool alpha{true}; +}; + +struct VSpanData { + enum class Type { None, Solid, LinearGradient, RadialGradient, Texture }; + + void updateSpanFunc(); + void init(VRasterBuffer *image); + void setup(const VBrush & brush, + VPainter::CompositionMode mode = VPainter::CompModeSrcOver, + int alpha = 255); + void setupMatrix(const VMatrix &matrix); + + VRect clipRect() const + { + return VRect(0, 0, mDrawableSize.width(), mDrawableSize.height()); + } + + void setDrawRegion(const VRect ®ion) + { + mOffset = VPoint(region.left(), region.top()); + mDrawableSize = VSize(region.width(), region.height()); + } + + uint *buffer(int x, int y) const + { + return (uint *)(mRasterBuffer->scanLine(y + mOffset.y())) + x + mOffset.x(); + } + void initTexture(const VBitmap *image, int alpha, VBitmapData::Type type, const VRect &sourceRect); + + VPainter::CompositionMode mCompositionMode{VPainter::CompositionMode::CompModeSrcOver}; + VRasterBuffer * mRasterBuffer; + ProcessRleSpan mBlendFunc; + ProcessRleSpan mUnclippedBlendFunc; + VSpanData::Type mType; + std::shared_ptr mColorTable{nullptr}; + VPoint mOffset; // offset to the subsurface + VSize mDrawableSize;// suburface size + union { + uint32_t mSolid; + VGradientData mGradient; + VBitmapData mBitmap; + }; + float m11, m12, m13, m21, m22, m23, m33, dx, dy; // inverse xform matrix + bool fast_matrix{true}; + VMatrix::MatrixType transformType{VMatrix::MatrixType::None}; +}; + +void vInitDrawhelperFunctions(); +extern void vInitBlendFunctions(); + +#define BYTE_MUL(c, a) \ + ((((((c) >> 8) & 0x00ff00ff) * (a)) & 0xff00ff00) + \ + (((((c)&0x00ff00ff) * (a)) >> 8) & 0x00ff00ff)) + +inline constexpr int vRed(uint32_t c) +{ + return ((c >> 16) & 0xff); +} + +inline constexpr int vGreen(uint32_t c) +{ + return ((c >> 8) & 0xff); +} + +inline constexpr int vBlue(uint32_t c) +{ + return (c & 0xff); +} + +inline constexpr int vAlpha(uint32_t c) +{ + return c >> 24; +} + +static inline uint INTERPOLATE_PIXEL_255(uint x, uint a, uint y, uint b) +{ + uint t = (x & 0xff00ff) * a + (y & 0xff00ff) * b; + t >>= 8; + t &= 0xff00ff; + x = ((x >> 8) & 0xff00ff) * a + ((y >> 8) & 0xff00ff) * b; + x &= 0xff00ff00; + x |= t; + return x; +} + +#define LOOP_ALIGNED_U1_A4(DEST, LENGTH, UOP, A4OP) \ + { \ + while ((uintptr_t)DEST & 0xF && LENGTH) \ + UOP \ + \ + while (LENGTH) \ + { \ + switch (LENGTH) { \ + case 3: \ + case 2: \ + case 1: \ + UOP break; \ + default: \ + A4OP break; \ + } \ + } \ + } + +#endif // QDRAWHELPER_P_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vdrawhelper_neon.cpp b/TMessagesProj/jni/rlottie/src/vector/vdrawhelper_neon.cpp new file mode 100755 index 000000000..2ef56ef03 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vdrawhelper_neon.cpp @@ -0,0 +1,27 @@ +#if defined(__ARM_NEON__) || defined(__ARM64_NEON__) + +#include "vdrawhelper.h" + +extern "C" void pixman_composite_src_n_8888_asm_neon(int32_t w, int32_t h, + uint32_t *dst, + int32_t dst_stride, + uint32_t src); + +extern "C" void pixman_composite_over_n_8888_asm_neon(int32_t w, int32_t h, + uint32_t *dst, + int32_t dst_stride, + uint32_t src); + +void memfill32(uint32_t *dest, uint32_t value, int length) +{ + pixman_composite_src_n_8888_asm_neon(length, 1, dest, length, value); +} + +void comp_func_solid_SourceOver_neon(uint32_t *dest, int length, uint32_t color, + uint32_t const_alpha) +{ + if (const_alpha != 255) color = BYTE_MUL(color, const_alpha); + + pixman_composite_over_n_8888_asm_neon(length, 1, dest, length, color); +} +#endif diff --git a/TMessagesProj/jni/rlottie/src/vector/velapsedtimer.cpp b/TMessagesProj/jni/rlottie/src/vector/velapsedtimer.cpp new file mode 100755 index 000000000..4c7402747 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/velapsedtimer.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "velapsedtimer.h" + +void VElapsedTimer::start() +{ + clock = std::chrono::high_resolution_clock::now(); + m_valid = true; +} + +double VElapsedTimer::restart() +{ + double elapsedTime = elapsed(); + start(); + return elapsedTime; +} + +double VElapsedTimer::elapsed() const +{ + if (!isValid()) return 0; + return std::chrono::duration( + std::chrono::high_resolution_clock::now() - clock) + .count(); +} + +bool VElapsedTimer::hasExpired(double time) +{ + double elapsedTime = elapsed(); + if (elapsedTime > time) return true; + return false; +} diff --git a/TMessagesProj/jni/rlottie/src/vector/velapsedtimer.h b/TMessagesProj/jni/rlottie/src/vector/velapsedtimer.h new file mode 100755 index 000000000..108fd6654 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/velapsedtimer.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VELAPSEDTIMER_H +#define VELAPSEDTIMER_H + +#include +#include "vglobal.h" + +class VElapsedTimer { +public: + double elapsed() const; + bool hasExpired(double millsec); + void start(); + double restart(); + inline bool isValid() const { return m_valid; } + +private: + std::chrono::high_resolution_clock::time_point clock; + bool m_valid{false}; +}; +#endif // VELAPSEDTIMER_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vglobal.h b/TMessagesProj/jni/rlottie/src/vector/vglobal.h new file mode 100755 index 000000000..98837fd23 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vglobal.h @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VGLOBAL_H +#define VGLOBAL_H + +#include +#include +#include +#include +#include + +typedef uint32_t uint; +typedef uint16_t ushort; +typedef uint8_t uchar; + +#if !defined(V_NAMESPACE) + +#define V_USE_NAMESPACE +#define V_BEGIN_NAMESPACE +#define V_END_NAMESPACE + +#else /* user namespace */ + +#define V_USE_NAMESPACE using namespace ::V_NAMESPACE; +#define V_BEGIN_NAMESPACE namespace V_NAMESPACE { +#define V_END_NAMESPACE } + +#endif + +#ifndef __has_attribute +# define __has_attribute(x) 0 +#endif /* !__has_attribute */ + +#if __has_attribute(unused) +# define V_UNUSED __attribute__((__unused__)) +#else +# define V_UNUSED +#endif /* V_UNUSED */ + +#if __has_attribute(warn_unused_result) +# define V_REQUIRED_RESULT __attribute__((__warn_unused_result__)) +#else +# define V_REQUIRED_RESULT +#endif /* V_REQUIRED_RESULT */ + +#define V_CONSTEXPR constexpr +#define V_NOTHROW noexcept + +#include "vdebug.h" + +#if __GNUC__ >= 7 +#define VECTOR_FALLTHROUGH __attribute__ ((fallthrough)); +#else +#define VECTOR_FALLTHROUGH +#endif + +#include +class RefCount { +public: + inline RefCount(int i) : atomic(i) {} + inline bool ref() + { + int count = atomic.load(); + if (count == 0) // !isSharable + return false; + if (count != -1) // !isStatic + atomic.fetch_add(1); + return true; + } + inline bool deref() + { + int count = atomic.load(); + if (count == 0) // !isSharable + return false; + if (count == -1) // isStatic + return true; + atomic.fetch_sub(1); + return --count; + } + bool isShared() const + { + int count = atomic.load(); + return (count != 1) && (count != 0); + } + bool isStatic() const + { + // Persistent object, never deleted + int count = atomic.load(); + return count == -1; + } + inline int count() const { return atomic; } + void setOwned() { atomic.store(1); } + +private: + std::atomic atomic; +}; + +template +V_CONSTEXPR inline const T &vMin(const T &a, const T &b) +{ + return (a < b) ? a : b; +} +template +V_CONSTEXPR inline const T &vMax(const T &a, const T &b) +{ + return (a < b) ? b : a; +} + +static const double EPSILON_DOUBLE = 0.000000000001f; +static const float EPSILON_FLOAT = 0.000001f; + +static inline bool vCompare(float p1, float p2) +{ + return (std::abs(p1 - p2) < EPSILON_FLOAT); +} + +static inline bool vIsZero(float f) +{ + return (std::abs(f) <= EPSILON_FLOAT); +} + +static inline bool vIsZero(double f) +{ + return (std::abs(f) <= EPSILON_DOUBLE); +} + +class vFlagHelper { + int i; + +public: + constexpr inline vFlagHelper(int ai) noexcept : i(ai) {} + constexpr inline operator int() const noexcept { return i; } + + constexpr inline vFlagHelper(uint ai) noexcept : i(int(ai)) {} + constexpr inline vFlagHelper(short ai) noexcept : i(int(ai)) {} + constexpr inline vFlagHelper(ushort ai) noexcept : i(int(uint(ai))) {} + constexpr inline operator uint() const noexcept { return uint(i); } +}; + +template +class vFlag { +public: + static_assert( + (sizeof(Enum) <= sizeof(int)), + "vFlag only supports int as storage so bigger type will overflow"); + static_assert((std::is_enum::value), + "vFlag is only usable on enumeration types."); + + typedef typename std::conditional< + std::is_unsigned::type>::value, + unsigned int, signed int>::type Int; + + typedef Enum enum_type; + // compiler-generated copy/move ctor/assignment operators are fine! + + constexpr inline vFlag(Enum f) noexcept : i(Int(f)) {} + constexpr inline vFlag() noexcept : i(0) {} + constexpr inline vFlag(vFlagHelper f) noexcept : i(f) {} + + inline vFlag &operator&=(int mask) noexcept + { + i &= mask; + return *this; + } + inline vFlag &operator&=(uint mask) noexcept + { + i &= mask; + return *this; + } + inline vFlag &operator&=(Enum mask) noexcept + { + i &= Int(mask); + return *this; + } + inline vFlag &operator|=(vFlag f) noexcept + { + i |= f.i; + return *this; + } + inline vFlag &operator|=(Enum f) noexcept + { + i |= Int(f); + return *this; + } + inline vFlag &operator^=(vFlag f) noexcept + { + i ^= f.i; + return *this; + } + inline vFlag &operator^=(Enum f) noexcept + { + i ^= Int(f); + return *this; + } + + constexpr inline operator Int() const noexcept { return i; } + + constexpr inline vFlag operator|(vFlag f) const + { + return vFlag(vFlagHelper(i | f.i)); + } + constexpr inline vFlag operator|(Enum f) const noexcept + { + return vFlag(vFlagHelper(i | Int(f))); + } + constexpr inline vFlag operator^(vFlag f) const noexcept + { + return vFlag(vFlagHelper(i ^ f.i)); + } + constexpr inline vFlag operator^(Enum f) const noexcept + { + return vFlag(vFlagHelper(i ^ Int(f))); + } + constexpr inline vFlag operator&(int mask) const noexcept + { + return vFlag(vFlagHelper(i & mask)); + } + constexpr inline vFlag operator&(uint mask) const noexcept + { + return vFlag(vFlagHelper(i & mask)); + } + constexpr inline vFlag operator&(Enum f) const noexcept + { + return vFlag(vFlagHelper(i & Int(f))); + } + constexpr inline vFlag operator~() const noexcept + { + return vFlag(vFlagHelper(~i)); + } + + constexpr inline bool operator!() const noexcept { return !i; } + + constexpr inline bool testFlag(Enum f) const noexcept + { + return (i & Int(f)) == Int(f) && (Int(f) != 0 || i == Int(f)); + } + inline vFlag &setFlag(Enum f, bool on = true) noexcept + { + return on ? (*this |= f) : (*this &= ~f); + } + + Int i; +}; + +class VColor { +public: + inline VColor() noexcept { a = r = g = b = 0; } + inline VColor(int red, int green, int blue, int alpha = 255) noexcept + { + r = red; + g = green; + b = blue; + a = alpha; + } + inline int red() const noexcept { return r; } + inline int green() const noexcept { return g; } + inline int blue() const noexcept { return b; } + inline int alpha() const noexcept { return a; } + inline void setRed(int red) noexcept { r = red; } + inline void setGreen(int green) noexcept { g = green; } + inline void setBlue(int blue) noexcept { b = blue; } + inline void setAlpha(int alpha) noexcept { a = alpha; } + inline bool isOpaque() const { return a == 255; } + inline bool operator==(const VColor &o) const + { + return ((a == o.a) && (r == o.r) && (g == o.g) && (b == o.b)); + } + uint premulARGB() const + { + int pr = (r * a) / 255; + int pg = (g * a) / 255; + int pb = (b * a) / 255; + return uint((a << 24) | (pr << 16) | (pg << 8) | (pb)); + } + + uint premulARGB(float opacity) const + { + int alpha = a * opacity; + int pr = (r * alpha) / 255; + int pg = (g * alpha) / 255; + int pb = (b * alpha) / 255; + return uint((alpha << 24) | (pr << 16) | (pg << 8) | (pb)); + } + +public: + uchar a; + uchar r; + uchar g; + uchar b; +}; + +enum class FillRule: unsigned char { EvenOdd, Winding }; +enum class JoinStyle: unsigned char { Miter, Bevel, Round }; +enum class CapStyle: unsigned char { Flat, Square, Round }; + +#ifndef V_CONSTRUCTOR_FUNCTION +#define V_CONSTRUCTOR_FUNCTION0(AFUNC) \ + namespace { \ + static const struct AFUNC##_ctor_class_ { \ + inline AFUNC##_ctor_class_() { AFUNC(); } \ + } AFUNC##_ctor_instance_; \ + } + +#define V_CONSTRUCTOR_FUNCTION(AFUNC) V_CONSTRUCTOR_FUNCTION0(AFUNC) +#endif + +#endif // VGLOBAL_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vimageloader.cpp b/TMessagesProj/jni/rlottie/src/vector/vimageloader.cpp new file mode 100755 index 000000000..79d7de282 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vimageloader.cpp @@ -0,0 +1,226 @@ +#include "vimageloader.h" +#include "config.h" +#include "vdebug.h" +#ifndef WIN32 +#include +#else +#include +#endif +#include + +using lottie_image_load_f = unsigned char *(*)(const char *filename, int *x, + int *y, int *comp, int req_comp); +using lottie_image_load_data_f = unsigned char *(*)(const char *data, int len, + int *x, int *y, int *comp, + int req_comp); +using lottie_image_free_f = void (*)(unsigned char *); + +#ifdef __cplusplus +extern "C" { +#endif + +extern unsigned char *lottie_image_load(char const *filename, int *x, int *y, + int *comp, int req_comp); +extern unsigned char *lottie_image_load_from_data(const char *imageData, + int len, int *x, int *y, + int *comp, int req_comp); +extern void lottie_image_free(unsigned char *data); + +#ifdef __cplusplus +} +#endif + +struct VImageLoader::Impl { + lottie_image_load_f imageLoad{nullptr}; + lottie_image_free_f imageFree{nullptr}; + lottie_image_load_data_f imageFromData{nullptr}; + +#ifndef LOTTIE_STATIC_IMAGE_LOADER +#ifdef WIN32 + HMODULE dl_handle{nullptr}; + bool moduleLoad() + { + dl_handle = LoadLibraryA("librlottie-image-loader.dll"); + return (dl_handle == nullptr); + } + void moduleFree() + { + if (dl_handle) FreeLibrary(dl_handle); + } + void init() + { + imageLoad = + (lottie_image_load_f)GetProcAddress(dl_handle, "lottie_image_load"); + imageFree = + (lottie_image_free_f)GetProcAddress(dl_handle, "lottie_image_free"); + imageFromData = (lottie_image_load_data_f)GetProcAddress( + dl_handle, "lottie_image_load_from_data"); + } +#else + void *dl_handle{nullptr}; + void init() + { + imageLoad = (lottie_image_load_f)dlsym(dl_handle, "lottie_image_load"); + imageFree = (lottie_image_free_f)dlsym(dl_handle, "lottie_image_free"); + imageFromData = (lottie_image_load_data_f)dlsym( + dl_handle, "lottie_image_load_from_data"); + } + + void moduleFree() + { + if (dl_handle) dlclose(dl_handle); + } +#ifdef __APPLE__ + bool moduleLoad() + { + dl_handle = dlopen("librlottie-image-loader.dylib", RTLD_LAZY); + return (dl_handle == nullptr); + } +#else + bool moduleLoad() + { + dl_handle = dlopen("librlottie-image-loader.so", RTLD_LAZY); + return (dl_handle == nullptr); + } +#endif +#endif +#else + void *dl_handle{nullptr}; + void init() + { + imageLoad = lottie_image_load; + imageFree = lottie_image_free; + imageFromData = lottie_image_load_from_data; + } + void moduleFree() {} + bool moduleLoad() { return false; } +#endif + + Impl() + { + if (moduleLoad()) { + vWarning << "Failed to dlopen librlottie-image-loader library"; + return; + } + + init(); + + if (!imageLoad) + vWarning << "Failed to find symbol lottie_image_load in " + "librlottie-image-loader library"; + + if (!imageFree) + vWarning << "Failed to find symbol lottie_image_free in " + "librlottie-image-loader library"; + + if (!imageFromData) + vWarning << "Failed to find symbol lottie_image_load_data in " + "librlottie-image-loader library"; + } + + ~Impl() { moduleFree(); } + + VBitmap createBitmap(unsigned char *data, int width, int height, + int channel) + { + // premultiply alpha + if (channel == 4) + convertToBGRAPremul(data, width, height); + else + convertToBGRA(data, width, height); + + // create a bitmap of same size. + VBitmap result = + VBitmap(width, height, VBitmap::Format::ARGB32); + + // copy the data to bitmap buffer + memcpy(result.data(), data, width * height * 4); + + // free the image data + imageFree(data); + + return result; + } + + VBitmap load(const char *fileName) + { + if (!imageLoad) return VBitmap(); + + int width, height, n; + unsigned char *data = imageLoad(fileName, &width, &height, &n, 4); + + if (!data) { + return VBitmap(); + } + + return createBitmap(data, width, height, n); + } + + VBitmap load(const char *imageData, int len) + { + if (!imageFromData) return VBitmap(); + + int width, height, n; + unsigned char *data = + imageFromData(imageData, len, &width, &height, &n, 4); + + if (!data) { + return VBitmap(); + } + + return createBitmap(data, width, height, n); + } + /* + * convert from RGBA to BGRA and premultiply + */ + void convertToBGRAPremul(unsigned char *bits, int width, int height) + { + int pixelCount = width * height; + unsigned char *pix = bits; + for (int i = 0; i < pixelCount; i++) { + unsigned char r = pix[0]; + unsigned char g = pix[1]; + unsigned char b = pix[2]; + unsigned char a = pix[3]; + + r = (r * a) / 255; + g = (g * a) / 255; + b = (b * a) / 255; + + pix[0] = b; + pix[1] = g; + pix[2] = r; + + pix += 4; + } + } + /* + * convert from RGBA to BGRA + */ + void convertToBGRA(unsigned char *bits, int width, int height) + { + int pixelCount = width * height; + unsigned char *pix = bits; + for (int i = 0; i < pixelCount; i++) { + unsigned char r = pix[0]; + unsigned char b = pix[2]; + pix[0] = b; + pix[2] = r; + pix += 4; + } + } +}; + +VImageLoader::VImageLoader() : mImpl(std::make_unique()) {} + +VImageLoader::~VImageLoader() {} + +VBitmap VImageLoader::load(const char *fileName) +{ + return mImpl->load(fileName); +} + +VBitmap VImageLoader::load(const char *data, int len) +{ + return mImpl->load(data, len); +} diff --git a/TMessagesProj/jni/rlottie/src/vector/vimageloader.h b/TMessagesProj/jni/rlottie/src/vector/vimageloader.h new file mode 100755 index 000000000..2ca4cec39 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vimageloader.h @@ -0,0 +1,26 @@ +#ifndef VIMAGELOADER_H +#define VIMAGELOADER_H + +#include + +#include "vbitmap.h" + +class VImageLoader +{ +public: + static VImageLoader& instance() + { + static VImageLoader singleton; + return singleton; + } + + VBitmap load(const char *fileName); + VBitmap load(const char *data, int len); + ~VImageLoader(); +private: + VImageLoader(); + struct Impl; + std::unique_ptr mImpl; +}; + +#endif // VIMAGELOADER_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vinterpolator.cpp b/TMessagesProj/jni/rlottie/src/vector/vinterpolator.cpp new file mode 100755 index 000000000..94f454ad3 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vinterpolator.cpp @@ -0,0 +1,154 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Mozilla SMIL module. + * + * The Initial Developer of the Original Code is Brian Birtles. + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian Birtles + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "vinterpolator.h" +#include + +V_BEGIN_NAMESPACE + +#define NEWTON_ITERATIONS 4 +#define NEWTON_MIN_SLOPE 0.02 +#define SUBDIVISION_PRECISION 0.0000001 +#define SUBDIVISION_MAX_ITERATIONS 10 + +const float VInterpolator::kSampleStepSize = + 1.0 / float(VInterpolator::kSplineTableSize - 1); + +void VInterpolator::init(float aX1, float aY1, float aX2, float aY2) +{ + mX1 = aX1; + mY1 = aY1; + mX2 = aX2; + mY2 = aY2; + + if (mX1 != mY1 || mX2 != mY2) CalcSampleValues(); +} + +/*static*/ float VInterpolator::CalcBezier(float aT, float aA1, float aA2) +{ + // use Horner's scheme to evaluate the Bezier polynomial + return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; +} + +void VInterpolator::CalcSampleValues() +{ + for (int i = 0; i < kSplineTableSize; ++i) { + mSampleValues[i] = CalcBezier(float(i) * kSampleStepSize, mX1, mX2); + } +} + +float VInterpolator::GetSlope(float aT, float aA1, float aA2) +{ + return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); +} + +float VInterpolator::value(float aX) const +{ + if (mX1 == mY1 && mX2 == mY2) return aX; + + return CalcBezier(GetTForX(aX), mY1, mY2); +} + +float VInterpolator::GetTForX(float aX) const +{ + // Find interval where t lies + float intervalStart = 0.0; + const float* currentSample = &mSampleValues[1]; + const float* const lastSample = &mSampleValues[kSplineTableSize - 1]; + for (; currentSample != lastSample && *currentSample <= aX; + ++currentSample) { + intervalStart += kSampleStepSize; + } + --currentSample; // t now lies between *currentSample and *currentSample+1 + + // Interpolate to provide an initial guess for t + float dist = + (aX - *currentSample) / (*(currentSample + 1) - *currentSample); + float guessForT = intervalStart + dist * kSampleStepSize; + + // Check the slope to see what strategy to use. If the slope is too small + // Newton-Raphson iteration won't converge on a root so we use bisection + // instead. + float initialSlope = GetSlope(guessForT, mX1, mX2); + if (initialSlope >= NEWTON_MIN_SLOPE) { + return NewtonRaphsonIterate(aX, guessForT); + } else if (initialSlope == 0.0) { + return guessForT; + } else { + return BinarySubdivide(aX, intervalStart, + intervalStart + kSampleStepSize); + } +} + +float VInterpolator::NewtonRaphsonIterate(float aX, float aGuessT) const +{ + // Refine guess with Newton-Raphson iteration + for (int i = 0; i < NEWTON_ITERATIONS; ++i) { + // We're trying to find where f(t) = aX, + // so we're actually looking for a root for: CalcBezier(t) - aX + float currentX = CalcBezier(aGuessT, mX1, mX2) - aX; + float currentSlope = GetSlope(aGuessT, mX1, mX2); + + if (currentSlope == 0.0) return aGuessT; + + aGuessT -= currentX / currentSlope; + } + + return aGuessT; +} + +float VInterpolator::BinarySubdivide(float aX, float aA, float aB) const +{ + float currentX; + float currentT; + int i = 0; + + do { + currentT = aA + (aB - aA) / 2.0; + currentX = CalcBezier(currentT, mX1, mX2) - aX; + + if (currentX > 0.0) { + aB = currentT; + } else { + aA = currentT; + } + } while (fabs(currentX) > SUBDIVISION_PRECISION && + ++i < SUBDIVISION_MAX_ITERATIONS); + + return currentT; +} + +V_END_NAMESPACE diff --git a/TMessagesProj/jni/rlottie/src/vector/vinterpolator.h b/TMessagesProj/jni/rlottie/src/vector/vinterpolator.h new file mode 100755 index 000000000..77aaaae26 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vinterpolator.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VINTERPOLATOR_H +#define VINTERPOLATOR_H + +#include "vpoint.h" + +V_BEGIN_NAMESPACE + +class VInterpolator { +public: + VInterpolator() + { /* caller must call Init later */ + } + + VInterpolator(float aX1, float aY1, float aX2, float aY2) + { + init(aX1, aY1, aX2, aY2); + } + + VInterpolator(VPointF pt1, VPointF pt2) + { + init(pt1.x(), pt1.y(), pt2.x(), pt2.y()); + } + + void init(float aX1, float aY1, float aX2, float aY2); + + float value(float aX) const; + + void GetSplineDerivativeValues(float aX, float& aDX, float& aDY) const; + +private: + void CalcSampleValues(); + + /** + * Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. + */ + static float CalcBezier(float aT, float aA1, float aA2); + + /** + * Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. + */ + static float GetSlope(float aT, float aA1, float aA2); + + float GetTForX(float aX) const; + + float NewtonRaphsonIterate(float aX, float aGuessT) const; + + float BinarySubdivide(float aX, float aA, float aB) const; + + static float A(float aA1, float aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; } + + static float B(float aA1, float aA2) { return 3.0 * aA2 - 6.0 * aA1; } + + static float C(float aA1) { return 3.0 * aA1; } + + float mX1; + float mY1; + float mX2; + float mY2; + enum { kSplineTableSize = 11 }; + float mSampleValues[kSplineTableSize]; + static const float kSampleStepSize; +}; + +V_END_NAMESPACE + +#endif // VINTERPOLATOR_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vline.h b/TMessagesProj/jni/rlottie/src/vector/vline.h new file mode 100755 index 000000000..f466e3177 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vline.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VLINE_H +#define VLINE_H + +#include "vglobal.h" +#include "vpoint.h" + +V_BEGIN_NAMESPACE + +class VLine { +public: + VLine() = default; + VLine(float x1, float y1, float x2, float y2) + : mX1(x1), mY1(y1), mX2(x2), mY2(y2) + { + } + VLine(const VPointF &p1, const VPointF &p2) + : mX1(p1.x()), mY1(p1.y()), mX2(p2.x()), mY2(p2.y()) + { + } + float length() const { return length(mX1, mY1, mX2, mY2);} + void splitAtLength(float length, VLine &left, VLine &right) const; + VPointF p1() const { return {mX1, mY1}; } + VPointF p2() const { return {mX2, mY2}; } + float angle() const; + static float length(float x1, float y1, float x2, float y2); + +private: + float mX1{0}; + float mY1{0}; + float mX2{0}; + float mY2{0}; +}; + +inline float VLine::angle() const +{ + const float dx = mX2 - mX1; + const float dy = mY2 - mY1; + + const float theta = std::atan2(dy, dx) * 180.0 / M_PI; + return theta; +} + +// approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm. +// With alpha = 1, beta = 3/8, giving results with the largest error less +// than 7% compared to the exact value. +inline float VLine::length(float x1, float y1, float x2, float y2) +{ + float x = x2 - x1; + float y = y2 - y1; + + x = x < 0 ? -x : x; + y = y < 0 ? -y : y; + + return (x > y ? x + 0.375 * y : y + 0.375 * x); +} + +inline void VLine::splitAtLength(float lengthAt, VLine &left, VLine &right) const +{ + float len = length(); + float dx = ((mX2 - mX1) / len) * lengthAt; + float dy = ((mY2 - mY1) / len) * lengthAt; + + left.mX1 = mX1; + left.mY1 = mY1; + left.mX2 = left.mX1 + dx; + left.mY2 = left.mY1 + dy; + + right.mX1 = left.mX2; + right.mY1 = left.mY2; + right.mX2 = mX2; + right.mY2 = mY2; +} + +#endif //VLINE_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vmatrix.cpp b/TMessagesProj/jni/rlottie/src/vector/vmatrix.cpp new file mode 100755 index 000000000..3ca1404f9 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vmatrix.cpp @@ -0,0 +1,732 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "vmatrix.h" +#include +#include +#include +#include + +V_BEGIN_NAMESPACE + +/* m11 m21 mtx + * m12 m22 mty + * m13 m23 m33 + */ + +inline float VMatrix::determinant() const +{ + return m11 * (m33 * m22 - mty * m23) - m21 * (m33 * m12 - mty * m13) + + mtx * (m23 * m12 - m22 * m13); +} + +bool VMatrix::isAffine() const +{ + return type() < MatrixType::Project; +} + +bool VMatrix::isIdentity() const +{ + return type() == MatrixType::None; +} + +bool VMatrix::isInvertible() const +{ + return !vIsZero(determinant()); +} + +bool VMatrix::isScaling() const +{ + return type() >= MatrixType::Scale; +} +bool VMatrix::isRotating() const +{ + return type() >= MatrixType::Rotate; +} + +bool VMatrix::isTranslating() const +{ + return type() >= MatrixType::Translate; +} + +VMatrix &VMatrix::operator*=(float num) +{ + if (num == 1.) return *this; + + m11 *= num; + m12 *= num; + m13 *= num; + m21 *= num; + m22 *= num; + m23 *= num; + mtx *= num; + mty *= num; + m33 *= num; + if (dirty < MatrixType::Scale) dirty = MatrixType::Scale; + + return *this; +} + +VMatrix &VMatrix::operator/=(float div) +{ + if (div == 0) return *this; + + div = 1 / div; + return operator*=(div); +} + +VMatrix::MatrixType VMatrix::type() const +{ + if (dirty == MatrixType::None || dirty < mType) return mType; + + switch (dirty) { + case MatrixType::Project: + if (!vIsZero(m13) || !vIsZero(m23) || !vIsZero(m33 - 1)) { + mType = MatrixType::Project; + break; + } + VECTOR_FALLTHROUGH + case MatrixType::Shear: + case MatrixType::Rotate: + if (!vIsZero(m12) || !vIsZero(m21)) { + const float dot = m11 * m12 + m21 * m22; + if (vIsZero(dot)) + mType = MatrixType::Rotate; + else + mType = MatrixType::Shear; + break; + } + VECTOR_FALLTHROUGH + case MatrixType::Scale: + if (!vIsZero(m11 - 1) || !vIsZero(m22 - 1)) { + mType = MatrixType::Scale; + break; + } + VECTOR_FALLTHROUGH + case MatrixType::Translate: + if (!vIsZero(mtx) || !vIsZero(mty)) { + mType = MatrixType::Translate; + break; + } + VECTOR_FALLTHROUGH + case MatrixType::None: + mType = MatrixType::None; + break; + } + + dirty = MatrixType::None; + return mType; +} + +VMatrix &VMatrix::translate(float dx, float dy) +{ + if (dx == 0 && dy == 0) return *this; + + switch (type()) { + case MatrixType::None: + mtx = dx; + mty = dy; + break; + case MatrixType::Translate: + mtx += dx; + mty += dy; + break; + case MatrixType::Scale: + mtx += dx * m11; + mty += dy * m22; + break; + case MatrixType::Project: + m33 += dx * m13 + dy * m23; + VECTOR_FALLTHROUGH + case MatrixType::Shear: + case MatrixType::Rotate: + mtx += dx * m11 + dy * m21; + mty += dy * m22 + dx * m12; + break; + } + if (dirty < MatrixType::Translate) dirty = MatrixType::Translate; + return *this; +} + +VMatrix &VMatrix::scale(float sx, float sy) +{ + if (sx == 1 && sy == 1) return *this; + + switch (type()) { + case MatrixType::None: + case MatrixType::Translate: + m11 = sx; + m22 = sy; + break; + case MatrixType::Project: + m13 *= sx; + m23 *= sy; + VECTOR_FALLTHROUGH + case MatrixType::Rotate: + case MatrixType::Shear: + m12 *= sx; + m21 *= sy; + VECTOR_FALLTHROUGH + case MatrixType::Scale: + m11 *= sx; + m22 *= sy; + break; + } + if (dirty < MatrixType::Scale) dirty = MatrixType::Scale; + return *this; +} + +VMatrix &VMatrix::shear(float sh, float sv) +{ + if (sh == 0 && sv == 0) return *this; + + switch (type()) { + case MatrixType::None: + case MatrixType::Translate: + m12 = sv; + m21 = sh; + break; + case MatrixType::Scale: + m12 = sv * m22; + m21 = sh * m11; + break; + case MatrixType::Project: { + float tm13 = sv * m23; + float tm23 = sh * m13; + m13 += tm13; + m23 += tm23; + VECTOR_FALLTHROUGH + } + case MatrixType::Rotate: + case MatrixType::Shear: { + float tm11 = sv * m21; + float tm22 = sh * m12; + float tm12 = sv * m22; + float tm21 = sh * m11; + m11 += tm11; + m12 += tm12; + m21 += tm21; + m22 += tm22; + break; + } + } + if (dirty < MatrixType::Shear) dirty = MatrixType::Shear; + return *this; +} + +static const float deg2rad = float(0.017453292519943295769); // pi/180 +static const float inv_dist_to_plane = 1. / 1024.; + +VMatrix &VMatrix::rotate(float a, Axis axis) +{ + if (a == 0) return *this; + + float sina = 0; + float cosa = 0; + if (a == 90. || a == -270.) + sina = 1.; + else if (a == 270. || a == -90.) + sina = -1.; + else if (a == 180.) + cosa = -1.; + else { + float b = deg2rad * a; // convert to radians + sina = std::sin(b); // fast and convenient + cosa = std::cos(b); + } + + if (axis == Axis::Z) { + switch (type()) { + case MatrixType::None: + case MatrixType::Translate: + m11 = cosa; + m12 = sina; + m21 = -sina; + m22 = cosa; + break; + case MatrixType::Scale: { + float tm11 = cosa * m11; + float tm12 = sina * m22; + float tm21 = -sina * m11; + float tm22 = cosa * m22; + m11 = tm11; + m12 = tm12; + m21 = tm21; + m22 = tm22; + break; + } + case MatrixType::Project: { + float tm13 = cosa * m13 + sina * m23; + float tm23 = -sina * m13 + cosa * m23; + m13 = tm13; + m23 = tm23; + VECTOR_FALLTHROUGH + } + case MatrixType::Rotate: + case MatrixType::Shear: { + float tm11 = cosa * m11 + sina * m21; + float tm12 = cosa * m12 + sina * m22; + float tm21 = -sina * m11 + cosa * m21; + float tm22 = -sina * m12 + cosa * m22; + m11 = tm11; + m12 = tm12; + m21 = tm21; + m22 = tm22; + break; + } + } + if (dirty < MatrixType::Rotate) dirty = MatrixType::Rotate; + } else { + VMatrix result; + if (axis == Axis::Y) { + result.m11 = cosa; + result.m13 = -sina * inv_dist_to_plane; + } else { + result.m22 = cosa; + result.m23 = -sina * inv_dist_to_plane; + } + result.mType = MatrixType::Project; + *this = result * *this; + } + + return *this; +} + +VMatrix VMatrix::operator*(const VMatrix &m) const +{ + const MatrixType otherType = m.type(); + if (otherType == MatrixType::None) return *this; + + const MatrixType thisType = type(); + if (thisType == MatrixType::None) return m; + + VMatrix t; + MatrixType type = vMax(thisType, otherType); + switch (type) { + case MatrixType::None: + break; + case MatrixType::Translate: + t.mtx = mtx + m.mtx; + t.mty += mty + m.mty; + break; + case MatrixType::Scale: { + float m11v = m11 * m.m11; + float m22v = m22 * m.m22; + + float m31v = mtx * m.m11 + m.mtx; + float m32v = mty * m.m22 + m.mty; + + t.m11 = m11v; + t.m22 = m22v; + t.mtx = m31v; + t.mty = m32v; + break; + } + case MatrixType::Rotate: + case MatrixType::Shear: { + float m11v = m11 * m.m11 + m12 * m.m21; + float m12v = m11 * m.m12 + m12 * m.m22; + + float m21v = m21 * m.m11 + m22 * m.m21; + float m22v = m21 * m.m12 + m22 * m.m22; + + float m31v = mtx * m.m11 + mty * m.m21 + m.mtx; + float m32v = mtx * m.m12 + mty * m.m22 + m.mty; + + t.m11 = m11v; + t.m12 = m12v; + t.m21 = m21v; + t.m22 = m22v; + t.mtx = m31v; + t.mty = m32v; + break; + } + case MatrixType::Project: { + float m11v = m11 * m.m11 + m12 * m.m21 + m13 * m.mtx; + float m12v = m11 * m.m12 + m12 * m.m22 + m13 * m.mty; + float m13v = m11 * m.m13 + m12 * m.m23 + m13 * m.m33; + + float m21v = m21 * m.m11 + m22 * m.m21 + m23 * m.mtx; + float m22v = m21 * m.m12 + m22 * m.m22 + m23 * m.mty; + float m23v = m21 * m.m13 + m22 * m.m23 + m23 * m.m33; + + float m31v = mtx * m.m11 + mty * m.m21 + m33 * m.mtx; + float m32v = mtx * m.m12 + mty * m.m22 + m33 * m.mty; + float m33v = mtx * m.m13 + mty * m.m23 + m33 * m.m33; + + t.m11 = m11v; + t.m12 = m12v; + t.m13 = m13v; + t.m21 = m21v; + t.m22 = m22v; + t.m23 = m23v; + t.mtx = m31v; + t.mty = m32v; + t.m33 = m33v; + } + } + + t.dirty = type; + t.mType = type; + + return t; +} + +VMatrix &VMatrix::operator*=(const VMatrix &o) +{ + const MatrixType otherType = o.type(); + if (otherType == MatrixType::None) return *this; + + const MatrixType thisType = type(); + if (thisType == MatrixType::None) return operator=(o); + + MatrixType t = vMax(thisType, otherType); + switch (t) { + case MatrixType::None: + break; + case MatrixType::Translate: + mtx += o.mtx; + mty += o.mty; + break; + case MatrixType::Scale: { + float m11v = m11 * o.m11; + float m22v = m22 * o.m22; + + float m31v = mtx * o.m11 + o.mtx; + float m32v = mty * o.m22 + o.mty; + + m11 = m11v; + m22 = m22v; + mtx = m31v; + mty = m32v; + break; + } + case MatrixType::Rotate: + case MatrixType::Shear: { + float m11v = m11 * o.m11 + m12 * o.m21; + float m12v = m11 * o.m12 + m12 * o.m22; + + float m21v = m21 * o.m11 + m22 * o.m21; + float m22v = m21 * o.m12 + m22 * o.m22; + + float m31v = mtx * o.m11 + mty * o.m21 + o.mtx; + float m32v = mtx * o.m12 + mty * o.m22 + o.mty; + + m11 = m11v; + m12 = m12v; + m21 = m21v; + m22 = m22v; + mtx = m31v; + mty = m32v; + break; + } + case MatrixType::Project: { + float m11v = m11 * o.m11 + m12 * o.m21 + m13 * o.mtx; + float m12v = m11 * o.m12 + m12 * o.m22 + m13 * o.mty; + float m13v = m11 * o.m13 + m12 * o.m23 + m13 * o.m33; + + float m21v = m21 * o.m11 + m22 * o.m21 + m23 * o.mtx; + float m22v = m21 * o.m12 + m22 * o.m22 + m23 * o.mty; + float m23v = m21 * o.m13 + m22 * o.m23 + m23 * o.m33; + + float m31v = mtx * o.m11 + mty * o.m21 + m33 * o.mtx; + float m32v = mtx * o.m12 + mty * o.m22 + m33 * o.mty; + float m33v = mtx * o.m13 + mty * o.m23 + m33 * o.m33; + + m11 = m11v; + m12 = m12v; + m13 = m13v; + m21 = m21v; + m22 = m22v; + m23 = m23v; + mtx = m31v; + mty = m32v; + m33 = m33v; + } + } + + dirty = t; + mType = t; + + return *this; +} + +VMatrix VMatrix::adjoint() const +{ + float h11, h12, h13, h21, h22, h23, h31, h32, h33; + h11 = m22 * m33 - m23 * mty; + h21 = m23 * mtx - m21 * m33; + h31 = m21 * mty - m22 * mtx; + h12 = m13 * mty - m12 * m33; + h22 = m11 * m33 - m13 * mtx; + h32 = m12 * mtx - m11 * mty; + h13 = m12 * m23 - m13 * m22; + h23 = m13 * m21 - m11 * m23; + h33 = m11 * m22 - m12 * m21; + + VMatrix res; + res.m11 = h11; + res.m12 = h12; + res.m13 = h13; + res.m21 = h21; + res.m22 = h22; + res.m23 = h23; + res.mtx = h31; + res.mty = h32; + res.m33 = h33; + res.mType = MatrixType::None; + res.dirty = MatrixType::Project; + + return res; +} + +VMatrix VMatrix::inverted(bool *invertible) const +{ + VMatrix invert; + bool inv = true; + + switch (type()) { + case MatrixType::None: + break; + case MatrixType::Translate: + invert.mtx = -mtx; + invert.mty = -mty; + break; + case MatrixType::Scale: + inv = !vIsZero(m11); + inv &= !vIsZero(m22); + if (inv) { + invert.m11 = 1. / m11; + invert.m22 = 1. / m22; + invert.mtx = -mtx * invert.m11; + invert.mty = -mty * invert.m22; + } + break; + default: + // general case + float det = determinant(); + inv = !vIsZero(det); + if (inv) invert = (adjoint() /= det); + // TODO Test above line + break; + } + + if (invertible) *invertible = inv; + + if (inv) { + // inverting doesn't change the type + invert.mType = mType; + invert.dirty = dirty; + } + + return invert; +} + +bool VMatrix::operator==(const VMatrix &o) const +{ + return fuzzyCompare(o); +} + +bool VMatrix::operator!=(const VMatrix &o) const +{ + return !operator==(o); +} + +bool VMatrix::fuzzyCompare(const VMatrix &o) const +{ + return vCompare(m11, o.m11) && vCompare(m12, o.m12) && + vCompare(m21, o.m21) && vCompare(m22, o.m22) && + vCompare(mtx, o.mtx) && vCompare(mty, o.mty); +} + +#define V_NEAR_CLIP 0.000001 +#ifdef MAP +#undef MAP +#endif +#define MAP(x, y, nx, ny) \ + do { \ + float FX_ = x; \ + float FY_ = y; \ + switch (t) { \ + case MatrixType::None: \ + nx = FX_; \ + ny = FY_; \ + break; \ + case MatrixType::Translate: \ + nx = FX_ + mtx; \ + ny = FY_ + mty; \ + break; \ + case MatrixType::Scale: \ + nx = m11 * FX_ + mtx; \ + ny = m22 * FY_ + mty; \ + break; \ + case MatrixType::Rotate: \ + case MatrixType::Shear: \ + case MatrixType::Project: \ + nx = m11 * FX_ + m21 * FY_ + mtx; \ + ny = m12 * FX_ + m22 * FY_ + mty; \ + if (t == MatrixType::Project) { \ + float w = (m13 * FX_ + m23 * FY_ + m33); \ + if (w < V_NEAR_CLIP) w = V_NEAR_CLIP; \ + w = 1. / w; \ + nx *= w; \ + ny *= w; \ + } \ + } \ + } while (0) + +VRect VMatrix::map(const VRect &rect) const +{ + VMatrix::MatrixType t = type(); + if (t <= MatrixType::Translate) + return rect.translated(round(mtx), round(mty)); + + if (t <= MatrixType::Scale) { + int x = round(m11 * rect.x() + mtx); + int y = round(m22 * rect.y() + mty); + int w = round(m11 * rect.width()); + int h = round(m22 * rect.height()); + if (w < 0) { + w = -w; + x -= w; + } + if (h < 0) { + h = -h; + y -= h; + } + return {x, y, w, h}; + } else if (t < MatrixType::Project) { + // see mapToPolygon for explanations of the algorithm. + float x = 0, y = 0; + MAP(rect.left(), rect.top(), x, y); + float xmin = x; + float ymin = y; + float xmax = x; + float ymax = y; + MAP(rect.right() + 1, rect.top(), x, y); + xmin = vMin(xmin, x); + ymin = vMin(ymin, y); + xmax = vMax(xmax, x); + ymax = vMax(ymax, y); + MAP(rect.right() + 1, rect.bottom() + 1, x, y); + xmin = vMin(xmin, x); + ymin = vMin(ymin, y); + xmax = vMax(xmax, x); + ymax = vMax(ymax, y); + MAP(rect.left(), rect.bottom() + 1, x, y); + xmin = vMin(xmin, x); + ymin = vMin(ymin, y); + xmax = vMax(xmax, x); + ymax = vMax(ymax, y); + return VRect(round(xmin), round(ymin), + round(xmax) - round(xmin), + round(ymax) - round(ymin)); + } else { + // Not supported + assert(0); + } + return {0, 0, 0, 0}; +} + +VRegion VMatrix::map(const VRegion &r) const +{ + VMatrix::MatrixType t = type(); + if (t == MatrixType::None) return r; + + if (t == MatrixType::Translate) { + VRegion copy(r); + copy.translate(round(mtx), round(mty)); + return copy; + } + + if (t == MatrixType::Scale && r.rectCount() == 1) + return VRegion(map(r.boundingRect())); + // handle mapping of region properly + assert(0); + return r; +} + +VPointF VMatrix::map(const VPointF &p) const +{ + float fx = p.x(); + float fy = p.y(); + + float x = 0, y = 0; + + VMatrix::MatrixType t = type(); + switch (t) { + case MatrixType::None: + x = fx; + y = fy; + break; + case MatrixType::Translate: + x = fx + mtx; + y = fy + mty; + break; + case MatrixType::Scale: + x = m11 * fx + mtx; + y = m22 * fy + mty; + break; + case MatrixType::Rotate: + case MatrixType::Shear: + case MatrixType::Project: + x = m11 * fx + m21 * fy + mtx; + y = m12 * fx + m22 * fy + mty; + if (t == MatrixType::Project) { + float w = 1. / (m13 * fx + m23 * fy + m33); + x *= w; + y *= w; + } + } + return {x, y}; +} +static std::string type_helper(VMatrix::MatrixType t) +{ + switch (t) { + case VMatrix::MatrixType::None: + return "MatrixType::None"; + break; + case VMatrix::MatrixType::Translate: + return "MatrixType::Translate"; + break; + case VMatrix::MatrixType::Scale: + return "MatrixType::Scale"; + break; + case VMatrix::MatrixType::Rotate: + return "MatrixType::Rotate"; + break; + case VMatrix::MatrixType::Shear: + return "MatrixType::Shear"; + break; + case VMatrix::MatrixType::Project: + return "MatrixType::Project"; + break; + } + return ""; +} +std::ostream &operator<<(std::ostream &os, const VMatrix &o) +{ + os << "[Matrix: " + << "type =" << type_helper(o.type()) << ", Data : " << o.m11 << " " + << o.m12 << " " << o.m13 << " " << o.m21 << " " << o.m22 << " " << o.m23 + << " " << o.mtx << " " << o.mty << " " << o.m33 << " " + << "]" << std::endl; + return os; +} + +V_END_NAMESPACE diff --git a/TMessagesProj/jni/rlottie/src/vector/vmatrix.h b/TMessagesProj/jni/rlottie/src/vector/vmatrix.h new file mode 100755 index 000000000..f76234330 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vmatrix.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VMATRIX_H +#define VMATRIX_H +#include "vglobal.h" +#include "vpoint.h" +#include "vregion.h" + +V_BEGIN_NAMESPACE + +struct VMatrixData; +class VMatrix { +public: + enum class Axis { X, Y, Z }; + enum class MatrixType: unsigned char { + None = 0x00, + Translate = 0x01, + Scale = 0x02, + Rotate = 0x04, + Shear = 0x08, + Project = 0x10 + }; + VMatrix() = default; + bool isAffine() const; + bool isIdentity() const; + bool isInvertible() const; + bool isScaling() const; + bool isRotating() const; + bool isTranslating() const; + MatrixType type() const; + inline float determinant() const; + + float m_11() const { return m11;} + float m_12() const { return m12;} + float m_13() const { return m13;} + + float m_21() const { return m21;} + float m_22() const { return m22;} + float m_23() const { return m23;} + + float m_tx() const { return mtx;} + float m_ty() const { return mty;} + float m_33() const { return m33;} + + VMatrix &translate(VPointF pos) { return translate(pos.x(), pos.y()); }; + VMatrix &translate(float dx, float dy); + VMatrix &scale(VPointF s) { return scale(s.x(), s.y()); }; + VMatrix &scale(float sx, float sy); + VMatrix &shear(float sh, float sv); + VMatrix &rotate(float a, Axis axis = VMatrix::Axis::Z); + VMatrix &rotateRadians(float a, Axis axis = VMatrix::Axis::Z); + + VPointF map(const VPointF &p) const; + inline VPointF map(float x, float y) const; + VRect map(const VRect &r) const; + VRegion map(const VRegion &r) const; + + V_REQUIRED_RESULT VMatrix inverted(bool *invertible = nullptr) const; + V_REQUIRED_RESULT VMatrix adjoint() const; + + VMatrix operator*(const VMatrix &o) const; + VMatrix & operator*=(const VMatrix &); + VMatrix & operator*=(float mul); + VMatrix & operator/=(float div); + bool operator==(const VMatrix &) const; + bool operator!=(const VMatrix &) const; + bool fuzzyCompare(const VMatrix &) const; + friend std::ostream &operator<<(std::ostream &os, const VMatrix &o); + +private: + friend struct VSpanData; + float m11{1}, m12{0}, m13{0}; + float m21{0}, m22{1}, m23{0}; + float mtx{0}, mty{0}, m33{1}; + mutable MatrixType mType{MatrixType::None}; + mutable MatrixType dirty{MatrixType::None}; +}; + +inline VPointF VMatrix::map(float x, float y) const +{ + return map(VPointF(x, y)); +} + +V_END_NAMESPACE + +#endif // VMATRIX_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vpainter.cpp b/TMessagesProj/jni/rlottie/src/vector/vpainter.cpp new file mode 100755 index 000000000..cdcaa11f4 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vpainter.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "vpainter.h" +#include +#include "vdrawhelper.h" + +V_BEGIN_NAMESPACE + +class VPainterImpl { +public: + void drawRle(const VPoint &pos, const VRle &rle); + void drawRle(const VRle &rle, const VRle &clip); + void setCompositionMode(VPainter::CompositionMode mode) + { + mSpanData.mCompositionMode = mode; + } + void drawBitmapUntransform(const VRect &target, const VBitmap &bitmap, + const VRect &source, uint8_t const_alpha); + +public: + VRasterBuffer mBuffer; + VSpanData mSpanData; +}; + +void VPainterImpl::drawRle(const VPoint &, const VRle &rle) +{ + if (rle.empty()) return; + // mSpanData.updateSpanFunc(); + + if (!mSpanData.mUnclippedBlendFunc) return; + + // do draw after applying clip. + rle.intersect(mSpanData.clipRect(), mSpanData.mUnclippedBlendFunc, + &mSpanData); +} + +void VPainterImpl::drawRle(const VRle &rle, const VRle &clip) +{ + if (rle.empty() || clip.empty()) return; + + if (!mSpanData.mUnclippedBlendFunc) return; + + rle.intersect(clip, mSpanData.mUnclippedBlendFunc, &mSpanData); +} + +static void fillRect(const VRect &r, VSpanData *data) +{ + int x1, x2, y1, y2; + + x1 = std::max(r.x(), 0); + x2 = std::min(r.x() + r.width(), data->mDrawableSize.width()); + y1 = std::max(r.y(), 0); + y2 = std::min(r.y() + r.height(), data->mDrawableSize.height()); + + if (x2 <= x1 || y2 <= y1) return; + + const int nspans = 256; + VRle::Span spans[nspans]; + + int y = y1; + while (y < y2) { + int n = std::min(nspans, y2 - y); + int i = 0; + while (i < n) { + spans[i].x = x1; + spans[i].len = x2 - x1; + spans[i].y = y + i; + spans[i].coverage = 255; + ++i; + } + + data->mUnclippedBlendFunc(n, spans, data); + y += n; + } +} + +void VPainterImpl::drawBitmapUntransform(const VRect & target, + const VBitmap &bitmap, + const VRect & source, + uint8_t const_alpha) +{ + mSpanData.initTexture(&bitmap, const_alpha, VBitmapData::Plain, source); + if (!mSpanData.mUnclippedBlendFunc) return; + mSpanData.dx = -target.x(); + mSpanData.dy = -target.y(); + + VRect rr = source.translated(target.x(), target.y()); + + fillRect(rr, &mSpanData); +} + +VPainter::~VPainter() +{ + delete mImpl; +} + +VPainter::VPainter() +{ + mImpl = new VPainterImpl; +} + +VPainter::VPainter(VBitmap *buffer) +{ + mImpl = new VPainterImpl; + begin(buffer); +} +bool VPainter::begin(VBitmap *buffer) +{ + mImpl->mBuffer.prepare(buffer); + mImpl->mSpanData.init(&mImpl->mBuffer); + // TODO find a better api to clear the surface + mImpl->mBuffer.clear(); + return true; +} +void VPainter::end() {} + +void VPainter::setDrawRegion(const VRect ®ion) +{ + mImpl->mSpanData.setDrawRegion(region); +} + +void VPainter::setBrush(const VBrush &brush) +{ + mImpl->mSpanData.setup(brush); +} + +void VPainter::setCompositionMode(CompositionMode mode) +{ + mImpl->setCompositionMode(mode); +} + +void VPainter::drawRle(const VPoint &pos, const VRle &rle) +{ + mImpl->drawRle(pos, rle); +} + +void VPainter::drawRle(const VRle &rle, const VRle &clip) +{ + mImpl->drawRle(rle, clip); +} + +VRect VPainter::clipBoundingRect() const +{ + return mImpl->mSpanData.clipRect(); +} + +void VPainter::drawBitmap(const VPoint &point, const VBitmap &bitmap, + const VRect &source, uint8_t const_alpha) +{ + if (!bitmap.valid()) return; + + drawBitmap(VRect(point.x(), point.y(), bitmap.width(), bitmap.height()), + bitmap, source, const_alpha); +} + +void VPainter::drawBitmap(const VRect &target, const VBitmap &bitmap, + const VRect &source, uint8_t const_alpha) +{ + if (!bitmap.valid()) return; + + // clear any existing brush data. + setBrush(VBrush()); + + if (target.size() == source.size()) { + mImpl->drawBitmapUntransform(target, bitmap, source, const_alpha); + } else { + // @TODO scaling + } +} + +void VPainter::drawBitmap(const VPoint &point, const VBitmap &bitmap, + uint8_t const_alpha) +{ + if (!bitmap.valid()) return; + + drawBitmap(VRect(point.x(), point.y(), bitmap.width(), bitmap.height()), + bitmap, VRect(0, 0, bitmap.width(), bitmap.height()), + const_alpha); +} + +void VPainter::drawBitmap(const VRect &rect, const VBitmap &bitmap, + uint8_t const_alpha) +{ + if (!bitmap.valid()) return; + + drawBitmap(rect, bitmap, VRect(0, 0, bitmap.width(), bitmap.height()), + const_alpha); +} + +V_END_NAMESPACE diff --git a/TMessagesProj/jni/rlottie/src/vector/vpainter.h b/TMessagesProj/jni/rlottie/src/vector/vpainter.h new file mode 100755 index 000000000..2d523370d --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vpainter.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VPAINTER_H +#define VPAINTER_H + +#include "vbrush.h" +#include "vpoint.h" +#include "vrle.h" + +V_BEGIN_NAMESPACE + +class VBitmap; +class VPainterImpl; +class VPainter { +public: + enum CompositionMode { + CompModeSrc, + CompModeSrcOver, + CompModeDestIn, + CompModeDestOut + }; + ~VPainter(); + VPainter(); + VPainter(VBitmap *buffer); + bool begin(VBitmap *buffer); + void end(); + void setDrawRegion(const VRect ®ion); // sub surface rendering area. + void setBrush(const VBrush &brush); + void setCompositionMode(CompositionMode mode); + void drawRle(const VPoint &pos, const VRle &rle); + void drawRle(const VRle &rle, const VRle &clip); + VRect clipBoundingRect() const; + + void drawBitmap(const VPoint &point, const VBitmap &bitmap, const VRect &source, uint8_t const_alpha = 255); + void drawBitmap(const VRect &target, const VBitmap &bitmap, const VRect &source, uint8_t const_alpha = 255); + void drawBitmap(const VPoint &point, const VBitmap &bitmap, uint8_t const_alpha = 255); + void drawBitmap(const VRect &rect, const VBitmap &bitmap, uint8_t const_alpha = 255); +private: + VPainterImpl *mImpl; +}; + +V_END_NAMESPACE + +#endif // VPAINTER_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vpath.cpp b/TMessagesProj/jni/rlottie/src/vector/vpath.cpp new file mode 100755 index 000000000..a82cbdf2c --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vpath.cpp @@ -0,0 +1,699 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "vpath.h" +#include +#include +#include +#include "vbezier.h" +#include "vdebug.h" +#include "vline.h" +#include "vrect.h" + +V_BEGIN_NAMESPACE + +void VPath::VPathData::transform(const VMatrix &m) +{ + for (auto &i : m_points) { + i = m.map(i); + } + mLengthDirty = true; +} + +float VPath::VPathData::length() const +{ + if (!mLengthDirty) return mLength; + + mLengthDirty = false; + mLength = 0.0; + + size_t i = 0; + for (auto e : m_elements) { + switch (e) { + case VPath::Element::MoveTo: + i++; + break; + case VPath::Element::LineTo: { + mLength += VLine(m_points[i - 1], m_points[i]).length(); + i++; + break; + } + case VPath::Element::CubicTo: { + mLength += VBezier::fromPoints(m_points[i - 1], m_points[i], + m_points[i + 1], m_points[i + 2]) + .length(); + i += 3; + break; + } + case VPath::Element::Close: + break; + } + } + + return mLength; +} + +void VPath::VPathData::checkNewSegment() +{ + if (mNewSegment) { + moveTo(0, 0); + mNewSegment = false; + } +} + +void VPath::VPathData::moveTo(float x, float y) +{ + mStartPoint = {x, y}; + mNewSegment = false; + m_elements.emplace_back(VPath::Element::MoveTo); + m_points.emplace_back(x, y); + m_segments++; + mLengthDirty = true; +} + +void VPath::VPathData::lineTo(float x, float y) +{ + checkNewSegment(); + m_elements.emplace_back(VPath::Element::LineTo); + m_points.emplace_back(x, y); + mLengthDirty = true; +} + +void VPath::VPathData::cubicTo(float cx1, float cy1, float cx2, float cy2, + float ex, float ey) +{ + checkNewSegment(); + m_elements.emplace_back(VPath::Element::CubicTo); + m_points.emplace_back(cx1, cy1); + m_points.emplace_back(cx2, cy2); + m_points.emplace_back(ex, ey); + mLengthDirty = true; +} + +void VPath::VPathData::close() +{ + if (empty()) return; + + const VPointF &lastPt = m_points.back(); + if (!fuzzyCompare(mStartPoint, lastPt)) { + lineTo(mStartPoint.x(), mStartPoint.y()); + } + m_elements.push_back(VPath::Element::Close); + mNewSegment = true; + mLengthDirty = true; +} + +void VPath::VPathData::reset() +{ + if (empty()) return; + + m_elements.clear(); + m_points.clear(); + m_segments = 0; + mLength = 0; + mLengthDirty = false; +} + +size_t VPath::VPathData::segments() const +{ + return m_segments; +} + +void VPath::VPathData::reserve(size_t pts, size_t elms) +{ + if (m_points.capacity() < m_points.size() + pts) + m_points.reserve(m_points.size() + pts); + if (m_elements.capacity() < m_elements.size() + elms) + m_elements.reserve(m_elements.size() + elms); +} + +static VPointF curvesForArc(const VRectF &, float, float, VPointF *, size_t *); +static constexpr float PATH_KAPPA = 0.5522847498f; +static constexpr float K_PI = float(M_PI); + +void VPath::VPathData::arcTo(const VRectF &rect, float startAngle, + float sweepLength, bool forceMoveTo) +{ + size_t point_count = 0; + VPointF pts[15]; + VPointF curve_start = + curvesForArc(rect, startAngle, sweepLength, pts, &point_count); + + reserve(point_count + 1, point_count / 3 + 1); + if (empty() || forceMoveTo) { + moveTo(curve_start.x(), curve_start.y()); + } else { + lineTo(curve_start.x(), curve_start.y()); + } + for (size_t i = 0; i < point_count; i += 3) { + cubicTo(pts[i].x(), pts[i].y(), pts[i + 1].x(), pts[i + 1].y(), + pts[i + 2].x(), pts[i + 2].y()); + } +} + +void VPath::VPathData::addCircle(float cx, float cy, float radius, + VPath::Direction dir) +{ + addOval(VRectF(cx - radius, cy - radius, 2 * radius, 2 * radius), dir); +} + +void VPath::VPathData::addOval(const VRectF &rect, VPath::Direction dir) +{ + if (rect.empty()) return; + + float x = rect.x(); + float y = rect.y(); + + float w = rect.width(); + float w2 = rect.width() / 2; + float w2k = w2 * PATH_KAPPA; + + float h = rect.height(); + float h2 = rect.height() / 2; + float h2k = h2 * PATH_KAPPA; + + reserve(13, 6); // 1Move + 4Cubic + 1Close + if (dir == VPath::Direction::CW) { + // moveto 12 o'clock. + moveTo(x + w2, y); + // 12 -> 3 o'clock + cubicTo(x + w2 + w2k, y, x + w, y + h2 - h2k, x + w, y + h2); + // 3 -> 6 o'clock + cubicTo(x + w, y + h2 + h2k, x + w2 + w2k, y + h, x + w2, y + h); + // 6 -> 9 o'clock + cubicTo(x + w2 - w2k, y + h, x, y + h2 + h2k, x, y + h2); + // 9 -> 12 o'clock + cubicTo(x, y + h2 - h2k, x + w2 - w2k, y, x + w2, y); + } else { + // moveto 12 o'clock. + moveTo(x + w2, y); + // 12 -> 9 o'clock + cubicTo(x + w2 - w2k, y, x, y + h2 - h2k, x, y + h2); + // 9 -> 6 o'clock + cubicTo(x, y + h2 + h2k, x + w2 - w2k, y + h, x + w2, y + h); + // 6 -> 3 o'clock + cubicTo(x + w2 + w2k, y + h, x + w, y + h2 + h2k, x + w, y + h2); + // 3 -> 12 o'clock + cubicTo(x + w, y + h2 - h2k, x + w2 + w2k, y, x + w2, y); + } + close(); +} + +void VPath::VPathData::addRect(const VRectF &rect, VPath::Direction dir) +{ + if (rect.empty()) return; + + float x = rect.x(); + float y = rect.y(); + float w = rect.width(); + float h = rect.height(); + + reserve(5, 6); // 1Move + 4Line + 1Close + if (dir == VPath::Direction::CW) { + moveTo(x + w, y); + lineTo(x + w, y + h); + lineTo(x, y + h); + lineTo(x, y); + close(); + } else { + moveTo(x + w, y); + lineTo(x, y); + lineTo(x, y + h); + lineTo(x + w, y + h); + close(); + } +} + +void VPath::VPathData::addRoundRect(const VRectF &rect, float roundness, + VPath::Direction dir) +{ + if (2 * roundness > rect.width()) roundness = rect.width() / 2.0f; + if (2 * roundness > rect.height()) roundness = rect.height() / 2.0f; + addRoundRect(rect, roundness, roundness, dir); +} + +void VPath::VPathData::addRoundRect(const VRectF &rect, float rx, float ry, + VPath::Direction dir) +{ + if (vCompare(rx, 0.f) || vCompare(ry, 0.f)) { + addRect(rect, dir); + return; + } + + float x = rect.x(); + float y = rect.y(); + float w = rect.width(); + float h = rect.height(); + // clamp the rx and ry radius value. + rx = 2 * rx; + ry = 2 * ry; + if (rx > w) rx = w; + if (ry > h) ry = h; + + reserve(17, 10); // 1Move + 4Cubic + 1Close + if (dir == VPath::Direction::CW) { + moveTo(x + w, y + ry / 2.f); + arcTo(VRectF(x + w - rx, y + h - ry, rx, ry), 0, -90, false); + arcTo(VRectF(x, y + h - ry, rx, ry), -90, -90, false); + arcTo(VRectF(x, y, rx, ry), -180, -90, false); + arcTo(VRectF(x + w - rx, y, rx, ry), -270, -90, false); + close(); + } else { + moveTo(x + w, y + ry / 2.f); + arcTo(VRectF(x + w - rx, y, rx, ry), 0, 90, false); + arcTo(VRectF(x, y, rx, ry), 90, 90, false); + arcTo(VRectF(x, y + h - ry, rx, ry), 180, 90, false); + arcTo(VRectF(x + w - rx, y + h - ry, rx, ry), 270, 90, false); + close(); + } +} + +static float tForArcAngle(float angle); +void findEllipseCoords(const VRectF &r, float angle, float length, + VPointF *startPoint, VPointF *endPoint) +{ + if (r.empty()) { + if (startPoint) *startPoint = VPointF(); + if (endPoint) *endPoint = VPointF(); + return; + } + + float w2 = r.width() / 2; + float h2 = r.height() / 2; + + float angles[2] = {angle, angle + length}; + VPointF *points[2] = {startPoint, endPoint}; + + for (int i = 0; i < 2; ++i) { + if (!points[i]) continue; + + float theta = angles[i] - 360 * floorf(angles[i] / 360); + float t = theta / 90; + // truncate + int quadrant = int(t); + t -= quadrant; + + t = tForArcAngle(90 * t); + + // swap x and y? + if (quadrant & 1) t = 1 - t; + + float a, b, c, d; + VBezier::coefficients(t, a, b, c, d); + VPointF p(a + b + c * PATH_KAPPA, d + c + b * PATH_KAPPA); + + // left quadrants + if (quadrant == 1 || quadrant == 2) p.rx() = -p.x(); + + // top quadrants + if (quadrant == 0 || quadrant == 1) p.ry() = -p.y(); + + *points[i] = r.center() + VPointF(w2 * p.x(), h2 * p.y()); + } +} + +static float tForArcAngle(float angle) +{ + float radians, cos_angle, sin_angle, tc, ts, t; + + if (vCompare(angle, 0.f)) return 0; + if (vCompare(angle, 90.0f)) return 1; + + radians = (angle / 180) * K_PI; + + cos_angle = cosf(radians); + sin_angle = sinf(radians); + + // initial guess + tc = angle / 90; + + // do some iterations of newton's method to approximate cos_angle + // finds the zero of the function b.pointAt(tc).x() - cos_angle + tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 - + cos_angle) // value + / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) * + tc); // derivative + tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 - + cos_angle) // value + / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) * + tc); // derivative + + // initial guess + ts = tc; + // do some iterations of newton's method to approximate sin_angle + // finds the zero of the function b.pointAt(tc).y() - sin_angle + ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts + + 3 * PATH_KAPPA) * + ts - + sin_angle) / + (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts + + 3 * PATH_KAPPA); + ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts + + 3 * PATH_KAPPA) * + ts - + sin_angle) / + (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts + + 3 * PATH_KAPPA); + + // use the average of the t that best approximates cos_angle + // and the t that best approximates sin_angle + t = 0.5f * (tc + ts); + return t; +} + +// The return value is the starting point of the arc +static VPointF curvesForArc(const VRectF &rect, float startAngle, + float sweepLength, VPointF *curves, + size_t *point_count) +{ + if (rect.empty()) { + return {}; + } + + float x = rect.x(); + float y = rect.y(); + + float w = rect.width(); + float w2 = rect.width() / 2; + float w2k = w2 * PATH_KAPPA; + + float h = rect.height(); + float h2 = rect.height() / 2; + float h2k = h2 * PATH_KAPPA; + + VPointF points[16] = { + // start point + VPointF(x + w, y + h2), + + // 0 -> 270 degrees + VPointF(x + w, y + h2 + h2k), VPointF(x + w2 + w2k, y + h), + VPointF(x + w2, y + h), + + // 270 -> 180 degrees + VPointF(x + w2 - w2k, y + h), VPointF(x, y + h2 + h2k), + VPointF(x, y + h2), + + // 180 -> 90 degrees + VPointF(x, y + h2 - h2k), VPointF(x + w2 - w2k, y), VPointF(x + w2, y), + + // 90 -> 0 degrees + VPointF(x + w2 + w2k, y), VPointF(x + w, y + h2 - h2k), + VPointF(x + w, y + h2)}; + + if (sweepLength > 360) + sweepLength = 360; + else if (sweepLength < -360) + sweepLength = -360; + + // Special case fast paths + if (startAngle == 0.0f) { + if (vCompare(sweepLength, 360)) { + for (int i = 11; i >= 0; --i) curves[(*point_count)++] = points[i]; + return points[12]; + } else if (vCompare(sweepLength, -360)) { + for (int i = 1; i <= 12; ++i) curves[(*point_count)++] = points[i]; + return points[0]; + } + } + + int startSegment = int(floorf(startAngle / 90.0f)); + int endSegment = int(floorf((startAngle + sweepLength) / 90.0f)); + + float startT = (startAngle - startSegment * 90) / 90; + float endT = (startAngle + sweepLength - endSegment * 90) / 90; + + int delta = sweepLength > 0 ? 1 : -1; + if (delta < 0) { + startT = 1 - startT; + endT = 1 - endT; + } + + // avoid empty start segment + if (vIsZero(startT - float(1))) { + startT = 0; + startSegment += delta; + } + + // avoid empty end segment + if (vIsZero(endT)) { + endT = 1; + endSegment -= delta; + } + + startT = tForArcAngle(startT * 90); + endT = tForArcAngle(endT * 90); + + const bool splitAtStart = !vIsZero(startT); + const bool splitAtEnd = !vIsZero(endT - float(1)); + + const int end = endSegment + delta; + + // empty arc? + if (startSegment == end) { + const int quadrant = 3 - ((startSegment % 4) + 4) % 4; + const int j = 3 * quadrant; + return delta > 0 ? points[j + 3] : points[j]; + } + + VPointF startPoint, endPoint; + findEllipseCoords(rect, startAngle, sweepLength, &startPoint, &endPoint); + + for (int i = startSegment; i != end; i += delta) { + const int quadrant = 3 - ((i % 4) + 4) % 4; + const int j = 3 * quadrant; + + VBezier b; + if (delta > 0) + b = VBezier::fromPoints(points[j + 3], points[j + 2], points[j + 1], + points[j]); + else + b = VBezier::fromPoints(points[j], points[j + 1], points[j + 2], + points[j + 3]); + + // empty arc? + if (startSegment == endSegment && vCompare(startT, endT)) + return startPoint; + + if (i == startSegment) { + if (i == endSegment && splitAtEnd) + b = b.onInterval(startT, endT); + else if (splitAtStart) + b = b.onInterval(startT, 1); + } else if (i == endSegment && splitAtEnd) { + b = b.onInterval(0, endT); + } + + // push control points + curves[(*point_count)++] = b.pt2(); + curves[(*point_count)++] = b.pt3(); + curves[(*point_count)++] = b.pt4(); + } + + curves[*(point_count)-1] = endPoint; + + return startPoint; +} + +void VPath::VPathData::addPolystar(float points, float innerRadius, + float outerRadius, float innerRoundness, + float outerRoundness, float startAngle, + float cx, float cy, VPath::Direction dir) +{ + const static float POLYSTAR_MAGIC_NUMBER = 0.47829f / 0.28f; + float currentAngle = (startAngle - 90.0f) * K_PI / 180.0f; + float x; + float y; + float partialPointRadius = 0; + float anglePerPoint = (2.0f * K_PI / points); + float halfAnglePerPoint = anglePerPoint / 2.0f; + float partialPointAmount = points - floorf(points); + bool longSegment = false; + size_t numPoints = size_t(ceilf(points) * 2); + float angleDir = ((dir == VPath::Direction::CW) ? 1.0 : -1.0); + bool hasRoundness = false; + + innerRoundness /= 100.0f; + outerRoundness /= 100.0f; + + if (!vCompare(partialPointAmount, 0)) { + currentAngle += + halfAnglePerPoint * (1.0f - partialPointAmount) * angleDir; + } + + if (!vCompare(partialPointAmount, 0)) { + partialPointRadius = + innerRadius + partialPointAmount * (outerRadius - innerRadius); + x = partialPointRadius * cosf(currentAngle); + y = partialPointRadius * sinf(currentAngle); + currentAngle += anglePerPoint * partialPointAmount / 2.0f * angleDir; + } else { + x = outerRadius * cosf(currentAngle); + y = outerRadius * sinf(currentAngle); + currentAngle += halfAnglePerPoint * angleDir; + } + + if (vIsZero(innerRoundness) && vIsZero(outerRoundness)) { + reserve(numPoints + 2, numPoints + 3); + } else { + reserve(numPoints * 3 + 2, numPoints + 3); + hasRoundness = true; + } + + moveTo(x + cx, y + cy); + + for (size_t i = 0; i < numPoints; i++) { + float radius = longSegment ? outerRadius : innerRadius; + float dTheta = halfAnglePerPoint; + if (!vIsZero(partialPointRadius) && i == numPoints - 2) { + dTheta = anglePerPoint * partialPointAmount / 2.0f; + } + if (!vIsZero(partialPointRadius) && i == numPoints - 1) { + radius = partialPointRadius; + } + float previousX = x; + float previousY = y; + x = radius * cosf(currentAngle); + y = radius * sinf(currentAngle); + + if (hasRoundness) { + float cp1Theta = + (atan2f(previousY, previousX) - K_PI / 2.0f * angleDir); + float cp1Dx = cosf(cp1Theta); + float cp1Dy = sinf(cp1Theta); + float cp2Theta = (atan2f(y, x) - K_PI / 2.0f * angleDir); + float cp2Dx = cosf(cp2Theta); + float cp2Dy = sinf(cp2Theta); + + float cp1Roundness = longSegment ? innerRoundness : outerRoundness; + float cp2Roundness = longSegment ? outerRoundness : innerRoundness; + float cp1Radius = longSegment ? innerRadius : outerRadius; + float cp2Radius = longSegment ? outerRadius : innerRadius; + + float cp1x = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER * + cp1Dx / points; + float cp1y = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER * + cp1Dy / points; + float cp2x = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER * + cp2Dx / points; + float cp2y = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER * + cp2Dy / points; + + if (!vIsZero(partialPointAmount) && + ((i == 0) || (i == numPoints - 1))) { + cp1x *= partialPointAmount; + cp1y *= partialPointAmount; + cp2x *= partialPointAmount; + cp2y *= partialPointAmount; + } + + cubicTo(previousX - cp1x + cx, previousY - cp1y + cy, x + cp2x + cx, + y + cp2y + cy, x + cx, y + cy); + } else { + lineTo(x + cx, y + cy); + } + + currentAngle += dTheta * angleDir; + longSegment = !longSegment; + } + + close(); +} + +void VPath::VPathData::addPolygon(float points, float radius, float roundness, + float startAngle, float cx, float cy, + VPath::Direction dir) +{ + // TODO: Need to support floating point number for number of points + const static float POLYGON_MAGIC_NUMBER = 0.25; + float currentAngle = (startAngle - 90.0f) * K_PI / 180.0f; + float x; + float y; + float anglePerPoint = 2.0f * K_PI / floorf(points); + size_t numPoints = size_t(floorf(points)); + float angleDir = ((dir == VPath::Direction::CW) ? 1.0 : -1.0); + bool hasRoundness = false; + + roundness /= 100.0f; + + currentAngle = (currentAngle - 90.0f) * K_PI / 180.0f; + x = radius * cosf(currentAngle); + y = radius * sinf(currentAngle); + currentAngle += anglePerPoint * angleDir; + + if (vIsZero(roundness)) { + reserve(numPoints + 2, numPoints + 3); + } else { + reserve(numPoints * 3 + 2, numPoints + 3); + hasRoundness = true; + } + + moveTo(x + cx, y + cy); + + for (size_t i = 0; i < numPoints; i++) { + float previousX = x; + float previousY = y; + x = (radius * cosf(currentAngle)); + y = (radius * sinf(currentAngle)); + + if (hasRoundness) { + float cp1Theta = + (atan2f(previousY, previousX) - K_PI / 2.0f * angleDir); + float cp1Dx = cosf(cp1Theta); + float cp1Dy = sinf(cp1Theta); + float cp2Theta = atan2f(y, x) - K_PI / 2.0f * angleDir; + float cp2Dx = cosf(cp2Theta); + float cp2Dy = sinf(cp2Theta); + + float cp1x = radius * roundness * POLYGON_MAGIC_NUMBER * cp1Dx; + float cp1y = radius * roundness * POLYGON_MAGIC_NUMBER * cp1Dy; + float cp2x = radius * roundness * POLYGON_MAGIC_NUMBER * cp2Dx; + float cp2y = radius * roundness * POLYGON_MAGIC_NUMBER * cp2Dy; + + cubicTo(previousX - cp1x + cx, previousY - cp1y + cy, x + cp2x + cx, + y + cp2y + cy, x, y); + } else { + lineTo(x + cx, y + cy); + } + + currentAngle += anglePerPoint * angleDir; + } + + close(); +} + +void VPath::VPathData::addPath(const VPathData &path) +{ + size_t segment = path.segments(); + + // make sure enough memory available + if (m_points.capacity() < m_points.size() + path.m_points.size()) + m_points.reserve(m_points.size() + path.m_points.size()); + + if (m_elements.capacity() < m_elements.size() + path.m_elements.size()) + m_elements.reserve(m_elements.size() + path.m_elements.size()); + + std::copy(path.m_points.begin(), path.m_points.end(), + back_inserter(m_points)); + std::copy(path.m_elements.begin(), path.m_elements.end(), + back_inserter(m_elements)); + + m_segments += segment; + mLengthDirty = true; +} + +V_END_NAMESPACE diff --git a/TMessagesProj/jni/rlottie/src/vector/vpath.h b/TMessagesProj/jni/rlottie/src/vector/vpath.h new file mode 100755 index 000000000..1c4bf0b78 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vpath.h @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VPATH_H +#define VPATH_H +#include +#include "vcowptr.h" +#include "vmatrix.h" +#include "vpoint.h" +#include "vrect.h" + +V_BEGIN_NAMESPACE + +struct VPathData; +class VPath { +public: + enum class Direction { CCW, CW }; + + enum class Element : uchar { MoveTo, LineTo, CubicTo, Close }; + bool empty() const; + bool null() const; + void moveTo(const VPointF &p); + void moveTo(float x, float y); + void lineTo(const VPointF &p); + void lineTo(float x, float y); + void cubicTo(const VPointF &c1, const VPointF &c2, const VPointF &e); + void cubicTo(float c1x, float c1y, float c2x, float c2y, float ex, + float ey); + void arcTo(const VRectF &rect, float startAngle, float sweepLength, + bool forceMoveTo); + void close(); + void reset(); + void reserve(size_t pts, size_t elms); + size_t segments() const; + void addCircle(float cx, float cy, float radius, + VPath::Direction dir = Direction::CW); + void addOval(const VRectF &rect, VPath::Direction dir = Direction::CW); + void addRoundRect(const VRectF &rect, float rx, float ry, + VPath::Direction dir = Direction::CW); + void addRoundRect(const VRectF &rect, float roundness, + VPath::Direction dir = Direction::CW); + void addRect(const VRectF &rect, VPath::Direction dir = Direction::CW); + void addPolystar(float points, float innerRadius, float outerRadius, + float innerRoundness, float outerRoundness, + float startAngle, float cx, float cy, + VPath::Direction dir = Direction::CW); + void addPolygon(float points, float radius, float roundness, + float startAngle, float cx, float cy, + VPath::Direction dir = Direction::CW); + void addPath(const VPath &path); + void transform(const VMatrix &m); + float length() const; + const std::vector &elements() const; + const std::vector & points() const; + void clone(const VPath &srcPath); + bool unique() const { return d.unique();} + int refCount() const { return d.refCount();} + +private: + struct VPathData { + bool empty() const { return m_elements.empty(); } + bool null() const { return empty() && !m_elements.capacity();} + void moveTo(float x, float y); + void lineTo(float x, float y); + void cubicTo(float cx1, float cy1, float cx2, float cy2, float ex, float ey); + void close(); + void reset(); + void reserve(size_t, size_t); + void checkNewSegment(); + size_t segments() const; + void transform(const VMatrix &m); + float length() const; + void addRoundRect(const VRectF &, float, float, VPath::Direction); + void addRoundRect(const VRectF &, float, VPath::Direction); + void addRect(const VRectF &, VPath::Direction); + void arcTo(const VRectF &, float, float, bool); + void addCircle(float, float, float, VPath::Direction); + void addOval(const VRectF &, VPath::Direction); + void addPolystar(float points, float innerRadius, float outerRadius, + float innerRoundness, float outerRoundness, + float startAngle, float cx, float cy, + VPath::Direction dir = Direction::CW); + void addPolygon(float points, float radius, float roundness, + float startAngle, float cx, float cy, + VPath::Direction dir = Direction::CW); + void addPath(const VPathData &path); + void clone(const VPath::VPathData &o) { *this = o;} + const std::vector &elements() const + { + return m_elements; + } + const std::vector &points() const { return m_points; } + std::vector m_points; + std::vector m_elements; + unsigned int m_segments; + VPointF mStartPoint; + mutable float mLength{0}; + mutable bool mLengthDirty{true}; + bool mNewSegment; + }; + + vcow_ptr d; +}; + +inline bool VPath::empty() const +{ + return d->empty(); +} + +/* + * path is empty as well as null(no memory for data allocated yet). + */ +inline bool VPath::null() const +{ + return d->null(); +} + +inline void VPath::moveTo(const VPointF &p) +{ + d.write().moveTo(p.x(), p.y()); +} + +inline void VPath::lineTo(const VPointF &p) +{ + d.write().lineTo(p.x(), p.y()); +} + +inline void VPath::close() +{ + d.write().close(); +} + +inline void VPath::reset() +{ + d.write().reset(); +} + +inline void VPath::reserve(size_t pts, size_t elms) +{ + d.write().reserve(pts, elms); +} + +inline size_t VPath::segments() const +{ + return d->segments(); +} + +inline float VPath::length() const +{ + return d->length(); +} + +inline void VPath::cubicTo(const VPointF &c1, const VPointF &c2, + const VPointF &e) +{ + d.write().cubicTo(c1.x(), c1.y(), c2.x(), c2.y(), e.x(), e.y()); +} + +inline void VPath::lineTo(float x, float y) +{ + d.write().lineTo(x, y); +} + +inline void VPath::moveTo(float x, float y) +{ + d.write().moveTo(x, y); +} + +inline void VPath::cubicTo(float c1x, float c1y, float c2x, float c2y, float ex, + float ey) +{ + d.write().cubicTo(c1x, c1y, c2x, c2y, ex, ey); +} + +inline void VPath::transform(const VMatrix &m) +{ + d.write().transform(m); +} + +inline void VPath::arcTo(const VRectF &rect, float startAngle, + float sweepLength, bool forceMoveTo) +{ + d.write().arcTo(rect, startAngle, sweepLength, forceMoveTo); +} + +inline void VPath::addRect(const VRectF &rect, VPath::Direction dir) +{ + d.write().addRect(rect, dir); +} + +inline void VPath::addRoundRect(const VRectF &rect, float rx, float ry, + VPath::Direction dir) +{ + d.write().addRoundRect(rect, rx, ry, dir); +} + +inline void VPath::addRoundRect(const VRectF &rect, float roundness, + VPath::Direction dir) +{ + d.write().addRoundRect(rect, roundness, dir); +} + +inline void VPath::addCircle(float cx, float cy, float radius, + VPath::Direction dir) +{ + d.write().addCircle(cx, cy, radius, dir); +} + +inline void VPath::addOval(const VRectF &rect, VPath::Direction dir) +{ + d.write().addOval(rect, dir); +} + +inline void VPath::addPolystar(float points, float innerRadius, + float outerRadius, float innerRoundness, + float outerRoundness, float startAngle, float cx, + float cy, VPath::Direction dir) +{ + d.write().addPolystar(points, innerRadius, outerRadius, innerRoundness, + outerRoundness, startAngle, cx, cy, dir); +} + +inline void VPath::addPolygon(float points, float radius, float roundness, + float startAngle, float cx, float cy, + VPath::Direction dir) +{ + d.write().addPolygon(points, radius, roundness, startAngle, cx, cy, dir); +} + +inline void VPath::addPath(const VPath &path) +{ + if (path.empty()) return; + + if (null()) { + *this = path; + } else { + d.write().addPath(path.d.read()); + } +} + +inline const std::vector &VPath::elements() const +{ + return d->elements(); +} + +inline const std::vector &VPath::points() const +{ + return d->points(); +} + +inline void VPath::clone(const VPath &o) +{ + d.write().clone(o.d.read()); +} + +V_END_NAMESPACE + +#endif // VPATH_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vpathmesure.cpp b/TMessagesProj/jni/rlottie/src/vector/vpathmesure.cpp new file mode 100755 index 000000000..f0f305b07 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vpathmesure.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "vpathmesure.h" +#include +#include "vbezier.h" +#include "vdasher.h" + +V_BEGIN_NAMESPACE + +/* + * start and end value must be normalized to [0 - 1] + * Path mesure trims the path from [start --> end] + * if start > end it treates as a loop and trims as two segment + * [0-->end] and [start --> 1] + */ +VPath VPathMesure::trim(const VPath &path) +{ + if (vCompare(mStart, mEnd)) return VPath(); + + if ((vCompare(mStart, 0.0f) && (vCompare(mEnd, 1.0f))) || + (vCompare(mStart, 1.0f) && (vCompare(mEnd, 0.0f)))) + return path; + + float length = path.length(); + + if (mStart < mEnd) { + float array[4] = { + 0.0f, length * mStart, // 1st segment + (mEnd - mStart) * length, + std::numeric_limits::max(), // 2nd segment + }; + VDasher dasher(array, 4); + return dasher.dashed(path); + } else { + float array[4] = { + length * mEnd, (mStart - mEnd) * length, // 1st segment + (1 - mStart) * length, + std::numeric_limits::max(), // 2nd segment + }; + VDasher dasher(array, 4); + return dasher.dashed(path); + } +} + +V_END_NAMESPACE diff --git a/TMessagesProj/jni/rlottie/src/vector/vpathmesure.h b/TMessagesProj/jni/rlottie/src/vector/vpathmesure.h new file mode 100755 index 000000000..de3e82405 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vpathmesure.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VPATHMESURE_H +#define VPATHMESURE_H + +#include "vpath.h" + +V_BEGIN_NAMESPACE + +class VPathMesure { +public: + void setStart(float start){mStart = start;} + void setEnd(float end){mEnd = end;} + VPath trim(const VPath &path); +private: + float mStart{0.0f}; + float mEnd{1.0f}; +}; + +V_END_NAMESPACE + +#endif // VPATHMESURE_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vpoint.h b/TMessagesProj/jni/rlottie/src/vector/vpoint.h new file mode 100755 index 000000000..1a84cb17c --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vpoint.h @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VPOINT_H +#define VPOINT_H + +#include "vglobal.h" + +V_BEGIN_NAMESPACE + +class VPointF { +public: + VPointF() = default; + constexpr inline VPointF(float x, float y) noexcept : mx(x), my(y) {} + constexpr inline float x() const noexcept { return mx; } + constexpr inline float y() const noexcept { return my; } + inline float & rx() noexcept { return mx; } + inline float & ry() noexcept { return my; } + inline void setX(float x) { mx = x; } + inline void setY(float y) { my = y; } + inline VPointF operator-() noexcept { return {-mx, -my}; } + inline VPointF & operator+=(const VPointF &p) noexcept; + inline VPointF & operator-=(const VPointF &p) noexcept; + friend const VPointF operator+(const VPointF &p1, const VPointF &p2) + { + return VPointF(p1.mx + p2.mx, p1.my + p2.my); + } + inline friend bool fuzzyCompare(const VPointF &p1, const VPointF &p2); + inline friend VDebug & operator<<(VDebug &os, const VPointF &o); + + friend inline VPointF operator-(const VPointF &p1, const VPointF &p2); + friend inline const VPointF operator*(const VPointF &, float val); + friend inline const VPointF operator*(float val, const VPointF &); + friend inline const VPointF operator/(const VPointF &, float val); + friend inline const VPointF operator/(float val, const VPointF &); + +private: + float mx{0}; + float my{0}; +}; + +inline bool fuzzyCompare(const VPointF &p1, const VPointF &p2) +{ + return (vCompare(p1.mx, p2.mx) && vCompare(p1.my, p2.my)); +} + +inline VPointF operator-(const VPointF &p1, const VPointF &p2) +{ + return {p1.mx - p2.mx, p1.my - p2.my}; +} + +inline const VPointF operator*(const VPointF &p, float c) +{ + return VPointF(p.mx * c, p.my * c); +} + +inline const VPointF operator*(float c, const VPointF &p) +{ + return VPointF(p.mx * c, p.my * c); +} + +inline const VPointF operator/(const VPointF &p, float c) +{ + return VPointF(p.mx / c, p.my / c); +} + +inline const VPointF operator/(float c, const VPointF &p) +{ + return VPointF(p.mx / c, p.my / c); +} + +inline VDebug &operator<<(VDebug &os, const VPointF &o) +{ + os << "{P " << o.x() << "," << o.y() << "}"; + return os; +} + +inline VPointF &VPointF::operator+=(const VPointF &p) noexcept +{ + mx += p.mx; + my += p.my; + return *this; +} + +inline VPointF &VPointF::operator-=(const VPointF &p) noexcept +{ + mx -= p.mx; + my -= p.my; + return *this; +} + +class VPoint { +public: + VPoint() = default; + constexpr inline VPoint(int x, int y) noexcept : mx(x), my(y) {} + constexpr inline int x() const noexcept { return mx; } + constexpr inline int y() const noexcept { return my; } + inline void setX(int x) { mx = x; } + inline void setY(int y) { my = y; } + inline VPoint & operator+=(const VPoint &p) noexcept; + inline VPoint & operator-=(const VPoint &p) noexcept; + constexpr inline bool operator==(const VPoint &o) const; + constexpr inline bool operator!=(const VPoint &o) const + { + return !(operator==(o)); + } + friend inline VPoint operator-(const VPoint &p1, const VPoint &p2); + inline friend VDebug &operator<<(VDebug &os, const VPoint &o); + +private: + int mx{0}; + int my{0}; +}; +inline VDebug &operator<<(VDebug &os, const VPoint &o) +{ + os << "{P " << o.x() << "," << o.y() << "}"; + return os; +} + +inline VPoint operator-(const VPoint &p1, const VPoint &p2) +{ + return {p1.mx - p2.mx, p1.my - p2.my}; +} + +constexpr inline bool VPoint::operator==(const VPoint &o) const +{ + return (mx == o.x() && my == o.y()); +} + +inline VPoint &VPoint::operator+=(const VPoint &p) noexcept +{ + mx += p.mx; + my += p.my; + return *this; +} + +inline VPoint &VPoint::operator-=(const VPoint &p) noexcept +{ + mx -= p.mx; + my -= p.my; + return *this; +} + +class VSize { +public: + VSize() = default; + constexpr inline VSize(int w, int h) noexcept : mw(w), mh(h) {} + bool empty() const {return (mw <= 0 || mh <= 0);} + constexpr inline int width() const noexcept { return mw; } + constexpr inline int height() const noexcept { return mh; } + inline void setWidth(int w) { mw = w; } + inline void setHeight(int h) { mh = h; } + inline VSize & operator+=(const VSize &p) noexcept; + inline VSize & operator-=(const VSize &p) noexcept; + constexpr inline bool operator==(const VSize &o) const; + constexpr inline bool operator!=(const VSize &o) const + { + return !(operator==(o)); + } + inline friend VDebug &operator<<(VDebug &os, const VSize &o); + +private: + int mw{0}; + int mh{0}; +}; +inline VDebug &operator<<(VDebug &os, const VSize &o) +{ + os << "{P " << o.width() << "," << o.height() << "}"; + return os; +} +constexpr inline bool VSize::operator==(const VSize &o) const +{ + return (mw == o.width() && mh == o.height()); +} + +inline VSize &VSize::operator+=(const VSize &p) noexcept +{ + mw += p.mw; + mh += p.mh; + return *this; +} + +inline VSize &VSize::operator-=(const VSize &p) noexcept +{ + mw -= p.mw; + mh -= p.mh; + return *this; +} + +V_END_NAMESPACE + +#endif // VPOINT_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vraster.cpp b/TMessagesProj/jni/rlottie/src/vector/vraster.cpp new file mode 100755 index 000000000..2c9989d07 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vraster.cpp @@ -0,0 +1,444 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "vraster.h" +#include +#include +#include "config.h" +#include "v_ft_raster.h" +#include "v_ft_stroker.h" +#include "vdebug.h" +#include "vmatrix.h" +#include "vpath.h" +#include "vrle.h" + +V_BEGIN_NAMESPACE + +template +class dyn_array { +public: + explicit dyn_array(size_t size) + : mCapacity(size), mData(std::make_unique(mCapacity)) + { + } + void reserve(size_t size) + { + if (mCapacity > size) return; + mCapacity = size; + mData = std::make_unique(mCapacity); + } + T * data() const { return mData.get(); } + dyn_array &operator=(dyn_array &&) noexcept = delete; + +private: + size_t mCapacity{0}; + std::unique_ptr mData{nullptr}; +}; + +struct FTOutline { +public: + void reset(); + void grow(size_t, size_t); + void convert(const VPath &path); + void convert(CapStyle, JoinStyle, float, float); + void moveTo(const VPointF &pt); + void lineTo(const VPointF &pt); + void cubicTo(const VPointF &ctr1, const VPointF &ctr2, const VPointF end); + void close(); + void end(); + void transform(const VMatrix &m); + SW_FT_Pos TO_FT_COORD(float x) + { + return SW_FT_Pos(x * 64); + } // to freetype 26.6 coordinate. + SW_FT_Outline ft; + bool closed{false}; + SW_FT_Stroker_LineCap ftCap; + SW_FT_Stroker_LineJoin ftJoin; + SW_FT_Fixed ftWidth; + SW_FT_Fixed ftMeterLimit; + dyn_array mPointMemory{100}; + dyn_array mTagMemory{100}; + dyn_array mContourMemory{10}; + dyn_array mContourFlagMemory{10}; +}; + +void FTOutline::reset() +{ + ft.n_points = ft.n_contours = 0; + ft.flags = 0x0; +} + +void FTOutline::grow(size_t points, size_t segments) +{ + reset(); + mPointMemory.reserve(points + segments); + mTagMemory.reserve(points + segments); + mContourMemory.reserve(segments); + mContourFlagMemory.reserve(segments); + + ft.points = mPointMemory.data(); + ft.tags = mTagMemory.data(); + ft.contours = mContourMemory.data(); + ft.contours_flag = mContourFlagMemory.data(); +} + +void FTOutline::convert(const VPath &path) +{ + const std::vector &elements = path.elements(); + const std::vector & points = path.points(); + + grow(points.size(), path.segments()); + + size_t index = 0; + for (auto element : elements) { + switch (element) { + case VPath::Element::MoveTo: + moveTo(points[index]); + index++; + break; + case VPath::Element::LineTo: + lineTo(points[index]); + index++; + break; + case VPath::Element::CubicTo: + cubicTo(points[index], points[index + 1], points[index + 2]); + index = index + 3; + break; + case VPath::Element::Close: + close(); + break; + } + } + end(); +} + +void FTOutline::convert(CapStyle cap, JoinStyle join, float width, + float meterLimit) +{ + // map strokeWidth to freetype. It uses as the radius of the pen not the + // diameter + width = width / 2.0f; + // convert to freetype co-ordinate + // IMP: stroker takes radius in 26.6 co-ordinate + ftWidth = SW_FT_Fixed(width * (1 << 6)); + // IMP: stroker takes meterlimit in 16.16 co-ordinate + ftMeterLimit = SW_FT_Fixed(meterLimit * (1 << 16)); + + // map to freetype capstyle + switch (cap) { + case CapStyle::Square: + ftCap = SW_FT_STROKER_LINECAP_SQUARE; + break; + case CapStyle::Round: + ftCap = SW_FT_STROKER_LINECAP_ROUND; + break; + default: + ftCap = SW_FT_STROKER_LINECAP_BUTT; + break; + } + switch (join) { + case JoinStyle::Bevel: + ftJoin = SW_FT_STROKER_LINEJOIN_BEVEL; + break; + case JoinStyle::Round: + ftJoin = SW_FT_STROKER_LINEJOIN_ROUND; + break; + default: + ftJoin = SW_FT_STROKER_LINEJOIN_MITER_FIXED; + break; + } +} + +void FTOutline::moveTo(const VPointF &pt) +{ + ft.points[ft.n_points].x = TO_FT_COORD(pt.x()); + ft.points[ft.n_points].y = TO_FT_COORD(pt.y()); + ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; + if (ft.n_points) { + ft.contours[ft.n_contours] = ft.n_points - 1; + ft.n_contours++; + } + // mark the current contour as open + // will be updated if ther is a close tag at the end. + ft.contours_flag[ft.n_contours] = 1; + + ft.n_points++; +} + +void FTOutline::lineTo(const VPointF &pt) +{ + ft.points[ft.n_points].x = TO_FT_COORD(pt.x()); + ft.points[ft.n_points].y = TO_FT_COORD(pt.y()); + ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; + ft.n_points++; +} + +void FTOutline::cubicTo(const VPointF &cp1, const VPointF &cp2, + const VPointF ep) +{ + ft.points[ft.n_points].x = TO_FT_COORD(cp1.x()); + ft.points[ft.n_points].y = TO_FT_COORD(cp1.y()); + ft.tags[ft.n_points] = SW_FT_CURVE_TAG_CUBIC; + ft.n_points++; + + ft.points[ft.n_points].x = TO_FT_COORD(cp2.x()); + ft.points[ft.n_points].y = TO_FT_COORD(cp2.y()); + ft.tags[ft.n_points] = SW_FT_CURVE_TAG_CUBIC; + ft.n_points++; + + ft.points[ft.n_points].x = TO_FT_COORD(ep.x()); + ft.points[ft.n_points].y = TO_FT_COORD(ep.y()); + ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; + ft.n_points++; +} +void FTOutline::close() +{ + // mark the contour as a close path. + ft.contours_flag[ft.n_contours] = 0; + + int index; + if (ft.n_contours) { + index = ft.contours[ft.n_contours - 1] + 1; + } else { + index = 0; + } + + // make sure atleast 1 point exists in the segment. + if (ft.n_points == index) { + closed = false; + return; + } + + ft.points[ft.n_points].x = ft.points[index].x; + ft.points[ft.n_points].y = ft.points[index].y; + ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; + ft.n_points++; +} + +void FTOutline::end() +{ + if (ft.n_points) { + ft.contours[ft.n_contours] = ft.n_points - 1; + ft.n_contours++; + } +} + +static void rleGenerationCb(int count, const SW_FT_Span *spans, void *user) +{ + VRle *rle = static_cast(user); + auto *rleSpan = reinterpret_cast(spans); + rle->addSpan(rleSpan, count); +} + +static void bboxCb(int x, int y, int w, int h, void *user) +{ + VRle *rle = static_cast(user); + rle->setBoundingRect({x, y, w, h}); +} + +class SharedRle { +public: + SharedRle() = default; + VRle &unsafe() { return _rle; } + void notify() + { + { + std::lock_guard lock(_mutex); + _ready = true; + } + _cv.notify_one(); + } + VRle &get() + { + if (!_pending) return _rle; + + std::unique_lock lock(_mutex); + while (!_ready) _cv.wait(lock); + _pending = false; + return _rle; + } + + void reset() + { + _ready = false; + _pending = true; + } + +private: + VRle _rle; + std::mutex _mutex; + std::condition_variable _cv; + bool _ready{true}; + bool _pending{false}; +}; + +struct VRleTask { + SharedRle mRle; + VPath mPath; + float mStrokeWidth; + float mMeterLimit; + VRect mClip; + FillRule mFillRule; + CapStyle mCap; + JoinStyle mJoin; + bool mGenerateStroke; + + VRle &rle() { return mRle.get(); } + + void update(VPath path, FillRule fillRule, const VRect &clip) + { + mRle.reset(); + mPath = std::move(path); + mFillRule = fillRule; + mClip = clip; + mGenerateStroke = false; + } + + void update(VPath path, CapStyle cap, JoinStyle join, float width, + float meterLimit, const VRect &clip) + { + mRle.reset(); + mPath = std::move(path); + mCap = cap; + mJoin = join; + mStrokeWidth = width; + mMeterLimit = meterLimit; + mClip = clip; + mGenerateStroke = true; + } + void render(FTOutline &outRef) + { + SW_FT_Raster_Params params; + + mRle.unsafe().reset(); + + params.flags = SW_FT_RASTER_FLAG_DIRECT | SW_FT_RASTER_FLAG_AA; + params.gray_spans = &rleGenerationCb; + params.bbox_cb = &bboxCb; + params.user = &mRle.unsafe(); + params.source = &outRef.ft; + + if (!mClip.empty()) { + params.flags |= SW_FT_RASTER_FLAG_CLIP; + + params.clip_box.xMin = mClip.left(); + params.clip_box.yMin = mClip.top(); + params.clip_box.xMax = mClip.right(); + params.clip_box.yMax = mClip.bottom(); + } + // compute rle + sw_ft_grays_raster.raster_render(nullptr, ¶ms); + } + + void update(FTOutline &outRef, SW_FT_Stroker &stroker) + { + if (mGenerateStroke) { // Stroke Task + outRef.convert(mPath); + outRef.convert(mCap, mJoin, mStrokeWidth, mMeterLimit); + + uint points, contors; + + SW_FT_Stroker_Set(stroker, outRef.ftWidth, outRef.ftCap, + outRef.ftJoin, outRef.ftMeterLimit); + SW_FT_Stroker_ParseOutline(stroker, &outRef.ft); + SW_FT_Stroker_GetCounts(stroker, &points, &contors); + + outRef.grow(points, contors); + + SW_FT_Stroker_Export(stroker, &outRef.ft); + + } else { // Fill Task + outRef.convert(mPath); + int fillRuleFlag = SW_FT_OUTLINE_NONE; + switch (mFillRule) { + case FillRule::EvenOdd: + fillRuleFlag = SW_FT_OUTLINE_EVEN_ODD_FILL; + break; + default: + fillRuleFlag = SW_FT_OUTLINE_NONE; + break; + } + outRef.ft.flags = fillRuleFlag; + } + + render(outRef); + + mPath = VPath(); + + mRle.notify(); + } +}; + +struct VRasterizer::VRasterizerImpl { + VRleTask mTask; + FTOutline outlineRef; + SW_FT_Stroker stroker; + + VRasterizerImpl() { + SW_FT_Stroker_New(&stroker); + } + + ~VRasterizerImpl() { + SW_FT_Stroker_Done(stroker); + } + + VRle & rle() { return mTask.rle(); } + VRleTask &task() { return mTask; } +}; + +VRle VRasterizer::rle() +{ + if (!d) return VRle(); + return d->rle(); +} + +void VRasterizer::init() +{ + if (!d) d = std::make_shared(); +} + +void VRasterizer::updateRequest() +{ + d->task().update(d->outlineRef, d->stroker); +} + +void VRasterizer::rasterize(VPath path, FillRule fillRule, const VRect &clip) +{ + init(); + if (path.empty()) { + d->rle().reset(); + return; + } + d->task().update(std::move(path), fillRule, clip); + updateRequest(); +} + +void VRasterizer::rasterize(VPath path, CapStyle cap, JoinStyle join, + float width, float meterLimit, const VRect &clip) +{ + init(); + if (path.empty() || vIsZero(width)) { + d->rle().reset(); + return; + } + d->task().update(std::move(path), cap, join, width, meterLimit, clip); + updateRequest(); +} + +V_END_NAMESPACE diff --git a/TMessagesProj/jni/rlottie/src/vector/vraster.h b/TMessagesProj/jni/rlottie/src/vector/vraster.h new file mode 100755 index 000000000..987f9ad2b --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vraster.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VRASTER_H +#define VRASTER_H +#include +#include "vglobal.h" +#include "vrect.h" + +V_BEGIN_NAMESPACE + +class VPath; +class VRle; + +class VRasterizer +{ +public: + void rasterize(VPath path, FillRule fillRule = FillRule::Winding, const VRect &clip = VRect()); + void rasterize(VPath path, CapStyle cap, JoinStyle join, float width, + float meterLimit, const VRect &clip = VRect()); + VRle rle(); +private: + struct VRasterizerImpl; + void init(); + void updateRequest(); + std::shared_ptr d{nullptr}; +}; + +V_END_NAMESPACE + +#endif // VRASTER_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vrect.cpp b/TMessagesProj/jni/rlottie/src/vector/vrect.cpp new file mode 100755 index 000000000..bc5b00873 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vrect.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "vrect.h" +#include + +VRect VRect::operator&(const VRect &r) const +{ + if (empty()) return VRect(); + + int l1 = x1; + int r1 = x1; + if (x2 - x1 + 1 < 0) + l1 = x2; + else + r1 = x2; + + int l2 = r.x1; + int r2 = r.x1; + if (r.x2 - r.x1 + 1 < 0) + l2 = r.x2; + else + r2 = r.x2; + + if (l1 > r2 || l2 > r1) return VRect(); + + int t1 = y1; + int b1 = y1; + if (y2 - y1 + 1 < 0) + t1 = y2; + else + b1 = y2; + + int t2 = r.y1; + int b2 = r.y1; + if (r.y2 - r.y1 + 1 < 0) + t2 = r.y2; + else + b2 = r.y2; + + if (t1 > b2 || t2 > b1) return VRect(); + + VRect tmp; + tmp.x1 = std::max(l1, l2); + tmp.x2 = std::min(r1, r2); + tmp.y1 = std::max(t1, t2); + tmp.y2 = std::min(b1, b2); + return tmp; +} diff --git a/TMessagesProj/jni/rlottie/src/vector/vrect.h b/TMessagesProj/jni/rlottie/src/vector/vrect.h new file mode 100755 index 000000000..494579bce --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vrect.h @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VRECT_H +#define VRECT_H +#include "vglobal.h" +#include "vpoint.h" + +V_BEGIN_NAMESPACE +class VRectF; + +class VRect { +public: + VRect() = default; + VRect(int x, int y, int w, int h):x1(x),y1(y),x2(x+w),y2(y+h){} + explicit VRect(const VRectF &r); + V_CONSTEXPR bool empty() const {return x1 >= x2 || y1 >= y2;} + V_CONSTEXPR int left() const {return x1;} + V_CONSTEXPR int top() const {return y1;} + V_CONSTEXPR int right() const {return x2;} + V_CONSTEXPR int bottom() const {return y2;} + V_CONSTEXPR int width() const {return x2-x1;} + V_CONSTEXPR int height() const {return y2-y1;} + V_CONSTEXPR int x() const {return x1;} + V_CONSTEXPR int y() const {return y1;} + VSize size() const {return {width(), height()};} + void setLeft(int l) { x1 = l; } + void setTop(int t) { y1 = t; } + void setRight(int r) { x2 = r; } + void setBottom(int b) { y2 = b; } + void setWidth(int w) { x2 = x1 + w; } + void setHeight(int h) { y2 = y1 + h; } + VRect translated(int dx, int dy) const; + void translate(int dx, int dy); + bool contains(const VRect &r, bool proper = false) const; + bool intersects(const VRect &r); + friend V_CONSTEXPR inline bool operator==(const VRect &, + const VRect &) noexcept; + friend V_CONSTEXPR inline bool operator!=(const VRect &, + const VRect &) noexcept; + friend VDebug & operator<<(VDebug &os, const VRect &o); + + VRect intersected(const VRect &r) const; + VRect operator&(const VRect &r) const; + +private: + int x1{0}; + int y1{0}; + int x2{0}; + int y2{0}; +}; + +inline VRect VRect::intersected(const VRect &r) const +{ + return *this & r; +} + +inline bool VRect::intersects(const VRect &r) +{ + return (right() > r.left() && left() < r.right() && bottom() > r.top() && + top() < r.bottom()); +} + +inline VDebug &operator<<(VDebug &os, const VRect &o) +{ + os << "{R " << o.x() << "," << o.y() << "," << o.width() << "," + << o.height() << "}"; + return os; +} +V_CONSTEXPR inline bool operator==(const VRect &r1, const VRect &r2) noexcept +{ + return r1.x1 == r2.x1 && r1.x2 == r2.x2 && r1.y1 == r2.y1 && r1.y2 == r2.y2; +} + +V_CONSTEXPR inline bool operator!=(const VRect &r1, const VRect &r2) noexcept +{ + return r1.x1 != r2.x1 || r1.x2 != r2.x2 || r1.y1 != r2.y1 || r1.y2 != r2.y2; +} + +inline VRect VRect::translated(int dx, int dy) const +{ + return {x1 + dx, y1 + dy, x2 - x1, y2 - y1}; +} + +inline void VRect::translate(int dx, int dy) +{ + x1 += dx; + y1 += dy; + x2 += dx; + y2 += dy; +} + +inline bool VRect::contains(const VRect &r, bool proper) const +{ + if (!proper) { + if ((x1 <= r.x1) && (x2 >= r.x2) && (y1 <= r.y1) && (y2 >= r.y2)) + return true; + return false; + } else { + if ((x1 < r.x1) && (x2 > r.x2) && (y1 < r.y1) && (y2 > r.y2)) + return true; + return false; + } +} + +class VRectF { +public: + VRectF() = default; + VRectF(float x, float y, float w, float h):x1(x),y1(y),x2(x+w),y2(y+h){} + explicit VRectF(const VRect &r):x1(r.left()),y1(r.top()), + x2(r.right()),y2(r.bottom()){} + + V_CONSTEXPR bool empty() const {return x1 >= x2 || y1 >= y2;} + V_CONSTEXPR float left() const {return x1;} + V_CONSTEXPR float top() const {return y1;} + V_CONSTEXPR float right() const {return x2;} + V_CONSTEXPR float bottom() const {return y2;} + V_CONSTEXPR float width() const {return x2-x1;} + V_CONSTEXPR float height() const {return y2-y1;} + V_CONSTEXPR float x() const {return x1;} + V_CONSTEXPR float y() const {return y1;} + V_CONSTEXPR inline VPointF center() const + { + return {x1 + (x2 - x1) / 2.f, y1 + (y2 - y1) / 2.f}; + } + void setLeft(float l) { x1 = l; } + void setTop(float t) { y1 = t; } + void setRight(float r) { x2 = r; } + void setBottom(float b) { y2 = b; } + void setWidth(float w) { x2 = x1 + w; } + void setHeight(float h) { y2 = y1 + h; } + void translate(float dx, float dy) + { + x1 += dx; + y1 += dy; + x2 += dx; + y2 += dy; + } + +private: + float x1{0}; + float y1{0}; + float x2{0}; + float y2{0}; +}; + +inline VRect::VRect(const VRectF &r):x1(r.left()),y1(r.top()), + x2(r.right()),y2(r.bottom()){} +V_END_NAMESPACE + +#endif // VRECT_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vrle.cpp b/TMessagesProj/jni/rlottie/src/vector/vrle.cpp new file mode 100755 index 000000000..988ea3af4 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vrle.cpp @@ -0,0 +1,731 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "vrle.h" +#include +#include +#include +#include +#include +#include "vdebug.h" +#include "vglobal.h" +#include "vregion.h" + +V_BEGIN_NAMESPACE + +enum class Operation { Add, Xor }; + +struct VRleHelper { + size_t alloc; + size_t size; + VRle::Span *spans; +}; +static void rleIntersectWithRle(VRleHelper *, int, int, VRleHelper *, + VRleHelper *); +static void rleIntersectWithRect(const VRect &, VRleHelper *, VRleHelper *); +static void rleOpGeneric(VRleHelper *, VRleHelper *, VRleHelper *, + Operation op); +static void rleSubstractWithRle(VRleHelper *, VRleHelper *, VRleHelper *); + +static inline uchar divBy255(int x) +{ + return (x + (x >> 8) + 0x80) >> 8; +} + +inline static void copyArrayToVector(const VRle::Span *span, size_t count, + std::vector &v) +{ + // make sure enough memory available + if (v.capacity() < v.size() + count) v.reserve(v.size() + count); + std::copy(span, span + count, back_inserter(v)); +} + +void VRle::VRleData::addSpan(const VRle::Span *span, size_t count) +{ + copyArrayToVector(span, count, mSpans); + mBboxDirty = true; +} + +VRect VRle::VRleData::bbox() const +{ + updateBbox(); + return mBbox; +} + +void VRle::VRleData::setBbox(const VRect &bbox) const +{ + mBboxDirty = false; + mBbox = bbox; +} + +void VRle::VRleData::reset() +{ + mSpans.clear(); + mBbox = VRect(); + mOffset = VPoint(); + mBboxDirty = false; +} + +void VRle::VRleData::clone(const VRle::VRleData &o) +{ + *this = o; +} + +void VRle::VRleData::translate(const VPoint &p) +{ + // take care of last offset if applied + mOffset = p - mOffset; + int x = mOffset.x(); + int y = mOffset.y(); + for (auto &i : mSpans) { + i.x = i.x + x; + i.y = i.y + y; + } + updateBbox(); + mBbox.translate(mOffset.x(), mOffset.y()); +} + +void VRle::VRleData::addRect(const VRect &rect) +{ + int x = rect.left(); + int y = rect.top(); + int width = rect.width(); + int height = rect.height(); + + mSpans.reserve(size_t(height)); + + VRle::Span span; + for (int i = 0; i < height; i++) { + span.x = x; + span.y = y + i; + span.len = width; + span.coverage = 255; + mSpans.push_back(span); + } + updateBbox(); +} + +void VRle::VRleData::updateBbox() const +{ + if (!mBboxDirty) return; + + mBboxDirty = false; + + int l = std::numeric_limits::max(); + const VRle::Span *span = mSpans.data(); + + mBbox = VRect(); + size_t sz = mSpans.size(); + if (sz) { + int t = span[0].y; + int b = span[sz - 1].y; + int r = 0; + for (size_t i = 0; i < sz; i++) { + if (span[i].x < l) l = span[i].x; + if (span[i].x + span[i].len > r) r = span[i].x + span[i].len; + } + mBbox = VRect(l, t, r - l, b - t + 1); + } +} + +void VRle::VRleData::invert() +{ + for (auto &i : mSpans) { + i.coverage = 255 - i.coverage; + } +} + +void VRle::VRleData::operator*=(int alpha) +{ + alpha &= 0xff; + for (auto &i : mSpans) { + i.coverage = divBy255(i.coverage * alpha); + } +} + +void VRle::VRleData::opIntersect(const VRect &r, VRle::VRleSpanCb cb, + void *userData) const +{ + if (empty()) return; + + if (r.contains(bbox())) { + cb(mSpans.size(), mSpans.data(), userData); + return; + } + + VRect clip = r; + VRleHelper tresult, tmp_obj; + std::array array; + + // setup the tresult object + tresult.size = array.size(); + tresult.alloc = array.size(); + tresult.spans = array.data(); + + // setup tmp object + tmp_obj.size = mSpans.size(); + tmp_obj.spans = const_cast(mSpans.data()); + + // run till all the spans are processed + while (tmp_obj.size) { + rleIntersectWithRect(clip, &tmp_obj, &tresult); + if (tresult.size) { + cb(tresult.size, tresult.spans, userData); + } + tresult.size = 0; + } +} + +// res = a - b; +void VRle::VRleData::opSubstract(const VRle::VRleData &a, + const VRle::VRleData &b) +{ + // if two rle are disjoint + if (!a.bbox().intersects(b.bbox())) { + mSpans = a.mSpans; + } else { + VRle::Span * aPtr = const_cast(a.mSpans.data()); + const VRle::Span *aEnd = a.mSpans.data() + a.mSpans.size(); + VRle::Span * bPtr = const_cast(b.mSpans.data()); + const VRle::Span *bEnd = b.mSpans.data() + b.mSpans.size(); + + // 1. forward till both y intersect + while ((aPtr != aEnd) && (aPtr->y < bPtr->y)) aPtr++; + size_t sizeA = size_t(aPtr - a.mSpans.data()); + if (sizeA) copyArrayToVector(a.mSpans.data(), sizeA, mSpans); + + // 2. forward b till it intersect with a. + while ((bPtr != bEnd) && (bPtr->y < aPtr->y)) bPtr++; + size_t sizeB = size_t(bPtr - b.mSpans.data()); + + // 2. calculate the intersect region + VRleHelper tresult, aObj, bObj; + std::array array; + + // setup the tresult object + tresult.size = array.size(); + tresult.alloc = array.size(); + tresult.spans = array.data(); + + // setup a object + aObj.size = a.mSpans.size() - sizeA; + aObj.spans = aPtr; + + // setup b object + bObj.size = b.mSpans.size() - sizeB; + bObj.spans = bPtr; + + // run till all the spans are processed + while (aObj.size && bObj.size) { + rleSubstractWithRle(&aObj, &bObj, &tresult); + if (tresult.size) { + copyArrayToVector(tresult.spans, tresult.size, mSpans); + } + tresult.size = 0; + } + // 3. copy the rest of a + if (aObj.size) copyArrayToVector(aObj.spans, aObj.size, mSpans); + } + + mBboxDirty = true; +} + +void VRle::VRleData::opGeneric(const VRle::VRleData &a, const VRle::VRleData &b, + OpCode code) +{ + // This routine assumes, obj1(span_y) < obj2(span_y). + + // reserve some space for the result vector. + mSpans.reserve(a.mSpans.size() + b.mSpans.size()); + + // if two rle are disjoint + if (!a.bbox().intersects(b.bbox())) { + if (a.mSpans[0].y < b.mSpans[0].y) { + copyArrayToVector(a.mSpans.data(), a.mSpans.size(), mSpans); + copyArrayToVector(b.mSpans.data(), b.mSpans.size(), mSpans); + } else { + copyArrayToVector(b.mSpans.data(), b.mSpans.size(), mSpans); + copyArrayToVector(a.mSpans.data(), a.mSpans.size(), mSpans); + } + } else { + VRle::Span * aPtr = const_cast(a.mSpans.data()); + const VRle::Span *aEnd = a.mSpans.data() + a.mSpans.size(); + VRle::Span * bPtr = const_cast(b.mSpans.data()); + const VRle::Span *bEnd = b.mSpans.data() + b.mSpans.size(); + + // 1. forward a till it intersects with b + while ((aPtr != aEnd) && (aPtr->y < bPtr->y)) aPtr++; + size_t sizeA = size_t(aPtr - a.mSpans.data()); + if (sizeA) copyArrayToVector(a.mSpans.data(), sizeA, mSpans); + + // 2. forward b till it intersects with a + while ((bPtr != bEnd) && (bPtr->y < aPtr->y)) bPtr++; + size_t sizeB = size_t(bPtr - b.mSpans.data()); + if (sizeB) copyArrayToVector(b.mSpans.data(), sizeB, mSpans); + + // 3. calculate the intersect region + VRleHelper tresult, aObj, bObj; + std::array array; + + // setup the tresult object + tresult.size = array.size(); + tresult.alloc = array.size(); + tresult.spans = array.data(); + + // setup a object + aObj.size = a.mSpans.size() - sizeA; + aObj.spans = aPtr; + + // setup b object + bObj.size = b.mSpans.size() - sizeB; + bObj.spans = bPtr; + + Operation op = Operation::Add; + switch (code) { + case OpCode::Add: + op = Operation::Add; + break; + case OpCode::Xor: + op = Operation::Xor; + break; + } + // run till all the spans are processed + while (aObj.size && bObj.size) { + rleOpGeneric(&aObj, &bObj, &tresult, op); + if (tresult.size) { + copyArrayToVector(tresult.spans, tresult.size, mSpans); + } + tresult.size = 0; + } + // 3. copy the rest + if (bObj.size) copyArrayToVector(bObj.spans, bObj.size, mSpans); + if (aObj.size) copyArrayToVector(aObj.spans, aObj.size, mSpans); + } + + // update result bounding box + VRegion reg(a.bbox()); + reg += b.bbox(); + mBbox = reg.boundingRect(); + mBboxDirty = false; +} + +static void rle_cb(size_t count, const VRle::Span *spans, void *userData) +{ + auto vector = static_cast *>(userData); + copyArrayToVector(spans, count, *vector); +} + +void opIntersectHelper(const VRle::VRleData &obj1, const VRle::VRleData &obj2, + VRle::VRleSpanCb cb, void *userData) +{ + VRleHelper result, source, clip; + std::array array; + + // setup the tresult object + result.size = array.size(); + result.alloc = array.size(); + result.spans = array.data(); + + // setup tmp object + source.size = obj1.mSpans.size(); + source.spans = const_cast(obj1.mSpans.data()); + + // setup tmp clip object + clip.size = obj2.mSpans.size(); + clip.spans = const_cast(obj2.mSpans.data()); + + // run till all the spans are processed + while (source.size) { + rleIntersectWithRle(&clip, 0, 0, &source, &result); + if (result.size) { + cb(result.size, result.spans, userData); + } + result.size = 0; + } +} + +void VRle::VRleData::opIntersect(const VRle::VRleData &obj1, + const VRle::VRleData &obj2) +{ + opIntersectHelper(obj1, obj2, rle_cb, &mSpans); + updateBbox(); +} + +#define VMIN(a, b) ((a) < (b) ? (a) : (b)) +#define VMAX(a, b) ((a) > (b) ? (a) : (b)) + +/* + * This function will clip a rle list with another rle object + * tmp_clip : The rle list that will be use to clip the rle + * tmp_obj : holds the list of spans that has to be clipped + * result : will hold the result after the processing + * NOTE: if the algorithm runs out of the result buffer list + * it will stop and update the tmp_obj with the span list + * that are yet to be processed as well as the tpm_clip object + * with the unprocessed clip spans. + */ +static void rleIntersectWithRle(VRleHelper *tmp_clip, int clip_offset_x, + int clip_offset_y, VRleHelper *tmp_obj, + VRleHelper *result) +{ + VRle::Span *out = result->spans; + int available = result->alloc; + VRle::Span *spans = tmp_obj->spans; + VRle::Span *end = tmp_obj->spans + tmp_obj->size; + VRle::Span *clipSpans = tmp_clip->spans; + VRle::Span *clipEnd = tmp_clip->spans + tmp_clip->size; + int sx1, sx2, cx1, cx2, x, len; + + while (available && spans < end) { + if (clipSpans >= clipEnd) { + spans = end; + break; + } + if ((clipSpans->y + clip_offset_y) > spans->y) { + ++spans; + continue; + } + if (spans->y != (clipSpans->y + clip_offset_y)) { + ++clipSpans; + continue; + } + // assert(spans->y == (clipSpans->y + clip_offset_y)); + sx1 = spans->x; + sx2 = sx1 + spans->len; + cx1 = (clipSpans->x + clip_offset_x); + cx2 = cx1 + clipSpans->len; + + if (cx1 < sx1 && cx2 < sx1) { + ++clipSpans; + continue; + } else if (sx1 < cx1 && sx2 < cx1) { + ++spans; + continue; + } + x = std::max(sx1, cx1); + len = std::min(sx2, cx2) - x; + if (len) { + out->x = std::max(sx1, cx1); + out->len = (std::min(sx2, cx2) - out->x); + out->y = spans->y; + out->coverage = divBy255(spans->coverage * clipSpans->coverage); + ++out; + --available; + } + if (sx2 < cx2) { + ++spans; + } else { + ++clipSpans; + } + } + + // update the span list that yet to be processed + tmp_obj->spans = spans; + tmp_obj->size = end - spans; + + // update the clip list that yet to be processed + tmp_clip->spans = clipSpans; + tmp_clip->size = clipEnd - clipSpans; + + // update the result + result->size = result->alloc - available; +} + +/* + * This function will clip a rle list with a given rect + * clip : The clip rect that will be use to clip the rle + * tmp_obj : holds the list of spans that has to be clipped + * result : will hold the result after the processing + * NOTE: if the algorithm runs out of the result buffer list + * it will stop and update the tmp_obj with the span list + * that are yet to be processed + */ +static void rleIntersectWithRect(const VRect &clip, VRleHelper *tmp_obj, + VRleHelper *result) +{ + VRle::Span *out = result->spans; + int available = result->alloc; + VRle::Span *spans = tmp_obj->spans; + VRle::Span *end = tmp_obj->spans + tmp_obj->size; + short minx, miny, maxx, maxy; + + minx = clip.left(); + miny = clip.top(); + maxx = clip.right() - 1; + maxy = clip.bottom() - 1; + + while (available && spans < end) { + if (spans->y > maxy) { + spans = end; // update spans so that we can breakout + break; + } + if (spans->y < miny || spans->x > maxx || + spans->x + spans->len <= minx) { + ++spans; + continue; + } + if (spans->x < minx) { + out->len = VMIN(spans->len - (minx - spans->x), maxx - minx + 1); + out->x = minx; + } else { + out->x = spans->x; + out->len = VMIN(spans->len, (maxx - spans->x + 1)); + } + if (out->len != 0) { + out->y = spans->y; + out->coverage = spans->coverage; + ++out; + --available; + } + ++spans; + } + + // update the span list that yet to be processed + tmp_obj->spans = spans; + tmp_obj->size = end - spans; + + // update the result + result->size = result->alloc - available; +} + +void blitXor(VRle::Span *spans, int count, uchar *buffer, int offsetX) +{ + while (count--) { + int x = spans->x + offsetX; + int l = spans->len; + uchar *ptr = buffer + x; + while (l--) { + int da = *ptr; + *ptr = divBy255((255 - spans->coverage) * (da) + + spans->coverage * (255 - da)); + ptr++; + } + spans++; + } +} + +void blitDestinationOut(VRle::Span *spans, int count, uchar *buffer, + int offsetX) +{ + while (count--) { + int x = spans->x + offsetX; + int l = spans->len; + uchar *ptr = buffer + x; + while (l--) { + *ptr = divBy255((255 - spans->coverage) * (*ptr)); + ptr++; + } + spans++; + } +} + +void blitSrcOver(VRle::Span *spans, int count, uchar *buffer, int offsetX) +{ + while (count--) { + int x = spans->x + offsetX; + int l = spans->len; + uchar *ptr = buffer + x; + while (l--) { + *ptr = spans->coverage + divBy255((255 - spans->coverage) * (*ptr)); + ptr++; + } + spans++; + } +} + +void blit(VRle::Span *spans, int count, uchar *buffer, int offsetX) +{ + while (count--) { + int x = spans->x + offsetX; + int l = spans->len; + uchar *ptr = buffer + x; + while (l--) { + *ptr = std::max(spans->coverage, *ptr); + ptr++; + } + spans++; + } +} + +size_t bufferToRle(uchar *buffer, int size, int offsetX, int y, VRle::Span *out) +{ + size_t count = 0; + uchar value = buffer[0]; + int curIndex = 0; + + size = offsetX < 0 ? size + offsetX : size; + for (int i = 0; i < size; i++) { + uchar curValue = buffer[0]; + if (value != curValue) { + if (value) { + out->y = y; + out->x = offsetX + curIndex; + out->len = i - curIndex; + out->coverage = value; + out++; + count++; + } + curIndex = i; + value = curValue; + } + buffer++; + } + if (value) { + out->y = y; + out->x = offsetX + curIndex; + out->len = size - curIndex; + out->coverage = value; + count++; + } + return count; +} + +static void rleOpGeneric(VRleHelper *a, VRleHelper *b, VRleHelper *result, + Operation op) +{ + std::array temp; + VRle::Span * out = result->spans; + size_t available = result->alloc; + VRle::Span * aPtr = a->spans; + VRle::Span * aEnd = a->spans + a->size; + VRle::Span * bPtr = b->spans; + VRle::Span * bEnd = b->spans + b->size; + + while (available && aPtr < aEnd && bPtr < bEnd) { + if (aPtr->y < bPtr->y) { + *out++ = *aPtr++; + available--; + } else if (bPtr->y < aPtr->y) { + *out++ = *bPtr++; + available--; + } else { // same y + VRle::Span *aStart = aPtr; + VRle::Span *bStart = bPtr; + + int y = aPtr->y; + + while (aPtr < aEnd && aPtr->y == y) aPtr++; + while (bPtr < bEnd && bPtr->y == y) bPtr++; + + int aLength = (aPtr - 1)->x + (aPtr - 1)->len; + int bLength = (bPtr - 1)->x + (bPtr - 1)->len; + int offset = std::min(aStart->x, bStart->x); + + std::array array = {{0}}; + blit(aStart, (aPtr - aStart), array.data(), -offset); + if (op == Operation::Add) + blitSrcOver(bStart, (bPtr - bStart), array.data(), -offset); + else if (op == Operation::Xor) + blitXor(bStart, (bPtr - bStart), array.data(), -offset); + VRle::Span *tResult = temp.data(); + size_t size = bufferToRle(array.data(), std::max(aLength, bLength), + offset, y, tResult); + if (available >= size) { + while (size--) { + *out++ = *tResult++; + available--; + } + } else { + aPtr = aStart; + bPtr = bStart; + break; + } + } + } + // update the span list that yet to be processed + a->spans = aPtr; + a->size = aEnd - aPtr; + + // update the clip list that yet to be processed + b->spans = bPtr; + b->size = bEnd - bPtr; + + // update the result + result->size = result->alloc - available; +} + +static void rleSubstractWithRle(VRleHelper *a, VRleHelper *b, + VRleHelper *result) +{ + std::array temp; + VRle::Span * out = result->spans; + size_t available = result->alloc; + VRle::Span * aPtr = a->spans; + VRle::Span * aEnd = a->spans + a->size; + VRle::Span * bPtr = b->spans; + VRle::Span * bEnd = b->spans + b->size; + + while (available && aPtr < aEnd && bPtr < bEnd) { + if (aPtr->y < bPtr->y) { + *out++ = *aPtr++; + available--; + } else if (bPtr->y < aPtr->y) { + bPtr++; + } else { // same y + VRle::Span *aStart = aPtr; + VRle::Span *bStart = bPtr; + + int y = aPtr->y; + + while (aPtr < aEnd && aPtr->y == y) aPtr++; + while (bPtr < bEnd && bPtr->y == y) bPtr++; + + int aLength = (aPtr - 1)->x + (aPtr - 1)->len; + int bLength = (bPtr - 1)->x + (bPtr - 1)->len; + int offset = std::min(aStart->x, bStart->x); + + std::array array = {{0}}; + blit(aStart, (aPtr - aStart), array.data(), -offset); + blitDestinationOut(bStart, (bPtr - bStart), array.data(), -offset); + VRle::Span *tResult = temp.data(); + size_t size = bufferToRle(array.data(), std::max(aLength, bLength), + offset, y, tResult); + if (available >= size) { + while (size--) { + *out++ = *tResult++; + available--; + } + } else { + aPtr = aStart; + bPtr = bStart; + break; + } + } + } + // update the span list that yet to be processed + a->spans = aPtr; + a->size = size_t(aEnd - aPtr); + + // update the clip list that yet to be processed + b->spans = bPtr; + b->size = size_t(bEnd - bPtr); + + // update the result + result->size = result->alloc - available; +} + +VRle VRle::toRle(const VRect &rect) +{ + if (rect.empty()) return VRle(); + + VRle result; + result.d.write().addRect(rect); + return result; +} + +V_END_NAMESPACE diff --git a/TMessagesProj/jni/rlottie/src/vector/vrle.h b/TMessagesProj/jni/rlottie/src/vector/vrle.h new file mode 100755 index 000000000..b8d97db3c --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vrle.h @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VRLE_H +#define VRLE_H + +#include +#include "vcowptr.h" +#include "vglobal.h" +#include "vpoint.h" +#include "vrect.h" + +V_BEGIN_NAMESPACE + +class VRle { +public: + struct Span { + short x; + short y; + ushort len; + uchar coverage; + }; + typedef void (*VRleSpanCb)(size_t count, const VRle::Span *spans, + void *userData); + bool empty() const; + VRect boundingRect() const; + void setBoundingRect(const VRect &bbox); + void addSpan(const VRle::Span *span, size_t count); + + void reset(); + void translate(const VPoint &p); + void invert(); + + void operator*=(int alpha); + + void intersect(const VRect &r, VRleSpanCb cb, void *userData) const; + void intersect(const VRle &rle, VRleSpanCb cb, void *userData) const; + + VRle operator&(const VRle &o) const; + VRle operator-(const VRle &o) const; + VRle operator+(const VRle &o) const; + VRle operator^(const VRle &o) const; + + static VRle toRle(const VRect &rect); + + bool unique() const {return d.unique();} + int refCount() const { return d.refCount();} + void clone(const VRle &o); + +private: + struct VRleData { + enum class OpCode { + Add, + Xor + }; + bool empty() const { return mSpans.empty(); } + void addSpan(const VRle::Span *span, size_t count); + void updateBbox() const; + VRect bbox() const; + void setBbox(const VRect &bbox) const; + void reset(); + void translate(const VPoint &p); + void operator*=(int alpha); + void invert(); + void opIntersect(const VRect &, VRle::VRleSpanCb, void *) const; + void opGeneric(const VRle::VRleData &, const VRle::VRleData &, OpCode code); + void opSubstract(const VRle::VRleData &, const VRle::VRleData &); + void opIntersect(const VRle::VRleData &, const VRle::VRleData &); + void addRect(const VRect &rect); + void clone(const VRle::VRleData &); + std::vector mSpans; + VPoint mOffset; + mutable VRect mBbox; + mutable bool mBboxDirty = true; + }; + friend void opIntersectHelper(const VRle::VRleData &obj1, + const VRle::VRleData &obj2, + VRle::VRleSpanCb cb, void *userData); + vcow_ptr d; +}; + +inline bool VRle::empty() const +{ + return d->empty(); +} + +inline void VRle::addSpan(const VRle::Span *span, size_t count) +{ + d.write().addSpan(span, count); +} + +inline VRect VRle::boundingRect() const +{ + return d->bbox(); +} + +inline void VRle::setBoundingRect(const VRect & bbox) +{ + d->setBbox(bbox); +} + +inline void VRle::invert() +{ + d.write().invert(); +} + +inline void VRle::operator*=(int alpha) +{ + d.write() *= alpha; +} + +inline VRle VRle::operator&(const VRle &o) const +{ + if (empty() || o.empty()) return VRle(); + + VRle result; + result.d.write().opIntersect(d.read(), o.d.read()); + + return result; +} + +inline VRle VRle::operator+(const VRle &o) const +{ + if (empty()) return o; + if (o.empty()) return *this; + + VRle result; + result.d.write().opGeneric(d.read(), o.d.read(), VRleData::OpCode::Add); + + return result; +} + +inline VRle VRle::operator^(const VRle &o) const +{ + if (empty()) return o; + if (o.empty()) return *this; + + VRle result; + result.d.write().opGeneric(d.read(), o.d.read(), VRleData::OpCode::Xor); + + return result; +} + +inline VRle VRle::operator-(const VRle &o) const +{ + if (empty()) return VRle(); + if (o.empty()) return *this; + + VRle result; + result.d.write().opSubstract(d.read(), o.d.read()); + + return result; +} + +inline void VRle::reset() +{ + d.write().reset(); +} + +inline void VRle::clone(const VRle &o) +{ + d.write().clone(o.d.read()); +} + +inline void VRle::translate(const VPoint &p) +{ + d.write().translate(p); +} + +inline void VRle::intersect(const VRect &r, VRleSpanCb cb, void *userData) const +{ + d->opIntersect(r, cb, userData); +} + +inline void VRle::intersect(const VRle &r, VRleSpanCb cb, void *userData) const +{ + if (empty() || r.empty()) return; + opIntersectHelper(d.read(), r.d.read(), cb, userData); +} + +V_END_NAMESPACE + +#endif // VRLE_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vstackallocator.h b/TMessagesProj/jni/rlottie/src/vector/vstackallocator.h new file mode 100755 index 000000000..598913397 --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vstackallocator.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VSTACK_ALLOCATOR_H +#define VSTACK_ALLOCATOR_H + +#include +#include + +template +class arena +{ + alignas(alignment) char buf_[N]; + char* ptr_; + +public: + ~arena() {ptr_ = nullptr;} + arena() noexcept : ptr_(buf_) {} + arena(const arena&) = delete; + arena& operator=(const arena&) = delete; + + template char* allocate(std::size_t n); + void deallocate(char* p, std::size_t n) noexcept; + + static constexpr std::size_t size() noexcept {return N;} + std::size_t used() const noexcept {return static_cast(ptr_ - buf_);} + void reset() noexcept {ptr_ = buf_;} + +private: + static + std::size_t + align_up(std::size_t n) noexcept + {return (n + (alignment-1)) & ~(alignment-1);} + + bool + pointer_in_buffer(char* p) noexcept + {return buf_ <= p && p <= buf_ + N;} +}; + +template +template +char* +arena::allocate(std::size_t n) +{ + static_assert(ReqAlign <= alignment, "alignment is too small for this arena"); + assert(pointer_in_buffer(ptr_) && "stack_alloc has outlived arena"); + auto const aligned_n = align_up(n); + if (static_cast(buf_ + N - ptr_) >= aligned_n) + { + char* r = ptr_; + ptr_ += aligned_n; + return r; + } + + static_assert(alignment <= alignof(std::max_align_t), "you've chosen an " + "alignment that is larger than alignof(std::max_align_t), and " + "cannot be guaranteed by normal operator new"); + return static_cast(::operator new(n)); +} + +template +void +arena::deallocate(char* p, std::size_t n) noexcept +{ + assert(pointer_in_buffer(ptr_) && "stack_alloc has outlived arena"); + if (pointer_in_buffer(p)) + { + n = align_up(n); + if (p + n == ptr_) + ptr_ = p; + } + else + ::operator delete(p); +} + +template +class stack_alloc +{ +public: + using value_type = T; + static auto constexpr alignment = Align; + static auto constexpr size = N; + using arena_type = arena; + +private: + arena_type& a_; + +public: + stack_alloc(const stack_alloc&) = default; + stack_alloc& operator=(const stack_alloc&) = delete; + + stack_alloc(arena_type& a) noexcept : a_(a) + { + static_assert(size % alignment == 0, + "size N needs to be a multiple of alignment Align"); + } + template + stack_alloc(const stack_alloc& a) noexcept + : a_(a.a_) {} + + template struct rebind {using other = stack_alloc<_Up, N, alignment>;}; + + T* allocate(std::size_t n) + { + return reinterpret_cast(a_.template allocate(n*sizeof(T))); + } + void deallocate(T* p, std::size_t n) noexcept + { + a_.deallocate(reinterpret_cast(p), n*sizeof(T)); + } + + template + friend + bool + operator==(const stack_alloc& x, const stack_alloc& y) noexcept; + + template friend class stack_alloc; +}; + +template +inline +bool +operator==(const stack_alloc& x, const stack_alloc& y) noexcept +{ + return N == M && A1 == A2 && &x.a_ == &y.a_; +} + +template +inline +bool +operator!=(const stack_alloc& x, const stack_alloc& y) noexcept +{ + return !(x == y); +} + +#endif // VSTACK_ALLOCATOR_H diff --git a/TMessagesProj/jni/rlottie/src/vector/vtaskqueue.h b/TMessagesProj/jni/rlottie/src/vector/vtaskqueue.h new file mode 100755 index 000000000..62c04360d --- /dev/null +++ b/TMessagesProj/jni/rlottie/src/vector/vtaskqueue.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VTASKQUEUE_H +#define VTASKQUEUE_H + +#include + +template +class TaskQueue { + using lock_t = std::unique_lock; + std::deque _q; + bool _done{false}; + std::mutex _mutex; + std::condition_variable _ready; + +public: + bool try_pop(Task &task) + { + lock_t lock{_mutex, std::try_to_lock}; + if (!lock || _q.empty()) return false; + task = std::move(_q.front()); + _q.pop_front(); + return true; + } + + bool try_push(Task &&task) + { + { + lock_t lock{_mutex, std::try_to_lock}; + if (!lock) return false; + _q.push_back(std::move(task)); + } + _ready.notify_one(); + return true; + } + + void done() + { + { + lock_t lock{_mutex}; + _done = true; + } + _ready.notify_all(); + } + + bool pop(Task &task) + { + lock_t lock{_mutex}; + while (_q.empty() && !_done) _ready.wait(lock); + if (_q.empty()) return false; + task = std::move(_q.front()); + _q.pop_front(); + return true; + } + + void push(Task &&task) + { + { + lock_t lock{_mutex}; + _q.push_back(std::move(task)); + } + _ready.notify_one(); + } + +}; + +#endif // VTASKQUEUE_H diff --git a/TMessagesProj/jni/tgnet/ApiScheme.h b/TMessagesProj/jni/tgnet/ApiScheme.h index 122041cd6..610f8647d 100644 --- a/TMessagesProj/jni/tgnet/ApiScheme.h +++ b/TMessagesProj/jni/tgnet/ApiScheme.h @@ -11,7 +11,6 @@ #include #include -#include #include "TLObject.h" class ByteArray; diff --git a/TMessagesProj/jni/tgnet/ConnectionsManager.cpp b/TMessagesProj/jni/tgnet/ConnectionsManager.cpp index b4193153a..14a4e24d3 100644 --- a/TMessagesProj/jni/tgnet/ConnectionsManager.cpp +++ b/TMessagesProj/jni/tgnet/ConnectionsManager.cpp @@ -377,7 +377,7 @@ void ConnectionsManager::loadConfig() { for (uint32_t a = 0; a < count; a++) { Datacenter *datacenter = new Datacenter(instanceNum, buffer); datacenters[datacenter->getDatacenterId()] = datacenter; - if (LOGS_ENABLED) DEBUG_D("datacenter(%p) %u loaded (hasAuthKey = %d)", datacenter, datacenter->getDatacenterId(), (int) datacenter->hasPermanentAuthKey()); + if (LOGS_ENABLED) DEBUG_D("datacenter(%p) %u loaded (hasAuthKey = %d, 0x%" PRIx64 ")", datacenter, datacenter->getDatacenterId(), (int) datacenter->hasPermanentAuthKey(), datacenter->getPermanentAuthKeyId()); } } } @@ -680,31 +680,33 @@ void ConnectionsManager::onConnectionClosed(Connection *connection, int reason) sendingPushPing = false; lastPushPingTime = getCurrentTimeMonotonicMillis() - nextPingTimeOffset + 4000; } else if (connection->getConnectionType() == ConnectionTypeProxy) { - for (std::vector>::iterator iter = proxyActiveChecks.begin(); iter != proxyActiveChecks.end(); iter++) { - ProxyCheckInfo *proxyCheckInfo = iter->get(); - if (proxyCheckInfo->connectionNum == connection->getConnectionNum()) { - bool found = false; - for (requestsIter iter2 = runningRequests.begin(); iter2 != runningRequests.end(); iter2++) { - Request *request = iter2->get(); - if (connection->getConnectionToken() == request->connectionToken && request->requestToken == proxyCheckInfo->requestToken && (request->connectionType & 0x0000ffff) == ConnectionTypeProxy) { - request->completed = true; - runningRequests.erase(iter2); - proxyCheckInfo->onRequestTime(-1); - found = true; - break; + scheduleTask([&, connection] { + for (std::vector>::iterator iter = proxyActiveChecks.begin(); iter != proxyActiveChecks.end(); iter++) { + ProxyCheckInfo *proxyCheckInfo = iter->get(); + if (proxyCheckInfo->connectionNum == connection->getConnectionNum()) { + bool found = false; + for (requestsIter iter2 = runningRequests.begin(); iter2 != runningRequests.end(); iter2++) { + Request *request = iter2->get(); + if (connection->getConnectionToken() == request->connectionToken && request->requestToken == proxyCheckInfo->requestToken && (request->connectionType & 0x0000ffff) == ConnectionTypeProxy) { + request->completed = true; + runningRequests.erase(iter2); + proxyCheckInfo->onRequestTime(-1); + found = true; + break; + } } - } - if (found) { - proxyActiveChecks.erase(iter); - if (!proxyCheckQueue.empty()) { - proxyCheckInfo = proxyCheckQueue[0].release(); - proxyCheckQueue.erase(proxyCheckQueue.begin()); - checkProxyInternal(proxyCheckInfo); + if (found) { + proxyActiveChecks.erase(iter); + if (!proxyCheckQueue.empty()) { + proxyCheckInfo = proxyCheckQueue[0].release(); + proxyCheckQueue.erase(proxyCheckQueue.begin()); + checkProxyInternal(proxyCheckInfo); + } } + break; } - break; } - } + }); } } @@ -1097,7 +1099,7 @@ void ConnectionsManager::processServerResponse(TLObject *message, int64_t messag if (!proxyCheckQueue.empty()) { proxyCheckInfo = proxyCheckQueue[0].release(); proxyCheckQueue.erase(proxyCheckQueue.begin()); - checkProxyInternal(proxyCheckInfo); + scheduleCheckProxyInternal(proxyCheckInfo); } break; } @@ -1880,7 +1882,7 @@ void ConnectionsManager::onDatacenterHandshakeComplete(Datacenter *datacenter, H if (type == HandshakeTypeTemp && !proxyCheckQueue.empty()) { ProxyCheckInfo *proxyCheckInfo = proxyCheckQueue[0].release(); proxyCheckQueue.erase(proxyCheckQueue.begin()); - checkProxyInternal(proxyCheckInfo); + scheduleCheckProxyInternal(proxyCheckInfo); } } @@ -2638,9 +2640,21 @@ std::unique_ptr ConnectionsManager::wrapInLayer(TLObject *object, Data request->api_id = currentApiId; request->app_version = currentAppVersion; request->lang_code = currentLangCode; - request->system_lang_code = currentLangCode; request->lang_pack = "android"; request->system_lang_code = currentSystemLangCode; + if (!currentRegId.empty()) { + TL_jsonObject *jsonObject = new TL_jsonObject(); + TL_jsonObjectValue *objectValue = new TL_jsonObjectValue(); + jsonObject->value.push_back(std::unique_ptr(objectValue)); + + TL_jsonString *jsonString = new TL_jsonString(); + jsonString->value = currentRegId; + objectValue->key = "device_token"; + objectValue->value = std::unique_ptr(jsonString); + request->params = std::unique_ptr(jsonObject); + + request->flags |= 2; + } if (!proxyAddress.empty() && !proxySecret.empty()) { request->flags |= 1; request->proxy = std::unique_ptr(new TL_inputClientProxy()); @@ -2996,7 +3010,7 @@ void ConnectionsManager::applyDnsConfig(NativeByteBuffer *buffer, std::string ph }); } -void ConnectionsManager::init(uint32_t version, int32_t layer, int32_t apiId, std::string deviceModel, std::string systemVersion, std::string appVersion, std::string langCode, std::string systemLangCode, std::string configPath, std::string logPath, int32_t userId, bool isPaused, bool enablePushConnection, bool hasNetwork, int32_t networkType) { +void ConnectionsManager::init(uint32_t version, int32_t layer, int32_t apiId, std::string deviceModel, std::string systemVersion, std::string appVersion, std::string langCode, std::string systemLangCode, std::string configPath, std::string logPath, std::string regId, int32_t userId, bool isPaused, bool enablePushConnection, bool hasNetwork, int32_t networkType) { currentVersion = version; currentLayer = layer; currentApiId = apiId; @@ -3005,6 +3019,7 @@ void ConnectionsManager::init(uint32_t version, int32_t layer, int32_t apiId, st currentSystemVersion = systemVersion; currentAppVersion = appVersion; currentLangCode = langCode; + currentRegId = regId; currentSystemLangCode = systemLangCode; currentUserId = userId; currentLogPath = logPath; @@ -3095,6 +3110,19 @@ void ConnectionsManager::setLangCode(std::string langCode) { }); } +void ConnectionsManager::setRegId(std::string regId) { + scheduleTask([&, regId] { + if (currentRegId.compare(regId) == 0) { + return; + } + currentRegId = regId; + for (std::map::iterator iter = datacenters.begin(); iter != datacenters.end(); iter++) { + iter->second->resetInitVersion(); + } + saveConfig(); + }); +} + void ConnectionsManager::setSystemLangCode(std::string langCode) { scheduleTask([&, langCode] { if (currentSystemLangCode.compare(langCode) == 0) { @@ -3184,51 +3212,55 @@ int64_t ConnectionsManager::checkProxy(std::string address, uint16_t port, std:: proxyCheckInfo->instanceNum = instanceNum; proxyCheckInfo->ptr1 = ptr1; - checkProxyInternal(proxyCheckInfo); + scheduleCheckProxyInternal(proxyCheckInfo); return proxyCheckInfo->pingId; } -void ConnectionsManager::checkProxyInternal(ProxyCheckInfo *proxyCheckInfo) { +void ConnectionsManager::scheduleCheckProxyInternal(ProxyCheckInfo *proxyCheckInfo) { scheduleTask([&, proxyCheckInfo] { - int32_t freeConnectionNum = -1; - if (proxyActiveChecks.size() != PROXY_CONNECTIONS_COUNT) { - for (int32_t a = 0; a < PROXY_CONNECTIONS_COUNT; a++) { - bool found = false; - for (std::vector>::iterator iter = proxyActiveChecks.begin(); iter != proxyActiveChecks.end(); iter++) { - if (iter->get()->connectionNum == a) { - found = true; - break; - } - } - if (!found) { - freeConnectionNum = a; + checkProxyInternal(proxyCheckInfo); + }); +} + +void ConnectionsManager::checkProxyInternal(ProxyCheckInfo *proxyCheckInfo) { + int32_t freeConnectionNum = -1; + if (proxyActiveChecks.size() != PROXY_CONNECTIONS_COUNT) { + for (int32_t a = 0; a < PROXY_CONNECTIONS_COUNT; a++) { + bool found = false; + for (std::vector>::iterator iter = proxyActiveChecks.begin(); iter != proxyActiveChecks.end(); iter++) { + if (iter->get()->connectionNum == a) { + found = true; break; } } - } - if (freeConnectionNum == -1) { - proxyCheckQueue.push_back(std::unique_ptr(proxyCheckInfo)); - } else { - ConnectionType connectionType = (ConnectionType) (ConnectionTypeProxy | (freeConnectionNum << 16)); - Datacenter *datacenter = getDatacenterWithId(DEFAULT_DATACENTER_ID); - Connection *connection = datacenter->getProxyConnection((uint8_t) freeConnectionNum, true, false); - if (connection != nullptr) { - connection->setOverrideProxy(proxyCheckInfo->address, proxyCheckInfo->port, proxyCheckInfo->username, proxyCheckInfo->password, proxyCheckInfo->secret); - connection->suspendConnection(); - proxyCheckInfo->connectionNum = freeConnectionNum; - TL_ping *request = new TL_ping(); - request->ping_id = proxyCheckInfo->pingId; - proxyCheckInfo->requestToken = sendRequest(request, nullptr, nullptr, RequestFlagEnableUnauthorized | RequestFlagWithoutLogin, DEFAULT_DATACENTER_ID, connectionType, true, 0); - proxyActiveChecks.push_back(std::unique_ptr(proxyCheckInfo)); - } else if (PFS_ENABLED) { - if (datacenter->isHandshaking(false)) { - datacenter->beginHandshake(HandshakeTypeTemp, false); - } - proxyCheckQueue.push_back(std::unique_ptr(proxyCheckInfo)); + if (!found) { + freeConnectionNum = a; + break; } } - }); + } + if (freeConnectionNum == -1) { + proxyCheckQueue.push_back(std::unique_ptr(proxyCheckInfo)); + } else { + ConnectionType connectionType = (ConnectionType) (ConnectionTypeProxy | (freeConnectionNum << 16)); + Datacenter *datacenter = getDatacenterWithId(DEFAULT_DATACENTER_ID); + Connection *connection = datacenter->getProxyConnection((uint8_t) freeConnectionNum, true, false); + if (connection != nullptr) { + connection->setOverrideProxy(proxyCheckInfo->address, proxyCheckInfo->port, proxyCheckInfo->username, proxyCheckInfo->password, proxyCheckInfo->secret); + connection->suspendConnection(); + proxyCheckInfo->connectionNum = freeConnectionNum; + TL_ping *request = new TL_ping(); + request->ping_id = proxyCheckInfo->pingId; + proxyCheckInfo->requestToken = sendRequest(request, nullptr, nullptr, RequestFlagEnableUnauthorized | RequestFlagWithoutLogin, DEFAULT_DATACENTER_ID, connectionType, true, 0); + proxyActiveChecks.push_back(std::unique_ptr(proxyCheckInfo)); + } else if (PFS_ENABLED) { + if (datacenter->isHandshaking(false)) { + datacenter->beginHandshake(HandshakeTypeTemp, false); + } + proxyCheckQueue.push_back(std::unique_ptr(proxyCheckInfo)); + } + } } #ifdef ANDROID diff --git a/TMessagesProj/jni/tgnet/ConnectionsManager.h b/TMessagesProj/jni/tgnet/ConnectionsManager.h index a5dee782b..14bf61829 100644 --- a/TMessagesProj/jni/tgnet/ConnectionsManager.h +++ b/TMessagesProj/jni/tgnet/ConnectionsManager.h @@ -15,7 +15,6 @@ #include #include #include -#include #include "Defines.h" #ifdef ANDROID @@ -63,9 +62,10 @@ public: void pauseNetwork(); void setNetworkAvailable(bool value, int32_t type, bool slow); void setUseIpv6(bool value); - void init(uint32_t version, int32_t layer, int32_t apiId, std::string deviceModel, std::string systemVersion, std::string appVersion, std::string langCode, std::string systemLangCode, std::string configPath, std::string logPath, int32_t userId, bool isPaused, bool enablePushConnection, bool hasNetwork, int32_t networkType); + void init(uint32_t version, int32_t layer, int32_t apiId, std::string deviceModel, std::string systemVersion, std::string appVersion, std::string langCode, std::string systemLangCode, std::string configPath, std::string logPath, std::string regId, int32_t userId, bool isPaused, bool enablePushConnection, bool hasNetwork, int32_t networkType); void setProxySettings(std::string address, uint16_t port, std::string username, std::string password, std::string secret); void setLangCode(std::string langCode); + void setRegId(std::string regId); void setSystemLangCode(std::string langCode); void updateDcSettings(uint32_t datacenterId, bool workaround); void setPushConnectionEnabled(bool value); @@ -125,6 +125,7 @@ private: bool isIpv6Enabled(); bool isNetworkAvailable(); + void scheduleCheckProxyInternal(ProxyCheckInfo *proxyCheckInfo); void checkProxyInternal(ProxyCheckInfo *proxyCheckInfo); int32_t instanceNum = 0; @@ -207,6 +208,7 @@ private: std::string currentSystemVersion; std::string currentAppVersion; std::string currentLangCode; + std::string currentRegId; std::string currentSystemLangCode; std::string currentConfigPath; std::string currentLogPath; diff --git a/TMessagesProj/jni/tgnet/Datacenter.cpp b/TMessagesProj/jni/tgnet/Datacenter.cpp index db203cc8d..b2cb31388 100644 --- a/TMessagesProj/jni/tgnet/Datacenter.cpp +++ b/TMessagesProj/jni/tgnet/Datacenter.cpp @@ -1228,6 +1228,10 @@ bool Datacenter::hasPermanentAuthKey() { return authKeyPerm != nullptr; } +int64_t Datacenter::getPermanentAuthKeyId() { + return authKeyPermId; +} + bool Datacenter::hasAuthKey(ConnectionType connectionType, int32_t allowPendingKey) { return getAuthKey(connectionType, false, nullptr, allowPendingKey) != nullptr; } diff --git a/TMessagesProj/jni/tgnet/Datacenter.h b/TMessagesProj/jni/tgnet/Datacenter.h index a5aa578aa..403552a93 100644 --- a/TMessagesProj/jni/tgnet/Datacenter.h +++ b/TMessagesProj/jni/tgnet/Datacenter.h @@ -12,7 +12,6 @@ #include #include #include -#include #include "Defines.h" class TL_future_salt; @@ -54,6 +53,7 @@ public: bool isHandshaking(HandshakeType type); bool hasAuthKey(ConnectionType connectionTyoe, int32_t allowPendingKey); bool hasPermanentAuthKey(); + int64_t getPermanentAuthKeyId(); bool isExportingAuthorization(); bool hasMediaAddress(); void resetInitVersion(); diff --git a/TMessagesProj/jni/tgnet/Defines.h b/TMessagesProj/jni/tgnet/Defines.h index 359dbcacb..24f2348ef 100644 --- a/TMessagesProj/jni/tgnet/Defines.h +++ b/TMessagesProj/jni/tgnet/Defines.h @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include "ByteArray.h" @@ -32,12 +31,6 @@ #define MAX_ACCOUNT_COUNT 3 #define USE_DELEGATE_HOST_RESOLVE -#define DOWNLOAD_CHUNK_SIZE 1024 * 32 -#define DOWNLOAD_CHUNK_BIG_SIZE 1024 * 128 -#define DOWNLOAD_MAX_REQUESTS 4 -#define DOWNLOAD_MAX_BIG_REQUESTS 4 -#define DOWNLOAD_BIG_FILE_MIN_SIZE 1024 * 1024 - #define NETWORK_TYPE_MOBILE 0 #define NETWORK_TYPE_WIFI 1 #define NETWORK_TYPE_ROAMING 2 diff --git a/TMessagesProj/jni/tgnet/MTProtoScheme.cpp b/TMessagesProj/jni/tgnet/MTProtoScheme.cpp index 332a4505b..eb270c3ac 100644 --- a/TMessagesProj/jni/tgnet/MTProtoScheme.cpp +++ b/TMessagesProj/jni/tgnet/MTProtoScheme.cpp @@ -947,6 +947,146 @@ void TL_inputClientProxy::serializeToStream(NativeByteBuffer *stream) { stream->writeInt32(port); } + +JSONValue *JSONValue::TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, int32_t instanceNum, bool &error) { + JSONValue *result = nullptr; + switch (constructor) { + case 0xc7345e6a: + result = new TL_jsonBool(); + break; + case 0x3f6d7b68: + result = new TL_jsonNull(); + break; + case 0xb71e767a: + result = new TL_jsonString(); + break; + case 0xf7444763: + result = new TL_jsonArray(); + break; + case 0x99c1d49d: + result = new TL_jsonObject(); + break; + case 0x2be0dfa4: + result = new TL_jsonNumber(); + break; + default: + error = true; + if (LOGS_ENABLED) DEBUG_E("can't parse magic %x in JSONValue", constructor); + return nullptr; + } + result->readParams(stream, instanceNum, error); + return result; +} + +TL_jsonObjectValue *TL_jsonObjectValue::TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, int32_t instanceNum, bool &error) { + if (TL_jsonObjectValue::constructor != constructor) { + error = true; + if (LOGS_ENABLED) DEBUG_E("can't parse magic %x in TL_jsonObjectValue", constructor); + return nullptr; + } + TL_jsonObjectValue *result = new TL_jsonObjectValue(); + result->readParams(stream, instanceNum, error); + return result; +} + + +void TL_jsonObjectValue::readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error) { + key = stream->readString(&error); + value = std::unique_ptr(JSONValue::TLdeserialize(stream, stream->readUint32(&error), instanceNum, error)); +} + + +void TL_jsonObjectValue::serializeToStream(NativeByteBuffer *stream) { + stream->writeInt32(constructor); + stream->writeString(key); + value->serializeToStream(stream); +} + +void TL_jsonBool::readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error) { + value = stream->readBool(&error); +} + +void TL_jsonBool::serializeToStream(NativeByteBuffer *stream) { + stream->writeInt32(constructor); + stream->writeBool(value); +} + +void TL_jsonNull::serializeToStream(NativeByteBuffer *stream) { + stream->writeInt32(constructor); +} + +void TL_jsonString::readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error) { + value = stream->readString(&error); +} + +void TL_jsonString::serializeToStream(NativeByteBuffer *stream) { + stream->writeInt32(constructor); + stream->writeString(value); +} + +void TL_jsonArray::readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error) { + int magic = stream->readInt32(&error); + if (magic != 0x1cb5c415) { + error = true; + if (LOGS_ENABLED) DEBUG_E("wrong Vector magic, got %x", magic); + return; + } + int count = stream->readInt32(&error); + for (int a = 0; a < count; a++) { + JSONValue *object = JSONValue::TLdeserialize(stream, stream->readUint32(&error), instanceNum, error); + if (object == nullptr) { + return; + } + value.push_back(std::unique_ptr(object)); + } +} + +void TL_jsonArray::serializeToStream(NativeByteBuffer *stream) { + stream->writeInt32(constructor); + stream->writeInt32(0x1cb5c415); + uint32_t count = (uint32_t) value.size(); + stream->writeInt32(count); + for (int a = 0; a < count; a++) { + value[a]->serializeToStream(stream); + } +} + +void TL_jsonObject::readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error) { + int magic = stream->readInt32(&error); + if (magic != 0x1cb5c415) { + error = true; + if (LOGS_ENABLED) DEBUG_E("wrong Vector magic, got %x", magic); + return; + } + int count = stream->readInt32(&error); + for (int a = 0; a < count; a++) { + TL_jsonObjectValue *object = TL_jsonObjectValue::TLdeserialize(stream, stream->readUint32(&error), instanceNum, error); + if (object == nullptr) { + return; + } + value.push_back(std::unique_ptr(object)); + } +} + +void TL_jsonObject::serializeToStream(NativeByteBuffer *stream) { + stream->writeInt32(constructor); + stream->writeInt32(0x1cb5c415); + uint32_t count = (uint32_t) value.size(); + stream->writeInt32(count); + for (int a = 0; a < count; a++) { + value[a]->serializeToStream(stream); + } +} + +void TL_jsonNumber::readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error) { + value = stream->readDouble(&error); +} + +void TL_jsonNumber::serializeToStream(NativeByteBuffer *stream) { + stream->writeInt32(constructor); + stream->writeDouble(value); +} + void initConnection::serializeToStream(NativeByteBuffer *stream) { stream->writeInt32(constructor); stream->writeInt32(flags); @@ -960,6 +1100,9 @@ void initConnection::serializeToStream(NativeByteBuffer *stream) { if ((flags & 1) != 0) { proxy->serializeToStream(stream); } + if ((flags & 2) != 0) { + params->serializeToStream(stream); + } query->serializeToStream(stream); } diff --git a/TMessagesProj/jni/tgnet/MTProtoScheme.h b/TMessagesProj/jni/tgnet/MTProtoScheme.h index c1611fedc..0b008349d 100644 --- a/TMessagesProj/jni/tgnet/MTProtoScheme.h +++ b/TMessagesProj/jni/tgnet/MTProtoScheme.h @@ -12,7 +12,6 @@ #include #include #include -#include #include "TLObject.h" class ByteArray; @@ -743,6 +742,88 @@ public: void serializeToStream(NativeByteBuffer *stream); }; +class JSONValue : public TLObject { + +public: + static JSONValue *TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, int32_t instanceNum, bool &error); +}; + +class TL_jsonObjectValue : public TLObject { + +public: + static const uint32_t constructor = 0xc0de1bd9; + + std::string key; + std::unique_ptr value; + + static TL_jsonObjectValue *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 TL_jsonBool : public JSONValue { + +public: + static const uint32_t constructor = 0xc7345e6a; + + bool value; + + void readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error); + void serializeToStream(NativeByteBuffer *stream); +}; + +class TL_jsonNull : public JSONValue { + +public: + static const uint32_t constructor = 0x3f6d7b68; + + void serializeToStream(NativeByteBuffer *stream); +}; + +class TL_jsonString : public JSONValue { + +public: + static const uint32_t constructor = 0xb71e767a; + + std::string value; + + void readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error); + void serializeToStream(NativeByteBuffer *stream); +}; + +class TL_jsonArray : public JSONValue { + +public: + static const uint32_t constructor = 0xf7444763; + + std::vector> value; + + void readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error); + void serializeToStream(NativeByteBuffer *stream); +}; + +class TL_jsonObject : public JSONValue { + +public: + static const uint32_t constructor = 0x99c1d49d; + + std::vector> value; + + void readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error); + void serializeToStream(NativeByteBuffer *stream); +}; + +class TL_jsonNumber : public JSONValue { + +public: + static const uint32_t constructor = 0x2be0dfa4; + + double value; + + void readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &error); + void serializeToStream(NativeByteBuffer *stream); +}; + class initConnection : public TLObject { public: @@ -757,6 +838,7 @@ public: std::string lang_pack; std::string lang_code; std::unique_ptr proxy; + std::unique_ptr params; std::unique_ptr query; void serializeToStream(NativeByteBuffer *stream); diff --git a/TMessagesProj/jni/tgnet/Request.h b/TMessagesProj/jni/tgnet/Request.h index 45f3dcf2f..ff5bcb1e2 100644 --- a/TMessagesProj/jni/tgnet/Request.h +++ b/TMessagesProj/jni/tgnet/Request.h @@ -11,7 +11,6 @@ #include #include -#include #include "Defines.h" #ifdef ANDROID diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AccountInstance.java b/TMessagesProj/src/main/java/org/telegram/messenger/AccountInstance.java index a863d7977..096b15530 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AccountInstance.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AccountInstance.java @@ -1,5 +1,7 @@ package org.telegram.messenger; +import android.content.SharedPreferences; + import org.telegram.tgnet.ConnectionsManager; public class AccountInstance { @@ -35,8 +37,8 @@ public class AccountInstance { return ContactsController.getInstance(currentAccount); } - public DataQuery getDataQuery() { - return DataQuery.getInstance(currentAccount); + public MediaDataController getMediaDataController() { + return MediaDataController.getInstance(currentAccount); } public ConnectionsManager getConnectionsManager() { @@ -51,7 +53,39 @@ public class AccountInstance { return NotificationCenter.getInstance(currentAccount); } + public LocationController getLocationController() { + return LocationController.getInstance(currentAccount); + } + public UserConfig getUserConfig() { return UserConfig.getInstance(currentAccount); } + + public DownloadController getDownloadController() { + return DownloadController.getInstance(currentAccount); + } + + public SendMessagesHelper getSendMessagesHelper() { + return SendMessagesHelper.getInstance(currentAccount); + } + + public SecretChatHelper getSecretChatHelper() { + return SecretChatHelper.getInstance(currentAccount); + } + + public StatsController getStatsController() { + return StatsController.getInstance(currentAccount); + } + + public FileLoader getFileLoader() { + return FileLoader.getInstance(currentAccount); + } + + public FileRefController getFileRefController() { + return FileRefController.getInstance(currentAccount); + } + + public SharedPreferences getNotificationsSettings() { + return MessagesController.getNotificationsSettings(currentAccount); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java index e666c1033..2df7d2f9f 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java @@ -2680,4 +2680,32 @@ public class AndroidUtilities { int aS = Color.alpha(color1); return Color.argb((int) ((aS + (aF - aS) * offset) * alpha), (int) (rS + (rF - rS) * offset), (int) (gS + (gF - gS) * offset), (int) (bS + (bF - bS) * offset)); } + + public static int indexOfIgnoreCase(final String origin, final String searchStr) { + if (searchStr.isEmpty() || origin.isEmpty()) { + return origin.indexOf(searchStr); + } + + for (int i = 0; i < origin.length(); i++) { + if (i + searchStr.length() > origin.length()) { + return -1; + } + int j = 0; + int ii = i; + while (ii < origin.length() && j < searchStr.length()) { + char c = Character.toLowerCase(origin.charAt(ii)); + char c2 = Character.toLowerCase(searchStr.charAt(j)); + if (c != c2) { + break; + } + j++; + ii++; + } + if (j == searchStr.length()) { + return i; + } + } + + return -1; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java index ce07aebd9..b1cd2b554 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java @@ -132,7 +132,11 @@ public class ApplicationLoader extends Application { for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { UserConfig.getInstance(a).loadConfig(); MessagesController.getInstance(a); - ConnectionsManager.getInstance(a); + if (a == 0) { + SharedConfig.pushStringStatus = "__FIREBASE_GENERATING_SINCE_" + ConnectionsManager.getInstance(a).getCurrentTime() + "__"; + } else { + ConnectionsManager.getInstance(a); + } TLRPC.User user = UserConfig.getInstance(a).getCurrentUser(); if (user != null) { MessagesController.getInstance(a).putUser(user, true); @@ -220,7 +224,7 @@ public class ApplicationLoader extends Application { if (checkPlayServices()) { final String currentPushString = SharedConfig.pushString; if (!TextUtils.isEmpty(currentPushString)) { - if (BuildVars.LOGS_ENABLED) { + if (BuildVars.DEBUG_PRIVATE_VERSION && BuildVars.LOGS_ENABLED) { FileLog.d("GCM regId = " + currentPushString); } } else { @@ -235,6 +239,12 @@ public class ApplicationLoader extends Application { if (!TextUtils.isEmpty(token)) { GcmPushListenerService.sendRegistrationToServer(token); } + }).addOnFailureListener(e -> { + if (BuildVars.LOGS_ENABLED) { + FileLog.d("Failed to get regid"); + } + SharedConfig.pushStringStatus = "__FIREBASE_FAILED__"; + GcmPushListenerService.sendRegistrationToServer(null); }); } catch (Throwable e) { FileLog.e(e); @@ -244,6 +254,8 @@ public class ApplicationLoader extends Application { if (BuildVars.LOGS_ENABLED) { FileLog.d("No valid Google Play Services APK found."); } + SharedConfig.pushStringStatus = "__NO_GOOGLE_PLAY_SERVICES__"; + GcmPushListenerService.sendRegistrationToServer(null); } }, 1000); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageHeardReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageHeardReceiver.java index c30e3f49f..ec5ae7759 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageHeardReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageHeardReceiver.java @@ -12,6 +12,8 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import org.telegram.tgnet.TLRPC; + public class AutoMessageHeardReceiver extends BroadcastReceiver { @Override @@ -23,6 +25,34 @@ public class AutoMessageHeardReceiver extends BroadcastReceiver { if (dialog_id == 0 || max_id == 0) { return; } + int lowerId = (int) dialog_id; + int highId = (int) (dialog_id >> 32); + AccountInstance accountInstance = AccountInstance.getInstance(currentAccount); + if (lowerId > 0) { + TLRPC.User user = accountInstance.getMessagesController().getUser(lowerId); + if (user == null) { + Utilities.globalQueue.postRunnable(() -> { + TLRPC.User user1 = accountInstance.getMessagesStorage().getUserSync(lowerId); + AndroidUtilities.runOnUIThread(() -> { + accountInstance.getMessagesController().putUser(user1, true); + MessagesController.getInstance(currentAccount).markDialogAsRead(dialog_id, max_id, max_id, 0, false, 0, true); + }); + }); + return; + } + } else if (lowerId < 0) { + TLRPC.Chat chat = accountInstance.getMessagesController().getChat(-lowerId); + if (chat == null) { + Utilities.globalQueue.postRunnable(() -> { + TLRPC.Chat chat1 = accountInstance.getMessagesStorage().getChatSync(-lowerId); + AndroidUtilities.runOnUIThread(() -> { + accountInstance.getMessagesController().putChat(chat1, true); + MessagesController.getInstance(currentAccount).markDialogAsRead(dialog_id, max_id, max_id, 0, false, 0, true); + }); + }); + return; + } + } MessagesController.getInstance(currentAccount).markDialogAsRead(dialog_id, max_id, max_id, 0, false, 0, true); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/BaseController.java b/TMessagesProj/src/main/java/org/telegram/messenger/BaseController.java new file mode 100644 index 000000000..8fa5deb38 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/BaseController.java @@ -0,0 +1,78 @@ +package org.telegram.messenger; + +import org.telegram.tgnet.ConnectionsManager; + +public class BaseController { + + protected int currentAccount; + private AccountInstance parentAccountInstance; + + public BaseController(int num) { + parentAccountInstance = AccountInstance.getInstance(num); + currentAccount = num; + } + + protected AccountInstance getAccountInstance() { + return parentAccountInstance; + } + + protected MessagesController getMessagesController() { + return parentAccountInstance.getMessagesController(); + } + + protected ContactsController getContactsController() { + return parentAccountInstance.getContactsController(); + } + + protected MediaDataController getMediaDataController() { + return parentAccountInstance.getMediaDataController(); + } + + protected ConnectionsManager getConnectionsManager() { + return parentAccountInstance.getConnectionsManager(); + } + + protected LocationController getLocationController() { + return parentAccountInstance.getLocationController(); + } + + protected NotificationsController getNotificationsController() { + return parentAccountInstance.getNotificationsController(); + } + + protected NotificationCenter getNotificationCenter() { + return parentAccountInstance.getNotificationCenter(); + } + + protected UserConfig getUserConfig() { + return parentAccountInstance.getUserConfig(); + } + + protected MessagesStorage getMessagesStorage() { + return parentAccountInstance.getMessagesStorage(); + } + + protected DownloadController getDownloadController() { + return parentAccountInstance.getDownloadController(); + } + + protected SendMessagesHelper getSendMessagesHelper() { + return parentAccountInstance.getSendMessagesHelper(); + } + + protected SecretChatHelper getSecretChatHelper() { + return parentAccountInstance.getSecretChatHelper(); + } + + protected StatsController getStatsController() { + return parentAccountInstance.getStatsController(); + } + + protected FileLoader getFileLoader() { + return parentAccountInstance.getFileLoader(); + } + + protected FileRefController getFileRefController() { + return parentAccountInstance.getFileRefController(); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java index 37f2589a8..f0714294b 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java @@ -18,8 +18,8 @@ public class BuildVars { public static boolean LOGS_ENABLED = false; public static boolean USE_CLOUD_STRINGS = true; public static boolean CHECK_UPDATES = false; - public static int BUILD_VERSION = 1608; - public static String BUILD_VERSION_STRING = "5.7.0"; + public static int BUILD_VERSION = 1648; + public static String BUILD_VERSION_STRING = "5.9.0"; public static int APP_ID = 0; //obtain your own APP_ID at https://core.telegram.org/api/obtaining_api_id public static String APP_HASH = ""; //obtain your own APP_HASH at https://core.telegram.org/api/obtaining_api_id public static String HOCKEY_APP_HASH = "your-hockeyapp-api-key-here"; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java index 370350dad..2d6ed43d9 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java @@ -36,7 +36,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; -public class ContactsController { +public class ContactsController extends BaseController { private Account systemAccount; private boolean loadingContacts; @@ -171,8 +171,7 @@ public class ContactsController { public HashMap contactsByShortPhone = new HashMap<>(); private int completedRequestsCount; - - private int currentAccount; + private static volatile ContactsController[] Instance = new ContactsController[UserConfig.MAX_ACCOUNT_COUNT]; public static ContactsController getInstance(int num) { ContactsController localInstance = Instance[num]; @@ -188,7 +187,7 @@ public class ContactsController { } public ContactsController(int instance) { - currentAccount = instance; + super(instance); SharedPreferences preferences = MessagesController.getMainSettings(currentAccount); if (preferences.getBoolean("needGetStatuses", false)) { reloadContactsStatuses(); @@ -276,7 +275,7 @@ public class ContactsController { if (!updatingInviteLink && (inviteLink == null || Math.abs(System.currentTimeMillis() / 1000 - time) >= 86400)) { updatingInviteLink = true; TLRPC.TL_help_getInviteText req = new TLRPC.TL_help_getInviteText(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { final TLRPC.TL_help_inviteText res = (TLRPC.TL_help_inviteText) response; if (res.message.length() != 0) { @@ -339,11 +338,11 @@ public class ContactsController { } catch (Throwable ignore) { } - if (UserConfig.getInstance(currentAccount).isClientActivated()) { + if (getUserConfig().isClientActivated()) { readContacts(); if (systemAccount == null) { try { - systemAccount = new Account("" + UserConfig.getInstance(currentAccount).getClientUserId(), "org.telegram.messenger"); + systemAccount = new Account("" + getUserConfig().getClientUserId(), "org.telegram.messenger"); am.addAccountExplicitly(systemAccount, "", null); } catch (Exception ignore) { @@ -416,10 +415,10 @@ public class ContactsController { TLRPC.TL_contacts_deleteContacts req = new TLRPC.TL_contacts_deleteContacts(); for (int a = 0, size = contacts.size(); a < size; a++) { TLRPC.TL_contact contact = contacts.get(a); - req.id.add(MessagesController.getInstance(currentAccount).getInputUser(contact.user_id)); + req.id.add(getMessagesController().getInputUser(contact.user_id)); } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { - if (response instanceof TLRPC.TL_boolTrue) { + getConnectionsManager().sendRequest(req, (response, error) -> { + if (error == null) { contactsBookSPhones.clear(); contactsBook.clear(); completedRequestsCount = 0; @@ -450,13 +449,13 @@ public class ContactsController { } try { - systemAccount = new Account("" + UserConfig.getInstance(currentAccount).getClientUserId(), "org.telegram.messenger"); + systemAccount = new Account("" + getUserConfig().getClientUserId(), "org.telegram.messenger"); am.addAccountExplicitly(systemAccount, "", null); } catch (Exception ignore) { } - MessagesStorage.getInstance(currentAccount).putCachedPhoneBook(new HashMap<>(), false, true); - MessagesStorage.getInstance(currentAccount).putContacts(new ArrayList<>(), true); + getMessagesStorage().putCachedPhoneBook(new HashMap<>(), false, true); + getMessagesStorage().putContacts(new ArrayList<>(), true); phoneBookContacts.clear(); contacts.clear(); contactsDict.clear(); @@ -469,17 +468,19 @@ public class ContactsController { sortedUsersMutualSectionsArray.clear(); contactsByPhone.clear(); contactsByShortPhone.clear(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.contactsDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.contactsDidLoad); loadContacts(false, 0); runnable.run(); }); + } else { + AndroidUtilities.runOnUIThread(runnable); } }); } public void resetImportedContacts() { TLRPC.TL_contacts_resetSaved req = new TLRPC.TL_contacts_resetSaved(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); } @@ -546,7 +547,7 @@ public class ContactsController { } private HashMap readContactsFromPhoneBook() { - if (!UserConfig.getInstance(currentAccount).syncContacts) { + if (!getUserConfig().syncContacts) { if (BuildVars.LOGS_ENABLED) { FileLog.d("contacts sync disabled"); } @@ -821,7 +822,7 @@ public class ContactsController { if (BuildVars.LOGS_ENABLED) { FileLog.d("migrated contacts " + migratedMap.size() + " of " + contactHashMap.size()); } - MessagesStorage.getInstance(currentAccount).putCachedPhoneBook(migratedMap, true, false); + getMessagesStorage().putCachedPhoneBook(migratedMap, true, false); }); } @@ -838,10 +839,10 @@ public class ContactsController { AccountManager am = AccountManager.get(ApplicationLoader.applicationContext); Account[] accounts = am.getAccountsByType("org.telegram.account"); boolean recreateAccount = false; - if (UserConfig.getInstance(currentAccount).isClientActivated()) { + if (getUserConfig().isClientActivated()) { if (accounts.length != 1) { FileLog.e("detected account deletion!"); - currentAccount = new Account(UserConfig.getInstance(currentAccount).getCurrentUser().phone, "org.telegram.account"); + currentAccount = new Account(getUserConfig().getCurrentUser().phone, "org.telegram.account"); am.addAccountExplicitly(currentAccount, "", null); AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -962,7 +963,7 @@ public class ContactsController { if (request) { TLRPC.TL_contact contact = contactsByPhone.get(sphone); if (contact != null) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(contact.user_id); + TLRPC.User user = getMessagesController().getUser(contact.user_id); if (user != null) { serverContactsInPhonebook++; if (TextUtils.isEmpty(user.first_name) && TextUtils.isEmpty(user.last_name) && (!TextUtils.isEmpty(value.first_name) || !TextUtils.isEmpty(value.last_name))) { @@ -979,7 +980,7 @@ public class ContactsController { if (!emptyNameReimport) { TLRPC.TL_contact contact = contactsByPhone.get(sphone); if (contact != null) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(contact.user_id); + TLRPC.User user = getMessagesController().getUser(contact.user_id); if (user != null) { serverContactsInPhonebook++; String firstName = user.first_name != null ? user.first_name : ""; @@ -1024,7 +1025,7 @@ public class ContactsController { } if (request && !contactHashMap.isEmpty() && !contactsMap.isEmpty()) { if (toImport.isEmpty()) { - MessagesStorage.getInstance(currentAccount).putCachedPhoneBook(contactsMap, false, false); + getMessagesStorage().putCachedPhoneBook(contactsMap, false, false); } if (!disableDeletion && !contactHashMap.isEmpty()) { AndroidUtilities.runOnUIThread(() -> { @@ -1046,7 +1047,7 @@ public class ContactsController { for (int a = 0; a < contacts.size(); a++) { TLRPC.TL_contact value = contacts.get(a); - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(value.user_id); + TLRPC.User user = getMessagesController().getUser(value.user_id); if (user == null || TextUtils.isEmpty(user.phone)) { continue; } @@ -1091,7 +1092,7 @@ public class ContactsController { String sphone9 = sphone.substring(Math.max(0, sphone.length() - 7)); TLRPC.TL_contact contact = contactsByPhone.get(sphone); if (contact != null) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(contact.user_id); + TLRPC.User user = getMessagesController().getUser(contact.user_id); if (user != null) { serverContactsInPhonebook++; String firstName = user.first_name != null ? user.first_name : ""; @@ -1144,7 +1145,7 @@ public class ContactsController { FileLog.d("new phone book contacts " + newPhonebookContacts + " serverContactsInPhonebook " + serverContactsInPhonebook + " totalContacts " + contactsByPhone.size()); } if (checkType != 0) { - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.hasNewContactsToImport, checkType, contactHashMap, first, schedule)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.hasNewContactsToImport, checkType, contactHashMap, first, schedule)); return; } else if (canceled) { Utilities.stageQueue.postRunnable(() -> { @@ -1159,12 +1160,12 @@ public class ContactsController { applyContactsUpdates(delayedContactsUpdate, null, null, null); delayedContactsUpdate.clear(); } - MessagesStorage.getInstance(currentAccount).putCachedPhoneBook(contactsMap, false, false); + getMessagesStorage().putCachedPhoneBook(contactsMap, false, false); AndroidUtilities.runOnUIThread(() -> { mergePhonebookAndTelegramContacts(phoneBookSectionsDictFinal, phoneBookSectionsArrayFinal, phoneBookByShortPhonesFinal); updateUnregisteredContacts(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.contactsDidLoad); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.contactsImported); + getNotificationCenter().postNotificationName(NotificationCenter.contactsDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.contactsImported); }); }); return; @@ -1184,7 +1185,7 @@ public class ContactsController { int start = a * 500; int end = Math.min(start + 500, toImport.size()); req.contacts = new ArrayList<>(toImport.subList(start, end)); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { completedRequestsCount++; if (error == null) { if (BuildVars.LOGS_ENABLED) { @@ -1214,7 +1215,7 @@ public class ContactsController { FileLog.e("received user " + user.first_name + " " + user.last_name + " " + user.phone); } }*/ - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, null, true, true); + getMessagesStorage().putUsersAndChats(res.users, null, true, true); ArrayList cArr = new ArrayList<>(); for (int a1 = 0; a1 < res.imported.size(); a1++) { TLRPC.TL_contact contact = new TLRPC.TL_contact(); @@ -1234,7 +1235,7 @@ public class ContactsController { } if (completedRequestsCount == count) { if (!contactsMapToSave.isEmpty()) { - MessagesStorage.getInstance(currentAccount).putCachedPhoneBook(contactsMapToSave, false, false); + getMessagesStorage().putCachedPhoneBook(contactsMapToSave, false, false); } Utilities.stageQueue.postRunnable(() -> { contactsBookSPhones = contactsBookShort; @@ -1250,10 +1251,10 @@ public class ContactsController { } AndroidUtilities.runOnUIThread(() -> { mergePhonebookAndTelegramContacts(phoneBookSectionsDictFinal, phoneBookSectionsArrayFinal, phoneBookByShortPhonesFinal); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.contactsImported); + getNotificationCenter().postNotificationName(NotificationCenter.contactsImported); }); if (hasErrors[0]) { - Utilities.globalQueue.postRunnable(() -> MessagesStorage.getInstance(currentAccount).getCachedPhoneBook(true), 60000 * 5); + Utilities.globalQueue.postRunnable(() -> getMessagesStorage().getCachedPhoneBook(true), 60000 * 5); } }); } @@ -1275,8 +1276,8 @@ public class ContactsController { AndroidUtilities.runOnUIThread(() -> { mergePhonebookAndTelegramContacts(phoneBookSectionsDictFinal, phoneBookSectionsArrayFinal, phoneBookByShortPhonesFinal); updateUnregisteredContacts(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.contactsDidLoad); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.contactsImported); + getNotificationCenter().postNotificationName(NotificationCenter.contactsDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.contactsImported); }); }); } @@ -1296,7 +1297,7 @@ public class ContactsController { AndroidUtilities.runOnUIThread(() -> mergePhonebookAndTelegramContacts(phoneBookSectionsDictFinal, phoneBookSectionsArrayFinal, phoneBookByShortPhonesFinal)); }); if (!contactsMap.isEmpty()) { - MessagesStorage.getInstance(currentAccount).putCachedPhoneBook(contactsMap, false, false); + getMessagesStorage().putCachedPhoneBook(contactsMap, false, false); } } }); @@ -1322,7 +1323,7 @@ public class ContactsController { int count = contacts.size(); for (int a = -1; a < count; a++) { if (a == -1) { - acc = ((acc * 20261) + 0x80000000L + UserConfig.getInstance(currentAccount).contactsSavedCount) % 0x80000000L; + acc = ((acc * 20261) + 0x80000000L + getUserConfig().contactsSavedCount) % 0x80000000L; } else { TLRPC.TL_contact set = contacts.get(a); acc = ((acc * 20261) + 0x80000000L + set.user_id) % 0x80000000L; @@ -1339,7 +1340,7 @@ public class ContactsController { if (BuildVars.LOGS_ENABLED) { FileLog.d("load contacts from cache"); } - MessagesStorage.getInstance(currentAccount).getContacts(); + getMessagesStorage().getContacts(); } else { if (BuildVars.LOGS_ENABLED) { FileLog.d("load contacts from server"); @@ -1347,7 +1348,7 @@ public class ContactsController { TLRPC.TL_contacts_getContacts req = new TLRPC.TL_contacts_getContacts(); req.hash = hash; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.contacts_Contacts res = (TLRPC.contacts_Contacts) response; if (hash != 0 && res instanceof TLRPC.TL_contacts_contactsNotModified) { @@ -1356,21 +1357,21 @@ public class ContactsController { applyContactsUpdates(delayedContactsUpdate, null, null, null); delayedContactsUpdate.clear(); } - UserConfig.getInstance(currentAccount).lastContactsSyncTime = (int) (System.currentTimeMillis() / 1000); - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().lastContactsSyncTime = (int) (System.currentTimeMillis() / 1000); + getUserConfig().saveConfig(false); AndroidUtilities.runOnUIThread(() -> { synchronized (loadContactsSync) { loadingContacts = false; } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.contactsDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.contactsDidLoad); }); if (BuildVars.LOGS_ENABLED) { FileLog.d("load contacts don't change"); } return; } else { - UserConfig.getInstance(currentAccount).contactsSavedCount = res.saved_count; - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().contactsSavedCount = res.saved_count; + getUserConfig().saveConfig(false); } processLoadedContacts(res.contacts, res.users, 0); } @@ -1381,7 +1382,7 @@ public class ContactsController { public void processLoadedContacts(final ArrayList contactsArr, final ArrayList usersArr, final int from) { //from: 0 - from server, 1 - from db, 2 - from imported contacts AndroidUtilities.runOnUIThread(() -> { - MessagesController.getInstance(currentAccount).putUsers(usersArr, from == 1); + getMessagesController().putUsers(usersArr, from == 1); final SparseArray usersDict = new SparseArray<>(); @@ -1399,7 +1400,7 @@ public class ContactsController { } for (int a = 0; a < contactsArr.size(); a++) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(contactsArr.get(a).user_id); + TLRPC.User user = getMessagesController().getUser(contactsArr.get(a).user_id); if (user != null) { usersDict.put(user.id, user); //if (BuildVars.DEBUG_VERSION) { @@ -1412,20 +1413,20 @@ public class ContactsController { if (BuildVars.LOGS_ENABLED) { FileLog.d("done loading contacts"); } - if (from == 1 && (contactsArr.isEmpty() || Math.abs(System.currentTimeMillis() / 1000 - UserConfig.getInstance(currentAccount).lastContactsSyncTime) >= 24 * 60 * 60)) { + if (from == 1 && (contactsArr.isEmpty() || Math.abs(System.currentTimeMillis() / 1000 - getUserConfig().lastContactsSyncTime) >= 24 * 60 * 60)) { loadContacts(false, getContactsHash(contactsArr)); if (contactsArr.isEmpty()) { return; } } if (from == 0) { - UserConfig.getInstance(currentAccount).lastContactsSyncTime = (int) (System.currentTimeMillis() / 1000); - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().lastContactsSyncTime = (int) (System.currentTimeMillis() / 1000); + getUserConfig().saveConfig(false); } for (int a = 0; a < contactsArr.size(); a++) { TLRPC.TL_contact contact = contactsArr.get(a); - if (usersDict.get(contact.user_id) == null && contact.user_id != UserConfig.getInstance(currentAccount).getClientUserId()) { + if (usersDict.get(contact.user_id) == null && contact.user_id != getUserConfig().getClientUserId()) { loadContacts(false, 0); if (BuildVars.LOGS_ENABLED) { FileLog.d("contacts are broken, load from server"); @@ -1435,8 +1436,8 @@ public class ContactsController { } if (from != 1) { - MessagesStorage.getInstance(currentAccount).putUsersAndChats(usersArr, null, true, true); - MessagesStorage.getInstance(currentAccount).putContacts(contactsArr, from != 2); + getMessagesStorage().putUsersAndChats(usersArr, null, true, true); + getMessagesStorage().putContacts(contactsArr, from != 2); } Collections.sort(contactsArr, (tl_contact, tl_contact2) -> { @@ -1543,7 +1544,7 @@ public class ContactsController { performWriteContactsToPhoneBook(); updateUnregisteredContacts(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.contactsDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.contactsDidLoad); if (from != 1 && !isEmpty) { saveContactsLoadTime(); @@ -1567,7 +1568,7 @@ public class ContactsController { return; } contactsSyncInProgress = true; - MessagesStorage.getInstance(currentAccount).getCachedPhoneBook(false); + getMessagesStorage().getCachedPhoneBook(false); }); } else { contactsLoaded = true; @@ -1606,7 +1607,7 @@ public class ContactsController { Utilities.globalQueue.postRunnable(() -> { for (int a = 0, size = contactsCopy.size(); a < size; a++) { TLRPC.TL_contact value = contactsCopy.get(a); - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(value.user_id); + TLRPC.User user = getMessagesController().getUser(value.user_id); if (user == null || TextUtils.isEmpty(user.phone)) { continue; } @@ -1684,7 +1685,7 @@ public class ContactsController { for (int a = 0, size = contacts.size(); a < size; a++) { TLRPC.TL_contact value = contacts.get(a); - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(value.user_id); + TLRPC.User user = getMessagesController().getUser(value.user_id); if (user == null || TextUtils.isEmpty(user.phone)) { continue; } @@ -1727,8 +1728,8 @@ public class ContactsController { private void buildContactsSectionsArrays(boolean sort) { if (sort) { Collections.sort(contacts, (tl_contact, tl_contact2) -> { - TLRPC.User user1 = MessagesController.getInstance(currentAccount).getUser(tl_contact.user_id); - TLRPC.User user2 = MessagesController.getInstance(currentAccount).getUser(tl_contact2.user_id); + TLRPC.User user1 = getMessagesController().getUser(tl_contact.user_id); + TLRPC.User user2 = getMessagesController().getUser(tl_contact2.user_id); String name1 = UserObject.getFirstName(user1); String name2 = UserObject.getFirstName(user2); return name1.compareTo(name2); @@ -1740,7 +1741,7 @@ public class ContactsController { for (int a = 0; a < contacts.size(); a++) { TLRPC.TL_contact value = contacts.get(a); - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(value.user_id); + TLRPC.User user = getMessagesController().getUser(value.user_id); if (user == null) { continue; } @@ -1826,7 +1827,7 @@ public class ContactsController { for (int a = 0; a < contactsArray.size(); a++) { TLRPC.TL_contact u = contactsArray.get(a); if (bookContacts.indexOfKey(u.user_id) < 0) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(u.user_id); + TLRPC.User user = getMessagesController().getUser(u.user_id); addContactToPhoneBook(user, false); } } @@ -1875,9 +1876,9 @@ public class ContactsController { user = userDict.get(newContact.user_id); } if (user == null) { - user = MessagesController.getInstance(currentAccount).getUser(newContact.user_id); + user = getMessagesController().getUser(newContact.user_id); } else { - MessagesController.getInstance(currentAccount).putUser(user, true); + getMessagesController().putUser(user, true); } if (user == null || TextUtils.isEmpty(user.phone)) { reloadContacts = true; @@ -1906,9 +1907,9 @@ public class ContactsController { user = userDict.get(uid); } if (user == null) { - user = MessagesController.getInstance(currentAccount).getUser(uid); + user = getMessagesController().getUser(uid); } else { - MessagesController.getInstance(currentAccount).putUser(user, true); + getMessagesController().putUser(user, true); } if (user == null) { reloadContacts = true; @@ -1931,7 +1932,7 @@ public class ContactsController { } if (toAdd.length() != 0 || toDelete.length() != 0) { - MessagesStorage.getInstance(currentAccount).applyPhoneBookUpdates(toAdd.toString(), toDelete.toString()); + getMessagesStorage().applyPhoneBookUpdates(toAdd.toString(), toDelete.toString()); } if (reloadContacts) { @@ -1961,7 +1962,7 @@ public class ContactsController { } performSyncPhoneBook(getContactsCopy(contactsBook), false, false, false, false, true, false); buildContactsSectionsArrays(!newContacts.isEmpty()); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.contactsDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.contactsDidLoad); }); } } @@ -1991,10 +1992,10 @@ public class ContactsController { } } if (!contactsToDelete.isEmpty()) { - MessagesStorage.getInstance(currentAccount).deleteContacts(contactsToDelete); + getMessagesStorage().deleteContacts(contactsToDelete); } if (!newContacts.isEmpty()) { - MessagesStorage.getInstance(currentAccount).putContacts(newContacts, false); + getMessagesStorage().putContacts(newContacts, false); } if (!contactsLoaded || !contactsBookLoaded) { delayedContactsUpdate.addAll(ids); @@ -2007,7 +2008,7 @@ public class ContactsController { } public long addContactToPhoneBook(TLRPC.User user, boolean check) { - if (systemAccount == null || user == null || TextUtils.isEmpty(user.phone)) { + if (systemAccount == null || user == null) { return -1; } if (!hasContactsPermission()) { @@ -2032,7 +2033,7 @@ public class ContactsController { ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI); builder.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, systemAccount.name); builder.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, systemAccount.type); - builder.withValue(ContactsContract.RawContacts.SYNC1, user.phone); + builder.withValue(ContactsContract.RawContacts.SYNC1, TextUtils.isEmpty(user.phone) ? "" : user.phone); builder.withValue(ContactsContract.RawContacts.SYNC2, user.id); query.add(builder.build()); @@ -2055,7 +2056,7 @@ public class ContactsController { builder.withValue(ContactsContract.Data.MIMETYPE, "vnd.android.cursor.item/vnd.org.telegram.messenger.android.profile"); builder.withValue(ContactsContract.Data.DATA1, user.id); builder.withValue(ContactsContract.Data.DATA2, "Telegram Profile"); - builder.withValue(ContactsContract.Data.DATA3, "+" + user.phone); + builder.withValue(ContactsContract.Data.DATA3, TextUtils.isEmpty(user.phone) ? ContactsController.formatName(user.first_name, user.last_name) : "+" + user.phone); builder.withValue(ContactsContract.Data.DATA4, user.id); query.add(builder.build()); try { @@ -2104,32 +2105,28 @@ public class ContactsController { }); } - public void addContact(TLRPC.User user) { - if (user == null || TextUtils.isEmpty(user.phone)) { + public void addContact(TLRPC.User user, boolean exception) { + if (user == null) { return; } - TLRPC.TL_contacts_importContacts req = new TLRPC.TL_contacts_importContacts(); - ArrayList contactsParams = new ArrayList<>(); - TLRPC.TL_inputPhoneContact c = new TLRPC.TL_inputPhoneContact(); - c.phone = user.phone; - if (!c.phone.startsWith("+")) { - c.phone = "+" + c.phone; + TLRPC.TL_contacts_addContact req = new TLRPC.TL_contacts_addContact(); + req.id = getMessagesController().getInputUser(user); + req.first_name = user.first_name; + req.last_name = user.last_name; + req.phone = user.phone; + req.add_phone_privacy_exception = exception; + if (req.phone == null) { + req.phone = ""; + } else if (req.phone.length() > 0 && !req.phone.startsWith("+")) { + req.phone = "+" + req.phone; } - c.first_name = user.first_name; - c.last_name = user.last_name; - c.client_id = 0; - contactsParams.add(c); - req.contacts = contactsParams; - /*if (BuildVars.DEBUG_VERSION) { - FileLog.e("add contact " + user.first_name + " " + user.last_name + " " + user.phone); - }*/ - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null) { return; } - final TLRPC.TL_contacts_importedContacts res = (TLRPC.TL_contacts_importedContacts) response; - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, null, true, true); + final TLRPC.Updates res = (TLRPC.Updates) response; + getMessagesController().processUpdates(res, false); /*if (BuildVars.DEBUG_VERSION) { for (TLRPC.User user : res.users) { @@ -2139,16 +2136,19 @@ public class ContactsController { for (int a = 0; a < res.users.size(); a++) { final TLRPC.User u = res.users.get(a); + if (u.id != user.id) { + continue; + } Utilities.phoneBookQueue.postRunnable(() -> addContactToPhoneBook(u, true)); TLRPC.TL_contact newContact = new TLRPC.TL_contact(); newContact.user_id = u.id; ArrayList arrayList = new ArrayList<>(); arrayList.add(newContact); - MessagesStorage.getInstance(currentAccount).putContacts(arrayList, false); + getMessagesStorage().putContacts(arrayList, false); if (!TextUtils.isEmpty(u.phone)) { CharSequence name = formatName(u.first_name, u.last_name); - MessagesStorage.getInstance(currentAccount).applyPhoneBookUpdates(u.phone, ""); + getMessagesStorage().applyPhoneBookUpdates(u.phone, ""); Contact contact = contactsBookSPhones.get(u.phone); if (contact != null) { int index = contact.shortPhones.indexOf(u.phone); @@ -2160,17 +2160,18 @@ public class ContactsController { } AndroidUtilities.runOnUIThread(() -> { - for (TLRPC.User u : res.users) { - MessagesController.getInstance(currentAccount).putUser(u, false); - if (contactsDict.get(u.id) == null) { - TLRPC.TL_contact newContact = new TLRPC.TL_contact(); - newContact.user_id = u.id; - contacts.add(newContact); - contactsDict.put(newContact.user_id, newContact); + for (int a = 0; a < res.users.size(); a++) { + TLRPC.User u = res.users.get(a); + if (!u.contact || contactsDict.get(u.id) != null) { + continue; } + TLRPC.TL_contact newContact = new TLRPC.TL_contact(); + newContact.user_id = u.id; + contacts.add(newContact); + contactsDict.put(newContact.user_id, newContact); } buildContactsSectionsArrays(true); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.contactsDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.contactsDidLoad); }); }, ConnectionsManager.RequestFlagFailOnServerErrors | ConnectionsManager.RequestFlagCanCompress); } @@ -2182,18 +2183,20 @@ public class ContactsController { TLRPC.TL_contacts_deleteContacts req = new TLRPC.TL_contacts_deleteContacts(); final ArrayList uids = new ArrayList<>(); for (TLRPC.User user : users) { - TLRPC.InputUser inputUser = MessagesController.getInstance(currentAccount).getInputUser(user); + TLRPC.InputUser inputUser = getMessagesController().getInputUser(user); if (inputUser == null) { continue; } + user.contact = false; uids.add(user.id); req.id.add(inputUser); } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null) { return; } - MessagesStorage.getInstance(currentAccount).deleteContacts(uids); + getMessagesController().processUpdates((TLRPC.Updates) response, false); + getMessagesStorage().deleteContacts(uids); Utilities.phoneBookQueue.postRunnable(() -> { for (TLRPC.User user : users) { deleteContactFromPhoneBook(user.id); @@ -2205,8 +2208,7 @@ public class ContactsController { if (TextUtils.isEmpty(user.phone)) { continue; } - CharSequence name = UserObject.getUserName(user); - MessagesStorage.getInstance(currentAccount).applyPhoneBookUpdates(user.phone, ""); + getMessagesStorage().applyPhoneBookUpdates(user.phone, ""); Contact contact = contactsBookSPhones.get(user.phone); if (contact != null) { int index = contact.shortPhones.indexOf(user.phone); @@ -2229,20 +2231,20 @@ public class ContactsController { if (remove) { buildContactsSectionsArrays(false); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, MessagesController.UPDATE_MASK_NAME); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.contactsDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, MessagesController.UPDATE_MASK_NAME); + getNotificationCenter().postNotificationName(NotificationCenter.contactsDidLoad); }); }); } public void reloadContactsStatuses() { saveContactsLoadTime(); - MessagesController.getInstance(currentAccount).clearFullUsers(); + getMessagesController().clearFullUsers(); SharedPreferences preferences = MessagesController.getMainSettings(currentAccount); final SharedPreferences.Editor editor = preferences.edit(); editor.putBoolean("needGetStatuses", true).commit(); TLRPC.TL_contacts_getStatuses req = new TLRPC.TL_contacts_getStatuses(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { AndroidUtilities.runOnUIThread(() -> { editor.remove("needGetStatuses").commit(); @@ -2264,16 +2266,16 @@ public class ContactsController { status.status.expires = -102; } - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(status.user_id); + TLRPC.User user = getMessagesController().getUser(status.user_id); if (user != null) { user.status = status.status; } toDbUser.status = status.status; dbUsersStatus.add(toDbUser); } - MessagesStorage.getInstance(currentAccount).updateUsers(dbUsersStatus, true, true, true); + getMessagesStorage().updateUsers(dbUsersStatus, true, true, true); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, MessagesController.UPDATE_MASK_STATUS); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, MessagesController.UPDATE_MASK_STATUS); }); } }); @@ -2283,7 +2285,7 @@ public class ContactsController { if (loadingDeleteInfo == 0) { loadingDeleteInfo = 1; TLRPC.TL_account_getAccountTTL req = new TLRPC.TL_account_getAccountTTL(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (error == null) { TLRPC.TL_accountDaysTTL ttl = (TLRPC.TL_accountDaysTTL) response; deleteAccountTTL = ttl.days; @@ -2291,7 +2293,7 @@ public class ContactsController { } else { loadingDeleteInfo = 0; } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.privacyRulesUpdated); + getNotificationCenter().postNotificationName(NotificationCenter.privacyRulesUpdated); })); } for (int a = 0; a < loadingPrivacyInfo.length; a++) { @@ -2328,11 +2330,11 @@ public class ContactsController { break; } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (error == null) { TLRPC.TL_account_privacyRules rules = (TLRPC.TL_account_privacyRules) response; - MessagesController.getInstance(currentAccount).putUsers(rules.users, false); - MessagesController.getInstance(currentAccount).putChats(rules.chats, false); + getMessagesController().putUsers(rules.users, false); + getMessagesController().putChats(rules.chats, false); switch (num) { case PRIVACY_RULES_TYPE_LASTSEEN: @@ -2363,10 +2365,10 @@ public class ContactsController { } else { loadingPrivacyInfo[num] = 0; } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.privacyRulesUpdated); + getNotificationCenter().postNotificationName(NotificationCenter.privacyRulesUpdated); })); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.privacyRulesUpdated); + getNotificationCenter().postNotificationName(NotificationCenter.privacyRulesUpdated); } public void setDeleteAccountTTL(int ttl) { @@ -2429,7 +2431,7 @@ public class ContactsController { phonePrivacyRules = rules; break; } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.privacyRulesUpdated); + getNotificationCenter().postNotificationName(NotificationCenter.privacyRulesUpdated); reloadContactsStatuses(); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java b/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java index eaf690ff6..d6d415f9a 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java @@ -17,14 +17,13 @@ import android.net.ConnectivityManager; import android.util.LongSparseArray; import android.util.SparseArray; -import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; -public class DownloadController implements NotificationCenter.NotificationCenterDelegate { +public class DownloadController extends BaseController implements NotificationCenter.NotificationCenterDelegate { public interface FileDownloadProgressListener { void onFailedDownload(String fileName, boolean canceled); @@ -189,8 +188,7 @@ public class DownloadController implements NotificationCenter.NotificationCenter public int currentMobilePreset; public int currentWifiPreset; public int currentRoamingPreset; - - private int currentAccount; + private static volatile DownloadController[] Instance = new DownloadController[UserConfig.MAX_ACCOUNT_COUNT]; public static DownloadController getInstance(int num) { @@ -207,13 +205,13 @@ public class DownloadController implements NotificationCenter.NotificationCenter } public DownloadController(int instance) { - currentAccount = instance; + super(instance); SharedPreferences preferences = MessagesController.getMainSettings(currentAccount); lowPreset = new Preset(preferences.getString("preset0", "1_1_1_1_1048576_512000_512000_524288_0_0_1_1")); mediumPreset = new Preset(preferences.getString("preset1", "13_13_13_13_1048576_10485760_1048576_524288_1_1_1_0")); highPreset = new Preset(preferences.getString("preset2", "13_13_13_13_1048576_15728640_3145728_524288_1_1_1_0")); boolean newConfig; - if (newConfig = preferences.contains("newConfig") || !UserConfig.getInstance(currentAccount).isClientActivated()) { + if (newConfig = preferences.contains("newConfig") || !getUserConfig().isClientActivated()) { mobilePreset = new Preset(preferences.getString("mobilePreset", mediumPreset.toString())); wifiPreset = new Preset(preferences.getString("wifiPreset", highPreset.toString())); roamingPreset = new Preset(preferences.getString("roamingPreset", lowPreset.toString())); @@ -268,12 +266,12 @@ public class DownloadController implements NotificationCenter.NotificationCenter } AndroidUtilities.runOnUIThread(() -> { - NotificationCenter.getInstance(currentAccount).addObserver(DownloadController.this, NotificationCenter.fileDidFailedLoad); - NotificationCenter.getInstance(currentAccount).addObserver(DownloadController.this, NotificationCenter.fileDidLoad); - NotificationCenter.getInstance(currentAccount).addObserver(DownloadController.this, NotificationCenter.FileLoadProgressChanged); - NotificationCenter.getInstance(currentAccount).addObserver(DownloadController.this, NotificationCenter.FileUploadProgressChanged); - NotificationCenter.getInstance(currentAccount).addObserver(DownloadController.this, NotificationCenter.httpFileDidLoad); - NotificationCenter.getInstance(currentAccount).addObserver(DownloadController.this, NotificationCenter.httpFileDidFailedLoad); + getNotificationCenter().addObserver(DownloadController.this, NotificationCenter.fileDidFailedLoad); + getNotificationCenter().addObserver(DownloadController.this, NotificationCenter.fileDidLoad); + getNotificationCenter().addObserver(DownloadController.this, NotificationCenter.FileLoadProgressChanged); + getNotificationCenter().addObserver(DownloadController.this, NotificationCenter.FileUploadProgressChanged); + getNotificationCenter().addObserver(DownloadController.this, NotificationCenter.httpFileDidLoad); + getNotificationCenter().addObserver(DownloadController.this, NotificationCenter.httpFileDidFailedLoad); loadAutoDownloadConfig(false); }); @@ -286,21 +284,21 @@ public class DownloadController implements NotificationCenter.NotificationCenter IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); ApplicationLoader.applicationContext.registerReceiver(networkStateReceiver, filter); - if (UserConfig.getInstance(currentAccount).isClientActivated()) { + if (getUserConfig().isClientActivated()) { checkAutodownloadSettings(); } } public void loadAutoDownloadConfig(boolean force) { - if (loadingAutoDownloadConfig || !force && Math.abs(System.currentTimeMillis() - UserConfig.getInstance(currentAccount).autoDownloadConfigLoadTime) < 24 * 60 * 60 * 1000) { + if (loadingAutoDownloadConfig || !force && Math.abs(System.currentTimeMillis() - getUserConfig().autoDownloadConfigLoadTime) < 24 * 60 * 60 * 1000) { return; } loadingAutoDownloadConfig = true; TLRPC.TL_account_getAutoDownloadSettings req = new TLRPC.TL_account_getAutoDownloadSettings(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { loadingAutoDownloadConfig = false; - UserConfig.getInstance(currentAccount).autoDownloadConfigLoadTime = System.currentTimeMillis(); - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().autoDownloadConfigLoadTime = System.currentTimeMillis(); + getUserConfig().saveConfig(false); if (response != null) { TLRPC.TL_account_autoDownloadSettings res = (TLRPC.TL_account_autoDownloadSettings) response; lowPreset.set(res.low); @@ -474,9 +472,9 @@ public class DownloadController implements NotificationCenter.NotificationCenter if (downloadObject.object instanceof TLRPC.Photo) { TLRPC.Photo photo = (TLRPC.Photo) downloadObject.object; TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, AndroidUtilities.getPhotoSize()); - FileLoader.getInstance(currentAccount).cancelLoadFile(photoSize); + getFileLoader().cancelLoadFile(photoSize); } else if (downloadObject.object instanceof TLRPC.Document) { - FileLoader.getInstance(currentAccount).cancelLoadFile((TLRPC.Document) downloadObject.object); + getFileLoader().cancelLoadFile((TLRPC.Document) downloadObject.object); } } photoDownloadQueue.clear(); @@ -488,7 +486,7 @@ public class DownloadController implements NotificationCenter.NotificationCenter } else { for (int a = 0; a < audioDownloadQueue.size(); a++) { DownloadObject downloadObject = audioDownloadQueue.get(a); - FileLoader.getInstance(currentAccount).cancelLoadFile((TLRPC.Document) downloadObject.object); + getFileLoader().cancelLoadFile((TLRPC.Document) downloadObject.object); } audioDownloadQueue.clear(); } @@ -500,7 +498,7 @@ public class DownloadController implements NotificationCenter.NotificationCenter for (int a = 0; a < documentDownloadQueue.size(); a++) { DownloadObject downloadObject = documentDownloadQueue.get(a); TLRPC.Document document = (TLRPC.Document) downloadObject.object; - FileLoader.getInstance(currentAccount).cancelLoadFile(document); + getFileLoader().cancelLoadFile(document); } documentDownloadQueue.clear(); } @@ -511,25 +509,25 @@ public class DownloadController implements NotificationCenter.NotificationCenter } else { for (int a = 0; a < videoDownloadQueue.size(); a++) { DownloadObject downloadObject = videoDownloadQueue.get(a); - FileLoader.getInstance(currentAccount).cancelLoadFile((TLRPC.Document) downloadObject.object); + getFileLoader().cancelLoadFile((TLRPC.Document) downloadObject.object); } videoDownloadQueue.clear(); } int mask = getAutodownloadMaskAll(); if (mask == 0) { - MessagesStorage.getInstance(currentAccount).clearDownloadQueue(0); + getMessagesStorage().clearDownloadQueue(0); } else { if ((mask & AUTODOWNLOAD_TYPE_PHOTO) == 0) { - MessagesStorage.getInstance(currentAccount).clearDownloadQueue(AUTODOWNLOAD_TYPE_PHOTO); + getMessagesStorage().clearDownloadQueue(AUTODOWNLOAD_TYPE_PHOTO); } if ((mask & AUTODOWNLOAD_TYPE_AUDIO) == 0) { - MessagesStorage.getInstance(currentAccount).clearDownloadQueue(AUTODOWNLOAD_TYPE_AUDIO); + getMessagesStorage().clearDownloadQueue(AUTODOWNLOAD_TYPE_AUDIO); } if ((mask & AUTODOWNLOAD_TYPE_VIDEO) == 0) { - MessagesStorage.getInstance(currentAccount).clearDownloadQueue(AUTODOWNLOAD_TYPE_VIDEO); + getMessagesStorage().clearDownloadQueue(AUTODOWNLOAD_TYPE_VIDEO); } if ((mask & AUTODOWNLOAD_TYPE_DOCUMENT) == 0) { - MessagesStorage.getInstance(currentAccount).clearDownloadQueue(AUTODOWNLOAD_TYPE_DOCUMENT); + getMessagesStorage().clearDownloadQueue(AUTODOWNLOAD_TYPE_DOCUMENT); } } } @@ -572,7 +570,7 @@ public class DownloadController implements NotificationCenter.NotificationCenter type = AUTODOWNLOAD_TYPE_VIDEO; } else if (MessageObject.isVoiceMessage(message)) { type = AUTODOWNLOAD_TYPE_AUDIO; - } else if (MessageObject.isPhoto(message) || MessageObject.isStickerMessage(message)) { + } else if (MessageObject.isPhoto(message) || MessageObject.isStickerMessage(message) || MessageObject.isAnimatedStickerMessage(message)) { type = AUTODOWNLOAD_TYPE_PHOTO; } else if (MessageObject.getDocument(message) != null) { type = AUTODOWNLOAD_TYPE_DOCUMENT; @@ -583,20 +581,20 @@ public class DownloadController implements NotificationCenter.NotificationCenter TLRPC.Peer peer = message.to_id; if (peer != null) { if (peer.user_id != 0) { - if (ContactsController.getInstance(currentAccount).contactsDict.containsKey(peer.user_id)) { + if (getContactsController().contactsDict.containsKey(peer.user_id)) { index = 0; } else { index = 1; } } else if (peer.chat_id != 0) { - if (message.from_id != 0 && ContactsController.getInstance(currentAccount).contactsDict.containsKey(message.from_id)) { + if (message.from_id != 0 && getContactsController().contactsDict.containsKey(message.from_id)) { index = 0; } else { index = 2; } } else { if (MessageObject.isMegagroup(message)) { - if (message.from_id != 0 && ContactsController.getInstance(currentAccount).contactsDict.containsKey(message.from_id)) { + if (message.from_id != 0 && getContactsController().contactsDict.containsKey(message.from_id)) { index = 0; } else { index = 2; @@ -716,7 +714,7 @@ public class DownloadController implements NotificationCenter.NotificationCenter req.settings.photo_size_max = photo ? preset.sizes[PRESET_SIZE_NUM_PHOTO] : 0; req.settings.video_size_max = video ? preset.sizes[PRESET_SIZE_NUM_VIDEO] : 0; req.settings.file_size_max = document ? preset.sizes[PRESET_SIZE_NUM_DOCUMENT] : 0; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); } @@ -763,10 +761,10 @@ public class DownloadController implements NotificationCenter.NotificationCenter } else { cacheType = 0; } - FileLoader.getInstance(currentAccount).loadFile(ImageLocation.getForPhoto(photoSize, photo), downloadObject.parent, null, 0, cacheType); + getFileLoader().loadFile(ImageLocation.getForPhoto(photoSize, photo), downloadObject.parent, null, 0, cacheType); } else if (downloadObject.object instanceof TLRPC.Document) { TLRPC.Document document = (TLRPC.Document) downloadObject.object; - FileLoader.getInstance(currentAccount).loadFile(document, downloadObject.parent, 0, downloadObject.secret ? 2 : 0); + getFileLoader().loadFile(document, downloadObject.parent, 0, downloadObject.secret ? 2 : 0); } else { added = false; } @@ -780,16 +778,16 @@ public class DownloadController implements NotificationCenter.NotificationCenter protected void newDownloadObjectsAvailable(int downloadMask) { int mask = getCurrentDownloadMask(); if ((mask & AUTODOWNLOAD_TYPE_PHOTO) != 0 && (downloadMask & AUTODOWNLOAD_TYPE_PHOTO) != 0 && photoDownloadQueue.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getDownloadQueue(AUTODOWNLOAD_TYPE_PHOTO); + getMessagesStorage().getDownloadQueue(AUTODOWNLOAD_TYPE_PHOTO); } if ((mask & AUTODOWNLOAD_TYPE_AUDIO) != 0 && (downloadMask & AUTODOWNLOAD_TYPE_AUDIO) != 0 && audioDownloadQueue.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getDownloadQueue(AUTODOWNLOAD_TYPE_AUDIO); + getMessagesStorage().getDownloadQueue(AUTODOWNLOAD_TYPE_AUDIO); } if ((mask & AUTODOWNLOAD_TYPE_VIDEO) != 0 && (downloadMask & AUTODOWNLOAD_TYPE_VIDEO) != 0 && videoDownloadQueue.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getDownloadQueue(AUTODOWNLOAD_TYPE_VIDEO); + getMessagesStorage().getDownloadQueue(AUTODOWNLOAD_TYPE_VIDEO); } if ((mask & AUTODOWNLOAD_TYPE_DOCUMENT) != 0 && (downloadMask & AUTODOWNLOAD_TYPE_DOCUMENT) != 0 && documentDownloadQueue.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getDownloadQueue(AUTODOWNLOAD_TYPE_DOCUMENT); + getMessagesStorage().getDownloadQueue(AUTODOWNLOAD_TYPE_DOCUMENT); } } @@ -798,7 +796,7 @@ public class DownloadController implements NotificationCenter.NotificationCenter if (downloadObject != null) { downloadQueueKeys.remove(fileName); if (state == 0 || state == 2) { - MessagesStorage.getInstance(currentAccount).removeFromDownloadQueue(downloadObject.id, downloadObject.type, false /*state != 0*/); + getMessagesStorage().removeFromDownloadQueue(downloadObject.id, downloadObject.type, false /*state != 0*/); } if (downloadObject.type == AUTODOWNLOAD_TYPE_PHOTO) { photoDownloadQueue.remove(downloadObject); @@ -973,7 +971,7 @@ public class DownloadController implements NotificationCenter.NotificationCenter listenerInProgress = false; processLaterArrays(); try { - ArrayList delayedMessages = SendMessagesHelper.getInstance(currentAccount).getDelayedMessages(fileName); + ArrayList delayedMessages = getSendMessagesHelper().getDelayedMessages(fileName); if (delayedMessages != null) { for (int a = 0; a < delayedMessages.size(); a++) { SendMessagesHelper.DelayedMessage delayedMessage = delayedMessages.get(a); @@ -984,9 +982,9 @@ public class DownloadController implements NotificationCenter.NotificationCenter if (lastTime == null || lastTime + 4000 < System.currentTimeMillis()) { MessageObject messageObject = (MessageObject) delayedMessage.extraHashMap.get(fileName + "_i"); if (messageObject != null && messageObject.isVideo()) { - MessagesController.getInstance(currentAccount).sendTyping(dialog_id, 5, 0); + getMessagesController().sendTyping(dialog_id, 5, 0); } else { - MessagesController.getInstance(currentAccount).sendTyping(dialog_id, 4, 0); + getMessagesController().sendTyping(dialog_id, 4, 0); } typingTimes.put(dialog_id, System.currentTimeMillis()); } @@ -995,15 +993,15 @@ public class DownloadController implements NotificationCenter.NotificationCenter TLRPC.Document document = delayedMessage.obj.getDocument(); if (lastTime == null || lastTime + 4000 < System.currentTimeMillis()) { if (delayedMessage.obj.isRoundVideo()) { - MessagesController.getInstance(currentAccount).sendTyping(dialog_id, 8, 0); + getMessagesController().sendTyping(dialog_id, 8, 0); } else if (delayedMessage.obj.isVideo()) { - MessagesController.getInstance(currentAccount).sendTyping(dialog_id, 5, 0); + getMessagesController().sendTyping(dialog_id, 5, 0); } else if (delayedMessage.obj.isVoice()) { - MessagesController.getInstance(currentAccount).sendTyping(dialog_id, 9, 0); + getMessagesController().sendTyping(dialog_id, 9, 0); } else if (delayedMessage.obj.getDocument() != null) { - MessagesController.getInstance(currentAccount).sendTyping(dialog_id, 3, 0); + getMessagesController().sendTyping(dialog_id, 3, 0); } else if (delayedMessage.photoSize != null) { - MessagesController.getInstance(currentAccount).sendTyping(dialog_id, 4, 0); + getMessagesController().sendTyping(dialog_id, 4, 0); } typingTimes.put(dialog_id, System.currentTimeMillis()); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java index 11453affb..00f02af1c 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java @@ -147,6 +147,7 @@ public class FileLoadOperation { private ArrayList delayedRequestInfos; private File cacheFileTemp; + private File cacheFileGzipTemp; private File cacheFileFinal; private File cacheIvTemp; private File cacheFileParts; @@ -226,6 +227,7 @@ public class FileLoadOperation { } allowDisordererFileSave = true; } + ungzip = imageLocation.lottieAnimation; initialDatacenterId = datacenterId = imageLocation.dc_id; currentType = ConnectionsManager.FileTypePhoto; totalBytesCount = size; @@ -683,6 +685,9 @@ public class FileLoadOperation { if (!finalFileExist) { cacheFileTemp = new File(tempPath, fileNameTemp); + if (ungzip) { + cacheFileGzipTemp = new File(tempPath, fileNameTemp + ".gz"); + } boolean newKeyGenerated = false; if (encryptFile) { @@ -1067,14 +1072,18 @@ public class FileLoadOperation { if (ungzip) { try { GZIPInputStream gzipInputStream = new GZIPInputStream(new FileInputStream(cacheFileTemp)); - FileLoader.copyFile(gzipInputStream, cacheFileFinal); + FileLoader.copyFile(gzipInputStream, cacheFileGzipTemp, 1024 * 1024 * 2); gzipInputStream.close(); cacheFileTemp.delete(); + cacheFileTemp = cacheFileGzipTemp; + ungzip = false; } catch (ZipException zipException) { ungzip = false; } catch (Throwable e) { FileLog.e(e); - FileLog.e("unable to ungzip temp = " + cacheFileTemp + " to final = " + cacheFileFinal); + if (BuildVars.LOGS_ENABLED) { + FileLog.e("unable to ungzip temp = " + cacheFileTemp + " to final = " + cacheFileFinal); + } } } if (!ungzip) { @@ -1097,6 +1106,9 @@ public class FileLoadOperation { } cacheFileFinal = cacheFileTemp; } + } else { + onFail(false, 0); + return; } } if (BuildVars.LOGS_ENABLED) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java index 5e7a5836a..8a8c16bba 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java @@ -19,14 +19,13 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; -public class FileLoader { +public class FileLoader extends BaseController { public interface FileLoaderDelegate { void fileUploadProgressChanged(String location, float progress, boolean isEncrypted); @@ -72,7 +71,6 @@ public class FileLoader { private int lastReferenceId; private ConcurrentHashMap parentObjectReferences = new ConcurrentHashMap<>(); - private int currentAccount; private static volatile FileLoader[] Instance = new FileLoader[UserConfig.MAX_ACCOUNT_COUNT]; public static FileLoader getInstance(int num) { FileLoader localInstance = Instance[num]; @@ -88,7 +86,7 @@ public class FileLoader { } public FileLoader(int instance) { - currentAccount = instance; + super(instance); } public static void setMediaDirs(SparseArray dirs) { @@ -128,7 +126,7 @@ public class FileLoader { String key = getAttachFileName(document); String dKey = key + (player ? "p" : ""); loadingVideos.put(dKey, true); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.videoLoadingStateChanged, key); + getNotificationCenter().postNotificationName(NotificationCenter.videoLoadingStateChanged, key); } public void setLoadingVideo(TLRPC.Document document, boolean player, boolean schedule) { @@ -156,7 +154,7 @@ public class FileLoader { String key = getAttachFileName(document); String dKey = key + (player ? "p" : ""); if (loadingVideos.remove(dKey) != null) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.videoLoadingStateChanged, key); + getNotificationCenter().postNotificationName(NotificationCenter.videoLoadingStateChanged, key); } } @@ -1222,13 +1220,23 @@ public class FileLoader { } public static boolean copyFile(InputStream sourceFile, File destFile) throws IOException { - OutputStream out = new FileOutputStream(destFile); + return copyFile(sourceFile, destFile, -1); + } + + public static boolean copyFile(InputStream sourceFile, File destFile, int maxSize) throws IOException { + FileOutputStream out = new FileOutputStream(destFile); byte[] buf = new byte[4096]; int len; + int totalLen = 0; while ((len = sourceFile.read(buf)) > 0) { Thread.yield(); out.write(buf, 0, len); + totalLen += len; + if (maxSize > 0 && totalLen >= maxSize) { + break; + } } + out.getFD().sync(); out.close(); return true; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileRefController.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileRefController.java index 6ef1f9a71..50451fa6e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileRefController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileRefController.java @@ -2,7 +2,6 @@ package org.telegram.messenger; import android.os.SystemClock; -import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; @@ -10,7 +9,7 @@ import org.telegram.tgnet.TLRPC; import java.util.ArrayList; import java.util.HashMap; -public class FileRefController { +public class FileRefController extends BaseController { private class Requester { private TLRPC.InputFileLocation location; @@ -32,8 +31,7 @@ public class FileRefController { private long lastCleanupTime = SystemClock.uptimeMillis(); - private int currentAccount; - private static volatile FileRefController Instance[] = new FileRefController[UserConfig.MAX_ACCOUNT_COUNT]; + private static volatile FileRefController[] Instance = new FileRefController[UserConfig.MAX_ACCOUNT_COUNT]; public static FileRefController getInstance(int num) { FileRefController localInstance = Instance[num]; @@ -49,7 +47,7 @@ public class FileRefController { } public FileRefController(int instance) { - currentAccount = instance; + super(instance); } public static String getKeyForParentObject(Object parentObject) { @@ -267,13 +265,13 @@ public class FileRefController { int channelId = messageObject.getChannelId(); if (channelId != 0) { TLRPC.TL_channels_getMessages req = new TLRPC.TL_channels_getMessages(); - req.channel = MessagesController.getInstance(currentAccount).getInputChannel(channelId); + req.channel = getMessagesController().getInputChannel(channelId); req.id.add(messageObject.getRealId()); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } else { TLRPC.TL_messages_getMessages req = new TLRPC.TL_messages_getMessages(); req.id.add(messageObject.getRealId()); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } } else if (parentObject instanceof TLRPC.TL_wallPaper) { TLRPC.TL_wallPaper wallPaper = (TLRPC.TL_wallPaper) parentObject; @@ -282,43 +280,43 @@ public class FileRefController { inputWallPaper.id = wallPaper.id; inputWallPaper.access_hash = wallPaper.access_hash; req.wallpaper = inputWallPaper; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } else if (parentObject instanceof TLRPC.WebPage) { TLRPC.WebPage webPage = (TLRPC.WebPage) parentObject; TLRPC.TL_messages_getWebPage req = new TLRPC.TL_messages_getWebPage(); req.url = webPage.url; req.hash = 0; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } else if (parentObject instanceof TLRPC.User) { TLRPC.User user = (TLRPC.User) parentObject; TLRPC.TL_users_getUsers req = new TLRPC.TL_users_getUsers(); - req.id.add(MessagesController.getInstance(currentAccount).getInputUser(user)); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + req.id.add(getMessagesController().getInputUser(user)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } else if (parentObject instanceof TLRPC.Chat) { TLRPC.Chat chat = (TLRPC.Chat) parentObject; if (chat instanceof TLRPC.TL_chat) { TLRPC.TL_messages_getChats req = new TLRPC.TL_messages_getChats(); req.id.add(chat.id); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } else if (chat instanceof TLRPC.TL_channel) { TLRPC.TL_channels_getChannels req = new TLRPC.TL_channels_getChannels(); req.id.add(MessagesController.getInputChannel(chat)); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } } else if (parentObject instanceof String) { String string = (String) parentObject; if ("wallpaper".equals(string)) { TLRPC.TL_account_getWallPapers req = new TLRPC.TL_account_getWallPapers(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } else if (string.startsWith("gif")) { TLRPC.TL_messages_getSavedGifs req = new TLRPC.TL_messages_getSavedGifs(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } else if ("recent".equals(string)) { TLRPC.TL_messages_getRecentStickers req = new TLRPC.TL_messages_getRecentStickers(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } else if ("fav".equals(string)) { TLRPC.TL_messages_getFavedStickers req = new TLRPC.TL_messages_getFavedStickers(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } else if (string.startsWith("avatar_")) { int id = Utilities.parseInt(string); if (id > 0) { @@ -326,16 +324,16 @@ public class FileRefController { req.limit = 80; req.offset = 0; req.max_id = 0; - req.user_id = MessagesController.getInstance(currentAccount).getInputUser(id); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + req.user_id = getMessagesController().getInputUser(id); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } else { TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); req.filter = new TLRPC.TL_inputMessagesFilterChatPhotos(); req.limit = 80; req.offset_id = 0; req.q = ""; - req.peer = MessagesController.getInstance(currentAccount).getInputPeer(id); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + req.peer = getMessagesController().getInputPeer(id); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } } else if (string.startsWith("sent_")) { String[] params = string.split("_"); @@ -343,13 +341,13 @@ public class FileRefController { int channelId = Utilities.parseInt(params[1]); if (channelId != 0) { TLRPC.TL_channels_getMessages req = new TLRPC.TL_channels_getMessages(); - req.channel = MessagesController.getInstance(currentAccount).getInputChannel(channelId); + req.channel = getMessagesController().getInputChannel(channelId); req.id.add(Utilities.parseInt(params[2])); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, false)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, false)); } else { TLRPC.TL_messages_getMessages req = new TLRPC.TL_messages_getMessages(); req.id.add(Utilities.parseInt(params[2])); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, false)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, false)); } } else { sendErrorToObject(args, 0); @@ -363,18 +361,18 @@ public class FileRefController { req.stickerset = new TLRPC.TL_inputStickerSetID(); req.stickerset.id = stickerSet.set.id; req.stickerset.access_hash = stickerSet.set.access_hash; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } else if (parentObject instanceof TLRPC.StickerSetCovered) { TLRPC.StickerSetCovered stickerSet = (TLRPC.StickerSetCovered) parentObject; TLRPC.TL_messages_getStickerSet req = new TLRPC.TL_messages_getStickerSet(); req.stickerset = new TLRPC.TL_inputStickerSetID(); req.stickerset.id = stickerSet.set.id; req.stickerset.access_hash = stickerSet.set.access_hash; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } else if (parentObject instanceof TLRPC.InputStickerSet) { TLRPC.TL_messages_getStickerSet req = new TLRPC.TL_messages_getStickerSet(); req.stickerset = (TLRPC.InputStickerSet) parentObject; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); + getConnectionsManager().sendRequest(req, (response, error) -> onRequestComplete(locationKey, parentKey, response, true)); } else { sendErrorToObject(args, 0); } @@ -422,7 +420,7 @@ public class FileRefController { } if (done) { multiMediaCache.remove(multiMedia); - SendMessagesHelper.getInstance(currentAccount).performSendMessageRequestMulti(multiMedia, (ArrayList) objects[1], (ArrayList) objects[2], null, (SendMessagesHelper.DelayedMessage) objects[4]); + getSendMessagesHelper().performSendMessageRequestMulti(multiMedia, (ArrayList) objects[1], (ArrayList) objects[2], null, (SendMessagesHelper.DelayedMessage) objects[4]); } } else if (requester.args[0] instanceof TLRPC.TL_messages_sendMedia) { TLRPC.TL_messages_sendMedia req = (TLRPC.TL_messages_sendMedia) requester.args[0]; @@ -433,7 +431,7 @@ public class FileRefController { TLRPC.TL_inputMediaPhoto mediaPhoto = (TLRPC.TL_inputMediaPhoto) req.media; mediaPhoto.id.file_reference = file_reference; } - SendMessagesHelper.getInstance(currentAccount).performSendMessageRequest((TLObject) requester.args[0], (MessageObject) requester.args[1], (String) requester.args[2], (SendMessagesHelper.DelayedMessage) requester.args[3], (Boolean) requester.args[4], (SendMessagesHelper.DelayedMessage) requester.args[5], null); + getSendMessagesHelper().performSendMessageRequest((TLObject) requester.args[0], (MessageObject) requester.args[1], (String) requester.args[2], (SendMessagesHelper.DelayedMessage) requester.args[3], (Boolean) requester.args[4], (SendMessagesHelper.DelayedMessage) requester.args[5], null); } else if (requester.args[0] instanceof TLRPC.TL_messages_editMessage) { TLRPC.TL_messages_editMessage req = (TLRPC.TL_messages_editMessage) requester.args[0]; if (req.media instanceof TLRPC.TL_inputMediaDocument) { @@ -443,23 +441,23 @@ public class FileRefController { TLRPC.TL_inputMediaPhoto mediaPhoto = (TLRPC.TL_inputMediaPhoto) req.media; mediaPhoto.id.file_reference = file_reference; } - SendMessagesHelper.getInstance(currentAccount).performSendMessageRequest((TLObject) requester.args[0], (MessageObject) requester.args[1], (String) requester.args[2], (SendMessagesHelper.DelayedMessage) requester.args[3], (Boolean) requester.args[4], (SendMessagesHelper.DelayedMessage) requester.args[5], null); + getSendMessagesHelper().performSendMessageRequest((TLObject) requester.args[0], (MessageObject) requester.args[1], (String) requester.args[2], (SendMessagesHelper.DelayedMessage) requester.args[3], (Boolean) requester.args[4], (SendMessagesHelper.DelayedMessage) requester.args[5], null); } else if (requester.args[0] instanceof TLRPC.TL_messages_saveGif) { TLRPC.TL_messages_saveGif req = (TLRPC.TL_messages_saveGif) requester.args[0]; req.id.file_reference = file_reference; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); } else if (requester.args[0] instanceof TLRPC.TL_messages_saveRecentSticker) { TLRPC.TL_messages_saveRecentSticker req = (TLRPC.TL_messages_saveRecentSticker) requester.args[0]; req.id.file_reference = file_reference; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); } else if (requester.args[0] instanceof TLRPC.TL_messages_faveSticker) { TLRPC.TL_messages_faveSticker req = (TLRPC.TL_messages_faveSticker) requester.args[0]; req.id.file_reference = file_reference; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); } else if (requester.args[0] instanceof TLRPC.TL_messages_getAttachedStickers) { @@ -471,7 +469,7 @@ public class FileRefController { TLRPC.TL_inputStickeredMediaPhoto mediaPhoto = (TLRPC.TL_inputStickeredMediaPhoto) req.media; mediaPhoto.id.file_reference = file_reference; } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (RequestDelegate) requester.args[1]); + getConnectionsManager().sendRequest(req, (RequestDelegate) requester.args[1]); } else if (requester.args[1] instanceof FileLoadOperation) { FileLoadOperation fileLoadOperation = (FileLoadOperation) requester.args[1]; if (locationReplacement != null) { @@ -491,10 +489,10 @@ public class FileRefController { Object[] objects = multiMediaCache.get(req); if (objects != null) { multiMediaCache.remove(req); - SendMessagesHelper.getInstance(currentAccount).performSendMessageRequestMulti(req, (ArrayList) objects[1], (ArrayList) objects[2], null, (SendMessagesHelper.DelayedMessage) objects[4]); + getSendMessagesHelper().performSendMessageRequestMulti(req, (ArrayList) objects[1], (ArrayList) objects[2], null, (SendMessagesHelper.DelayedMessage) objects[4]); } } else if (args[0] instanceof TLRPC.TL_messages_sendMedia || args[0] instanceof TLRPC.TL_messages_editMessage) { - SendMessagesHelper.getInstance(currentAccount).performSendMessageRequest((TLObject) args[0], (MessageObject) args[1], (String) args[2], (SendMessagesHelper.DelayedMessage) args[3], (Boolean) args[4], (SendMessagesHelper.DelayedMessage) args[5], null); + getSendMessagesHelper().performSendMessageRequest((TLObject) args[0], (MessageObject) args[1], (String) args[2], (SendMessagesHelper.DelayedMessage) args[3], (Boolean) args[4], (SendMessagesHelper.DelayedMessage) args[5], null); } else if (args[0] instanceof TLRPC.TL_messages_saveGif) { TLRPC.TL_messages_saveGif req = (TLRPC.TL_messages_saveGif) args[0]; //do nothing @@ -506,7 +504,7 @@ public class FileRefController { //do nothing } else if (args[0] instanceof TLRPC.TL_messages_getAttachedStickers) { TLRPC.TL_messages_getAttachedStickers req = (TLRPC.TL_messages_getAttachedStickers) args[0]; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (RequestDelegate) args[1]); + getConnectionsManager().sendRequest(req, (RequestDelegate) args[1]); } else { if (reason == 0) { TLRPC.TL_error error = new TLRPC.TL_error(); @@ -547,9 +545,9 @@ public class FileRefController { parentRequester.remove(parentKey); } } - byte result[] = null; + byte[] result = null; TLRPC.InputFileLocation[] locationReplacement = null; - boolean needReplacement[] = null; + boolean[] needReplacement = null; ArrayList arrayList = locationRequester.get(locationKey); if (arrayList == null) { return found; @@ -598,13 +596,13 @@ public class FileRefController { } } } - MessagesStorage.getInstance(currentAccount).replaceMessageIfExists(message, currentAccount, res.users, res.chats, false); + getMessagesStorage().replaceMessageIfExists(message, currentAccount, res.users, res.chats, false); } break; } } if (result == null) { - MessagesStorage.getInstance(currentAccount).replaceMessageIfExists(res.messages.get(0), currentAccount, res.users, res.chats,true); + getMessagesStorage().replaceMessageIfExists(res.messages.get(0), currentAccount, res.users, res.chats,true); if (BuildVars.DEBUG_VERSION) { FileLog.d("file ref not found in messages, replacing message"); } @@ -621,7 +619,7 @@ public class FileRefController { } } if (result != null && cache) { - MessagesStorage.getInstance(currentAccount).putWallpapers(accountWallPapers.wallpapers, 1); + getMessagesStorage().putWallpapers(accountWallPapers.wallpapers, 1); } } else if (response instanceof TLRPC.TL_wallPaper) { TLRPC.TL_wallPaper wallPaper = (TLRPC.TL_wallPaper) response; @@ -629,7 +627,7 @@ public class FileRefController { if (result != null && cache) { ArrayList wallpapers = new ArrayList<>(); wallpapers.add(wallPaper); - MessagesStorage.getInstance(currentAccount).putWallpapers(wallpapers, 0); + getMessagesStorage().putWallpapers(wallpapers, 0); } } else if (response instanceof TLRPC.Vector) { TLRPC.Vector vector = (TLRPC.Vector) response; @@ -642,8 +640,8 @@ public class FileRefController { if (cache && result != null) { ArrayList arrayList1 = new ArrayList<>(); arrayList1.add(user); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(arrayList1, null, true, true); - AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).putUser(user, false)); + getMessagesStorage().putUsersAndChats(arrayList1, null, true, true); + AndroidUtilities.runOnUIThread(() -> getMessagesController().putUser(user, false)); } } else if (object instanceof TLRPC.Chat) { TLRPC.Chat chat = (TLRPC.Chat) object; @@ -651,8 +649,8 @@ public class FileRefController { if (cache && result != null) { ArrayList arrayList1 = new ArrayList<>(); arrayList1.add(chat); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(null, arrayList1, true, true); - AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).putChat(chat, false)); + getMessagesStorage().putUsersAndChats(null, arrayList1, true, true); + AndroidUtilities.runOnUIThread(() -> getMessagesController().putChat(chat, false)); } } if (result != null) { @@ -670,8 +668,8 @@ public class FileRefController { if (cache) { ArrayList arrayList1 = new ArrayList<>(); arrayList1.add(chat); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(null, arrayList1, true, true); - AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).putChat(chat, false)); + getMessagesStorage().putUsersAndChats(null, arrayList1, true, true); + AndroidUtilities.runOnUIThread(() -> getMessagesController().putChat(chat, false)); } break; } @@ -686,7 +684,7 @@ public class FileRefController { } } if (cache) { - DataQuery.getInstance(currentAccount).processLoadedRecentDocuments(DataQuery.TYPE_IMAGE, savedGifs.gifs, true, 0, true); + getMediaDataController().processLoadedRecentDocuments(MediaDataController.TYPE_IMAGE, savedGifs.gifs, true, 0, true); } } else if (response instanceof TLRPC.TL_messages_stickerSet) { TLRPC.TL_messages_stickerSet stickerSet = (TLRPC.TL_messages_stickerSet) response; @@ -699,7 +697,7 @@ public class FileRefController { } } if (cache) { - AndroidUtilities.runOnUIThread(() -> DataQuery.getInstance(currentAccount).replaceStickerSet(stickerSet)); + AndroidUtilities.runOnUIThread(() -> getMediaDataController().replaceStickerSet(stickerSet)); } } else if (response instanceof TLRPC.TL_messages_recentStickers) { TLRPC.TL_messages_recentStickers recentStickers = (TLRPC.TL_messages_recentStickers) response; @@ -710,7 +708,7 @@ public class FileRefController { } } if (cache) { - DataQuery.getInstance(currentAccount).processLoadedRecentDocuments(DataQuery.TYPE_IMAGE, recentStickers.stickers, false, 0, true); + getMediaDataController().processLoadedRecentDocuments(MediaDataController.TYPE_IMAGE, recentStickers.stickers, false, 0, true); } } else if (response instanceof TLRPC.TL_messages_favedStickers) { TLRPC.TL_messages_favedStickers favedStickers = (TLRPC.TL_messages_favedStickers) response; @@ -721,7 +719,7 @@ public class FileRefController { } } if (cache) { - DataQuery.getInstance(currentAccount).processLoadedRecentDocuments(DataQuery.TYPE_FAVE, favedStickers.stickers, false, 0, true); + getMediaDataController().processLoadedRecentDocuments(MediaDataController.TYPE_FAVE, favedStickers.stickers, false, 0, true); } } else if (response instanceof TLRPC.photos_Photos) { TLRPC.photos_Photos res = (TLRPC.photos_Photos) response; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/GcmPushListenerService.java b/TMessagesProj/src/main/java/org/telegram/messenger/GcmPushListenerService.java index be7766aec..0d69bdbdc 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/GcmPushListenerService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/GcmPushListenerService.java @@ -237,12 +237,7 @@ public class GcmPushListenerService extends FirebaseMessagingService { deletedMessages.put(channel_id, ids); NotificationsController.getInstance(currentAccount).removeDeletedMessagesFromNotifications(deletedMessages); - final long dialogIdFinal = dialog_id; - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { - MessagesStorage.getInstance(accountFinal).deletePushMessages(dialogIdFinal, ids); - ArrayList dialogIds = MessagesStorage.getInstance(accountFinal).markMessagesAsDeleted(ids, false, channel_id); - MessagesStorage.getInstance(accountFinal).updateDialogsWithDeletedMessages(ids, dialogIds, false, channel_id); - }); + MessagesController.getInstance(currentAccount).deleteMessagesByPush(dialog_id, ids, channel_id); } else if (!TextUtils.isEmpty(loc_key)) { int msg_id; if (custom.has("msg_id")) { @@ -877,6 +872,7 @@ public class GcmPushListenerService extends FirebaseMessagingService { messageOwner.to_id = new TLRPC.TL_peerUser(); messageOwner.to_id.user_id = user_id; } + messageOwner.flags |= 256; messageOwner.from_id = chat_from_id; messageOwner.mentioned = mention || pinned; messageOwner.silent = silent; @@ -914,7 +910,7 @@ public class GcmPushListenerService extends FirebaseMessagingService { update.max_id = max_id; updates.add(update); } - MessagesController.getInstance(accountFinal).processUpdateArray(updates, null, null, false); + MessagesController.getInstance(accountFinal).processUpdateArray(updates, null, null, false, 0); countDownLatch.countDown(); } } @@ -969,6 +965,10 @@ public class GcmPushListenerService extends FirebaseMessagingService { public static void sendRegistrationToServer(final String token) { Utilities.stageQueue.postRunnable(() -> { + ConnectionsManager.setRegId(token, SharedConfig.pushStringStatus); + if (token == null) { + return; + } SharedConfig.pushString = token; for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { UserConfig userConfig = UserConfig.getInstance(a); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java index 5f7b06ae3..8b31a4634 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java @@ -29,9 +29,6 @@ import android.provider.MediaStore; import android.text.TextUtils; import android.util.SparseArray; -import com.airbnb.lottie.LottieCompositionFactory; -import com.airbnb.lottie.LottieDrawable; - import org.json.JSONArray; import org.json.JSONObject; import org.telegram.messenger.secretmedia.EncryptedFileInputStream; @@ -39,6 +36,7 @@ import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.Components.AnimatedFileDrawable; +import org.telegram.ui.Components.RLottieDrawable; import java.io.ByteArrayOutputStream; import java.io.File; @@ -67,7 +65,7 @@ public class ImageLoader { private HashMap bitmapUseCounts = new HashMap<>(); private LruCache memCache; - private LruCache lottieMemCache; + private LruCache lottieMemCache; private HashMap imageLoadingByUrl = new HashMap<>(); private HashMap imageLoadingByKeys = new HashMap<>(); private SparseArray imageLoadingByTag = new SparseArray<>(); @@ -112,6 +110,7 @@ public class ImageLoader { private TLRPC.Document parentDocument; private String filter; private ArrayList imageReceiverArray = new ArrayList<>(); + private ArrayList imageReceiverGuidsArray = new ArrayList<>(); private boolean big; } @@ -698,6 +697,7 @@ public class ImageLoader { } final BitmapDrawable bitmapDrawable = new BitmapDrawable(originalBitmap); final ArrayList finalImageReceiverArray = new ArrayList<>(info.imageReceiverArray); + final ArrayList finalImageReceiverGuidsArray = new ArrayList<>(info.imageReceiverGuidsArray); AndroidUtilities.runOnUIThread(() -> { removeTask(); @@ -708,7 +708,7 @@ public class ImageLoader { for (int a = 0; a < finalImageReceiverArray.size(); a++) { ImageReceiver imgView = finalImageReceiverArray.get(a); - imgView.setImageBitmapByKey(bitmapDrawable, kf, ImageReceiver.TYPE_IMAGE, false); + imgView.setImageBitmapByKey(bitmapDrawable, kf, ImageReceiver.TYPE_IMAGE, false, finalImageReceiverGuidsArray.get(a)); } memCache.put(kf, bitmapDrawable); @@ -773,18 +773,26 @@ public class ImageLoader { return; } } - LottieDrawable lottieDrawable = new LottieDrawable(); - try { - FileInputStream is = new FileInputStream(cacheImage.finalFilePath); - lottieDrawable.setComposition(LottieCompositionFactory.fromJsonInputStreamSync(is, cacheImage.finalFilePath.toString()).getValue()); - is.close(); - } catch (Throwable e) { - FileLog.e(e); + int w = Math.min(512, AndroidUtilities.dp(170.6f)); + int h = Math.min(512, AndroidUtilities.dp(170.6f)); + boolean precache = false; + boolean limitFps = false; + if (cacheImage.filter != null) { + String[] args = cacheImage.filter.split("_"); + if (args.length >= 2) { + float w_filter = Float.parseFloat(args[0]); + float h_filter = Float.parseFloat(args[1]); + w = Math.min(512, (int) (w_filter * AndroidUtilities.density)); + h = Math.min(512, (int) (h_filter * AndroidUtilities.density)); + if (w_filter <= 90 && h_filter <= 90) { + w = Math.min(w, 160); + h = Math.min(h, 160); + limitFps = true; + precache = SharedConfig.getDevicePerfomanceClass() != SharedConfig.PERFORMANCE_CLASS_HIGH; + } + } } - lottieDrawable.setRepeatMode(LottieDrawable.RESTART); - lottieDrawable.setRepeatCount(LottieDrawable.INFINITE); - lottieDrawable.start(); - Thread.interrupted(); + RLottieDrawable lottieDrawable = new RLottieDrawable(cacheImage.finalFilePath, w, h, precache, limitFps); onPostExecute(lottieDrawable); } else if (cacheImage.animatedFile) { synchronized (sync) { @@ -1258,12 +1266,18 @@ public class ImageLoader { AndroidUtilities.runOnUIThread(() -> { Drawable toSet = null; String decrementKey = null; - if (drawable instanceof LottieDrawable) { - LottieDrawable lottieDrawable = (LottieDrawable) drawable; + if (drawable instanceof RLottieDrawable) { + RLottieDrawable lottieDrawable = (RLottieDrawable) drawable; toSet = lottieMemCache.get(cacheImage.key); if (toSet == null) { lottieMemCache.put(cacheImage.key, lottieDrawable); toSet = lottieDrawable; + } else { + lottieDrawable.recycle(); + } + if (toSet != null) { + incrementUseCount(cacheImage.key); + decrementKey = cacheImage.key; } } else if (drawable instanceof AnimatedFileDrawable) { toSet = drawable; @@ -1327,22 +1341,24 @@ public class ImageLoader { protected CacheOutTask cacheTask; protected ArrayList imageReceiverArray = new ArrayList<>(); + protected ArrayList imageReceiverGuidsArray = new ArrayList<>(); protected ArrayList keys = new ArrayList<>(); protected ArrayList filters = new ArrayList<>(); protected ArrayList imageTypes = new ArrayList<>(); - public void addImageReceiver(ImageReceiver imageReceiver, String key, String filter, int type) { + public void addImageReceiver(ImageReceiver imageReceiver, String key, String filter, int type, int guid) { if (imageReceiverArray.contains(imageReceiver)) { return; } imageReceiverArray.add(imageReceiver); + imageReceiverGuidsArray.add(guid); keys.add(key); filters.add(filter); imageTypes.add(type); imageLoadingByTag.put(imageReceiver.getTag(type), this); } - public void replaceImageReceiver(ImageReceiver imageReceiver, String key, String filter, int type) { + public void replaceImageReceiver(ImageReceiver imageReceiver, String key, String filter, int type, int guid) { int index = imageReceiverArray.indexOf(imageReceiver); if (index == -1) { return; @@ -1353,6 +1369,7 @@ public class ImageLoader { return; } } + imageReceiverGuidsArray.set(index, guid); keys.set(index, key); filters.set(index, filter); } @@ -1363,6 +1380,7 @@ public class ImageLoader { ImageReceiver obj = imageReceiverArray.get(a); if (obj == null || obj == imageReceiver) { imageReceiverArray.remove(a); + imageReceiverGuidsArray.remove(a); keys.remove(a); filters.remove(a); currentImageType = imageTypes.remove(a); @@ -1421,6 +1439,7 @@ public class ImageLoader { public void setImageAndClear(final Drawable image, String decrementKey) { if (image != null) { final ArrayList finalImageReceiverArray = new ArrayList<>(imageReceiverArray); + final ArrayList finalImageReceiverGuidsArray = new ArrayList<>(imageReceiverGuidsArray); AndroidUtilities.runOnUIThread(() -> { if (image instanceof AnimatedFileDrawable) { boolean imageSet = false; @@ -1428,7 +1447,7 @@ public class ImageLoader { for (int a = 0; a < finalImageReceiverArray.size(); a++) { ImageReceiver imgView = finalImageReceiverArray.get(a); AnimatedFileDrawable toSet = (a == 0 ? fileDrawable : fileDrawable.makeCopy()); - if (imgView.setImageBitmapByKey(toSet, key, imageType, false)) { + if (imgView.setImageBitmapByKey(toSet, key, imageType, false, finalImageReceiverGuidsArray.get(a))) { if (toSet == fileDrawable) { imageSet = true; } @@ -1444,7 +1463,7 @@ public class ImageLoader { } else { for (int a = 0; a < finalImageReceiverArray.size(); a++) { ImageReceiver imgView = finalImageReceiverArray.get(a); - imgView.setImageBitmapByKey(image, key, imageTypes.get(a), false); + imgView.setImageBitmapByKey(image, key, imageTypes.get(a), false, finalImageReceiverGuidsArray.get(a)); } } if (decrementKey != null) { @@ -1457,6 +1476,7 @@ public class ImageLoader { imageLoadingByTag.remove(imageReceiver.getTag(imageType)); } imageReceiverArray.clear(); + imageReceiverGuidsArray.clear(); if (url != null) { imageLoadingByUrl.remove(url); } @@ -1514,10 +1534,18 @@ public class ImageLoader { } }; - lottieMemCache = new LruCache(5) { + lottieMemCache = new LruCache(512 * 512 * 2 * 4 * 5) { @Override - protected int sizeOf(String key, LottieDrawable value) { - return 1; + protected int sizeOf(String key, RLottieDrawable value) { + return value.getIntrinsicWidth() * value.getIntrinsicHeight() * 4 * 2; + } + + @Override + protected void entryRemoved(boolean evicted, String key, final RLottieDrawable oldValue, RLottieDrawable newValue) { + final Integer count = bitmapUseCounts.get(key); + if (count == null || count == 0) { + oldValue.recycle(); + } } }; @@ -1868,8 +1896,12 @@ public class ImageLoader { memCache.remove(key); } - public boolean isInCache(String key) { - return memCache.get(key) != null; + public boolean isInMemCache(String key, boolean animated) { + if (animated) { + return lottieMemCache.get(key) != null; + } else { + return memCache.get(key) != null; + } } public void clearMemory() { @@ -1882,7 +1914,11 @@ public class ImageLoader { if (location != null) { ThumbGenerateInfo info = waitingForQualityThumb.get(location); if (info != null) { - info.imageReceiverArray.remove(imageReceiver); + int index = info.imageReceiverArray.indexOf(imageReceiver); + if (index >= 0) { + info.imageReceiverArray.remove(index); + info.imageReceiverGuidsArray.remove(index); + } if (info.imageReceiverArray.isEmpty()) { waitingForQualityThumb.remove(location); } @@ -2012,7 +2048,7 @@ public class ImageLoader { imageLoadQueue.postRunnable(() -> forceLoadingImages.remove(key)); } - private void createLoadOperationForImageReceiver(final ImageReceiver imageReceiver, final String key, final String url, final String ext, final ImageLocation imageLocation, final String filter, final int size, final int cacheType, final int imageType, final int thumb) { + private void createLoadOperationForImageReceiver(final ImageReceiver imageReceiver, final String key, final String url, final String ext, final ImageLocation imageLocation, final String filter, final int size, final int cacheType, final int imageType, final int thumb, int guid) { if (imageReceiver == null || url == null || key == null || imageLocation == null) { return; } @@ -2043,7 +2079,7 @@ public class ImageLoader { added = true; } else if (alreadyLoadingImage == alreadyLoadingUrl) { if (alreadyLoadingCache == null) { - alreadyLoadingImage.replaceImageReceiver(imageReceiver, key, filter, imageType); + alreadyLoadingImage.replaceImageReceiver(imageReceiver, key, filter, imageType, guid); } added = true; } else { @@ -2052,11 +2088,11 @@ public class ImageLoader { } if (!added && alreadyLoadingCache != null) { - alreadyLoadingCache.addImageReceiver(imageReceiver, key, filter, imageType); + alreadyLoadingCache.addImageReceiver(imageReceiver, key, filter, imageType, guid); added = true; } if (!added && alreadyLoadingUrl != null) { - alreadyLoadingUrl.addImageReceiver(imageReceiver, key, filter, imageType); + alreadyLoadingUrl.addImageReceiver(imageReceiver, key, filter, imageType, guid); added = true; } } @@ -2152,6 +2188,7 @@ public class ImageLoader { } if (!info.imageReceiverArray.contains(imageReceiver)) { info.imageReceiverArray.add(imageReceiver); + info.imageReceiverGuidsArray.add(guid); } waitingForQualityThumbByTag.put(finalTag, location); if (attachPath.exists() && shouldGenerateQualityThumb) { @@ -2230,10 +2267,13 @@ public class ImageLoader { img.ext = ext; img.currentAccount = currentAccount; img.parentObject = parentObject; + if (imageLocation.lottieAnimation) { + img.lottieFile = true; + } if (cacheType == 2) { img.encryptionKeyPath = new File(FileLoader.getInternalCacheDir(), url + ".enc.key"); } - img.addImageReceiver(imageReceiver, key, filter, imageType); + img.addImageReceiver(imageReceiver, key, filter, imageType, guid); if (onlyCache || cacheFileExists || cacheFile.exists()) { img.finalFilePath = cacheFile; img.imageLocation = imageLocation; @@ -2293,17 +2333,21 @@ public class ImageLoader { boolean imageSet = false; String mediaKey = imageReceiver.getMediaKey(); + int guid = imageReceiver.getNewGuid(); if (mediaKey != null) { ImageLocation mediaLocation = imageReceiver.getMediaLocation(); Drawable drawable; - if (MessageObject.isAnimatedStickerDocument(mediaLocation.document)) { + if (mediaLocation != null && (MessageObject.isAnimatedStickerDocument(mediaLocation.document) || mediaLocation.lottieAnimation)) { drawable = lottieMemCache.get(mediaKey); } else { drawable = memCache.get(mediaKey); + if (drawable != null) { + memCache.moveToFront(mediaKey); + } } if (drawable != null) { cancelLoadingForImageReceiver(imageReceiver, true); - imageReceiver.setImageBitmapByKey(drawable, mediaKey, ImageReceiver.TYPE_MEDIA, true); + imageReceiver.setImageBitmapByKey(drawable, mediaKey, ImageReceiver.TYPE_MEDIA, true, guid); imageSet = true; if (!imageReceiver.isForcePreview()) { return; @@ -2314,14 +2358,17 @@ public class ImageLoader { if (!imageSet && imageKey != null) { ImageLocation imageLocation = imageReceiver.getImageLocation(); Drawable drawable; - if (imageLocation != null && MessageObject.isAnimatedStickerDocument(imageLocation.document)) { + if (imageLocation != null && (MessageObject.isAnimatedStickerDocument(imageLocation.document) || imageLocation.lottieAnimation)) { drawable = lottieMemCache.get(imageKey); } else { drawable = memCache.get(imageKey); + if (drawable != null) { + memCache.moveToFront(imageKey); + } } if (drawable != null) { cancelLoadingForImageReceiver(imageReceiver, true); - imageReceiver.setImageBitmapByKey(drawable, imageKey, ImageReceiver.TYPE_IMAGE, true); + imageReceiver.setImageBitmapByKey(drawable, imageKey, ImageReceiver.TYPE_IMAGE, true, guid); imageSet = true; if (!imageReceiver.isForcePreview() && mediaKey == null) { return; @@ -2331,9 +2378,18 @@ public class ImageLoader { boolean thumbSet = false; String thumbKey = imageReceiver.getThumbKey(); if (thumbKey != null) { - BitmapDrawable bitmapDrawable = memCache.get(thumbKey); - if (bitmapDrawable != null) { - imageReceiver.setImageBitmapByKey(bitmapDrawable, thumbKey, ImageReceiver.TYPE_THUMB, true); + ImageLocation thumbLocation = imageReceiver.getThumbLocation(); + Drawable drawable; + if (thumbLocation != null && (MessageObject.isAnimatedStickerDocument(thumbLocation.document) || thumbLocation.lottieAnimation)) { + drawable = lottieMemCache.get(imageKey); + } else { + drawable = memCache.get(thumbKey); + if (drawable != null) { + memCache.moveToFront(thumbKey); + } + } + if (drawable != null) { + imageReceiver.setImageBitmapByKey(drawable, thumbKey, ImageReceiver.TYPE_THUMB, true, guid); cancelLoadingForImageReceiver(imageReceiver, false); if (imageSet && imageReceiver.isForcePreview()) { return; @@ -2472,8 +2528,8 @@ public class ImageLoader { } if (imageLocation != null && imageLocation.path != null) { - createLoadOperationForImageReceiver(imageReceiver, thumbKey, thumbUrl, ext, thumbLocation, thumbFilter, 0, 1, ImageReceiver.TYPE_THUMB, thumbSet ? 2 : 1); - createLoadOperationForImageReceiver(imageReceiver, imageKey, imageUrl, ext, imageLocation, imageFilter, imageReceiver.getSize(), 1, ImageReceiver.TYPE_IMAGE, 0); + createLoadOperationForImageReceiver(imageReceiver, thumbKey, thumbUrl, ext, thumbLocation, thumbFilter, 0, 1, ImageReceiver.TYPE_THUMB, thumbSet ? 2 : 1, guid); + createLoadOperationForImageReceiver(imageReceiver, imageKey, imageUrl, ext, imageLocation, imageFilter, imageReceiver.getSize(), 1, ImageReceiver.TYPE_IMAGE, 0, guid); } else if (mediaLocation != null) { int mediaCacheType = imageReceiver.getCacheType(); int imageCacheType = 1; @@ -2482,20 +2538,20 @@ public class ImageLoader { } int thumbCacheType = mediaCacheType == 0 ? 1 : mediaCacheType; if (!thumbSet) { - createLoadOperationForImageReceiver(imageReceiver, thumbKey, thumbUrl, ext, thumbLocation, thumbFilter, 0, thumbCacheType, ImageReceiver.TYPE_THUMB, thumbSet ? 2 : 1); + createLoadOperationForImageReceiver(imageReceiver, thumbKey, thumbUrl, ext, thumbLocation, thumbFilter, 0, thumbCacheType, ImageReceiver.TYPE_THUMB, thumbSet ? 2 : 1, guid); } if (!imageSet) { - createLoadOperationForImageReceiver(imageReceiver, imageKey, imageUrl, ext, imageLocation, imageFilter, 0, imageCacheType, ImageReceiver.TYPE_IMAGE, 0); + createLoadOperationForImageReceiver(imageReceiver, imageKey, imageUrl, ext, imageLocation, imageFilter, 0, imageCacheType, ImageReceiver.TYPE_IMAGE, 0, guid); } - createLoadOperationForImageReceiver(imageReceiver, mediaKey, mediaUrl, ext, mediaLocation, mediaFilter, imageReceiver.getSize(), mediaCacheType, ImageReceiver.TYPE_MEDIA, 0); + createLoadOperationForImageReceiver(imageReceiver, mediaKey, mediaUrl, ext, mediaLocation, mediaFilter, imageReceiver.getSize(), mediaCacheType, ImageReceiver.TYPE_MEDIA, 0, guid); } else { int imageCacheType = imageReceiver.getCacheType(); if (imageCacheType == 0 && saveImageToCache) { imageCacheType = 1; } int thumbCacheType = imageCacheType == 0 ? 1 : imageCacheType; - createLoadOperationForImageReceiver(imageReceiver, thumbKey, thumbUrl, ext, thumbLocation, thumbFilter, 0, thumbCacheType, ImageReceiver.TYPE_THUMB, thumbSet ? 2 : 1); - createLoadOperationForImageReceiver(imageReceiver, imageKey, imageUrl, ext, imageLocation, imageFilter, imageReceiver.getSize(), imageCacheType, ImageReceiver.TYPE_IMAGE, 0); + createLoadOperationForImageReceiver(imageReceiver, thumbKey, thumbUrl, ext, thumbLocation, thumbFilter, 0, thumbCacheType, ImageReceiver.TYPE_THUMB, thumbSet ? 2 : 1, guid); + createLoadOperationForImageReceiver(imageReceiver, imageKey, imageUrl, ext, imageLocation, imageFilter, imageReceiver.getSize(), imageCacheType, ImageReceiver.TYPE_IMAGE, 0, guid); } } @@ -2543,6 +2599,7 @@ public class ImageLoader { String filter = img.filters.get(a); int imageType = img.imageTypes.get(a); ImageReceiver imageReceiver = img.imageReceiverArray.get(a); + int guid = img.imageReceiverGuidsArray.get(a); CacheImage cacheImage = imageLoadingByKeys.get(key); if (cacheImage == null) { cacheImage = new CacheImage(); @@ -2561,7 +2618,7 @@ public class ImageLoader { imageLoadingByKeys.put(key, cacheImage); tasks.add(cacheImage.cacheTask); } - cacheImage.addImageReceiver(imageReceiver, key, filter, imageType); + cacheImage.addImageReceiver(imageReceiver, key, filter, imageType, guid); } for (int a = 0; a < tasks.size(); a++) { CacheOutTask task = tasks.get(a); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLocation.java b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLocation.java index f0a16f395..c74601ac1 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLocation.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLocation.java @@ -23,6 +23,7 @@ public class ImageLocation { public boolean photoPeerBig; public TLRPC.InputPeer photoPeer; public TLRPC.InputStickerSet stickerSet; + public boolean lottieAnimation; public int currentSize; @@ -155,11 +156,15 @@ public class ImageLocation { } else if (photoSize == null || sticker == null) { return null; } - TLRPC.InputStickerSet stickerSet = DataQuery.getInputStickerSet(sticker); + TLRPC.InputStickerSet stickerSet = MediaDataController.getInputStickerSet(sticker); if (stickerSet == null) { return null; } - return getForPhoto(photoSize.location, photoSize.size, null, null, null, false, sticker.dc_id, stickerSet, photoSize.type); + ImageLocation imageLocation = getForPhoto(photoSize.location, photoSize.size, null, null, null, false, sticker.dc_id, stickerSet, photoSize.type); + if (MessageObject.isAnimatedStickerDocument(sticker)) { + imageLocation.lottieAnimation = true; + } + return imageLocation; } public static ImageLocation getForDocument(TLRPC.PhotoSize photoSize, TLRPC.Document document) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java index 23b073449..9ab6f5c27 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java @@ -22,10 +22,9 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.view.View; -import com.airbnb.lottie.LottieDrawable; - import org.telegram.tgnet.TLRPC; import org.telegram.ui.Components.AnimatedFileDrawable; +import org.telegram.ui.Components.RLottieDrawable; import org.telegram.ui.Components.RecyclableDrawable; public class ImageReceiver implements NotificationCenter.NotificationCenterDelegate { @@ -74,7 +73,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg return; } boolean canDelete = ImageLoader.getInstance().decrementUseCount(key); - if (!ImageLoader.getInstance().isInCache(key)) { + if (!ImageLoader.getInstance().isInMemCache(key, false)) { if (canDelete) { bitmap.recycle(); } @@ -113,6 +112,9 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg private static PorterDuffColorFilter selectedGroupColorFilter = new PorterDuffColorFilter(0xffbbbbbb, PorterDuff.Mode.MULTIPLY); private boolean forceLoding; + private int currentLayerNum; + private int currentOpenedLayerFlags; + private SetImageBackup setImageBackup; private ImageLocation strippedLocation; @@ -143,6 +145,8 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg private String currentExt; + private int currentGuid; + private int currentSize; private int currentCacheType; private boolean allowStartAnimation = true; @@ -464,6 +468,10 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg return imageOrientation; } + public void setLayerNum(int value) { + currentLayerNum = value; + } + public void setImageBitmap(Bitmap bitmap) { setImageBitmap(bitmap != null ? new BitmapDrawable(null, bitmap) : null); } @@ -521,10 +529,19 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg fileDrawable.start(); } fileDrawable.setAllowDecodeSingleFrame(allowDecodeSingleFrame); + } else if (bitmap instanceof RLottieDrawable) { + RLottieDrawable fileDrawable = (RLottieDrawable) bitmap; + fileDrawable.addParentView(parentView); + if (currentOpenedLayerFlags == 0) { + fileDrawable.start(); + } + fileDrawable.setAllowDecodeSingleFrame(true); } staticThumbDrawable = bitmap; if (roundRadius != 0 && bitmap instanceof BitmapDrawable) { - if (bitmap instanceof AnimatedFileDrawable) { + if (bitmap instanceof RLottieDrawable) { + + } else if (bitmap instanceof AnimatedFileDrawable) { ((AnimatedFileDrawable) bitmap).setRoundRadius(roundRadius); } else { Bitmap object = ((BitmapDrawable) bitmap).getBitmap(); @@ -604,15 +621,34 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg setImageBackup.parentObject = currentParentObject; } NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.didReplacedPhotoInMemCache); + NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.stopAllHeavyOperations); + NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.startAllHeavyOperations); + clearImage(); } public boolean onAttachedToWindow() { + currentOpenedLayerFlags = NotificationCenter.getGlobalInstance().getCurrentHeavyOperationFlags(); + currentOpenedLayerFlags &=~ currentLayerNum; NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.didReplacedPhotoInMemCache); + NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.stopAllHeavyOperations); + NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.startAllHeavyOperations); if (setImageBackup != null && (setImageBackup.imageLocation != null || setImageBackup.thumbLocation != null || setImageBackup.mediaLocation != null || setImageBackup.thumb != null)) { setImage(setImageBackup.mediaLocation, setImageBackup.mediaFilter, setImageBackup.imageLocation, setImageBackup.imageFilter, setImageBackup.thumbLocation, setImageBackup.thumbFilter, setImageBackup.thumb, setImageBackup.size, setImageBackup.ext, setImageBackup.parentObject, setImageBackup.cacheType); + if (currentOpenedLayerFlags == 0) { + RLottieDrawable lottieDrawable = getLottieAnimation(); + if (lottieDrawable != null) { + lottieDrawable.start(); + } + } return true; } + if (currentOpenedLayerFlags == 0) { + RLottieDrawable lottieDrawable = getLottieAnimation(); + if (lottieDrawable != null) { + lottieDrawable.start(); + } + } return false; } @@ -657,7 +693,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg } int bitmapW; int bitmapH; - if (bitmapDrawable instanceof AnimatedFileDrawable) { + if (bitmapDrawable instanceof AnimatedFileDrawable || bitmapDrawable instanceof RLottieDrawable) { if (orientation % 360 == 90 || orientation % 360 == 270) { bitmapW = bitmapDrawable.getIntrinsicHeight(); bitmapH = bitmapDrawable.getIntrinsicWidth(); @@ -836,39 +872,12 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH); drawable.setBounds((int) drawRegion.left, (int) drawRegion.top, (int) drawRegion.right, (int) drawRegion.bottom); if (isVisible) { - if (drawable instanceof LottieDrawable) { - canvas.save(); - float tx = imageX; - float ty = imageY; - int bitmapWidth = getBitmapWidth(); - int bitmapHeight = getBitmapHeight(); - float scale; - if (bitmapWidth > imageW || bitmapHeight > imageH) { - scale = Math.min(imageW / (float) bitmapWidth, imageH / (float) bitmapHeight); - bitmapWidth *= scale; - bitmapHeight *= scale; - canvas.scale(scale, scale); - } else { - scale = 1.0f; - } - canvas.translate((imageX + (imageW - bitmapWidth) / 2) / scale, (imageY + (imageH - bitmapHeight) / 2) / scale); - if (parentView != null) { - if (invalidateAll) { - parentView.invalidate(); - } else { - parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH); - } - } - } try { drawable.setAlpha(alpha); drawable.draw(canvas); } catch (Exception e) { FileLog.e(e); } - if (drawable instanceof LottieDrawable) { - canvas.restore(); - } } } } @@ -922,7 +931,11 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg try { Drawable drawable = null; AnimatedFileDrawable animation = getAnimation(); - boolean animationNotReady = animation != null && !animation.hasBitmap(); + RLottieDrawable lottieDrawable = getLottieAnimation(); + boolean animationNotReady = animation != null && !animation.hasBitmap() || lottieDrawable != null && !lottieDrawable.hasBitmap(); + if (lottieDrawable != null) { + lottieDrawable.setCurrentParentView(parentView); + } int orientation = 0; BitmapShader shaderToUse = null; if (!forcePreview && currentMediaDrawable != null && !animationNotReady) { @@ -1029,13 +1042,16 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg public Bitmap getBitmap() { AnimatedFileDrawable animation = getAnimation(); - if (animation != null && animation.hasBitmap()) { + RLottieDrawable lottieDrawable = getLottieAnimation(); + if (lottieDrawable != null && lottieDrawable.hasBitmap()) { + return lottieDrawable.getAnimatedBitmap(); + } else if (animation != null && animation.hasBitmap()) { return animation.getAnimatedBitmap(); - } else if (currentMediaDrawable instanceof BitmapDrawable && !(currentMediaDrawable instanceof AnimatedFileDrawable)) { + } else if (currentMediaDrawable instanceof BitmapDrawable && !(currentMediaDrawable instanceof AnimatedFileDrawable) && !(currentMediaDrawable instanceof RLottieDrawable)) { return ((BitmapDrawable) currentMediaDrawable).getBitmap(); - } else if (currentImageDrawable instanceof BitmapDrawable && !(currentImageDrawable instanceof AnimatedFileDrawable)) { + } else if (currentImageDrawable instanceof BitmapDrawable && !(currentImageDrawable instanceof AnimatedFileDrawable) && !(currentMediaDrawable instanceof RLottieDrawable)) { return ((BitmapDrawable) currentImageDrawable).getBitmap(); - } else if (currentThumbDrawable instanceof BitmapDrawable && !(currentThumbDrawable instanceof AnimatedFileDrawable)) { + } else if (currentThumbDrawable instanceof BitmapDrawable && !(currentThumbDrawable instanceof AnimatedFileDrawable) && !(currentMediaDrawable instanceof RLottieDrawable)) { return ((BitmapDrawable) currentThumbDrawable).getBitmap(); } else if (staticThumbDrawable instanceof BitmapDrawable) { return ((BitmapDrawable) staticThumbDrawable).getBitmap(); @@ -1047,15 +1063,18 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg Bitmap bitmap = null; String key = null; AnimatedFileDrawable animation = getAnimation(); - if (animation != null && animation.hasBitmap()) { + RLottieDrawable lottieDrawable = getLottieAnimation(); + if (lottieDrawable != null && lottieDrawable.hasBitmap()) { + bitmap = lottieDrawable.getAnimatedBitmap(); + } else if (animation != null && animation.hasBitmap()) { bitmap = animation.getAnimatedBitmap(); - } else if (currentMediaDrawable instanceof BitmapDrawable && !(currentMediaDrawable instanceof AnimatedFileDrawable)) { + } else if (currentMediaDrawable instanceof BitmapDrawable && !(currentMediaDrawable instanceof AnimatedFileDrawable) && !(currentMediaDrawable instanceof RLottieDrawable)) { bitmap = ((BitmapDrawable) currentMediaDrawable).getBitmap(); key = currentMediaKey; - } else if (currentImageDrawable instanceof BitmapDrawable && !(currentImageDrawable instanceof AnimatedFileDrawable)) { + } else if (currentImageDrawable instanceof BitmapDrawable && !(currentImageDrawable instanceof AnimatedFileDrawable) && !(currentMediaDrawable instanceof RLottieDrawable)) { bitmap = ((BitmapDrawable) currentImageDrawable).getBitmap(); key = currentImageKey; - } else if (currentThumbDrawable instanceof BitmapDrawable && !(currentThumbDrawable instanceof AnimatedFileDrawable)) { + } else if (currentThumbDrawable instanceof BitmapDrawable && !(currentThumbDrawable instanceof AnimatedFileDrawable) && !(currentMediaDrawable instanceof RLottieDrawable)) { bitmap = ((BitmapDrawable) currentThumbDrawable).getBitmap(); key = currentThumbKey; } else if (staticThumbDrawable instanceof BitmapDrawable) { @@ -1093,13 +1112,14 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg public int getBitmapWidth() { Drawable drawable = getDrawable(); - if (drawable instanceof LottieDrawable) { - return drawable.getIntrinsicWidth(); - } AnimatedFileDrawable animation = getAnimation(); if (animation != null) { return imageOrientation % 360 == 0 || imageOrientation % 360 == 180 ? animation.getIntrinsicWidth() : animation.getIntrinsicHeight(); } + RLottieDrawable lottieDrawable = getLottieAnimation(); + if (lottieDrawable != null) { + return lottieDrawable.getIntrinsicWidth(); + } Bitmap bitmap = getBitmap(); if (bitmap == null) { if (staticThumbDrawable != null) { @@ -1112,13 +1132,14 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg public int getBitmapHeight() { Drawable drawable = getDrawable(); - if (drawable instanceof LottieDrawable) { - return drawable.getIntrinsicHeight(); - } AnimatedFileDrawable animation = getAnimation(); if (animation != null) { return imageOrientation % 360 == 0 || imageOrientation % 360 == 180 ? animation.getIntrinsicHeight() : animation.getIntrinsicWidth(); } + RLottieDrawable lottieDrawable = getLottieAnimation(); + if (lottieDrawable != null) { + return lottieDrawable.getIntrinsicHeight(); + } Bitmap bitmap = getBitmap(); if (bitmap == null) { if (staticThumbDrawable != null) { @@ -1258,6 +1279,10 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg return drawRegion; } + public int getNewGuid() { + return ++currentGuid; + } + public String getImageKey() { return currentImageKey; } @@ -1416,6 +1441,20 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg return null; } + public RLottieDrawable getLottieAnimation() { + RLottieDrawable animatedFileDrawable; + if (currentMediaDrawable instanceof RLottieDrawable) { + return (RLottieDrawable) currentMediaDrawable; + } else if (currentImageDrawable instanceof RLottieDrawable) { + return (RLottieDrawable) currentImageDrawable; + } else if (currentThumbDrawable instanceof RLottieDrawable) { + return (RLottieDrawable) currentThumbDrawable; + } else if (staticThumbDrawable instanceof RLottieDrawable) { + return (RLottieDrawable) staticThumbDrawable; + } + return null; + } + protected int getTag(int type) { if (type == TYPE_THUMB) { return thumbTag; @@ -1444,15 +1483,15 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg return param; } - protected boolean setImageBitmapByKey(Drawable drawable, String key, int type, boolean memCache) { - if (drawable == null || key == null) { + protected boolean setImageBitmapByKey(Drawable drawable, String key, int type, boolean memCache, int guid) { + if (drawable == null || key == null || currentGuid != guid) { return false; } if (type == TYPE_IMAGE) { if (!key.equals(currentImageKey)) { return false; } - if (!(drawable instanceof AnimatedFileDrawable) && !(drawable instanceof LottieDrawable)) { + if (!(drawable instanceof AnimatedFileDrawable)) { ImageLoader.getInstance().incrementUseCount(currentImageKey); } currentImageDrawable = drawable; @@ -1460,7 +1499,9 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg imageOrientation = ((ExtendedBitmapDrawable) drawable).getOrientation(); } if (roundRadius != 0 && drawable instanceof BitmapDrawable) { - if (drawable instanceof AnimatedFileDrawable) { + if (drawable instanceof RLottieDrawable) { + + } else if (drawable instanceof AnimatedFileDrawable) { AnimatedFileDrawable animatedFileDrawable = (AnimatedFileDrawable) drawable; animatedFileDrawable.setRoundRadius(roundRadius); } else { @@ -1475,6 +1516,8 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg boolean allowCorssfade = true; if (currentMediaDrawable instanceof AnimatedFileDrawable && ((AnimatedFileDrawable) currentMediaDrawable).hasBitmap()) { allowCorssfade = false; + } else if (currentImageDrawable instanceof RLottieDrawable) { + allowCorssfade = false; } if (allowCorssfade && (currentThumbDrawable == null && staticThumbDrawable == null || currentAlpha == 1.0f || forceCrossfade)) { currentAlpha = 0.0f; @@ -1488,12 +1531,14 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg if (!key.equals(currentMediaKey)) { return false; } - if (!(drawable instanceof AnimatedFileDrawable) && !(drawable instanceof LottieDrawable)) { + if (!(drawable instanceof AnimatedFileDrawable)) { ImageLoader.getInstance().incrementUseCount(currentMediaKey); } currentMediaDrawable = drawable; if (roundRadius != 0 && drawable instanceof BitmapDrawable) { - if (drawable instanceof AnimatedFileDrawable) { + if (drawable instanceof RLottieDrawable) { + + } else if (drawable instanceof AnimatedFileDrawable) { AnimatedFileDrawable animatedFileDrawable = (AnimatedFileDrawable) drawable; animatedFileDrawable.setRoundRadius(roundRadius); } else { @@ -1505,6 +1550,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg } if (currentImageDrawable == null) { + boolean allowCorssfade = true; if (!memCache && !forcePreview || forceCrossfade) { if (currentThumbDrawable == null && staticThumbDrawable == null || currentAlpha == 1.0f || forceCrossfade) { currentAlpha = 0.0f; @@ -1539,7 +1585,9 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg } if (roundRadius != 0 && drawable instanceof BitmapDrawable) { - if (drawable instanceof AnimatedFileDrawable) { + if (drawable instanceof RLottieDrawable) { + + } else if (drawable instanceof AnimatedFileDrawable) { AnimatedFileDrawable animatedFileDrawable = (AnimatedFileDrawable) drawable; animatedFileDrawable.setRoundRadius(roundRadius); } else { @@ -1570,6 +1618,13 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg fileDrawable.start(); } fileDrawable.setAllowDecodeSingleFrame(allowDecodeSingleFrame); + } else if (drawable instanceof RLottieDrawable) { + RLottieDrawable fileDrawable = (RLottieDrawable) drawable; + fileDrawable.addParentView(parentView); + if (currentOpenedLayerFlags == 0) { + fileDrawable.start(); + } + fileDrawable.setAllowDecodeSingleFrame(true); } if (parentView != null) { if (invalidateAll) { @@ -1606,15 +1661,27 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg key = replacedKey; } } + if (image instanceof RLottieDrawable) { + RLottieDrawable lottieDrawable = (RLottieDrawable) image; + lottieDrawable.removeParentView(parentView); + } String replacedKey = ImageLoader.getInstance().getReplacedKey(key); if (key != null && (newKey == null || !newKey.equals(key)) && image != null) { - if (image instanceof AnimatedFileDrawable) { + if (image instanceof RLottieDrawable) { + RLottieDrawable fileDrawable = (RLottieDrawable) image; + boolean canDelete = ImageLoader.getInstance().decrementUseCount(key); + if (!ImageLoader.getInstance().isInMemCache(key, true)) { + if (canDelete) { + fileDrawable.recycle(); + } + } + } else if (image instanceof AnimatedFileDrawable) { AnimatedFileDrawable fileDrawable = (AnimatedFileDrawable) image; fileDrawable.recycle(); } else if (image instanceof BitmapDrawable) { Bitmap bitmap = ((BitmapDrawable) image).getBitmap(); boolean canDelete = ImageLoader.getInstance().decrementUseCount(key); - if (!ImageLoader.getInstance().isInCache(key)) { + if (!ImageLoader.getInstance().isInMemCache(key, false)) { if (canDelete) { bitmap.recycle(); } @@ -1661,6 +1728,30 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg setImageBackup.thumbLocation = (ImageLocation) args[2]; } } + } else if (id == NotificationCenter.stopAllHeavyOperations) { + Integer layer = (Integer) args[0]; + if (currentLayerNum >= layer) { + return; + } + currentOpenedLayerFlags |= layer; + if (currentOpenedLayerFlags != 0) { + RLottieDrawable lottieDrawable = getLottieAnimation(); + if (lottieDrawable != null) { + lottieDrawable.stop(); + } + } + } else if (id == NotificationCenter.startAllHeavyOperations) { + Integer layer = (Integer) args[0]; + if (currentLayerNum >= layer || currentOpenedLayerFlags == 0) { + return; + } + currentOpenedLayerFlags &=~ layer; + if (currentOpenedLayerFlags == 0) { + RLottieDrawable lottieDrawable = getLottieAnimation(); + if (lottieDrawable != null) { + lottieDrawable.start(); + } + } } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java b/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java index 0aa32e84e..80795d391 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java @@ -15,6 +15,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.res.Configuration; +import android.telephony.TelephonyManager; import android.text.TextUtils; import android.text.format.DateFormat; import android.util.Xml; @@ -509,6 +510,10 @@ public class LocaleController { } public static String getLocaleStringIso639() { + LocaleInfo info = getInstance().currentLocaleInfo; + if (info != null) { + return info.getLangCode(); + } Locale locale = getInstance().currentLocale; if (locale == null) { return "en"; @@ -2698,4 +2703,55 @@ public class LocaleController { public static String addNbsp(String src) { return src.replace(' ', '\u00A0'); } + + private static Boolean useImperialSystemType; + + public static void resetImperialSystemType() { + useImperialSystemType = null; + } + + public static String formatDistance(float distance) { + if (useImperialSystemType == null) { + if (SharedConfig.distanceSystemType == 0) { + try { + TelephonyManager telephonyManager = (TelephonyManager) ApplicationLoader.applicationContext.getSystemService(Context.TELEPHONY_SERVICE); + if (telephonyManager != null) { + String country = telephonyManager.getSimCountryIso().toUpperCase(); + useImperialSystemType = "US".equals(country) || "GB".equals(country) || "MM".equals(country) || "LR".equals(country); + } + } catch (Exception e) { + useImperialSystemType = false; + FileLog.e(e); + } + } else { + useImperialSystemType = SharedConfig.distanceSystemType == 2; + } + } + if (useImperialSystemType) { + distance *= 3.28084f; + if (distance < 1000) { + return formatString("FootsAway", R.string.FootsAway, String.format("%d", (int) Math.max(1, distance))); + } else { + String arg; + if (distance % 5280 == 0) { + arg = String.format("%d", (int) (distance / 5280)); + } else { + arg = String.format("%.2f", distance / 5280.0f); + } + return formatString("MilesAway", R.string.MilesAway, arg); + } + } else { + if (distance < 1000) { + return formatString("MetersAway2", R.string.MetersAway2, String.format("%d", (int) Math.max(1, distance))); + } else { + String arg; + if (distance % 1000 == 0) { + arg = String.format("%d", (int) (distance / 1000)); + } else { + arg = String.format("%.2f", distance / 1000.0f); + } + return formatString("KMetersAway2", R.string.KMetersAway2, arg); + } + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/LocationController.java b/TMessagesProj/src/main/java/org/telegram/messenger/LocationController.java index db1a293e8..837750184 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/LocationController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/LocationController.java @@ -10,23 +10,40 @@ package org.telegram.messenger; import android.content.Context; import android.content.Intent; +import android.location.Address; +import android.location.Geocoder; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; +import android.os.Build; import android.os.Bundle; +import android.os.SystemClock; import android.text.TextUtils; import android.util.LongSparseArray; import android.util.SparseIntArray; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.PendingResult; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.location.LocationRequest; +import com.google.android.gms.location.LocationServices; +import com.google.android.gms.location.LocationSettingsRequest; +import com.google.android.gms.location.LocationSettingsResult; +import com.google.android.gms.location.LocationSettingsStatusCodes; + import org.telegram.SQLite.SQLiteCursor; import org.telegram.SQLite.SQLitePreparedStatement; -import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.NativeByteBuffer; import org.telegram.tgnet.TLRPC; import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; -public class LocationController implements NotificationCenter.NotificationCenterDelegate { +public class LocationController extends BaseController implements NotificationCenter.NotificationCenterDelegate, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { private LongSparseArray sharingLocationsMap = new LongSparseArray<>(); private ArrayList sharingLocations = new ArrayList<>(); @@ -35,6 +52,7 @@ public class LocationController implements NotificationCenter.NotificationCenter private GpsLocationListener gpsLocationListener = new GpsLocationListener(); private GpsLocationListener networkLocationListener = new GpsLocationListener(); private GpsLocationListener passiveLocationListener = new GpsLocationListener(); + private FusedLocationListener fusedLocationListener = new FusedLocationListener(); private Location lastKnownLocation; private long lastLocationSendTime; private boolean locationSentSinceLastGoogleMapUpdate = true; @@ -44,16 +62,26 @@ public class LocationController implements NotificationCenter.NotificationCenter private SparseIntArray requests = new SparseIntArray(); private LongSparseArray cacheRequests = new LongSparseArray<>(); + private boolean lookingForPeopleNearby; + public ArrayList sharingLocationsUI = new ArrayList<>(); private LongSparseArray sharingLocationsMapUI = new LongSparseArray<>(); - private final static int BACKGROUD_UPDATE_TIME = 90 * 1000; + private Boolean playServicesAvailable; + private boolean wasConnectedToPlayServices; + private GoogleApiClient googleApiClient; + private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000; + private final static long UPDATE_INTERVAL = 1000, FASTEST_INTERVAL = 1000; + private final static int BACKGROUD_UPDATE_TIME = 30 * 1000; private final static int LOCATION_ACQUIRE_TIME = 10 * 1000; private final static int FOREGROUND_UPDATE_TIME = 20 * 1000; - private final static double eps = 0.0001; - private int currentAccount; - private static volatile LocationController Instance[] = new LocationController[UserConfig.MAX_ACCOUNT_COUNT]; + private ArrayList cachedNearbyUsers = new ArrayList<>(); + private ArrayList cachedNearbyChats = new ArrayList<>(); + + private LocationRequest locationRequest; + + private static volatile LocationController[] Instance = new LocationController[UserConfig.MAX_ACCOUNT_COUNT]; public static LocationController getInstance(int num) { LocationController localInstance = Instance[num]; @@ -85,11 +113,11 @@ public class LocationController implements NotificationCenter.NotificationCenter } if (lastKnownLocation != null && (this == networkLocationListener || this == passiveLocationListener)) { if (!started && location.distanceTo(lastKnownLocation) > 20) { - lastKnownLocation = location; - lastLocationSendTime = System.currentTimeMillis() - BACKGROUD_UPDATE_TIME + 5000; + setLastKnownLocation(location); + lastLocationSendTime = SystemClock.uptimeMillis() - BACKGROUD_UPDATE_TIME + 5000; } } else { - lastKnownLocation = location; + setLastKnownLocation(location); } } @@ -109,14 +137,36 @@ public class LocationController implements NotificationCenter.NotificationCenter } } - public LocationController(final int instance) { - currentAccount = instance; + private class FusedLocationListener implements com.google.android.gms.location.LocationListener { + + @Override + public void onLocationChanged(Location location) { + if (location == null) { + return; + } + setLastKnownLocation(location); + } + } + + public LocationController(int instance) { + super(instance); + locationManager = (LocationManager) ApplicationLoader.applicationContext.getSystemService(Context.LOCATION_SERVICE); + googleApiClient = new GoogleApiClient.Builder(ApplicationLoader.applicationContext). + addApi(LocationServices.API). + addConnectionCallbacks(this). + addOnConnectionFailedListener(this).build(); + + locationRequest = new LocationRequest(); + locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); + locationRequest.setInterval(UPDATE_INTERVAL); + locationRequest.setFastestInterval(FASTEST_INTERVAL); + AndroidUtilities.runOnUIThread(() -> { - LocationController locationController = getInstance(currentAccount); - NotificationCenter.getInstance(currentAccount).addObserver(locationController, NotificationCenter.didReceiveNewMessages); - NotificationCenter.getInstance(currentAccount).addObserver(locationController, NotificationCenter.messagesDeleted); - NotificationCenter.getInstance(currentAccount).addObserver(locationController, NotificationCenter.replaceMessagesObjects); + LocationController locationController = getAccountInstance().getLocationController(); + getNotificationCenter().addObserver(locationController, NotificationCenter.didReceiveNewMessages); + getNotificationCenter().addObserver(locationController, NotificationCenter.messagesDeleted); + getNotificationCenter().addObserver(locationController, NotificationCenter.replaceMessagesObjects); }); loadSharingLocations(); } @@ -210,28 +260,115 @@ public class LocationController implements NotificationCenter.NotificationCenter } } + @Override + public void onConnected(Bundle bundle) { + wasConnectedToPlayServices = true; + try { + if (Build.VERSION.SDK_INT >= 21) { + LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder().addLocationRequest(locationRequest); + PendingResult result = LocationServices.SettingsApi.checkLocationSettings(googleApiClient, builder.build()); + result.setResultCallback(locationSettingsResult -> { + final Status status = locationSettingsResult.getStatus(); + switch (status.getStatusCode()) { + case LocationSettingsStatusCodes.SUCCESS: + startFusedLocationRequest(true); + break; + case LocationSettingsStatusCodes.RESOLUTION_REQUIRED: + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.needShowPlayServicesAlert, status)); + break; + case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE: + Utilities.stageQueue.postRunnable(() -> { + playServicesAvailable = false; + try { + googleApiClient.disconnect(); + start(); + } catch (Throwable ignore) { + + } + }); + break; + } + }); + } else { + startFusedLocationRequest(true); + } + } catch (Throwable e) { + FileLog.e(e); + } + } + + public void startFusedLocationRequest(boolean permissionsGranted) { + Utilities.stageQueue.postRunnable(() -> { + if (!permissionsGranted) { + playServicesAvailable = false; + } + if (lookingForPeopleNearby || !sharingLocations.isEmpty()) { + if (permissionsGranted) { + try { + setLastKnownLocation(LocationServices.FusedLocationApi.getLastLocation(googleApiClient)); + LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, fusedLocationListener); + } catch (Throwable e) { + FileLog.e(e); + } + } else { + start(); + } + } + }); + } + + @Override + public void onConnectionSuspended(int i) { + + } + + @Override + public void onConnectionFailed(ConnectionResult connectionResult) { + if (wasConnectedToPlayServices) { + return; + } + playServicesAvailable = false; + if (started) { + started = false; + start(); + } + } + + private boolean checkPlayServices() { + if (playServicesAvailable == null) { + GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); + int resultCode = apiAvailability.isGooglePlayServicesAvailable(ApplicationLoader.applicationContext); + playServicesAvailable = resultCode == ConnectionResult.SUCCESS; + } + return playServicesAvailable; + } + private void broadcastLastKnownLocation() { if (lastKnownLocation == null) { return; } if (requests.size() != 0) { for (int a = 0; a < requests.size(); a++) { - ConnectionsManager.getInstance(currentAccount).cancelRequest(requests.keyAt(a), false); + getConnectionsManager().cancelRequest(requests.keyAt(a), false); } requests.clear(); } - int date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + int date = getConnectionsManager().getCurrentTime(); + float[] result = new float[1]; for (int a = 0; a < sharingLocations.size(); a++) { final SharingLocationInfo info = sharingLocations.get(a); if (info.messageObject.messageOwner.media != null && info.messageObject.messageOwner.media.geo != null) { int messageDate = info.messageObject.messageOwner.edit_date != 0 ? info.messageObject.messageOwner.edit_date : info.messageObject.messageOwner.date; TLRPC.GeoPoint point = info.messageObject.messageOwner.media.geo; - if (Math.abs(date - messageDate) < 30 && Math.abs(point.lat - lastKnownLocation.getLatitude()) <= eps && Math.abs(point._long - lastKnownLocation.getLongitude()) <= eps) { - continue; + if (Math.abs(date - messageDate) < 10) { + Location.distanceBetween(point.lat, point._long, lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude(), result); + if (result[0] < 1.0f) { + continue; + } } } TLRPC.TL_messages_editMessage req = new TLRPC.TL_messages_editMessage(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) info.did); + req.peer = getMessagesController().getInputPeer((int) info.did); req.id = info.mid; req.flags |= 16384; req.media = new TLRPC.TL_inputMediaGeoLive(); @@ -240,7 +377,7 @@ public class LocationController implements NotificationCenter.NotificationCenter req.media.geo_point.lat = AndroidUtilities.fixLocationCoord(lastKnownLocation.getLatitude()); req.media.geo_point._long = AndroidUtilities.fixLocationCoord(lastKnownLocation.getLongitude()); final int[] reqId = new int[1]; - reqId[0] = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + reqId[0] = getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null) { if (error.text.equals("MESSAGE_ID_INVALID")) { sharingLocations.remove(info); @@ -273,11 +410,11 @@ public class LocationController implements NotificationCenter.NotificationCenter if (updated) { saveSharingLocation(info, 0); } - MessagesController.getInstance(currentAccount).processUpdates(updates, false); + getMessagesController().processUpdates(updates, false); }); requests.put(reqId[0], 0); } - ConnectionsManager.getInstance(currentAccount).resumeNetworkMaybe(); + getConnectionsManager().resumeNetworkMaybe(); stop(false); } @@ -287,7 +424,7 @@ public class LocationController implements NotificationCenter.NotificationCenter } for (int a = 0; a < sharingLocations.size(); a++) { final SharingLocationInfo info = sharingLocations.get(a); - int currentTime = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + int currentTime = getConnectionsManager().getCurrentTime(); if (info.stopTime <= currentTime) { sharingLocations.remove(a); sharingLocationsMap.remove(info.did); @@ -304,15 +441,15 @@ public class LocationController implements NotificationCenter.NotificationCenter } } if (!started) { - if (Math.abs(lastLocationSendTime - System.currentTimeMillis()) > BACKGROUD_UPDATE_TIME) { - lastLocationStartTime = System.currentTimeMillis(); + if (Math.abs(lastLocationSendTime - SystemClock.uptimeMillis()) > BACKGROUD_UPDATE_TIME) { + lastLocationStartTime = SystemClock.uptimeMillis(); start(); } } else { - if (lastLocationByGoogleMaps || Math.abs(lastLocationStartTime - System.currentTimeMillis()) > LOCATION_ACQUIRE_TIME) { + if (lastLocationByGoogleMaps || Math.abs(lastLocationStartTime - SystemClock.uptimeMillis()) > LOCATION_ACQUIRE_TIME) { lastLocationByGoogleMaps = false; locationSentSinceLastGoogleMapUpdate = true; - lastLocationSendTime = System.currentTimeMillis(); + lastLocationSendTime = SystemClock.uptimeMillis(); broadcastLastKnownLocation(); } } @@ -323,23 +460,45 @@ public class LocationController implements NotificationCenter.NotificationCenter sharingLocationsMapUI.clear(); locationsCache.clear(); cacheRequests.clear(); + cachedNearbyUsers.clear(); + cachedNearbyChats.clear(); stopService(); Utilities.stageQueue.postRunnable(() -> { requests.clear(); sharingLocationsMap.clear(); sharingLocations.clear(); - lastKnownLocation = null; + setLastKnownLocation(null); stop(true); }); } + private void setLastKnownLocation(Location location) { + lastKnownLocation = location; + if (lastKnownLocation != null) { + AndroidUtilities.runOnUIThread(() -> NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.newLocationAvailable)); + } + } + + public void setCachedNearbyUsersAndChats(ArrayList u, ArrayList c) { + cachedNearbyUsers = new ArrayList<>(u); + cachedNearbyChats = new ArrayList<>(c); + } + + public ArrayList getCachedNearbyUsers() { + return cachedNearbyUsers; + } + + public ArrayList getCachedNearbyChats() { + return cachedNearbyChats; + } + protected void addSharingLocation(long did, int mid, int period, TLRPC.Message message) { final SharingLocationInfo info = new SharingLocationInfo(); info.did = did; info.mid = mid; info.period = period; info.messageObject = new MessageObject(currentAccount, message, false); - info.stopTime = ConnectionsManager.getInstance(currentAccount).getCurrentTime() + period; + info.stopTime = getConnectionsManager().getCurrentTime() + period; final SharingLocationInfo old = sharingLocationsMap.get(did); sharingLocationsMap.put(did, info); if (old != null) { @@ -347,7 +506,7 @@ public class LocationController implements NotificationCenter.NotificationCenter } sharingLocations.add(info); saveSharingLocation(info, 0); - lastLocationSendTime = System.currentTimeMillis() - BACKGROUD_UPDATE_TIME + 5000; + lastLocationSendTime = SystemClock.uptimeMillis() - BACKGROUD_UPDATE_TIME + 5000; AndroidUtilities.runOnUIThread(() -> { if (old != null) { sharingLocationsUI.remove(old); @@ -368,14 +527,14 @@ public class LocationController implements NotificationCenter.NotificationCenter } private void loadSharingLocations() { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { final ArrayList result = new ArrayList<>(); final ArrayList users = new ArrayList<>(); final ArrayList chats = new ArrayList<>(); try { ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized("SELECT uid, mid, date, period, message FROM sharing_locations WHERE 1"); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT uid, mid, date, period, message FROM sharing_locations WHERE 1"); while (cursor.next()) { SharingLocationInfo info = new SharingLocationInfo(); info.did = cursor.longValue(0); @@ -409,18 +568,18 @@ public class LocationController implements NotificationCenter.NotificationCenter } cursor.dispose(); if (!chatsToLoad.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getChatsInternal(TextUtils.join(",", chatsToLoad), chats); + getMessagesStorage().getChatsInternal(TextUtils.join(",", chatsToLoad), chats); } if (!usersToLoad.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getUsersInternal(TextUtils.join(",", usersToLoad), users); + getMessagesStorage().getUsersInternal(TextUtils.join(",", usersToLoad), users); } } catch (Exception e) { FileLog.e(e); } if (!result.isEmpty()) { AndroidUtilities.runOnUIThread(() -> { - MessagesController.getInstance(currentAccount).putUsers(users, true); - MessagesController.getInstance(currentAccount).putChats(chats, true); + getMessagesController().putUsers(users, true); + getMessagesController().putChats(chats, true); Utilities.stageQueue.postRunnable(() -> { sharingLocations.addAll(result); for (int a = 0; a < sharingLocations.size(); a++) { @@ -443,20 +602,20 @@ public class LocationController implements NotificationCenter.NotificationCenter } private void saveSharingLocation(final SharingLocationInfo info, final int remove) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { if (remove == 2) { - MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("DELETE FROM sharing_locations WHERE 1").stepThis().dispose(); + getMessagesStorage().getDatabase().executeFast("DELETE FROM sharing_locations WHERE 1").stepThis().dispose(); } else if (remove == 1) { if (info == null) { return; } - MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("DELETE FROM sharing_locations WHERE uid = " + info.did).stepThis().dispose(); + getMessagesStorage().getDatabase().executeFast("DELETE FROM sharing_locations WHERE uid = " + info.did).stepThis().dispose(); } else { if (info == null) { return; } - SQLitePreparedStatement state = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("REPLACE INTO sharing_locations VALUES(?, ?, ?, ?, ?)"); + SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO sharing_locations VALUES(?, ?, ?, ?, ?)"); state.requery(); NativeByteBuffer data = new NativeByteBuffer(info.messageObject.messageOwner.getObjectSize()); @@ -484,17 +643,17 @@ public class LocationController implements NotificationCenter.NotificationCenter sharingLocationsMap.remove(did); if (info != null) { TLRPC.TL_messages_editMessage req = new TLRPC.TL_messages_editMessage(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) info.did); + req.peer = getMessagesController().getInputPeer((int) info.did); req.id = info.mid; req.flags |= 16384; req.media = new TLRPC.TL_inputMediaGeoLive(); req.media.stopped = true; req.media.geo_point = new TLRPC.TL_inputGeoPointEmpty(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null) { return; } - MessagesController.getInstance(currentAccount).processUpdates((TLRPC.Updates) response, false); + getMessagesController().processUpdates((TLRPC.Updates) response, false); }); sharingLocations.remove(info); saveSharingLocation(info, 1); @@ -510,7 +669,6 @@ public class LocationController implements NotificationCenter.NotificationCenter stop(true); } } - }); } @@ -535,17 +693,17 @@ public class LocationController implements NotificationCenter.NotificationCenter for (int a = 0; a < sharingLocations.size(); a++) { SharingLocationInfo info = sharingLocations.get(a); TLRPC.TL_messages_editMessage req = new TLRPC.TL_messages_editMessage(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) info.did); + req.peer = getMessagesController().getInputPeer((int) info.did); req.id = info.mid; req.flags |= 16384; req.media = new TLRPC.TL_inputMediaGeoLive(); req.media.stopped = true; req.media.geo_point = new TLRPC.TL_inputGeoPointEmpty(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null) { return; } - MessagesController.getInstance(currentAccount).processUpdates((TLRPC.Updates) response, false); + getMessagesController().processUpdates((TLRPC.Updates) response, false); }); } sharingLocations.clear(); @@ -567,50 +725,72 @@ public class LocationController implements NotificationCenter.NotificationCenter } lastLocationByGoogleMaps = true; if (first || lastKnownLocation != null && lastKnownLocation.distanceTo(location) >= 20) { - lastLocationSendTime = System.currentTimeMillis() - BACKGROUD_UPDATE_TIME; + lastLocationSendTime = SystemClock.uptimeMillis() - BACKGROUD_UPDATE_TIME; locationSentSinceLastGoogleMapUpdate = false; } else if (locationSentSinceLastGoogleMapUpdate) { - lastLocationSendTime = System.currentTimeMillis() - BACKGROUD_UPDATE_TIME + FOREGROUND_UPDATE_TIME; + lastLocationSendTime = SystemClock.uptimeMillis() - BACKGROUD_UPDATE_TIME + FOREGROUND_UPDATE_TIME; locationSentSinceLastGoogleMapUpdate = false; } - lastKnownLocation = location; + setLastKnownLocation(location); } private void start() { if (started) { return; } - lastLocationStartTime = System.currentTimeMillis(); + lastLocationStartTime = SystemClock.uptimeMillis(); started = true; - try { - locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1, 0, gpsLocationListener); - } catch (Exception e) { - FileLog.e(e); - } - try { - locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1, 0, networkLocationListener); - } catch (Exception e) { - FileLog.e(e); - } - try { - locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 1, 0, passiveLocationListener); - } catch (Exception e) { - FileLog.e(e); - } - if (lastKnownLocation == null) { + boolean ok = false; + if (checkPlayServices()) { try { - lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); - if (lastKnownLocation == null) { - lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); - } + googleApiClient.connect(); + ok = true; + } catch (Throwable e) { + FileLog.e(e); + } + } + if (!ok) { + try { + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1, 0, gpsLocationListener); } catch (Exception e) { FileLog.e(e); } + try { + locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1, 0, networkLocationListener); + } catch (Exception e) { + FileLog.e(e); + } + try { + locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 1, 0, passiveLocationListener); + } catch (Exception e) { + FileLog.e(e); + } + if (lastKnownLocation == null) { + try { + setLastKnownLocation(locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)); + if (lastKnownLocation == null) { + setLastKnownLocation(locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)); + } + } catch (Exception e) { + FileLog.e(e); + } + } } } private void stop(boolean empty) { + if (lookingForPeopleNearby) { + return; + } started = false; + if (checkPlayServices()) { + try { + LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, fusedLocationListener); + googleApiClient.disconnect(); + } catch (Throwable e) { + FileLog.e(e); + } + } locationManager.removeUpdates(gpsLocationListener); if (empty) { locationManager.removeUpdates(networkLocationListener); @@ -618,15 +798,30 @@ public class LocationController implements NotificationCenter.NotificationCenter } } + public void startLocationLookupForPeopleNearby(boolean stop) { + Utilities.stageQueue.postRunnable(() -> { + lookingForPeopleNearby = !stop; + if (lookingForPeopleNearby) { + start(); + } else if (sharingLocations.isEmpty()) { + stop(true); + } + }); + } + + public Location getLastKnownLocation() { + return lastKnownLocation; + } + public void loadLiveLocations(final long did) { if (cacheRequests.indexOfKey(did) >= 0) { return; } cacheRequests.put(did, true); TLRPC.TL_messages_getRecentLocations req = new TLRPC.TL_messages_getRecentLocations(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) did); + req.peer = getMessagesController().getInputPeer((int) did); req.limit = 100; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null) { return; } @@ -639,9 +834,9 @@ public class LocationController implements NotificationCenter.NotificationCenter a--; } } - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, res.chats, true, true); - MessagesController.getInstance(currentAccount).putUsers(res.users, false); - MessagesController.getInstance(currentAccount).putChats(res.chats, false); + getMessagesStorage().putUsersAndChats(res.users, res.chats, true, true); + getMessagesController().putUsers(res.users, false); + getMessagesController().putChats(res.chats, false); locationsCache.put(did, res.messages); NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.liveLocationsCacheChanged, did, currentAccount); }); @@ -655,4 +850,134 @@ public class LocationController implements NotificationCenter.NotificationCenter } return count; } + + public interface LocationFetchCallback { + void onLocationAddressAvailable(String address, String displayAddress, Location location); + } + + private static HashMap callbacks = new HashMap<>(); + public static void fetchLocationAddress(Location location, LocationFetchCallback callback) { + if (callback == null) { + return; + } + Runnable fetchLocationRunnable = callbacks.get(callback); + if (fetchLocationRunnable != null) { + Utilities.globalQueue.cancelRunnable(fetchLocationRunnable); + callbacks.remove(callback); + } + if (location == null) { + if (callback != null) { + callback.onLocationAddressAvailable(null, null, null); + } + return; + } + + Utilities.globalQueue.postRunnable(fetchLocationRunnable = () -> { + String name; + String displayName; + try { + Geocoder gcd = new Geocoder(ApplicationLoader.applicationContext, LocaleController.getInstance().getSystemDefaultLocale()); + List
addresses = gcd.getFromLocation(location.getLatitude(), location.getLongitude(), 1); + if (addresses.size() > 0) { + Address address = addresses.get(0); + boolean hasAny = false; + String arg; + + StringBuilder nameBuilder = new StringBuilder(); + StringBuilder displayNameBuilder = new StringBuilder(); + + arg = address.getSubThoroughfare(); + if (!TextUtils.isEmpty(arg)) { + nameBuilder.append(arg); + hasAny = true; + } + arg = address.getThoroughfare(); + if (!TextUtils.isEmpty(arg)) { + if (nameBuilder.length() > 0) { + nameBuilder.append(", "); + } + nameBuilder.append(arg); + hasAny = true; + } + if (!hasAny) { + arg = address.getAdminArea(); + if (!TextUtils.isEmpty(arg)) { + if (nameBuilder.length() > 0) { + nameBuilder.append(", "); + } + nameBuilder.append(arg); + } + arg = address.getSubAdminArea(); + if (!TextUtils.isEmpty(arg)) { + if (nameBuilder.length() > 0) { + nameBuilder.append(", "); + } + nameBuilder.append(arg); + } + } + arg = address.getLocality(); + if (!TextUtils.isEmpty(arg)) { + if (nameBuilder.length() > 0) { + nameBuilder.append(", "); + } + nameBuilder.append(arg); + } + arg = address.getCountryName(); + if (!TextUtils.isEmpty(arg)) { + if (nameBuilder.length() > 0) { + nameBuilder.append(", "); + } + nameBuilder.append(arg); + } + + arg = address.getCountryName(); + if (!TextUtils.isEmpty(arg)) { + if (displayNameBuilder.length() > 0) { + displayNameBuilder.append(", "); + } + displayNameBuilder.append(arg); + } + arg = address.getLocality(); + if (!TextUtils.isEmpty(arg)) { + if (displayNameBuilder.length() > 0) { + displayNameBuilder.append(", "); + } + displayNameBuilder.append(arg); + } + if (!hasAny) { + arg = address.getAdminArea(); + if (!TextUtils.isEmpty(arg)) { + if (displayNameBuilder.length() > 0) { + displayNameBuilder.append(", "); + } + displayNameBuilder.append(arg); + } + arg = address.getSubAdminArea(); + if (!TextUtils.isEmpty(arg)) { + if (displayNameBuilder.length() > 0) { + displayNameBuilder.append(", "); + } + displayNameBuilder.append(arg); + } + } + + name = nameBuilder.toString(); + displayName = displayNameBuilder.toString(); + } else { + name = displayName = String.format(Locale.US, "Unknown address (%f,%f)", location.getLatitude(), location.getLongitude()); + } + } catch (Exception ignore) { + name = displayName = String.format(Locale.US, "Unknown address (%f,%f)", location.getLatitude(), location.getLongitude()); + } + final String nameFinal = name; + final String displayNameFinal = displayName; + AndroidUtilities.runOnUIThread(() -> { + callbacks.remove(callback); + if (callback != null) { + callback.onLocationAddressAvailable(nameFinal, displayNameFinal, location); + } + }); + }, 300); + callbacks.put(callback, fetchLocationRunnable); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/LocationSharingService.java b/TMessagesProj/src/main/java/org/telegram/messenger/LocationSharingService.java index c325d1964..76b1503c6 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/LocationSharingService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/LocationSharingService.java @@ -36,20 +36,15 @@ public class LocationSharingService extends Service implements NotificationCente public void onCreate() { super.onCreate(); handler = new Handler(); - runnable = new Runnable() { - public void run() { - handler.postDelayed(runnable, 60000); - Utilities.stageQueue.postRunnable(new Runnable() { - @Override - public void run() { - for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { - LocationController.getInstance(a).update(); - } - } - }); - } + runnable = () -> { + handler.postDelayed(runnable, 1000); + Utilities.stageQueue.postRunnable(() -> { + for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { + LocationController.getInstance(a).update(); + } + }); }; - handler.postDelayed(runnable, 60000); + handler.postDelayed(runnable, 1000); } public IBinder onBind(Intent arg2) { @@ -61,6 +56,7 @@ public class LocationSharingService extends Service implements NotificationCente handler.removeCallbacks(runnable); } stopForeground(true); + NotificationManagerCompat.from(ApplicationLoader.applicationContext).cancel(6); NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.liveLocationsChanged); } @@ -68,15 +64,12 @@ public class LocationSharingService extends Service implements NotificationCente public void didReceivedNotification(int id, int account, Object... args) { if (id == NotificationCenter.liveLocationsChanged) { if (handler != null) { - handler.post(new Runnable() { - @Override - public void run() { - ArrayList infos = getInfos(); - if (infos.isEmpty()) { - stopSelf(); - } else { - updateNotification(true); - } + handler.post(() -> { + ArrayList infos = getInfos(); + if (infos.isEmpty()) { + stopSelf(); + } else { + updateNotification(true); } }); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/LruCache.java b/TMessagesProj/src/main/java/org/telegram/messenger/LruCache.java index 2c0bf7b9d..9c761cc5b 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/LruCache.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/LruCache.java @@ -71,6 +71,13 @@ public class LruCache { return null; } + public void moveToFront(String key) { + T value = map.remove(key); + if (value != null) { + map.put(key, value); + } + } + /** * Caches {@code value} for {@code key}. The value is moved to the head of * the queue. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java index 740ef849a..58b81bf9f 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java @@ -1728,7 +1728,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, currentPlaylistNum = 0; } if (loadMusic) { - DataQuery.getInstance(current.currentAccount).loadMusic(current.getDialogId(), playlist.get(0).getIdWithChannel()); + MediaDataController.getInstance(current.currentAccount).loadMusic(current.getDialogId(), playlist.get(0).getIdWithChannel()); } } return playMessage(current); @@ -3825,7 +3825,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, checkConversionCanceled(); - if (resultWidth != originalWidth || resultHeight != originalHeight || rotateRender != 0 || messageObject.videoEditedInfo.roundVideo) { + if (resultWidth != originalWidth || resultHeight != originalHeight || rotateRender != 0 || messageObject.videoEditedInfo.roundVideo || Build.VERSION.SDK_INT >= 18 && startTime != -1) { int videoIndex = findTrack(extractor, false); int audioIndex = bitrate != -1 ? findTrack(extractor, true) : -1; if (videoIndex >= 0) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/DataQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java similarity index 80% rename from TMessagesProj/src/main/java/org/telegram/messenger/DataQuery.java rename to TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java index 786d3eb48..89c4d4c09 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/DataQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java @@ -28,10 +28,12 @@ import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Build; +import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.SpannedString; import android.text.TextUtils; +import android.text.style.CharacterStyle; import android.util.LongSparseArray; import android.util.SparseArray; import android.widget.Toast; @@ -48,7 +50,7 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.StickersArchiveAlert; -import org.telegram.ui.Components.TypefaceSpan; +import org.telegram.ui.Components.TextStyleSpan; import org.telegram.ui.Components.URLSpanReplacement; import org.telegram.ui.Components.URLSpanUserMention; import org.telegram.ui.LaunchActivity; @@ -64,25 +66,24 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; @SuppressWarnings("unchecked") -public class DataQuery { +public class MediaDataController extends BaseController { - private int currentAccount; - private static volatile DataQuery[] Instance = new DataQuery[3]; - public static DataQuery getInstance(int num) { - DataQuery localInstance = Instance[num]; + private static volatile MediaDataController[] Instance = new MediaDataController[UserConfig.MAX_ACCOUNT_COUNT]; + public static MediaDataController getInstance(int num) { + MediaDataController localInstance = Instance[num]; if (localInstance == null) { - synchronized (DataQuery.class) { + synchronized (MediaDataController.class) { localInstance = Instance[num]; if (localInstance == null) { - Instance[num] = localInstance = new DataQuery(num); + Instance[num] = localInstance = new MediaDataController(num); } } } return localInstance; } - public DataQuery(int num) { - currentAccount = num; + public MediaDataController(int num) { + super(num); if (currentAccount == 0) { preferences = ApplicationLoader.applicationContext.getSharedPreferences("drafts", Activity.MODE_PRIVATE); @@ -98,7 +99,7 @@ public class DataQuery { SerializedData serializedData = new SerializedData(bytes); if (key.startsWith("r_")) { TLRPC.Message message = TLRPC.Message.TLdeserialize(serializedData, serializedData.readInt32(true), true); - message.readAttachPath(serializedData, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(serializedData, getUserConfig().clientUserId); if (message != null) { draftMessages.put(did, message); } @@ -190,8 +191,8 @@ public class DataQuery { loaded = false; hints.clear(); inlineBots.clear(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.reloadHints); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.reloadInlineHints); + getNotificationCenter().postNotificationName(NotificationCenter.reloadHints); + getNotificationCenter().postNotificationName(NotificationCenter.reloadInlineHints); drafts.clear(); draftMessages.clear(); @@ -265,18 +266,18 @@ public class DataQuery { req.id.file_reference = new byte[0]; } req.unfave = remove; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null && FileRefController.isFileRefError(error.text) && parentObject != null) { - FileRefController.getInstance(currentAccount).requestReference(parentObject, req); + getFileRefController().requestReference(parentObject, req); } }); - maxCount = MessagesController.getInstance(currentAccount).maxFaveStickersCount; + maxCount = getMessagesController().maxFaveStickersCount; } else { - maxCount = MessagesController.getInstance(currentAccount).maxRecentStickersCount; + maxCount = getMessagesController().maxRecentStickersCount; } if (recentStickers[type].size() > maxCount || remove) { final TLRPC.Document old = remove ? document : recentStickers[type].remove(recentStickers[type].size() - 1); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { int cacheType; if (type == TYPE_IMAGE) { cacheType = 3; @@ -286,7 +287,7 @@ public class DataQuery { cacheType = 5; } try { - MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + old.id + "' AND type = " + cacheType).stepThis().dispose(); + getMessagesStorage().getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + old.id + "' AND type = " + cacheType).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } @@ -298,7 +299,7 @@ public class DataQuery { processLoadedRecentDocuments(type, arrayList, false, date, false); } if (type == TYPE_FAVE) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.recentDocumentsDidLoad, false, type); + getNotificationCenter().postNotificationName(NotificationCenter.recentDocumentsDidLoad, false, type); } } @@ -317,14 +318,14 @@ public class DataQuery { req.id.file_reference = new byte[0]; } req.unsave = true; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null && FileRefController.isFileRefError(error.text)) { - FileRefController.getInstance(currentAccount).requestReference("gif", req); + getFileRefController().requestReference("gif", req); } }); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { - MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + document.id + "' AND type = 2").stepThis().dispose(); + getMessagesStorage().getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + document.id + "' AND type = 2").stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } @@ -357,11 +358,11 @@ public class DataQuery { if (!found) { recentGifs.add(0, document); } - if (recentGifs.size() > MessagesController.getInstance(currentAccount).maxRecentGifsCount) { + if (recentGifs.size() > getMessagesController().maxRecentGifsCount) { final TLRPC.Document old = recentGifs.remove(recentGifs.size() - 1); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { - MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + old.id + "' AND type = 2").stepThis().dispose(); + getMessagesStorage().getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + old.id + "' AND type = 2").stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } @@ -442,10 +443,10 @@ public class DataQuery { private void loadGroupStickerSet(final TLRPC.StickerSet stickerSet, boolean cache) { if (cache) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { final TLRPC.TL_messages_stickerSet set; - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized("SELECT document FROM web_recent_v3 WHERE id = 's_" + stickerSet.id + "'"); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT document FROM web_recent_v3 WHERE id = 's_" + stickerSet.id + "'"); if (cursor.next() && !cursor.isNull(0)) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { @@ -464,7 +465,7 @@ public class DataQuery { if (set != null && set.set != null) { AndroidUtilities.runOnUIThread(() -> { groupStickerSets.put(set.set.id, set); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.groupStickersDidLoad, set.set.id); + getNotificationCenter().postNotificationName(NotificationCenter.groupStickersDidLoad, set.set.id); }); } } catch (Throwable e) { @@ -476,12 +477,12 @@ public class DataQuery { req.stickerset = new TLRPC.TL_inputStickerSetID(); req.stickerset.id = stickerSet.id; req.stickerset.access_hash = stickerSet.access_hash; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { TLRPC.TL_messages_stickerSet set = (TLRPC.TL_messages_stickerSet) response; AndroidUtilities.runOnUIThread(() -> { groupStickerSets.put(set.set.id, set); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.groupStickersDidLoad, set.set.id); + getNotificationCenter().postNotificationName(NotificationCenter.groupStickersDidLoad, set.set.id); }); } }); @@ -489,9 +490,9 @@ public class DataQuery { } private void putSetToCache(TLRPC.TL_messages_stickerSet set) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { - SQLiteDatabase database = MessagesStorage.getInstance(currentAccount).getDatabase(); + SQLiteDatabase database = getMessagesStorage().getDatabase(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO web_recent_v3 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); state.requery(); state.bindString(1, "s_" + set.set.id); @@ -546,7 +547,7 @@ public class DataQuery { public boolean areAllTrendingStickerSetsUnread() { for (int a = 0, N = featuredStickerSets.size(); a < N; a++) { TLRPC.StickerSetCovered pack = featuredStickerSets.get(a); - if (DataQuery.getInstance(currentAccount).isStickerPackInstalled(pack.set.id) || pack.covers.isEmpty() && pack.cover == null) { + if (isStickerPackInstalled(pack.set.id) || pack.covers.isEmpty() && pack.cover == null) { continue; } if (!unreadStickerSets.contains(pack.set.id)) { @@ -610,7 +611,7 @@ public class DataQuery { } } if (cache) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { final int cacheType; if (gif) { @@ -622,7 +623,7 @@ public class DataQuery { } else { cacheType = 5; } - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized("SELECT document FROM web_recent_v3 WHERE type = " + cacheType + " ORDER BY date DESC"); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT document FROM web_recent_v3 WHERE type = " + cacheType + " ORDER BY date DESC"); final ArrayList arrayList = new ArrayList<>(); while (cursor.next()) { if (!cursor.isNull(0)) { @@ -647,7 +648,7 @@ public class DataQuery { loadingRecentStickers[type] = false; recentStickersLoaded[type] = true; } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.recentDocumentsDidLoad, gif, type); + getNotificationCenter().postNotificationName(NotificationCenter.recentDocumentsDidLoad, gif, type); loadRecents(type, gif, false, false); }); } catch (Throwable e) { @@ -679,7 +680,7 @@ public class DataQuery { if (gif) { TLRPC.TL_messages_getSavedGifs req = new TLRPC.TL_messages_getSavedGifs(); req.hash = calcDocumentsHash(recentGifs); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { ArrayList arrayList = null; if (response instanceof TLRPC.TL_messages_savedGifs) { TLRPC.TL_messages_savedGifs res = (TLRPC.TL_messages_savedGifs) response; @@ -699,7 +700,7 @@ public class DataQuery { req.attached = type == TYPE_MASK; request = req; } - ConnectionsManager.getInstance(currentAccount).sendRequest(request, (response, error) -> { + getConnectionsManager().sendRequest(request, (response, error) -> { ArrayList arrayList = null; if (type == TYPE_FAVE) { if (response instanceof TLRPC.TL_messages_favedStickers) { @@ -720,17 +721,17 @@ public class DataQuery { protected void processLoadedRecentDocuments(final int type, final ArrayList documents, final boolean gif, final int date, boolean replace) { if (documents != null) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { - SQLiteDatabase database = MessagesStorage.getInstance(currentAccount).getDatabase(); + SQLiteDatabase database = getMessagesStorage().getDatabase(); int maxCount; if (gif) { - maxCount = MessagesController.getInstance(currentAccount).maxRecentGifsCount; + maxCount = getMessagesController().maxRecentGifsCount; } else { if (type == TYPE_FAVE) { - maxCount = MessagesController.getInstance(currentAccount).maxFaveStickersCount; + maxCount = getMessagesController().maxFaveStickersCount; } else { - maxCount = MessagesController.getInstance(currentAccount).maxRecentStickersCount; + maxCount = getMessagesController().maxRecentStickersCount; } } database.beginTransaction(); @@ -811,7 +812,7 @@ public class DataQuery { } else { recentStickers[type] = documents; } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.recentDocumentsDidLoad, gif, type); + getNotificationCenter().postNotificationName(NotificationCenter.recentDocumentsDidLoad, gif, type); } else { } @@ -831,7 +832,7 @@ public class DataQuery { return 0; }); loadHash[type] = calcStickersHash(stickerSets[type]); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.stickersDidLoad, type); + getNotificationCenter().postNotificationName(NotificationCenter.stickersDidLoad, type); loadStickers(type, false, true); } @@ -873,7 +874,7 @@ public class DataQuery { } } loadHash[type] = calcStickersHash(stickerSets[type]); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.stickersDidLoad, type); + getNotificationCenter().postNotificationName(NotificationCenter.stickersDidLoad, type); loadStickers(type, false, true); } @@ -883,14 +884,14 @@ public class DataQuery { } loadingFeaturedStickers = true; if (cache) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { ArrayList newStickerArray = null; ArrayList unread = new ArrayList<>(); int date = 0; int hash = 0; SQLiteCursor cursor = null; try { - cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized("SELECT data, unread, date, hash FROM stickers_featured WHERE 1"); + cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT data, unread, date, hash FROM stickers_featured WHERE 1"); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { @@ -925,7 +926,7 @@ public class DataQuery { } else { final TLRPC.TL_messages_getFeaturedStickers req = new TLRPC.TL_messages_getFeaturedStickers(); req.hash = force ? 0 : loadFeaturedHash; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (response instanceof TLRPC.TL_messages_featuredStickers) { TLRPC.TL_messages_featuredStickers res = (TLRPC.TL_messages_featuredStickers) response; processLoadedFeaturedStickers(res.sets, res.unread, false, (int) (System.currentTimeMillis() / 1000), res.hash); @@ -974,7 +975,7 @@ public class DataQuery { loadFeaturedHash = hash; loadFeaturedDate = date; loadStickers(TYPE_FEATURED, true, false); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.featuredStickersDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.featuredStickersDidLoad); }); } catch (Throwable e) { FileLog.e(e); @@ -988,10 +989,10 @@ public class DataQuery { private void putFeaturedStickersToCache(ArrayList stickers, final ArrayList unreadStickers, final int date, final int hash) { final ArrayList stickersFinal = stickers != null ? new ArrayList<>(stickers) : null; - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { if (stickersFinal != null) { - SQLitePreparedStatement state = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("REPLACE INTO stickers_featured VALUES(?, ?, ?, ?, ?)"); + SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO stickers_featured VALUES(?, ?, ?, ?, ?)"); state.requery(); int size = 4; for (int a = 0; a < stickersFinal.size(); a++) { @@ -1017,7 +1018,7 @@ public class DataQuery { data2.reuse(); state.dispose(); } else { - SQLitePreparedStatement state = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("UPDATE stickers_featured SET date = ?"); + SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("UPDATE stickers_featured SET date = ?"); state.requery(); state.bindInteger(1, date); state.step(); @@ -1053,11 +1054,11 @@ public class DataQuery { } unreadStickerSets.clear(); loadFeaturedHash = calcFeaturedStickersHash(featuredStickerSets); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.featuredStickersDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.featuredStickersDidLoad); putFeaturedStickersToCache(featuredStickerSets, unreadStickerSets, loadFeaturedDate, loadFeaturedHash); if (query) { TLRPC.TL_messages_readFeaturedStickers req = new TLRPC.TL_messages_readFeaturedStickers(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); } @@ -1085,14 +1086,14 @@ public class DataQuery { readingStickerSets.add(id); TLRPC.TL_messages_readFeaturedStickers req = new TLRPC.TL_messages_readFeaturedStickers(); req.id.add(id); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); AndroidUtilities.runOnUIThread(() -> { unreadStickerSets.remove(id); readingStickerSets.remove(id); loadFeaturedHash = calcFeaturedStickersHash(featuredStickerSets); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.featuredStickersDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.featuredStickersDidLoad); putFeaturedStickersToCache(featuredStickerSets, unreadStickerSets, loadFeaturedDate, loadFeaturedHash); }, 1000); } @@ -1109,19 +1110,19 @@ public class DataQuery { loadArchivedStickersCount(type, false); } else { archivedStickersCount[type] = count; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.archivedStickersCountDidLoad, type); + getNotificationCenter().postNotificationName(NotificationCenter.archivedStickersCountDidLoad, type); } } else { TLRPC.TL_messages_getArchivedStickers req = new TLRPC.TL_messages_getArchivedStickers(); req.limit = 0; req.masks = type == TYPE_MASK; - int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + int reqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (error == null) { TLRPC.TL_messages_archivedStickers res = (TLRPC.TL_messages_archivedStickers) response; archivedStickersCount[type] = res.count; SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); preferences.edit().putInt("archivedStickersCount" + type, res.count).commit(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.archivedStickersCountDidLoad, type); + getNotificationCenter().postNotificationName(NotificationCenter.archivedStickersCountDidLoad, type); } })); } @@ -1158,7 +1159,7 @@ public class DataQuery { req.stickerset.id = stickerSet.id; req.stickerset.access_hash = stickerSet.access_hash; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { TLRPC.TL_messages_stickerSet res1 = (TLRPC.TL_messages_stickerSet) response; newStickerArray.set(index, res1); newStickerSets.put(stickerSet.id, res1); @@ -1181,7 +1182,7 @@ public class DataQuery { return; } if (type == TYPE_FEATURED) { - if (featuredStickerSets.isEmpty() || !MessagesController.getInstance(currentAccount).preloadFeaturedStickers) { + if (featuredStickerSets.isEmpty() || !getMessagesController().preloadFeaturedStickers) { return; } } else { @@ -1189,13 +1190,13 @@ public class DataQuery { } loadingStickers[type] = true; if (cache) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { ArrayList newStickerArray = null; int date = 0; int hash = 0; SQLiteCursor cursor = null; try { - cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized("SELECT data, date, hash FROM stickers_v2 WHERE id = " + (type + 1)); + cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT data, date, hash FROM stickers_v2 WHERE id = " + (type + 1)); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { @@ -1238,7 +1239,7 @@ public class DataQuery { req = new TLRPC.TL_messages_getMaskStickers(); hash = ((TLRPC.TL_messages_getMaskStickers) req).hash = force ? 0 : loadHash[type]; } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (response instanceof TLRPC.TL_messages_allStickers) { processLoadStickersResponse(type, (TLRPC.TL_messages_allStickers) response); } else { @@ -1250,10 +1251,10 @@ public class DataQuery { private void putStickersToCache(final int type, ArrayList stickers, final int date, final int hash) { final ArrayList stickersFinal = stickers != null ? new ArrayList<>(stickers) : null; - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { if (stickersFinal != null) { - SQLitePreparedStatement state = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("REPLACE INTO stickers_v2 VALUES(?, ?, ?, ?)"); + SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO stickers_v2 VALUES(?, ?, ?, ?)"); state.requery(); int size = 4; for (int a = 0; a < stickersFinal.size(); a++) { @@ -1272,7 +1273,7 @@ public class DataQuery { data.reuse(); state.dispose(); } else { - SQLitePreparedStatement state = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("UPDATE stickers_v2 SET date = ?"); + SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("UPDATE stickers_v2 SET date = ?"); state.requery(); state.bindInteger(1, date); state.step(); @@ -1429,7 +1430,7 @@ public class DataQuery { } else if (type == TYPE_FEATURED) { allStickersFeatured = allStickersNew; } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.stickersDidLoad, type); + getNotificationCenter().postNotificationName(NotificationCenter.stickersDidLoad, type); }); } catch (Throwable e) { FileLog.e(e); @@ -1464,14 +1465,14 @@ public class DataQuery { } loadHash[type] = calcStickersHash(stickerSets[type]); putStickersToCache(type, stickerSets[type], loadDate[type], loadHash[type]); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.stickersDidLoad, type); + getNotificationCenter().postNotificationName(NotificationCenter.stickersDidLoad, type); TLRPC.TL_messages_installStickerSet req = new TLRPC.TL_messages_installStickerSet(); req.stickerset = stickerSetID; req.archived = hide == 1; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { AndroidUtilities.runOnUIThread(() -> { if (response instanceof TLRPC.TL_messages_stickerSetInstallResultArchive) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.needReloadArchivedStickers, type); + getNotificationCenter().postNotificationName(NotificationCenter.needReloadArchivedStickers, type); if (hide != 1 && baseFragment != null && baseFragment.getParentActivity() != null) { StickersArchiveAlert alert = new StickersArchiveAlert(baseFragment.getParentActivity(), showSettings ? baseFragment : null, ((TLRPC.TL_messages_stickerSetInstallResultArchive) response).sets); baseFragment.showDialog(alert.create()); @@ -1483,7 +1484,7 @@ public class DataQuery { } else { TLRPC.TL_messages_uninstallStickerSet req = new TLRPC.TL_messages_uninstallStickerSet(); req.stickerset = stickerSetID; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { try { if (error == null) { if (stickerSet.masks) { @@ -1538,11 +1539,11 @@ public class DataQuery { long queryWithDialog = dialog_id; boolean firstQuery = !internal; if (reqId != 0) { - ConnectionsManager.getInstance(currentAccount).cancelRequest(reqId, true); + getConnectionsManager().cancelRequest(reqId, true); reqId = 0; } if (mergeReqId != 0) { - ConnectionsManager.getInstance(currentAccount).cancelRequest(mergeReqId, true); + getConnectionsManager().cancelRequest(mergeReqId, true); mergeReqId = 0; } if (query == null) { @@ -1553,7 +1554,7 @@ public class DataQuery { lastReturnedNum++; if (lastReturnedNum < searchResultMessages.size()) { MessageObject messageObject = searchResultMessages.get(lastReturnedNum); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatSearchResultsAvailable, guid, messageObject.getId(), getMask(), messageObject.getDialogId(), lastReturnedNum, messagesSearchCount[0] + messagesSearchCount[1]); + getNotificationCenter().postNotificationName(NotificationCenter.chatSearchResultsAvailable, guid, messageObject.getId(), getMask(), messageObject.getDialogId(), lastReturnedNum, messagesSearchCount[0] + messagesSearchCount[1]); return; } else { if (messagesSearchEndReached[0] && mergeDialogId == 0 && messagesSearchEndReached[1]) { @@ -1584,13 +1585,13 @@ public class DataQuery { lastReturnedNum = searchResultMessages.size() - 1; } MessageObject messageObject = searchResultMessages.get(lastReturnedNum); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatSearchResultsAvailable, guid, messageObject.getId(), getMask(), messageObject.getDialogId(), lastReturnedNum, messagesSearchCount[0] + messagesSearchCount[1]); + getNotificationCenter().postNotificationName(NotificationCenter.chatSearchResultsAvailable, guid, messageObject.getId(), getMask(), messageObject.getDialogId(), lastReturnedNum, messagesSearchCount[0] + messagesSearchCount[1]); return; } else { return; } } else if (firstQuery) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatSearchResultsLoading, guid); + getNotificationCenter().postNotificationName(NotificationCenter.chatSearchResultsLoading, guid); messagesSearchEndReached[0] = messagesSearchEndReached[1] = false; messagesSearchCount[0] = messagesSearchCount[1] = 0; searchResultMessages.clear(); @@ -1602,7 +1603,7 @@ public class DataQuery { } if (queryWithDialog == dialog_id && firstQuery) { if (mergeDialogId != 0) { - TLRPC.InputPeer inputPeer = MessagesController.getInstance(currentAccount).getInputPeer((int) mergeDialogId); + TLRPC.InputPeer inputPeer = getMessagesController().getInputPeer((int) mergeDialogId); if (inputPeer == null) { return; } @@ -1612,11 +1613,11 @@ public class DataQuery { req.limit = 1; req.q = query != null ? query : ""; if (user != null) { - req.from_id = MessagesController.getInstance(currentAccount).getInputUser(user); + req.from_id = getMessagesController().getInputUser(user); req.flags |= 1; } req.filter = new TLRPC.TL_inputMessagesFilterEmpty(); - mergeReqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + mergeReqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (lastMergeDialogId == mergeDialogId) { mergeReqId = 0; if (response != null) { @@ -1635,7 +1636,7 @@ public class DataQuery { } } final TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) queryWithDialog); + req.peer = getMessagesController().getInputPeer((int) queryWithDialog); if (req.peer == null) { return; } @@ -1643,14 +1644,14 @@ public class DataQuery { req.q = query != null ? query : ""; req.offset_id = max_id; if (user != null) { - req.from_id = MessagesController.getInstance(currentAccount).getInputUser(user); + req.from_id = getMessagesController().getInputUser(user); req.flags |= 1; } req.filter = new TLRPC.TL_inputMessagesFilterEmpty(); final int currentReqId = ++lastReqId; lastSearchQuery = query; final long queryWithDialogFinal = queryWithDialog; - reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + reqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (currentReqId == lastReqId) { reqId = 0; if (response != null) { @@ -1662,9 +1663,9 @@ public class DataQuery { a--; } } - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, res.chats, true, true); - MessagesController.getInstance(currentAccount).putUsers(res.users, false); - MessagesController.getInstance(currentAccount).putChats(res.chats, false); + getMessagesStorage().putUsersAndChats(res.users, res.chats, true, true); + getMessagesController().putUsers(res.users, false); + getMessagesController().putChats(res.chats, false); if (req.offset_id == 0 && queryWithDialogFinal == dialog_id) { lastReturnedNum = 0; searchResultMessages.clear(); @@ -1683,14 +1684,14 @@ public class DataQuery { messagesSearchEndReached[queryWithDialogFinal == dialog_id ? 0 : 1] = res.messages.size() != 21; messagesSearchCount[queryWithDialogFinal == dialog_id ? 0 : 1] = res instanceof TLRPC.TL_messages_messagesSlice || res instanceof TLRPC.TL_messages_channelMessages ? res.count : res.messages.size(); if (searchResultMessages.isEmpty()) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatSearchResultsAvailable, guid, 0, getMask(), (long) 0, 0, 0); + getNotificationCenter().postNotificationName(NotificationCenter.chatSearchResultsAvailable, guid, 0, getMask(), (long) 0, 0, 0); } else { if (added) { if (lastReturnedNum >= searchResultMessages.size()) { lastReturnedNum = searchResultMessages.size() - 1; } MessageObject messageObject = searchResultMessages.get(lastReturnedNum); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatSearchResultsAvailable, guid, messageObject.getId(), getMask(), messageObject.getDialogId(), lastReturnedNum, messagesSearchCount[0] + messagesSearchCount[1]); + getNotificationCenter().postNotificationName(NotificationCenter.chatSearchResultsAvailable, guid, messageObject.getId(), getMask(), messageObject.getDialogId(), lastReturnedNum, messagesSearchCount[0] + messagesSearchCount[1]); } } if (queryWithDialogFinal == dialog_id && messagesSearchEndReached[0] && mergeDialogId != 0 && !messagesSearchEndReached[1]) { @@ -1736,28 +1737,28 @@ public class DataQuery { req.filter = new TLRPC.TL_inputMessagesFilterMusic(); } req.q = ""; - req.peer = MessagesController.getInstance(currentAccount).getInputPeer(lower_part); + req.peer = getMessagesController().getInputPeer(lower_part); if (req.peer == null) { return; } - int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + int reqId = getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; - MessagesController.getInstance(currentAccount).removeDeletedMessagesFromArray(uid, res.messages); + getMessagesController().removeDeletedMessagesFromArray(uid, res.messages); processLoadedMedia(res, uid, count, max_id, type, 0, classGuid, isChannel, res.messages.size() == 0); } }); - ConnectionsManager.getInstance(currentAccount).bindRequestToGuid(reqId, classGuid); + getConnectionsManager().bindRequestToGuid(reqId, classGuid); } } public void getMediaCounts(final long uid, final int classGuid) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { int[] counts = new int[] {-1, -1, -1, -1, -1}; int[] countsFinal = new int[] {-1, -1, -1, -1, -1}; int[] old = new int[] {0, 0, 0, 0, 0}; - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT type, count, old FROM media_counts_v2 WHERE uid = %d", uid)); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT type, count, old FROM media_counts_v2 WHERE uid = %d", uid)); while (cursor.next()) { int type = cursor.intValue(0); if (type >= 0 && type < MEDIA_TYPES_COUNT) { @@ -1770,7 +1771,7 @@ public class DataQuery { if (lower_part == 0) { for (int a = 0; a < counts.length; a++) { if (counts[a] == -1) { - cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM media_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, a)); + cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM media_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, a)); if (cursor.next()) { counts[a] = cursor.intValue(0); } else { @@ -1780,7 +1781,7 @@ public class DataQuery { putMediaCountDatabase(uid, a, counts[a]); } } - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.mediaCountsDidLoad, uid, counts)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.mediaCountsDidLoad, uid, counts)); } else { boolean missing = false; for (int a = 0; a < counts.length; a++) { @@ -1802,12 +1803,12 @@ public class DataQuery { req.filter = new TLRPC.TL_inputMessagesFilterMusic(); } req.q = ""; - req.peer = MessagesController.getInstance(currentAccount).getInputPeer(lower_part); + req.peer = getMessagesController().getInputPeer(lower_part); if (req.peer == null) { counts[a] = 0; continue; } - int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + int reqId = getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; if (res instanceof TLRPC.TL_messages_messages) { @@ -1827,10 +1828,10 @@ public class DataQuery { } } if (finished) { - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.mediaCountsDidLoad, uid, counts)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.mediaCountsDidLoad, uid, counts)); } }); - ConnectionsManager.getInstance(currentAccount).bindRequestToGuid(reqId, classGuid); + getConnectionsManager().bindRequestToGuid(reqId, classGuid); if (counts[a] == -1) { missing = true; } else if (old[a] == 1) { @@ -1839,7 +1840,7 @@ public class DataQuery { } } if (!missing) { - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.mediaCountsDidLoad, uid, countsFinal)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.mediaCountsDidLoad, uid, countsFinal)); } } } catch (Exception e) { @@ -1868,14 +1869,14 @@ public class DataQuery { req.filter = new TLRPC.TL_inputMessagesFilterMusic(); } req.q = ""; - req.peer = MessagesController.getInstance(currentAccount).getInputPeer(lower_part); + req.peer = getMessagesController().getInputPeer(lower_part); if (req.peer == null) { return; } - int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + int reqId = getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, res.chats, true, true); + getMessagesStorage().putUsersAndChats(res.users, res.chats, true, true); int count; if (res instanceof TLRPC.TL_messages_messages) { count = res.messages.size(); @@ -1883,14 +1884,14 @@ public class DataQuery { count = res.count; } AndroidUtilities.runOnUIThread(() -> { - MessagesController.getInstance(currentAccount).putUsers(res.users, false); - MessagesController.getInstance(currentAccount).putChats(res.chats, false); + getMessagesController().putUsers(res.users, false); + getMessagesController().putChats(res.chats, false); }); processLoadedMediaCount(count, uid, type, classGuid, false, 0); } }); - ConnectionsManager.getInstance(currentAccount).bindRequestToGuid(reqId, classGuid); + getConnectionsManager().bindRequestToGuid(reqId, classGuid); } } @@ -1905,7 +1906,7 @@ public class DataQuery { return MEDIA_AUDIO; } else if (MessageObject.isVideoMessage(message)) { return MEDIA_PHOTOVIDEO; - } else if (MessageObject.isStickerMessage(message)) { + } else if (MessageObject.isStickerMessage(message) || MessageObject.isAnimatedStickerMessage(message)) { return -1; } else if (MessageObject.isMusicMessage(message)) { return MEDIA_MUSIC; @@ -1952,7 +1953,7 @@ public class DataQuery { } else { if (fromCache == 0) { ImageLoader.saveMessagesThumbs(res.messages); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, res.chats, true, true); + getMessagesStorage().putUsersAndChats(res.users, res.chats, true, true); putMediaDatabase(uid, type, res.messages, max_id, topReached); } @@ -1969,9 +1970,9 @@ public class DataQuery { AndroidUtilities.runOnUIThread(() -> { int totalCount = res.count; - MessagesController.getInstance(currentAccount).putUsers(res.users, fromCache != 0); - MessagesController.getInstance(currentAccount).putChats(res.chats, fromCache != 0); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.mediaDidLoad, uid, totalCount, objects, classGuid, type, topReached); + getMessagesController().putUsers(res.users, fromCache != 0); + getMessagesController().putChats(res.chats, fromCache != 0); + getNotificationCenter().postNotificationName(NotificationCenter.mediaDidLoad, uid, totalCount, objects, classGuid, type, topReached); }); } } @@ -1987,15 +1988,15 @@ public class DataQuery { if (!fromCache) { putMediaCountDatabase(uid, type, count); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.mediaCountDidLoad, uid, (fromCache && count == -1 ? 0 : count), fromCache, type); + getNotificationCenter().postNotificationName(NotificationCenter.mediaCountDidLoad, uid, (fromCache && count == -1 ? 0 : count), fromCache, type); } }); } private void putMediaCountDatabase(final long uid, final int type, final int count) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { - SQLitePreparedStatement state2 = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("REPLACE INTO media_counts_v2 VALUES(?, ?, ?, ?)"); + SQLitePreparedStatement state2 = getMessagesStorage().getDatabase().executeFast("REPLACE INTO media_counts_v2 VALUES(?, ?, ?, ?)"); state2.requery(); state2.bindLong(1, uid); state2.bindInteger(2, type); @@ -2010,11 +2011,11 @@ public class DataQuery { } private void getMediaCountDatabase(final long uid, final int type, final int classGuid) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { int count = -1; int old = 0; - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT count, old FROM media_counts_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type)); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT count, old FROM media_counts_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type)); if (cursor.next()) { count = cursor.intValue(0); old = cursor.intValue(1); @@ -2022,7 +2023,7 @@ public class DataQuery { cursor.dispose(); int lower_part = (int)uid; if (count == -1 && lower_part == 0) { - cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM media_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type)); + cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM media_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type)); if (cursor.next()) { count = cursor.intValue(0); } @@ -2040,7 +2041,7 @@ public class DataQuery { } private void loadMediaDatabase(final long uid, final int count, final int max_id, final int type, final int classGuid, final boolean isChannel, final int fromCache) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { boolean topReached = false; TLRPC.TL_messages_messages res = new TLRPC.TL_messages_messages(); try { @@ -2049,7 +2050,7 @@ public class DataQuery { int countToLoad = count + 1; SQLiteCursor cursor; - SQLiteDatabase database = MessagesStorage.getInstance(currentAccount).getDatabase(); + SQLiteDatabase database = getMessagesStorage().getDatabase(); boolean isEnd = false; if ((int) uid != 0) { int channelId = 0; @@ -2128,7 +2129,7 @@ public class DataQuery { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); message.id = cursor.intValue(1); message.dialog_id = uid; @@ -2142,10 +2143,10 @@ public class DataQuery { cursor.dispose(); if (!usersToLoad.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getUsersInternal(TextUtils.join(",", usersToLoad), res.users); + getMessagesStorage().getUsersInternal(TextUtils.join(",", usersToLoad), res.users); } if (!chatsToLoad.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getChatsInternal(TextUtils.join(",", chatsToLoad), res.chats); + getMessagesStorage().getChatsInternal(TextUtils.join(",", chatsToLoad), res.chats); } if (res.messages.size() > count) { topReached = false; @@ -2165,16 +2166,16 @@ public class DataQuery { } private void putMediaDatabase(final long uid, final int type, final ArrayList messages, final int max_id, final boolean topReached) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { if (messages.isEmpty() || topReached) { - MessagesStorage.getInstance(currentAccount).doneHolesInMedia(uid, max_id, type); + getMessagesStorage().doneHolesInMedia(uid, max_id, type); if (messages.isEmpty()) { return; } } - MessagesStorage.getInstance(currentAccount).getDatabase().beginTransaction(); - SQLitePreparedStatement state2 = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); + getMessagesStorage().getDatabase().beginTransaction(); + SQLitePreparedStatement state2 = getMessagesStorage().getDatabase().executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); for (TLRPC.Message message : messages) { if (canAddMessageToMedia(message)) { @@ -2199,12 +2200,12 @@ public class DataQuery { if (!topReached || max_id != 0) { int minId = topReached ? 1 : messages.get(messages.size() - 1).id; if (max_id != 0) { - MessagesStorage.getInstance(currentAccount).closeHolesInMedia(uid, minId, max_id, type); + getMessagesStorage().closeHolesInMedia(uid, minId, max_id, type); } else { - MessagesStorage.getInstance(currentAccount).closeHolesInMedia(uid, minId, Integer.MAX_VALUE, type); + getMessagesStorage().closeHolesInMedia(uid, minId, Integer.MAX_VALUE, type); } } - MessagesStorage.getInstance(currentAccount).getDatabase().commitTransaction(); + getMessagesStorage().getDatabase().commitTransaction(); } catch (Exception e) { FileLog.e(e); } @@ -2212,22 +2213,22 @@ public class DataQuery { } public void loadMusic(final long uid, final long max_id) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { final ArrayList arrayList = new ArrayList<>(); try { int lower_id = (int) uid; SQLiteCursor cursor; if (lower_id != 0) { - cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid < %d AND type = %d ORDER BY date DESC, mid DESC LIMIT 1000", uid, max_id, MEDIA_MUSIC)); + cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid < %d AND type = %d ORDER BY date DESC, mid DESC LIMIT 1000", uid, max_id, MEDIA_MUSIC)); } else { - cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid > %d AND type = %d ORDER BY date DESC, mid DESC LIMIT 1000", uid, max_id, MEDIA_MUSIC)); + cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid > %d AND type = %d ORDER BY date DESC, mid DESC LIMIT 1000", uid, max_id, MEDIA_MUSIC)); } while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (MessageObject.isMusicMessage(message)) { message.id = cursor.intValue(1); @@ -2240,7 +2241,7 @@ public class DataQuery { } catch (Exception e) { FileLog.e(e); } - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.musicDidLoad, uid, arrayList)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.musicDidLoad, uid, arrayList)); }); } //---------------- MEDIA END ---------------- @@ -2329,14 +2330,14 @@ public class DataQuery { long did; if (hint.peer.user_id != 0) { shortcutIntent.putExtra("userId", hint.peer.user_id); - user = MessagesController.getInstance(currentAccount).getUser(hint.peer.user_id); + user = getMessagesController().getUser(hint.peer.user_id); did = hint.peer.user_id; } else { int chat_id = hint.peer.chat_id; if (chat_id == 0) { chat_id = hint.peer.channel_id; } - chat = MessagesController.getInstance(currentAccount).getChat(chat_id); + chat = getMessagesController().getChat(chat_id); shortcutIntent.putExtra("chatId", chat_id); did = -chat_id; } @@ -2424,7 +2425,7 @@ public class DataQuery { } public void loadHints(boolean cache) { - if (loading || !UserConfig.getInstance(currentAccount).suggestContacts) { + if (loading || !getUserConfig().suggestContacts) { return; } if (cache) { @@ -2432,16 +2433,16 @@ public class DataQuery { return; } loading = true; - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { final ArrayList hintsNew = new ArrayList<>(); final ArrayList inlineBotsNew = new ArrayList<>(); final ArrayList users = new ArrayList<>(); final ArrayList chats = new ArrayList<>(); - int selfUserId = UserConfig.getInstance(currentAccount).getClientUserId(); + int selfUserId = getUserConfig().getClientUserId(); try { ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized("SELECT did, type, rating FROM chat_hints WHERE 1 ORDER BY rating DESC"); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT did, type, rating FROM chat_hints WHERE 1 ORDER BY rating DESC"); while (cursor.next()) { int did = cursor.intValue(0); if (did == selfUserId) { @@ -2467,23 +2468,23 @@ public class DataQuery { } cursor.dispose(); if (!usersToLoad.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getUsersInternal(TextUtils.join(",", usersToLoad), users); + getMessagesStorage().getUsersInternal(TextUtils.join(",", usersToLoad), users); } if (!chatsToLoad.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getChatsInternal(TextUtils.join(",", chatsToLoad), chats); + getMessagesStorage().getChatsInternal(TextUtils.join(",", chatsToLoad), chats); } AndroidUtilities.runOnUIThread(() -> { - MessagesController.getInstance(currentAccount).putUsers(users, true); - MessagesController.getInstance(currentAccount).putChats(chats, true); + getMessagesController().putUsers(users, true); + getMessagesController().putChats(chats, true); loading = false; loaded = true; hints = hintsNew; inlineBots = inlineBotsNew; buildShortcuts(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.reloadHints); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.reloadInlineHints); - if (Math.abs(UserConfig.getInstance(currentAccount).lastHintsSyncTime - (int) (System.currentTimeMillis() / 1000)) >= 24 * 60 * 60) { + getNotificationCenter().postNotificationName(NotificationCenter.reloadHints); + getNotificationCenter().postNotificationName(NotificationCenter.reloadInlineHints); + if (Math.abs(getUserConfig().lastHintsSyncTime - (int) (System.currentTimeMillis() / 1000)) >= 24 * 60 * 60) { loadHints(false); } }); @@ -2503,20 +2504,20 @@ public class DataQuery { req.bots_inline = true; req.offset = 0; req.limit = 20; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response instanceof TLRPC.TL_contacts_topPeers) { AndroidUtilities.runOnUIThread(() -> { final TLRPC.TL_contacts_topPeers topPeers = (TLRPC.TL_contacts_topPeers) response; - MessagesController.getInstance(currentAccount).putUsers(topPeers.users, false); - MessagesController.getInstance(currentAccount).putChats(topPeers.chats, false); + getMessagesController().putUsers(topPeers.users, false); + getMessagesController().putChats(topPeers.chats, false); for (int a = 0; a < topPeers.categories.size(); a++) { TLRPC.TL_topPeerCategoryPeers category = topPeers.categories.get(a); if (category.category instanceof TLRPC.TL_topPeerCategoryBotsInline) { inlineBots = category.peers; - UserConfig.getInstance(currentAccount).botRatingLoadTime = (int) (System.currentTimeMillis() / 1000); + getUserConfig().botRatingLoadTime = (int) (System.currentTimeMillis() / 1000); } else { hints = category.peers; - int selfUserId = UserConfig.getInstance(currentAccount).getClientUserId(); + int selfUserId = getUserConfig().getClientUserId(); for (int b = 0; b < hints.size(); b++) { TLRPC.TL_topPeer topPeer = hints.get(b); if (topPeer.peer.user_id == selfUserId) { @@ -2524,20 +2525,20 @@ public class DataQuery { break; } } - UserConfig.getInstance(currentAccount).ratingLoadTime = (int) (System.currentTimeMillis() / 1000); + getUserConfig().ratingLoadTime = (int) (System.currentTimeMillis() / 1000); } } - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().saveConfig(false); buildShortcuts(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.reloadHints); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.reloadInlineHints); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getNotificationCenter().postNotificationName(NotificationCenter.reloadHints); + getNotificationCenter().postNotificationName(NotificationCenter.reloadInlineHints); + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { - MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("DELETE FROM chat_hints WHERE 1").stepThis().dispose(); - MessagesStorage.getInstance(currentAccount).getDatabase().beginTransaction(); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(topPeers.users, topPeers.chats, false, false); + getMessagesStorage().getDatabase().executeFast("DELETE FROM chat_hints WHERE 1").stepThis().dispose(); + getMessagesStorage().getDatabase().beginTransaction(); + getMessagesStorage().putUsersAndChats(topPeers.users, topPeers.chats, false, false); - SQLitePreparedStatement state = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("REPLACE INTO chat_hints VALUES(?, ?, ?, ?)"); + SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO chat_hints VALUES(?, ?, ?, ?)"); for (int a = 0; a < topPeers.categories.size(); a++) { int type; TLRPC.TL_topPeerCategoryPeers category = topPeers.categories.get(a); @@ -2567,11 +2568,11 @@ public class DataQuery { state.dispose(); - MessagesStorage.getInstance(currentAccount).getDatabase().commitTransaction(); + getMessagesStorage().getDatabase().commitTransaction(); AndroidUtilities.runOnUIThread(() -> { - UserConfig.getInstance(currentAccount).suggestContacts = true; - UserConfig.getInstance(currentAccount).lastHintsSyncTime = (int) (System.currentTimeMillis() / 1000); - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().suggestContacts = true; + getUserConfig().lastHintsSyncTime = (int) (System.currentTimeMillis() / 1000); + getUserConfig().saveConfig(false); }); } catch (Exception e) { FileLog.e(e); @@ -2580,9 +2581,9 @@ public class DataQuery { }); } else if (response instanceof TLRPC.TL_contacts_topPeersDisabled) { AndroidUtilities.runOnUIThread(() -> { - UserConfig.getInstance(currentAccount).suggestContacts = false; - UserConfig.getInstance(currentAccount).lastHintsSyncTime = (int) (System.currentTimeMillis() / 1000); - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().suggestContacts = false; + getUserConfig().lastHintsSyncTime = (int) (System.currentTimeMillis() / 1000); + getUserConfig().saveConfig(false); clearTopPeers(); }); } @@ -2593,11 +2594,11 @@ public class DataQuery { public void clearTopPeers() { hints.clear(); inlineBots.clear(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.reloadHints); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.reloadInlineHints); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getNotificationCenter().postNotificationName(NotificationCenter.reloadHints); + getNotificationCenter().postNotificationName(NotificationCenter.reloadInlineHints); + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { - MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("DELETE FROM chat_hints WHERE 1").stepThis().dispose(); + getMessagesStorage().getDatabase().executeFast("DELETE FROM chat_hints WHERE 1").stepThis().dispose(); } catch (Exception ignore) { } @@ -2606,12 +2607,12 @@ public class DataQuery { } public void increaseInlineRaiting(final int uid) { - if (!UserConfig.getInstance(currentAccount).suggestContacts) { + if (!getUserConfig().suggestContacts) { return; } int dt; - if (UserConfig.getInstance(currentAccount).botRatingLoadTime != 0) { - dt = Math.max(1, ((int) (System.currentTimeMillis() / 1000)) - UserConfig.getInstance(currentAccount).botRatingLoadTime); + if (getUserConfig().botRatingLoadTime != 0) { + dt = Math.max(1, ((int) (System.currentTimeMillis() / 1000)) - getUserConfig().botRatingLoadTime); } else { dt = 60; } @@ -2630,7 +2631,7 @@ public class DataQuery { peer.peer.user_id = uid; inlineBots.add(peer); } - peer.rating += Math.exp(dt / MessagesController.getInstance(currentAccount).ratingDecay); + peer.rating += Math.exp(dt / getMessagesController().ratingDecay); Collections.sort(inlineBots, (lhs, rhs) -> { if (lhs.rating > rhs.rating) { return -1; @@ -2643,7 +2644,7 @@ public class DataQuery { inlineBots.remove(inlineBots.size() - 1); } savePeer(uid, 1, peer.rating); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.reloadInlineHints); + getNotificationCenter().postNotificationName(NotificationCenter.reloadInlineHints); } public void removeInline(final int uid) { @@ -2653,12 +2654,12 @@ public class DataQuery { inlineBots.remove(a); TLRPC.TL_contacts_resetTopPeerRating req = new TLRPC.TL_contacts_resetTopPeerRating(); req.category = new TLRPC.TL_topPeerCategoryBotsInline(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer(uid); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + req.peer = getMessagesController().getInputPeer(uid); + getConnectionsManager().sendRequest(req, (response, error) -> { }); deletePeer(uid, 1); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.reloadInlineHints); + getNotificationCenter().postNotificationName(NotificationCenter.reloadInlineHints); return; } } @@ -2668,12 +2669,12 @@ public class DataQuery { for (int a = 0; a < hints.size(); a++) { if (hints.get(a).peer.user_id == uid) { hints.remove(a); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.reloadHints); + getNotificationCenter().postNotificationName(NotificationCenter.reloadHints); TLRPC.TL_contacts_resetTopPeerRating req = new TLRPC.TL_contacts_resetTopPeerRating(); req.category = new TLRPC.TL_topPeerCategoryCorrespondents(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer(uid); + req.peer = getMessagesController().getInputPeer(uid); deletePeer(uid, 0); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); return; @@ -2682,7 +2683,7 @@ public class DataQuery { } public void increasePeerRaiting(final long did) { - if (!UserConfig.getInstance(currentAccount).suggestContacts) { + if (!getUserConfig().suggestContacts) { return; } final int lower_id = (int) did; @@ -2690,24 +2691,24 @@ public class DataQuery { return; } //remove chats and bots for now - final TLRPC.User user = lower_id > 0 ? MessagesController.getInstance(currentAccount).getUser(lower_id) : null; + final TLRPC.User user = lower_id > 0 ? getMessagesController().getUser(lower_id) : null; //final TLRPC.Chat chat = lower_id < 0 ? MessagesController.getInstance().getChat(-lower_id) : null; if (user == null || user.bot || user.self/*&& chat == null || ChatObject.isChannel(chat) && !chat.megagroup*/) { return; } - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { double dt = 0; try { int lastTime = 0; int lastMid = 0; - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT MAX(mid), MAX(date) FROM messages WHERE uid = %d AND out = 1", did)); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT MAX(mid), MAX(date) FROM messages WHERE uid = %d AND out = 1", did)); if (cursor.next()) { lastMid = cursor.intValue(0); lastTime = cursor.intValue(1); } cursor.dispose(); - if (lastMid > 0 && UserConfig.getInstance(currentAccount).ratingLoadTime != 0) { - dt = (lastTime - UserConfig.getInstance(currentAccount).ratingLoadTime); + if (lastMid > 0 && getUserConfig().ratingLoadTime != 0) { + dt = (lastTime - getUserConfig().ratingLoadTime); } } catch (Exception e) { FileLog.e(e); @@ -2733,7 +2734,7 @@ public class DataQuery { } hints.add(peer); } - peer.rating += Math.exp(dtFinal / MessagesController.getInstance(currentAccount).ratingDecay); + peer.rating += Math.exp(dtFinal / getMessagesController().ratingDecay); Collections.sort(hints, (lhs, rhs) -> { if (lhs.rating > rhs.rating) { return -1; @@ -2745,15 +2746,15 @@ public class DataQuery { savePeer((int) did, 0, peer.rating); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.reloadHints); + getNotificationCenter().postNotificationName(NotificationCenter.reloadHints); }); }); } private void savePeer(final int did, final int type, final double rating) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { - SQLitePreparedStatement state = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("REPLACE INTO chat_hints VALUES(?, ?, ?, ?)"); + SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO chat_hints VALUES(?, ?, ?, ?)"); state.requery(); state.bindInteger(1, did); state.bindInteger(2, type); @@ -2768,9 +2769,9 @@ public class DataQuery { } private void deletePeer(final int did, final int type) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { - MessagesStorage.getInstance(currentAccount).getDatabase().executeFast(String.format(Locale.US, "DELETE FROM chat_hints WHERE did = %d AND type = %d", did, type)).stepThis().dispose(); + getMessagesStorage().getDatabase().executeFast(String.format(Locale.US, "DELETE FROM chat_hints WHERE did = %d AND type = %d", did, type)).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } @@ -2785,7 +2786,7 @@ public class DataQuery { if (lower_id == 0) { shortcutIntent.putExtra("encId", high_id); - TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance(currentAccount).getEncryptedChat(high_id); + TLRPC.EncryptedChat encryptedChat = getMessagesController().getEncryptedChat(high_id); if (encryptedChat == null) { return null; } @@ -2813,15 +2814,15 @@ public class DataQuery { TLRPC.User user = null; TLRPC.Chat chat = null; if (lower_id == 0) { - TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance(currentAccount).getEncryptedChat(high_id); + TLRPC.EncryptedChat encryptedChat = getMessagesController().getEncryptedChat(high_id); if (encryptedChat == null) { return; } - user = MessagesController.getInstance(currentAccount).getUser(encryptedChat.user_id); + user = getMessagesController().getUser(encryptedChat.user_id); } else if (lower_id > 0) { - user = MessagesController.getInstance(currentAccount).getUser(lower_id); + user = getMessagesController().getUser(lower_id); } else if (lower_id < 0) { - chat = MessagesController.getInstance(currentAccount).getChat(-lower_id); + chat = getMessagesController().getChat(-lower_id); } else { return; } @@ -2971,15 +2972,15 @@ public class DataQuery { TLRPC.User user = null; TLRPC.Chat chat = null; if (lower_id == 0) { - TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance(currentAccount).getEncryptedChat(high_id); + TLRPC.EncryptedChat encryptedChat = getMessagesController().getEncryptedChat(high_id); if (encryptedChat == null) { return; } - user = MessagesController.getInstance(currentAccount).getUser(encryptedChat.user_id); + user = getMessagesController().getUser(encryptedChat.user_id); } else if (lower_id > 0) { - user = MessagesController.getInstance(currentAccount).getUser(lower_id); + user = getMessagesController().getUser(lower_id); } else if (lower_id < 0) { - chat = MessagesController.getInstance(currentAccount).getChat(-lower_id); + chat = getMessagesController().getChat(-lower_id); } else { return; } @@ -3019,7 +3020,7 @@ public class DataQuery { public MessageObject loadPinnedMessage(final long dialogId, final int channelId, final int mid, boolean useQueue) { if (useQueue) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> loadPinnedMessageInternal(dialogId, channelId, mid, false)); + getMessagesStorage().getStorageQueue().postRunnable(() -> loadPinnedMessageInternal(dialogId, channelId, mid, false)); } else { return loadPinnedMessageInternal(dialogId, channelId, mid, true); } @@ -3041,12 +3042,12 @@ public class DataQuery { ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid, date FROM messages WHERE mid = %d", messageId)); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid, date FROM messages WHERE mid = %d", messageId)); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { result = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - result.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + result.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (result.action instanceof TLRPC.TL_messageActionHistoryClear) { result = null; @@ -3061,12 +3062,12 @@ public class DataQuery { cursor.dispose(); if (result == null) { - cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT data FROM chat_pinned WHERE uid = %d", dialogId)); + cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data FROM chat_pinned WHERE uid = %d", dialogId)); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { result = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - result.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + result.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (result.id != mid || result.action instanceof TLRPC.TL_messageActionHistoryClear) { result = null; @@ -3082,9 +3083,9 @@ public class DataQuery { if (result == null) { if (channelId != 0) { final TLRPC.TL_channels_getMessages req = new TLRPC.TL_channels_getMessages(); - req.channel = MessagesController.getInstance(currentAccount).getInputChannel(channelId); + req.channel = getMessagesController().getInputChannel(channelId); req.id.add(mid); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { boolean ok = false; if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; @@ -3092,19 +3093,19 @@ public class DataQuery { if (!messagesRes.messages.isEmpty()) { ImageLoader.saveMessagesThumbs(messagesRes.messages); broadcastPinnedMessage(messagesRes.messages.get(0), messagesRes.users, messagesRes.chats, false, false); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); + getMessagesStorage().putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); savePinnedMessage(messagesRes.messages.get(0)); ok = true; } } if (!ok) { - MessagesStorage.getInstance(currentAccount).updateChatPinnedMessage(channelId, 0); + getMessagesStorage().updateChatPinnedMessage(channelId, 0); } }); } else { final TLRPC.TL_messages_getMessages req = new TLRPC.TL_messages_getMessages(); req.id.add(mid); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { boolean ok = false; if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; @@ -3112,13 +3113,13 @@ public class DataQuery { if (!messagesRes.messages.isEmpty()) { ImageLoader.saveMessagesThumbs(messagesRes.messages); broadcastPinnedMessage(messagesRes.messages.get(0), messagesRes.users, messagesRes.chats, false, false); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); + getMessagesStorage().putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); savePinnedMessage(messagesRes.messages.get(0)); ok = true; } } if (!ok) { - MessagesStorage.getInstance(currentAccount).updateChatPinnedMessage(channelId, 0); + getMessagesStorage().updateChatPinnedMessage(channelId, 0); } }); } @@ -3127,10 +3128,10 @@ public class DataQuery { return broadcastPinnedMessage(result, users, chats, true, returnValue); } else { if (!usersToLoad.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getUsersInternal(TextUtils.join(",", usersToLoad), users); + getMessagesStorage().getUsersInternal(TextUtils.join(",", usersToLoad), users); } if (!chatsToLoad.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getChatsInternal(TextUtils.join(",", chatsToLoad), chats); + getMessagesStorage().getChatsInternal(TextUtils.join(",", chatsToLoad), chats); } broadcastPinnedMessage(result, users, chats, true, false); } @@ -3142,7 +3143,7 @@ public class DataQuery { } private void savePinnedMessage(final TLRPC.Message result) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { long dialogId; if (result.to_id.channel_id != 0) { @@ -3154,8 +3155,8 @@ public class DataQuery { } else { return; } - MessagesStorage.getInstance(currentAccount).getDatabase().beginTransaction(); - SQLitePreparedStatement state = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("REPLACE INTO chat_pinned VALUES(?, ?, ?)"); + getMessagesStorage().getDatabase().beginTransaction(); + SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO chat_pinned VALUES(?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(result.getObjectSize()); result.serializeToStream(data); state.requery(); @@ -3165,7 +3166,7 @@ public class DataQuery { state.step(); data.reuse(); state.dispose(); - MessagesStorage.getInstance(currentAccount).getDatabase().commitTransaction(); + getMessagesStorage().getDatabase().commitTransaction(); } catch (Exception e) { FileLog.e(e); } @@ -3187,9 +3188,9 @@ public class DataQuery { return new MessageObject(currentAccount, result, usersDict, chatsDict, false); } else { AndroidUtilities.runOnUIThread(() -> { - MessagesController.getInstance(currentAccount).putUsers(users, isCache); - MessagesController.getInstance(currentAccount).putChats(chats, isCache); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.pinnedMessageDidLoad, new MessageObject(currentAccount, result, usersDict, chatsDict, false)); + getMessagesController().putUsers(users, isCache); + getMessagesController().putChats(chats, isCache); + getNotificationCenter().postNotificationName(NotificationCenter.pinnedMessageDidLoad, new MessageObject(currentAccount, result, usersDict, chatsDict, false)); }); } return null; @@ -3228,14 +3229,14 @@ public class DataQuery { return; } - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, m.date, r.random_id FROM randoms as r INNER JOIN messages as m ON r.mid = m.mid WHERE r.random_id IN(%s)", TextUtils.join(",", replyMessages))); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, m.date, r.random_id FROM randoms as r INNER JOIN messages as m ON r.mid = m.mid WHERE r.random_id IN(%s)", TextUtils.join(",", replyMessages))); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); message.id = cursor.intValue(1); message.date = cursor.intValue(2); @@ -3266,7 +3267,7 @@ public class DataQuery { } } } - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.replyMessagesDidLoad, dialogId)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.replyMessagesDidLoad, dialogId)); } catch (Exception e) { FileLog.e(e); } @@ -3305,7 +3306,7 @@ public class DataQuery { } final int channelIdFinal = channelId; - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { final ArrayList result = new ArrayList<>(); final ArrayList users = new ArrayList<>(); @@ -3313,12 +3314,12 @@ public class DataQuery { ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid, date FROM messages WHERE mid IN(%s)", stringBuilder.toString())); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid, date FROM messages WHERE mid IN(%s)", stringBuilder.toString())); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); message.id = cursor.intValue(1); message.date = cursor.intValue(2); @@ -3331,38 +3332,38 @@ public class DataQuery { cursor.dispose(); if (!usersToLoad.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getUsersInternal(TextUtils.join(",", usersToLoad), users); + getMessagesStorage().getUsersInternal(TextUtils.join(",", usersToLoad), users); } if (!chatsToLoad.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getChatsInternal(TextUtils.join(",", chatsToLoad), chats); + getMessagesStorage().getChatsInternal(TextUtils.join(",", chatsToLoad), chats); } broadcastReplyMessages(result, replyMessageOwners, users, chats, dialogId, true); if (!replyMessages.isEmpty()) { if (channelIdFinal != 0) { final TLRPC.TL_channels_getMessages req = new TLRPC.TL_channels_getMessages(); - req.channel = MessagesController.getInstance(currentAccount).getInputChannel(channelIdFinal); + req.channel = getMessagesController().getInputChannel(channelIdFinal); req.id = replyMessages; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; removeEmptyMessages(messagesRes.messages); ImageLoader.saveMessagesThumbs(messagesRes.messages); broadcastReplyMessages(messagesRes.messages, replyMessageOwners, messagesRes.users, messagesRes.chats, dialogId, false); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); + getMessagesStorage().putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); saveReplyMessages(replyMessageOwners, messagesRes.messages); } }); } else { TLRPC.TL_messages_getMessages req = new TLRPC.TL_messages_getMessages(); req.id = replyMessages; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; removeEmptyMessages(messagesRes.messages); ImageLoader.saveMessagesThumbs(messagesRes.messages); broadcastReplyMessages(messagesRes.messages, replyMessageOwners, messagesRes.users, messagesRes.chats, dialogId, false); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); + getMessagesStorage().putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); saveReplyMessages(replyMessageOwners, messagesRes.messages); } }); @@ -3376,10 +3377,10 @@ public class DataQuery { } private void saveReplyMessages(final SparseArray> replyMessageOwners, final ArrayList result) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { - MessagesStorage.getInstance(currentAccount).getDatabase().beginTransaction(); - SQLitePreparedStatement state = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("UPDATE messages SET replydata = ? WHERE mid = ?"); + getMessagesStorage().getDatabase().beginTransaction(); + SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("UPDATE messages SET replydata = ? WHERE mid = ?"); for (int a = 0; a < result.size(); a++) { TLRPC.Message message = result.get(a); ArrayList messageObjects = replyMessageOwners.get(message.id); @@ -3401,7 +3402,7 @@ public class DataQuery { } } state.dispose(); - MessagesStorage.getInstance(currentAccount).getDatabase().commitTransaction(); + getMessagesStorage().getDatabase().commitTransaction(); } catch (Exception e) { FileLog.e(e); } @@ -3420,8 +3421,8 @@ public class DataQuery { chatsDict.put(chat.id, chat); } AndroidUtilities.runOnUIThread(() -> { - MessagesController.getInstance(currentAccount).putUsers(users, isCache); - MessagesController.getInstance(currentAccount).putChats(chats, isCache); + getMessagesController().putUsers(users, isCache); + getMessagesController().putChats(chats, isCache); boolean changed = false; for (int a = 0; a < result.size(); a++) { TLRPC.Message message = result.get(a); @@ -3446,7 +3447,7 @@ public class DataQuery { } } if (changed) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.replyMessagesDidLoad, dialog_id); + getNotificationCenter().postNotificationName(NotificationCenter.replyMessagesDidLoad, dialog_id); } }); } @@ -3503,6 +3504,206 @@ public class DataQuery { } } + private static CharacterStyle createNewSpan(CharacterStyle baseSpan, TextStyleSpan.TextStyleRun textStyleRun, TextStyleSpan.TextStyleRun newStyleRun, boolean allowIntersection) { + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(textStyleRun); + if (newStyleRun != null) { + if (allowIntersection) { + run.merge(newStyleRun); + } else { + run.replace(newStyleRun); + } + } + if (baseSpan instanceof TextStyleSpan) { + return new TextStyleSpan(run); + } else if (baseSpan instanceof URLSpanReplacement) { + URLSpanReplacement span = (URLSpanReplacement) baseSpan; + return new URLSpanReplacement(span.getURL(), run); + } + return null; + } + + public static void addStyleToText(TextStyleSpan span, int start, int end, Spannable editable, boolean allowIntersection) { + try { + allowIntersection = true; + CharacterStyle[] spans = editable.getSpans(start, end, CharacterStyle.class); + if (spans != null && spans.length > 0) { + for (int a = 0; a < spans.length; a++) { + CharacterStyle oldSpan = spans[a]; + TextStyleSpan.TextStyleRun textStyleRun; + TextStyleSpan.TextStyleRun newStyleRun = span != null ? span.getTextStyleRun() : new TextStyleSpan.TextStyleRun(); + if (oldSpan instanceof TextStyleSpan) { + TextStyleSpan textStyleSpan = (TextStyleSpan) oldSpan; + textStyleRun = textStyleSpan.getTextStyleRun(); + } else if (oldSpan instanceof URLSpanReplacement) { + URLSpanReplacement urlSpanReplacement = (URLSpanReplacement) oldSpan; + textStyleRun = urlSpanReplacement.getTextStyleRun(); + if (textStyleRun == null) { + textStyleRun = new TextStyleSpan.TextStyleRun(); + } + } else { + continue; + } + if (textStyleRun == null) { + continue; + } + int spanStart = editable.getSpanStart(oldSpan); + int spanEnd = editable.getSpanEnd(oldSpan); + editable.removeSpan(oldSpan); + if (spanStart > start && end > spanEnd) { + editable.setSpan(createNewSpan(oldSpan, textStyleRun, newStyleRun, allowIntersection), spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + if (span != null) { + editable.setSpan(new TextStyleSpan(new TextStyleSpan.TextStyleRun(newStyleRun)), spanEnd, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + end = spanStart; + } else { + int startTemp = start; + if (spanStart <= start) { + if (spanStart != start) { + editable.setSpan(createNewSpan(oldSpan, textStyleRun, null, allowIntersection), spanStart, start, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (spanEnd > start) { + if (span != null) { + editable.setSpan(createNewSpan(oldSpan, textStyleRun, newStyleRun, allowIntersection), start, Math.min(spanEnd, end), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + start = spanEnd; + } + } + if (spanEnd >= end) { + if (spanEnd != end) { + editable.setSpan(createNewSpan(oldSpan, textStyleRun, null, allowIntersection), end, spanEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (end > spanStart && spanEnd <= startTemp) { + if (span != null) { + editable.setSpan(createNewSpan(oldSpan, textStyleRun, newStyleRun, allowIntersection), spanStart, Math.min(spanEnd, end), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + end = spanStart; + } + } + } + } + } + if (span != null && start < end) { + editable.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } catch (Exception e) { + FileLog.e(e); + } + } + + public static ArrayList getTextStyleRuns(ArrayList entities, CharSequence text) { + ArrayList runs = new ArrayList<>(); + ArrayList entitiesCopy = new ArrayList<>(entities); + + Collections.sort(entitiesCopy, (o1, o2) -> { + if (o1.offset > o2.offset) { + return 1; + } else if (o1.offset < o2.offset) { + return -1; + } + return 0; + }); + for (int a = 0, N = entitiesCopy.size(); a < N; a++) { + TLRPC.MessageEntity entity = entitiesCopy.get(a); + if (entity.length <= 0 || entity.offset < 0 || entity.offset >= text.length()) { + continue; + } else if (entity.offset + entity.length > text.length()) { + entity.length = text.length() - entity.offset; + } + + TextStyleSpan.TextStyleRun newRun = new TextStyleSpan.TextStyleRun(); + newRun.start = entity.offset; + newRun.end = newRun.start + entity.length; + TLRPC.MessageEntity urlEntity = null; + if (entity instanceof TLRPC.TL_messageEntityStrike) { + newRun.flags = TextStyleSpan.FLAG_STYLE_STRIKE; + } else if (entity instanceof TLRPC.TL_messageEntityUnderline) { + newRun.flags = TextStyleSpan.FLAG_STYLE_UNDERLINE; + } else if (entity instanceof TLRPC.TL_messageEntityBlockquote) { + newRun.flags = TextStyleSpan.FLAG_STYLE_QUOTE; + } else if (entity instanceof TLRPC.TL_messageEntityBold) { + newRun.flags = TextStyleSpan.FLAG_STYLE_BOLD; + } else if (entity instanceof TLRPC.TL_messageEntityItalic) { + newRun.flags = TextStyleSpan.FLAG_STYLE_ITALIC; + } else if (entity instanceof TLRPC.TL_messageEntityCode || entity instanceof TLRPC.TL_messageEntityPre) { + newRun.flags = TextStyleSpan.FLAG_STYLE_MONO; + } else if (entity instanceof TLRPC.TL_messageEntityMentionName) { + newRun.flags = TextStyleSpan.FLAG_STYLE_MENTION; + newRun.urlEntity = entity; + } else if (entity instanceof TLRPC.TL_inputMessageEntityMentionName) { + newRun.flags = TextStyleSpan.FLAG_STYLE_MENTION; + newRun.urlEntity = entity; + } else { + newRun.flags = TextStyleSpan.FLAG_STYLE_URL; + newRun.urlEntity = entity; + } + + for (int b = 0, N2 = runs.size(); b < N2; b++) { + TextStyleSpan.TextStyleRun run = runs.get(b); + + if (newRun.start > run.start) { + if (newRun.start >= run.end) { + continue; + } + + if (newRun.end < run.end) { + TextStyleSpan.TextStyleRun r = new TextStyleSpan.TextStyleRun(newRun); + r.merge(run); + b++; + N2++; + runs.add(b, r); + + r = new TextStyleSpan.TextStyleRun(run); + r.start = newRun.end; + b++; + N2++; + runs.add(b, r); + } else if (newRun.end >= run.end) { + TextStyleSpan.TextStyleRun r = new TextStyleSpan.TextStyleRun(newRun); + r.merge(run); + r.end = run.end; + b++; + N2++; + runs.add(b, r); + } + + int temp = newRun.start; + newRun.start = run.end; + run.end = temp; + } else { + if (run.start >= newRun.end) { + continue; + } + int temp = run.start; + if (newRun.end == run.end) { + run.merge(newRun); + } else if (newRun.end < run.end) { + TextStyleSpan.TextStyleRun r = new TextStyleSpan.TextStyleRun(run); + r.merge(newRun); + r.end = newRun.end; + b++; + N2++; + runs.add(b, r); + + run.start = newRun.end; + } else { + TextStyleSpan.TextStyleRun r = new TextStyleSpan.TextStyleRun(newRun); + r.start = run.end; + b++; + N2++; + runs.add(b, r); + + run.merge(newRun); + } + newRun.end = temp; + } + } + if (newRun.start < newRun.end) { + runs.add(newRun); + } + } + return runs; + } + public ArrayList getEntities(CharSequence[] message) { if (message == null || message[0] == null) { return null; @@ -3584,10 +3785,10 @@ public class DataQuery { if (message[0] instanceof Spanned) { Spanned spannable = (Spanned) message[0]; - TypefaceSpan[] spans = spannable.getSpans(0, message[0].length(), TypefaceSpan.class); + TextStyleSpan[] spans = spannable.getSpans(0, message[0].length(), TextStyleSpan.class); if (spans != null && spans.length > 0) { for (int a = 0; a < spans.length; a++) { - TypefaceSpan span = spans[a]; + TextStyleSpan span = spans[a]; int spanStart = spannable.getSpanStart(span); int spanEnd = spannable.getSpanEnd(span); if (checkInclusion(spanStart, entities) || checkInclusion(spanEnd, entities) || checkIntersection(spanStart, spanEnd, entities)) { @@ -3596,17 +3797,43 @@ public class DataQuery { if (entities == null) { entities = new ArrayList<>(); } - TLRPC.MessageEntity entity; - if (span.isMono()) { - entity = new TLRPC.TL_messageEntityCode(); - } else if (span.isBold()) { - entity = new TLRPC.TL_messageEntityBold(); - } else { - entity = new TLRPC.TL_messageEntityItalic(); + int flags = span.getStyleFlags(); + if ((flags & TextStyleSpan.FLAG_STYLE_BOLD) != 0) { + TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityBold(); + entity.offset = spanStart; + entity.length = spanEnd - spanStart; + entities.add(entity); + } + if ((flags & TextStyleSpan.FLAG_STYLE_ITALIC) != 0) { + TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityItalic(); + entity.offset = spanStart; + entity.length = spanEnd - spanStart; + entities.add(entity); + } + if ((flags & TextStyleSpan.FLAG_STYLE_MONO) != 0) { + TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityCode(); + entity.offset = spanStart; + entity.length = spanEnd - spanStart; + entities.add(entity); + } + if ((flags & TextStyleSpan.FLAG_STYLE_STRIKE) != 0) { + TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityStrike(); + entity.offset = spanStart; + entity.length = spanEnd - spanStart; + entities.add(entity); + } + if ((flags & TextStyleSpan.FLAG_STYLE_UNDERLINE) != 0) { + TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityUnderline(); + entity.offset = spanStart; + entity.length = spanEnd - spanStart; + entities.add(entity); + } + if ((flags & TextStyleSpan.FLAG_STYLE_QUOTE) != 0) { + TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityBlockquote(); + entity.offset = spanStart; + entity.length = spanEnd - spanStart; + entities.add(entity); } - entity.offset = spanStart; - entity.length = spanEnd - spanStart; - entities.add(entity); } } @@ -3617,7 +3844,7 @@ public class DataQuery { } for (int b = 0; b < spansMentions.length; b++) { TLRPC.TL_inputMessageEntityMentionName entity = new TLRPC.TL_inputMessageEntityMentionName(); - entity.user_id = MessagesController.getInstance(currentAccount).getInputUser(Utilities.parseInt(spansMentions[b].getURL())); + entity.user_id = getMessagesController().getInputUser(Utilities.parseInt(spansMentions[b].getURL())); if (entity.user_id != null) { entity.offset = spannable.getSpanStart(spansMentions[b]); entity.length = Math.min(spannable.getSpanEnd(spansMentions[b]), message[0].length()) - entity.offset; @@ -3708,20 +3935,20 @@ public class DataQuery { private boolean loadingDrafts; public void loadDrafts() { - if (UserConfig.getInstance(currentAccount).draftsLoaded || loadingDrafts) { + if (getUserConfig().draftsLoaded || loadingDrafts) { return; } loadingDrafts = true; TLRPC.TL_messages_getAllDrafts req = new TLRPC.TL_messages_getAllDrafts(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null) { return; } - MessagesController.getInstance(currentAccount).processUpdates((TLRPC.Updates) response, false); + getMessagesController().processUpdates((TLRPC.Updates) response, false); AndroidUtilities.runOnUIThread(() -> { - UserConfig.getInstance(currentAccount).draftsLoaded = true; + getUserConfig().draftsLoaded = true; loadingDrafts = false; - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().saveConfig(false); }); }); } @@ -3769,7 +3996,7 @@ public class DataQuery { int lower_id = (int) did; if (lower_id != 0) { TLRPC.TL_messages_saveDraft req = new TLRPC.TL_messages_saveDraft(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer(lower_id); + req.peer = getMessagesController().getInputPeer(lower_id); if (req.peer == null) { return; } @@ -3778,12 +4005,12 @@ public class DataQuery { req.reply_to_msg_id = draftMessage.reply_to_msg_id; req.entities = draftMessage.entities; req.flags = draftMessage.flags; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); } - MessagesController.getInstance(currentAccount).sortDialogs(null); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getMessagesController().sortDialogs(null); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } public void saveDraft(final long did, TLRPC.DraftMessage draft, TLRPC.Message replyToMessage, boolean fromServer) { @@ -3820,9 +4047,9 @@ public class DataQuery { TLRPC.User user = null; TLRPC.Chat chat = null; if (lower_id > 0) { - user = MessagesController.getInstance(currentAccount).getUser(lower_id); + user = getMessagesController().getUser(lower_id); } else { - chat = MessagesController.getInstance(currentAccount).getChat(-lower_id); + chat = getMessagesController().getChat(-lower_id); } if (user != null || chat != null) { long messageId = draft.reply_to_msg_id; @@ -3835,15 +4062,15 @@ public class DataQuery { } final long messageIdFinal = messageId; - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { TLRPC.Message message = null; - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT data FROM messages WHERE mid = %d", messageIdFinal)); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data FROM messages WHERE mid = %d", messageIdFinal)); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); } } @@ -3851,9 +4078,9 @@ public class DataQuery { if (message == null) { if (channelIdFinal != 0) { final TLRPC.TL_channels_getMessages req = new TLRPC.TL_channels_getMessages(); - req.channel = MessagesController.getInstance(currentAccount).getInputChannel(channelIdFinal); + req.channel = getMessagesController().getInputChannel(channelIdFinal); req.id.add((int) messageIdFinal); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; if (!messagesRes.messages.isEmpty()) { @@ -3864,7 +4091,7 @@ public class DataQuery { } else { TLRPC.TL_messages_getMessages req = new TLRPC.TL_messages_getMessages(); req.id.add((int) messageIdFinal); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; if (!messagesRes.messages.isEmpty()) { @@ -3882,7 +4109,7 @@ public class DataQuery { }); } } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.newDraftReceived, did); + getNotificationCenter().postNotificationName(NotificationCenter.newDraftReceived, did); } } @@ -3897,7 +4124,7 @@ public class DataQuery { SerializedData serializedData = new SerializedData(message.getObjectSize()); message.serializeToStream(serializedData); preferences.edit().putString("r_" + did, Utilities.bytesToHex(serializedData.toByteArray())).commit(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.newDraftReceived, did); + getNotificationCenter().postNotificationName(NotificationCenter.newDraftReceived, did); serializedData.cleanup(); } }); @@ -3907,8 +4134,8 @@ public class DataQuery { drafts.clear(); draftMessages.clear(); preferences.edit().clear().commit(); - MessagesController.getInstance(currentAccount).sortDialogs(null); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getMessagesController().sortDialogs(null); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } public void cleanDraft(long did, boolean replyOnly) { @@ -3920,8 +4147,8 @@ public class DataQuery { drafts.remove(did); draftMessages.remove(did); preferences.edit().remove("" + did).remove("r_" + did).commit(); - MessagesController.getInstance(currentAccount).sortDialogs(null); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getMessagesController().sortDialogs(null); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } else if (draftMessage.reply_to_msg_id != 0) { draftMessage.reply_to_msg_id = 0; draftMessage.flags &= ~1; @@ -3951,12 +4178,12 @@ public class DataQuery { if (did1 != 0) { botKeyboards.remove(did1); botKeyboardsByMids.delete(messages.get(a)); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.botKeyboardDidLoad, null, did1); + getNotificationCenter().postNotificationName(NotificationCenter.botKeyboardDidLoad, null, did1); } } } else { botKeyboards.remove(did); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.botKeyboardDidLoad, null, did); + getNotificationCenter().postNotificationName(NotificationCenter.botKeyboardDidLoad, null, did); } }); } @@ -3964,13 +4191,13 @@ public class DataQuery { public void loadBotKeyboard(final long did) { TLRPC.Message keyboard = botKeyboards.get(did); if (keyboard != null) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.botKeyboardDidLoad, keyboard, did); + getNotificationCenter().postNotificationName(NotificationCenter.botKeyboardDidLoad, keyboard, did); return; } - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { TLRPC.Message botKeyboard = null; - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT info FROM bot_keyboard WHERE uid = %d", did)); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT info FROM bot_keyboard WHERE uid = %d", did)); if (cursor.next()) { NativeByteBuffer data; @@ -3986,7 +4213,7 @@ public class DataQuery { if (botKeyboard != null) { final TLRPC.Message botKeyboardFinal = botKeyboard; - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.botKeyboardDidLoad, botKeyboardFinal, did)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.botKeyboardDidLoad, botKeyboardFinal, did)); } } catch (Exception e) { FileLog.e(e); @@ -3998,14 +4225,14 @@ public class DataQuery { if (cache) { TLRPC.BotInfo botInfo = botInfos.get(uid); if (botInfo != null) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.botInfoDidLoad, botInfo, classGuid); + getNotificationCenter().postNotificationName(NotificationCenter.botInfoDidLoad, botInfo, classGuid); return; } } - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { TLRPC.BotInfo botInfo = null; - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT info FROM bot_info WHERE uid = %d", uid)); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT info FROM bot_info WHERE uid = %d", uid)); if (cursor.next()) { NativeByteBuffer data; @@ -4021,7 +4248,7 @@ public class DataQuery { if (botInfo != null) { final TLRPC.BotInfo botInfoFinal = botInfo; - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.botInfoDidLoad, botInfoFinal, classGuid)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.botInfoDidLoad, botInfoFinal, classGuid)); } } catch (Exception e) { FileLog.e(e); @@ -4035,7 +4262,7 @@ public class DataQuery { } try { int mid = 0; - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT mid FROM bot_keyboard WHERE uid = %d", did)); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT mid FROM bot_keyboard WHERE uid = %d", did)); if (cursor.next()) { mid = cursor.intValue(0); } @@ -4044,7 +4271,7 @@ public class DataQuery { return; } - SQLitePreparedStatement state = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("REPLACE INTO bot_keyboard VALUES(?, ?, ?)"); + SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO bot_keyboard VALUES(?, ?, ?)"); state.requery(); NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); @@ -4062,7 +4289,7 @@ public class DataQuery { botKeyboardsByMids.delete(old.id); } botKeyboardsByMids.put(message.id, did); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.botKeyboardDidLoad, message, did); + getNotificationCenter().postNotificationName(NotificationCenter.botKeyboardDidLoad, message, did); }); } catch (Exception e) { FileLog.e(e); @@ -4074,9 +4301,9 @@ public class DataQuery { return; } botInfos.put(botInfo.user_id, botInfo); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { - SQLitePreparedStatement state = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("REPLACE INTO bot_info(uid, info) VALUES(?, ?)"); + SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO bot_info(uid, info) VALUES(?, ?)"); state.requery(); NativeByteBuffer data = new NativeByteBuffer(botInfo.getObjectSize()); botInfo.serializeToStream(data); @@ -4119,12 +4346,12 @@ public class DataQuery { return; } currentFetchingEmoji.put(langCode, true); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { int version = -1; String alias = null; long date = 0; try { - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized("SELECT alias, version, date FROM emoji_keywords_info_v2 WHERE lang = ?", langCode); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT alias, version, date FROM emoji_keywords_info_v2 WHERE lang = ?", langCode); if (cursor.next()) { alias = cursor.stringValue(0); version = cursor.intValue(1); @@ -4151,13 +4378,13 @@ public class DataQuery { } String aliasFinal = alias; int versionFinal = version; - ConnectionsManager.getInstance(currentAccount).sendRequest(request, (response, error) -> { + getConnectionsManager().sendRequest(request, (response, error) -> { if (response != null) { TLRPC.TL_emojiKeywordsDifference res = (TLRPC.TL_emojiKeywordsDifference) response; if (versionFinal != -1 && !res.lang_code.equals(aliasFinal)) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { - SQLitePreparedStatement deleteState = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("DELETE FROM emoji_keywords_info_v2 WHERE lang = ?"); + SQLitePreparedStatement deleteState = getMessagesStorage().getDatabase().executeFast("DELETE FROM emoji_keywords_info_v2 WHERE lang = ?"); deleteState.bindString(1, langCode); deleteState.step(); deleteState.dispose(); @@ -4185,12 +4412,12 @@ public class DataQuery { if (res == null) { return; } - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { if (!res.keywords.isEmpty()) { - SQLitePreparedStatement insertState = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("REPLACE INTO emoji_keywords_v2 VALUES(?, ?, ?)"); - SQLitePreparedStatement deleteState = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("DELETE FROM emoji_keywords_v2 WHERE lang = ? AND keyword = ? AND emoji = ?"); - MessagesStorage.getInstance(currentAccount).getDatabase().beginTransaction(); + SQLitePreparedStatement insertState = getMessagesStorage().getDatabase().executeFast("REPLACE INTO emoji_keywords_v2 VALUES(?, ?, ?)"); + SQLitePreparedStatement deleteState = getMessagesStorage().getDatabase().executeFast("DELETE FROM emoji_keywords_v2 WHERE lang = ? AND keyword = ? AND emoji = ?"); + getMessagesStorage().getDatabase().beginTransaction(); for (int a = 0, N = res.keywords.size(); a < N; a++) { TLRPC.EmojiKeyword keyword = res.keywords.get(a); if (keyword instanceof TLRPC.TL_emojiKeyword) { @@ -4215,12 +4442,12 @@ public class DataQuery { } } } - MessagesStorage.getInstance(currentAccount).getDatabase().commitTransaction(); + getMessagesStorage().getDatabase().commitTransaction(); insertState.dispose(); deleteState.dispose(); } - SQLitePreparedStatement infoState = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("REPLACE INTO emoji_keywords_info_v2 VALUES(?, ?, ?, ?)"); + SQLitePreparedStatement infoState = getMessagesStorage().getDatabase().executeFast("REPLACE INTO emoji_keywords_info_v2 VALUES(?, ?, ?, ?)"); infoState.bindString(1, lang); infoState.bindString(2, res.lang_code); infoState.bindInteger(3, res.version); @@ -4230,7 +4457,7 @@ public class DataQuery { AndroidUtilities.runOnUIThread(() -> { currentFetchingEmoji.remove(lang); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.newEmojiSuggestionsAvailable, lang); + getNotificationCenter().postNotificationName(NotificationCenter.newEmojiSuggestionsAvailable, lang); }); } catch (Exception e) { FileLog.e(e); @@ -4251,7 +4478,7 @@ public class DataQuery { return; } ArrayList recentEmoji = new ArrayList<>(Emoji.recentEmoji); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { ArrayList result = new ArrayList(); HashMap resultMap = new HashMap<>(); String alias = null; @@ -4259,7 +4486,7 @@ public class DataQuery { SQLiteCursor cursor; boolean hasAny = false; for (int a = 0; a < langCodes.length; a++) { - cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized("SELECT alias FROM emoji_keywords_info_v2 WHERE lang = ?", langCodes[a]); + cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT alias FROM emoji_keywords_info_v2 WHERE lang = ?", langCodes[a]); if (cursor.next()) { alias = cursor.stringValue(0); } @@ -4304,12 +4531,12 @@ public class DataQuery { } if (fullMatch) { - cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized("SELECT emoji, keyword FROM emoji_keywords_v2 WHERE keyword = ?", key); + cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT emoji, keyword FROM emoji_keywords_v2 WHERE keyword = ?", key); } else if (key2 != null) { - cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized("SELECT emoji, keyword FROM emoji_keywords_v2 WHERE keyword >= ? AND keyword < ?", key, key2); + cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT emoji, keyword FROM emoji_keywords_v2 WHERE keyword >= ? AND keyword < ?", key, key2); } else { key += "%"; - cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized("SELECT emoji, keyword FROM emoji_keywords_v2 WHERE keyword LIKE ?", key); + cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT emoji, keyword FROM emoji_keywords_v2 WHERE keyword LIKE ?", key); } while (cursor.next()) { String value = cursor.stringValue(0).replace("\ufe0f", ""); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java index d16372a3c..3766942c2 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java @@ -31,6 +31,7 @@ import org.telegram.tgnet.SerializedData; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.TextStyleSpan; import org.telegram.ui.Components.TypefaceSpan; import org.telegram.ui.Components.URLSpanBotCommand; import org.telegram.ui.Components.URLSpanBrowser; @@ -48,6 +49,7 @@ import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; +import java.util.Collections; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.regex.Matcher; @@ -766,7 +768,7 @@ public class MessageObject { eventId = eid; if (message.replyMessage != null) { - replyMessageObject = new MessageObject(accountNum, message.replyMessage, users, chats, sUsers, sChats, false, eid); + replyMessageObject = new MessageObject(currentAccount, message.replyMessage, users, chats, sUsers, sChats, false, eid); } TLRPC.User fromUser = null; @@ -777,407 +779,11 @@ public class MessageObject { fromUser = sUsers.get(message.from_id); } if (fromUser == null) { - fromUser = MessagesController.getInstance(accountNum).getUser(message.from_id); + fromUser = MessagesController.getInstance(currentAccount).getUser(message.from_id); } } - if (message instanceof TLRPC.TL_messageService) { - if (message.action != null) { - if (message.action instanceof TLRPC.TL_messageActionCustomAction) { - messageText = message.action.message; - } else if (message.action instanceof TLRPC.TL_messageActionChatCreate) { - if (isOut()) { - messageText = LocaleController.getString("ActionYouCreateGroup", R.string.ActionYouCreateGroup); - } else { - messageText = replaceWithLink(LocaleController.getString("ActionCreateGroup", R.string.ActionCreateGroup), "un1", fromUser); - } - } else if (message.action instanceof TLRPC.TL_messageActionChatDeleteUser) { - if (message.action.user_id == message.from_id) { - if (isOut()) { - messageText = LocaleController.getString("ActionYouLeftUser", R.string.ActionYouLeftUser); - } else { - messageText = replaceWithLink(LocaleController.getString("ActionLeftUser", R.string.ActionLeftUser), "un1", fromUser); - } - } else { - TLRPC.User whoUser = null; - if (users != null) { - whoUser = users.get(message.action.user_id); - } else if (sUsers != null) { - whoUser = sUsers.get(message.action.user_id); - } - if (whoUser == null) { - whoUser = MessagesController.getInstance(accountNum).getUser(message.action.user_id); - } - if (isOut()) { - messageText = replaceWithLink(LocaleController.getString("ActionYouKickUser", R.string.ActionYouKickUser), "un2", whoUser); - } else if (message.action.user_id == UserConfig.getInstance(currentAccount).getClientUserId()) { - messageText = replaceWithLink(LocaleController.getString("ActionKickUserYou", R.string.ActionKickUserYou), "un1", fromUser); - } else { - messageText = replaceWithLink(LocaleController.getString("ActionKickUser", R.string.ActionKickUser), "un2", whoUser); - messageText = replaceWithLink(messageText, "un1", fromUser); - } - } - } else if (message.action instanceof TLRPC.TL_messageActionChatAddUser) { - int singleUserId = messageOwner.action.user_id; - if (singleUserId == 0 && messageOwner.action.users.size() == 1) { - singleUserId = messageOwner.action.users.get(0); - } - if (singleUserId != 0) { - TLRPC.User whoUser = null; - if (users != null) { - whoUser = users.get(singleUserId); - } else if (sUsers != null) { - whoUser = sUsers.get(singleUserId); - } - if (whoUser == null) { - whoUser = MessagesController.getInstance(accountNum).getUser(singleUserId); - } - if (singleUserId == message.from_id) { - if (message.to_id.channel_id != 0 && !isMegagroup()) { - messageText = LocaleController.getString("ChannelJoined", R.string.ChannelJoined); - } else { - if (message.to_id.channel_id != 0 && isMegagroup()) { - if (singleUserId == UserConfig.getInstance(currentAccount).getClientUserId()) { - messageText = LocaleController.getString("ChannelMegaJoined", R.string.ChannelMegaJoined); - } else { - messageText = replaceWithLink(LocaleController.getString("ActionAddUserSelfMega", R.string.ActionAddUserSelfMega), "un1", fromUser); - } - } else if (isOut()) { - messageText = LocaleController.getString("ActionAddUserSelfYou", R.string.ActionAddUserSelfYou); - } else { - messageText = replaceWithLink(LocaleController.getString("ActionAddUserSelf", R.string.ActionAddUserSelf), "un1", fromUser); - } - } - } else { - if (isOut()) { - messageText = replaceWithLink(LocaleController.getString("ActionYouAddUser", R.string.ActionYouAddUser), "un2", whoUser); - } else if (singleUserId == UserConfig.getInstance(currentAccount).getClientUserId()) { - if (message.to_id.channel_id != 0) { - if (isMegagroup()) { - messageText = replaceWithLink(LocaleController.getString("MegaAddedBy", R.string.MegaAddedBy), "un1", fromUser); - } else { - messageText = replaceWithLink(LocaleController.getString("ChannelAddedBy", R.string.ChannelAddedBy), "un1", fromUser); - } - } else { - messageText = replaceWithLink(LocaleController.getString("ActionAddUserYou", R.string.ActionAddUserYou), "un1", fromUser); - } - } else { - messageText = replaceWithLink(LocaleController.getString("ActionAddUser", R.string.ActionAddUser), "un2", whoUser); - messageText = replaceWithLink(messageText, "un1", fromUser); - } - } - } else { - if (isOut()) { - messageText = replaceWithLink(LocaleController.getString("ActionYouAddUser", R.string.ActionYouAddUser), "un2", message.action.users, users, sUsers); - } else { - messageText = replaceWithLink(LocaleController.getString("ActionAddUser", R.string.ActionAddUser), "un2", message.action.users, users, sUsers); - messageText = replaceWithLink(messageText, "un1", fromUser); - } - } - } else if (message.action instanceof TLRPC.TL_messageActionChatJoinedByLink) { - if (isOut()) { - messageText = LocaleController.getString("ActionInviteYou", R.string.ActionInviteYou); - } else { - messageText = replaceWithLink(LocaleController.getString("ActionInviteUser", R.string.ActionInviteUser), "un1", fromUser); - } - } else if (message.action instanceof TLRPC.TL_messageActionChatEditPhoto) { - if (message.to_id.channel_id != 0 && !isMegagroup()) { - messageText = LocaleController.getString("ActionChannelChangedPhoto", R.string.ActionChannelChangedPhoto); - } else { - if (isOut()) { - messageText = LocaleController.getString("ActionYouChangedPhoto", R.string.ActionYouChangedPhoto); - } else { - messageText = replaceWithLink(LocaleController.getString("ActionChangedPhoto", R.string.ActionChangedPhoto), "un1", fromUser); - } - } - } else if (message.action instanceof TLRPC.TL_messageActionChatEditTitle) { - if (message.to_id.channel_id != 0 && !isMegagroup()) { - messageText = LocaleController.getString("ActionChannelChangedTitle", R.string.ActionChannelChangedTitle).replace("un2", message.action.title); - } else { - if (isOut()) { - messageText = LocaleController.getString("ActionYouChangedTitle", R.string.ActionYouChangedTitle).replace("un2", message.action.title); - } else { - messageText = replaceWithLink(LocaleController.getString("ActionChangedTitle", R.string.ActionChangedTitle).replace("un2", message.action.title), "un1", fromUser); - } - } - } else if (message.action instanceof TLRPC.TL_messageActionChatDeletePhoto) { - if (message.to_id.channel_id != 0 && !isMegagroup()) { - messageText = LocaleController.getString("ActionChannelRemovedPhoto", R.string.ActionChannelRemovedPhoto); - } else { - if (isOut()) { - messageText = LocaleController.getString("ActionYouRemovedPhoto", R.string.ActionYouRemovedPhoto); - } else { - messageText = replaceWithLink(LocaleController.getString("ActionRemovedPhoto", R.string.ActionRemovedPhoto), "un1", fromUser); - } - } - } else if (message.action instanceof TLRPC.TL_messageActionTTLChange) { - if (message.action.ttl != 0) { - if (isOut()) { - messageText = LocaleController.formatString("MessageLifetimeChangedOutgoing", R.string.MessageLifetimeChangedOutgoing, LocaleController.formatTTLString(message.action.ttl)); - } else { - messageText = LocaleController.formatString("MessageLifetimeChanged", R.string.MessageLifetimeChanged, UserObject.getFirstName(fromUser), LocaleController.formatTTLString(message.action.ttl)); - } - } else { - if (isOut()) { - messageText = LocaleController.getString("MessageLifetimeYouRemoved", R.string.MessageLifetimeYouRemoved); - } else { - messageText = LocaleController.formatString("MessageLifetimeRemoved", R.string.MessageLifetimeRemoved, UserObject.getFirstName(fromUser)); - } - } - } else if (message.action instanceof TLRPC.TL_messageActionLoginUnknownLocation) { - String date; - long time = ((long) message.date) * 1000; - if (LocaleController.getInstance().formatterDay != null && LocaleController.getInstance().formatterYear != null) { - date = LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, LocaleController.getInstance().formatterYear.format(time), LocaleController.getInstance().formatterDay.format(time)); - } else { - date = "" + message.date; - } - TLRPC.User to_user = UserConfig.getInstance(currentAccount).getCurrentUser(); - if (to_user == null) { - if (users != null) { - to_user = users.get(messageOwner.to_id.user_id); - } else if (sUsers != null) { - to_user = sUsers.get(messageOwner.to_id.user_id); - } - if (to_user == null) { - to_user = MessagesController.getInstance(accountNum).getUser(messageOwner.to_id.user_id); - } - } - String name = to_user != null ? UserObject.getFirstName(to_user) : ""; - messageText = LocaleController.formatString("NotificationUnrecognizedDevice", R.string.NotificationUnrecognizedDevice, name, date, message.action.title, message.action.address); - } else if (message.action instanceof TLRPC.TL_messageActionUserJoined || message.action instanceof TLRPC.TL_messageActionContactSignUp) { - messageText = LocaleController.formatString("NotificationContactJoined", R.string.NotificationContactJoined, UserObject.getUserName(fromUser)); - } else if (message.action instanceof TLRPC.TL_messageActionUserUpdatedPhoto) { - messageText = LocaleController.formatString("NotificationContactNewPhoto", R.string.NotificationContactNewPhoto, UserObject.getUserName(fromUser)); - } else if (message.action instanceof TLRPC.TL_messageEncryptedAction) { - if (message.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionScreenshotMessages) { - if (isOut()) { - messageText = LocaleController.formatString("ActionTakeScreenshootYou", R.string.ActionTakeScreenshootYou); - } else { - messageText = replaceWithLink(LocaleController.getString("ActionTakeScreenshoot", R.string.ActionTakeScreenshoot), "un1", fromUser); - } - } else if (message.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionSetMessageTTL) { - TLRPC.TL_decryptedMessageActionSetMessageTTL action = (TLRPC.TL_decryptedMessageActionSetMessageTTL) message.action.encryptedAction; - if (action.ttl_seconds != 0) { - if (isOut()) { - messageText = LocaleController.formatString("MessageLifetimeChangedOutgoing", R.string.MessageLifetimeChangedOutgoing, LocaleController.formatTTLString(action.ttl_seconds)); - } else { - messageText = LocaleController.formatString("MessageLifetimeChanged", R.string.MessageLifetimeChanged, UserObject.getFirstName(fromUser), LocaleController.formatTTLString(action.ttl_seconds)); - } - } else { - if (isOut()) { - messageText = LocaleController.getString("MessageLifetimeYouRemoved", R.string.MessageLifetimeYouRemoved); - } else { - messageText = LocaleController.formatString("MessageLifetimeRemoved", R.string.MessageLifetimeRemoved, UserObject.getFirstName(fromUser)); - } - } - } - } else if (message.action instanceof TLRPC.TL_messageActionScreenshotTaken) { - if (isOut()) { - messageText = LocaleController.formatString("ActionTakeScreenshootYou", R.string.ActionTakeScreenshootYou); - } else { - messageText = replaceWithLink(LocaleController.getString("ActionTakeScreenshoot", R.string.ActionTakeScreenshoot), "un1", fromUser); - } - } else if (message.action instanceof TLRPC.TL_messageActionCreatedBroadcastList) { - messageText = LocaleController.formatString("YouCreatedBroadcastList", R.string.YouCreatedBroadcastList); - } else if (message.action instanceof TLRPC.TL_messageActionChannelCreate) { - if (isMegagroup()) { - messageText = LocaleController.getString("ActionCreateMega", R.string.ActionCreateMega); - } else { - messageText = LocaleController.getString("ActionCreateChannel", R.string.ActionCreateChannel); - } - } else if (message.action instanceof TLRPC.TL_messageActionChatMigrateTo) { - messageText = LocaleController.getString("ActionMigrateFromGroup", R.string.ActionMigrateFromGroup); - } else if (message.action instanceof TLRPC.TL_messageActionChannelMigrateFrom) { - messageText = LocaleController.getString("ActionMigrateFromGroup", R.string.ActionMigrateFromGroup); - } else if (message.action instanceof TLRPC.TL_messageActionPinMessage) { - TLRPC.Chat chat; - if (fromUser == null) { - if (chats != null) { - chat = chats.get(message.to_id.channel_id); - } else if (sChats != null) { - chat = sChats.get(message.to_id.channel_id); - } else { - chat = null; - } - } else { - chat = null; - } - generatePinMessageText(fromUser, chat); - } else if (message.action instanceof TLRPC.TL_messageActionHistoryClear) { - messageText = LocaleController.getString("HistoryCleared", R.string.HistoryCleared); - } else if (message.action instanceof TLRPC.TL_messageActionGameScore) { - generateGameMessageText(fromUser); - } else if (message.action instanceof TLRPC.TL_messageActionPhoneCall) { - TLRPC.TL_messageActionPhoneCall call = (TLRPC.TL_messageActionPhoneCall) messageOwner.action; - boolean isMissed = call.reason instanceof TLRPC.TL_phoneCallDiscardReasonMissed; - if (messageOwner.from_id == UserConfig.getInstance(currentAccount).getClientUserId()) { - if (isMissed) { - messageText = LocaleController.getString("CallMessageOutgoingMissed", R.string.CallMessageOutgoingMissed); - }else { - messageText = LocaleController.getString("CallMessageOutgoing", R.string.CallMessageOutgoing); - } - } else { - if (isMissed) { - messageText = LocaleController.getString("CallMessageIncomingMissed", R.string.CallMessageIncomingMissed); - } else if(call.reason instanceof TLRPC.TL_phoneCallDiscardReasonBusy) { - messageText = LocaleController.getString("CallMessageIncomingDeclined", R.string.CallMessageIncomingDeclined); - } else { - messageText = LocaleController.getString("CallMessageIncoming", R.string.CallMessageIncoming); - } - } - if (call.duration > 0) { - String duration = LocaleController.formatCallDuration(call.duration); - messageText = LocaleController.formatString("CallMessageWithDuration", R.string.CallMessageWithDuration, messageText, duration); - String _messageText = messageText.toString(); - int start = _messageText.indexOf(duration); - if (start != -1) { - SpannableString sp = new SpannableString(messageText); - int end = start + duration.length(); - if (start > 0 && _messageText.charAt(start - 1) == '(') { - start--; - } - if (end < _messageText.length() && _messageText.charAt(end) == ')') { - end++; - } - sp.setSpan(new TypefaceSpan(Typeface.DEFAULT), start, end, 0); - messageText = sp; - } - } - } else if (message.action instanceof TLRPC.TL_messageActionPaymentSent) { - TLRPC.User user = null; - int uid = (int) getDialogId(); - if (users != null) { - fromUser = users.get(uid); - } else if (sUsers != null) { - fromUser = sUsers.get(uid); - } - if (fromUser == null) { - fromUser = MessagesController.getInstance(accountNum).getUser(uid); - } - generatePaymentSentMessageText(user); - } else if (message.action instanceof TLRPC.TL_messageActionBotAllowed) { - String domain = ((TLRPC.TL_messageActionBotAllowed) message.action).domain; - String text = LocaleController.getString("ActionBotAllowed", R.string.ActionBotAllowed); - int start = text.indexOf("%1$s"); - SpannableString str = new SpannableString(String.format(text, domain)); - if (start >= 0) { - str.setSpan(new URLSpanNoUnderlineBold("http://" + domain), start, start + domain.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - messageText = str; - } else if (message.action instanceof TLRPC.TL_messageActionSecureValuesSent) { - TLRPC.TL_messageActionSecureValuesSent valuesSent = (TLRPC.TL_messageActionSecureValuesSent) message.action; - StringBuilder str = new StringBuilder(); - for (int a = 0, size = valuesSent.types.size(); a < size; a++) { - TLRPC.SecureValueType type = valuesSent.types.get(a); - if (str.length() > 0) { - str.append(", "); - } - if (type instanceof TLRPC.TL_secureValueTypePhone) { - str.append(LocaleController.getString("ActionBotDocumentPhone", R.string.ActionBotDocumentPhone)); - } else if (type instanceof TLRPC.TL_secureValueTypeEmail) { - str.append(LocaleController.getString("ActionBotDocumentEmail", R.string.ActionBotDocumentEmail)); - } else if (type instanceof TLRPC.TL_secureValueTypeAddress) { - str.append(LocaleController.getString("ActionBotDocumentAddress", R.string.ActionBotDocumentAddress)); - } else if (type instanceof TLRPC.TL_secureValueTypePersonalDetails) { - str.append(LocaleController.getString("ActionBotDocumentIdentity", R.string.ActionBotDocumentIdentity)); - } else if (type instanceof TLRPC.TL_secureValueTypePassport) { - str.append(LocaleController.getString("ActionBotDocumentPassport", R.string.ActionBotDocumentPassport)); - } else if (type instanceof TLRPC.TL_secureValueTypeDriverLicense) { - str.append(LocaleController.getString("ActionBotDocumentDriverLicence", R.string.ActionBotDocumentDriverLicence)); - } else if (type instanceof TLRPC.TL_secureValueTypeIdentityCard) { - str.append(LocaleController.getString("ActionBotDocumentIdentityCard", R.string.ActionBotDocumentIdentityCard)); - } else if (type instanceof TLRPC.TL_secureValueTypeUtilityBill) { - str.append(LocaleController.getString("ActionBotDocumentUtilityBill", R.string.ActionBotDocumentUtilityBill)); - } else if (type instanceof TLRPC.TL_secureValueTypeBankStatement) { - str.append(LocaleController.getString("ActionBotDocumentBankStatement", R.string.ActionBotDocumentBankStatement)); - } else if (type instanceof TLRPC.TL_secureValueTypeRentalAgreement) { - str.append(LocaleController.getString("ActionBotDocumentRentalAgreement", R.string.ActionBotDocumentRentalAgreement)); - } else if (type instanceof TLRPC.TL_secureValueTypeInternalPassport) { - str.append(LocaleController.getString("ActionBotDocumentInternalPassport", R.string.ActionBotDocumentInternalPassport)); - } else if (type instanceof TLRPC.TL_secureValueTypePassportRegistration) { - str.append(LocaleController.getString("ActionBotDocumentPassportRegistration", R.string.ActionBotDocumentPassportRegistration)); - } else if (type instanceof TLRPC.TL_secureValueTypeTemporaryRegistration) { - str.append(LocaleController.getString("ActionBotDocumentTemporaryRegistration", R.string.ActionBotDocumentTemporaryRegistration)); - } - } - TLRPC.User user = null; - if (message.to_id != null) { - if (users != null) { - user = users.get(message.to_id.user_id); - } else if (sUsers != null) { - user = sUsers.get(message.to_id.user_id); - } - if (user == null) { - user = MessagesController.getInstance(accountNum).getUser(message.to_id.user_id); - } - } - messageText = LocaleController.formatString("ActionBotDocuments", R.string.ActionBotDocuments, UserObject.getFirstName(user), str.toString()); - } - } - } else if (!isMediaEmpty()) { - if (message.media instanceof TLRPC.TL_messageMediaPoll) { - messageText = LocaleController.getString("Poll", R.string.Poll); - } else if (message.media instanceof TLRPC.TL_messageMediaPhoto) { - if (message.media.ttl_seconds != 0 && !(message instanceof TLRPC.TL_message_secret)) { - messageText = LocaleController.getString("AttachDestructingPhoto", R.string.AttachDestructingPhoto); - } else { - messageText = LocaleController.getString("AttachPhoto", R.string.AttachPhoto); - } - } else if (isVideo() || message.media instanceof TLRPC.TL_messageMediaDocument && message.media.document instanceof TLRPC.TL_documentEmpty && message.media.ttl_seconds != 0) { - if (message.media.ttl_seconds != 0 && !(message instanceof TLRPC.TL_message_secret)) { - messageText = LocaleController.getString("AttachDestructingVideo", R.string.AttachDestructingVideo); - } else { - messageText = LocaleController.getString("AttachVideo", R.string.AttachVideo); - } - } else if (isVoice()) { - messageText = LocaleController.getString("AttachAudio", R.string.AttachAudio); - } else if (isRoundVideo()) { - messageText = LocaleController.getString("AttachRound", R.string.AttachRound); - } else if (message.media instanceof TLRPC.TL_messageMediaGeo || message.media instanceof TLRPC.TL_messageMediaVenue) { - messageText = LocaleController.getString("AttachLocation", R.string.AttachLocation); - } else if (message.media instanceof TLRPC.TL_messageMediaGeoLive) { - messageText = LocaleController.getString("AttachLiveLocation", R.string.AttachLiveLocation); - } else if (message.media instanceof TLRPC.TL_messageMediaContact) { - messageText = LocaleController.getString("AttachContact", R.string.AttachContact); - if (!TextUtils.isEmpty(message.media.vcard)) { - vCardData = VCardData.parse(message.media.vcard); - } - } else if (message.media instanceof TLRPC.TL_messageMediaGame) { - messageText = message.message; - } else if (message.media instanceof TLRPC.TL_messageMediaInvoice) { - messageText = message.media.description; - } else if (message.media instanceof TLRPC.TL_messageMediaUnsupported) { - messageText = LocaleController.getString("UnsupportedMedia", R.string.UnsupportedMedia); - } else if (message.media instanceof TLRPC.TL_messageMediaDocument) { - if (isSticker() || isAnimatedSticker()) { - String sch = getStrickerChar(); - if (sch != null && sch.length() > 0) { - messageText = String.format("%s %s", sch, LocaleController.getString("AttachSticker", R.string.AttachSticker)); - } else { - messageText = LocaleController.getString("AttachSticker", R.string.AttachSticker); - } - } else if (isMusic()) { - messageText = LocaleController.getString("AttachMusic", R.string.AttachMusic); - } else if (isGif()) { - messageText = LocaleController.getString("AttachGif", R.string.AttachGif); - } else { - String name = FileLoader.getDocumentFileName(message.media.document); - if (name != null && name.length() > 0) { - messageText = name; - } else { - messageText = LocaleController.getString("AttachDocument", R.string.AttachDocument); - } - } - } - } else { - messageText = message.message; - } - - if (messageText == null) { - messageText = ""; - } - + updateMessageText(users, chats, sUsers, sChats); setType(); measureInlineBotButtons(); @@ -1261,13 +867,15 @@ public class MessageObject { } public MessageObject(int accountNum, TLRPC.TL_channelAdminLogEvent event, ArrayList messageObjects, HashMap> messagesByDays, TLRPC.Chat chat, int[] mid) { + currentEvent = event; + currentAccount = accountNum; + TLRPC.User fromUser = null; if (event.user_id > 0) { if (fromUser == null) { - fromUser = MessagesController.getInstance(accountNum).getUser(event.user_id); + fromUser = MessagesController.getInstance(currentAccount).getUser(event.user_id); } } - currentEvent = event; Calendar rightNow = new GregorianCalendar(); rightNow.setTimeInMillis((long) (event.date) * 1000); @@ -1325,7 +933,7 @@ public class MessageObject { } else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionParticipantInvite) { messageOwner = new TLRPC.TL_messageService(); messageOwner.action = new TLRPC.TL_messageActionChatAddUser(); - TLRPC.User whoUser = MessagesController.getInstance(accountNum).getUser(event.action.participant.user_id); + TLRPC.User whoUser = MessagesController.getInstance(currentAccount).getUser(event.action.participant.user_id); if (event.action.participant.user_id == messageOwner.from_id) { if (chat.megagroup) { messageText = replaceWithLink(LocaleController.getString("EventLogGroupJoined", R.string.EventLogGroupJoined), "un1", fromUser); @@ -1338,57 +946,63 @@ public class MessageObject { } } else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionParticipantToggleAdmin) { messageOwner = new TLRPC.TL_message(); + TLRPC.User whoUser = MessagesController.getInstance(currentAccount).getUser(event.action.prev_participant.user_id); + StringBuilder rights; + if (!(event.action.prev_participant instanceof TLRPC.TL_channelParticipantCreator) && event.action.new_participant instanceof TLRPC.TL_channelParticipantCreator) { + String str = LocaleController.getString("EventLogChangedOwnership", R.string.EventLogChangedOwnership); + int offset = str.indexOf("%1$s"); + rights = new StringBuilder(String.format(str, getUserName(whoUser, messageOwner.entities, offset))); + } else { + String str = LocaleController.getString("EventLogPromoted", R.string.EventLogPromoted); + int offset = str.indexOf("%1$s"); + rights = new StringBuilder(String.format(str, getUserName(whoUser, messageOwner.entities, offset))); + rights.append("\n"); - TLRPC.User whoUser = MessagesController.getInstance(accountNum).getUser(event.action.prev_participant.user_id); - String str = LocaleController.getString("EventLogPromoted", R.string.EventLogPromoted); - int offset = str.indexOf("%1$s"); - StringBuilder rights = new StringBuilder(String.format(str, getUserName(whoUser, messageOwner.entities, offset))); - rights.append("\n"); - - TLRPC.TL_chatAdminRights o = event.action.prev_participant.admin_rights; - TLRPC.TL_chatAdminRights n = event.action.new_participant.admin_rights; - if (o == null) { - o = new TLRPC.TL_chatAdminRights(); - } - if (n == null) { - n = new TLRPC.TL_chatAdminRights(); - } - if (o.change_info != n.change_info) { - rights.append('\n').append(n.change_info ? '+' : '-').append(' '); - rights.append(chat.megagroup ? LocaleController.getString("EventLogPromotedChangeGroupInfo", R.string.EventLogPromotedChangeGroupInfo) : LocaleController.getString("EventLogPromotedChangeChannelInfo", R.string.EventLogPromotedChangeChannelInfo)); - } - if (!chat.megagroup) { - if (o.post_messages != n.post_messages) { - rights.append('\n').append(n.post_messages ? '+' : '-').append(' '); - rights.append(LocaleController.getString("EventLogPromotedPostMessages", R.string.EventLogPromotedPostMessages)); + TLRPC.TL_chatAdminRights o = event.action.prev_participant.admin_rights; + TLRPC.TL_chatAdminRights n = event.action.new_participant.admin_rights; + if (o == null) { + o = new TLRPC.TL_chatAdminRights(); } - if (o.edit_messages != n.edit_messages) { - rights.append('\n').append(n.edit_messages ? '+' : '-').append(' '); - rights.append(LocaleController.getString("EventLogPromotedEditMessages", R.string.EventLogPromotedEditMessages)); + if (n == null) { + n = new TLRPC.TL_chatAdminRights(); } - } - if (o.delete_messages != n.delete_messages) { - rights.append('\n').append(n.delete_messages ? '+' : '-').append(' '); - rights.append(LocaleController.getString("EventLogPromotedDeleteMessages", R.string.EventLogPromotedDeleteMessages)); - } - if (o.add_admins != n.add_admins) { - rights.append('\n').append(n.add_admins ? '+' : '-').append(' '); - rights.append(LocaleController.getString("EventLogPromotedAddAdmins", R.string.EventLogPromotedAddAdmins)); - } - if (chat.megagroup) { - if (o.ban_users != n.ban_users) { - rights.append('\n').append(n.ban_users ? '+' : '-').append(' '); - rights.append(LocaleController.getString("EventLogPromotedBanUsers", R.string.EventLogPromotedBanUsers)); + if (o.change_info != n.change_info) { + rights.append('\n').append(n.change_info ? '+' : '-').append(' '); + rights.append(chat.megagroup ? LocaleController.getString("EventLogPromotedChangeGroupInfo", R.string.EventLogPromotedChangeGroupInfo) : LocaleController.getString("EventLogPromotedChangeChannelInfo", R.string.EventLogPromotedChangeChannelInfo)); } - } - if (o.invite_users != n.invite_users) { - rights.append('\n').append(n.invite_users ? '+' : '-').append(' '); - rights.append(LocaleController.getString("EventLogPromotedAddUsers", R.string.EventLogPromotedAddUsers)); - } - if (chat.megagroup) { - if (o.pin_messages != n.pin_messages) { - rights.append('\n').append(n.pin_messages ? '+' : '-').append(' '); - rights.append(LocaleController.getString("EventLogPromotedPinMessages", R.string.EventLogPromotedPinMessages)); + if (!chat.megagroup) { + if (o.post_messages != n.post_messages) { + rights.append('\n').append(n.post_messages ? '+' : '-').append(' '); + rights.append(LocaleController.getString("EventLogPromotedPostMessages", R.string.EventLogPromotedPostMessages)); + } + if (o.edit_messages != n.edit_messages) { + rights.append('\n').append(n.edit_messages ? '+' : '-').append(' '); + rights.append(LocaleController.getString("EventLogPromotedEditMessages", R.string.EventLogPromotedEditMessages)); + } + } + if (o.delete_messages != n.delete_messages) { + rights.append('\n').append(n.delete_messages ? '+' : '-').append(' '); + rights.append(LocaleController.getString("EventLogPromotedDeleteMessages", R.string.EventLogPromotedDeleteMessages)); + } + if (o.add_admins != n.add_admins) { + rights.append('\n').append(n.add_admins ? '+' : '-').append(' '); + rights.append(LocaleController.getString("EventLogPromotedAddAdmins", R.string.EventLogPromotedAddAdmins)); + } + if (chat.megagroup) { + if (o.ban_users != n.ban_users) { + rights.append('\n').append(n.ban_users ? '+' : '-').append(' '); + rights.append(LocaleController.getString("EventLogPromotedBanUsers", R.string.EventLogPromotedBanUsers)); + } + } + if (o.invite_users != n.invite_users) { + rights.append('\n').append(n.invite_users ? '+' : '-').append(' '); + rights.append(LocaleController.getString("EventLogPromotedAddUsers", R.string.EventLogPromotedAddUsers)); + } + if (chat.megagroup) { + if (o.pin_messages != n.pin_messages) { + rights.append('\n').append(n.pin_messages ? '+' : '-').append(' '); + rights.append(LocaleController.getString("EventLogPromotedPinMessages", R.string.EventLogPromotedPinMessages)); + } } } messageText = rights.toString(); @@ -1473,7 +1087,7 @@ public class MessageObject { messageText = rights.toString(); } else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionParticipantToggleBan) { messageOwner = new TLRPC.TL_message(); - TLRPC.User whoUser = MessagesController.getInstance(accountNum).getUser(event.action.prev_participant.user_id); + TLRPC.User whoUser = MessagesController.getInstance(currentAccount).getUser(event.action.prev_participant.user_id); TLRPC.TL_chatBannedRights o = event.action.prev_participant.banned_rights; TLRPC.TL_chatBannedRights n = event.action.new_participant.banned_rights; if (chat.megagroup && (n == null || !n.view_messages || n != null && o != null && n.until_date != o.until_date)) { @@ -1710,7 +1324,7 @@ public class MessageObject { message.to_id = to_id; message.date = event.date; if (!TextUtils.isEmpty(newLink)) { - message.message = "https://" + MessagesController.getInstance(accountNum).linkPrefix + "/" + newLink; + message.message = "https://" + MessagesController.getInstance(currentAccount).linkPrefix + "/" + newLink; } else { message.message = ""; } @@ -1725,7 +1339,7 @@ public class MessageObject { message.media.webpage.display_url = ""; message.media.webpage.url = ""; message.media.webpage.site_name = LocaleController.getString("EventLogPreviousLink", R.string.EventLogPreviousLink); - message.media.webpage.description = "https://" + MessagesController.getInstance(accountNum).linkPrefix + "/" + ((TLRPC.TL_channelAdminLogEventActionChangeUsername) event.action).prev_value; + message.media.webpage.description = "https://" + MessagesController.getInstance(currentAccount).linkPrefix + "/" + ((TLRPC.TL_channelAdminLogEventActionChangeUsername) event.action).prev_value; } else { message.media = new TLRPC.TL_messageMediaEmpty(); } @@ -1796,6 +1410,14 @@ public class MessageObject { } else { messageText = replaceWithLink(LocaleController.getString("EventLogChangedStickersSet", R.string.EventLogChangedStickersSet), "un1", fromUser); } + } else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionChangeLocation) { + TLRPC.TL_channelAdminLogEventActionChangeLocation location = (TLRPC.TL_channelAdminLogEventActionChangeLocation) event.action; + if (location.new_value instanceof TLRPC.TL_channelLocationEmpty) { + messageText = replaceWithLink(LocaleController.getString("EventLogRemovedLocation", R.string.EventLogRemovedLocation), "un1", fromUser); + } else { + TLRPC.TL_channelLocation channelLocation = (TLRPC.TL_channelLocation) location.new_value; + messageText = replaceWithLink(LocaleController.formatString("EventLogChangedLocation", R.string.EventLogChangedLocation, channelLocation.address), "un1", fromUser); + } } else { messageText = "unsupported " + event.action; } @@ -1829,21 +1451,21 @@ public class MessageObject { if (chat.megagroup) { message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; } - MessageObject messageObject = new MessageObject(accountNum, message, null, null, true, eventId); + MessageObject messageObject = new MessageObject(currentAccount, message, null, null, true, eventId); if (messageObject.contentType >= 0) { if (mediaController.isPlayingMessage(messageObject)) { MessageObject player = mediaController.getPlayingMessageObject(); messageObject.audioProgress = player.audioProgress; messageObject.audioProgressSec = player.audioProgressSec; } - createDateArray(accountNum, event, messageObjects, messagesByDays); + createDateArray(currentAccount, event, messageObjects, messagesByDays); messageObjects.add(messageObjects.size() - 1, messageObject); } else { contentType = -1; } } if (contentType >= 0) { - createDateArray(accountNum, event, messageObjects, messagesByDays); + createDateArray(currentAccount, event, messageObjects, messagesByDays); messageObjects.add(messageObjects.size() - 1, this); } else { return; @@ -2246,6 +1868,417 @@ public class MessageObject { return localType != 0; } + private void updateMessageText(AbstractMap users, AbstractMap chats, SparseArray sUsers, SparseArray sChats) { + TLRPC.User fromUser = null; + if (messageOwner.from_id > 0) { + if (users != null) { + fromUser = users.get(messageOwner.from_id); + } else if (sUsers != null) { + fromUser = sUsers.get(messageOwner.from_id); + } + if (fromUser == null) { + fromUser = MessagesController.getInstance(currentAccount).getUser(messageOwner.from_id); + } + } + + if (messageOwner instanceof TLRPC.TL_messageService) { + if (messageOwner.action != null) { + if (messageOwner.action instanceof TLRPC.TL_messageActionCustomAction) { + messageText = messageOwner.action.message; + } else if (messageOwner.action instanceof TLRPC.TL_messageActionChatCreate) { + if (isOut()) { + messageText = LocaleController.getString("ActionYouCreateGroup", R.string.ActionYouCreateGroup); + } else { + messageText = replaceWithLink(LocaleController.getString("ActionCreateGroup", R.string.ActionCreateGroup), "un1", fromUser); + } + } else if (messageOwner.action instanceof TLRPC.TL_messageActionChatDeleteUser) { + if (messageOwner.action.user_id == messageOwner.from_id) { + if (isOut()) { + messageText = LocaleController.getString("ActionYouLeftUser", R.string.ActionYouLeftUser); + } else { + messageText = replaceWithLink(LocaleController.getString("ActionLeftUser", R.string.ActionLeftUser), "un1", fromUser); + } + } else { + TLRPC.User whoUser = null; + if (users != null) { + whoUser = users.get(messageOwner.action.user_id); + } else if (sUsers != null) { + whoUser = sUsers.get(messageOwner.action.user_id); + } + if (whoUser == null) { + whoUser = MessagesController.getInstance(currentAccount).getUser(messageOwner.action.user_id); + } + if (isOut()) { + messageText = replaceWithLink(LocaleController.getString("ActionYouKickUser", R.string.ActionYouKickUser), "un2", whoUser); + } else if (messageOwner.action.user_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + messageText = replaceWithLink(LocaleController.getString("ActionKickUserYou", R.string.ActionKickUserYou), "un1", fromUser); + } else { + messageText = replaceWithLink(LocaleController.getString("ActionKickUser", R.string.ActionKickUser), "un2", whoUser); + messageText = replaceWithLink(messageText, "un1", fromUser); + } + } + } else if (messageOwner.action instanceof TLRPC.TL_messageActionChatAddUser) { + int singleUserId = messageOwner.action.user_id; + if (singleUserId == 0 && messageOwner.action.users.size() == 1) { + singleUserId = messageOwner.action.users.get(0); + } + if (singleUserId != 0) { + TLRPC.User whoUser = null; + if (users != null) { + whoUser = users.get(singleUserId); + } else if (sUsers != null) { + whoUser = sUsers.get(singleUserId); + } + if (whoUser == null) { + whoUser = MessagesController.getInstance(currentAccount).getUser(singleUserId); + } + if (singleUserId == messageOwner.from_id) { + if (messageOwner.to_id.channel_id != 0 && !isMegagroup()) { + messageText = LocaleController.getString("ChannelJoined", R.string.ChannelJoined); + } else { + if (messageOwner.to_id.channel_id != 0 && isMegagroup()) { + if (singleUserId == UserConfig.getInstance(currentAccount).getClientUserId()) { + messageText = LocaleController.getString("ChannelMegaJoined", R.string.ChannelMegaJoined); + } else { + messageText = replaceWithLink(LocaleController.getString("ActionAddUserSelfMega", R.string.ActionAddUserSelfMega), "un1", fromUser); + } + } else if (isOut()) { + messageText = LocaleController.getString("ActionAddUserSelfYou", R.string.ActionAddUserSelfYou); + } else { + messageText = replaceWithLink(LocaleController.getString("ActionAddUserSelf", R.string.ActionAddUserSelf), "un1", fromUser); + } + } + } else { + if (isOut()) { + messageText = replaceWithLink(LocaleController.getString("ActionYouAddUser", R.string.ActionYouAddUser), "un2", whoUser); + } else if (singleUserId == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (messageOwner.to_id.channel_id != 0) { + if (isMegagroup()) { + messageText = replaceWithLink(LocaleController.getString("MegaAddedBy", R.string.MegaAddedBy), "un1", fromUser); + } else { + messageText = replaceWithLink(LocaleController.getString("ChannelAddedBy", R.string.ChannelAddedBy), "un1", fromUser); + } + } else { + messageText = replaceWithLink(LocaleController.getString("ActionAddUserYou", R.string.ActionAddUserYou), "un1", fromUser); + } + } else { + messageText = replaceWithLink(LocaleController.getString("ActionAddUser", R.string.ActionAddUser), "un2", whoUser); + messageText = replaceWithLink(messageText, "un1", fromUser); + } + } + } else { + if (isOut()) { + messageText = replaceWithLink(LocaleController.getString("ActionYouAddUser", R.string.ActionYouAddUser), "un2", messageOwner.action.users, users, sUsers); + } else { + messageText = replaceWithLink(LocaleController.getString("ActionAddUser", R.string.ActionAddUser), "un2", messageOwner.action.users, users, sUsers); + messageText = replaceWithLink(messageText, "un1", fromUser); + } + } + } else if (messageOwner.action instanceof TLRPC.TL_messageActionChatJoinedByLink) { + if (isOut()) { + messageText = LocaleController.getString("ActionInviteYou", R.string.ActionInviteYou); + } else { + messageText = replaceWithLink(LocaleController.getString("ActionInviteUser", R.string.ActionInviteUser), "un1", fromUser); + } + } else if (messageOwner.action instanceof TLRPC.TL_messageActionChatEditPhoto) { + if (messageOwner.to_id.channel_id != 0 && !isMegagroup()) { + messageText = LocaleController.getString("ActionChannelChangedPhoto", R.string.ActionChannelChangedPhoto); + } else { + if (isOut()) { + messageText = LocaleController.getString("ActionYouChangedPhoto", R.string.ActionYouChangedPhoto); + } else { + messageText = replaceWithLink(LocaleController.getString("ActionChangedPhoto", R.string.ActionChangedPhoto), "un1", fromUser); + } + } + } else if (messageOwner.action instanceof TLRPC.TL_messageActionChatEditTitle) { + if (messageOwner.to_id.channel_id != 0 && !isMegagroup()) { + messageText = LocaleController.getString("ActionChannelChangedTitle", R.string.ActionChannelChangedTitle).replace("un2", messageOwner.action.title); + } else { + if (isOut()) { + messageText = LocaleController.getString("ActionYouChangedTitle", R.string.ActionYouChangedTitle).replace("un2", messageOwner.action.title); + } else { + messageText = replaceWithLink(LocaleController.getString("ActionChangedTitle", R.string.ActionChangedTitle).replace("un2", messageOwner.action.title), "un1", fromUser); + } + } + } else if (messageOwner.action instanceof TLRPC.TL_messageActionChatDeletePhoto) { + if (messageOwner.to_id.channel_id != 0 && !isMegagroup()) { + messageText = LocaleController.getString("ActionChannelRemovedPhoto", R.string.ActionChannelRemovedPhoto); + } else { + if (isOut()) { + messageText = LocaleController.getString("ActionYouRemovedPhoto", R.string.ActionYouRemovedPhoto); + } else { + messageText = replaceWithLink(LocaleController.getString("ActionRemovedPhoto", R.string.ActionRemovedPhoto), "un1", fromUser); + } + } + } else if (messageOwner.action instanceof TLRPC.TL_messageActionTTLChange) { + if (messageOwner.action.ttl != 0) { + if (isOut()) { + messageText = LocaleController.formatString("MessageLifetimeChangedOutgoing", R.string.MessageLifetimeChangedOutgoing, LocaleController.formatTTLString(messageOwner.action.ttl)); + } else { + messageText = LocaleController.formatString("MessageLifetimeChanged", R.string.MessageLifetimeChanged, UserObject.getFirstName(fromUser), LocaleController.formatTTLString(messageOwner.action.ttl)); + } + } else { + if (isOut()) { + messageText = LocaleController.getString("MessageLifetimeYouRemoved", R.string.MessageLifetimeYouRemoved); + } else { + messageText = LocaleController.formatString("MessageLifetimeRemoved", R.string.MessageLifetimeRemoved, UserObject.getFirstName(fromUser)); + } + } + } else if (messageOwner.action instanceof TLRPC.TL_messageActionLoginUnknownLocation) { + String date; + long time = ((long) messageOwner.date) * 1000; + if (LocaleController.getInstance().formatterDay != null && LocaleController.getInstance().formatterYear != null) { + date = LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, LocaleController.getInstance().formatterYear.format(time), LocaleController.getInstance().formatterDay.format(time)); + } else { + date = "" + messageOwner.date; + } + TLRPC.User to_user = UserConfig.getInstance(currentAccount).getCurrentUser(); + if (to_user == null) { + if (users != null) { + to_user = users.get(messageOwner.to_id.user_id); + } else if (sUsers != null) { + to_user = sUsers.get(messageOwner.to_id.user_id); + } + if (to_user == null) { + to_user = MessagesController.getInstance(currentAccount).getUser(messageOwner.to_id.user_id); + } + } + String name = to_user != null ? UserObject.getFirstName(to_user) : ""; + messageText = LocaleController.formatString("NotificationUnrecognizedDevice", R.string.NotificationUnrecognizedDevice, name, date, messageOwner.action.title, messageOwner.action.address); + } else if (messageOwner.action instanceof TLRPC.TL_messageActionUserJoined || messageOwner.action instanceof TLRPC.TL_messageActionContactSignUp) { + messageText = LocaleController.formatString("NotificationContactJoined", R.string.NotificationContactJoined, UserObject.getUserName(fromUser)); + } else if (messageOwner.action instanceof TLRPC.TL_messageActionUserUpdatedPhoto) { + messageText = LocaleController.formatString("NotificationContactNewPhoto", R.string.NotificationContactNewPhoto, UserObject.getUserName(fromUser)); + } else if (messageOwner.action instanceof TLRPC.TL_messageEncryptedAction) { + if (messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionScreenshotMessages) { + if (isOut()) { + messageText = LocaleController.formatString("ActionTakeScreenshootYou", R.string.ActionTakeScreenshootYou); + } else { + messageText = replaceWithLink(LocaleController.getString("ActionTakeScreenshoot", R.string.ActionTakeScreenshoot), "un1", fromUser); + } + } else if (messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionSetMessageTTL) { + TLRPC.TL_decryptedMessageActionSetMessageTTL action = (TLRPC.TL_decryptedMessageActionSetMessageTTL) messageOwner.action.encryptedAction; + if (action.ttl_seconds != 0) { + if (isOut()) { + messageText = LocaleController.formatString("MessageLifetimeChangedOutgoing", R.string.MessageLifetimeChangedOutgoing, LocaleController.formatTTLString(action.ttl_seconds)); + } else { + messageText = LocaleController.formatString("MessageLifetimeChanged", R.string.MessageLifetimeChanged, UserObject.getFirstName(fromUser), LocaleController.formatTTLString(action.ttl_seconds)); + } + } else { + if (isOut()) { + messageText = LocaleController.getString("MessageLifetimeYouRemoved", R.string.MessageLifetimeYouRemoved); + } else { + messageText = LocaleController.formatString("MessageLifetimeRemoved", R.string.MessageLifetimeRemoved, UserObject.getFirstName(fromUser)); + } + } + } + } else if (messageOwner.action instanceof TLRPC.TL_messageActionScreenshotTaken) { + if (isOut()) { + messageText = LocaleController.formatString("ActionTakeScreenshootYou", R.string.ActionTakeScreenshootYou); + } else { + messageText = replaceWithLink(LocaleController.getString("ActionTakeScreenshoot", R.string.ActionTakeScreenshoot), "un1", fromUser); + } + } else if (messageOwner.action instanceof TLRPC.TL_messageActionCreatedBroadcastList) { + messageText = LocaleController.formatString("YouCreatedBroadcastList", R.string.YouCreatedBroadcastList); + } else if (messageOwner.action instanceof TLRPC.TL_messageActionChannelCreate) { + if (isMegagroup()) { + messageText = LocaleController.getString("ActionCreateMega", R.string.ActionCreateMega); + } else { + messageText = LocaleController.getString("ActionCreateChannel", R.string.ActionCreateChannel); + } + } else if (messageOwner.action instanceof TLRPC.TL_messageActionChatMigrateTo) { + messageText = LocaleController.getString("ActionMigrateFromGroup", R.string.ActionMigrateFromGroup); + } else if (messageOwner.action instanceof TLRPC.TL_messageActionChannelMigrateFrom) { + messageText = LocaleController.getString("ActionMigrateFromGroup", R.string.ActionMigrateFromGroup); + } else if (messageOwner.action instanceof TLRPC.TL_messageActionPinMessage) { + TLRPC.Chat chat; + if (fromUser == null) { + if (chats != null) { + chat = chats.get(messageOwner.to_id.channel_id); + } else if (sChats != null) { + chat = sChats.get(messageOwner.to_id.channel_id); + } else { + chat = null; + } + } else { + chat = null; + } + 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_messageActionGameScore) { + generateGameMessageText(fromUser); + } else if (messageOwner.action instanceof TLRPC.TL_messageActionPhoneCall) { + TLRPC.TL_messageActionPhoneCall call = (TLRPC.TL_messageActionPhoneCall) messageOwner.action; + boolean isMissed = call.reason instanceof TLRPC.TL_phoneCallDiscardReasonMissed; + if (messageOwner.from_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (isMissed) { + messageText = LocaleController.getString("CallMessageOutgoingMissed", R.string.CallMessageOutgoingMissed); + }else { + messageText = LocaleController.getString("CallMessageOutgoing", R.string.CallMessageOutgoing); + } + } else { + if (isMissed) { + messageText = LocaleController.getString("CallMessageIncomingMissed", R.string.CallMessageIncomingMissed); + } else if(call.reason instanceof TLRPC.TL_phoneCallDiscardReasonBusy) { + messageText = LocaleController.getString("CallMessageIncomingDeclined", R.string.CallMessageIncomingDeclined); + } else { + messageText = LocaleController.getString("CallMessageIncoming", R.string.CallMessageIncoming); + } + } + if (call.duration > 0) { + String duration = LocaleController.formatCallDuration(call.duration); + messageText = LocaleController.formatString("CallMessageWithDuration", R.string.CallMessageWithDuration, messageText, duration); + String _messageText = messageText.toString(); + int start = _messageText.indexOf(duration); + if (start != -1) { + SpannableString sp = new SpannableString(messageText); + int end = start + duration.length(); + if (start > 0 && _messageText.charAt(start - 1) == '(') { + start--; + } + if (end < _messageText.length() && _messageText.charAt(end) == ')') { + end++; + } + sp.setSpan(new TypefaceSpan(Typeface.DEFAULT), start, end, 0); + messageText = sp; + } + } + } else if (messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { + TLRPC.User user = null; + int uid = (int) getDialogId(); + if (users != null) { + user = users.get(uid); + } else if (sUsers != null) { + user = sUsers.get(uid); + } + if (user == null) { + user = MessagesController.getInstance(currentAccount).getUser(uid); + } + generatePaymentSentMessageText(user); + } else if (messageOwner.action instanceof TLRPC.TL_messageActionBotAllowed) { + String domain = ((TLRPC.TL_messageActionBotAllowed) messageOwner.action).domain; + String text = LocaleController.getString("ActionBotAllowed", R.string.ActionBotAllowed); + int start = text.indexOf("%1$s"); + SpannableString str = new SpannableString(String.format(text, domain)); + if (start >= 0) { + str.setSpan(new URLSpanNoUnderlineBold("http://" + domain), start, start + domain.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + messageText = str; + } else if (messageOwner.action instanceof TLRPC.TL_messageActionSecureValuesSent) { + TLRPC.TL_messageActionSecureValuesSent valuesSent = (TLRPC.TL_messageActionSecureValuesSent) messageOwner.action; + StringBuilder str = new StringBuilder(); + for (int a = 0, size = valuesSent.types.size(); a < size; a++) { + TLRPC.SecureValueType type = valuesSent.types.get(a); + if (str.length() > 0) { + str.append(", "); + } + if (type instanceof TLRPC.TL_secureValueTypePhone) { + str.append(LocaleController.getString("ActionBotDocumentPhone", R.string.ActionBotDocumentPhone)); + } else if (type instanceof TLRPC.TL_secureValueTypeEmail) { + str.append(LocaleController.getString("ActionBotDocumentEmail", R.string.ActionBotDocumentEmail)); + } else if (type instanceof TLRPC.TL_secureValueTypeAddress) { + str.append(LocaleController.getString("ActionBotDocumentAddress", R.string.ActionBotDocumentAddress)); + } else if (type instanceof TLRPC.TL_secureValueTypePersonalDetails) { + str.append(LocaleController.getString("ActionBotDocumentIdentity", R.string.ActionBotDocumentIdentity)); + } else if (type instanceof TLRPC.TL_secureValueTypePassport) { + str.append(LocaleController.getString("ActionBotDocumentPassport", R.string.ActionBotDocumentPassport)); + } else if (type instanceof TLRPC.TL_secureValueTypeDriverLicense) { + str.append(LocaleController.getString("ActionBotDocumentDriverLicence", R.string.ActionBotDocumentDriverLicence)); + } else if (type instanceof TLRPC.TL_secureValueTypeIdentityCard) { + str.append(LocaleController.getString("ActionBotDocumentIdentityCard", R.string.ActionBotDocumentIdentityCard)); + } else if (type instanceof TLRPC.TL_secureValueTypeUtilityBill) { + str.append(LocaleController.getString("ActionBotDocumentUtilityBill", R.string.ActionBotDocumentUtilityBill)); + } else if (type instanceof TLRPC.TL_secureValueTypeBankStatement) { + str.append(LocaleController.getString("ActionBotDocumentBankStatement", R.string.ActionBotDocumentBankStatement)); + } else if (type instanceof TLRPC.TL_secureValueTypeRentalAgreement) { + str.append(LocaleController.getString("ActionBotDocumentRentalAgreement", R.string.ActionBotDocumentRentalAgreement)); + } else if (type instanceof TLRPC.TL_secureValueTypeInternalPassport) { + str.append(LocaleController.getString("ActionBotDocumentInternalPassport", R.string.ActionBotDocumentInternalPassport)); + } else if (type instanceof TLRPC.TL_secureValueTypePassportRegistration) { + str.append(LocaleController.getString("ActionBotDocumentPassportRegistration", R.string.ActionBotDocumentPassportRegistration)); + } else if (type instanceof TLRPC.TL_secureValueTypeTemporaryRegistration) { + str.append(LocaleController.getString("ActionBotDocumentTemporaryRegistration", R.string.ActionBotDocumentTemporaryRegistration)); + } + } + TLRPC.User user = null; + if (messageOwner.to_id != null) { + if (users != null) { + user = users.get(messageOwner.to_id.user_id); + } else if (sUsers != null) { + user = sUsers.get(messageOwner.to_id.user_id); + } + if (user == null) { + user = MessagesController.getInstance(currentAccount).getUser(messageOwner.to_id.user_id); + } + } + messageText = LocaleController.formatString("ActionBotDocuments", R.string.ActionBotDocuments, UserObject.getFirstName(user), str.toString()); + } + } + } else if (!isMediaEmpty()) { + if (messageOwner.media instanceof TLRPC.TL_messageMediaPoll) { + messageText = LocaleController.getString("Poll", R.string.Poll); + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { + if (messageOwner.media.ttl_seconds != 0 && !(messageOwner instanceof TLRPC.TL_message_secret)) { + messageText = LocaleController.getString("AttachDestructingPhoto", R.string.AttachDestructingPhoto); + } else { + messageText = LocaleController.getString("AttachPhoto", R.string.AttachPhoto); + } + } else if (isVideo() || messageOwner.media instanceof TLRPC.TL_messageMediaDocument && messageOwner.media.document instanceof TLRPC.TL_documentEmpty && messageOwner.media.ttl_seconds != 0) { + if (messageOwner.media.ttl_seconds != 0 && !(messageOwner instanceof TLRPC.TL_message_secret)) { + messageText = LocaleController.getString("AttachDestructingVideo", R.string.AttachDestructingVideo); + } else { + messageText = LocaleController.getString("AttachVideo", R.string.AttachVideo); + } + } else if (isVoice()) { + messageText = LocaleController.getString("AttachAudio", R.string.AttachAudio); + } else if (isRoundVideo()) { + messageText = LocaleController.getString("AttachRound", R.string.AttachRound); + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageOwner.media instanceof TLRPC.TL_messageMediaVenue) { + messageText = LocaleController.getString("AttachLocation", R.string.AttachLocation); + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaGeoLive) { + messageText = LocaleController.getString("AttachLiveLocation", R.string.AttachLiveLocation); + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaContact) { + messageText = LocaleController.getString("AttachContact", R.string.AttachContact); + if (!TextUtils.isEmpty(messageOwner.media.vcard)) { + vCardData = VCardData.parse(messageOwner.media.vcard); + } + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaGame) { + messageText = messageOwner.message; + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) { + messageText = messageOwner.media.description; + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaUnsupported) { + messageText = LocaleController.getString("UnsupportedMedia", R.string.UnsupportedMedia); + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { + if (isSticker() || isAnimatedSticker()) { + String sch = getStrickerChar(); + if (sch != null && sch.length() > 0) { + messageText = String.format("%s %s", sch, LocaleController.getString("AttachSticker", R.string.AttachSticker)); + } else { + messageText = LocaleController.getString("AttachSticker", R.string.AttachSticker); + } + } else if (isMusic()) { + messageText = LocaleController.getString("AttachMusic", R.string.AttachMusic); + } else if (isGif()) { + messageText = LocaleController.getString("AttachGif", R.string.AttachGif); + } else { + String name = FileLoader.getDocumentFileName(messageOwner.media.document); + if (name != null && name.length() > 0) { + messageText = name; + } else { + messageText = LocaleController.getString("AttachDocument", R.string.AttachDocument); + } + } + } + } else { + messageText = messageOwner.message; + } + + if (messageText == null) { + messageText = ""; + } + } + public void setType() { int oldType = type; isRoundVideoCached = 0; @@ -2320,6 +2353,7 @@ public class MessageObject { } } if (oldType != 1000 && oldType != type) { + updateMessageText(MessagesController.getInstance(currentAccount).getUsers(), MessagesController.getInstance(currentAccount).getChats(), null, null); generateThumbs(false); } } @@ -3036,16 +3070,16 @@ public class MessageObject { } public static boolean addEntitiesToText(CharSequence text, ArrayList entities, boolean out, int type, boolean usernames, boolean photoViewer, boolean useManualParse) { - boolean hasUrls = false; if (!(text instanceof Spannable)) { return false; } Spannable spannable = (Spannable) text; - int count = entities.size(); URLSpan[] spans = spannable.getSpans(0, text.length(), URLSpan.class); - if (spans != null && spans.length > 0) { - hasUrls = true; + boolean hasUrls = spans != null && spans.length > 0; + if (entities.isEmpty()) { + return hasUrls; } + byte t; if (photoViewer) { t = 2; @@ -3054,15 +3088,31 @@ public class MessageObject { } else { t = 0; } - for (int a = 0; a < count; a++) { - TLRPC.MessageEntity entity = entities.get(a); + + ArrayList runs = new ArrayList<>(); + ArrayList entitiesCopy = new ArrayList<>(entities); + + Collections.sort(entitiesCopy, (o1, o2) -> { + if (o1.offset > o2.offset) { + return 1; + } else if (o1.offset < o2.offset) { + return -1; + } + return 0; + }); + for (int a = 0, N = entitiesCopy.size(); a < N; a++) { + TLRPC.MessageEntity entity = entitiesCopy.get(a); if (entity.length <= 0 || entity.offset < 0 || entity.offset >= text.length()) { continue; } else if (entity.offset + entity.length > text.length()) { entity.length = text.length() - entity.offset; } + if (!useManualParse || entity instanceof TLRPC.TL_messageEntityBold || entity instanceof TLRPC.TL_messageEntityItalic || + entity instanceof TLRPC.TL_messageEntityStrike || + entity instanceof TLRPC.TL_messageEntityUnderline || + entity instanceof TLRPC.TL_messageEntityBlockquote || entity instanceof TLRPC.TL_messageEntityCode || entity instanceof TLRPC.TL_messageEntityPre || entity instanceof TLRPC.TL_messageEntityMentionName || @@ -3081,52 +3131,150 @@ public class MessageObject { } } } - if (entity instanceof TLRPC.TL_messageEntityBold) { - spannable.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + TextStyleSpan.TextStyleRun newRun = new TextStyleSpan.TextStyleRun(); + newRun.start = entity.offset; + newRun.end = newRun.start + entity.length; + TLRPC.MessageEntity urlEntity = null; + if (entity instanceof TLRPC.TL_messageEntityStrike) { + newRun.flags = TextStyleSpan.FLAG_STYLE_STRIKE; + } else if (entity instanceof TLRPC.TL_messageEntityUnderline) { + newRun.flags = TextStyleSpan.FLAG_STYLE_UNDERLINE; + } else if (entity instanceof TLRPC.TL_messageEntityBlockquote) { + newRun.flags = TextStyleSpan.FLAG_STYLE_QUOTE; + } else if (entity instanceof TLRPC.TL_messageEntityBold) { + newRun.flags = TextStyleSpan.FLAG_STYLE_BOLD; } else if (entity instanceof TLRPC.TL_messageEntityItalic) { - spannable.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/ritalic.ttf")), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + newRun.flags = TextStyleSpan.FLAG_STYLE_ITALIC; } else if (entity instanceof TLRPC.TL_messageEntityCode || entity instanceof TLRPC.TL_messageEntityPre) { - spannable.setSpan(new URLSpanMono(spannable, entity.offset, entity.offset + entity.length, t), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + newRun.flags = TextStyleSpan.FLAG_STYLE_MONO; } else if (entity instanceof TLRPC.TL_messageEntityMentionName) { - if (usernames) { - spannable.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_messageEntityMentionName) entity).user_id, t), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + if (!usernames) { + continue; } + newRun.flags = TextStyleSpan.FLAG_STYLE_MENTION; + newRun.urlEntity = entity; } else if (entity instanceof TLRPC.TL_inputMessageEntityMentionName) { - if (usernames) { - spannable.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_inputMessageEntityMentionName) entity).user_id.user_id, t), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + if (!usernames) { + continue; } - } else if (!useManualParse) { - String url = TextUtils.substring(text, entity.offset, entity.offset + entity.length); - if (entity instanceof TLRPC.TL_messageEntityBotCommand) { - spannable.setSpan(new URLSpanBotCommand(url, t), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else if (entity instanceof TLRPC.TL_messageEntityHashtag || (usernames && entity instanceof TLRPC.TL_messageEntityMention) || entity instanceof TLRPC.TL_messageEntityCashtag) { - spannable.setSpan(new URLSpanNoUnderline(url), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else if (entity instanceof TLRPC.TL_messageEntityEmail) { - spannable.setSpan(new URLSpanReplacement("mailto:" + url), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else if (entity instanceof TLRPC.TL_messageEntityUrl) { - if (Browser.isPassportUrl(entity.url)) { + newRun.flags = TextStyleSpan.FLAG_STYLE_MENTION; + newRun.urlEntity = entity; + } else { + if (useManualParse) { + continue; + } + if ((entity instanceof TLRPC.TL_messageEntityUrl || entity instanceof TLRPC.TL_messageEntityTextUrl) && Browser.isPassportUrl(entity.url)) { + continue; + } + if (entity instanceof TLRPC.TL_messageEntityMention && !usernames) { + continue; + } + newRun.flags = TextStyleSpan.FLAG_STYLE_URL; + newRun.urlEntity = entity; + } + + for (int b = 0, N2 = runs.size(); b < N2; b++) { + TextStyleSpan.TextStyleRun run = runs.get(b); + + if (newRun.start > run.start) { + if (newRun.start >= run.end) { continue; } - hasUrls = true; - if (!url.toLowerCase().startsWith("http") && !url.toLowerCase().startsWith("tg://")) { - spannable.setSpan(new URLSpanBrowser("http://" + url), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + if (newRun.end < run.end) { + TextStyleSpan.TextStyleRun r = new TextStyleSpan.TextStyleRun(newRun); + r.merge(run); + b++; + N2++; + runs.add(b, r); + + r = new TextStyleSpan.TextStyleRun(run); + r.start = newRun.end; + b++; + N2++; + runs.add(b, r); + } else if (newRun.end >= run.end) { + TextStyleSpan.TextStyleRun r = new TextStyleSpan.TextStyleRun(newRun); + r.merge(run); + r.end = run.end; + b++; + N2++; + runs.add(b, r); + } + + int temp = newRun.start; + newRun.start = run.end; + run.end = temp; + } else { + if (run.start >= newRun.end) { + continue; + } + int temp = run.start; + if (newRun.end == run.end) { + run.merge(newRun); + } else if (newRun.end < run.end) { + TextStyleSpan.TextStyleRun r = new TextStyleSpan.TextStyleRun(run); + r.merge(newRun); + r.end = newRun.end; + b++; + N2++; + runs.add(b, r); + + run.start = newRun.end; } else { - spannable.setSpan(new URLSpanBrowser(url), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + TextStyleSpan.TextStyleRun r = new TextStyleSpan.TextStyleRun(newRun); + r.start = run.end; + b++; + N2++; + runs.add(b, r); + + run.merge(newRun); } - } else if (entity instanceof TLRPC.TL_messageEntityPhone) { - hasUrls = true; - String tel = PhoneFormat.stripExceptNumbers(url); - if (url.startsWith("+")) { - tel = "+" + tel; - } - spannable.setSpan(new URLSpanBrowser("tel:" + tel), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else if (entity instanceof TLRPC.TL_messageEntityTextUrl) { - if (Browser.isPassportUrl(entity.url)) { - continue; - } - spannable.setSpan(new URLSpanReplacement(entity.url), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + newRun.end = temp; } } + if (newRun.start < newRun.end) { + runs.add(newRun); + } + } + + int count = runs.size(); + for (int a = 0; a < count; a++) { + TextStyleSpan.TextStyleRun run = runs.get(a); + + String url = run.urlEntity != null ? TextUtils.substring(text, run.urlEntity.offset, run.urlEntity.offset + run.urlEntity.length) : null; + if (run.urlEntity instanceof TLRPC.TL_messageEntityBotCommand) { + spannable.setSpan(new URLSpanBotCommand(url, t, run), run.start, run.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (run.urlEntity instanceof TLRPC.TL_messageEntityHashtag || run.urlEntity instanceof TLRPC.TL_messageEntityMention || run.urlEntity instanceof TLRPC.TL_messageEntityCashtag) { + spannable.setSpan(new URLSpanNoUnderline(url, run), run.start, run.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (run.urlEntity instanceof TLRPC.TL_messageEntityEmail) { + spannable.setSpan(new URLSpanReplacement("mailto:" + url, run), run.start, run.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (run.urlEntity instanceof TLRPC.TL_messageEntityUrl) { + hasUrls = true; + if (!url.toLowerCase().startsWith("http") && !url.toLowerCase().startsWith("tg://")) { + spannable.setSpan(new URLSpanBrowser("http://" + url, run), run.start, run.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + spannable.setSpan(new URLSpanBrowser(url, run), run.start, run.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } else if (run.urlEntity instanceof TLRPC.TL_messageEntityPhone) { + hasUrls = true; + String tel = PhoneFormat.stripExceptNumbers(url); + if (url.startsWith("+")) { + tel = "+" + tel; + } + spannable.setSpan(new URLSpanBrowser("tel:" + tel, run), run.start, run.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (run.urlEntity instanceof TLRPC.TL_messageEntityTextUrl) { + spannable.setSpan(new URLSpanReplacement(run.urlEntity.url, run), run.start, run.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (run.urlEntity instanceof TLRPC.TL_messageEntityMentionName) { + spannable.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_messageEntityMentionName) run.urlEntity).user_id, t, run), run.start, run.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (run.urlEntity instanceof TLRPC.TL_inputMessageEntityMentionName) { + spannable.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_inputMessageEntityMentionName) run.urlEntity).user_id.user_id, t, run), run.start, run.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if ((run.flags & TextStyleSpan.FLAG_STYLE_MONO) != 0) { + spannable.setSpan(new URLSpanMono(spannable, run.start, run.end, t, run), run.start, run.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + spannable.setSpan(new TextStyleSpan(run), run.start, run.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } } return hasUrls; } @@ -3209,12 +3357,12 @@ public class MessageObject { boolean hasEntities; if (messageOwner.send_state != MESSAGE_SEND_STATE_SENT) { hasEntities = false; - for (int a = 0; a < messageOwner.entities.size(); a++) { + /*for (int a = 0; a < messageOwner.entities.size(); a++) { if (!(messageOwner.entities.get(a) instanceof TLRPC.TL_inputMessageEntityMentionName)) { hasEntities = true; break; } - } + }*/ } else { hasEntities = !messageOwner.entities.isEmpty(); } @@ -3768,7 +3916,11 @@ public class MessageObject { } public static boolean isAnimatedStickerDocument(TLRPC.Document document) { - return SharedConfig.showAnimatedStickers && document != null && "application/x-tgsticker".equals(document.mime_type) && !document.thumbs.isEmpty(); + return document != null && "application/x-tgsticker".equals(document.mime_type) && !document.thumbs.isEmpty(); + } + + public static boolean canAutoplayAnimatedSticker(TLRPC.Document document) { + return isAnimatedStickerDocument(document) && SharedConfig.getDevicePerfomanceClass() != SharedConfig.PERFORMANCE_CLASS_LOW; } public static boolean isMaskDocument(TLRPC.Document document) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java index 938e01417..e8965a9d6 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java @@ -12,6 +12,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.location.Location; import android.os.Build; import android.os.Bundle; import android.os.SystemClock; @@ -50,7 +51,7 @@ import java.util.concurrent.CountDownLatch; import androidx.core.app.NotificationManagerCompat; -public class MessagesController implements NotificationCenter.NotificationCenterDelegate { +public class MessagesController extends BaseController implements NotificationCenter.NotificationCenterDelegate { private ConcurrentHashMap chats = new ConcurrentHashMap<>(100, 1.0f, 2); private ConcurrentHashMap encryptedChats = new ConcurrentHashMap<>(10, 1.0f, 2); @@ -176,6 +177,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter private int nextProxyInfoCheckTime; private boolean checkingProxyInfo; private int checkingProxyInfoRequestId; + private int lastCheckProxyId; private TLRPC.Dialog proxyDialog; private boolean isLeftProxyChannel; private long proxyDialogId; @@ -191,12 +193,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter private Runnable themeCheckRunnable = Theme::checkAutoNightThemeConditions; private volatile static long lastPasswordCheckTime; - private Runnable passwordCheckRunnable = new Runnable() { - @Override - public void run() { - UserConfig.getInstance(currentAccount).checkSavedPassword(); - } - }; + private Runnable passwordCheckRunnable = () -> getUserConfig().checkSavedPassword(); private long lastStatusUpdateTime; private int statusRequest; @@ -289,37 +286,34 @@ public class MessagesController implements NotificationCenter.NotificationCenter public TLRPC.SendMessageAction action; } - private final Comparator dialogComparator = new Comparator() { - @Override - public int compare(TLRPC.Dialog dialog1, TLRPC.Dialog dialog2) { - if (dialog1 instanceof TLRPC.TL_dialogFolder && !(dialog2 instanceof TLRPC.TL_dialogFolder)) { - return -1; - } else if (!(dialog1 instanceof TLRPC.TL_dialogFolder) && dialog2 instanceof TLRPC.TL_dialogFolder) { + private final Comparator dialogComparator = (dialog1, dialog2) -> { + if (dialog1 instanceof TLRPC.TL_dialogFolder && !(dialog2 instanceof TLRPC.TL_dialogFolder)) { + return -1; + } else if (!(dialog1 instanceof TLRPC.TL_dialogFolder) && dialog2 instanceof TLRPC.TL_dialogFolder) { + return 1; + } else if (!dialog1.pinned && dialog2.pinned) { + return 1; + } else if (dialog1.pinned && !dialog2.pinned) { + return -1; + } else if (dialog1.pinned && dialog2.pinned) { + if (dialog1.pinnedNum < dialog2.pinnedNum) { return 1; - } else if (!dialog1.pinned && dialog2.pinned) { - return 1; - } else if (dialog1.pinned && !dialog2.pinned) { + } else if (dialog1.pinnedNum > dialog2.pinnedNum) { return -1; - } else if (dialog1.pinned && dialog2.pinned) { - if (dialog1.pinnedNum < dialog2.pinnedNum) { - return 1; - } else if (dialog1.pinnedNum > dialog2.pinnedNum) { - return -1; - } else { - return 0; - } + } else { + return 0; } - TLRPC.DraftMessage draftMessage = DataQuery.getInstance(currentAccount).getDraft(dialog1.id); - int date1 = draftMessage != null && draftMessage.date >= dialog1.last_message_date ? draftMessage.date : dialog1.last_message_date; - draftMessage = DataQuery.getInstance(currentAccount).getDraft(dialog2.id); - int date2 = draftMessage != null && draftMessage.date >= dialog2.last_message_date ? draftMessage.date : dialog2.last_message_date; - if (date1 < date2) { - return 1; - } else if (date1 > date2) { - return -1; - } - return 0; } + TLRPC.DraftMessage draftMessage = getMediaDataController().getDraft(dialog1.id); + int date1 = draftMessage != null && draftMessage.date >= dialog1.last_message_date ? draftMessage.date : dialog1.last_message_date; + draftMessage = getMediaDataController().getDraft(dialog2.id); + int date2 = draftMessage != null && draftMessage.date >= dialog2.last_message_date ? draftMessage.date : dialog2.last_message_date; + if (date1 < date2) { + return 1; + } else if (date1 > date2) { + return -1; + } + return 0; }; private final Comparator updatesComparator = (lhs, rhs) -> { @@ -343,7 +337,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter return 0; }; - private int currentAccount; private static volatile MessagesController[] Instance = new MessagesController[UserConfig.MAX_ACCOUNT_COUNT]; public static MessagesController getInstance(int num) { MessagesController localInstance = Instance[num]; @@ -383,18 +376,19 @@ public class MessagesController implements NotificationCenter.NotificationCenter } public MessagesController(int num) { + super(num); currentAccount = num; ImageLoader.getInstance(); - MessagesStorage.getInstance(currentAccount); - LocationController.getInstance(currentAccount); + getMessagesStorage(); + getLocationController(); AndroidUtilities.runOnUIThread(() -> { - MessagesController messagesController = getInstance(currentAccount); - NotificationCenter.getInstance(currentAccount).addObserver(messagesController, NotificationCenter.FileDidUpload); - NotificationCenter.getInstance(currentAccount).addObserver(messagesController, NotificationCenter.FileDidFailUpload); - NotificationCenter.getInstance(currentAccount).addObserver(messagesController, NotificationCenter.fileDidLoad); - NotificationCenter.getInstance(currentAccount).addObserver(messagesController, NotificationCenter.fileDidFailedLoad); - NotificationCenter.getInstance(currentAccount).addObserver(messagesController, NotificationCenter.messageReceivedByServer); - NotificationCenter.getInstance(currentAccount).addObserver(messagesController, NotificationCenter.updateMessageMedia); + MessagesController messagesController = getMessagesController(); + getNotificationCenter().addObserver(messagesController, NotificationCenter.FileDidUpload); + getNotificationCenter().addObserver(messagesController, NotificationCenter.FileDidFailUpload); + getNotificationCenter().addObserver(messagesController, NotificationCenter.fileDidLoad); + getNotificationCenter().addObserver(messagesController, NotificationCenter.fileDidFailedLoad); + getNotificationCenter().addObserver(messagesController, NotificationCenter.messageReceivedByServer); + getNotificationCenter().addObserver(messagesController, NotificationCenter.updateMessageMedia); }); addSupportUser(); if (currentAccount == 0) { @@ -448,7 +442,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter public void updateConfig(final TLRPC.TL_config config) { AndroidUtilities.runOnUIThread(() -> { - DownloadController.getInstance(currentAccount).loadAutoDownloadConfig(false); + getDownloadController().loadAutoDownloadConfig(false); maxMegagroupCount = config.megagroup_size_max; maxGroupCount = config.chat_size_max; maxEditTime = config.edit_time_limit; @@ -575,7 +569,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter editor.commit(); LocaleController.getInstance().checkUpdateForCurrentRemoteLocale(currentAccount, config.lang_pack_version, config.base_lang_pack_version); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.configLoaded); + getNotificationCenter().postNotificationName(NotificationCenter.configLoaded); }); } @@ -605,7 +599,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter return new TLRPC.TL_inputUserEmpty(); } TLRPC.InputUser inputUser; - if (user.id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (user.id == getUserConfig().getClientUserId()) { inputUser = new TLRPC.TL_inputUserSelf(); } else { inputUser = new TLRPC.TL_inputUser(); @@ -686,14 +680,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (uploadingAvatar != null && uploadingAvatar.equals(location)) { TLRPC.TL_photos_uploadProfilePhoto req = new TLRPC.TL_photos_uploadProfilePhoto(); req.file = file; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { - TLRPC.User user = getUser(UserConfig.getInstance(currentAccount).getClientUserId()); + TLRPC.User user = getUser(getUserConfig().getClientUserId()); if (user == null) { - user = UserConfig.getInstance(currentAccount).getCurrentUser(); + user = getUserConfig().getCurrentUser(); putUser(user, true); } else { - UserConfig.getInstance(currentAccount).setCurrentUser(user); + getUserConfig().setCurrentUser(user); } if (user == null) { return; @@ -712,14 +706,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else if (smallSize != null) { user.photo.photo_small = smallSize.location; } - MessagesStorage.getInstance(currentAccount).clearUserPhotos(user.id); + getMessagesStorage().clearUserPhotos(user.id); ArrayList users = new ArrayList<>(); users.add(user); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(users, null, false, true); + getMessagesStorage().putUsersAndChats(users, null, false, true); AndroidUtilities.runOnUIThread(() -> { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.mainUserInfoChanged); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_AVATAR); - UserConfig.getInstance(currentAccount).saveConfig(true); + getNotificationCenter().postNotificationName(NotificationCenter.mainUserInfoChanged); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_AVATAR); + getUserConfig().saveConfig(true); }); } }); @@ -731,7 +725,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter settings.blur = uploadingWallpaperBlurred; settings.motion = uploadingWallpaperMotion; req.settings = settings; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { TLRPC.TL_wallPaper wallPaper = (TLRPC.TL_wallPaper) response; File path = new File(ApplicationLoader.getFilesDirFixed(), uploadingWallpaperBlurred ? "wallpaper_original.jpg" : "wallpaper.jpg"); if (wallPaper != null) { @@ -751,7 +745,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter editor.commit(); ArrayList wallpapers = new ArrayList<>(); wallpapers.add(wallPaper); - MessagesStorage.getInstance(currentAccount).putWallpapers(wallpapers, 2); + getMessagesStorage().putWallpapers(wallpapers, 2); TLRPC.PhotoSize image = FileLoader.getClosestPhotoSizeWithSize(wallPaper.document.thumbs, 320); if (image != null) { String newKey = image.location.volume_id + "_" + image.location.local_id + "@100_100"; @@ -782,7 +776,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.Dialog dialog = dialogs_dict.get(did); if (dialog != null && dialog.top_message == msgId) { dialog.top_message = newMsgId; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } obj = dialogMessagesByIds.get(msgId); dialogMessagesByIds.remove(msgId); @@ -796,20 +790,20 @@ public class MessagesController implements NotificationCenter.NotificationCenter existMessageObject.messageOwner.media = message.media; if (message.media.ttl_seconds != 0 && (message.media.photo instanceof TLRPC.TL_photoEmpty || message.media.document instanceof TLRPC.TL_documentEmpty)) { existMessageObject.setType(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.notificationsSettingsUpdated); + getNotificationCenter().postNotificationName(NotificationCenter.notificationsSettingsUpdated); } } } } public void cleanup() { - ContactsController.getInstance(currentAccount).cleanup(); + getContactsController().cleanup(); MediaController.getInstance().cleanup(); - NotificationsController.getInstance(currentAccount).cleanup(); - SendMessagesHelper.getInstance(currentAccount).cleanup(); - SecretChatHelper.getInstance(currentAccount).cleanup(); - LocationController.getInstance(currentAccount).cleanup(); - DataQuery.getInstance(currentAccount).cleanup(); + getNotificationsController().cleanup(); + getSendMessagesHelper().cleanup(); + getSecretChatHelper().cleanup(); + getLocationController().cleanup(); + getMediaDataController().cleanup(); DialogsActivity.dialogsLoaded[currentAccount] = false; @@ -919,7 +913,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter statusSettingState = 0; Utilities.stageQueue.postRunnable(() -> { - ConnectionsManager.getInstance(currentAccount).setIsUpdating(false); + getConnectionsManager().setIsUpdating(false); updatesQueueChannels.clear(); updatesStartWaitTimeChannels.clear(); gettingDifferenceChannels.clear(); @@ -936,7 +930,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } addSupportUser(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } public TLRPC.User getUser(Integer id) { @@ -954,6 +948,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter return users; } + public ConcurrentHashMap getChats() { + return chats; + } + public TLRPC.Chat getChat(Integer id) { return chats.get(id); } @@ -967,7 +965,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (chat == null || created && (chat instanceof TLRPC.TL_encryptedChatWaiting || chat instanceof TLRPC.TL_encryptedChatRequested)) { CountDownLatch countDownLatch = new CountDownLatch(1); ArrayList result = new ArrayList<>(); - MessagesStorage.getInstance(currentAccount).getEncryptedChat(chat_id, countDownLatch, result); + getMessagesStorage().getEncryptedChat(chat_id, countDownLatch, result); try { countDownLatch.await(); } catch (Exception e) { @@ -1075,9 +1073,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else { if (!fromCache) { users.put(user.id, user); - if (user.id == UserConfig.getInstance(currentAccount).getClientUserId()) { - UserConfig.getInstance(currentAccount).setCurrentUser(user); - UserConfig.getInstance(currentAccount).saveConfig(true); + if (user.id == getUserConfig().getClientUserId()) { + getUserConfig().setCurrentUser(user); + getUserConfig().saveConfig(true); } if (oldUser != null && user.status != null && oldUser.status != null && user.status.expires != oldUser.status.expires) { return true; @@ -1121,7 +1119,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } if (updateStatus) { - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_STATUS)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_STATUS)); } } @@ -1207,7 +1205,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter oldChat.flags |= 16384; } if (oldFlags != newFlags || oldFlags2 != newFlags2) { - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.channelRightsUpdated, chat)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.channelRightsUpdated, chat)); } } chats.put(chat.id, chat); @@ -1338,7 +1336,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (req.peers.isEmpty()) { return; } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { TLRPC.TL_messages_peerDialogs res = (TLRPC.TL_messages_peerDialogs) response; ArrayList arrayList = new ArrayList<>(); @@ -1391,7 +1389,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } if (!arrayList.isEmpty()) { - processUpdateArray(arrayList, null, null, false); + processUpdateArray(arrayList, null, null, false, 0); } } }); @@ -1408,7 +1406,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } loadingChannelAdmins.put(chatId, 0); if (cache) { - MessagesStorage.getInstance(currentAccount).loadChannelAdmins(chatId); + getMessagesStorage().loadChannelAdmins(chatId); } else { TLRPC.TL_channels_getParticipants req = new TLRPC.TL_channels_getParticipants(); ArrayList array = channelAdmins.get(chatId); @@ -1422,7 +1420,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.channel = getInputChannel(chatId); req.limit = 100; req.filter = new TLRPC.TL_channelParticipantsAdmins(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response instanceof TLRPC.TL_channels_channelParticipants) { TLRPC.TL_channels_channelParticipants participants = (TLRPC.TL_channels_channelParticipants) response; final ArrayList array1 = new ArrayList<>(participants.participants.size()); @@ -1438,7 +1436,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter public void processLoadedChannelAdmins(final ArrayList array, final int chatId, final boolean cache) { Collections.sort(array); if (!cache) { - MessagesStorage.getInstance(currentAccount).putChannelAdmins(chatId, array); + getMessagesStorage().putChannelAdmins(chatId, array); } AndroidUtilities.runOnUIThread(() -> { loadingChannelAdmins.delete(chatId); @@ -1473,16 +1471,16 @@ public class MessagesController implements NotificationCenter.NotificationCenter reloadDialogsReadValue(null, dialog_id); } } - int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(request, (response, error) -> { + int reqId = getConnectionsManager().sendRequest(request, (response, error) -> { if (error == null) { final TLRPC.TL_messages_chatFull res = (TLRPC.TL_messages_chatFull) response; - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, res.chats, true, true); - MessagesStorage.getInstance(currentAccount).updateChatInfo(res.full_chat, false); + getMessagesStorage().putUsersAndChats(res.users, res.chats, true, true); + getMessagesStorage().updateChatInfo(res.full_chat, false); if (ChatObject.isChannel(chat)) { Integer value = dialogs_read_inbox_max.get(dialog_id); if (value == null) { - value = MessagesStorage.getInstance(currentAccount).getDialogReadMax(false, dialog_id); + value = getMessagesStorage().getDialogReadMax(false, dialog_id); } dialogs_read_inbox_max.put(dialog_id, Math.max(res.full_chat.read_inbox_max_id, value)); @@ -1492,12 +1490,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter update.channel_id = chat_id; update.max_id = res.full_chat.read_inbox_max_id; arrayList.add(update); - processUpdateArray(arrayList, null, null, false); + processUpdateArray(arrayList, null, null, false, 0); } value = dialogs_read_outbox_max.get(dialog_id); if (value == null) { - value = MessagesStorage.getInstance(currentAccount).getDialogReadMax(true, dialog_id); + value = getMessagesStorage().getDialogReadMax(true, dialog_id); } dialogs_read_outbox_max.put(dialog_id, Math.max(res.full_chat.read_outbox_max_id, value)); if (value == 0) { @@ -1506,7 +1504,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter update.channel_id = chat_id; update.max_id = res.full_chat.read_outbox_max_id; arrayList.add(update); - processUpdateArray(arrayList, null, null, false); + processUpdateArray(arrayList, null, null, false, 0); } } @@ -1515,7 +1513,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter applyDialogNotificationsSettings(-chat_id, 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); - DataQuery.getInstance(currentAccount).putBotInfo(botInfo); + getMediaDataController().putBotInfo(botInfo); } exportedChats.put(chat_id, res.full_chat.exported_invite); loadingFullChats.remove((Integer) chat_id); @@ -1524,9 +1522,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter putUsers(res.users, false); putChats(res.chats, false); if (res.full_chat.stickerset != null) { - DataQuery.getInstance(currentAccount).getGroupStickerSetById(res.full_chat.stickerset); + getMediaDataController().getGroupStickerSetById(res.full_chat.stickerset); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatInfoDidLoad, res.full_chat, classGuid, false, null); + getNotificationCenter().postNotificationName(NotificationCenter.chatInfoDidLoad, res.full_chat, classGuid, false, null); }); } else { AndroidUtilities.runOnUIThread(() -> { @@ -1536,7 +1534,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } }); if (classGuid != 0) { - ConnectionsManager.getInstance(currentAccount).bindRequestToGuid(reqId, classGuid); + getConnectionsManager().bindRequestToGuid(reqId, classGuid); } } @@ -1551,32 +1549,34 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (dialogs_read_inbox_max.get(dialog_id) == null || dialogs_read_outbox_max.get(dialog_id) == null) { reloadDialogsReadValue(null, dialog_id); } - int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + int reqId = getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.UserFull userFull = (TLRPC.UserFull) response; - MessagesStorage.getInstance(currentAccount).updateUserInfo(userFull, false); + getMessagesStorage().updateUserInfo(userFull, false); AndroidUtilities.runOnUIThread(() -> { + savePeerSettings(userFull.user.id, userFull.settings, false); + applyDialogNotificationsSettings(user.id, userFull.notify_settings); if (userFull.bot_info instanceof TLRPC.TL_botInfo) { - DataQuery.getInstance(currentAccount).putBotInfo(userFull.bot_info); + getMediaDataController().putBotInfo(userFull.bot_info); } int index = blockedUsers.indexOfKey(user.id); if (userFull.blocked) { if (index < 0) { SparseIntArray ids = new SparseIntArray(); ids.put(user.id, 1); - MessagesStorage.getInstance(currentAccount).putBlockedUsers(ids, false); + getMessagesStorage().putBlockedUsers(ids, false); blockedUsers.put(user.id, 1); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.blockedUsersDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.blockedUsersDidLoad); } } else { if (index >= 0) { - MessagesStorage.getInstance(currentAccount).deleteBlockedUser(user.id); + getMessagesStorage().deleteBlockedUser(user.id); blockedUsers.removeAt(index); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.blockedUsersDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.blockedUsersDidLoad); } } fullUsers.put(user.id, userFull); @@ -1586,20 +1586,20 @@ public class MessagesController implements NotificationCenter.NotificationCenter ArrayList users = new ArrayList<>(); users.add(userFull.user); putUsers(users, false); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(users, null, false, true); + getMessagesStorage().putUsersAndChats(users, null, false, true); if (names != null && !names.equals(userFull.user.first_name + userFull.user.last_name + userFull.user.username)) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_NAME); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_NAME); } if (userFull.bot_info instanceof TLRPC.TL_botInfo) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.botInfoDidLoad, userFull.bot_info, classGuid); + getNotificationCenter().postNotificationName(NotificationCenter.botInfoDidLoad, userFull.bot_info, classGuid); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.userInfoDidLoad, user.id, userFull, null); + getNotificationCenter().postNotificationName(NotificationCenter.userInfoDidLoad, user.id, userFull, null); }); } else { AndroidUtilities.runOnUIThread(() -> loadingFullUsers.remove((Integer) user.id)); } }); - ConnectionsManager.getInstance(currentAccount).bindRequestToGuid(reqId, classGuid); + getConnectionsManager().bindRequestToGuid(reqId, classGuid); } private void reloadMessages(final ArrayList mids, final long dialog_id) { @@ -1635,7 +1635,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter reloadingMessages.put(dialog_id, arrayList); } arrayList.addAll(result); - ConnectionsManager.getInstance(currentAccount).sendRequest(request, (response, error) -> { + getConnectionsManager().sendRequest(request, (response, error) -> { if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; @@ -1652,13 +1652,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter Integer inboxValue = dialogs_read_inbox_max.get(dialog_id); if (inboxValue == null) { - inboxValue = MessagesStorage.getInstance(currentAccount).getDialogReadMax(false, dialog_id); + inboxValue = getMessagesStorage().getDialogReadMax(false, dialog_id); dialogs_read_inbox_max.put(dialog_id, inboxValue); } Integer outboxValue = dialogs_read_outbox_max.get(dialog_id); if (outboxValue == null) { - outboxValue = MessagesStorage.getInstance(currentAccount).getDialogReadMax(true, dialog_id); + outboxValue = getMessagesStorage().getDialogReadMax(true, dialog_id); dialogs_read_outbox_max.put(dialog_id, outboxValue); } @@ -1674,7 +1674,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } ImageLoader.saveMessagesThumbs(messagesRes.messages); - MessagesStorage.getInstance(currentAccount).putMessages(messagesRes, dialog_id, -1, 0, false); + getMessagesStorage().putMessages(messagesRes, dialog_id, -1, 0, false); AndroidUtilities.runOnUIThread(() -> { ArrayList arrayList1 = reloadingMessages.get(dialog_id); @@ -1697,43 +1697,43 @@ public class MessagesController implements NotificationCenter.NotificationCenter dialogMessagesByIds.put(obj2.getId(), obj2); } } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); break; } } } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.replaceMessagesObjects, dialog_id, objects); + getNotificationCenter().postNotificationName(NotificationCenter.replaceMessagesObjects, dialog_id, objects); }); } }); } - public void hideReportSpam(final long dialogId, TLRPC.User currentUser, TLRPC.Chat currentChat) { + public void hidePeerSettingsBar(final long dialogId, TLRPC.User currentUser, TLRPC.Chat currentChat) { if (currentUser == null && currentChat == null) { return; } SharedPreferences.Editor editor = notificationsPreferences.edit(); - editor.putInt("spam3_" + dialogId, 1); + editor.putInt("dialog_bar_vis3" + dialogId, 3); editor.commit(); if ((int) dialogId != 0) { - TLRPC.TL_messages_hideReportSpam req = new TLRPC.TL_messages_hideReportSpam(); + TLRPC.TL_messages_hidePeerSettingsBar req = new TLRPC.TL_messages_hidePeerSettingsBar(); if (currentUser != null) { req.peer = getInputPeer(currentUser.id); } else if (currentChat != null) { req.peer = getInputPeer(-currentChat.id); } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); } } - public void reportSpam(final long dialogId, TLRPC.User currentUser, TLRPC.Chat currentChat, TLRPC.EncryptedChat currentEncryptedChat) { + public void reportSpam(final long dialogId, TLRPC.User currentUser, TLRPC.Chat currentChat, TLRPC.EncryptedChat currentEncryptedChat, boolean geo) { if (currentUser == null && currentChat == null && currentEncryptedChat == null) { return; } SharedPreferences.Editor editor = notificationsPreferences.edit(); - editor.putInt("spam3_" + dialogId, 1); + editor.putInt("dialog_bar_vis3" + dialogId, 3); editor.commit(); if ((int) dialogId == 0) { if (currentEncryptedChat == null || currentEncryptedChat.access_hash == 0) { @@ -1743,22 +1743,47 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.peer = new TLRPC.TL_inputEncryptedChat(); req.peer.chat_id = currentEncryptedChat.id; req.peer.access_hash = currentEncryptedChat.access_hash; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }, ConnectionsManager.RequestFlagFailOnServerErrors); } else { - TLRPC.TL_messages_reportSpam req = new TLRPC.TL_messages_reportSpam(); + TLRPC.TL_account_reportPeer req = new TLRPC.TL_account_reportPeer(); if (currentChat != null) { req.peer = getInputPeer(-currentChat.id); } else if (currentUser != null) { req.peer = getInputPeer(currentUser.id); } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + if (geo) { + req.reason = new TLRPC.TL_inputReportReasonGeoIrrelevant(); + } else { + req.reason = new TLRPC.TL_inputReportReasonSpam(); + } + getConnectionsManager().sendRequest(req, (response, error) -> { }, ConnectionsManager.RequestFlagFailOnServerErrors); } } + private void savePeerSettings(long dialogId, TLRPC.TL_peerSettings settings, boolean update) { + if (settings == null || notificationsPreferences.getInt("dialog_bar_vis3" + dialogId, 0) == 3) { + return; + } + SharedPreferences.Editor editor = notificationsPreferences.edit(); + boolean bar_hidden = !settings.report_spam && !settings.add_contact && !settings.block_contact && !settings.share_contact && !settings.report_geo; + if (BuildVars.LOGS_ENABLED) { + FileLog.d("peer settings loaded for " + dialogId + " add = " + settings.add_contact + " block = " + settings.block_contact + " spam = " + settings.report_spam + " share = " + settings.share_contact + " geo = " + settings.report_geo + " hide = " + bar_hidden); + } + editor.putInt("dialog_bar_vis3" + dialogId, bar_hidden ? 1 : 2); + editor.putBoolean("dialog_bar_share" + dialogId, settings.share_contact); + editor.putBoolean("dialog_bar_report" + dialogId, settings.report_spam); + editor.putBoolean("dialog_bar_add" + dialogId, settings.add_contact); + editor.putBoolean("dialog_bar_block" + dialogId, settings.block_contact); + editor.putBoolean("dialog_bar_exception" + dialogId, settings.need_contacts_exception); + editor.putBoolean("dialog_bar_location" + dialogId, settings.report_geo); + editor.commit(); + getNotificationCenter().postNotificationName(NotificationCenter.peerSettingsDidLoad, dialogId); + } + public void loadPeerSettings(TLRPC.User currentUser, TLRPC.Chat currentChat) { if (currentUser == null && currentChat == null) { return; @@ -1776,54 +1801,23 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (BuildVars.LOGS_ENABLED) { FileLog.d("request spam button for " + dialogId); } - if (notificationsPreferences.getInt("spam3_" + dialogId, 0) == 1) { + int vis = notificationsPreferences.getInt("dialog_bar_vis3" + dialogId, 0); + if (vis == 1 || vis == 3) { if (BuildVars.LOGS_ENABLED) { - FileLog.d("spam button already hidden for " + dialogId); + FileLog.d("dialog bar already hidden for " + dialogId); } return; } - boolean hidden = notificationsPreferences.getBoolean("spam_" + dialogId, false); - if (hidden) { - TLRPC.TL_messages_hideReportSpam req = new TLRPC.TL_messages_hideReportSpam(); - if (currentUser != null) { - req.peer = getInputPeer(currentUser.id); - } else if (currentChat != null) { - req.peer = getInputPeer(-currentChat.id); - } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { - loadingPeerSettings.remove(dialogId); - SharedPreferences.Editor editor = notificationsPreferences.edit(); - editor.remove("spam_" + dialogId); - editor.putInt("spam3_" + dialogId, 1); - editor.commit(); - })); - return; - } TLRPC.TL_messages_getPeerSettings req = new TLRPC.TL_messages_getPeerSettings(); if (currentUser != null) { req.peer = getInputPeer(currentUser.id); } else if (currentChat != null) { req.peer = getInputPeer(-currentChat.id); } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { loadingPeerSettings.remove(dialogId); if (response != null) { - TLRPC.TL_peerSettings res = (TLRPC.TL_peerSettings) response; - SharedPreferences.Editor editor = notificationsPreferences.edit(); - if (!res.report_spam) { - if (BuildVars.LOGS_ENABLED) { - FileLog.d("don't show spam button for " + dialogId); - } - editor.putInt("spam3_" + dialogId, 1); - editor.commit(); - } else { - if (BuildVars.LOGS_ENABLED) { - FileLog.d("show spam button for " + dialogId); - } - editor.putInt("spam3_" + dialogId, 2); - editor.commit(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.peerSettingsDidLoad, dialogId); - } + savePeerSettings(dialogId, (TLRPC.TL_peerSettings) response, false); } })); } @@ -1834,7 +1828,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } int channelPts = channelsPts.get(channelId); if (channelPts == 0) { - channelPts = MessagesStorage.getInstance(currentAccount).getChannelPtsSync(channelId); + channelPts = getMessagesStorage().getChannelPtsSync(channelId); if (channelPts == 0) { channelPts = 1; } @@ -1845,7 +1839,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter FileLog.d("APPLY CHANNEL PTS"); } channelsPts.put(channelId, pts); - MessagesStorage.getInstance(currentAccount).saveChannelPts(channelId, pts); + getMessagesStorage().saveChannelPts(channelId, pts); } else if (channelPts != pts) { long updatesStartWaitTime = updatesStartWaitTimeChannels.get(channelId); boolean gettingDifferenceChannel = gettingDifferenceChannels.get(channelId); @@ -1877,13 +1871,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter FileLog.d("processNewDifferenceParams seq = " + seq + " pts = " + pts + " date = " + date + " pts_count = " + pts_count); } if (pts != -1) { - if (MessagesStorage.getInstance(currentAccount).getLastPtsValue() + pts_count == pts) { + if (getMessagesStorage().getLastPtsValue() + pts_count == pts) { if (BuildVars.LOGS_ENABLED) { FileLog.d("APPLY PTS"); } - MessagesStorage.getInstance(currentAccount).setLastPtsValue(pts); - MessagesStorage.getInstance(currentAccount).saveDiffParams(MessagesStorage.getInstance(currentAccount).getLastSeqValue(), MessagesStorage.getInstance(currentAccount).getLastPtsValue(), MessagesStorage.getInstance(currentAccount).getLastDateValue(), MessagesStorage.getInstance(currentAccount).getLastQtsValue()); - } else if (MessagesStorage.getInstance(currentAccount).getLastPtsValue() != pts) { + getMessagesStorage().setLastPtsValue(pts); + getMessagesStorage().saveDiffParams(getMessagesStorage().getLastSeqValue(), getMessagesStorage().getLastPtsValue(), getMessagesStorage().getLastDateValue(), getMessagesStorage().getLastQtsValue()); + } else if (getMessagesStorage().getLastPtsValue() != pts) { if (gettingDifference || updatesStartWaitTimePts == 0 || Math.abs(System.currentTimeMillis() - updatesStartWaitTimePts) <= 1500) { if (BuildVars.LOGS_ENABLED) { FileLog.d("ADD UPDATE TO QUEUE pts = " + pts + " pts_count = " + pts_count); @@ -1901,16 +1895,16 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } if (seq != -1) { - if (MessagesStorage.getInstance(currentAccount).getLastSeqValue() + 1 == seq) { + if (getMessagesStorage().getLastSeqValue() + 1 == seq) { if (BuildVars.LOGS_ENABLED) { FileLog.d("APPLY SEQ"); } - MessagesStorage.getInstance(currentAccount).setLastSeqValue(seq); + getMessagesStorage().setLastSeqValue(seq); if (date != -1) { - MessagesStorage.getInstance(currentAccount).setLastDateValue(date); + getMessagesStorage().setLastDateValue(date); } - MessagesStorage.getInstance(currentAccount).saveDiffParams(MessagesStorage.getInstance(currentAccount).getLastSeqValue(), MessagesStorage.getInstance(currentAccount).getLastPtsValue(), MessagesStorage.getInstance(currentAccount).getLastDateValue(), MessagesStorage.getInstance(currentAccount).getLastQtsValue()); - } else if (MessagesStorage.getInstance(currentAccount).getLastSeqValue() != seq) { + getMessagesStorage().saveDiffParams(getMessagesStorage().getLastSeqValue(), getMessagesStorage().getLastPtsValue(), getMessagesStorage().getLastDateValue(), getMessagesStorage().getLastQtsValue()); + } else if (getMessagesStorage().getLastSeqValue() != seq) { if (gettingDifference || updatesStartWaitTimeSeq == 0 || Math.abs(System.currentTimeMillis() - updatesStartWaitTimeSeq) <= 1500) { if (BuildVars.LOGS_ENABLED) { FileLog.d("ADD UPDATE TO QUEUE seq = " + seq); @@ -1934,18 +1928,18 @@ public class MessagesController implements NotificationCenter.NotificationCenter getNewDeleteTask(null, 0); } }); - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.didCreatedNewDeleteTask, mids)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.didCreatedNewDeleteTask, mids)); } public void getNewDeleteTask(final ArrayList oldTask, final int channelId) { Utilities.stageQueue.postRunnable(() -> { gettingNewDeleteTask = true; - MessagesStorage.getInstance(currentAccount).getNewTask(oldTask, channelId); + getMessagesStorage().getNewTask(oldTask, channelId); }); } private boolean checkDeletingTask(boolean runnable) { - int currentServerTime = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + int currentServerTime = getConnectionsManager().getCurrentTime(); if (currentDeletingTaskMids != null && (runnable || currentDeletingTaskTime != 0 && currentDeletingTaskTime <= currentServerTime)) { currentDeletingTaskTime = 0; @@ -1956,7 +1950,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter final ArrayList mids = new ArrayList<>(currentDeletingTaskMids); AndroidUtilities.runOnUIThread(() -> { if (!mids.isEmpty() && mids.get(0) > 0) { - MessagesStorage.getInstance(currentAccount).emptyMessagesMedia(mids); + getMessagesStorage().emptyMessagesMedia(mids); } else { deleteMessages(mids, null, null, 0, false); } @@ -1985,7 +1979,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (!checkDeletingTask(false)) { currentDeleteTaskRunnable = () -> checkDeletingTask(true); - int currentServerTime = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + int currentServerTime = getConnectionsManager().getCurrentTime(); Utilities.stageQueue.postRunnable(currentDeleteTaskRunnable, (long) Math.abs(currentServerTime - currentDeletingTaskTime) * 1000); } } else { @@ -1997,7 +1991,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter public void loadDialogPhotos(final int did, final int count, final long max_id, final boolean fromCache, final int classGuid) { if (fromCache) { - MessagesStorage.getInstance(currentAccount).getDialogPhotos(did, count, max_id, classGuid); + getMessagesStorage().getDialogPhotos(did, count, max_id, classGuid); } else { if (did > 0) { TLRPC.User user = getUser(did); @@ -2009,13 +2003,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.offset = 0; req.max_id = (int) max_id; req.user_id = getInputUser(user); - int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + int reqId = getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.photos_Photos res = (TLRPC.photos_Photos) response; processLoadedUserPhotos(res, did, count, max_id, false, classGuid); } }); - ConnectionsManager.getInstance(currentAccount).bindRequestToGuid(reqId, classGuid); + getConnectionsManager().bindRequestToGuid(reqId, classGuid); } else if (did < 0) { TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); req.filter = new TLRPC.TL_inputMessagesFilterChatPhotos(); @@ -2023,7 +2017,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.offset_id = (int) max_id; req.q = ""; req.peer = getInputPeer(did); - int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + int reqId = getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.messages_Messages messages = (TLRPC.messages_Messages) response; TLRPC.TL_photos_photos res = new TLRPC.TL_photos_photos(); @@ -2039,7 +2033,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter processLoadedUserPhotos(res, did, count, max_id, false, classGuid); } }); - ConnectionsManager.getInstance(currentAccount).bindRequestToGuid(reqId, classGuid); + getConnectionsManager().bindRequestToGuid(reqId, classGuid); } } } @@ -2051,18 +2045,18 @@ public class MessagesController implements NotificationCenter.NotificationCenter } blockedUsers.put(user_id, 1); if (user.bot) { - DataQuery.getInstance(currentAccount).removeInline(user_id); + getMediaDataController().removeInline(user_id); } else { - DataQuery.getInstance(currentAccount).removePeer(user_id); + getMediaDataController().removePeer(user_id); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.blockedUsersDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.blockedUsersDidLoad); TLRPC.TL_contacts_block req = new TLRPC.TL_contacts_block(); req.id = getInputUser(user); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { SparseIntArray ids = new SparseIntArray(); ids.put(user.id, 1); - MessagesStorage.getInstance(currentAccount).putBlockedUsers(ids, false); + getMessagesStorage().putBlockedUsers(ids, false); } }); } @@ -2075,7 +2069,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.channel = getInputChannel(chatId); req.user_id = getInputUser(user); req.banned_rights = rights; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { processUpdates((TLRPC.Updates) response, false); AndroidUtilities.runOnUIThread(() -> loadFullChat(chatId, 0, true), 1000); @@ -2092,7 +2086,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter final TLRPC.TL_messages_editChatDefaultBannedRights req = new TLRPC.TL_messages_editChatDefaultBannedRights(); req.peer = getInputPeer(-chatId); req.banned_rights = rights; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { processUpdates((TLRPC.Updates) response, false); AndroidUtilities.runOnUIThread(() -> loadFullChat(chatId, 0, true), 1000); @@ -2112,7 +2106,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.channel = getInputChannel(chat); req.user_id = getInputUser(user); req.admin_rights = rights; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { processUpdates((TLRPC.Updates) response, false); AndroidUtilities.runOnUIThread(() -> loadFullChat(chatId, 0, true), 1000); @@ -2133,9 +2127,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter } }; if (req.is_admin && addingNew) { - addUserToChat(chatId, user, null, 0, null, parentFragment, () -> ConnectionsManager.getInstance(currentAccount).sendRequest(req, requestDelegate)); + addUserToChat(chatId, user, null, 0, null, parentFragment, () -> getConnectionsManager().sendRequest(req, requestDelegate)); } else { - ConnectionsManager.getInstance(currentAccount).sendRequest(req, requestDelegate); + getConnectionsManager().sendRequest(req, requestDelegate); } } } @@ -2148,22 +2142,22 @@ public class MessagesController implements NotificationCenter.NotificationCenter } blockedUsers.delete(user.id); req.id = getInputUser(user); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.blockedUsersDidLoad); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> MessagesStorage.getInstance(currentAccount).deleteBlockedUser(user.id)); + getNotificationCenter().postNotificationName(NotificationCenter.blockedUsersDidLoad); + getConnectionsManager().sendRequest(req, (response, error) -> getMessagesStorage().deleteBlockedUser(user.id)); } public void getBlockedUsers(boolean cache) { - if (!UserConfig.getInstance(currentAccount).isClientActivated() || loadingBlockedUsers) { + if (!getUserConfig().isClientActivated() || loadingBlockedUsers) { return; } loadingBlockedUsers = true; if (cache) { - MessagesStorage.getInstance(currentAccount).getBlockedUsers(); + getMessagesStorage().getBlockedUsers(); } else { TLRPC.TL_contacts_getBlocked req = new TLRPC.TL_contacts_getBlocked(); req.offset = 0; req.limit = 200; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { SparseIntArray blocked = new SparseIntArray(); ArrayList users = null; if (error == null) { @@ -2172,8 +2166,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter blocked.put(contactBlocked.user_id, 1); } users = res.users; - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, null, true, true); - MessagesStorage.getInstance(currentAccount).putBlockedUsers(blocked, true); + getMessagesStorage().putUsersAndChats(res.users, null, true, true); + getMessagesStorage().putBlockedUsers(blocked, true); } processLoadedBlockedUsers(blocked, users, false); }); @@ -2186,15 +2180,15 @@ public class MessagesController implements NotificationCenter.NotificationCenter putUsers(users, cache); } loadingBlockedUsers = false; - if (ids.size() == 0 && cache && !UserConfig.getInstance(currentAccount).blockedUsersLoaded) { + if (ids.size() == 0 && cache && !getUserConfig().blockedUsersLoaded) { getBlockedUsers(false); return; } else if (!cache) { - UserConfig.getInstance(currentAccount).blockedUsersLoaded = true; - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().blockedUsersLoaded = true; + getUserConfig().saveConfig(false); } blockedUsers = ids; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.blockedUsersDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.blockedUsersDidLoad); }); } @@ -2202,45 +2196,45 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (photo == null) { TLRPC.TL_photos_updateProfilePhoto req = new TLRPC.TL_photos_updateProfilePhoto(); req.id = new TLRPC.TL_inputPhotoEmpty(); - UserConfig.getInstance(currentAccount).getCurrentUser().photo = new TLRPC.TL_userProfilePhotoEmpty(); - TLRPC.User user = getUser(UserConfig.getInstance(currentAccount).getClientUserId()); + getUserConfig().getCurrentUser().photo = new TLRPC.TL_userProfilePhotoEmpty(); + TLRPC.User user = getUser(getUserConfig().getClientUserId()); if (user == null) { - user = UserConfig.getInstance(currentAccount).getCurrentUser(); + user = getUserConfig().getCurrentUser(); } if (user == null) { return; } - user.photo = UserConfig.getInstance(currentAccount).getCurrentUser().photo; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.mainUserInfoChanged); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_ALL); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + user.photo = getUserConfig().getCurrentUser().photo; + getNotificationCenter().postNotificationName(NotificationCenter.mainUserInfoChanged); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_ALL); + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { - TLRPC.User user1 = getUser(UserConfig.getInstance(currentAccount).getClientUserId()); + TLRPC.User user1 = getUser(getUserConfig().getClientUserId()); if (user1 == null) { - user1 = UserConfig.getInstance(currentAccount).getCurrentUser(); + user1 = getUserConfig().getCurrentUser(); putUser(user1, false); } else { - UserConfig.getInstance(currentAccount).setCurrentUser(user1); + getUserConfig().setCurrentUser(user1); } if (user1 == null) { return; } - MessagesStorage.getInstance(currentAccount).clearUserPhotos(user1.id); + getMessagesStorage().clearUserPhotos(user1.id); ArrayList users = new ArrayList<>(); users.add(user1); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(users, null, false, true); + getMessagesStorage().putUsersAndChats(users, null, false, true); user1.photo = (TLRPC.UserProfilePhoto) response; AndroidUtilities.runOnUIThread(() -> { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.mainUserInfoChanged); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_ALL); - UserConfig.getInstance(currentAccount).saveConfig(true); + getNotificationCenter().postNotificationName(NotificationCenter.mainUserInfoChanged); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_ALL); + getUserConfig().saveConfig(true); }); } }); } else { TLRPC.TL_photos_deletePhotos req = new TLRPC.TL_photos_deletePhotos(); req.id.add(photo); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); } @@ -2248,15 +2242,15 @@ public class MessagesController implements NotificationCenter.NotificationCenter public void processLoadedUserPhotos(final TLRPC.photos_Photos res, final int did, final int count, final long max_id, final boolean fromCache, final int classGuid) { if (!fromCache) { - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, null, true, true); - MessagesStorage.getInstance(currentAccount).putDialogPhotos(did, res); + getMessagesStorage().putUsersAndChats(res.users, null, true, true); + getMessagesStorage().putDialogPhotos(did, res); } else if (res == null || res.photos.isEmpty()) { loadDialogPhotos(did, count, max_id, false, classGuid); return; } AndroidUtilities.runOnUIThread(() -> { putUsers(res.users, fromCache); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogPhotosLoaded, did, count, fromCache, classGuid, res.photos); + getNotificationCenter().postNotificationName(NotificationCenter.dialogPhotosLoaded, did, count, fromCache, classGuid, res.photos); }); } @@ -2265,7 +2259,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter return; } uploadingAvatar = FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + location.volume_id + "_" + location.local_id + ".jpg"; - FileLoader.getInstance(currentAccount).uploadFile(uploadingAvatar, false, true, ConnectionsManager.FileTypePhoto); + getFileLoader().uploadFile(uploadingAvatar, false, true, ConnectionsManager.FileTypePhoto); } public void saveWallpaperToServer(File path, long wallPaperId, long accessHash, boolean isBlurred, boolean isMotion, int backgroundColor, float intesity, boolean install, long taskId) { @@ -2276,14 +2270,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter uploadingWallpaperBlurred = isBlurred; return; } - FileLoader.getInstance(currentAccount).cancelUploadFile(uploadingWallpaper, false); + getFileLoader().cancelUploadFile(uploadingWallpaper, false); uploadingWallpaper = null; } if (path != null) { uploadingWallpaper = path.getAbsolutePath(); uploadingWallpaperMotion = isMotion; uploadingWallpaperBlurred = isBlurred; - FileLoader.getInstance(currentAccount).uploadFile(uploadingWallpaper, false, true, ConnectionsManager.FileTypePhoto); + getFileLoader().uploadFile(uploadingWallpaper, false, true, ConnectionsManager.FileTypePhoto); } else if (accessHash != 0) { TLRPC.TL_inputWallPaper inputWallPaper = new TLRPC.TL_inputWallPaper(); inputWallPaper.id = wallPaperId; @@ -2330,11 +2324,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter } catch (Exception e) { FileLog.e(e); } - newTaskId = MessagesStorage.getInstance(currentAccount).createPendingTask(data); + newTaskId = getMessagesStorage().createPendingTask(data); } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getConnectionsManager().sendRequest(req, (response, error) -> { + getMessagesStorage().removePendingTask(newTaskId); if (!install && uploadingWallpaper != null) { SharedPreferences preferences = getGlobalMainSettings(); SharedPreferences.Editor editor = preferences.edit(); @@ -2386,9 +2380,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter toSend.add(mid); } } - MessagesStorage.getInstance(currentAccount).markMessagesAsDeleted(messages, true, channelId); - MessagesStorage.getInstance(currentAccount).updateDialogsWithDeletedMessages(messages, null, true, channelId); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messagesDeleted, messages, channelId); + getMessagesStorage().markMessagesAsDeleted(messages, true, channelId); + getMessagesStorage().updateDialogsWithDeletedMessages(messages, null, true, channelId); + getNotificationCenter().postNotificationName(NotificationCenter.messagesDeleted, messages, channelId); } final long newTaskId; @@ -2411,21 +2405,21 @@ public class MessagesController implements NotificationCenter.NotificationCenter } catch (Exception e) { FileLog.e(e); } - newTaskId = MessagesStorage.getInstance(currentAccount).createPendingTask(data); + newTaskId = getMessagesStorage().createPendingTask(data); } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.TL_messages_affectedMessages res = (TLRPC.TL_messages_affectedMessages) response; processNewChannelDifferenceParams(res.pts, res.pts_count, channelId); } if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } }); } else { if (randoms != null && encryptedChat != null && !randoms.isEmpty()) { - SecretChatHelper.getInstance(currentAccount).sendMessagesDeleteMessage(encryptedChat, randoms, null); + getSecretChatHelper().sendMessagesDeleteMessage(encryptedChat, randoms, null); } TLRPC.TL_messages_deleteMessages req; if (taskRequest != null) { @@ -2445,16 +2439,16 @@ public class MessagesController implements NotificationCenter.NotificationCenter } catch (Exception e) { FileLog.e(e); } - newTaskId = MessagesStorage.getInstance(currentAccount).createPendingTask(data); + newTaskId = getMessagesStorage().createPendingTask(data); } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.TL_messages_affectedMessages res = (TLRPC.TL_messages_affectedMessages) response; processNewDifferenceParams(-1, res.pts, -1, res.pts_count); } if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } }); } @@ -2468,7 +2462,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.peer = getInputPeer(chat != null ? -chat.id : user.id); req.id = id; req.silent = !notify; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.Updates updates = (TLRPC.Updates) response; processUpdates(updates, false); @@ -2478,12 +2472,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter public void deleteUserChannelHistory(final TLRPC.Chat chat, final TLRPC.User user, int offset) { if (offset == 0) { - MessagesStorage.getInstance(currentAccount).deleteUserChannelHistory(chat.id, user.id); + getMessagesStorage().deleteUserChannelHistory(chat.id, user.id); } TLRPC.TL_channels_deleteUserHistory req = new TLRPC.TL_channels_deleteUserHistory(); req.channel = getInputChannel(chat); req.user_id = getInputUser(user); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.TL_messages_affectedHistory res = (TLRPC.TL_messages_affectedHistory) response; if (res.offset > 0) { @@ -2563,17 +2557,17 @@ public class MessagesController implements NotificationCenter.NotificationCenter public void setDialogsInTransaction(boolean transaction) { dialogsInTransaction = transaction; if (!transaction) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload, true); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload, true); } } protected void deleteDialog(final long did, final boolean first, final int onlyHistory, final int max_id, boolean revoke, TLRPC.InputPeer peer, final long taskId) { if (onlyHistory == 2) { - MessagesStorage.getInstance(currentAccount).deleteDialog(did, onlyHistory); + getMessagesStorage().deleteDialog(did, onlyHistory); return; } if (onlyHistory == 0 || onlyHistory == 3) { - DataQuery.getInstance(currentAccount).uninstallShortcut(did); + getMediaDataController().uninstallShortcut(did); } int lower_part = (int) did; int high_id = (int) (did >> 32); @@ -2581,10 +2575,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (first) { boolean isProxyDialog = false; - MessagesStorage.getInstance(currentAccount).deleteDialog(did, onlyHistory); + getMessagesStorage().deleteDialog(did, onlyHistory); TLRPC.Dialog dialog = dialogs_dict.get(did); if (onlyHistory == 0 || onlyHistory == 3) { - NotificationsController.getInstance(currentAccount).deleteNotificationChannel(did); + getNotificationsController().deleteNotificationChannel(did); } if (dialog != null) { if (max_id_delete == 0) { @@ -2630,8 +2624,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (onlyHistory == 1 && lower_part != 0 && lastMessageId > 0) { TLRPC.TL_messageService message = new TLRPC.TL_messageService(); message.id = dialog.top_message; - message.out = UserConfig.getInstance(currentAccount).getClientUserId() == did; - message.from_id = UserConfig.getInstance(currentAccount).getClientUserId(); + message.out = getUserConfig().getClientUserId() == did; + message.from_id = getUserConfig().getClientUserId(); message.flags |= 256; message.action = new TLRPC.TL_messageActionHistoryClear(); message.date = dialog.last_message_date; @@ -2655,7 +2649,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter ArrayList arr = new ArrayList<>(); arr.add(message); updateInterfaceWithMessages(did, objArr); - MessagesStorage.getInstance(currentAccount).putMessages(arr, false, true, false, 0); + getMessagesStorage().putMessages(arr, false, true, false, 0); } else { dialog.top_message = 0; } @@ -2663,13 +2657,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter } if (!dialogsInTransaction) { if (isProxyDialog) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload, true); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload, true); } else { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.removeAllMessagesFromDialog, did, false); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.removeAllMessagesFromDialog, did, false); } } - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> NotificationsController.getInstance(currentAccount).removeNotificationsForDialog(did))); + getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> getNotificationsController().removeNotificationsForDialog(did))); } if (high_id == 1 || onlyHistory == 3) { @@ -2704,7 +2698,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } catch (Exception e) { FileLog.e(e); } - newTaskId = MessagesStorage.getInstance(currentAccount).createPendingTask(data); + newTaskId = getMessagesStorage().createPendingTask(data); } else { newTaskId = taskId; } @@ -2715,7 +2709,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (peer instanceof TLRPC.TL_inputPeerChannel) { if (onlyHistory == 0) { if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } return; } @@ -2724,9 +2718,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.channel.channel_id = peer.channel_id; req.channel.access_hash = peer.access_hash; req.max_id = max_id_delete > 0 ? max_id_delete : Integer.MAX_VALUE; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } AndroidUtilities.runOnUIThread(() -> deletedHistory.remove(did)); }, ConnectionsManager.RequestFlagInvokeAfter); @@ -2738,9 +2732,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.revoke = revoke; final int max_id_delete_final = max_id_delete; final TLRPC.InputPeer peerFinal = peer; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } if (error == null) { TLRPC.TL_messages_affectedHistory res = (TLRPC.TL_messages_affectedHistory) response; @@ -2748,15 +2742,15 @@ public class MessagesController implements NotificationCenter.NotificationCenter deleteDialog(did, false, onlyHistory, max_id_delete_final, revoke, peerFinal, 0); } processNewDifferenceParams(-1, res.pts, -1, res.pts_count); - MessagesStorage.getInstance(currentAccount).onDeleteQueryComplete(did); + getMessagesStorage().onDeleteQueryComplete(did); } }, ConnectionsManager.RequestFlagInvokeAfter); } } else { if (onlyHistory == 1) { - SecretChatHelper.getInstance(currentAccount).sendClearHistoryMessage(getEncryptedChat(high_id), null); + getSecretChatHelper().sendClearHistoryMessage(getEncryptedChat(high_id), null); } else { - SecretChatHelper.getInstance(currentAccount).declineSecretChat(high_id); + getSecretChatHelper().declineSecretChat(high_id); } } } @@ -2774,9 +2768,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.id.file_reference = new byte[0]; } req.unsave = false; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null && FileRefController.isFileRefError(error.text) && parentObject != null) { - FileRefController.getInstance(currentAccount).requestReference(parentObject, req); + getFileRefController().requestReference(parentObject, req); } }); } @@ -2795,9 +2789,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter } req.unsave = false; req.attached = asMask; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null && FileRefController.isFileRefError(error.text) && parentObject != null) { - FileRefController.getInstance(currentAccount).requestReference(parentObject, req); + getFileRefController().requestReference(parentObject, req); } }); } @@ -2813,12 +2807,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.filter = new TLRPC.TL_channelParticipantsRecent(); req.offset = 0; req.limit = 32; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (error == null) { TLRPC.TL_channels_channelParticipants res = (TLRPC.TL_channels_channelParticipants) response; putUsers(res.users, false); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, null, true, true); - MessagesStorage.getInstance(currentAccount).updateChannelUsers(chat_id, res.participants); + getMessagesStorage().putUsersAndChats(res.users, null, true, true); + getMessagesStorage().updateChannelUsers(chat_id, res.participants); loadedFullParticipants.add(chat_id); } loadingFullParticipants.remove(chat_id); @@ -2826,7 +2820,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } public void loadChatInfo(final int chat_id, CountDownLatch countDownLatch, boolean force) { - MessagesStorage.getInstance(currentAccount).loadChatInfo(chat_id, countDownLatch, force, false); + getMessagesStorage().loadChatInfo(chat_id, countDownLatch, force, false); } public void processChatInfo(int chat_id, final TLRPC.ChatFull info, final ArrayList usersArr, final boolean fromCache, boolean force, final boolean byChannelUsers, final MessageObject pinnedMessageObject) { @@ -2837,15 +2831,15 @@ public class MessagesController implements NotificationCenter.NotificationCenter AndroidUtilities.runOnUIThread(() -> { putUsers(usersArr, fromCache); if (info.stickerset != null) { - DataQuery.getInstance(currentAccount).getGroupStickerSetById(info.stickerset); + getMediaDataController().getGroupStickerSetById(info.stickerset); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatInfoDidLoad, info, 0, byChannelUsers, pinnedMessageObject); + getNotificationCenter().postNotificationName(NotificationCenter.chatInfoDidLoad, info, 0, byChannelUsers, pinnedMessageObject); }); } } public void loadUserInfo(TLRPC.User user, boolean force, int classGuid) { - MessagesStorage.getInstance(currentAccount).loadUserInfo(user, force, classGuid); + getMessagesStorage().loadUserInfo(user, force, classGuid); } public void processUserInfo(TLRPC.User user, final TLRPC.UserFull info, final boolean fromCache, boolean force, final MessageObject pinnedMessageObject, int classGuid) { @@ -2856,7 +2850,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (fullUsers.get(user.id) == null) { fullUsers.put(user.id, info); } - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.userInfoDidLoad, user.id, info, pinnedMessageObject)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.userInfoDidLoad, user.id, info, pinnedMessageObject)); } } @@ -2866,19 +2860,19 @@ public class MessagesController implements NotificationCenter.NotificationCenter checkDeletingTask(false); checkReadTasks(); - if (UserConfig.getInstance(currentAccount).isClientActivated()) { - if (ConnectionsManager.getInstance(currentAccount).getPauseTime() == 0 && ApplicationLoader.isScreenOn && !ApplicationLoader.mainInterfacePausedStageQueue) { + if (getUserConfig().isClientActivated()) { + if (getConnectionsManager().getPauseTime() == 0 && ApplicationLoader.isScreenOn && !ApplicationLoader.mainInterfacePausedStageQueue) { if (ApplicationLoader.mainInterfacePausedStageQueueTime != 0 && Math.abs(ApplicationLoader.mainInterfacePausedStageQueueTime - System.currentTimeMillis()) > 1000) { if (statusSettingState != 1 && (lastStatusUpdateTime == 0 || Math.abs(System.currentTimeMillis() - lastStatusUpdateTime) >= 55000 || offlineSent)) { statusSettingState = 1; if (statusRequest != 0) { - ConnectionsManager.getInstance(currentAccount).cancelRequest(statusRequest, true); + getConnectionsManager().cancelRequest(statusRequest, true); } TLRPC.TL_account_updateStatus req = new TLRPC.TL_account_updateStatus(); req.offline = false; - statusRequest = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + statusRequest = getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { lastStatusUpdateTime = System.currentTimeMillis(); offlineSent = false; @@ -2892,14 +2886,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter }); } } - } else if (statusSettingState != 2 && !offlineSent && Math.abs(System.currentTimeMillis() - ConnectionsManager.getInstance(currentAccount).getPauseTime()) >= 2000) { + } else if (statusSettingState != 2 && !offlineSent && Math.abs(System.currentTimeMillis() - getConnectionsManager().getPauseTime()) >= 2000) { statusSettingState = 2; if (statusRequest != 0) { - ConnectionsManager.getInstance(currentAccount).cancelRequest(statusRequest, true); + getConnectionsManager().cancelRequest(statusRequest, true); } TLRPC.TL_account_updateStatus req = new TLRPC.TL_account_updateStatus(); req.offline = true; - statusRequest = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + statusRequest = getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { offlineSent = true; } else { @@ -2942,7 +2936,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.peer = getInputPeer(key); req.id = channelViewsToSend.valueAt(a); req.increment = a == 0; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { TLRPC.Vector vector = (TLRPC.Vector) response; final SparseArray channelViews = new SparseArray<>(); @@ -2957,8 +2951,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter } array.put(req.id.get(a1), (Integer) vector.objects.get(a1)); } - MessagesStorage.getInstance(currentAccount).putChannelViews(channelViews, req.peer instanceof TLRPC.TL_inputPeerChannel); - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.didUpdatedMessagesViews, channelViews)); + getMessagesStorage().putChannelViews(channelViews, req.peer instanceof TLRPC.TL_inputPeerChannel); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.didUpdatedMessagesViews, channelViews)); } }); } @@ -2985,7 +2979,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.TL_messages_getPollResults req = new TLRPC.TL_messages_getPollResults(); req.peer = getInputPeer((int) messageObject.getDialogId()); req.msg_id = messageObject.getId(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { processUpdates((TLRPC.Updates) response, false); } @@ -3004,7 +2998,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } if (!onlinePrivacy.isEmpty()) { ArrayList toRemove = null; - int currentServerTime = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + int currentServerTime = getConnectionsManager().getCurrentTime(); for (ConcurrentHashMap.Entry entry : onlinePrivacy.entrySet()) { if (entry.getValue() < currentServerTime - 30) { if (toRemove == null) { @@ -3017,7 +3011,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter for (Integer uid : toRemove) { onlinePrivacy.remove(uid); } - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_STATUS)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_STATUS)); } } if (shortPollChannels.size() != 0) { @@ -3047,11 +3041,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter } TLRPC.TL_messages_getOnlines req = new TLRPC.TL_messages_getOnlines(); req.peer = getInputPeer(-key); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { TLRPC.TL_chatOnlines res = (TLRPC.TL_chatOnlines) response; - MessagesStorage.getInstance(currentAccount).updateChatOnlineCount(key, res.onlines); - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatOnlineCountDidLoad, key, res.onlines)); + getMessagesStorage().updateChatOnlineCount(key, res.onlines); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.chatOnlineCountDidLoad, key, res.onlines)); } }); } @@ -3089,32 +3083,32 @@ public class MessagesController implements NotificationCenter.NotificationCenter updatePrintingStrings(); if (updated) { - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_USER_PRINT)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_USER_PRINT)); } } if (Theme.selectedAutoNightType == Theme.AUTO_NIGHT_TYPE_SCHEDULED && Math.abs(currentTime - lastThemeCheckTime) >= 60) { AndroidUtilities.runOnUIThread(themeCheckRunnable); lastThemeCheckTime = currentTime; } - if (UserConfig.getInstance(currentAccount).savedPasswordHash != null && Math.abs(currentTime - lastPasswordCheckTime) >= 60) { + if (getUserConfig().savedPasswordHash != null && Math.abs(currentTime - lastPasswordCheckTime) >= 60) { AndroidUtilities.runOnUIThread(passwordCheckRunnable); lastPasswordCheckTime = currentTime; } if (lastPushRegisterSendTime != 0 && Math.abs(SystemClock.elapsedRealtime() - lastPushRegisterSendTime) >= 3 * 60 * 60 * 1000) { GcmPushListenerService.sendRegistrationToServer(SharedConfig.pushString); } - LocationController.getInstance(currentAccount).update(); + getLocationController().update(); checkProxyInfoInternal(false); checkTosUpdate(); } private void checkTosUpdate() { - if (nextTosCheckTime > ConnectionsManager.getInstance(currentAccount).getCurrentTime() || checkingTosUpdate || !UserConfig.getInstance(currentAccount).isClientActivated()) { + if (nextTosCheckTime > getConnectionsManager().getCurrentTime() || checkingTosUpdate || !getUserConfig().isClientActivated()) { return; } checkingTosUpdate = true; TLRPC.TL_help_getTermsOfServiceUpdate req = new TLRPC.TL_help_getTermsOfServiceUpdate(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { checkingTosUpdate = false; if (response instanceof TLRPC.TL_help_termsOfServiceUpdateEmpty) { TLRPC.TL_help_termsOfServiceUpdateEmpty res = (TLRPC.TL_help_termsOfServiceUpdateEmpty) response; @@ -3122,9 +3116,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else if (response instanceof TLRPC.TL_help_termsOfServiceUpdate) { final TLRPC.TL_help_termsOfServiceUpdate res = (TLRPC.TL_help_termsOfServiceUpdate) response; nextTosCheckTime = res.expires; - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.needShowAlert, 4, res.terms_of_service)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.needShowAlert, 4, res.terms_of_service)); } else { - nextTosCheckTime = ConnectionsManager.getInstance(currentAccount).getCurrentTime() + 60 * 60; + nextTosCheckTime = getConnectionsManager().getCurrentTime() + 60 * 60; } notificationsPreferences.edit().putInt("nextTosCheckTime", nextTosCheckTime).commit(); }); @@ -3138,11 +3132,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (reset && checkingProxyInfo) { checkingProxyInfo = false; } - if (!reset && nextProxyInfoCheckTime > ConnectionsManager.getInstance(currentAccount).getCurrentTime() || checkingProxyInfo) { + if (!reset && nextProxyInfoCheckTime > getConnectionsManager().getCurrentTime() || checkingProxyInfo) { return; } if (checkingProxyInfoRequestId != 0) { - ConnectionsManager.getInstance(currentAccount).cancelRequest(checkingProxyInfoRequestId, true); + getConnectionsManager().cancelRequest(checkingProxyInfoRequestId, true); checkingProxyInfoRequestId = 0; } SharedPreferences preferences = getGlobalMainSettings(); @@ -3153,11 +3147,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (proxyDialogId != 0 && proxyDialogAddress != null && !proxyDialogAddress.equals(proxyAddress + proxySecret)) { removeCurrent = 1; } + lastCheckProxyId++; if (enabled && !TextUtils.isEmpty(proxyAddress) && !TextUtils.isEmpty(proxySecret)) { checkingProxyInfo = true; + int checkProxyId = lastCheckProxyId; TLRPC.TL_help_getProxyData req = new TLRPC.TL_help_getProxyData(); - checkingProxyInfoRequestId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { - if (checkingProxyInfoRequestId == 0) { + checkingProxyInfoRequestId = getConnectionsManager().sendRequest(req, (response, error) -> { + if (checkProxyId != lastCheckProxyId) { return; } boolean noDialog = false; @@ -3205,7 +3201,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (proxyDialog != null) { checkingProxyInfo = false; sortDialogs(null); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload, true); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload, true); } else { final SparseArray usersDict = new SparseArray<>(); final SparseArray chatsDict = new SparseArray<>(); @@ -3244,26 +3240,38 @@ public class MessagesController implements NotificationCenter.NotificationCenter } req1.peers.add(peer); - ConnectionsManager.getInstance(currentAccount).sendRequest(req1, (response1, error1) -> { - if (checkingProxyInfoRequestId == 0) { + checkingProxyInfoRequestId = getConnectionsManager().sendRequest(req1, (response1, error1) -> { + if (checkProxyId != lastCheckProxyId) { return; } checkingProxyInfoRequestId = 0; final TLRPC.TL_messages_peerDialogs res2 = (TLRPC.TL_messages_peerDialogs) response1; if (res2 != null && !res2.dialogs.isEmpty()) { - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, res.chats, true, true); + getMessagesStorage().putUsersAndChats(res.users, res.chats, true, true); TLRPC.TL_messages_dialogs dialogs = new TLRPC.TL_messages_dialogs(); dialogs.chats = res2.chats; dialogs.users = res2.users; dialogs.dialogs = res2.dialogs; dialogs.messages = res2.messages; - MessagesStorage.getInstance(currentAccount).putDialogs(dialogs, 2); + getMessagesStorage().putDialogs(dialogs, 2); AndroidUtilities.runOnUIThread(() -> { putUsers(res.users, false); putChats(res.chats, false); putUsers(res2.users, false); putChats(res2.chats, false); + if (proxyDialog != null) { + int lowerId = (int) proxyDialog.id; + if (lowerId < 0) { + TLRPC.Chat chat = getChat(-lowerId); + if (ChatObject.isNotInChat(chat) || chat.restricted) { + removeDialog(proxyDialog); + } + } else { + removeDialog(proxyDialog); + } + } + proxyDialog = res2.dialogs.get(0); proxyDialog.id = did; proxyDialog.folder_id = 0; @@ -3299,7 +3307,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } sortDialogs(null); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload, true); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload, true); }); } else { AndroidUtilities.runOnUIThread(() -> { @@ -3315,7 +3323,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } proxyDialog = null; sortDialogs(null); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } }); } @@ -3325,7 +3333,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter }); } } else { - nextProxyInfoCheckTime = ConnectionsManager.getInstance(currentAccount).getCurrentTime() + 60 * 60; + nextProxyInfoCheckTime = getConnectionsManager().getCurrentTime() + 60 * 60; noDialog = true; } if (noDialog) { @@ -3346,7 +3354,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } proxyDialog = null; sortDialogs(null); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } }); } @@ -3358,11 +3366,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter proxyDialogId = 0; proxyDialogAddress = null; getGlobalMainSettings().edit().putLong("proxy_dialog", proxyDialogId).remove("proxyDialogAddress").commit(); - nextProxyInfoCheckTime = ConnectionsManager.getInstance(currentAccount).getCurrentTime() + 60 * 60; + nextProxyInfoCheckTime = getConnectionsManager().getCurrentTime() + 60 * 60; if (removeCurrent == 2) { checkingProxyInfo = false; if (checkingProxyInfoRequestId != 0) { - ConnectionsManager.getInstance(currentAccount).cancelRequest(checkingProxyInfoRequestId, true); + getConnectionsManager().cancelRequest(checkingProxyInfoRequestId, true); checkingProxyInfoRequestId = 0; } } @@ -3379,7 +3387,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } proxyDialog = null; sortDialogs(null); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } }); } @@ -3577,14 +3585,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.action = new TLRPC.TL_sendMessageUploadAudioAction(); } typings.put(dialog_id, true); - int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + int reqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { LongSparseArray typings1 = sendingTypings.get(action); if (typings1 != null) { typings1.remove(dialog_id); } }), ConnectionsManager.RequestFlagFailOnServerErrors); if (classGuid != 0) { - ConnectionsManager.getInstance(currentAccount).bindRequestToGuid(reqId, classGuid); + getConnectionsManager().bindRequestToGuid(reqId, classGuid); } } else { if (action != 0) { @@ -3598,14 +3606,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.peer.access_hash = chat.access_hash; req.typing = true; typings.put(dialog_id, true); - int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + int reqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { LongSparseArray typings12 = sendingTypings.get(action); if (typings12 != null) { typings12.remove(dialog_id); } }), ConnectionsManager.RequestFlagFailOnServerErrors); if (classGuid != 0) { - ConnectionsManager.getInstance(currentAccount).bindRequestToGuid(reqId, classGuid); + getConnectionsManager().bindRequestToGuid(reqId, classGuid); } } } @@ -3640,7 +3648,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } int lower_part = (int) dialog_id; if (fromCache || lower_part == 0) { - MessagesStorage.getInstance(currentAccount).getMessages(dialog_id, count, max_id, offset_date, midDate, classGuid, load_type, isChannel, loadIndex); + getMessagesStorage().getMessages(dialog_id, count, max_id, offset_date, midDate, classGuid, load_type, isChannel, loadIndex); } else { if (loadDialog && (load_type == 3 || load_type == 2) && last_message_id == 0) { TLRPC.TL_messages_getPeerDialogs req = new TLRPC.TL_messages_getPeerDialogs(); @@ -3649,7 +3657,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter inputDialogPeer.peer = inputPeer; req.peers.add(inputDialogPeer); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { TLRPC.TL_messages_peerDialogs res = (TLRPC.TL_messages_peerDialogs) response; if (!res.dialogs.isEmpty()) { @@ -3661,7 +3669,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter dialogs.users = res.users; dialogs.dialogs = res.dialogs; dialogs.messages = res.messages; - MessagesStorage.getInstance(currentAccount).putDialogs(dialogs, 0); + getMessagesStorage().putDialogs(dialogs, 0); } loadMessagesInternal(dialog_id, count, max_id, offset_date, false, midDate, classGuid, load_type, dialog.top_message, isChannel, loadIndex, first_unread, dialog.unread_count, last_date, queryFromServer, dialog.unread_mentions_count, false); @@ -3692,7 +3700,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.limit = count; req.offset_id = max_id; req.offset_date = offset_date; - int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + int reqId = getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; removeDeletedMessagesFromArray(dialog_id, res.messages); @@ -3713,7 +3721,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter processLoadedMessages(res, dialog_id, count, mid, offset_date, false, classGuid, first_unread, last_message_id, unread_count, last_date, load_type, isChannel, false, loadIndex, queryFromServer, mentionsCount); } }); - ConnectionsManager.getInstance(currentAccount).bindRequestToGuid(reqId, classGuid); + getConnectionsManager().bindRequestToGuid(reqId, classGuid); } } @@ -3729,7 +3737,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter arrayList.addAll(messages); TLRPC.TL_messages_getWebPagePreview req = new TLRPC.TL_messages_getWebPagePreview(); req.message = url; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { ArrayList arrayList1 = reloadingWebpages.remove(url); if (arrayList1 == null) { return; @@ -3755,8 +3763,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } if (!messagesRes.messages.isEmpty()) { - MessagesStorage.getInstance(currentAccount).putMessages(messagesRes, dialog_id, -2, 0, false); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.replaceMessagesObjects, dialog_id, arrayList1); + getMessagesStorage().putMessages(messagesRes, dialog_id, -2, 0, false); + getNotificationCenter().postNotificationName(NotificationCenter.replaceMessagesObjects, dialog_id, arrayList1); } })); } @@ -3774,7 +3782,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter int channelId = -(int) dialog_id; int channelPts = channelsPts.get(channelId); if (channelPts == 0) { - channelPts = MessagesStorage.getInstance(currentAccount).getChannelPtsSync(channelId); + channelPts = getMessagesStorage().getChannelPtsSync(channelId); if (channelPts == 0) { channelsPts.put(channelId, messagesRes.pts); createDialog = true; @@ -3816,13 +3824,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (!isCache) { Integer inboxValue = dialogs_read_inbox_max.get(dialog_id); if (inboxValue == null) { - inboxValue = MessagesStorage.getInstance(currentAccount).getDialogReadMax(false, dialog_id); + inboxValue = getMessagesStorage().getDialogReadMax(false, dialog_id); dialogs_read_inbox_max.put(dialog_id, inboxValue); } Integer outboxValue = dialogs_read_outbox_max.get(dialog_id); if (outboxValue == null) { - outboxValue = MessagesStorage.getInstance(currentAccount).getDialogReadMax(true, dialog_id); + outboxValue = getMessagesStorage().getDialogReadMax(true, dialog_id); dialogs_read_outbox_max.put(dialog_id, outboxValue); } @@ -3846,7 +3854,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter message.unread = (message.out ? outboxValue : inboxValue) < message.id; } } - MessagesStorage.getInstance(currentAccount).putMessages(messagesRes, dialog_id, load_type, max_id, createDialog); + getMessagesStorage().putMessages(messagesRes, dialog_id, load_type, max_id, createDialog); } final ArrayList objects = new ArrayList<>(); final ArrayList messagesToReload = new ArrayList<>(); @@ -3866,7 +3874,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } if (message.media instanceof TLRPC.TL_messageMediaWebPage) { - if (message.media.webpage instanceof TLRPC.TL_webPagePending && message.media.webpage.date <= ConnectionsManager.getInstance(currentAccount).getCurrentTime()) { + if (message.media.webpage instanceof TLRPC.TL_webPagePending && message.media.webpage.date <= getConnectionsManager().getCurrentTime()) { messagesToReload.add(message.id); } else if (message.media.webpage instanceof TLRPC.TL_webPageUrlPending) { ArrayList arrayList = webpagesToReload.get(message.media.webpage.url); @@ -3894,7 +3902,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (first_unread_final == Integer.MAX_VALUE) { first_unread_final = first_unread; } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messagesDidLoad, dialog_id, count, objects, isCache, first_unread_final, last_message_id, unread_count, last_date, load_type, isEnd, classGuid, loadIndex, max_id, mentionsCount); + getNotificationCenter().postNotificationName(NotificationCenter.messagesDidLoad, dialog_id, count, objects, isCache, first_unread_final, last_message_id, unread_count, last_date, load_type, isEnd, classGuid, loadIndex, max_id, mentionsCount); if (!messagesToReload.isEmpty()) { reloadMessages(messagesToReload, dialog_id); } @@ -3911,7 +3919,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } TLRPC.TL_help_getRecentMeUrls req = new TLRPC.TL_help_getRecentMeUrls(); req.referer = installReferer; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { AndroidUtilities.runOnUIThread(() -> { /*installReferer = null; @@ -3923,7 +3931,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter hintDialogs.clear(); hintDialogs.addAll(res.urls); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); }); } }); @@ -3964,7 +3972,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.TL_messages_dialogs dialogs = new TLRPC.TL_messages_dialogs(); dialogs.dialogs.add(dialogFolder); - MessagesStorage.getInstance(currentAccount).putDialogs(dialogs, 1); + getMessagesStorage().putDialogs(dialogs, 1); dialogs_dict.put(folderDialogId, dialogFolder); allDialogs.add(0, dialogFolder); @@ -3980,12 +3988,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter dialogs_dict.remove(dialogId); allDialogs.remove(dialog); sortDialogs(null); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.folderBecomeEmpty, folderId); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.folderBecomeEmpty, folderId); } protected void onFolderEmpty(int folderId) { - int[] dialogsLoadOffset = UserConfig.getInstance(currentAccount).getDialogLoadOffsets(folderId); + int[] dialogsLoadOffset = getUserConfig().getDialogLoadOffsets(folderId); if (dialogsLoadOffset[UserConfig.i_dialogsLoadOffsetId] == Integer.MAX_VALUE) { removeFolder(folderId); } else { @@ -3997,7 +4005,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (folderId == 0) { return; } - MessagesStorage.getInstance(currentAccount).checkIfFolderEmpty(folderId); + getMessagesStorage().checkIfFolderEmpty(folderId); } public int addDialogToFolder(long dialogId, int folderId, int pinnedNum, long taskId) { @@ -4013,7 +4021,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter final long newTaskId; if (taskId == 0) { boolean added = false; - int selfUserId = UserConfig.getInstance(currentAccount).getClientUserId(); + int selfUserId = getUserConfig().getClientUserId(); int size = 0; for (int a = 0, N = dialogIds.size(); a < N; a++) { long dialogId = dialogIds.get(a); @@ -4041,7 +4049,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter ensureFolderDialogExists(folderId, folderCreated); } if (DialogObject.isSecretDialogId(dialogId)) { - MessagesStorage.getInstance(currentAccount).setDialogsFolderId(null, null, dialogId, folderId); + getMessagesStorage().setDialogsFolderId(null, null, dialogId, folderId); } else { TLRPC.TL_inputFolderPeer folderPeer = new TLRPC.TL_inputFolderPeer(); folderPeer.folder_id = folderId; @@ -4054,7 +4062,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter return 0; } sortDialogs(null); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); if (size != 0) { NativeByteBuffer data = null; @@ -4069,7 +4077,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } catch (Exception e) { FileLog.e(e); } - newTaskId = MessagesStorage.getInstance(currentAccount).createPendingTask(data); + newTaskId = getMessagesStorage().createPendingTask(data); } else { newTaskId = 0; } @@ -4078,15 +4086,15 @@ public class MessagesController implements NotificationCenter.NotificationCenter newTaskId = taskId; } if (!req.folder_peers.isEmpty()) { - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { processUpdates((TLRPC.Updates) response, false); } if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } }); - MessagesStorage.getInstance(currentAccount).setDialogsFolderId(null, req.folder_peers, 0, folderId); + getMessagesStorage().setDialogsFolderId(null, req.folder_peers, 0, folderId); } return folderCreated == null ? 0 : (folderCreated[0] ? 2 : 1); } @@ -4100,12 +4108,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter return; } loadingDialogs.put(folderId, true); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); if (BuildVars.LOGS_ENABLED) { FileLog.d("folderId = " + folderId + " load cacheOffset = " + offset + " count = " + count + " cache = " + fromCache); } if (fromCache) { - MessagesStorage.getInstance(currentAccount).getDialogs(folderId, offset == 0 ? 0 : nextDialogsCacheOffset.get(folderId, 0), count); + getMessagesStorage().getDialogs(folderId, offset == 0 ? 0 : nextDialogsCacheOffset.get(folderId, 0), count); } else { TLRPC.TL_messages_getDialogs req = new TLRPC.TL_messages_getDialogs(); req.limit = count; @@ -4114,13 +4122,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.flags |= 2; req.folder_id = folderId; } - int[] dialogsLoadOffset = UserConfig.getInstance(currentAccount).getDialogLoadOffsets(folderId); + int[] dialogsLoadOffset = getUserConfig().getDialogLoadOffsets(folderId); if (dialogsLoadOffset[UserConfig.i_dialogsLoadOffsetId] != -1) { if (dialogsLoadOffset[UserConfig.i_dialogsLoadOffsetId] == Integer.MAX_VALUE) { dialogsEndReached.put(folderId, true); serverDialogsEndReached.put(folderId, true); loadingDialogs.put(folderId, false); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); return; } req.offset_id = dialogsLoadOffset[UserConfig.i_dialogsLoadOffsetId]; @@ -4173,7 +4181,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.offset_peer = new TLRPC.TL_inputPeerEmpty(); } } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { final TLRPC.messages_Dialogs dialogsRes = (TLRPC.messages_Dialogs) response; processLoadedDialogs(dialogsRes, null, folderId, 0, count, 0, false, false, false); @@ -4186,7 +4194,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } public void loadGlobalNotificationsSettings() { - if (loadingNotificationSettings == 0 && !UserConfig.getInstance(currentAccount).notificationsSettingsLoaded) { + if (loadingNotificationSettings == 0 && !getUserConfig().notificationsSettingsLoaded) { SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); SharedPreferences.Editor editor1 = null; if (preferences.contains("EnableGroup")) { @@ -4225,7 +4233,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.peer = new TLRPC.TL_inputNotifyBroadcasts(); } final int type = a; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (response != null) { loadingNotificationSettings--; TLRPC.TL_peerNotifySettings notify_settings = (TLRPC.TL_peerNotifySettings) response; @@ -4275,14 +4283,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter } editor.commit(); if (loadingNotificationSettings == 0) { - UserConfig.getInstance(currentAccount).notificationsSettingsLoaded = true; - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().notificationsSettingsLoaded = true; + getUserConfig().saveConfig(false); } } })); } } - if (!UserConfig.getInstance(currentAccount).notificationsSignUpSettingsLoaded) { + if (!getUserConfig().notificationsSignUpSettingsLoaded) { loadSignUpNotificationsSettings(); } } @@ -4291,21 +4299,21 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (!loadingNotificationSignUpSettings) { loadingNotificationSignUpSettings = true; TLRPC.TL_account_getContactSignUpNotification req = new TLRPC.TL_account_getContactSignUpNotification(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { loadingNotificationSignUpSettings = false; SharedPreferences.Editor editor = notificationsPreferences.edit(); enableJoined = response instanceof TLRPC.TL_boolFalse; editor.putBoolean("EnableContactJoined", enableJoined); editor.commit(); - UserConfig.getInstance(currentAccount).notificationsSignUpSettingsLoaded = true; - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().notificationsSignUpSettingsLoaded = true; + getUserConfig().saveConfig(false); })); } } public void forceResetDialogs() { - resetDialogs(true, MessagesStorage.getInstance(currentAccount).getLastSeqValue(), MessagesStorage.getInstance(currentAccount).getLastPtsValue(), MessagesStorage.getInstance(currentAccount).getLastDateValue(), MessagesStorage.getInstance(currentAccount).getLastQtsValue()); - NotificationsController.getInstance(currentAccount).deleteAllNotificationChannels(); + resetDialogs(true, getMessagesStorage().getLastSeqValue(), getMessagesStorage().getLastPtsValue(), getMessagesStorage().getLastDateValue(), getMessagesStorage().getLastQtsValue()); + getNotificationsController().deleteAllNotificationChannels(); } protected void loadUnknownDialog(final TLRPC.InputPeer peer, final long taskId) { @@ -4335,11 +4343,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter } catch (Exception e) { FileLog.e(e); } - newTaskId = MessagesStorage.getInstance(currentAccount).createPendingTask(data); + newTaskId = getMessagesStorage().createPendingTask(data); } else { newTaskId = taskId; } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { TLRPC.TL_messages_peerDialogs res = (TLRPC.TL_messages_peerDialogs) response; if (!res.dialogs.isEmpty()) { @@ -4353,7 +4361,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } gettingUnknownDialogs.delete(dialogId); }); @@ -4419,10 +4427,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (resetingDialogs) { return; } - UserConfig.getInstance(currentAccount).setPinnedDialogsLoaded(1, false); + getUserConfig().setPinnedDialogsLoaded(1, false); resetingDialogs = true; TLRPC.TL_messages_getPinnedDialogs req = new TLRPC.TL_messages_getPinnedDialogs(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { resetDialogsPinned = (TLRPC.TL_messages_peerDialogs) response; for (int a = 0; a < resetDialogsPinned.dialogs.size(); a++) { @@ -4436,7 +4444,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter req2.limit = 100; req2.exclude_pinned = true; req2.offset_peer = new TLRPC.TL_inputPeerEmpty(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req2, (response, error) -> { + getConnectionsManager().sendRequest(req2, (response, error) -> { if (error == null) { resetDialogsAll = (TLRPC.messages_Dialogs) response; resetDialogs(false, seq, newPts, date, qts); @@ -4548,14 +4556,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter ConcurrentHashMap read_max = message.out ? dialogs_read_outbox_max : dialogs_read_inbox_max; Integer value = read_max.get(message.dialog_id); if (value == null) { - value = MessagesStorage.getInstance(currentAccount).getDialogReadMax(message.out, message.dialog_id); + value = getMessagesStorage().getDialogReadMax(message.out, message.dialog_id); read_max.put(message.dialog_id, value); } message.unread = value < message.id; } } - MessagesStorage.getInstance(currentAccount).resetDialogs(resetDialogsAll, messagesCount, seq, newPts, date, qts, new_dialogs_dict, new_dialogMessage, lastMessage, dialogsCount); + getMessagesStorage().resetDialogs(resetDialogsAll, messagesCount, seq, newPts, date, qts, new_dialogs_dict, new_dialogMessage, lastMessage, dialogsCount); resetDialogsPinned = null; resetDialogsAll = null; } @@ -4564,16 +4572,16 @@ public class MessagesController implements NotificationCenter.NotificationCenter protected void completeDialogsReset(final TLRPC.messages_Dialogs dialogsRes, final int messagesCount, final int seq, final int newPts, final int date, final int qts, final LongSparseArray new_dialogs_dict, final LongSparseArray new_dialogMessage, final TLRPC.Message lastMessage) { Utilities.stageQueue.postRunnable(() -> { gettingDifference = false; - MessagesStorage.getInstance(currentAccount).setLastPtsValue(newPts); - MessagesStorage.getInstance(currentAccount).setLastDateValue(date); - MessagesStorage.getInstance(currentAccount).setLastQtsValue(qts); + getMessagesStorage().setLastPtsValue(newPts); + getMessagesStorage().setLastDateValue(date); + getMessagesStorage().setLastQtsValue(qts); getDifference(); AndroidUtilities.runOnUIThread(() -> { resetingDialogs = false; applyDialogsNotificationsSettings(dialogsRes.dialogs); - if (!UserConfig.getInstance(currentAccount).draftsLoaded) { - DataQuery.getInstance(currentAccount).loadDrafts(); + if (!getUserConfig().draftsLoaded) { + getMediaDataController().loadDrafts(); } putUsers(dialogsRes.users, false); @@ -4598,7 +4606,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter long key = new_dialogs_dict.keyAt(a); TLRPC.Dialog value = new_dialogs_dict.valueAt(a); if (value.draft instanceof TLRPC.TL_draftMessage) { - DataQuery.getInstance(currentAccount).saveDraft(value.id, value.draft, null, false); + getMediaDataController().saveDraft(value.id, value.draft, null, false); } dialogs_dict.put(key, value); MessageObject messageObject = new_dialogMessage.get(value.id); @@ -4622,12 +4630,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter dialogsEndReached.put(1, true); serverDialogsEndReached.put(1, false); - int totalDialogsLoadCount = UserConfig.getInstance(currentAccount).getTotalDialogsCount(0); - int[] dialogsLoadOffset = UserConfig.getInstance(currentAccount).getDialogLoadOffsets(0); + int totalDialogsLoadCount = getUserConfig().getTotalDialogsCount(0); + int[] dialogsLoadOffset = getUserConfig().getDialogLoadOffsets(0); if (totalDialogsLoadCount < 400 && dialogsLoadOffset[UserConfig.i_dialogsLoadOffsetId] != -1 && dialogsLoadOffset[UserConfig.i_dialogsLoadOffsetId] != Integer.MAX_VALUE) { loadDialogs(0, 100, 0, false); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); }); }); } @@ -4661,14 +4669,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter } req.offset_peer.access_hash = accessPeer; } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { final TLRPC.messages_Dialogs dialogsRes = (TLRPC.messages_Dialogs) response; - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { int offsetId; - int totalDialogsLoadCount = UserConfig.getInstance(currentAccount).getTotalDialogsCount(0); - UserConfig.getInstance(currentAccount).setTotalDialogsCount(0, totalDialogsLoadCount + dialogsRes.dialogs.size()); + int totalDialogsLoadCount = getUserConfig().getTotalDialogsCount(0); + getUserConfig().setTotalDialogsCount(0, totalDialogsLoadCount + dialogsRes.dialogs.size()); TLRPC.Message lastMessage = null; for (int a = 0; a < dialogsRes.messages.size(); a++) { TLRPC.Message message = dialogsRes.messages.get(a); @@ -4689,13 +4697,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter FileLog.d("migrate stop due to not 100 dialogs"); } for (int i = 0; i < 2; i++) { - UserConfig.getInstance(currentAccount).setDialogsLoadOffset(i, + getUserConfig().setDialogsLoadOffset(i, Integer.MAX_VALUE, - UserConfig.getInstance(currentAccount).migrateOffsetDate, - UserConfig.getInstance(currentAccount).migrateOffsetUserId, - UserConfig.getInstance(currentAccount).migrateOffsetChatId, - UserConfig.getInstance(currentAccount).migrateOffsetChannelId, - UserConfig.getInstance(currentAccount).migrateOffsetAccess); + getUserConfig().migrateOffsetDate, + getUserConfig().migrateOffsetUserId, + getUserConfig().migrateOffsetChatId, + getUserConfig().migrateOffsetChannelId, + getUserConfig().migrateOffsetAccess); } offsetId = -1; } @@ -4711,7 +4719,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter dids.append(dialog.id); dialogHashMap.put(dialog.id, dialog); } - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT did, folder_id FROM dialogs WHERE did IN (%s)", dids.toString())); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT did, folder_id FROM dialogs WHERE did IN (%s)", dids.toString())); while (cursor.next()) { long did = cursor.longValue(0); int folder_id = cursor.intValue(1); @@ -4740,7 +4748,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (BuildVars.LOGS_ENABLED) { FileLog.d("migrate found missing dialogs " + dialogsRes.dialogs.size()); } - cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized("SELECT min(date) FROM dialogs WHERE date != 0 AND did >> 32 IN (0, -1)"); + cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT min(date) FROM dialogs WHERE date != 0 AND did >> 32 IN (0, -1)"); if (cursor.next()) { int date = Math.max(1441062000, cursor.intValue(0)); for (int a = 0; a < dialogsRes.messages.size(); a++) { @@ -4748,13 +4756,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (message.date < date) { if (offset != -1) { for (int i = 0; i < 2; i++) { - UserConfig.getInstance(currentAccount).setDialogsLoadOffset(i, - UserConfig.getInstance(currentAccount).migrateOffsetId, - UserConfig.getInstance(currentAccount).migrateOffsetDate, - UserConfig.getInstance(currentAccount).migrateOffsetUserId, - UserConfig.getInstance(currentAccount).migrateOffsetChatId, - UserConfig.getInstance(currentAccount).migrateOffsetChannelId, - UserConfig.getInstance(currentAccount).migrateOffsetAccess); + getUserConfig().setDialogsLoadOffset(i, + getUserConfig().migrateOffsetId, + getUserConfig().migrateOffsetDate, + getUserConfig().migrateOffsetUserId, + getUserConfig().migrateOffsetChatId, + getUserConfig().migrateOffsetChannelId, + getUserConfig().migrateOffsetAccess); } offsetId = -1; if (BuildVars.LOGS_ENABLED) { @@ -4773,13 +4781,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter } if (lastMessage != null && lastMessage.date < date && offset != -1) { for (int i = 0; i < 2; i++) { - UserConfig.getInstance(currentAccount).setDialogsLoadOffset(i, - UserConfig.getInstance(currentAccount).migrateOffsetId, - UserConfig.getInstance(currentAccount).migrateOffsetDate, - UserConfig.getInstance(currentAccount).migrateOffsetUserId, - UserConfig.getInstance(currentAccount).migrateOffsetChatId, - UserConfig.getInstance(currentAccount).migrateOffsetChannelId, - UserConfig.getInstance(currentAccount).migrateOffsetAccess); + getUserConfig().setDialogsLoadOffset(i, + getUserConfig().migrateOffsetId, + getUserConfig().migrateOffsetDate, + getUserConfig().migrateOffsetUserId, + getUserConfig().migrateOffsetChatId, + getUserConfig().migrateOffsetChannelId, + getUserConfig().migrateOffsetAccess); } offsetId = -1; if (BuildVars.LOGS_ENABLED) { @@ -4789,37 +4797,37 @@ public class MessagesController implements NotificationCenter.NotificationCenter } cursor.dispose(); - UserConfig.getInstance(currentAccount).migrateOffsetDate = lastMessage.date; + getUserConfig().migrateOffsetDate = lastMessage.date; if (lastMessage.to_id.channel_id != 0) { - UserConfig.getInstance(currentAccount).migrateOffsetChannelId = lastMessage.to_id.channel_id; - UserConfig.getInstance(currentAccount).migrateOffsetChatId = 0; - UserConfig.getInstance(currentAccount).migrateOffsetUserId = 0; + getUserConfig().migrateOffsetChannelId = lastMessage.to_id.channel_id; + getUserConfig().migrateOffsetChatId = 0; + getUserConfig().migrateOffsetUserId = 0; for (int a = 0; a < dialogsRes.chats.size(); a++) { TLRPC.Chat chat = dialogsRes.chats.get(a); - if (chat.id == UserConfig.getInstance(currentAccount).migrateOffsetChannelId) { - UserConfig.getInstance(currentAccount).migrateOffsetAccess = chat.access_hash; + if (chat.id == getUserConfig().migrateOffsetChannelId) { + getUserConfig().migrateOffsetAccess = chat.access_hash; break; } } } else if (lastMessage.to_id.chat_id != 0) { - UserConfig.getInstance(currentAccount).migrateOffsetChatId = lastMessage.to_id.chat_id; - UserConfig.getInstance(currentAccount).migrateOffsetChannelId = 0; - UserConfig.getInstance(currentAccount).migrateOffsetUserId = 0; + getUserConfig().migrateOffsetChatId = lastMessage.to_id.chat_id; + getUserConfig().migrateOffsetChannelId = 0; + getUserConfig().migrateOffsetUserId = 0; for (int a = 0; a < dialogsRes.chats.size(); a++) { TLRPC.Chat chat = dialogsRes.chats.get(a); - if (chat.id == UserConfig.getInstance(currentAccount).migrateOffsetChatId) { - UserConfig.getInstance(currentAccount).migrateOffsetAccess = chat.access_hash; + if (chat.id == getUserConfig().migrateOffsetChatId) { + getUserConfig().migrateOffsetAccess = chat.access_hash; break; } } } else if (lastMessage.to_id.user_id != 0) { - UserConfig.getInstance(currentAccount).migrateOffsetUserId = lastMessage.to_id.user_id; - UserConfig.getInstance(currentAccount).migrateOffsetChatId = 0; - UserConfig.getInstance(currentAccount).migrateOffsetChannelId = 0; + getUserConfig().migrateOffsetUserId = lastMessage.to_id.user_id; + getUserConfig().migrateOffsetChatId = 0; + getUserConfig().migrateOffsetChannelId = 0; for (int a = 0; a < dialogsRes.users.size(); a++) { TLRPC.User user = dialogsRes.users.get(a); - if (user.id == UserConfig.getInstance(currentAccount).migrateOffsetUserId) { - UserConfig.getInstance(currentAccount).migrateOffsetAccess = user.access_hash; + if (user.id == getUserConfig().migrateOffsetUserId) { + getUserConfig().migrateOffsetAccess = user.access_hash; break; } } @@ -4851,7 +4859,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (BuildVars.LOGS_ENABLED) { FileLog.d("loaded folderId " + folderId + " loadType " + loadType + " count " + dialogsRes.dialogs.size()); } - int[] dialogsLoadOffset = UserConfig.getInstance(currentAccount).getDialogLoadOffsets(folderId); + int[] dialogsLoadOffset = getUserConfig().getDialogLoadOffsets(folderId); if (loadType == DIALOGS_LOAD_TYPE_CACHE && dialogsRes.dialogs.size() == 0) { AndroidUtilities.runOnUIThread(() -> { putUsers(dialogsRes.users, true); @@ -4865,7 +4873,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else { loadDialogs(folderId, 0, count, false); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); }); return; } @@ -4922,7 +4930,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } if (!fromCache && !migrate && dialogsLoadOffset[UserConfig.i_dialogsLoadOffsetId] != -1 && loadType == 0) { - int totalDialogsLoadCount = UserConfig.getInstance(currentAccount).getTotalDialogsCount(folderId); + int totalDialogsLoadCount = getUserConfig().getTotalDialogsCount(folderId); int dialogsLoadOffsetId; int dialogsLoadOffsetDate = 0; int dialogsLoadOffsetChannelId = 0; @@ -4970,15 +4978,15 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else { dialogsLoadOffsetId = Integer.MAX_VALUE; } - UserConfig.getInstance(currentAccount).setDialogsLoadOffset(folderId, + getUserConfig().setDialogsLoadOffset(folderId, dialogsLoadOffsetId, dialogsLoadOffsetDate, dialogsLoadOffsetUserId, dialogsLoadOffsetChatId, dialogsLoadOffsetChannelId, dialogsLoadOffsetAccess); - UserConfig.getInstance(currentAccount).setTotalDialogsCount(folderId, totalDialogsLoadCount); - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().setTotalDialogsCount(folderId, totalDialogsLoadCount); + getUserConfig().saveConfig(false); } final ArrayList dialogsToReload = new ArrayList<>(); @@ -5061,13 +5069,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter ConcurrentHashMap read_max = message.out ? dialogs_read_outbox_max : dialogs_read_inbox_max; Integer value = read_max.get(message.dialog_id); if (value == null) { - value = MessagesStorage.getInstance(currentAccount).getDialogReadMax(message.out, message.dialog_id); + value = getMessagesStorage().getDialogReadMax(message.out, message.dialog_id); read_max.put(message.dialog_id, value); } message.unread = value < message.id; } } - MessagesStorage.getInstance(currentAccount).putDialogs(dialogsRes, 0); + getMessagesStorage().putDialogs(dialogsRes, 0); } if (loadType == DIALOGS_LOAD_TYPE_CHANNEL) { TLRPC.Chat chat = dialogsRes.chats.get(0); @@ -5078,8 +5086,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter AndroidUtilities.runOnUIThread(() -> { if (loadType != DIALOGS_LOAD_TYPE_CACHE) { applyDialogsNotificationsSettings(dialogsRes.dialogs); - if (!UserConfig.getInstance(currentAccount).draftsLoaded) { - DataQuery.getInstance(currentAccount).loadDrafts(); + if (!getUserConfig().draftsLoaded) { + getMediaDataController().loadDrafts(); } } putUsers(dialogsRes.users, loadType == DIALOGS_LOAD_TYPE_CACHE); @@ -5088,7 +5096,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter for (int a = 0; a < encChats.size(); a++) { TLRPC.EncryptedChat encryptedChat = encChats.get(a); if (encryptedChat instanceof TLRPC.TL_encryptedChat && AndroidUtilities.getMyLayerVersion(encryptedChat.layer) < SecretChatHelper.CURRENT_SECRET_CHAT_LAYER) { - SecretChatHelper.getInstance(currentAccount).sendNotifyLayerMessage(encryptedChat, null); + getSecretChatHelper().sendNotifyLayerMessage(encryptedChat, null); } putEncryptedChat(encryptedChat, true); } @@ -5117,7 +5125,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter continue; } if (loadType != DIALOGS_LOAD_TYPE_CACHE && value.draft instanceof TLRPC.TL_draftMessage) { - DataQuery.getInstance(currentAccount).saveDraft(value.id, value.draft, null, false); + getMediaDataController().saveDraft(value.id, value.draft, null, false); } if (value.folder_id != folderId) { archivedDialogsCount++; @@ -5189,7 +5197,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter dialogsEndReached.put(folderId, (dialogsRes.dialogs.size() == 0 || dialogsRes.dialogs.size() != count) && loadType == 0); if (archivedDialogsCount > 0 && archivedDialogsCount < 20 && folderId == 0) { dialogsEndReached.put(1, true); - int[] dialogsLoadOffsetArchived = UserConfig.getInstance(currentAccount).getDialogLoadOffsets(folderId); + int[] dialogsLoadOffsetArchived = getUserConfig().getDialogLoadOffsets(folderId); if (dialogsLoadOffsetArchived[UserConfig.i_dialogsLoadOffsetId] == Integer.MAX_VALUE) { serverDialogsEndReached.put(1, true); } @@ -5199,25 +5207,25 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } } - int totalDialogsLoadCount = UserConfig.getInstance(currentAccount).getTotalDialogsCount(folderId); - int[] dialogsLoadOffset2 = UserConfig.getInstance(currentAccount).getDialogLoadOffsets(folderId); + int totalDialogsLoadCount = getUserConfig().getTotalDialogsCount(folderId); + int[] dialogsLoadOffset2 = getUserConfig().getDialogLoadOffsets(folderId); if (!fromCache && !migrate && totalDialogsLoadCount < 400 && dialogsLoadOffset2[UserConfig.i_dialogsLoadOffsetId] != -1 && dialogsLoadOffset2[UserConfig.i_dialogsLoadOffsetId] != Integer.MAX_VALUE) { loadDialogs(0, 100, folderId, false); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); if (migrate) { - UserConfig.getInstance(currentAccount).migrateOffsetId = offset; - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().migrateOffsetId = offset; + getUserConfig().saveConfig(false); migratingDialogs = false; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.needReloadRecentDialogsSearch); + getNotificationCenter().postNotificationName(NotificationCenter.needReloadRecentDialogsSearch); } else { generateUpdateMessage(); if (!added && loadType == DIALOGS_LOAD_TYPE_CACHE) { loadDialogs(folderId, 0, count, false); } } - migrateDialogs(UserConfig.getInstance(currentAccount).migrateOffsetId, UserConfig.getInstance(currentAccount).migrateOffsetDate, UserConfig.getInstance(currentAccount).migrateOffsetUserId, UserConfig.getInstance(currentAccount).migrateOffsetChatId, UserConfig.getInstance(currentAccount).migrateOffsetChannelId, UserConfig.getInstance(currentAccount).migrateOffsetAccess); + migrateDialogs(getUserConfig().migrateOffsetId, getUserConfig().migrateOffsetDate, getUserConfig().migrateOffsetUserId, getUserConfig().migrateOffsetChatId, getUserConfig().migrateOffsetChannelId, getUserConfig().migrateOffsetAccess); if (!dialogsToReload.isEmpty()) { reloadDialogsReadValue(dialogsToReload, 0); } @@ -5244,9 +5252,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter editor.remove("silent_" + dialog_id); } if ((notify_settings.flags & 4) != 0) { - if (notify_settings.mute_until > ConnectionsManager.getInstance(currentAccount).getCurrentTime()) { + if (notify_settings.mute_until > getConnectionsManager().getCurrentTime()) { int until = 0; - if (notify_settings.mute_until > ConnectionsManager.getInstance(currentAccount).getCurrentTime() + 60 * 60 * 24 * 365) { + if (notify_settings.mute_until > getConnectionsManager().getCurrentTime() + 60 * 60 * 24 * 365) { if (currentValue != 2) { updated = true; editor.putInt("notify2_" + dialog_id, 2); @@ -5265,8 +5273,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter } until = notify_settings.mute_until; } - MessagesStorage.getInstance(currentAccount).setDialogFlags(dialog_id, ((long) until << 32) | 1); - NotificationsController.getInstance(currentAccount).removeNotificationsForDialog(dialog_id); + getMessagesStorage().setDialogFlags(dialog_id, ((long) until << 32) | 1); + getNotificationsController().removeNotificationsForDialog(dialog_id); } else { if (currentValue != 0 && currentValue != 1) { updated = true; @@ -5275,7 +5283,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } editor.putInt("notify2_" + dialog_id, 0); } - MessagesStorage.getInstance(currentAccount).setDialogFlags(dialog_id, 0); + getMessagesStorage().setDialogFlags(dialog_id, 0); } } else { if (currentValue != -1) { @@ -5285,11 +5293,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter } editor.remove("notify2_" + dialog_id); } - MessagesStorage.getInstance(currentAccount).setDialogFlags(dialog_id, 0); + getMessagesStorage().setDialogFlags(dialog_id, 0); } editor.commit(); if (updated) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.notificationsSettingsUpdated); + getNotificationCenter().postNotificationName(NotificationCenter.notificationsSettingsUpdated); } } @@ -5315,8 +5323,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter editor.remove("silent_" + dialog_id); } if ((dialog.notify_settings.flags & 4) != 0) { - if (dialog.notify_settings.mute_until > ConnectionsManager.getInstance(currentAccount).getCurrentTime()) { - if (dialog.notify_settings.mute_until > ConnectionsManager.getInstance(currentAccount).getCurrentTime() + 60 * 60 * 24 * 365) { + if (dialog.notify_settings.mute_until > getConnectionsManager().getCurrentTime()) { + if (dialog.notify_settings.mute_until > getConnectionsManager().getCurrentTime() + 60 * 60 * 24 * 365) { editor.putInt("notify2_" + dialog_id, 2); dialog.notify_settings.mute_until = Integer.MAX_VALUE; } else { @@ -5343,7 +5351,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.TL_messages_getUnreadMentions req = new TLRPC.TL_messages_getUnreadMentions(); req.peer = getInputPeer((int) dialog_id); req.limit = 1; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; if (res != null) { int newCount; @@ -5352,7 +5360,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else { newCount = res.messages.size(); } - MessagesStorage.getInstance(currentAccount).resetMentionsCount(dialog_id, newCount); + getMessagesStorage().resetMentionsCount(dialog_id, newCount); } })); } @@ -5383,14 +5391,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (currentDialog != null) { currentDialog.unread_mentions_count = dialogsMentionsToUpdate.valueAt(a); if (createdDialogMainThreadIds.contains(currentDialog.id)) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateMentionsCount, currentDialog.id, currentDialog.unread_mentions_count); + getNotificationCenter().postNotificationName(NotificationCenter.updateMentionsCount, currentDialog.id, currentDialog.unread_mentions_count); } } } } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_READ_DIALOG_MESSAGE); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_READ_DIALOG_MESSAGE); if (dialogsToUpdate != null) { - NotificationsController.getInstance(currentAccount).processDialogsUpdateRead(dialogsToUpdate); + getNotificationsController().processDialogsUpdateRead(dialogsToUpdate); } }); } @@ -5431,12 +5439,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter } catch (Exception e) { FileLog.e(e); } - newTaskId = MessagesStorage.getInstance(currentAccount).createPendingTask(data); + newTaskId = getMessagesStorage().createPendingTask(data); } else { newTaskId = taskId; } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; removeDeletedMessagesFromArray(lower_id, res.messages); @@ -5464,7 +5472,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter dialogs.messages.addAll(res.messages); dialogs.count = 1; processDialogsUpdate(dialogs, null); - MessagesStorage.getInstance(currentAccount).putMessages(res.messages, true, true, false, DownloadController.getInstance(currentAccount).getAutodownloadMask(), true); + getMessagesStorage().putMessages(res.messages, true, true, false, getDownloadController().getAutodownloadMask(), true); } else { AndroidUtilities.runOnUIThread(() -> { TLRPC.Dialog currentDialog = dialogs_dict.get(dialog.id); @@ -5475,7 +5483,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } AndroidUtilities.runOnUIThread(() -> checkingLastMessagesDialogs.delete(lower_id)); }); @@ -5579,7 +5587,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (currentDialog.unread_mentions_count != value.unread_mentions_count) { currentDialog.unread_mentions_count = value.unread_mentions_count; if (createdDialogMainThreadIds.contains(currentDialog.id)) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateMentionsCount, currentDialog.id, currentDialog.unread_mentions_count); + getNotificationCenter().postNotificationName(NotificationCenter.updateMentionsCount, currentDialog.id, currentDialog.unread_mentions_count); } } MessageObject oldMsg = dialogMessage.get(key); @@ -5629,8 +5637,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter allDialogs.add(dialogs_dict.valueAt(a)); } sortDialogs(null); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); - NotificationsController.getInstance(currentAccount).processDialogsUpdateRead(dialogsToUpdate); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationsController().processDialogsUpdateRead(dialogsToUpdate); }); }); } @@ -5683,11 +5691,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter messageId |= ((long) messageObject.messageOwner.to_id.channel_id) << 32; } if (messageObject.messageOwner.mentioned) { - MessagesStorage.getInstance(currentAccount).markMentionMessageAsRead(messageObject.getId(), messageObject.messageOwner.to_id.channel_id, messageObject.getDialogId()); + getMessagesStorage().markMentionMessageAsRead(messageObject.getId(), messageObject.messageOwner.to_id.channel_id, messageObject.getDialogId()); } arrayList.add(messageId); - MessagesStorage.getInstance(currentAccount).markMessagesContentAsRead(arrayList, 0); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messagesReadContent, arrayList); + getMessagesStorage().markMessagesContentAsRead(arrayList, 0); + getNotificationCenter().postNotificationName(NotificationCenter.messagesReadContent, arrayList); if (messageObject.getId() < 0) { markMessageAsRead(messageObject.getDialogId(), messageObject.messageOwner.random_id, Integer.MIN_VALUE); } else { @@ -5698,13 +5706,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter return; } req.id.add(messageObject.getId()); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); } else { TLRPC.TL_messages_readMessageContents req = new TLRPC.TL_messages_readMessageContents(); req.id.add(messageObject.getId()); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.TL_messages_affectedMessages res = (TLRPC.TL_messages_affectedMessages) response; processNewDifferenceParams(-1, res.pts, -1, res.pts_count); @@ -5715,7 +5723,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } public void markMentionMessageAsRead(final int mid, final int channelId, final long did) { - MessagesStorage.getInstance(currentAccount).markMentionMessageAsRead(mid, channelId, did); + getMessagesStorage().markMentionMessageAsRead(mid, channelId, did); if (channelId != 0) { TLRPC.TL_channels_readMessageContents req = new TLRPC.TL_channels_readMessageContents(); req.channel = getInputChannel(channelId); @@ -5723,13 +5731,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter return; } req.id.add(mid); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); } else { TLRPC.TL_messages_readMessageContents req = new TLRPC.TL_messages_readMessageContents(); req.id.add(mid); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.TL_messages_affectedMessages res = (TLRPC.TL_messages_affectedMessages) response; processNewDifferenceParams(-1, res.pts, -1, res.pts_count); @@ -5763,31 +5771,31 @@ public class MessagesController implements NotificationCenter.NotificationCenter } catch (Exception e) { FileLog.e(e); } - newTaskId = MessagesStorage.getInstance(currentAccount).createPendingTask(data); + newTaskId = getMessagesStorage().createPendingTask(data); } else { newTaskId = taskId; } - int time = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); - MessagesStorage.getInstance(currentAccount).createTaskForMid(mid, channelId, time, time, ttl, false); + int time = getConnectionsManager().getCurrentTime(); + getMessagesStorage().createTaskForMid(mid, channelId, time, time, ttl, false); if (channelId != 0) { TLRPC.TL_channels_readMessageContents req = new TLRPC.TL_channels_readMessageContents(); req.channel = inputChannel; req.id.add(mid); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } }); } else { TLRPC.TL_messages_readMessageContents req = new TLRPC.TL_messages_readMessageContents(); req.id.add(mid); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.TL_messages_affectedMessages res = (TLRPC.TL_messages_affectedMessages) response; processNewDifferenceParams(-1, res.pts, -1, res.pts_count); } if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } }); } @@ -5808,10 +5816,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter } ArrayList random_ids = new ArrayList<>(); random_ids.add(random_id); - SecretChatHelper.getInstance(currentAccount).sendMessagesReadMessage(chat, random_ids, null); + getSecretChatHelper().sendMessagesReadMessage(chat, random_ids, null); if (ttl > 0) { - int time = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); - MessagesStorage.getInstance(currentAccount).createTaskForSecretChat(chat.id, time, time, 0, random_ids); + int time = getConnectionsManager().getCurrentTime(); + getMessagesStorage().createTaskForSecretChat(chat.id, time, time, 0, random_ids); } } @@ -5833,7 +5841,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter request.max_id = task.maxId; req = request; } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { if (response instanceof TLRPC.TL_messages_affectedMessages) { TLRPC.TL_messages_affectedMessages res = (TLRPC.TL_messages_affectedMessages) response; @@ -5849,7 +5857,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.peer.chat_id = chat.id; req.peer.access_hash = chat.access_hash; req.max_date = task.maxDate; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); } @@ -5887,10 +5895,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter if ((int) dialogId == 0) { return; } - MessagesStorage.getInstance(currentAccount).resetMentionsCount(dialogId, 0); + getMessagesStorage().resetMentionsCount(dialogId, 0); TLRPC.TL_messages_readMentions req = new TLRPC.TL_messages_readMentions(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) dialogId); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + req.peer = getInputPeer((int) dialogId); + getConnectionsManager().sendRequest(req, (response, error) -> { }); } @@ -5900,7 +5908,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter int high_id = (int) (dialogId >> 32); boolean createReadTask; - boolean countMessages = NotificationsController.getInstance(currentAccount).showBadgeMessages; + boolean countMessages = getNotificationsController().showBadgeMessages; if (lower_part != 0) { if (maxPositiveId == 0 || high_id == 1) { return; @@ -5922,8 +5930,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter } dialogs_read_inbox_max.put(dialogId, Math.max(value, maxPositiveId)); - MessagesStorage.getInstance(currentAccount).processPendingRead(dialogId, maxMessageId, minMessageId, maxDate, isChannel); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> { + getMessagesStorage().processPendingRead(dialogId, maxMessageId, minMessageId, isChannel); + getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> { TLRPC.Dialog dialog = dialogs_dict.get(dialogId); if (dialog != null) { int prevCount = dialog.unread_count; @@ -5959,20 +5967,20 @@ public class MessagesController implements NotificationCenter.NotificationCenter } if (dialog.unread_mark) { dialog.unread_mark = false; - MessagesStorage.getInstance(currentAccount).setDialogUnread(dialog.id, false); + getMessagesStorage().setDialogUnread(dialog.id, false); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_READ_DIALOG_MESSAGE); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_READ_DIALOG_MESSAGE); } if (!popup) { - NotificationsController.getInstance(currentAccount).processReadMessages(null, dialogId, 0, maxPositiveId, false); + getNotificationsController().processReadMessages(null, dialogId, 0, maxPositiveId, false); LongSparseArray dialogsToUpdate = new LongSparseArray<>(1); dialogsToUpdate.put(dialogId, 0); - NotificationsController.getInstance(currentAccount).processDialogsUpdateRead(dialogsToUpdate); + getNotificationsController().processDialogsUpdateRead(dialogsToUpdate); } else { - NotificationsController.getInstance(currentAccount).processReadMessages(null, dialogId, 0, maxPositiveId, true); + getNotificationsController().processReadMessages(null, dialogId, 0, maxPositiveId, true); LongSparseArray dialogsToUpdate = new LongSparseArray<>(1); dialogsToUpdate.put(dialogId, -1); - NotificationsController.getInstance(currentAccount).processDialogsUpdateRead(dialogsToUpdate); + getNotificationsController().processDialogsUpdateRead(dialogsToUpdate); } })); @@ -5984,9 +5992,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter createReadTask = true; TLRPC.EncryptedChat chat = getEncryptedChat(high_id); - MessagesStorage.getInstance(currentAccount).processPendingRead(dialogId, maxPositiveId, maxNegativeId, maxDate, false); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> { - NotificationsController.getInstance(currentAccount).processReadMessages(null, dialogId, maxDate, 0, popup); + getMessagesStorage().processPendingRead(dialogId, maxPositiveId, maxNegativeId, false); + getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> { + getNotificationsController().processReadMessages(null, dialogId, maxDate, 0, popup); TLRPC.Dialog dialog = dialogs_dict.get(dialogId); if (dialog != null) { int prevCount = dialog.unread_count; @@ -6021,18 +6029,18 @@ public class MessagesController implements NotificationCenter.NotificationCenter } if (dialog.unread_mark) { dialog.unread_mark = false; - MessagesStorage.getInstance(currentAccount).setDialogUnread(dialog.id, false); + getMessagesStorage().setDialogUnread(dialog.id, false); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_READ_DIALOG_MESSAGE); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_READ_DIALOG_MESSAGE); } LongSparseArray dialogsToUpdate = new LongSparseArray<>(1); dialogsToUpdate.put(dialogId, 0); - NotificationsController.getInstance(currentAccount).processDialogsUpdateRead(dialogsToUpdate); + getNotificationsController().processDialogsUpdateRead(dialogsToUpdate); })); if (chat != null && chat.ttl > 0) { - int serverTime = Math.max(ConnectionsManager.getInstance(currentAccount).getCurrentTime(), maxDate); - MessagesStorage.getInstance(currentAccount).createTaskForSecretChat(chat.id, serverTime, serverTime, 0, null); + int serverTime = Math.max(getConnectionsManager().getCurrentTime(), maxDate); + getMessagesStorage().createTaskForSecretChat(chat.id, serverTime, serverTime, 0, null); } } @@ -6057,20 +6065,20 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } - public int createChat(String title, ArrayList selectedContacts, final String about, int type, final BaseFragment fragment) { + public int createChat(String title, ArrayList selectedContacts, final String about, int type, Location location, String locationAddress, final BaseFragment fragment) { if (type == ChatObject.CHAT_TYPE_BROADCAST) { TLRPC.TL_chat chat = new TLRPC.TL_chat(); - chat.id = UserConfig.getInstance(currentAccount).lastBroadcastId; + chat.id = getUserConfig().lastBroadcastId; chat.title = title; chat.photo = new TLRPC.TL_chatPhotoEmpty(); chat.participants_count = selectedContacts.size(); chat.date = (int) (System.currentTimeMillis() / 1000); chat.version = 1; - UserConfig.getInstance(currentAccount).lastBroadcastId--; + getUserConfig().lastBroadcastId--; putChat(chat, false); ArrayList chatsArrays = new ArrayList<>(); chatsArrays.add(chat); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(null, chatsArrays, true, true); + getMessagesStorage().putUsersAndChats(null, chatsArrays, true, true); TLRPC.TL_chatFull chatFull = new TLRPC.TL_chatFull(); chatFull.id = chat.id; @@ -6079,28 +6087,28 @@ public class MessagesController implements NotificationCenter.NotificationCenter chatFull.exported_invite = new TLRPC.TL_chatInviteEmpty(); chatFull.participants = new TLRPC.TL_chatParticipants(); chatFull.participants.chat_id = chat.id; - chatFull.participants.admin_id = UserConfig.getInstance(currentAccount).getClientUserId(); + chatFull.participants.admin_id = getUserConfig().getClientUserId(); chatFull.participants.version = 1; for (int a = 0; a < selectedContacts.size(); a++) { TLRPC.TL_chatParticipant participant = new TLRPC.TL_chatParticipant(); participant.user_id = selectedContacts.get(a); - participant.inviter_id = UserConfig.getInstance(currentAccount).getClientUserId(); + participant.inviter_id = getUserConfig().getClientUserId(); participant.date = (int) (System.currentTimeMillis() / 1000); chatFull.participants.participants.add(participant); } - MessagesStorage.getInstance(currentAccount).updateChatInfo(chatFull, false); + getMessagesStorage().updateChatInfo(chatFull, false); TLRPC.TL_messageService newMsg = new TLRPC.TL_messageService(); newMsg.action = new TLRPC.TL_messageActionCreatedBroadcastList(); - newMsg.local_id = newMsg.id = UserConfig.getInstance(currentAccount).getNewMessageId(); - newMsg.from_id = UserConfig.getInstance(currentAccount).getClientUserId(); + newMsg.local_id = newMsg.id = getUserConfig().getNewMessageId(); + newMsg.from_id = getUserConfig().getClientUserId(); newMsg.dialog_id = AndroidUtilities.makeBroadcastId(chat.id); newMsg.to_id = new TLRPC.TL_peerChat(); newMsg.to_id.chat_id = chat.id; - newMsg.date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + newMsg.date = getConnectionsManager().getCurrentTime(); newMsg.random_id = 0; newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_FROM_ID; - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().saveConfig(false); MessageObject newMsgObj = new MessageObject(currentAccount, newMsg, users, true); newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; @@ -6108,10 +6116,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter objArr.add(newMsgObj); ArrayList arr = new ArrayList<>(); arr.add(newMsg); - MessagesStorage.getInstance(currentAccount).putMessages(arr, false, true, false, 0); + getMessagesStorage().putMessages(arr, false, true, false, 0); updateInterfaceWithMessages(newMsg.dialog_id, objArr); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatDidCreated, chat.id); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.chatDidCreated, chat.id); return 0; } else if (type == ChatObject.CHAT_TYPE_CHAT) { @@ -6124,11 +6132,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter } req.users.add(getInputUser(user)); } - return ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + return getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null) { AndroidUtilities.runOnUIThread(() -> { AlertsCreator.processError(currentAccount, error, fragment, req); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatDidFailCreate); + getNotificationCenter().postNotificationName(NotificationCenter.chatDidFailCreate); }); return; } @@ -6138,9 +6146,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter putUsers(updates.users, false); putChats(updates.chats, false); if (updates.chats != null && !updates.chats.isEmpty()) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatDidCreated, updates.chats.get(0).id); + getNotificationCenter().postNotificationName(NotificationCenter.chatDidCreated, updates.chats.get(0).id); } else { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatDidFailCreate); + getNotificationCenter().postNotificationName(NotificationCenter.chatDidFailCreate); } }); }, ConnectionsManager.RequestFlagFailOnServerErrors); @@ -6153,11 +6161,18 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else { req.broadcast = true; } - return ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + if (location != null) { + req.geo_point = new TLRPC.TL_inputGeoPoint(); + req.geo_point.lat = location.getLatitude(); + req.geo_point._long = location.getLongitude(); + req.address = locationAddress; + req.flags |= 4; + } + return getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null) { AndroidUtilities.runOnUIThread(() -> { AlertsCreator.processError(currentAccount, error, fragment, req); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatDidFailCreate); + getNotificationCenter().postNotificationName(NotificationCenter.chatDidFailCreate); }); return; } @@ -6167,9 +6182,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter putUsers(updates.users, false); putChats(updates.chats, false); if (updates.chats != null && !updates.chats.isEmpty()) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatDidCreated, updates.chats.get(0).id); + getNotificationCenter().postNotificationName(NotificationCenter.chatDidCreated, updates.chats.get(0).id); } else { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatDidFailCreate); + getNotificationCenter().postNotificationName(NotificationCenter.chatDidFailCreate); } }); }, ConnectionsManager.RequestFlagFailOnServerErrors); @@ -6181,7 +6196,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.TL_messages_migrateChat req = new TLRPC.TL_messages_migrateChat(); req.chat_id = chat_id; final AlertDialog progressDialog = new AlertDialog(context, 3); - final int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + final int reqId = getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { AndroidUtilities.runOnUIThread(() -> { if (!((Activity) context).isFinishing()) { @@ -6222,7 +6237,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter }); } }); - progressDialog.setOnCancelListener(dialog -> ConnectionsManager.getInstance(currentAccount).cancelRequest(reqId, true)); + progressDialog.setOnCancelListener(dialog -> getConnectionsManager().cancelRequest(reqId, true)); try { progressDialog.show(); } catch (Exception e) { @@ -6237,7 +6252,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter final TLRPC.TL_channels_inviteToChannel req = new TLRPC.TL_channels_inviteToChannel(); req.channel = getInputChannel(chat_id); req.users = users; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null) { AndroidUtilities.runOnUIThread(() -> AlertsCreator.processError(currentAccount, error, fragment, req, true)); return; @@ -6250,10 +6265,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.TL_channels_toggleSignatures req = new TLRPC.TL_channels_toggleSignatures(); req.channel = getInputChannel(chat_id); req.enabled = enabled; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { processUpdates((TLRPC.Updates) response, false); - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT)); } }, ConnectionsManager.RequestFlagInvokeAfter); } @@ -6262,10 +6277,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.TL_channels_togglePreHistoryHidden req = new TLRPC.TL_channels_togglePreHistoryHidden(); req.channel = getInputChannel(chat_id); req.enabled = enabled; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { processUpdates((TLRPC.Updates) response, false); - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT)); } }, ConnectionsManager.RequestFlagInvokeAfter); } @@ -6277,12 +6292,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.TL_messages_editChatAbout req = new TLRPC.TL_messages_editChatAbout(); req.peer = getInputPeer(-chat_id); req.about = about; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response instanceof TLRPC.TL_boolTrue) { AndroidUtilities.runOnUIThread(() -> { info.about = about; - MessagesStorage.getInstance(currentAccount).updateChatInfo(info, false); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatInfoDidLoad, info, 0, false, null); + getMessagesStorage().updateChatInfo(info, false); + getNotificationCenter().postNotificationName(NotificationCenter.chatInfoDidLoad, info, 0, false, null); }); } }, ConnectionsManager.RequestFlagInvokeAfter); @@ -6292,7 +6307,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.TL_channels_updateUsername req = new TLRPC.TL_channels_updateUsername(); req.channel = getInputChannel(chat_id); req.username = userName; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response instanceof TLRPC.TL_boolTrue) { AndroidUtilities.runOnUIThread(() -> { TLRPC.Chat chat = getChat(chat_id); @@ -6304,8 +6319,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter chat.username = userName; ArrayList arrayList = new ArrayList<>(); arrayList.add(chat); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(null, arrayList, true, true); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT); + getMessagesStorage().putUsersAndChats(null, arrayList, true, true); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT); }); } }, ConnectionsManager.RequestFlagInvokeAfter); @@ -6320,7 +6335,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.peer = getInputPeer(user.id); req.start_param = botHash; req.random_id = Utilities.random.nextLong(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null) { return; } @@ -6380,7 +6395,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter request = req; } - ConnectionsManager.getInstance(currentAccount).sendRequest(request, (response, error) -> { + getConnectionsManager().sendRequest(request, (response, error) -> { if (isChannel && inputUser instanceof TLRPC.TL_inputUserSelf) { AndroidUtilities.runOnUIThread(() -> joiningToChannels.remove((Integer) chat_id)); } @@ -6388,7 +6403,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter AndroidUtilities.runOnUIThread(() -> { AlertsCreator.processError(currentAccount, error, fragment, request, isChannel && !isMegagroup); if (isChannel && inputUser instanceof TLRPC.TL_inputUserSelf) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT); } }); return; @@ -6412,7 +6427,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter AndroidUtilities.runOnUIThread(() -> loadFullChat(chat_id, 0, true), 1000); } if (isChannel && inputUser instanceof TLRPC.TL_inputUserSelf) { - MessagesStorage.getInstance(currentAccount).updateDialogsWithDeletedMessages(new ArrayList<>(), null, true, chat_id); + getMessagesStorage().updateDialogsWithDeletedMessages(new ArrayList<>(), null, true, chat_id); } if (onFinishRunnable != null) { AndroidUtilities.runOnUIThread(onFinishRunnable); @@ -6430,16 +6445,16 @@ public class MessagesController implements NotificationCenter.NotificationCenter chat.participants_count++; ArrayList chatArrayList = new ArrayList<>(); chatArrayList.add(chat); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(null, chatArrayList, true, true); + getMessagesStorage().putUsersAndChats(null, chatArrayList, true, true); TLRPC.TL_chatParticipant newPart = new TLRPC.TL_chatParticipant(); newPart.user_id = user.id; - newPart.inviter_id = UserConfig.getInstance(currentAccount).getClientUserId(); - newPart.date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + newPart.inviter_id = getUserConfig().getClientUserId(); + newPart.date = getConnectionsManager().getCurrentTime(); info.participants.participants.add(0, newPart); - MessagesStorage.getInstance(currentAccount).updateChatInfo(info, true); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatInfoDidLoad, info, 0, false, null); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT_MEMBERS); + getMessagesStorage().updateChatInfo(info, true); + getNotificationCenter().postNotificationName(NotificationCenter.chatInfoDidLoad, info, 0, false, null); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT_MEMBERS); } } } @@ -6493,10 +6508,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.user_id = getInputUser(user); request = req; } - if (user.id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (user.id == getUserConfig().getClientUserId()) { deleteDialog(-chat_id, 0, revoke); } - ConnectionsManager.getInstance(currentAccount).sendRequest(request, (response, error) -> { + getConnectionsManager().sendRequest(request, (response, error) -> { if (error != null) { return; } @@ -6512,7 +6527,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter chat.participants_count--; ArrayList chatArrayList = new ArrayList<>(); chatArrayList.add(chat); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(null, chatArrayList, true, true); + getMessagesStorage().putUsersAndChats(null, chatArrayList, true, true); boolean changed = false; for (int a = 0; a < info.participants.participants.size(); a++) { @@ -6524,10 +6539,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } if (changed) { - MessagesStorage.getInstance(currentAccount).updateChatInfo(info, true); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatInfoDidLoad, info, 0, false, null); + getMessagesStorage().updateChatInfo(info, true); + getNotificationCenter().postNotificationName(NotificationCenter.chatInfoDidLoad, info, 0, false, null); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT_MEMBERS); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT_MEMBERS); } } } @@ -6546,7 +6561,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.title = title; request = req; } - ConnectionsManager.getInstance(currentAccount).sendRequest(request, (response, error) -> { + getConnectionsManager().sendRequest(request, (response, error) -> { if (error != null) { return; } @@ -6557,9 +6572,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter chat.title = title; ArrayList chatArrayList = new ArrayList<>(); chatArrayList.add(chat); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(null, chatArrayList, true, true); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT_NAME); + getMessagesStorage().putUsersAndChats(null, chatArrayList, true, true); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT_NAME); } } @@ -6586,7 +6601,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } request = req; } - ConnectionsManager.getInstance(currentAccount).sendRequest(request, (response, error) -> { + getConnectionsManager().sendRequest(request, (response, error) -> { if (error != null) { return; } @@ -6630,7 +6645,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } public void unregistedPush() { - if (UserConfig.getInstance(currentAccount).registeredForPush && SharedConfig.pushString.length() == 0) { + if (getUserConfig().registeredForPush && SharedConfig.pushString.length() == 0) { TLRPC.TL_account_unregisterDevice req = new TLRPC.TL_account_unregisterDevice(); req.token = SharedConfig.pushString; req.token_type = 2; @@ -6640,7 +6655,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.other_uids.add(userConfig.getClientUserId()); } } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); } @@ -6650,15 +6665,15 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (type == 1) { unregistedPush(); TLRPC.TL_auth_logOut req = new TLRPC.TL_auth_logOut(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> ConnectionsManager.getInstance(currentAccount).cleanup(false)); + getConnectionsManager().sendRequest(req, (response, error) -> getConnectionsManager().cleanup(false)); } else { - ConnectionsManager.getInstance(currentAccount).cleanup(type == 2); + getConnectionsManager().cleanup(type == 2); } - UserConfig.getInstance(currentAccount).clearConfig(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.appDidLogout); - MessagesStorage.getInstance(currentAccount).cleanup(false); + getUserConfig().clearConfig(); + getNotificationCenter().postNotificationName(NotificationCenter.appDidLogout); + getMessagesStorage().cleanup(false); cleanup(); - ContactsController.getInstance(currentAccount).deleteUnknownAppAccounts(); + getContactsController().deleteUnknownAppAccounts(); } public void generateUpdateMessage() { @@ -6667,7 +6682,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } TLRPC.TL_help_getAppChangelog req = new TLRPC.TL_help_getAppChangelog(); req.prev_app_version = SharedConfig.lastUpdateVersion; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { SharedConfig.lastUpdateVersion = BuildVars.BUILD_VERSION_STRING; SharedConfig.saveConfig(); @@ -6679,10 +6694,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter } public void registerForPush(final String regid) { - if (TextUtils.isEmpty(regid) || registeringForPush || UserConfig.getInstance(currentAccount).getClientUserId() == 0) { + if (TextUtils.isEmpty(regid) || registeringForPush || getUserConfig().getClientUserId() == 0) { return; } - if (UserConfig.getInstance(currentAccount).registeredForPush && regid.equals(SharedConfig.pushString)) { + if (getUserConfig().registeredForPush && regid.equals(SharedConfig.pushString)) { return; } registeringForPush = true; @@ -6695,6 +6710,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.TL_account_registerDevice req = new TLRPC.TL_account_registerDevice(); req.token_type = 2; req.token = regid; + req.no_muted = false; req.secret = SharedConfig.pushAuthKey; for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { UserConfig userConfig = UserConfig.getInstance(a); @@ -6706,14 +6722,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response instanceof TLRPC.TL_boolTrue) { if (BuildVars.LOGS_ENABLED) { FileLog.d("account " + currentAccount + " registered for push"); } - UserConfig.getInstance(currentAccount).registeredForPush = true; + getUserConfig().registeredForPush = true; SharedConfig.pushString = regid; - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().saveConfig(false); } AndroidUtilities.runOnUIThread(() -> registeringForPush = false); }); @@ -6725,18 +6741,18 @@ public class MessagesController implements NotificationCenter.NotificationCenter } updatingState = true; TLRPC.TL_updates_getState req = new TLRPC.TL_updates_getState(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { updatingState = false; if (error == null) { TLRPC.TL_updates_state res = (TLRPC.TL_updates_state) response; - MessagesStorage.getInstance(currentAccount).setLastDateValue(res.date); - MessagesStorage.getInstance(currentAccount).setLastPtsValue(res.pts); - MessagesStorage.getInstance(currentAccount).setLastSeqValue(res.seq); - MessagesStorage.getInstance(currentAccount).setLastQtsValue(res.qts); + getMessagesStorage().setLastDateValue(res.date); + getMessagesStorage().setLastPtsValue(res.pts); + getMessagesStorage().setLastSeqValue(res.seq); + getMessagesStorage().setLastQtsValue(res.qts); for (int a = 0; a < 3; a++) { processUpdatesQueue(a, 2); } - MessagesStorage.getInstance(currentAccount).saveDiffParams(MessagesStorage.getInstance(currentAccount).getLastSeqValue(), MessagesStorage.getInstance(currentAccount).getLastPtsValue(), MessagesStorage.getInstance(currentAccount).getLastDateValue(), MessagesStorage.getInstance(currentAccount).getLastQtsValue()); + getMessagesStorage().saveDiffParams(getMessagesStorage().getLastSeqValue(), getMessagesStorage().getLastPtsValue(), getMessagesStorage().getLastDateValue(), getMessagesStorage().getLastQtsValue()); } else { if (error.code != 401) { loadCurrentState(); @@ -6777,25 +6793,25 @@ public class MessagesController implements NotificationCenter.NotificationCenter private int isValidUpdate(TLRPC.Updates updates, int type) { if (type == 0) { int seq = getUpdateSeq(updates); - if (MessagesStorage.getInstance(currentAccount).getLastSeqValue() + 1 == seq || MessagesStorage.getInstance(currentAccount).getLastSeqValue() == seq) { + if (getMessagesStorage().getLastSeqValue() + 1 == seq || getMessagesStorage().getLastSeqValue() == seq) { return 0; - } else if (MessagesStorage.getInstance(currentAccount).getLastSeqValue() < seq) { + } else if (getMessagesStorage().getLastSeqValue() < seq) { return 1; } else { return 2; } } else if (type == 1) { - if (updates.pts <= MessagesStorage.getInstance(currentAccount).getLastPtsValue()) { + if (updates.pts <= getMessagesStorage().getLastPtsValue()) { return 2; - } else if (MessagesStorage.getInstance(currentAccount).getLastPtsValue() + updates.pts_count == updates.pts) { + } else if (getMessagesStorage().getLastPtsValue() + updates.pts_count == updates.pts) { return 0; } else { return 1; } } else if (type == 2) { - if (updates.pts <= MessagesStorage.getInstance(currentAccount).getLastQtsValue()) { + if (updates.pts <= getMessagesStorage().getLastQtsValue()) { return 2; - } else if (MessagesStorage.getInstance(currentAccount).getLastQtsValue() + updates.updates.size() == updates.pts) { + } else if (getMessagesStorage().getLastQtsValue() + updates.updates.size() == updates.pts) { return 0; } else { return 1; @@ -6882,11 +6898,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (state == 2) { TLRPC.Updates updates = updatesQueue.get(0); if (type == 0) { - MessagesStorage.getInstance(currentAccount).setLastSeqValue(getUpdateSeq(updates)); + getMessagesStorage().setLastSeqValue(getUpdateSeq(updates)); } else if (type == 1) { - MessagesStorage.getInstance(currentAccount).setLastPtsValue(updates.pts); + getMessagesStorage().setLastPtsValue(updates.pts); } else { - MessagesStorage.getInstance(currentAccount).setLastQtsValue(updates.pts); + getMessagesStorage().setLastQtsValue(updates.pts); } } for (int a = 0; a < updatesQueue.size(); a++) { @@ -6934,7 +6950,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } if (channel.access_hash == 0) { if (taskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(taskId); + getMessagesStorage().removePendingTask(taskId); } return; } @@ -6958,11 +6974,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter } catch (Exception e) { FileLog.e(e); } - newTaskId = MessagesStorage.getInstance(currentAccount).createPendingTask(data); + newTaskId = getMessagesStorage().createPendingTask(data); } else { newTaskId = taskId; } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { TLRPC.TL_messages_peerDialogs res = (TLRPC.TL_messages_peerDialogs) response; if (!res.dialogs.isEmpty() && !res.chats.isEmpty()) { @@ -6976,7 +6992,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } gettingUnknownChannels.delete(channel.id); }); @@ -7039,7 +7055,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else { channelPts = channelsPts.get(channelId); if (channelPts == 0) { - channelPts = MessagesStorage.getInstance(currentAccount).getChannelPtsSync(channelId); + channelPts = getMessagesStorage().getChannelPtsSync(channelId); if (channelPts != 0) { channelsPts.put(channelId, channelPts); } @@ -7055,7 +7071,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (inputChannel == null) { TLRPC.Chat chat = getChat(channelId); if (chat == null) { - chat = MessagesStorage.getInstance(currentAccount).getChatSync(channelId); + chat = getMessagesStorage().getChatSync(channelId); if (chat != null) { putChat(chat, true); } @@ -7064,7 +7080,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } if (inputChannel == null || inputChannel.access_hash == 0) { if (taskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(taskId); + getMessagesStorage().removePendingTask(taskId); } return; } @@ -7080,7 +7096,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } catch (Exception e) { FileLog.e(e); } - newTaskId = MessagesStorage.getInstance(currentAccount).createPendingTask(data); + newTaskId = getMessagesStorage().createPendingTask(data); } else { newTaskId = taskId; } @@ -7095,7 +7111,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (BuildVars.LOGS_ENABLED) { FileLog.d("start getChannelDifference with pts = " + channelPts + " channelId = " + channelId); } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { final TLRPC.updates_ChannelDifference res = (TLRPC.updates_ChannelDifference) response; @@ -7126,17 +7142,17 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, res.chats, true, true); + getMessagesStorage().putUsersAndChats(res.users, res.chats, true, true); AndroidUtilities.runOnUIThread(() -> { putUsers(res.users, false); putChats(res.chats, false); }); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { if (!msgUpdates.isEmpty()) { final SparseArray corrected = new SparseArray<>(); for (TLRPC.TL_updateMessageID update : msgUpdates) { - long[] ids = MessagesStorage.getInstance(currentAccount).updateMessageStateAndId(update.random_id, null, update.id, 0, false, channelId); + long[] ids = getMessagesStorage().updateMessageStateAndId(update.random_id, null, update.id, 0, false, channelId); if (ids != null) { corrected.put(update.id, ids); } @@ -7148,8 +7164,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter int newId = corrected.keyAt(a); long[] ids = corrected.valueAt(a); int oldId = (int) ids[1]; - SendMessagesHelper.getInstance(currentAccount).processSentMessage(oldId); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newId, null, ids[0], 0L, -1); + getSendMessagesHelper().processSentMessage(oldId); + getNotificationCenter().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newId, null, ids[0], 0L, -1); } }); } @@ -7165,13 +7181,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter long dialog_id = -channelId; Integer inboxValue = dialogs_read_inbox_max.get(dialog_id); if (inboxValue == null) { - inboxValue = MessagesStorage.getInstance(currentAccount).getDialogReadMax(false, dialog_id); + inboxValue = getMessagesStorage().getDialogReadMax(false, dialog_id); dialogs_read_inbox_max.put(dialog_id, inboxValue); } Integer outboxValue = dialogs_read_outbox_max.get(dialog_id); if (outboxValue == null) { - outboxValue = MessagesStorage.getInstance(currentAccount).getDialogReadMax(true, dialog_id); + outboxValue = getMessagesStorage().getDialogReadMax(true, dialog_id); dialogs_read_outbox_max.put(dialog_id, outboxValue); } @@ -7202,33 +7218,33 @@ public class MessagesController implements NotificationCenter.NotificationCenter ArrayList value = messages.valueAt(a); updateInterfaceWithMessages(key, value); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); }); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { if (!pushMessages.isEmpty()) { - AndroidUtilities.runOnUIThread(() -> NotificationsController.getInstance(currentAccount).processNewMessages(pushMessages, true, false, null)); + AndroidUtilities.runOnUIThread(() -> getNotificationsController().processNewMessages(pushMessages, true, false, null)); } - MessagesStorage.getInstance(currentAccount).putMessages(res.new_messages, true, false, false, DownloadController.getInstance(currentAccount).getAutodownloadMask()); + getMessagesStorage().putMessages(res.new_messages, true, false, false, getDownloadController().getAutodownloadMask()); }); } if (!res.other_updates.isEmpty()) { - processUpdateArray(res.other_updates, res.users, res.chats, true); + processUpdateArray(res.other_updates, res.users, res.chats, true, 0); } processChannelsUpdatesQueue(channelId, 1); - MessagesStorage.getInstance(currentAccount).saveChannelPts(channelId, res.pts); + getMessagesStorage().saveChannelPts(channelId, res.pts); } else if (res instanceof TLRPC.TL_updates_channelDifferenceTooLong) { long dialog_id = -channelId; Integer inboxValue = dialogs_read_inbox_max.get(dialog_id); if (inboxValue == null) { - inboxValue = MessagesStorage.getInstance(currentAccount).getDialogReadMax(false, dialog_id); + inboxValue = getMessagesStorage().getDialogReadMax(false, dialog_id); dialogs_read_inbox_max.put(dialog_id, inboxValue); } Integer outboxValue = dialogs_read_outbox_max.get(dialog_id); if (outboxValue == null) { - outboxValue = MessagesStorage.getInstance(currentAccount).getDialogReadMax(true, dialog_id); + outboxValue = getMessagesStorage().getDialogReadMax(true, dialog_id); dialogs_read_outbox_max.put(dialog_id, outboxValue); } @@ -7240,7 +7256,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; } } - MessagesStorage.getInstance(currentAccount).overwriteChannel(channelId, (TLRPC.TL_updates_channelDifferenceTooLong) res, newDialogType); + getMessagesStorage().overwriteChannel(channelId, (TLRPC.TL_updates_channelDifferenceTooLong) res, newDialogType); } gettingDifferenceChannels.delete(channelId); channelsPts.put(channelId, res.pts); @@ -7257,7 +7273,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } }); }); @@ -7265,7 +7281,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter AndroidUtilities.runOnUIThread(() -> checkChannelError(error.text, channelId)); gettingDifferenceChannels.delete(channelId); if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } } }); @@ -7274,24 +7290,24 @@ public class MessagesController implements NotificationCenter.NotificationCenter private void checkChannelError(String text, int channelId) { switch (text) { case "CHANNEL_PRIVATE": - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatInfoCantLoad, channelId, 0); + getNotificationCenter().postNotificationName(NotificationCenter.chatInfoCantLoad, channelId, 0); break; case "CHANNEL_PUBLIC_GROUP_NA": - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatInfoCantLoad, channelId, 1); + getNotificationCenter().postNotificationName(NotificationCenter.chatInfoCantLoad, channelId, 1); break; case "USER_BANNED_IN_CHANNEL": - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatInfoCantLoad, channelId, 2); + getNotificationCenter().postNotificationName(NotificationCenter.chatInfoCantLoad, channelId, 2); break; } } public void getDifference() { - getDifference(MessagesStorage.getInstance(currentAccount).getLastPtsValue(), MessagesStorage.getInstance(currentAccount).getLastDateValue(), MessagesStorage.getInstance(currentAccount).getLastQtsValue(), false); + getDifference(getMessagesStorage().getLastPtsValue(), getMessagesStorage().getLastDateValue(), getMessagesStorage().getLastQtsValue(), false); } public void getDifference(int pts, final int date, final int qts, boolean slice) { registerForPush(SharedConfig.pushString); - if (MessagesStorage.getInstance(currentAccount).getLastPtsValue() == 0) { + if (getMessagesStorage().getLastPtsValue() == 0) { loadCurrentState(); return; } @@ -7313,20 +7329,20 @@ public class MessagesController implements NotificationCenter.NotificationCenter getDifferenceFirstSync = false; } if (req.date == 0) { - req.date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + req.date = getConnectionsManager().getCurrentTime(); } if (BuildVars.LOGS_ENABLED) { FileLog.d("start getDifference with date = " + date + " pts = " + pts + " qts = " + qts); } - ConnectionsManager.getInstance(currentAccount).setIsUpdating(true); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().setIsUpdating(true); + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { final TLRPC.updates_Difference res = (TLRPC.updates_Difference) response; if (res instanceof TLRPC.TL_updates_differenceTooLong) { AndroidUtilities.runOnUIThread(() -> { loadedFullUsers.clear(); loadedFullChats.clear(); - resetDialogs(true, MessagesStorage.getInstance(currentAccount).getLastSeqValue(), res.pts, date, qts); + resetDialogs(true, getMessagesStorage().getLastSeqValue(), res.pts, date, qts); }); } else { if (res instanceof TLRPC.TL_updates_differenceSlice) { @@ -7356,7 +7372,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter int channelId = getUpdateChannelId(upd); int channelPts = channelsPts.get(channelId); if (channelPts == 0) { - channelPts = MessagesStorage.getInstance(currentAccount).getChannelPtsSync(channelId); + channelPts = getMessagesStorage().getChannelPtsSync(channelId); if (channelPts != 0) { channelsPts.put(channelId, channelPts); } @@ -7376,13 +7392,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter putChats(res.chats, false); }); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, res.chats, true, false); + getMessagesStorage().getStorageQueue().postRunnable(() -> { + getMessagesStorage().putUsersAndChats(res.users, res.chats, true, false); if (!msgUpdates.isEmpty()) { final SparseArray corrected = new SparseArray<>(); for (int a = 0; a < msgUpdates.size(); a++) { TLRPC.TL_updateMessageID update = msgUpdates.get(a); - long[] ids = MessagesStorage.getInstance(currentAccount).updateMessageStateAndId(update.random_id, null, update.id, 0, false, 0); + long[] ids = getMessagesStorage().updateMessageStateAndId(update.random_id, null, update.id, 0, false, 0); if (ids != null) { corrected.put(update.id, ids); } @@ -7394,8 +7410,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter int newId = corrected.keyAt(a); long[] ids = corrected.valueAt(a); int oldId = (int) ids[1]; - SendMessagesHelper.getInstance(currentAccount).processSentMessage(oldId); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newId, null, ids[0], 0L, -1); + getSendMessagesHelper().processSentMessage(oldId); + getNotificationCenter().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newId, null, ids[0], 0L, -1); } }); } @@ -7406,7 +7422,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter final LongSparseArray> messages = new LongSparseArray<>(); for (int b = 0; b < res.new_encrypted_messages.size(); b++) { TLRPC.EncryptedMessage encryptedMessage = res.new_encrypted_messages.get(b); - ArrayList decryptedMessages = SecretChatHelper.getInstance(currentAccount).decryptMessage(encryptedMessage); + ArrayList decryptedMessages = getSecretChatHelper().decryptMessage(encryptedMessage); if (decryptedMessages != null && !decryptedMessages.isEmpty()) { res.new_messages.addAll(decryptedMessages); } @@ -7415,14 +7431,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter ImageLoader.saveMessagesThumbs(res.new_messages); final ArrayList pushMessages = new ArrayList<>(); - int clientUserId = UserConfig.getInstance(currentAccount).getClientUserId(); + int clientUserId = getUserConfig().getClientUserId(); for (int a = 0; a < res.new_messages.size(); a++) { TLRPC.Message message = res.new_messages.get(a); if (message.dialog_id == 0) { if (message.to_id.chat_id != 0) { message.dialog_id = -message.to_id.chat_id; } else { - if (message.to_id.user_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (message.to_id.user_id == getUserConfig().getClientUserId()) { message.to_id.user_id = message.from_id; } message.dialog_id = message.to_id.user_id; @@ -7444,7 +7460,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter ConcurrentHashMap read_max = message.out ? dialogs_read_outbox_max : dialogs_read_inbox_max; Integer value = read_max.get(message.dialog_id); if (value == null) { - value = MessagesStorage.getInstance(currentAccount).getDialogReadMax(message.out, message.dialog_id); + value = getMessagesStorage().getDialogReadMax(message.out, message.dialog_id); read_max.put(message.dialog_id, value); } message.unread = value < message.id; @@ -7476,55 +7492,55 @@ public class MessagesController implements NotificationCenter.NotificationCenter ArrayList value = messages.valueAt(a); updateInterfaceWithMessages(key, value); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); }); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { if (!pushMessages.isEmpty()) { - AndroidUtilities.runOnUIThread(() -> NotificationsController.getInstance(currentAccount).processNewMessages(pushMessages, !(res instanceof TLRPC.TL_updates_differenceSlice), false, null)); + AndroidUtilities.runOnUIThread(() -> getNotificationsController().processNewMessages(pushMessages, !(res instanceof TLRPC.TL_updates_differenceSlice), false, null)); } - MessagesStorage.getInstance(currentAccount).putMessages(res.new_messages, true, false, false, DownloadController.getInstance(currentAccount).getAutodownloadMask()); + getMessagesStorage().putMessages(res.new_messages, true, false, false, getDownloadController().getAutodownloadMask()); }); - SecretChatHelper.getInstance(currentAccount).processPendingEncMessages(); + getSecretChatHelper().processPendingEncMessages(); } if (!res.other_updates.isEmpty()) { - processUpdateArray(res.other_updates, res.users, res.chats, true); + processUpdateArray(res.other_updates, res.users, res.chats, true, 0); } if (res instanceof TLRPC.TL_updates_difference) { gettingDifference = false; - MessagesStorage.getInstance(currentAccount).setLastSeqValue(res.state.seq); - MessagesStorage.getInstance(currentAccount).setLastDateValue(res.state.date); - MessagesStorage.getInstance(currentAccount).setLastPtsValue(res.state.pts); - MessagesStorage.getInstance(currentAccount).setLastQtsValue(res.state.qts); - ConnectionsManager.getInstance(currentAccount).setIsUpdating(false); + getMessagesStorage().setLastSeqValue(res.state.seq); + getMessagesStorage().setLastDateValue(res.state.date); + getMessagesStorage().setLastPtsValue(res.state.pts); + getMessagesStorage().setLastQtsValue(res.state.qts); + getConnectionsManager().setIsUpdating(false); for (int a = 0; a < 3; a++) { processUpdatesQueue(a, 1); } } else if (res instanceof TLRPC.TL_updates_differenceSlice) { - MessagesStorage.getInstance(currentAccount).setLastDateValue(res.intermediate_state.date); - MessagesStorage.getInstance(currentAccount).setLastPtsValue(res.intermediate_state.pts); - MessagesStorage.getInstance(currentAccount).setLastQtsValue(res.intermediate_state.qts); + getMessagesStorage().setLastDateValue(res.intermediate_state.date); + getMessagesStorage().setLastPtsValue(res.intermediate_state.pts); + getMessagesStorage().setLastQtsValue(res.intermediate_state.qts); } else if (res instanceof TLRPC.TL_updates_differenceEmpty) { gettingDifference = false; - MessagesStorage.getInstance(currentAccount).setLastSeqValue(res.seq); - MessagesStorage.getInstance(currentAccount).setLastDateValue(res.date); - ConnectionsManager.getInstance(currentAccount).setIsUpdating(false); + getMessagesStorage().setLastSeqValue(res.seq); + getMessagesStorage().setLastDateValue(res.date); + getConnectionsManager().setIsUpdating(false); for (int a = 0; a < 3; a++) { processUpdatesQueue(a, 1); } } - MessagesStorage.getInstance(currentAccount).saveDiffParams(MessagesStorage.getInstance(currentAccount).getLastSeqValue(), MessagesStorage.getInstance(currentAccount).getLastPtsValue(), MessagesStorage.getInstance(currentAccount).getLastDateValue(), MessagesStorage.getInstance(currentAccount).getLastQtsValue()); + getMessagesStorage().saveDiffParams(getMessagesStorage().getLastSeqValue(), getMessagesStorage().getLastPtsValue(), getMessagesStorage().getLastDateValue(), getMessagesStorage().getLastQtsValue()); if (BuildVars.LOGS_ENABLED) { - FileLog.d("received difference with date = " + MessagesStorage.getInstance(currentAccount).getLastDateValue() + " pts = " + MessagesStorage.getInstance(currentAccount).getLastPtsValue() + " seq = " + MessagesStorage.getInstance(currentAccount).getLastSeqValue() + " messages = " + res.new_messages.size() + " users = " + res.users.size() + " chats = " + res.chats.size() + " other updates = " + res.other_updates.size()); + FileLog.d("received difference with date = " + getMessagesStorage().getLastDateValue() + " pts = " + getMessagesStorage().getLastPtsValue() + " seq = " + getMessagesStorage().getLastSeqValue() + " messages = " + res.new_messages.size() + " users = " + res.users.size() + " chats = " + res.chats.size() + " other updates = " + res.other_updates.size()); } }); }); } } else { gettingDifference = false; - ConnectionsManager.getInstance(currentAccount).setIsUpdating(false); + getConnectionsManager().setIsUpdating(false); } }); } @@ -7536,8 +7552,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (dialog.unread_count == 0 && !isDialogMuted(did)) { unreadUnmutedDialogs++; } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_READ_DIALOG_MESSAGE); - MessagesStorage.getInstance(currentAccount).setDialogUnread(did, true); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_READ_DIALOG_MESSAGE); + getMessagesStorage().setDialogUnread(did, true); } int lower_id = (int) did; if (lower_id != 0) { @@ -7564,26 +7580,26 @@ public class MessagesController implements NotificationCenter.NotificationCenter } catch (Exception e) { FileLog.e(e); } - newTaskId = MessagesStorage.getInstance(currentAccount).createPendingTask(data); + newTaskId = getMessagesStorage().createPendingTask(data); } else { newTaskId = taskId; } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } }); } } public void loadUnreadDialogs() { - if (loadingUnreadDialogs || UserConfig.getInstance(currentAccount).unreadDialogsLoaded) { + if (loadingUnreadDialogs || getUserConfig().unreadDialogsLoaded) { return; } loadingUnreadDialogs = true; TLRPC.TL_messages_getDialogUnreadMarks req = new TLRPC.TL_messages_getDialogUnreadMarks(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (response != null) { final TLRPC.Vector vector = (TLRPC.Vector) response; for (int a = 0, size = vector.objects.size(); a < size; a++) { @@ -7602,7 +7618,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else { did = 0; } - MessagesStorage.getInstance(currentAccount).setDialogUnread(did, true); + getMessagesStorage().setDialogUnread(did, true); TLRPC.Dialog dialog = dialogs_dict.get(did); if (dialog != null && !dialog.unread_mark) { dialog.unread_mark = true; @@ -7612,9 +7628,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } } - UserConfig.getInstance(currentAccount).unreadDialogsLoaded = true; - UserConfig.getInstance(currentAccount).saveConfig(false); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_READ_DIALOG_MESSAGE); + getUserConfig().unreadDialogsLoaded = true; + getUserConfig().saveConfig(false); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_READ_DIALOG_MESSAGE); loadingUnreadDialogs = false; } })); @@ -7641,7 +7657,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (!dialog.pinned) { break; } - MessagesStorage.getInstance(currentAccount).setDialogPinned(dialog.id, dialog.pinnedNum); + getMessagesStorage().setDialogPinned(dialog.id, dialog.pinnedNum); if ((int) dialog.id != 0) { TLRPC.InputPeer inputPeer = getInputPeer((int) dialogs.get(a).id); TLRPC.TL_inputDialogPeer inputDialogPeer = new TLRPC.TL_inputDialogPeer(); @@ -7663,15 +7679,15 @@ public class MessagesController implements NotificationCenter.NotificationCenter } catch (Exception e) { FileLog.e(e); } - newTaskId = MessagesStorage.getInstance(currentAccount).createPendingTask(data); + newTaskId = getMessagesStorage().createPendingTask(data); } else { req.order = order; newTaskId = taskId; } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } }); } @@ -7705,7 +7721,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (!pin && dialogs.get(dialogs.size() - 1) == dialog && !dialogsEndReached.get(folderId)) { dialogs.remove(dialogs.size() - 1); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); if (lower_id != 0) { if (taskId != -1) { TLRPC.TL_messages_toggleDialogPin req = new TLRPC.TL_messages_toggleDialogPin(); @@ -7732,30 +7748,30 @@ public class MessagesController implements NotificationCenter.NotificationCenter } catch (Exception e) { FileLog.e(e); } - newTaskId = MessagesStorage.getInstance(currentAccount).createPendingTask(data); + newTaskId = getMessagesStorage().createPendingTask(data); } else { newTaskId = taskId; } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } }); } } - MessagesStorage.getInstance(currentAccount).setDialogPinned(did, dialog.pinnedNum); + getMessagesStorage().setDialogPinned(did, dialog.pinnedNum); return true; } public void loadPinnedDialogs(final int folderId, final long newDialogId, final ArrayList order) { - if (loadingPinnedDialogs.indexOfKey(folderId) >= 0 || UserConfig.getInstance(currentAccount).isPinnedDialogsLoaded(folderId)) { + if (loadingPinnedDialogs.indexOfKey(folderId) >= 0 || getUserConfig().isPinnedDialogsLoaded(folderId)) { return; } loadingPinnedDialogs.put(folderId, 1); TLRPC.TL_messages_getPinnedDialogs req = new TLRPC.TL_messages_getPinnedDialogs(); req.folder_id = folderId; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { final TLRPC.TL_messages_peerDialogs res = (TLRPC.TL_messages_peerDialogs) response; ArrayList newPinnedDialogs = new ArrayList<>(res.dialogs); @@ -7831,7 +7847,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter dialogs_read_outbox_max.put(d.id, Math.max(value, d.read_outbox_max_id)); } - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> { loadingPinnedDialogs.delete(folderId); applyDialogsNotificationsSettings(newPinnedDialogs); boolean changed = false; @@ -7878,7 +7894,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (d != null) { d.pinned = true; d.pinnedNum = dialog.pinnedNum; - MessagesStorage.getInstance(currentAccount).setDialogPinned(dialog.id, dialog.pinnedNum); + getMessagesStorage().setDialogPinned(dialog.id, dialog.pinnedNum); } else { added = true; dialogs_dict.put(dialog.id, dialog); @@ -7903,12 +7919,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } sortDialogs(null); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } - MessagesStorage.getInstance(currentAccount).unpinAllDialogsExceptNew(pinnedDialogs, folderId); - MessagesStorage.getInstance(currentAccount).putDialogs(toCache, 1); - UserConfig.getInstance(currentAccount).setPinnedDialogsLoaded(folderId, true); - UserConfig.getInstance(currentAccount).saveConfig(false); + getMessagesStorage().unpinAllDialogsExceptNew(pinnedDialogs, folderId); + getMessagesStorage().putDialogs(toCache, 1); + getUserConfig().setPinnedDialogsLoaded(folderId, true); + getUserConfig().saveConfig(false); })); } }); @@ -7922,19 +7938,19 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.TL_messageService message = new TLRPC.TL_messageService(); message.flags = TLRPC.MESSAGE_FLAG_HAS_FROM_ID; - message.local_id = message.id = UserConfig.getInstance(currentAccount).getNewMessageId(); - message.date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); - message.from_id = UserConfig.getInstance(currentAccount).getClientUserId(); + message.local_id = message.id = getUserConfig().getNewMessageId(); + message.date = getConnectionsManager().getCurrentTime(); + message.from_id = getUserConfig().getClientUserId(); message.to_id = new TLRPC.TL_peerChannel(); message.to_id.channel_id = chat_id; message.dialog_id = -chat_id; message.post = true; message.action = new TLRPC.TL_messageActionChatAddUser(); - message.action.users.add(UserConfig.getInstance(currentAccount).getClientUserId()); + message.action.users.add(getUserConfig().getClientUserId()); if (chat.megagroup) { message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; } - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().saveConfig(false); final ArrayList pushMessages = new ArrayList<>(); final ArrayList messagesArr = new ArrayList<>(); @@ -7943,12 +7959,42 @@ public class MessagesController implements NotificationCenter.NotificationCenter MessageObject obj = new MessageObject(currentAccount, message, true); pushMessages.add(obj); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> NotificationsController.getInstance(currentAccount).processNewMessages(pushMessages, true, false, null))); - MessagesStorage.getInstance(currentAccount).putMessages(messagesArr, true, true, false, 0); + getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> getNotificationsController().processNewMessages(pushMessages, true, false, null))); + getMessagesStorage().putMessages(messagesArr, true, true, false, 0); AndroidUtilities.runOnUIThread(() -> { updateInterfaceWithMessages(-chat_id, pushMessages); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); + }); + } + + protected void deleteMessagesByPush(long dialogId, ArrayList ids, int channelId) { + getMessagesStorage().getStorageQueue().postRunnable(() -> { + AndroidUtilities.runOnUIThread(() -> { + getNotificationCenter().postNotificationName(NotificationCenter.messagesDeleted, ids, channelId); + if (channelId == 0) { + for (int b = 0, size2 = ids.size(); b < size2; b++) { + Integer id = ids.get(b); + MessageObject obj = dialogMessagesByIds.get(id); + if (obj != null) { + obj.deleted = true; + } + } + } else { + MessageObject obj = dialogMessage.get((long) -channelId); + if (obj != null) { + for (int b = 0, size2 = ids.size(); b < size2; b++) { + if (obj.getId() == ids.get(b)) { + obj.deleted = true; + break; + } + } + } + } + }); + getMessagesStorage().deletePushMessages(dialogId, ids); + ArrayList dialogIds = getMessagesStorage().markMessagesAsDeleted(ids, false, channelId); + getMessagesStorage().updateDialogsWithDeletedMessages(ids, dialogIds, false, channelId); }); } @@ -7961,14 +8007,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.TL_channels_getParticipant req = new TLRPC.TL_channels_getParticipant(); req.channel = getInputChannel(chat_id); req.user_id = new TLRPC.TL_inputUserSelf(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { final TLRPC.TL_channels_channelParticipant res = (TLRPC.TL_channels_channelParticipant) response; - if (res != null && res.participant instanceof TLRPC.TL_channelParticipantSelf && res.participant.inviter_id != UserConfig.getInstance(currentAccount).getClientUserId()) { - if (chat.megagroup && MessagesStorage.getInstance(currentAccount).isMigratedChat(chat.id)) { + if (res != null && res.participant instanceof TLRPC.TL_channelParticipantSelf && res.participant.inviter_id != getUserConfig().getClientUserId()) { + if (chat.megagroup && getMessagesStorage().isMigratedChat(chat.id)) { return; } AndroidUtilities.runOnUIThread(() -> putUsers(res.users, false)); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, null, true, true); + getMessagesStorage().putUsersAndChats(res.users, null, true, true); TLRPC.TL_messageService message = new TLRPC.TL_messageService(); message.media_unread = true; @@ -7978,15 +8024,15 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (chat.megagroup) { message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; } - message.local_id = message.id = UserConfig.getInstance(currentAccount).getNewMessageId(); + message.local_id = message.id = getUserConfig().getNewMessageId(); message.date = res.participant.date; message.action = new TLRPC.TL_messageActionChatAddUser(); message.from_id = res.participant.inviter_id; - message.action.users.add(UserConfig.getInstance(currentAccount).getClientUserId()); + message.action.users.add(getUserConfig().getClientUserId()); message.to_id = new TLRPC.TL_peerChannel(); message.to_id.channel_id = chat_id; message.dialog_id = -chat_id; - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().saveConfig(false); final ArrayList pushMessages = new ArrayList<>(); final ArrayList messagesArr = new ArrayList<>(); @@ -8001,12 +8047,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter MessageObject obj = new MessageObject(currentAccount, message, usersDict, true); pushMessages.add(obj); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> NotificationsController.getInstance(currentAccount).processNewMessages(pushMessages, true, false, null))); - MessagesStorage.getInstance(currentAccount).putMessages(messagesArr, true, true, false, 0); + getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> getNotificationsController().processNewMessages(pushMessages, true, false, null))); + getMessagesStorage().putMessages(messagesArr, true, true, false, 0); AndroidUtilities.runOnUIThread(() -> { updateInterfaceWithMessages(-chat_id, pushMessages); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); }); } }); @@ -8139,7 +8185,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (updates instanceof TLRPC.TL_updateShort) { ArrayList arr = new ArrayList<>(); arr.add(updates.update); - processUpdateArray(arr, null, null, false); + processUpdateArray(arr, null, null, false, updates.date); } else if (updates instanceof TLRPC.TL_updateShortChatMessage || updates instanceof TLRPC.TL_updateShortMessage) { final int user_id = updates instanceof TLRPC.TL_updateShortChatMessage ? updates.from_id : updates.user_id; TLRPC.User user = getUser(user_id); @@ -8148,7 +8194,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.Chat channel = null; if (user == null || user.min) { - user = MessagesStorage.getInstance(currentAccount).getUserSync(user_id); + user = getMessagesStorage().getUserSync(user_id); if (user != null && user.min) { user = null; } @@ -8160,7 +8206,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (updates.fwd_from.from_id != 0) { user2 = getUser(updates.fwd_from.from_id); if (user2 == null) { - user2 = MessagesStorage.getInstance(currentAccount).getUserSync(updates.fwd_from.from_id); + user2 = getMessagesStorage().getUserSync(updates.fwd_from.from_id); putUser(user2, true); } needFwdUser = true; @@ -8168,7 +8214,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (updates.fwd_from.channel_id != 0) { channel = getChat(updates.fwd_from.channel_id); if (channel == null) { - channel = MessagesStorage.getInstance(currentAccount).getChatSync(updates.fwd_from.channel_id); + channel = getMessagesStorage().getChatSync(updates.fwd_from.channel_id); putChat(channel, true); } needFwdUser = true; @@ -8179,7 +8225,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (updates.via_bot_id != 0) { user3 = getUser(updates.via_bot_id); if (user3 == null) { - user3 = MessagesStorage.getInstance(currentAccount).getUserSync(updates.via_bot_id); + user3 = getMessagesStorage().getUserSync(updates.via_bot_id); putUser(user3, true); } needBotUser = true; @@ -8191,7 +8237,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else { TLRPC.Chat chat = getChat(updates.chat_id); if (chat == null) { - chat = MessagesStorage.getInstance(currentAccount).getChatSync(updates.chat_id); + chat = getMessagesStorage().getChatSync(updates.chat_id); putChat(chat, true); } missingData = chat == null || user == null || needFwdUser && user2 == null && channel == null || needBotUser && user3 == null; @@ -8203,7 +8249,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter int uid = ((TLRPC.TL_messageEntityMentionName) entity).user_id; TLRPC.User entityUser = getUser(uid); if (entityUser == null || entityUser.min) { - entityUser = MessagesStorage.getInstance(currentAccount).getUserSync(uid); + entityUser = getMessagesStorage().getUserSync(uid); if (entityUser != null && entityUser.min) { entityUser = null; } @@ -8216,18 +8262,18 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } } - if (user != null && user.status != null && user.status.expires <= 0) { - onlinePrivacy.put(user.id, ConnectionsManager.getInstance(currentAccount).getCurrentTime()); + if (user != null && user.status != null && user.status.expires <= 0 && Math.abs(getConnectionsManager().getCurrentTime() - updates.date) < 30) { + onlinePrivacy.put(user.id, updates.date); updateStatus = true; } if (missingData) { needGetDiff = true; } else { - if (MessagesStorage.getInstance(currentAccount).getLastPtsValue() + updates.pts_count == updates.pts) { + if (getMessagesStorage().getLastPtsValue() + updates.pts_count == updates.pts) { TLRPC.TL_message message = new TLRPC.TL_message(); message.id = updates.id; - int clientUserId = UserConfig.getInstance(currentAccount).getClientUserId(); + int clientUserId = getUserConfig().getClientUserId(); if (updates instanceof TLRPC.TL_updateShortMessage) { if (updates.out) { message.from_id = clientUserId; @@ -8260,7 +8306,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter ConcurrentHashMap read_max = message.out ? dialogs_read_outbox_max : dialogs_read_inbox_max; Integer value = read_max.get(message.dialog_id); if (value == null) { - value = MessagesStorage.getInstance(currentAccount).getDialogReadMax(message.out, message.dialog_id); + value = getMessagesStorage().getDialogReadMax(message.out, message.dialog_id); read_max.put(message.dialog_id, value); } message.unread = value < message.id; @@ -8271,7 +8317,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter message.out = true; } - MessagesStorage.getInstance(currentAccount).setLastPtsValue(updates.pts); + getMessagesStorage().setLastPtsValue(updates.pts); final MessageObject obj = new MessageObject(currentAccount, message, createdDialogIds.contains(message.dialog_id)); final ArrayList objArr = new ArrayList<>(); objArr.add(obj); @@ -8284,10 +8330,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter } AndroidUtilities.runOnUIThread(() -> { if (printUpdate) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_USER_PRINT); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_USER_PRINT); } updateInterfaceWithMessages(user_id, objArr); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); }); } else { final boolean printUpdate = updatePrintingUsersWithNewMessages(-updates.chat_id, objArr); @@ -8296,20 +8342,20 @@ public class MessagesController implements NotificationCenter.NotificationCenter } AndroidUtilities.runOnUIThread(() -> { if (printUpdate) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_USER_PRINT); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_USER_PRINT); } updateInterfaceWithMessages(-updates.chat_id, objArr); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); }); } if (!obj.isOut()) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> NotificationsController.getInstance(currentAccount).processNewMessages(objArr, true, false, null))); + getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> getNotificationsController().processNewMessages(objArr, true, false, null))); } - MessagesStorage.getInstance(currentAccount).putMessages(arr, false, true, false, 0); - } else if (MessagesStorage.getInstance(currentAccount).getLastPtsValue() != updates.pts) { + getMessagesStorage().putMessages(arr, false, true, false, 0); + } else if (getMessagesStorage().getLastPtsValue() != updates.pts) { if (BuildVars.LOGS_ENABLED) { - FileLog.d("need get diff short message, pts: " + MessagesStorage.getInstance(currentAccount).getLastPtsValue() + " " + updates.pts + " count = " + updates.pts_count); + FileLog.d("need get diff short message, pts: " + getMessagesStorage().getLastPtsValue() + " " + updates.pts + " count = " + updates.pts_count); } if (gettingDifference || updatesStartWaitTimePts == 0 || Math.abs(System.currentTimeMillis() - updatesStartWaitTimePts) <= 1500) { if (updatesStartWaitTimePts == 0) { @@ -8332,7 +8378,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (chat.min) { TLRPC.Chat existChat = getChat(chat.id); if (existChat == null || existChat.min) { - TLRPC.Chat cacheChat = MessagesStorage.getInstance(currentAccount).getChatSync(updates.chat_id); + TLRPC.Chat cacheChat = getMessagesStorage().getChatSync(updates.chat_id); putChat(cacheChat, true); existChat = cacheChat; } @@ -8372,7 +8418,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } if (!needGetDiff) { - MessagesStorage.getInstance(currentAccount).putUsersAndChats(updates.users, updates.chats, true, true); + getMessagesStorage().putUsersAndChats(updates.users, updates.chats, true, true); Collections.sort(updates.updates, updatesComparator); for (int a = 0; a < updates.updates.size(); a++) { TLRPC.Update update = updates.updates.get(a); @@ -8395,18 +8441,18 @@ public class MessagesController implements NotificationCenter.NotificationCenter break; } } - if (MessagesStorage.getInstance(currentAccount).getLastPtsValue() + updatesNew.pts_count == updatesNew.pts) { - if (!processUpdateArray(updatesNew.updates, updates.users, updates.chats, false)) { + if (getMessagesStorage().getLastPtsValue() + updatesNew.pts_count == updatesNew.pts) { + if (!processUpdateArray(updatesNew.updates, updates.users, updates.chats, false, updates.date)) { if (BuildVars.LOGS_ENABLED) { - FileLog.d("need get diff inner TL_updates, pts: " + MessagesStorage.getInstance(currentAccount).getLastPtsValue() + " " + updates.seq); + FileLog.d("need get diff inner TL_updates, pts: " + getMessagesStorage().getLastPtsValue() + " " + updates.seq); } needGetDiff = true; } else { - MessagesStorage.getInstance(currentAccount).setLastPtsValue(updatesNew.pts); + getMessagesStorage().setLastPtsValue(updatesNew.pts); } - } else if (MessagesStorage.getInstance(currentAccount).getLastPtsValue() != updatesNew.pts) { + } else if (getMessagesStorage().getLastPtsValue() != updatesNew.pts) { if (BuildVars.LOGS_ENABLED) { - FileLog.d(update + " need get diff, pts: " + MessagesStorage.getInstance(currentAccount).getLastPtsValue() + " " + updatesNew.pts + " count = " + updatesNew.pts_count); + FileLog.d(update + " need get diff, pts: " + getMessagesStorage().getLastPtsValue() + " " + updatesNew.pts + " count = " + updatesNew.pts_count); } if (gettingDifference || updatesStartWaitTimePts == 0 || updatesStartWaitTimePts != 0 && Math.abs(System.currentTimeMillis() - updatesStartWaitTimePts) <= 1500) { if (updatesStartWaitTimePts == 0) { @@ -8436,13 +8482,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter break; } } - if (MessagesStorage.getInstance(currentAccount).getLastQtsValue() == 0 || MessagesStorage.getInstance(currentAccount).getLastQtsValue() + updatesNew.updates.size() == updatesNew.pts) { - processUpdateArray(updatesNew.updates, updates.users, updates.chats, false); - MessagesStorage.getInstance(currentAccount).setLastQtsValue(updatesNew.pts); + if (getMessagesStorage().getLastQtsValue() == 0 || getMessagesStorage().getLastQtsValue() + updatesNew.updates.size() == updatesNew.pts) { + processUpdateArray(updatesNew.updates, updates.users, updates.chats, false, updates.date); + getMessagesStorage().setLastQtsValue(updatesNew.pts); needReceivedQueue = true; - } else if (MessagesStorage.getInstance(currentAccount).getLastPtsValue() != updatesNew.pts) { + } else if (getMessagesStorage().getLastPtsValue() != updatesNew.pts) { if (BuildVars.LOGS_ENABLED) { - FileLog.d(update + " need get diff, qts: " + MessagesStorage.getInstance(currentAccount).getLastQtsValue() + " " + updatesNew.pts); + FileLog.d(update + " need get diff, qts: " + getMessagesStorage().getLastQtsValue() + " " + updatesNew.pts); } if (gettingDifference || updatesStartWaitTimeQts == 0 || updatesStartWaitTimeQts != 0 && Math.abs(System.currentTimeMillis() - updatesStartWaitTimeQts) <= 1500) { if (updatesStartWaitTimeQts == 0) { @@ -8461,7 +8507,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter boolean skipUpdate = false; int channelPts = channelsPts.get(channelId); if (channelPts == 0) { - channelPts = MessagesStorage.getInstance(currentAccount).getChannelPtsSync(channelId); + channelPts = getMessagesStorage().getChannelPtsSync(channelId); if (channelPts == 0) { for (int c = 0; c < updates.chats.size(); c++) { TLRPC.Chat chat = updates.chats.get(c); @@ -8495,7 +8541,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } if (!skipUpdate) { if (channelPts + updatesNew.pts_count == updatesNew.pts) { - if (!processUpdateArray(updatesNew.updates, updates.users, updates.chats, false)) { + if (!processUpdateArray(updatesNew.updates, updates.users, updates.chats, false, updates.date)) { if (BuildVars.LOGS_ENABLED) { FileLog.d("need get channel diff inner TL_updates, channel_id = " + channelId); } @@ -8506,7 +8552,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } else { channelsPts.put(channelId, updatesNew.pts); - MessagesStorage.getInstance(currentAccount).saveChannelPts(channelId, updatesNew.pts); + getMessagesStorage().saveChannelPts(channelId, updatesNew.pts); } } else if (channelPts != updatesNew.pts) { if (BuildVars.LOGS_ENABLED) { @@ -8549,24 +8595,24 @@ public class MessagesController implements NotificationCenter.NotificationCenter boolean processUpdate; if (updates instanceof TLRPC.TL_updatesCombined) { - processUpdate = MessagesStorage.getInstance(currentAccount).getLastSeqValue() + 1 == updates.seq_start || MessagesStorage.getInstance(currentAccount).getLastSeqValue() == updates.seq_start; + processUpdate = getMessagesStorage().getLastSeqValue() + 1 == updates.seq_start || getMessagesStorage().getLastSeqValue() == updates.seq_start; } else { - processUpdate = MessagesStorage.getInstance(currentAccount).getLastSeqValue() + 1 == updates.seq || updates.seq == 0 || updates.seq == MessagesStorage.getInstance(currentAccount).getLastSeqValue(); + processUpdate = getMessagesStorage().getLastSeqValue() + 1 == updates.seq || updates.seq == 0 || updates.seq == getMessagesStorage().getLastSeqValue(); } if (processUpdate) { - processUpdateArray(updates.updates, updates.users, updates.chats, false); + processUpdateArray(updates.updates, updates.users, updates.chats, false, updates.date); if (updates.seq != 0) { if (updates.date != 0) { - MessagesStorage.getInstance(currentAccount).setLastDateValue(updates.date); + getMessagesStorage().setLastDateValue(updates.date); } - MessagesStorage.getInstance(currentAccount).setLastSeqValue(updates.seq); + getMessagesStorage().setLastSeqValue(updates.seq); } } else { if (BuildVars.LOGS_ENABLED) { if (updates instanceof TLRPC.TL_updatesCombined) { - FileLog.d("need get diff TL_updatesCombined, seq: " + MessagesStorage.getInstance(currentAccount).getLastSeqValue() + " " + updates.seq_start); + FileLog.d("need get diff TL_updatesCombined, seq: " + getMessagesStorage().getLastSeqValue() + " " + updates.seq_start); } else { - FileLog.d("need get diff TL_updates, seq: " + MessagesStorage.getInstance(currentAccount).getLastSeqValue() + " " + updates.seq); + FileLog.d("need get diff TL_updates, seq: " + getMessagesStorage().getLastSeqValue() + " " + updates.seq); } } @@ -8589,16 +8635,16 @@ public class MessagesController implements NotificationCenter.NotificationCenter } needGetDiff = true; } else if (updates instanceof UserActionUpdatesSeq) { - MessagesStorage.getInstance(currentAccount).setLastSeqValue(updates.seq); + getMessagesStorage().setLastSeqValue(updates.seq); } else if (updates instanceof UserActionUpdatesPts) { if (updates.chat_id != 0) { channelsPts.put(updates.chat_id, updates.pts); - MessagesStorage.getInstance(currentAccount).saveChannelPts(updates.chat_id, updates.pts); + getMessagesStorage().saveChannelPts(updates.chat_id, updates.pts); } else { - MessagesStorage.getInstance(currentAccount).setLastPtsValue(updates.pts); + getMessagesStorage().setLastPtsValue(updates.pts); } } - SecretChatHelper.getInstance(currentAccount).processPendingEncMessages(); + getSecretChatHelper().processPendingEncMessages(); if (!fromQueue) { for (int a = 0; a < updatesQueueChannels.size(); a++) { int key = updatesQueueChannels.keyAt(a); @@ -8618,18 +8664,18 @@ public class MessagesController implements NotificationCenter.NotificationCenter } if (needReceivedQueue) { TLRPC.TL_messages_receivedQueue req = new TLRPC.TL_messages_receivedQueue(); - req.max_qts = MessagesStorage.getInstance(currentAccount).getLastQtsValue(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + req.max_qts = getMessagesStorage().getLastQtsValue(); + getConnectionsManager().sendRequest(req, (response, error) -> { }); } if (updateStatus) { - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_STATUS)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_STATUS)); } - MessagesStorage.getInstance(currentAccount).saveDiffParams(MessagesStorage.getInstance(currentAccount).getLastSeqValue(), MessagesStorage.getInstance(currentAccount).getLastPtsValue(), MessagesStorage.getInstance(currentAccount).getLastDateValue(), MessagesStorage.getInstance(currentAccount).getLastQtsValue()); + getMessagesStorage().saveDiffParams(getMessagesStorage().getLastSeqValue(), getMessagesStorage().getLastPtsValue(), getMessagesStorage().getLastDateValue(), getMessagesStorage().getLastQtsValue()); } - public boolean processUpdateArray(ArrayList updates, final ArrayList usersArr, final ArrayList chatsArr, boolean fromGetDifference) { + public boolean processUpdateArray(ArrayList updates, final ArrayList usersArr, final ArrayList chatsArr, boolean fromGetDifference, int date) { if (updates.isEmpty()) { if (usersArr != null || chatsArr != null) { AndroidUtilities.runOnUIThread(() -> { @@ -8709,7 +8755,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (BuildVars.LOGS_ENABLED) { FileLog.d(baseUpdate + " channelId = " + message.to_id.channel_id); } - if (!message.out && message.from_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (!message.out && message.from_id == getUserConfig().getClientUserId()) { message.out = true; } } @@ -8729,7 +8775,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter chat = getChat(chat_id); } if (chat == null) { - chat = MessagesStorage.getInstance(currentAccount).getChatSync(chat_id); + chat = getMessagesStorage().getChatSync(chat_id); putChat(chat, true); } } @@ -8765,7 +8811,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter user = getUser(user_id); } if (user == null || !allowMin && user.min) { - user = MessagesStorage.getInstance(currentAccount).getUserSync(user_id); + user = getMessagesStorage().getUserSync(user_id); if (user != null && !allowMin && user.min) { user = null; } @@ -8777,8 +8823,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter } return false; } - if (a == 1 && user.status != null && user.status.expires <= 0) { - onlinePrivacy.put(user_id, ConnectionsManager.getInstance(currentAccount).getCurrentTime()); + if (a == 1 && user.status != null && user.status.expires <= 0 && Math.abs(getConnectionsManager().getCurrentTime() - message.date) < 30) { + onlinePrivacy.put(user_id, message.date); interfaceUpdateMask |= UPDATE_MASK_STATUS; } } @@ -8793,7 +8839,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (user != null && user.bot) { message.reply_markup = new TLRPC.TL_replyKeyboardHide(); message.flags |= 64; - } else if (message.from_id == UserConfig.getInstance(currentAccount).getClientUserId() && message.action.user_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + } else if (message.from_id == getUserConfig().getClientUserId() && message.action.user_id == getUserConfig().getClientUserId()) { continue; } } @@ -8803,7 +8849,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } messagesArr.add(message); ImageLoader.saveMessageThumbs(message); - int clientUserId = UserConfig.getInstance(currentAccount).getClientUserId(); + int clientUserId = getUserConfig().getClientUserId(); if (message.to_id.chat_id != 0) { message.dialog_id = -message.to_id.chat_id; } else if (message.to_id.channel_id != 0) { @@ -8818,7 +8864,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter ConcurrentHashMap read_max = message.out ? dialogs_read_outbox_max : dialogs_read_inbox_max; Integer value = read_max.get(message.dialog_id); if (value == null) { - value = MessagesStorage.getInstance(currentAccount).getDialogReadMax(message.out, message.dialog_id); + value = getMessagesStorage().getDialogReadMax(message.out, message.dialog_id); read_max.put(message.dialog_id, value); } message.unread = !(value >= message.id || chat != null && ChatObject.isNotInChat(chat) || message.action instanceof TLRPC.TL_messageActionChatMigrateTo || message.action instanceof TLRPC.TL_messageActionChannelCreate); @@ -8883,7 +8929,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } Integer value = dialogs_read_inbox_max.get(dialog_id); if (value == null) { - value = MessagesStorage.getInstance(currentAccount).getDialogReadMax(false, dialog_id); + value = getMessagesStorage().getDialogReadMax(false, dialog_id); } dialogs_read_inbox_max.put(dialog_id, Math.max(value, update.max_id)); } else if (baseUpdate instanceof TLRPC.TL_updateReadHistoryOutbox) { @@ -8901,7 +8947,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } Integer value = dialogs_read_outbox_max.get(dialog_id); if (value == null) { - value = MessagesStorage.getInstance(currentAccount).getDialogReadMax(true, dialog_id); + value = getMessagesStorage().getDialogReadMax(true, dialog_id); } dialogs_read_outbox_max.put(dialog_id, Math.max(value, update.max_id)); } else if (baseUpdate instanceof TLRPC.TL_updateDeleteMessages) { @@ -8930,7 +8976,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter user_id = update.user_id; action = update.action; } - if (user_id != UserConfig.getInstance(currentAccount).getClientUserId()) { + if (user_id != getUserConfig().getClientUserId()) { long uid = -chat_id; if (uid == 0) { uid = user_id; @@ -8976,7 +9022,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter printChanged = true; } } - onlinePrivacy.put(user_id, ConnectionsManager.getInstance(currentAccount).getCurrentTime()); + if (Math.abs(getConnectionsManager().getCurrentTime() - date) < 30) { + onlinePrivacy.put(user_id, date); + } } } else if (baseUpdate instanceof TLRPC.TL_updateChatParticipants) { TLRPC.TL_updateChatParticipants update = (TLRPC.TL_updateChatParticipants) baseUpdate; @@ -9000,7 +9048,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else if (baseUpdate instanceof TLRPC.TL_updateUserPhoto) { TLRPC.TL_updateUserPhoto update = (TLRPC.TL_updateUserPhoto) baseUpdate; interfaceUpdateMask |= UPDATE_MASK_AVATAR; - MessagesStorage.getInstance(currentAccount).clearUserPhotos(update.user_id); + getMessagesStorage().clearUserPhotos(update.user_id); if (updatesOnMainThread == null) { updatesOnMainThread = new ArrayList<>(); } @@ -9011,30 +9059,39 @@ public class MessagesController implements NotificationCenter.NotificationCenter updatesOnMainThread = new ArrayList<>(); } updatesOnMainThread.add(baseUpdate); - } else if (baseUpdate instanceof TLRPC.TL_updateContactLink) { - TLRPC.TL_updateContactLink update = (TLRPC.TL_updateContactLink) baseUpdate; + } else if (baseUpdate instanceof TLRPC.TL_updatePeerSettings) { + TLRPC.TL_updatePeerSettings update = (TLRPC.TL_updatePeerSettings) baseUpdate; if (contactsIds == null) { contactsIds = new ArrayList<>(); } - if (update.my_link instanceof TLRPC.TL_contactLinkContact) { - int idx = contactsIds.indexOf(-update.user_id); - if (idx != -1) { - contactsIds.remove(idx); - } - if (!contactsIds.contains(update.user_id)) { - contactsIds.add(update.user_id); - } - } else { - int idx = contactsIds.indexOf(update.user_id); - if (idx != -1) { - contactsIds.remove(idx); - } - if (!contactsIds.contains(update.user_id)) { - contactsIds.add(-update.user_id); + if (update.peer instanceof TLRPC.TL_peerUser) { + TLRPC.User user = usersDict.get(update.peer.user_id); + if (user != null) { + if (user.contact) { + int idx = contactsIds.indexOf(-update.peer.user_id); + if (idx != -1) { + contactsIds.remove(idx); + } + if (!contactsIds.contains(update.peer.user_id)) { + contactsIds.add(update.peer.user_id); + } + } else { + int idx = contactsIds.indexOf(update.peer.user_id); + if (idx != -1) { + contactsIds.remove(idx); + } + if (!contactsIds.contains(update.peer.user_id)) { + contactsIds.add(-update.peer.user_id); + } + } } } + if (updatesOnMainThread == null) { + updatesOnMainThread = new ArrayList<>(); + } + updatesOnMainThread.add(baseUpdate); } else if (baseUpdate instanceof TLRPC.TL_updateNewEncryptedMessage) { - ArrayList decryptedMessages = SecretChatHelper.getInstance(currentAccount).decryptMessage(((TLRPC.TL_updateNewEncryptedMessage) baseUpdate).message); + ArrayList decryptedMessages = getSecretChatHelper().decryptMessage(((TLRPC.TL_updateNewEncryptedMessage) baseUpdate).message); if (decryptedMessages != null && !decryptedMessages.isEmpty()) { int cid = ((TLRPC.TL_updateNewEncryptedMessage) baseUpdate).message.chat_id; long uid = ((long) cid) << 32; @@ -9089,7 +9146,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter arr.add(newUser); printChanged = true; } - onlinePrivacy.put(encryptedChat.user_id, ConnectionsManager.getInstance(currentAccount).getCurrentTime()); + if (Math.abs(getConnectionsManager().getCurrentTime() - date) < 30) { + onlinePrivacy.put(encryptedChat.user_id, date); + } } } else if (baseUpdate instanceof TLRPC.TL_updateEncryptedMessagesRead) { TLRPC.TL_updateEncryptedMessagesRead update = (TLRPC.TL_updateEncryptedMessagesRead) baseUpdate; @@ -9103,24 +9162,24 @@ public class MessagesController implements NotificationCenter.NotificationCenter tasks.add((TLRPC.TL_updateEncryptedMessagesRead) baseUpdate); } else if (baseUpdate instanceof TLRPC.TL_updateChatParticipantAdd) { TLRPC.TL_updateChatParticipantAdd update = (TLRPC.TL_updateChatParticipantAdd) baseUpdate; - MessagesStorage.getInstance(currentAccount).updateChatInfo(update.chat_id, update.user_id, 0, update.inviter_id, update.version); + getMessagesStorage().updateChatInfo(update.chat_id, update.user_id, 0, update.inviter_id, update.version); } else if (baseUpdate instanceof TLRPC.TL_updateChatParticipantDelete) { TLRPC.TL_updateChatParticipantDelete update = (TLRPC.TL_updateChatParticipantDelete) baseUpdate; - MessagesStorage.getInstance(currentAccount).updateChatInfo(update.chat_id, update.user_id, 1, 0, update.version); + getMessagesStorage().updateChatInfo(update.chat_id, update.user_id, 1, 0, update.version); } else if (baseUpdate instanceof TLRPC.TL_updateDcOptions || baseUpdate instanceof TLRPC.TL_updateConfig) { - ConnectionsManager.getInstance(currentAccount).updateDcSettings(); + getConnectionsManager().updateDcSettings(); } else if (baseUpdate instanceof TLRPC.TL_updateEncryption) { - SecretChatHelper.getInstance(currentAccount).processUpdateEncryption((TLRPC.TL_updateEncryption) baseUpdate, usersDict); + getSecretChatHelper().processUpdateEncryption((TLRPC.TL_updateEncryption) baseUpdate, usersDict); } else if (baseUpdate instanceof TLRPC.TL_updateUserBlocked) { final TLRPC.TL_updateUserBlocked finalUpdate = (TLRPC.TL_updateUserBlocked) baseUpdate; if (finalUpdate.blocked) { SparseIntArray ids = new SparseIntArray(); ids.put(finalUpdate.user_id, 1); - MessagesStorage.getInstance(currentAccount).putBlockedUsers(ids, false); + getMessagesStorage().putBlockedUsers(ids, false); } else { - MessagesStorage.getInstance(currentAccount).deleteBlockedUser(finalUpdate.user_id); + getMessagesStorage().deleteBlockedUser(finalUpdate.user_id); } - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> { if (finalUpdate.blocked) { if (blockedUsers.indexOfKey(finalUpdate.user_id) < 0) { blockedUsers.put(finalUpdate.user_id, 1); @@ -9128,7 +9187,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else { blockedUsers.delete(finalUpdate.user_id); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.blockedUsersDidLoad); + getNotificationCenter().postNotificationName(NotificationCenter.blockedUsersDidLoad); })); } else if (baseUpdate instanceof TLRPC.TL_updateNotifySettings) { if (updatesOnMainThread == null) { @@ -9138,12 +9197,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else if (baseUpdate instanceof TLRPC.TL_updateServiceNotification) { final TLRPC.TL_updateServiceNotification update = (TLRPC.TL_updateServiceNotification) baseUpdate; if (update.popup && update.message != null && update.message.length() > 0) { - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.needShowAlert, 2, update.message, update.type)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.needShowAlert, 2, update.message, update.type)); } if ((update.flags & 2) != 0) { TLRPC.TL_message newMessage = new TLRPC.TL_message(); - newMessage.local_id = newMessage.id = UserConfig.getInstance(currentAccount).getNewMessageId(); - UserConfig.getInstance(currentAccount).saveConfig(false); + newMessage.local_id = newMessage.id = getUserConfig().getNewMessageId(); + getUserConfig().saveConfig(false); newMessage.unread = true; newMessage.flags = TLRPC.MESSAGE_FLAG_HAS_FROM_ID; if (update.inbox_date != 0) { @@ -9153,7 +9212,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } newMessage.from_id = 777000; newMessage.to_id = new TLRPC.TL_peerUser(); - newMessage.to_id.user_id = UserConfig.getInstance(currentAccount).getClientUserId(); + newMessage.to_id.user_id = getUserConfig().getClientUserId(); newMessage.dialog_id = 777000; if (update.media != null) { newMessage.media = update.media; @@ -9162,6 +9221,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter newMessage.message = update.message; if (update.entities != null) { newMessage.entities = update.entities; + newMessage.flags |= 128; } if (messagesArr == null) { @@ -9200,7 +9260,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter updatesOnMainThread.add(baseUpdate); TLRPC.TL_updateFolderPeers update = (TLRPC.TL_updateFolderPeers) baseUpdate; - MessagesStorage.getInstance(currentAccount).setDialogsFolderId(update.folder_peers, null, 0, 0); + getMessagesStorage().setDialogsFolderId(update.folder_peers, null, 0, 0); } else if (baseUpdate instanceof TLRPC.TL_updatePrivacy) { if (updatesOnMainThread == null) { updatesOnMainThread = new ArrayList<>(); @@ -9225,14 +9285,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter } int channelPts = channelsPts.get(update.channel_id); if (channelPts == 0) { - channelPts = MessagesStorage.getInstance(currentAccount).getChannelPtsSync(update.channel_id); + channelPts = getMessagesStorage().getChannelPtsSync(update.channel_id); if (channelPts == 0) { TLRPC.Chat chat = chatsDict.get(update.channel_id); if (chat == null || chat.min) { chat = getChat(update.channel_id); } if (chat == null || chat.min) { - chat = MessagesStorage.getInstance(currentAccount).getChatSync(update.channel_id); + chat = getMessagesStorage().getChatSync(update.channel_id); putChat(chat, true); } if (chat != null && !chat.min) { @@ -9262,7 +9322,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter markAsReadMessagesInbox.put(-update.channel_id, message_id); Integer value = dialogs_read_inbox_max.get(dialog_id); if (value == null) { - value = MessagesStorage.getInstance(currentAccount).getDialogReadMax(false, dialog_id); + value = getMessagesStorage().getDialogReadMax(false, dialog_id); } dialogs_read_inbox_max.put(dialog_id, Math.max(value, update.max_id)); } else if (baseUpdate instanceof TLRPC.TL_updateReadChannelOutbox) { @@ -9276,7 +9336,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter markAsReadMessagesOutbox.put(-update.channel_id, message_id); Integer value = dialogs_read_outbox_max.get(dialog_id); if (value == null) { - value = MessagesStorage.getInstance(currentAccount).getDialogReadMax(true, dialog_id); + value = getMessagesStorage().getDialogReadMax(true, dialog_id); } dialogs_read_outbox_max.put(dialog_id, Math.max(value, update.max_id)); } else if (baseUpdate instanceof TLRPC.TL_updateDeleteChannelMessages) { @@ -9318,7 +9378,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter array.put(update.id, update.views); } else if (baseUpdate instanceof TLRPC.TL_updateChatParticipantAdmin) { TLRPC.TL_updateChatParticipantAdmin update = (TLRPC.TL_updateChatParticipantAdmin) baseUpdate; - MessagesStorage.getInstance(currentAccount).updateChatInfo(update.chat_id, update.user_id, 2, update.is_admin ? 1 : 0, update.version); + getMessagesStorage().updateChatInfo(update.chat_id, update.user_id, 2, update.is_admin ? 1 : 0, update.version); } else if (baseUpdate instanceof TLRPC.TL_updateChatDefaultBannedRights) { TLRPC.TL_updateChatDefaultBannedRights update = (TLRPC.TL_updateChatDefaultBannedRights) baseUpdate; int chatId; @@ -9327,7 +9387,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else { chatId = update.peer.chat_id; } - MessagesStorage.getInstance(currentAccount).updateChatDefaultBannedRights(chatId, update.default_banned_rights, update.version); + getMessagesStorage().updateChatDefaultBannedRights(chatId, update.default_banned_rights, update.version); if (updatesOnMainThread == null) { updatesOnMainThread = new ArrayList<>(); } @@ -9359,7 +9419,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter updatesOnMainThread.add(baseUpdate); } else if (baseUpdate instanceof TLRPC.TL_updateEditChannelMessage || baseUpdate instanceof TLRPC.TL_updateEditMessage) { TLRPC.Message message; - int clientUserId = UserConfig.getInstance(currentAccount).getClientUserId(); + int clientUserId = getUserConfig().getClientUserId(); if (baseUpdate instanceof TLRPC.TL_updateEditChannelMessage) { message = ((TLRPC.TL_updateEditChannelMessage) baseUpdate).message; TLRPC.Chat chat = chatsDict.get(message.to_id.channel_id); @@ -9367,7 +9427,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter chat = getChat(message.to_id.channel_id); } if (chat == null) { - chat = MessagesStorage.getInstance(currentAccount).getChatSync(message.to_id.channel_id); + chat = getMessagesStorage().getChatSync(message.to_id.channel_id); putChat(chat, true); } if (chat != null && chat.megagroup) { @@ -9381,7 +9441,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter message.out = true; } } - if (!message.out && message.from_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (!message.out && message.from_id == getUserConfig().getClientUserId()) { message.out = true; } if (!fromGetDifference) { @@ -9394,7 +9454,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter user = getUser(user_id); } if (user == null || user.min) { - user = MessagesStorage.getInstance(currentAccount).getUserSync(user_id); + user = getMessagesStorage().getUserSync(user_id); if (user != null && user.min) { user = null; } @@ -9412,7 +9472,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else if (message.to_id.channel_id != 0) { message.dialog_id = -message.to_id.channel_id; } else { - if (message.to_id.user_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (message.to_id.user_id == getUserConfig().getClientUserId()) { message.to_id.user_id = message.from_id; } message.dialog_id = message.to_id.user_id; @@ -9421,7 +9481,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter ConcurrentHashMap read_max = message.out ? dialogs_read_outbox_max : dialogs_read_inbox_max; Integer value = read_max.get(message.dialog_id); if (value == null) { - value = MessagesStorage.getInstance(currentAccount).getDialogReadMax(message.out, message.dialog_id); + value = getMessagesStorage().getDialogReadMax(message.out, message.dialog_id); read_max.put(message.dialog_id, value); } message.unread = value < message.id; @@ -9453,13 +9513,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (BuildVars.LOGS_ENABLED) { FileLog.d(baseUpdate + " channelId = " + update.channel_id); } - MessagesStorage.getInstance(currentAccount).updateChatPinnedMessage(update.channel_id, update.id); + getMessagesStorage().updateChatPinnedMessage(update.channel_id, update.id); } else if (baseUpdate instanceof TLRPC.TL_updateChatPinnedMessage) { TLRPC.TL_updateChatPinnedMessage update = (TLRPC.TL_updateChatPinnedMessage) baseUpdate; - MessagesStorage.getInstance(currentAccount).updateChatPinnedMessage(update.chat_id, update.id); + getMessagesStorage().updateChatPinnedMessage(update.chat_id, update.id); } else if (baseUpdate instanceof TLRPC.TL_updateUserPinnedMessage) { TLRPC.TL_updateUserPinnedMessage update = (TLRPC.TL_updateUserPinnedMessage) baseUpdate; - MessagesStorage.getInstance(currentAccount).updateUserPinnedMessage(update.user_id, update.id); + getMessagesStorage().updateUserPinnedMessage(update.user_id, update.id); } else if (baseUpdate instanceof TLRPC.TL_updateReadFeaturedStickers) { if (updatesOnMainThread == null) { updatesOnMainThread = new ArrayList<>(); @@ -9502,11 +9562,16 @@ public class MessagesController implements NotificationCenter.NotificationCenter updatesOnMainThread.add(baseUpdate); } else if (baseUpdate instanceof TLRPC.TL_updateMessagePoll) { TLRPC.TL_updateMessagePoll update = (TLRPC.TL_updateMessagePoll) baseUpdate; - long time = SendMessagesHelper.getInstance(currentAccount).getVoteSendTime(update.poll_id); + long time = getSendMessagesHelper().getVoteSendTime(update.poll_id); if (Math.abs(SystemClock.uptimeMillis() - time) < 600) { continue; } - MessagesStorage.getInstance(currentAccount).updateMessagePollResults(update.poll_id, update.poll, update.results); + getMessagesStorage().updateMessagePollResults(update.poll_id, update.poll, update.results); + if (updatesOnMainThread == null) { + updatesOnMainThread = new ArrayList<>(); + } + updatesOnMainThread.add(baseUpdate); + } else if (baseUpdate instanceof TLRPC.TL_updatePeerLocated) { if (updatesOnMainThread == null) { updatesOnMainThread = new ArrayList<>(); } @@ -9531,17 +9596,17 @@ public class MessagesController implements NotificationCenter.NotificationCenter final boolean printChangedArg = printChanged; if (contactsIds != null) { - ContactsController.getInstance(currentAccount).processContactsUpdates(contactsIds, usersDict); + getContactsController().processContactsUpdates(contactsIds, usersDict); } if (pushMessages != null) { final ArrayList pushMessagesFinal = pushMessages; - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> NotificationsController.getInstance(currentAccount).processNewMessages(pushMessagesFinal, true, false, null))); + getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> getNotificationsController().processNewMessages(pushMessagesFinal, true, false, null))); } if (messagesArr != null) { - StatsController.getInstance(currentAccount).incrementReceivedItemsCount(ApplicationLoader.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, messagesArr.size()); - MessagesStorage.getInstance(currentAccount).putMessages(messagesArr, true, true, false, DownloadController.getInstance(currentAccount).getAutodownloadMask()); + getStatsController().incrementReceivedItemsCount(ApplicationLoader.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, messagesArr.size()); + getMessagesStorage().putMessages(messagesArr, true, true, false, getDownloadController().getAutodownloadMask()); } if (editingMessages != null) { for (int b = 0, size = editingMessages.size(); b < size; b++) { @@ -9550,12 +9615,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter for (int a = 0, size2 = messageObjects.size(); a < size2; a++) { messagesRes.messages.add(messageObjects.get(a).messageOwner); } - MessagesStorage.getInstance(currentAccount).putMessages(messagesRes, editingMessages.keyAt(b), -2, 0, false); + getMessagesStorage().putMessages(messagesRes, editingMessages.keyAt(b), -2, 0, false); } } if (channelViews != null) { - MessagesStorage.getInstance(currentAccount).putChannelViews(channelViews, true); + getMessagesStorage().putChannelViews(channelViews, true); } final LongSparseArray> editingMessagesFinal = editingMessages; @@ -9578,13 +9643,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (baseUpdate instanceof TLRPC.TL_updatePrivacy) { TLRPC.TL_updatePrivacy update = (TLRPC.TL_updatePrivacy) baseUpdate; if (update.key instanceof TLRPC.TL_privacyKeyStatusTimestamp) { - ContactsController.getInstance(currentAccount).setPrivacyRules(update.rules, 0); + getContactsController().setPrivacyRules(update.rules, 0); } else if (update.key instanceof TLRPC.TL_privacyKeyChatInvite) { - ContactsController.getInstance(currentAccount).setPrivacyRules(update.rules, 1); + getContactsController().setPrivacyRules(update.rules, 1); } else if (update.key instanceof TLRPC.TL_privacyKeyPhoneCall) { - ContactsController.getInstance(currentAccount).setPrivacyRules(update.rules, 2); + getContactsController().setPrivacyRules(update.rules, 2); } else if (update.key instanceof TLRPC.TL_privacyKeyPhoneP2P) { - ContactsController.getInstance(currentAccount).setPrivacyRules(update.rules, 3); + getContactsController().setPrivacyRules(update.rules, 3); } } else if (baseUpdate instanceof TLRPC.TL_updateUserStatus) { TLRPC.TL_updateUserStatus update = (TLRPC.TL_updateUserStatus) baseUpdate; @@ -9605,8 +9670,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter toDbUser.id = update.user_id; toDbUser.status = update.status; dbUsersStatus.add(toDbUser); - if (update.user_id == UserConfig.getInstance(currentAccount).getClientUserId()) { - NotificationsController.getInstance(currentAccount).setLastOnlineFromOtherDevice(update.status.expires); + if (update.user_id == getUserConfig().getClientUserId()) { + getNotificationsController().setLastOnlineFromOtherDevice(update.status.expires); } } else if (baseUpdate instanceof TLRPC.TL_updateUserName) { TLRPC.TL_updateUserName update = (TLRPC.TL_updateUserName) baseUpdate; @@ -9640,14 +9705,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter did = 0; } if (!pinDialog(did, update.pinned, null, -1)) { - UserConfig.getInstance(currentAccount).setPinnedDialogsLoaded(update.folder_id, false); - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().setPinnedDialogsLoaded(update.folder_id, false); + getUserConfig().saveConfig(false); loadPinnedDialogs(update.folder_id, did, null); } } else if (baseUpdate instanceof TLRPC.TL_updatePinnedDialogs) { TLRPC.TL_updatePinnedDialogs update = (TLRPC.TL_updatePinnedDialogs) baseUpdate; - UserConfig.getInstance(currentAccount).setPinnedDialogsLoaded(update.folder_id, false); - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().setPinnedDialogsLoaded(update.folder_id, false); + getUserConfig().saveConfig(false); ArrayList order; if ((update.flags & 1) != 0) { order = new ArrayList<>(); @@ -9702,7 +9767,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter final TLRPC.User currentUser = getUser(update.user_id); if (currentUser != null) { currentUser.phone = update.phone; - Utilities.phoneBookQueue.postRunnable(() -> ContactsController.getInstance(currentAccount).addContactToPhoneBook(currentUser, true)); + Utilities.phoneBookQueue.postRunnable(() -> getContactsController().addContactToPhoneBook(currentUser, true)); + if (UserObject.isUserSelf(currentUser)) { + getNotificationCenter().postNotificationName(NotificationCenter.mainUserInfoChanged); + } } final TLRPC.User toDbUser = new TLRPC.TL_user(); toDbUser.id = update.user_id; @@ -9714,7 +9782,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (editor == null) { editor = notificationsPreferences.edit(); } - int currentTime1 = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + int currentTime1 = getConnectionsManager().getCurrentTime(); if (update.peer instanceof TLRPC.TL_notifyPeer) { TLRPC.TL_notifyPeer notifyPeer = (TLRPC.TL_notifyPeer) update.peer; long dialog_id; @@ -9750,21 +9818,21 @@ public class MessagesController implements NotificationCenter.NotificationCenter update.notify_settings.mute_until = until; } } - MessagesStorage.getInstance(currentAccount).setDialogFlags(dialog_id, ((long) until << 32) | 1); - NotificationsController.getInstance(currentAccount).removeNotificationsForDialog(dialog_id); + getMessagesStorage().setDialogFlags(dialog_id, ((long) until << 32) | 1); + getNotificationsController().removeNotificationsForDialog(dialog_id); } else { if (dialog != null) { update.notify_settings.mute_until = 0; } editor.putInt("notify2_" + dialog_id, 0); - MessagesStorage.getInstance(currentAccount).setDialogFlags(dialog_id, 0); + getMessagesStorage().setDialogFlags(dialog_id, 0); } } else { if (dialog != null) { update.notify_settings.mute_until = 0; } editor.remove("notify2_" + dialog_id); - MessagesStorage.getInstance(currentAccount).setDialogFlags(dialog_id, 0); + getMessagesStorage().setDialogFlags(dialog_id, 0); } } else if (update.peer instanceof TLRPC.TL_notifyChats) { if ((update.notify_settings.flags & 1) != 0) { @@ -9834,21 +9902,21 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.Chat chat = getChat(chatId); if (chat != null) { chat.default_banned_rights = update.default_banned_rights; - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.channelRightsUpdated, chat)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.channelRightsUpdated, chat)); } } else if (baseUpdate instanceof TLRPC.TL_updateStickerSets) { TLRPC.TL_updateStickerSets update = (TLRPC.TL_updateStickerSets) baseUpdate; - DataQuery.getInstance(currentAccount).loadStickers(DataQuery.TYPE_IMAGE, false, true); + getMediaDataController().loadStickers(MediaDataController.TYPE_IMAGE, false, true); } else if (baseUpdate instanceof TLRPC.TL_updateStickerSetsOrder) { TLRPC.TL_updateStickerSetsOrder update = (TLRPC.TL_updateStickerSetsOrder) baseUpdate; - DataQuery.getInstance(currentAccount).reorderStickers(update.masks ? DataQuery.TYPE_MASK : DataQuery.TYPE_IMAGE, ((TLRPC.TL_updateStickerSetsOrder) baseUpdate).order); + getMediaDataController().reorderStickers(update.masks ? MediaDataController.TYPE_MASK : MediaDataController.TYPE_IMAGE, ((TLRPC.TL_updateStickerSetsOrder) baseUpdate).order); } else if (baseUpdate instanceof TLRPC.TL_updateFavedStickers) { - DataQuery.getInstance(currentAccount).loadRecents(DataQuery.TYPE_FAVE, false, false, true); + getMediaDataController().loadRecents(MediaDataController.TYPE_FAVE, false, false, true); } else if (baseUpdate instanceof TLRPC.TL_updateContactsReset) { - ContactsController.getInstance(currentAccount).forceImportContacts(); + getContactsController().forceImportContacts(); } else if (baseUpdate instanceof TLRPC.TL_updateNewStickerSet) { TLRPC.TL_updateNewStickerSet update = (TLRPC.TL_updateNewStickerSet) baseUpdate; - DataQuery.getInstance(currentAccount).addNewStickerSet(update.stickerset); + getMediaDataController().addNewStickerSet(update.stickerset); } else if (baseUpdate instanceof TLRPC.TL_updateSavedGifs) { SharedPreferences.Editor editor2 = emojiPreferences.edit(); editor2.putLong("lastGifLoadTime", 0).commit(); @@ -9867,9 +9935,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else { did = -peer.chat_id; } - DataQuery.getInstance(currentAccount).saveDraft(did, update.draft, null, true); + getMediaDataController().saveDraft(did, update.draft, null, true); } else if (baseUpdate instanceof TLRPC.TL_updateReadFeaturedStickers) { - DataQuery.getInstance(currentAccount).markFaturedStickersAsRead(false); + getMediaDataController().markFaturedStickersAsRead(false); } else if (baseUpdate instanceof TLRPC.TL_updatePhoneCall) { TLRPC.TL_updatePhoneCall upd = (TLRPC.TL_updatePhoneCall) baseUpdate; TLRPC.PhoneCall call = upd.phone_call; @@ -9879,7 +9947,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter FileLog.d("call id " + call.id); } if (call instanceof TLRPC.TL_phoneCallRequested) { - if (call.date + callRingTimeout / 1000 < ConnectionsManager.getInstance(currentAccount).getCurrentTime()) { + if (call.date + callRingTimeout / 1000 < getConnectionsManager().getCurrentTime()) { if (BuildVars.LOGS_ENABLED) { FileLog.d("ignoring too old call"); } @@ -9900,7 +9968,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter req.peer.access_hash = call.access_hash; req.peer.id = call.id; req.reason = new TLRPC.TL_phoneCallDiscardReasonBusy(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { TLRPC.Updates updates1 = (TLRPC.Updates) response; processUpdates(updates1, false); @@ -9914,7 +9982,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter VoIPService.callIShouldHavePutIntoIntent = call; Intent intent = new Intent(ApplicationLoader.applicationContext, VoIPService.class); intent.putExtra("is_outgoing", false); - intent.putExtra("user_id", call.participant_id == UserConfig.getInstance(currentAccount).getClientUserId() ? call.admin_id : call.participant_id); + intent.putExtra("user_id", call.participant_id == getUserConfig().getClientUserId() ? call.admin_id : call.participant_id); intent.putExtra("account", currentAccount); try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -9952,7 +10020,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } else { did = 0; } - MessagesStorage.getInstance(currentAccount).setDialogUnread(did, update.unread); + getMessagesStorage().setDialogUnread(did, update.unread); TLRPC.Dialog dialog = dialogs_dict.get(did); if (dialog != null && dialog.unread_mark != update.unread) { dialog.unread_mark = update.unread; @@ -9967,23 +10035,32 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } else if (baseUpdate instanceof TLRPC.TL_updateMessagePoll) { TLRPC.TL_updateMessagePoll update = (TLRPC.TL_updateMessagePoll) baseUpdate; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.didUpdatePollResults, update.poll_id, update.poll, update.results); - } else if (baseUpdate instanceof TLRPC.TL_updateGroupCall) { - - } else if (baseUpdate instanceof TLRPC.TL_updateGroupCallParticipant) { - + getNotificationCenter().postNotificationName(NotificationCenter.didUpdatePollResults, update.poll_id, update.poll, update.results); + } else if (baseUpdate instanceof TLRPC.TL_updatePeerSettings) { + TLRPC.TL_updatePeerSettings update = (TLRPC.TL_updatePeerSettings) baseUpdate; + long dialogId; + if (update.peer instanceof TLRPC.TL_peerUser) { + dialogId = update.peer.user_id; + } else if (update.peer instanceof TLRPC.TL_peerChat) { + dialogId = -update.peer.chat_id; + } else { + dialogId = -update.peer.channel_id; + } + savePeerSettings(dialogId, update.settings, true); + } else if (baseUpdate instanceof TLRPC.TL_updatePeerLocated) { + getNotificationCenter().postNotificationName(NotificationCenter.newPeopleNearbyAvailable, baseUpdate); } } if (editor != null) { editor.commit(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.notificationsSettingsUpdated); + getNotificationCenter().postNotificationName(NotificationCenter.notificationsSettingsUpdated); } - MessagesStorage.getInstance(currentAccount).updateUsers(dbUsersStatus, true, true, true); - MessagesStorage.getInstance(currentAccount).updateUsers(dbUsers, false, true, true); + getMessagesStorage().updateUsers(dbUsersStatus, true, true, true); + getMessagesStorage().updateUsers(dbUsers, false, true, true); } if (webPagesFinal != null) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.didReceivedWebpagesInUpdates, webPagesFinal); + getNotificationCenter().postNotificationName(NotificationCenter.didReceivedWebpagesInUpdates, webPagesFinal); for (int b = 0, size = webPagesFinal.size(); b < size; b++) { long key = webPagesFinal.keyAt(b); ArrayList arrayList = reloadingWebpagesPending.get(key); @@ -10005,8 +10082,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter reloadingWebpagesPending.put(webpage.id, arrayList); } if (!arr.isEmpty()) { - MessagesStorage.getInstance(currentAccount).putMessages(arr, true, true, false, DownloadController.getInstance(currentAccount).getAutodownloadMask()); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.replaceMessagesObjects, dialog_id, arrayList); + getMessagesStorage().putMessages(arr, true, true, false, getDownloadController().getAutodownloadMask()); + getNotificationCenter().postNotificationName(NotificationCenter.replaceMessagesObjects, dialog_id, arrayList); } } } @@ -10047,12 +10124,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } } - DataQuery.getInstance(currentAccount).loadReplyMessagesForMessages(arrayList, dialog_id); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.replaceMessagesObjects, dialog_id, arrayList); + getMediaDataController().loadReplyMessagesForMessages(arrayList, dialog_id); + getNotificationCenter().postNotificationName(NotificationCenter.replaceMessagesObjects, dialog_id, arrayList); } } if (updateDialogs) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } if (printChangedArg) { @@ -10065,14 +10142,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (chatInfoToUpdateFinal != null) { for (int a = 0, size = chatInfoToUpdateFinal.size(); a < size; a++) { TLRPC.ChatParticipants info = chatInfoToUpdateFinal.get(a); - MessagesStorage.getInstance(currentAccount).updateChatParticipants(info); + getMessagesStorage().updateChatParticipants(info); } } if (channelViewsFinal != null) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.didUpdatedMessagesViews, channelViewsFinal); + getNotificationCenter().postNotificationName(NotificationCenter.didUpdatedMessagesViews, channelViewsFinal); } if (updateMask != 0) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, updateMask); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, updateMask); } }); @@ -10082,12 +10159,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter final SparseIntArray markAsReadEncryptedFinal = markAsReadEncrypted; final SparseArray> deletedMessagesFinal = deletedMessages; final SparseIntArray clearHistoryMessagesFinal = clearHistoryMessages; - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> { int updateMask = 0; if (markAsReadMessagesInboxFinal != null || markAsReadMessagesOutboxFinal != null) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messagesRead, markAsReadMessagesInboxFinal, markAsReadMessagesOutboxFinal); + getNotificationCenter().postNotificationName(NotificationCenter.messagesRead, markAsReadMessagesInboxFinal, markAsReadMessagesOutboxFinal); if (markAsReadMessagesInboxFinal != null) { - NotificationsController.getInstance(currentAccount).processReadMessages(markAsReadMessagesInboxFinal, 0, 0, 0, false); + getNotificationsController().processReadMessages(markAsReadMessagesInboxFinal, 0, 0, 0, false); SharedPreferences.Editor editor = notificationsPreferences.edit(); for (int b = 0, size = markAsReadMessagesInboxFinal.size(); b < size; b++) { int key = markAsReadMessagesInboxFinal.keyAt(b); @@ -10100,7 +10177,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter updateMask |= UPDATE_MASK_READ_DIALOG_MESSAGE; } } - if (key != UserConfig.getInstance(currentAccount).getClientUserId()) { + if (key != getUserConfig().getClientUserId()) { editor.remove("diditem" + key); editor.remove("diditemo" + key); } @@ -10126,7 +10203,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter for (int a = 0, size = markAsReadEncryptedFinal.size(); a < size; a++) { int key = markAsReadEncryptedFinal.keyAt(a); int value = markAsReadEncryptedFinal.valueAt(a); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messagesReadEncrypted, key, value); + getNotificationCenter().postNotificationName(NotificationCenter.messagesReadEncrypted, key, value); long dialog_id = (long) (key) << 32; TLRPC.Dialog dialog = dialogs_dict.get(dialog_id); if (dialog != null) { @@ -10139,7 +10216,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } if (markAsReadMessagesFinal != null) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messagesReadContent, markAsReadMessagesFinal); + getNotificationCenter().postNotificationName(NotificationCenter.messagesReadContent, markAsReadMessagesFinal); } if (deletedMessagesFinal != null) { for (int a = 0, size = deletedMessagesFinal.size(); a < size; a++) { @@ -10148,7 +10225,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (arrayList == null) { continue; } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messagesDeleted, arrayList, key); + getNotificationCenter().postNotificationName(NotificationCenter.messagesDeleted, arrayList, key); if (key == 0) { for (int b = 0, size2 = arrayList.size(); b < size2; b++) { Integer id = arrayList.get(b); @@ -10169,14 +10246,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } } - NotificationsController.getInstance(currentAccount).removeDeletedMessagesFromNotifications(deletedMessagesFinal); + getNotificationsController().removeDeletedMessagesFromNotifications(deletedMessagesFinal); } if (clearHistoryMessagesFinal != null) { for (int a = 0, size = clearHistoryMessagesFinal.size(); a < size; a++) { int key = clearHistoryMessagesFinal.keyAt(a); int id = clearHistoryMessagesFinal.valueAt(a); long did = (long) -key; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.historyCleared, did, id); + getNotificationCenter().postNotificationName(NotificationCenter.historyCleared, did, id); MessageObject obj = dialogMessage.get(did); if (obj != null) { if (obj.getId() <= id) { @@ -10185,32 +10262,32 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } } - NotificationsController.getInstance(currentAccount).removeDeletedHisoryFromNotifications(clearHistoryMessagesFinal); + getNotificationsController().removeDeletedHisoryFromNotifications(clearHistoryMessagesFinal); } if (updateMask != 0) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, updateMask); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, updateMask); } })); if (webPages != null) { - MessagesStorage.getInstance(currentAccount).putWebPages(webPages); + getMessagesStorage().putWebPages(webPages); } if (markAsReadMessagesInbox != null || markAsReadMessagesOutbox != null || markAsReadEncrypted != null || markAsReadMessages != null) { if (markAsReadMessagesInbox != null || markAsReadMessages != null) { - MessagesStorage.getInstance(currentAccount).updateDialogsWithReadMessages(markAsReadMessagesInbox, markAsReadMessagesOutbox, markAsReadMessages, true); + getMessagesStorage().updateDialogsWithReadMessages(markAsReadMessagesInbox, markAsReadMessagesOutbox, markAsReadMessages, true); } - MessagesStorage.getInstance(currentAccount).markMessagesAsRead(markAsReadMessagesInbox, markAsReadMessagesOutbox, markAsReadEncrypted, true); + getMessagesStorage().markMessagesAsRead(markAsReadMessagesInbox, markAsReadMessagesOutbox, markAsReadEncrypted, true); } if (markAsReadMessages != null) { - MessagesStorage.getInstance(currentAccount).markMessagesContentAsRead(markAsReadMessages, ConnectionsManager.getInstance(currentAccount).getCurrentTime()); + getMessagesStorage().markMessagesContentAsRead(markAsReadMessages, getConnectionsManager().getCurrentTime()); } if (deletedMessages != null) { for (int a = 0, size = deletedMessages.size(); a < size; a++) { final int key = deletedMessages.keyAt(a); final ArrayList arrayList = deletedMessages.valueAt(a); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { - ArrayList dialogIds = MessagesStorage.getInstance(currentAccount).markMessagesAsDeleted(arrayList, false, key); - MessagesStorage.getInstance(currentAccount).updateDialogsWithDeletedMessages(arrayList, dialogIds, false, key); + getMessagesStorage().getStorageQueue().postRunnable(() -> { + ArrayList dialogIds = getMessagesStorage().markMessagesAsDeleted(arrayList, false, key); + getMessagesStorage().updateDialogsWithDeletedMessages(arrayList, dialogIds, false, key); }); } } @@ -10218,16 +10295,16 @@ public class MessagesController implements NotificationCenter.NotificationCenter for (int a = 0, size = clearHistoryMessages.size(); a < size; a++) { final int key = clearHistoryMessages.keyAt(a); final int id = clearHistoryMessages.valueAt(a); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { - ArrayList dialogIds = MessagesStorage.getInstance(currentAccount).markMessagesAsDeleted(key, id, false); - MessagesStorage.getInstance(currentAccount).updateDialogsWithDeletedMessages(new ArrayList<>(), dialogIds, false, key); + getMessagesStorage().getStorageQueue().postRunnable(() -> { + ArrayList dialogIds = getMessagesStorage().markMessagesAsDeleted(key, id, false); + getMessagesStorage().updateDialogsWithDeletedMessages(new ArrayList<>(), dialogIds, false, key); }); } } if (tasks != null) { for (int a = 0, size = tasks.size(); a < size; a++) { TLRPC.TL_updateEncryptedMessagesRead update = tasks.get(a); - MessagesStorage.getInstance(currentAccount).createTaskForSecretChat(update.chat_id, update.max_date, update.date, 1, null); + getMessagesStorage().createTaskForSecretChat(update.chat_id, update.max_date, update.date, 1, null); } } @@ -10237,13 +10314,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter public boolean isDialogMuted(long dialog_id) { int mute_type = notificationsPreferences.getInt("notify2_" + dialog_id, -1); if (mute_type == -1) { - return !NotificationsController.getInstance(currentAccount).isGlobalNotificationsEnabled(dialog_id); + return !getNotificationsController().isGlobalNotificationsEnabled(dialog_id); } if (mute_type == 2) { return true; } else if (mute_type == 3) { int mute_until = notificationsPreferences.getInt("notifyuntil_" + dialog_id, 0); - if (mute_until >= ConnectionsManager.getInstance(currentAccount).getCurrentTime()) { + if (mute_until >= getConnectionsManager().getCurrentTime()) { return true; } } @@ -10314,17 +10391,17 @@ public class MessagesController implements NotificationCenter.NotificationCenter } if (message.isOut() && !message.isSending() && !message.isForwarded()) { if (message.isNewGif()) { - DataQuery.getInstance(currentAccount).addRecentGif(message.messageOwner.media.document, message.messageOwner.date); - } else if (message.isSticker()) { - DataQuery.getInstance(currentAccount).addRecentSticker(DataQuery.TYPE_IMAGE, message, message.messageOwner.media.document, message.messageOwner.date, false); + getMediaDataController().addRecentGif(message.messageOwner.media.document, message.messageOwner.date); + } else if (message.isSticker() || message.isAnimatedSticker()) { + getMediaDataController().addRecentSticker(MediaDataController.TYPE_IMAGE, message, message.messageOwner.media.document, message.messageOwner.date, false); } } if (message.isOut() && message.isSent()) { updateRating = true; } } - DataQuery.getInstance(currentAccount).loadReplyMessagesForMessages(messages, uid); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.didReceiveNewMessages, uid, messages); + getMediaDataController().loadReplyMessagesForMessages(messages, uid); + getNotificationCenter().postNotificationName(NotificationCenter.didReceiveNewMessages, uid, messages); if (lastMessage == null) { return; @@ -10357,8 +10434,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter dialogMessagesByRandomIds.remove(object.messageOwner.random_id); } dialog.top_message = 0; - NotificationsController.getInstance(currentAccount).removeNotificationsForDialog(dialog.id); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.needReloadRecentDialogsSearch); + getNotificationsController().removeNotificationsForDialog(dialog.id); + getNotificationCenter().postNotificationName(NotificationCenter.needReloadRecentDialogsSearch); } return; } @@ -10368,7 +10445,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (dialog == null) { if (!isBroadcast) { TLRPC.Chat chat = getChat(channelId); - if (channelId != 0 && chat == null || chat != null && chat.left) { + if (channelId != 0 && chat == null || chat != null && ChatObject.isNotInChat(chat)) { return; } if (BuildVars.LOGS_ENABLED) { @@ -10395,12 +10472,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter //nextDialogsCacheOffset.put(dialog.folder_id, offset); TLRPC.Dialog dialogFinal = dialog; - MessagesStorage.getInstance(currentAccount).getDialogFolderId(uid, param -> { + getMessagesStorage().getDialogFolderId(uid, param -> { if (param != -1) { if (param != 0) { dialogFinal.folder_id = param; sortDialogs(null); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload, true); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload, true); } } else { int lowerId = (int) uid; @@ -10415,7 +10492,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter dialog.folder_id = 0; dialog.pinned = false; dialog.pinnedNum = 0; - MessagesStorage.getInstance(currentAccount).setDialogsFolderId(null, null, dialog.id, 0); + getMessagesStorage().setDialogsFolderId(null, null, dialog.id, 0); changed = true; } if ((dialog.top_message > 0 && lastMessage.getId() > 0 && lastMessage.getId() > dialog.top_message) || @@ -10446,7 +10523,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } if (updateRating) { - DataQuery.getInstance(currentAccount).increasePeerRaiting(uid); + getMediaDataController().increasePeerRaiting(uid); } } @@ -10462,7 +10539,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter allDialogs.remove(dialog); sortDialogs(null); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload, true); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload, true); } public void removeDialogAction(long did, boolean clean, boolean apply) { @@ -10480,7 +10557,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } if (!apply) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload, true); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload, true); } } @@ -10496,14 +10573,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter dialogsUsersOnly.clear(); dialogsForward.clear(); for (int a = 0; a < dialogsByFolder.size(); a++) { - ArrayList arrayList = dialogsByFolder.get(a); + ArrayList arrayList = dialogsByFolder.valueAt(a); if (arrayList != null) { arrayList.clear(); } } unreadUnmutedDialogs = 0; boolean selfAdded = false; - int selfId = UserConfig.getInstance(currentAccount).getClientUserId(); + int selfId = getUserConfig().getClientUserId(); Collections.sort(allDialogs, dialogComparator); isLeftProxyChannel = true; if (proxyDialog != null && proxyDialog.id < 0) { @@ -10512,7 +10589,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter isLeftProxyChannel = false; } } - boolean countMessages = NotificationsController.getInstance(currentAccount).showBadgeMessages; + boolean countMessages = getNotificationsController().showBadgeMessages; for (int a = 0, N = allDialogs.size(); a < N; a++) { TLRPC.Dialog d = allDialogs.get(a); int high_id = (int) (d.id >> 32); @@ -10544,11 +10621,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter } dialogsCanAddUsers.add(d); dialogsGroupsOnly.add(d); - } else if (lower_id > 0) { + } else if (lower_id > 0 && lower_id != selfId) { dialogsUsersOnly.add(d); } } - if (canAddToForward) { + if (canAddToForward && d.folder_id == 0) { if (lower_id == selfId) { dialogsForward.add(0, d); selfAdded = true; @@ -10573,7 +10650,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter addDialogToItsFolder(-2, proxyDialog, countMessages); } if (!selfAdded) { - TLRPC.User user = UserConfig.getInstance(currentAccount).getCurrentUser(); + TLRPC.User user = getUserConfig().getCurrentUser(); if (user != null) { TLRPC.Dialog dialog = new TLRPC.TL_dialog(); dialog.id = user.id; @@ -10712,7 +10789,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter request.id.add(originalMessage.getId()); req = request; } - final int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + final int reqId = getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { AndroidUtilities.runOnUIThread(() -> { try { @@ -10723,13 +10800,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; putUsers(res.users, false); putChats(res.chats, false); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, res.chats, true, true); + getMessagesStorage().putUsersAndChats(res.users, res.chats, true, true); fragment.presentFragment(new ChatActivity(bundle), true); }); } }); progressDialog.setOnCancelListener(dialog -> { - ConnectionsManager.getInstance(currentAccount).cancelRequest(reqId, true); + getConnectionsManager().cancelRequest(reqId, true); if (fragment != null) { fragment.setVisibleDialog(null); } @@ -10805,7 +10882,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.TL_contacts_resolveUsername req = new TLRPC.TL_contacts_resolveUsername(); req.username = username; - final int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + final int reqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { try { progressDialog[0].dismiss(); } catch (Exception ignored) { @@ -10817,7 +10894,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.TL_contacts_resolvedPeer res = (TLRPC.TL_contacts_resolvedPeer) response; putUsers(res.users, false); putChats(res.chats, false); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, res.chats, false, true); + getMessagesStorage().putUsersAndChats(res.users, res.chats, false, true); if (!res.chats.isEmpty()) { openChatOrProfileWith(null, res.chats.get(0), fragment, 1, false); } else if (!res.users.isEmpty()) { @@ -10837,7 +10914,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (progressDialog[0] == null) { return; } - progressDialog[0].setOnCancelListener(dialog -> ConnectionsManager.getInstance(currentAccount).cancelRequest(reqId, true)); + progressDialog[0].setOnCancelListener(dialog -> getConnectionsManager().cancelRequest(reqId, true)); fragment.showDialog(progressDialog[0]); }, 500); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java index 07ee82a92..b47d06350 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java @@ -19,7 +19,6 @@ import org.telegram.SQLite.SQLiteCursor; import org.telegram.SQLite.SQLiteDatabase; import org.telegram.SQLite.SQLitePreparedStatement; import org.telegram.messenger.support.SparseLongArray; -import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.NativeByteBuffer; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; @@ -35,7 +34,7 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicLong; -public class MessagesStorage { +public class MessagesStorage extends BaseController { public interface IntCallback { void run(int param); @@ -67,7 +66,6 @@ public class MessagesStorage { private CountDownLatch openSync = new CountDownLatch(1); - private int currentAccount; private static volatile MessagesStorage[] Instance = new MessagesStorage[UserConfig.MAX_ACCOUNT_COUNT]; private final static int LAST_DB_VERSION = 60; @@ -163,7 +161,7 @@ public class MessagesStorage { } public MessagesStorage(int instance) { - currentAccount = instance; + super(instance); //storageQueue.setPriority(Thread.MAX_PRIORITY); storageQueue.postRunnable(() -> openDatabase(1)); } @@ -367,10 +365,10 @@ public class MessagesStorage { if (openTries == 2) { cleanupInternal(true); for (int a = 0; a < 2; a++) { - UserConfig.getInstance(currentAccount).setDialogsLoadOffset(a, 0, 0, 0, 0, 0, 0); - UserConfig.getInstance(currentAccount).setTotalDialogsCount(a, 0); + getUserConfig().setDialogsLoadOffset(a, 0, 0, 0, 0, 0, 0); + getUserConfig().setTotalDialogsCount(a, 0); } - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().saveConfig(false); } else { cleanupInternal(false); } @@ -837,7 +835,7 @@ public class MessagesStorage { cleanupInternal(true); openDatabase(1); if (isLogin) { - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).getDifference()); + Utilities.stageQueue.postRunnable(() -> getMessagesController().getDifference()); } }); } @@ -957,14 +955,14 @@ public class MessagesStorage { case 0: { final TLRPC.Chat chat = TLRPC.Chat.TLdeserialize(data, data.readInt32(false), false); if (chat != null) { - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).loadUnknownChannel(chat, taskId)); + Utilities.stageQueue.postRunnable(() -> getMessagesController().loadUnknownChannel(chat, taskId)); } break; } case 1: { final int channelId = data.readInt32(false); final int newDialogType = data.readInt32(false); - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).getChannelDifference(channelId, newDialogType, taskId, null)); + Utilities.stageQueue.postRunnable(() -> getMessagesController().getChannelDifference(channelId, newDialogType, taskId, null)); break; } case 2: @@ -995,28 +993,28 @@ public class MessagesStorage { dialog.folder_id = data.readInt32(false); } final TLRPC.InputPeer peer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); - AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).checkLastDialogMessage(dialog, peer, taskId)); + AndroidUtilities.runOnUIThread(() -> getMessagesController().checkLastDialogMessage(dialog, peer, taskId)); break; } case 3: { long random_id = data.readInt64(false); TLRPC.InputPeer peer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); TLRPC.TL_inputMediaGame game = (TLRPC.TL_inputMediaGame) TLRPC.InputMedia.TLdeserialize(data, data.readInt32(false), false); - SendMessagesHelper.getInstance(currentAccount).sendGame(peer, game, random_id, taskId); + getSendMessagesHelper().sendGame(peer, game, random_id, taskId); break; } case 4: { final long did = data.readInt64(false); final boolean pin = data.readBool(false); final TLRPC.InputPeer peer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); - AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).pinDialog(did, pin, peer, taskId)); + AndroidUtilities.runOnUIThread(() -> getMessagesController().pinDialog(did, pin, peer, taskId)); break; } case 6: { final int channelId = data.readInt32(false); final int newDialogType = data.readInt32(false); final TLRPC.InputChannel inputChannel = TLRPC.InputChannel.TLdeserialize(data, data.readInt32(false), false); - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).getChannelDifference(channelId, newDialogType, taskId, inputChannel)); + Utilities.stageQueue.postRunnable(() -> getMessagesController().getChannelDifference(channelId, newDialogType, taskId, inputChannel)); break; } case 7: { @@ -1030,14 +1028,14 @@ public class MessagesStorage { removePendingTask(taskId); } else { final TLObject finalRequest = request; - AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).deleteMessages(null, null, null, channelId, true, taskId, finalRequest)); + AndroidUtilities.runOnUIThread(() -> getMessagesController().deleteMessages(null, null, null, channelId, true, taskId, finalRequest)); } break; } case 9: { final long did = data.readInt64(false); final TLRPC.InputPeer peer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); - AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).markDialogAsUnread(did, peer, taskId)); + AndroidUtilities.runOnUIThread(() -> getMessagesController().markDialogAsUnread(did, peer, taskId)); break; } case 11: { @@ -1050,7 +1048,7 @@ public class MessagesStorage { } else { inputChannel = null; } - AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).markMessageAsRead(mid, channelId, inputChannel, ttl, taskId)); + AndroidUtilities.runOnUIThread(() -> getMessagesController().markMessageAsRead(mid, channelId, inputChannel, ttl, taskId)); break; } case 12: { @@ -1061,7 +1059,7 @@ public class MessagesStorage { int backgroundColor = data.readInt32(false); float intesity = (float) data.readDouble(false); boolean install = data.readBool(false); - AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).saveWallpaperToServer(null, wallPaperId, accessHash, isBlurred, isMotion, backgroundColor, intesity, install, taskId)); + AndroidUtilities.runOnUIThread(() -> getMessagesController().saveWallpaperToServer(null, wallPaperId, accessHash, isBlurred, isMotion, backgroundColor, intesity, install, taskId)); break; } case 13: { @@ -1071,12 +1069,12 @@ public class MessagesStorage { final int maxIdDelete = data.readInt32(false); final boolean revoke = data.readBool(false); TLRPC.InputPeer inputPeer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); - AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).deleteDialog(did, first, onlyHistory, maxIdDelete, revoke, inputPeer, taskId)); + AndroidUtilities.runOnUIThread(() -> getMessagesController().deleteDialog(did, first, onlyHistory, maxIdDelete, revoke, inputPeer, taskId)); break; } case 15: { TLRPC.InputPeer inputPeer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).loadUnknownDialog(inputPeer, taskId)); + Utilities.stageQueue.postRunnable(() -> getMessagesController().loadUnknownDialog(inputPeer, taskId)); break; } case 16: { @@ -1087,7 +1085,7 @@ public class MessagesStorage { TLRPC.InputDialogPeer inputPeer = TLRPC.InputDialogPeer.TLdeserialize(data, data.readInt32(false), false); peers.add(inputPeer); } - AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).reorderPinnedDialogs(folderId, peers, taskId)); + AndroidUtilities.runOnUIThread(() -> getMessagesController().reorderPinnedDialogs(folderId, peers, taskId)); break; } case 17: { @@ -1098,7 +1096,7 @@ public class MessagesStorage { TLRPC.TL_inputFolderPeer inputPeer = TLRPC.TL_inputFolderPeer.TLdeserialize(data, data.readInt32(false), false); peers.add(inputPeer); } - AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).addDialogToFolder(null, folderId, -1, peers, taskId)); + AndroidUtilities.runOnUIThread(() -> getMessagesController().addDialogToFolder(null, folderId, -1, peers, taskId)); break; } } @@ -1272,13 +1270,13 @@ public class MessagesStorage { getChatsInternal(TextUtils.join(",", chatsToLoad), chats); } AndroidUtilities.runOnUIThread(() -> { - MessagesController.getInstance(currentAccount).putUsers(users, true); - MessagesController.getInstance(currentAccount).putChats(chats, true); - MessagesController.getInstance(currentAccount).putEncryptedChats(encryptedChats, true); + getMessagesController().putUsers(users, true); + getMessagesController().putChats(chats, true); + getMessagesController().putEncryptedChats(encryptedChats, true); for (int a = 0; a < dialogs.size(); a++) { long did = dialogs.keyAt(a); ReadDialog dialog = dialogs.valueAt(a); - MessagesController.getInstance(currentAccount).markDialogAsRead(did, dialog.lastMid, dialog.lastMid, dialog.date, false, dialog.unreadCount, true); + getMessagesController().markDialogAsRead(did, dialog.lastMid, dialog.lastMid, dialog.date, false, dialog.unreadCount, true); } }); } catch (Exception e) { @@ -1298,7 +1296,7 @@ public class MessagesStorage { final LongSparseArray pushDialogs = new LongSparseArray<>(); SQLiteCursor cursor = database.queryFinalized("SELECT d.did, d.unread_count, s.flags FROM dialogs as d LEFT JOIN dialog_settings as s ON d.did = s.did WHERE d.unread_count != 0"); StringBuilder ids = new StringBuilder(); - int currentTime = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + int currentTime = getConnectionsManager().getCurrentTime(); while (cursor.next()) { long flags = cursor.longValue(2); boolean muted = (flags & 1) != 0; @@ -1349,7 +1347,7 @@ public class MessagesStorage { NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); MessageObject.setUnreadFlags(message, cursor.intValue(0)); message.id = cursor.intValue(3); @@ -1377,7 +1375,7 @@ public class MessagesStorage { data = cursor.byteBufferValue(6); if (data != null) { message.replyMessage = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.replyMessage.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.replyMessage.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (message.replyMessage != null) { if (MessageObject.isMegagroup(message)) { @@ -1425,6 +1423,22 @@ public class MessagesStorage { String name = cursor.isNull(6) ? null : cursor.stringValue(6); String userName = cursor.isNull(7) ? null : cursor.stringValue(7); int flags = cursor.intValue(8); + if (message.from_id == 0) { + int lowerId = (int) message.dialog_id; + if (lowerId > 0) { + message.from_id = lowerId; + } + } + int lower_id = (int) message.dialog_id; + if (lower_id > 0) { + if (!usersToLoad.contains(lower_id)) { + usersToLoad.add(lower_id); + } + } else if (lower_id < 0) { + if (!chatsToLoad.contains(-lower_id)) { + chatsToLoad.add(-lower_id); + } + } pushMessages.add(new MessageObject(currentAccount, message, messageText, name, userName, (flags & 1) != 0, (flags & 2) != 0, false)); addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); @@ -1438,7 +1452,7 @@ public class MessagesStorage { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); message.id = cursor.intValue(1); message.date = cursor.intValue(2); @@ -1492,7 +1506,7 @@ public class MessagesStorage { } } Collections.reverse(messages); - AndroidUtilities.runOnUIThread(() -> NotificationsController.getInstance(currentAccount).processLoadedUnreadMessages(pushDialogs, messages, pushMessages, users, chats, encryptedChats)); + AndroidUtilities.runOnUIThread(() -> getNotificationsController().processLoadedUnreadMessages(pushDialogs, messages, pushMessages, users, chats, encryptedChats)); } catch (Exception e) { FileLog.e(e); } @@ -1599,7 +1613,7 @@ public class MessagesStorage { arrayList.add(searchImage); } cursor.dispose();*/ - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.recentImagesDidLoad, type, arrayList)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.recentImagesDidLoad, type, arrayList)); } catch (Throwable e) { FileLog.e(e); } @@ -1722,7 +1736,7 @@ public class MessagesStorage { getUsersInternal(usersToLoad.toString(), users); } - MessagesController.getInstance(currentAccount).processLoadedBlockedUsers(ids, users, true); + getMessagesController().processLoadedBlockedUsers(ids, users, true); } catch (Exception e) { FileLog.e(e); } @@ -1775,7 +1789,7 @@ public class MessagesStorage { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (message != null && message.from_id == uid && message.id != 1) { mids.add(message.id); @@ -1807,12 +1821,12 @@ public class MessagesStorage { FileLog.e(e); } cursor.dispose(); - AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).markChannelDialogMessageAsDeleted(mids, channelId)); + AndroidUtilities.runOnUIThread(() -> getMessagesController().markChannelDialogMessageAsDeleted(mids, channelId)); markMessagesAsDeletedInternal(mids, channelId); updateDialogsWithDeletedMessagesInternal(mids, null, channelId); - FileLoader.getInstance(currentAccount).deleteFiles(filesToDelete, 0); + getFileLoader().deleteFiles(filesToDelete, 0); if (!mids.isEmpty()) { - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messagesDeleted, mids, channelId)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.messagesDeleted, mids, channelId)); } } catch (Exception e) { FileLog.e(e); @@ -1842,7 +1856,7 @@ public class MessagesStorage { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (message != null && message.media != null) { if (message.media instanceof TLRPC.TL_messageMediaPhoto) { @@ -1873,7 +1887,7 @@ public class MessagesStorage { FileLog.e(e); } cursor.dispose(); - FileLoader.getInstance(currentAccount).deleteFiles(filesToDelete, messagesOnly); + getFileLoader().deleteFiles(filesToDelete, messagesOnly); } if (messagesOnly == 0 || messagesOnly == 3) { @@ -1906,7 +1920,7 @@ public class MessagesStorage { NativeByteBuffer data = cursor2.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (message != null) { messageId = message.id; @@ -1924,7 +1938,7 @@ public class MessagesStorage { database.executeFast("DELETE FROM media_counts_v2 WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM media_v2 WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM media_holes_v2 WHERE uid = " + did).stepThis().dispose(); - DataQuery.getInstance(currentAccount).clearBotKeyboard(did, null); + getMediaDataController().clearBotKeyboard(did, null); SQLitePreparedStatement state5 = database.executeFast("REPLACE INTO messages_holes VALUES(?, ?, ?)"); SQLitePreparedStatement state6 = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); @@ -1945,8 +1959,8 @@ public class MessagesStorage { database.executeFast("DELETE FROM media_v2 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(); - DataQuery.getInstance(currentAccount).clearBotKeyboard(did, null); - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.needReloadRecentDialogsSearch)); + getMediaDataController().clearBotKeyboard(did, null); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.needReloadRecentDialogsSearch)); } catch (Exception e) { FileLog.e(e); } @@ -1986,7 +2000,7 @@ public class MessagesStorage { } cursor.dispose(); - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).processLoadedUserPhotos(res, did, count, max_id, true, classGuid)); + Utilities.stageQueue.postRunnable(() -> getMessagesController().processLoadedUserPhotos(res, did, count, max_id, true, classGuid)); } catch (Exception e) { FileLog.e(e); } @@ -2099,8 +2113,8 @@ public class MessagesStorage { putDialogsInternal(dialogsRes, 0); saveDiffParamsInternal(seq, newPts, date, qts); - int totalDialogsLoadCount = UserConfig.getInstance(currentAccount).getTotalDialogsCount(0); - int[] dialogsLoadOffset = UserConfig.getInstance(currentAccount).getDialogLoadOffsets(0); + int totalDialogsLoadCount = getUserConfig().getTotalDialogsCount(0); + int[] dialogsLoadOffset = getUserConfig().getDialogLoadOffsets(0); int dialogsLoadOffsetId; int dialogsLoadOffsetDate; int dialogsLoadOffsetChannelId = 0; @@ -2146,17 +2160,17 @@ public class MessagesStorage { } } for (int a = 0; a < 2; a++) { - UserConfig.getInstance(currentAccount).setDialogsLoadOffset(a, + getUserConfig().setDialogsLoadOffset(a, dialogsLoadOffsetId, dialogsLoadOffsetDate, dialogsLoadOffsetUserId, dialogsLoadOffsetChatId, dialogsLoadOffsetChannelId, dialogsLoadOffsetAccess); - UserConfig.getInstance(currentAccount).setTotalDialogsCount(a, totalDialogsLoadCount); + getUserConfig().setTotalDialogsCount(a, totalDialogsLoadCount); } - UserConfig.getInstance(currentAccount).saveConfig(false); - MessagesController.getInstance(currentAccount).completeDialogsReset(dialogsRes, messagesCount, seq, newPts, date, qts, new_dialogs_dict, new_dialogMessage, lastMessage); + getUserConfig().saveConfig(false); + getMessagesController().completeDialogsReset(dialogsRes, messagesCount, seq, newPts, date, qts, new_dialogs_dict, new_dialogMessage, lastMessage); } catch (Exception e) { FileLog.e(e); } @@ -2202,7 +2216,7 @@ public class MessagesStorage { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (message.media != null) { if (message.media.document != null) { @@ -2270,11 +2284,11 @@ public class MessagesStorage { state.dispose(); AndroidUtilities.runOnUIThread(() -> { for (int a = 0; a < messages.size(); a++) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateMessageMedia, messages.get(a)); + getNotificationCenter().postNotificationName(NotificationCenter.updateMessageMedia, messages.get(a)); } }); } - FileLoader.getInstance(currentAccount).deleteFiles(filesToDelete, 0); + getFileLoader().deleteFiles(filesToDelete, 0); } catch (Exception e) { FileLog.e(e); } @@ -2302,7 +2316,7 @@ public class MessagesStorage { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (message.media instanceof TLRPC.TL_messageMediaPoll) { TLRPC.TL_messageMediaPoll media = (TLRPC.TL_messageMediaPoll) message.media; @@ -2363,7 +2377,7 @@ public class MessagesStorage { arr.add((int) mid); } cursor.dispose(); - MessagesController.getInstance(currentAccount).processLoadedDeleteTask(date, arr, channelId1); + getMessagesController().processLoadedDeleteTask(date, arr, channelId1); } catch (Exception e) { FileLog.e(e); } @@ -2389,7 +2403,7 @@ public class MessagesStorage { database.executeFast(String.format(Locale.US, "UPDATE dialogs SET unread_count_i = %d WHERE did = %d", old_mentions_count, did)).stepThis().dispose(); LongSparseArray sparseArray = new LongSparseArray<>(1); sparseArray.put(did, old_mentions_count); - MessagesController.getInstance(currentAccount).processDialogsUpdateRead(null, sparseArray); + getMessagesController().processDialogsUpdateRead(null, sparseArray); } catch (Exception e) { FileLog.e(e); } @@ -2415,7 +2429,7 @@ public class MessagesStorage { database.executeFast(String.format(Locale.US, "UPDATE dialogs SET unread_count_i = %d WHERE did = %d", count, did)).stepThis().dispose(); LongSparseArray sparseArray = new LongSparseArray<>(1); sparseArray.put(did, count); - MessagesController.getInstance(currentAccount).processDialogsUpdateRead(null, sparseArray); + getMessagesController().processDialogsUpdateRead(null, sparseArray); } catch (Exception e) { FileLog.e(e); } @@ -2440,7 +2454,7 @@ public class MessagesStorage { if (!inner) { markMessagesContentAsRead(midsArray, 0); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messagesReadContent, midsArray); + getNotificationCenter().postNotificationName(NotificationCenter.messagesReadContent, midsArray); }); SQLitePreparedStatement state = database.executeFast("REPLACE INTO enc_tasks_v2 VALUES(?, ?)"); @@ -2456,7 +2470,7 @@ public class MessagesStorage { } state.dispose(); database.executeFast(String.format(Locale.US, "UPDATE messages SET ttl = 0 WHERE mid = %d", mid)).stepThis().dispose(); - MessagesController.getInstance(currentAccount).didAddedNewTask(minDate, messages); + getMessagesController().didAddedNewTask(minDate, messages); } catch (Exception e) { FileLog.e(e); } @@ -2504,7 +2518,7 @@ public class MessagesStorage { if (random_ids != null) { AndroidUtilities.runOnUIThread(() -> { markMessagesContentAsRead(midsArray, 0); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messagesReadContent, midsArray); + getNotificationCenter().postNotificationName(NotificationCenter.messagesReadContent, midsArray); }); } @@ -2524,7 +2538,7 @@ public class MessagesStorage { state.dispose(); database.commitTransaction(); database.executeFast(String.format(Locale.US, "UPDATE messages SET ttl = 0 WHERE mid IN(%s)", mids.toString())).stepThis().dispose(); - MessagesController.getInstance(currentAccount).didAddedNewTask(minDate, messages); + getMessagesController().didAddedNewTask(minDate, messages); } } catch (Exception e) { FileLog.e(e); @@ -2649,9 +2663,9 @@ public class MessagesStorage { database.commitTransaction(); } - MessagesController.getInstance(currentAccount).processDialogsUpdateRead(dialogsToUpdate, dialogsToUpdateMentions); + getMessagesController().processDialogsUpdateRead(dialogsToUpdate, dialogsToUpdateMentions); if (!channelMentionsToReload.isEmpty()) { - MessagesController.getInstance(currentAccount).reloadMentionsCountForChannels(channelMentionsToReload); + getMessagesController().reloadMentionsCountForChannels(channelMentionsToReload); } } catch (Exception e) { FileLog.e(e); @@ -2711,7 +2725,7 @@ public class MessagesStorage { if (info instanceof TLRPC.TL_chatFull) { info.participants = participants; final TLRPC.ChatFull finalInfo = info; - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatInfoDidLoad, finalInfo, 0, false, null)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.chatInfoDidLoad, finalInfo, 0, false, null)); SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_settings_v2 VALUES(?, ?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(info.getObjectSize()); @@ -2739,7 +2753,7 @@ public class MessagesStorage { ids.add(cursor.intValue(0)); } cursor.dispose(); - MessagesController.getInstance(currentAccount).processLoadedChannelAdmins(ids, chatId, true); + getMessagesController().processLoadedChannelAdmins(ids, chatId, true); } catch (Exception e) { FileLog.e(e); } @@ -2804,7 +2818,7 @@ public class MessagesStorage { } storageQueue.postRunnable(() -> { try { - int currentDate = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + int currentDate = getConnectionsManager().getCurrentTime(); if (result instanceof TLRPC.TL_messages_botCallbackAnswer) { currentDate += ((TLRPC.TL_messages_botCallbackAnswer) result).cache_time; } else if (result instanceof TLRPC.TL_messages_botResults) { @@ -2829,7 +2843,7 @@ public class MessagesStorage { if (key == null || requestDelegate == null) { return; } - final int currentDate = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + final int currentDate = getConnectionsManager().getCurrentTime(); storageQueue.postRunnable(() -> { TLObject result = null; try { @@ -2879,12 +2893,12 @@ public class MessagesStorage { } cursor.dispose(); if (info != null && info.pinned_msg_id != 0) { - pinnedMessageObject = DataQuery.getInstance(currentAccount).loadPinnedMessage(user.id, 0, info.pinned_msg_id, false); + pinnedMessageObject = getMediaDataController().loadPinnedMessage(user.id, 0, info.pinned_msg_id, false); } } catch (Exception e) { FileLog.e(e); } finally { - MessagesController.getInstance(currentAccount).processUserInfo(user, info, true, force, pinnedMessageObject, classGuid); + getMessagesController().processUserInfo(user, info, true, force, pinnedMessageObject, classGuid); } }); } @@ -2986,7 +3000,7 @@ public class MessagesStorage { info.flags |= 64; final TLRPC.UserFull finalInfo = info; - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.userInfoDidLoad, userId, finalInfo, null)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.userInfoDidLoad, userId, finalInfo, null)); SQLitePreparedStatement state = database.executeFast("REPLACE INTO user_settings VALUES(?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(info.getObjectSize()); @@ -3044,7 +3058,7 @@ public class MessagesStorage { } final TLRPC.ChatFull finalInfo = info; - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatInfoDidLoad, finalInfo, 0, false, null)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.chatInfoDidLoad, finalInfo, 0, false, null)); SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_settings_v2 VALUES(?, ?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(info.getObjectSize()); @@ -3097,7 +3111,7 @@ public class MessagesStorage { TLRPC.TL_chatParticipant participant = new TLRPC.TL_chatParticipant(); participant.user_id = user_id; participant.inviter_id = invited_id; - participant.date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + participant.date = getConnectionsManager().getCurrentTime(); info.participants.participants.add(participant); } else if (what == 2) { for (int a = 0; a < info.participants.participants.size(); a++) { @@ -3123,7 +3137,7 @@ public class MessagesStorage { info.participants.version = version; final TLRPC.ChatFull finalInfo = info; - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.chatInfoDidLoad, finalInfo, 0, false, null)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.chatInfoDidLoad, finalInfo, 0, false, null)); SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_settings_v2 VALUES(?, ?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(info.getObjectSize()); @@ -3259,12 +3273,12 @@ public class MessagesStorage { countDownLatch.countDown(); } if (info != null && info.pinned_msg_id != 0) { - pinnedMessageObject = DataQuery.getInstance(currentAccount).loadPinnedMessage(-chat_id, info instanceof TLRPC.TL_channelFull ? chat_id : 0, info.pinned_msg_id, false); + pinnedMessageObject = getMediaDataController().loadPinnedMessage(-chat_id, info instanceof TLRPC.TL_channelFull ? chat_id : 0, info.pinned_msg_id, false); } } catch (Exception e) { FileLog.e(e); } finally { - MessagesController.getInstance(currentAccount).processChatInfo(chat_id, info, loadedUsers, true, force, byChannelUsers, pinnedMessageObject); + getMessagesController().processChatInfo(chat_id, info, loadedUsers, true, force, byChannelUsers, pinnedMessageObject); if (countDownLatch != null) { countDownLatch.countDown(); } @@ -3272,7 +3286,8 @@ public class MessagesStorage { }); } - public void processPendingRead(final long dialog_id, final long maxPositiveId, final long maxNegativeId, final int max_date, final boolean isChannel) { + public void processPendingRead(final long dialog_id, final long maxPositiveId, final long maxNegativeId, final boolean isChannel) { + final int maxDate = lastSavedDate; storageQueue.postRunnable(() -> { try { long currentMaxId = 0; @@ -3322,6 +3337,13 @@ public class MessagesStorage { state.bindLong(2, currentMaxId); state.step(); state.dispose(); + + state = database.executeFast("DELETE FROM unread_push_messages WHERE uid = ? AND date <= ?"); + state.requery(); + state.bindLong(1, dialog_id); + state.bindLong(2, maxDate); + state.step(); + state.dispose(); } else { currentMaxId = (int) maxNegativeId; @@ -3343,13 +3365,6 @@ public class MessagesStorage { cursor.dispose(); unreadCount = Math.max(0, unreadCount - updatedCount); } - - state = database.executeFast("DELETE FROM unread_push_messages WHERE uid = ? AND mid >= ?"); - state.requery(); - state.bindLong(1, dialog_id); - state.bindLong(2, currentMaxId); - state.step(); - state.dispose(); } state = database.executeFast("UPDATE dialogs SET unread_count = ?, inbox_max = ? WHERE did = ?"); @@ -3409,7 +3424,7 @@ public class MessagesStorage { } public void applyPhoneBookUpdates(final String adds, final String deletes) { - if (adds.length() == 0 && deletes.length() == 0) { + if (TextUtils.isEmpty(adds)) { return; } storageQueue.postRunnable(() -> { @@ -3532,7 +3547,7 @@ public class MessagesStorage { } cursor.dispose(); cursor = null; - ContactsController.getInstance(currentAccount).migratePhoneBookToV7(contactHashMap); + getContactsController().migratePhoneBookToV7(contactHashMap); return; } } catch (Throwable e) { @@ -3619,7 +3634,7 @@ public class MessagesStorage { cursor.dispose(); } } - ContactsController.getInstance(currentAccount).performSyncPhoneBook(contactHashMap, true, true, false, false, !byError, false); + getContactsController().performSyncPhoneBook(contactHashMap, true, true, false, false, !byError, false); }); } @@ -3651,7 +3666,7 @@ public class MessagesStorage { users.clear(); FileLog.e(e); } - ContactsController.getInstance(currentAccount).processLoadedContacts(contacts, users, 1); + getContactsController().processLoadedContacts(contacts, users, 1); }); } @@ -3674,7 +3689,7 @@ public class MessagesStorage { if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.send_state = cursor.intValue(2); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (messageHashMap.indexOfKey(message.id) < 0) { MessageObject.setUnreadFlags(message, cursor.intValue(0)); @@ -3756,7 +3771,7 @@ public class MessagesStorage { getChatsInternal(stringToLoad.toString(), chats); } - SendMessagesHelper.getInstance(currentAccount).processUnsentMessages(messages, users, chats, encryptedChats); + getSendMessagesHelper().processUnsentMessages(messages, users, chats, encryptedChats); } catch (Exception e) { FileLog.e(e); } @@ -3855,7 +3870,7 @@ public class MessagesStorage { public void getMessages(final long dialog_id, final int count, final int max_id, final int offset_date, final int minDate, final int classGuid, final int load_type, final boolean isChannel, final int loadIndex) { storageQueue.postRunnable(() -> { - int currentUserId = UserConfig.getInstance(currentAccount).clientUserId; + int currentUserId = getUserConfig().clientUserId; TLRPC.TL_messages_messages res = new TLRPC.TL_messages_messages(); int count_unread = 0; int mentions_unread = 0; @@ -4466,7 +4481,7 @@ public class MessagesStorage { res.users.clear(); FileLog.e(e); } finally { - MessagesController.getInstance(currentAccount).processLoadedMessages(res, dialog_id, count_query, max_id_override, offset_date, true, classGuid, min_unread_id, last_message_id, count_unread, max_unread_date, load_type, isChannel, isEnd, loadIndex, queryFromServer, mentions_unread); + getMessagesController().processLoadedMessages(res, dialog_id, count_query, max_id_override, offset_date, true, classGuid, min_unread_id, last_message_id, count_unread, max_unread_date, load_type, isChannel, isEnd, loadIndex, queryFromServer, mentions_unread); } }); } @@ -5216,7 +5231,7 @@ public class MessagesStorage { } cursor.dispose(); - AndroidUtilities.runOnUIThread(() -> DownloadController.getInstance(currentAccount).processDownloadObjects(type, objects)); + AndroidUtilities.runOnUIThread(() -> getDownloadController().processDownloadObjects(type, objects)); } catch (Exception e) { FileLog.e(e); } @@ -5265,7 +5280,7 @@ public class MessagesStorage { NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (message.media instanceof TLRPC.TL_messageMediaWebPage) { message.id = mid; @@ -5314,7 +5329,7 @@ public class MessagesStorage { database.commitTransaction(); - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.didReceivedWebpages, messages)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.didReceivedWebpages, messages)); } catch (Exception e) { FileLog.e(e); } @@ -5345,7 +5360,7 @@ public class MessagesStorage { database.executeFast("DELETE FROM media_v2 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(); - DataQuery.getInstance(currentAccount).clearBotKeyboard(did, null); + getMediaDataController().clearBotKeyboard(did, null); TLRPC.TL_messages_dialogs dialogs = new TLRPC.TL_messages_dialogs(); dialogs.chats.addAll(difference.chats); @@ -5361,12 +5376,12 @@ public class MessagesStorage { putDialogsInternal(dialogs, 0); updateDialogsWithDeletedMessages(new ArrayList<>(), null, false, channel_id); - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.removeAllMessagesFromDialog, did, true)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.removeAllMessagesFromDialog, did, true)); if (checkInvite) { if (newDialogType == 1) { - MessagesController.getInstance(currentAccount).checkChannelInviter(channel_id); + getMessagesController().checkChannelInviter(channel_id); } else { - MessagesController.getInstance(currentAccount).generateJoinMessage(channel_id, false); + getMessagesController().generateJoinMessage(channel_id, false); } } } catch (Exception e) { @@ -5486,7 +5501,7 @@ public class MessagesStorage { messagesIdsMap.put(messageId, message.dialog_id); } } - if (DataQuery.canAddMessageToMedia(message)) { + if (MediaDataController.canAddMessageToMedia(message)) { if (messageMediaIds == null) { messageMediaIds = new StringBuilder(); messagesMediaIdsMap = new LongSparseArray<>(); @@ -5497,7 +5512,7 @@ public class MessagesStorage { } messageMediaIds.append(messageId); messagesMediaIdsMap.put(messageId, message.dialog_id); - mediaTypes.put(messageId, DataQuery.getMediaType(message)); + mediaTypes.put(messageId, MediaDataController.getMediaType(message)); } if (isValidKeyboardToSave(message)) { TLRPC.Message oldMessage = botKeyboards.get(message.dialog_id); @@ -5508,7 +5523,7 @@ public class MessagesStorage { } for (int a = 0; a < botKeyboards.size(); a++) { - DataQuery.getInstance(currentAccount).putBotKeyboard(botKeyboards.keyAt(a), botKeyboards.valueAt(a)); + getMediaDataController().putBotKeyboard(botKeyboards.keyAt(a), botKeyboards.valueAt(a)); } if (messageMediaIds != null) { @@ -5647,7 +5662,7 @@ public class MessagesStorage { state_randoms.step(); } - if (DataQuery.canAddMessageToMedia(message)) { + if (MediaDataController.canAddMessageToMedia(message)) { if (state_media == null) { state_media = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); } @@ -5655,7 +5670,7 @@ public class MessagesStorage { state_media.bindLong(1, messageId); state_media.bindLong(2, message.dialog_id); state_media.bindInteger(3, message.date); - state_media.bindInteger(4, DataQuery.getMediaType(message)); + state_media.bindInteger(4, MediaDataController.getMediaType(message)); state_media.bindByteBuffer(5, data); state_media.step(); } @@ -5678,7 +5693,7 @@ public class MessagesStorage { data.reuse(); - if (downloadMask != 0 && (message.to_id.channel_id == 0 || message.post) && message.date >= ConnectionsManager.getInstance(currentAccount).getCurrentTime() - 60 * 60 && DownloadController.getInstance(currentAccount).canDownloadMedia(message) == 1) { + if (downloadMask != 0 && (message.to_id.channel_id == 0 || message.post) && message.date >= getConnectionsManager().getCurrentTime() - 60 * 60 && getDownloadController().canDownloadMedia(message) == 1) { if (message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaDocument || message.media instanceof TLRPC.TL_messageMediaWebPage) { int type = 0; long id = 0; @@ -5691,7 +5706,7 @@ public class MessagesStorage { object = new TLRPC.TL_messageMediaDocument(); object.document = document; object.flags |= 1; - } else if (MessageObject.isStickerMessage(message)) { + } else if (MessageObject.isStickerMessage(message) || MessageObject.isAnimatedStickerMessage(message)) { id = document.id; type = DownloadController.AUTODOWNLOAD_TYPE_PHOTO; object = new TLRPC.TL_messageMediaDocument(); @@ -5780,7 +5795,7 @@ public class MessagesStorage { last_mid = cursor.intValue(2); old_mentions_count = Math.max(0, cursor.intValue(3)); } else if (channelId != 0) { - MessagesController.getInstance(currentAccount).checkChannelInviter(channelId); + getMessagesController().checkChannelInviter(channelId); } cursor.dispose(); @@ -5869,11 +5884,11 @@ public class MessagesStorage { if (withTransaction) { database.commitTransaction(); } - MessagesController.getInstance(currentAccount).processDialogsUpdateRead(messagesCounts, mentionCounts); + getMessagesController().processDialogsUpdateRead(messagesCounts, mentionCounts); if (downloadMediaMask != 0) { final int downloadMediaMaskFinal = downloadMediaMask; - AndroidUtilities.runOnUIThread(() -> DownloadController.getInstance(currentAccount).newDownloadObjectsAvailable(downloadMediaMaskFinal)); + AndroidUtilities.runOnUIThread(() -> getDownloadController().newDownloadObjectsAvailable(downloadMediaMaskFinal)); } } catch (Exception e) { FileLog.e(e); @@ -6106,9 +6121,6 @@ public class MessagesStorage { } private void updateUsersInternal(final ArrayList users, final boolean onlyStatus, final boolean withTransaction) { - if (Thread.currentThread().getId() != storageQueue.getId()) { - throw new RuntimeException("wrong db thread"); - } try { if (onlyStatus) { if (withTransaction) { @@ -6277,7 +6289,7 @@ public class MessagesStorage { } cursor.dispose(); if (!mids.isEmpty()) { - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messagesDeleted, mids, 0)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.messagesDeleted, mids, 0)); updateDialogsWithReadMessagesInternal(mids, null, null, null); markMessagesAsDeletedInternal(mids, 0); updateDialogsWithDeletedMessagesInternal(mids, null, 0); @@ -6317,7 +6329,7 @@ public class MessagesStorage { ids = TextUtils.join(",", messages); } ArrayList filesToDelete = new ArrayList<>(); - int currentUser = UserConfig.getInstance(currentAccount).getClientUserId(); + int currentUser = getUserConfig().getClientUserId(); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, data, read_state, out, mention, mid FROM messages WHERE mid IN(%s)", ids)); try { @@ -6348,7 +6360,7 @@ public class MessagesStorage { NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (message != null) { if (message.media instanceof TLRPC.TL_messageMediaPhoto) { @@ -6380,7 +6392,7 @@ public class MessagesStorage { } cursor.dispose(); - FileLoader.getInstance(currentAccount).deleteFiles(filesToDelete, 0); + getFileLoader().deleteFiles(filesToDelete, 0); for (int a = 0; a < dialogsToUpdate.size(); a++) { long did = dialogsToUpdate.keyAt(a); @@ -6471,7 +6483,7 @@ public class MessagesStorage { } } database.executeFast(String.format(Locale.US, "DELETE FROM media_v2 WHERE mid IN(%s)", ids)).stepThis().dispose(); - DataQuery.getInstance(currentAccount).clearBotKeyboard(0, messages); + getMediaDataController().clearBotKeyboard(0, messages); return dialogsIds; } catch (Exception e) { FileLog.e(e); @@ -6480,9 +6492,6 @@ public class MessagesStorage { } private void updateDialogsWithDeletedMessagesInternal(final ArrayList messages, ArrayList additionalDialogsToUpdate, int channelId) { - if (Thread.currentThread().getId() != storageQueue.getId()) { - throw new RuntimeException("wrong db thread"); - } try { String ids; ArrayList dialogsToUpdate = new ArrayList<>(); @@ -6569,7 +6578,7 @@ public class MessagesStorage { NativeByteBuffer data = cursor.byteBufferValue(4); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); MessageObject.setUnreadFlags(message, cursor.intValue(5)); message.id = cursor.intValue(6); @@ -6623,7 +6632,7 @@ public class MessagesStorage { } if (!dialogs.dialogs.isEmpty() || !encryptedChats.isEmpty()) { - MessagesController.getInstance(currentAccount).processDialogsUpdate(dialogs, encryptedChats); + getMessagesController().processDialogsUpdate(dialogs, encryptedChats); } } catch (Exception e) { FileLog.e(e); @@ -6662,7 +6671,7 @@ public class MessagesStorage { maxMessageId |= ((long) channelId) << 32; ArrayList filesToDelete = new ArrayList<>(); - int currentUser = UserConfig.getInstance(currentAccount).getClientUserId(); + int currentUser = getUserConfig().getClientUserId(); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, data, read_state, out, mention FROM messages WHERE uid = %d AND mid <= %d", -channelId, maxMessageId)); @@ -6692,7 +6701,7 @@ public class MessagesStorage { NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (message != null) { if (message.media instanceof TLRPC.TL_messageMediaPhoto) { @@ -6724,7 +6733,7 @@ public class MessagesStorage { } cursor.dispose(); - FileLoader.getInstance(currentAccount).deleteFiles(filesToDelete, 0); + getFileLoader().deleteFiles(filesToDelete, 0); for (int a = 0; a < dialogsToUpdate.size(); a++) { long did = dialogsToUpdate.keyAt(a); @@ -6809,7 +6818,7 @@ public class MessagesStorage { database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND start = 0", did)).stepThis().dispose(); } SQLitePreparedStatement state = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); - for (int a = 0; a < DataQuery.MEDIA_TYPES_COUNT; a++) { + for (int a = 0; a < MediaDataController.MEDIA_TYPES_COUNT; a++) { state.requery(); state.bindLong(1, did); state.bindInteger(2, a); @@ -7040,12 +7049,12 @@ public class MessagesStorage { state.bindInteger(11, message.mentioned ? 1 : 0); state.step(); - if (DataQuery.canAddMessageToMedia(message)) { + if (MediaDataController.canAddMessageToMedia(message)) { state2.requery(); state2.bindLong(1, messageId); state2.bindLong(2, message.dialog_id); state2.bindInteger(3, message.date); - state2.bindInteger(4, DataQuery.getMediaType(message)); + state2.bindInteger(4, MediaDataController.getMediaType(message)); state2.bindByteBuffer(5, data); state2.step(); } @@ -7069,7 +7078,7 @@ public class MessagesStorage { MessageObject messageObject = new MessageObject(currentAccount, message, userHashMap, chatHashMap, true); ArrayList arrayList = new ArrayList<>(); arrayList.add(messageObject); - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.replaceMessagesObjects, messageObject.getDialogId(), arrayList)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.replaceMessagesObjects, messageObject.getDialogId(), arrayList)); } } catch (Exception e) { FileLog.e(e); @@ -7138,7 +7147,7 @@ public class MessagesStorage { NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { TLRPC.Message oldMessage = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - oldMessage.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + oldMessage.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); int send_state = cursor.intValue(5); if (oldMessage != null && send_state != 3) { @@ -7238,12 +7247,12 @@ public class MessagesStorage { state_messages.bindInteger(11, message.mentioned ? 1 : 0); state_messages.step(); - if (DataQuery.canAddMessageToMedia(message)) { + if (MediaDataController.canAddMessageToMedia(message)) { state_media.requery(); state_media.bindLong(1, messageId); state_media.bindLong(2, dialog_id); state_media.bindInteger(3, message.date); - state_media.bindInteger(4, DataQuery.getMediaType(message)); + state_media.bindInteger(4, MediaDataController.getMediaType(message)); state_media.bindByteBuffer(5, data); state_media.step(); } @@ -7283,7 +7292,7 @@ public class MessagesStorage { state_polls.dispose(); } if (botKeyboard != null) { - DataQuery.getInstance(currentAccount).putBotKeyboard(dialog_id, botKeyboard); + getMediaDataController().putBotKeyboard(dialog_id, botKeyboard); } putUsersInternal(messages.users); @@ -7293,7 +7302,7 @@ public class MessagesStorage { database.executeFast(String.format(Locale.US, "UPDATE dialogs SET unread_count_i = %d WHERE did = %d", mentionCountUpdate, dialog_id)).stepThis().dispose(); LongSparseArray sparseArray = new LongSparseArray<>(1); sparseArray.put(dialog_id, mentionCountUpdate); - MessagesController.getInstance(currentAccount).processDialogsUpdateRead(null, sparseArray); + getMessagesController().processDialogsUpdateRead(null, sparseArray); } database.commitTransaction(); @@ -7396,7 +7405,7 @@ public class MessagesStorage { ArrayList encryptedChats = new ArrayList<>(); try { ArrayList usersToLoad = new ArrayList<>(); - usersToLoad.add(UserConfig.getInstance(currentAccount).getClientUserId()); + usersToLoad.add(getUserConfig().getClientUserId()); ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedToLoad = new ArrayList<>(); ArrayList replyMessages = new ArrayList<>(); @@ -7466,7 +7475,7 @@ public class MessagesStorage { NativeByteBuffer data = cursor.byteBufferValue(4); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (message != null) { MessageObject.setUnreadFlags(message, cursor.intValue(5)); @@ -7490,7 +7499,7 @@ public class MessagesStorage { data = cursor.byteBufferValue(13); if (data != null) { message.replyMessage = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.replyMessage.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.replyMessage.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (message.replyMessage != null) { if (MessageObject.isMegagroup(message)) { @@ -7550,7 +7559,7 @@ public class MessagesStorage { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); message.id = cursor.intValue(1); message.date = cursor.intValue(2); @@ -7580,14 +7589,14 @@ public class MessagesStorage { if (!usersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", usersToLoad), dialogs.users); } - MessagesController.getInstance(currentAccount).processLoadedDialogs(dialogs, encryptedChats, folderId, offset, count, 1, false, false, true); + getMessagesController().processLoadedDialogs(dialogs, encryptedChats, folderId, offset, count, 1, false, false, true); } catch (Exception e) { dialogs.dialogs.clear(); dialogs.users.clear(); dialogs.chats.clear(); encryptedChats.clear(); FileLog.e(e); - MessagesController.getInstance(currentAccount).processLoadedDialogs(dialogs, encryptedChats, folderId, 0, 100, 1, true, false, true); + getMessagesController().processLoadedDialogs(dialogs, encryptedChats, folderId, 0, 100, 1, true, false, true); } }); } @@ -7599,7 +7608,7 @@ public class MessagesStorage { state5.bindInteger(3, messageId); state5.step(); - for (int b = 0; b < DataQuery.MEDIA_TYPES_COUNT; b++) { + for (int b = 0; b < MediaDataController.MEDIA_TYPES_COUNT; b++) { state6.requery(); state6.bindLong(1, did); state6.bindInteger(2, b); @@ -7652,7 +7661,7 @@ public class MessagesStorage { messageDate = Math.max(message.date, messageDate); if (isValidKeyboardToSave(message)) { - DataQuery.getInstance(currentAccount).putBotKeyboard(dialog.id, message); + getMediaDataController().putBotKeyboard(dialog.id, message); } fixUnsupportedMedia(message); @@ -7678,12 +7687,12 @@ public class MessagesStorage { state_messages.bindInteger(11, message.mentioned ? 1 : 0); state_messages.step(); - if (DataQuery.canAddMessageToMedia(message)) { + if (MediaDataController.canAddMessageToMedia(message)) { state_media.requery(); state_media.bindLong(1, messageId); state_media.bindLong(2, dialog.id); state_media.bindInteger(3, message.date); - state_media.bindInteger(4, DataQuery.getMediaType(message)); + state_media.bindInteger(4, MediaDataController.getMediaType(message)); state_media.bindByteBuffer(5, data); state_media.step(); } @@ -7831,7 +7840,7 @@ public class MessagesStorage { try { SQLiteCursor cursor = database.queryFinalized("SELECT did FROM dialogs WHERE folder_id = ?", folderId); if (!cursor.next()) { - AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).onFolderEmpty(folderId)); + AndroidUtilities.runOnUIThread(() -> getMessagesController().onFolderEmpty(folderId)); database.executeFast("DELETE FROM dialogs WHERE did = " + DialogObject.makeFolderDialogId(folderId)).stepThis().dispose(); } cursor.dispose(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MusicBrowserService.java b/TMessagesProj/src/main/java/org/telegram/messenger/MusicBrowserService.java index 879219d2c..211e436d6 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MusicBrowserService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MusicBrowserService.java @@ -156,7 +156,7 @@ public class MusicBrowserService extends MediaBrowserService implements Notifica try { ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); - SQLiteCursor cursor = messagesStorage.getDatabase().queryFinalized(String.format(Locale.US, "SELECT DISTINCT uid FROM media_v2 WHERE uid != 0 AND mid > 0 AND type = %d", DataQuery.MEDIA_MUSIC)); + SQLiteCursor cursor = messagesStorage.getDatabase().queryFinalized(String.format(Locale.US, "SELECT DISTINCT uid FROM media_v2 WHERE uid != 0 AND mid > 0 AND type = %d", MediaDataController.MEDIA_MUSIC)); while (cursor.next()) { int lower_part = (int) cursor.longValue(0); if (lower_part == 0) { @@ -172,7 +172,7 @@ public class MusicBrowserService extends MediaBrowserService implements Notifica cursor.dispose(); if (!dialogs.isEmpty()) { String ids = TextUtils.join(",", dialogs); - cursor = messagesStorage.getDatabase().queryFinalized(String.format(Locale.US, "SELECT uid, data, mid FROM media_v2 WHERE uid IN (%s) AND mid > 0 AND type = %d ORDER BY date DESC, mid DESC", ids, DataQuery.MEDIA_MUSIC)); + cursor = messagesStorage.getDatabase().queryFinalized(String.format(Locale.US, "SELECT uid, data, mid FROM media_v2 WHERE uid IN (%s) AND mid > 0 AND type = %d ORDER BY date DESC, mid DESC", ids, MediaDataController.MEDIA_MUSIC)); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java index 182b51525..32c7d3f78 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java @@ -72,6 +72,7 @@ public class NotificationCenter { public static final int chatSearchResultsLoading = totalEvents++; public static final int musicDidLoad = totalEvents++; public static final int needShowAlert = totalEvents++; + public static final int needShowPlayServicesAlert = totalEvents++; public static final int didUpdatedMessagesViews = totalEvents++; public static final int needReloadRecentDialogsSearch = totalEvents++; public static final int peerSettingsDidLoad = totalEvents++; @@ -89,6 +90,9 @@ public class NotificationCenter { public static final int didUpdatePollResults = totalEvents++; public static final int chatOnlineCountDidLoad = totalEvents++; public static final int videoLoadingStateChanged = totalEvents++; + public static final int newPeopleNearbyAvailable = totalEvents++; + public static final int stopAllHeavyOperations = totalEvents++; + public static final int startAllHeavyOperations = totalEvents++; public static final int httpFileDidLoad = totalEvents++; public static final int httpFileDidFailedLoad = totalEvents++; @@ -156,6 +160,7 @@ public class NotificationCenter { public static final int proxySettingsChanged = totalEvents++; public static final int proxyCheckDone = totalEvents++; public static final int liveLocationsChanged = totalEvents++; + public static final int newLocationAvailable = totalEvents++; public static final int liveLocationsCacheChanged = totalEvents++; public static final int notificationsCountUpdated = totalEvents++; public static final int playerDidStartPlaying = totalEvents++; @@ -187,7 +192,8 @@ public class NotificationCenter { } private int currentAccount; - private static volatile NotificationCenter Instance[] = new NotificationCenter[UserConfig.MAX_ACCOUNT_COUNT]; + private int currentHeavyOperationFlags; + private static volatile NotificationCenter[] Instance = new NotificationCenter[UserConfig.MAX_ACCOUNT_COUNT]; private static volatile NotificationCenter globalInstance; @UiThread @@ -222,11 +228,16 @@ public class NotificationCenter { currentAccount = account; } - public void setAllowedNotificationsDutingAnimation(int notifications[]) { + public void setAllowedNotificationsDutingAnimation(int[] notifications) { allowedNotifications = notifications; } public void setAnimationInProgress(boolean value) { + if (value) { + NotificationCenter.getGlobalInstance().postNotificationName(stopAllHeavyOperations, 512); + } else { + NotificationCenter.getGlobalInstance().postNotificationName(startAllHeavyOperations, 512); + } animationInProgress = value; if (!animationInProgress && !delayedPosts.isEmpty()) { for (int a = 0; a < delayedPosts.size(); a++) { @@ -241,9 +252,13 @@ public class NotificationCenter { return animationInProgress; } + public int getCurrentHeavyOperationFlags() { + return currentHeavyOperationFlags; + } + public void postNotificationName(int id, Object... args) { - boolean allowDuringAnimation = false; - if (allowedNotifications != null) { + boolean allowDuringAnimation = id == startAllHeavyOperations || id == stopAllHeavyOperations; + if (!allowDuringAnimation && allowedNotifications != null) { for (int a = 0; a < allowedNotifications.length; a++) { if (allowedNotifications[a] == id) { allowDuringAnimation = true; @@ -251,6 +266,13 @@ public class NotificationCenter { } } } + if (id == startAllHeavyOperations) { + Integer flags = (Integer) args[0]; + currentHeavyOperationFlags &=~ flags; + } else if (id == stopAllHeavyOperations) { + Integer flags = (Integer) args[0]; + currentHeavyOperationFlags |= flags; + } postNotificationNameInternal(id, allowDuringAnimation, args); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java index 1dd532fc1..589cfd66e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java @@ -66,7 +66,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; -public class NotificationsController { +public class NotificationsController extends BaseController { public static final String EXTRA_VOICE_REPLY = "extra_voice_reply"; public static String OTHER_NOTIFICATIONS_CHANNEL = null; @@ -127,8 +127,7 @@ public class NotificationsController { } audioManager = (AudioManager) ApplicationLoader.applicationContext.getSystemService(Context.AUDIO_SERVICE); } - - private int currentAccount; + private static volatile NotificationsController[] Instance = new NotificationsController[UserConfig.MAX_ACCOUNT_COUNT]; public static NotificationsController getInstance(int num) { @@ -145,10 +144,11 @@ public class NotificationsController { } public NotificationsController(int instance) { - currentAccount = instance; + super(instance); + notificationId = currentAccount + 1; notificationGroup = "messages" + (currentAccount == 0 ? "" : currentAccount); - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); inChatSoundEnabled = preferences.getBoolean("EnableInChatSound", true); showBadgeNumber = preferences.getBoolean("badgeNumber", true); showBadgeMuted = preferences.getBoolean("badgeNumberMuted", false); @@ -170,7 +170,7 @@ public class NotificationsController { try { PowerManager pm = (PowerManager) ApplicationLoader.applicationContext.getSystemService(Context.POWER_SERVICE); - notificationDelayWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "lock"); + notificationDelayWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "telegram:notification_delay_lock"); notificationDelayWakelock.setReferenceCounted(false); } catch (Exception e) { FileLog.e(e); @@ -250,7 +250,7 @@ public class NotificationsController { } dismissNotification(); setBadge(getTotalAllUnreadCount()); - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); SharedPreferences.Editor editor = preferences.edit(); editor.clear(); editor.commit(); @@ -292,10 +292,10 @@ public class NotificationsController { } public void removeNotificationsForDialog(long did) { - NotificationsController.getInstance(currentAccount).processReadMessages(null, did, 0, Integer.MAX_VALUE, false); + processReadMessages(null, did, 0, Integer.MAX_VALUE, false); LongSparseArray dialogsToUpdate = new LongSparseArray<>(); dialogsToUpdate.put(did, 0); - NotificationsController.getInstance(currentAccount).processDialogsUpdateRead(dialogsToUpdate); + processDialogsUpdateRead(dialogsToUpdate); } public boolean hasMessagesToReply() { @@ -342,7 +342,7 @@ public class NotificationsController { final ArrayList popupArrayRemove = new ArrayList<>(0); notificationsQueue.postRunnable(() -> { int old_unread_count = total_unread_count; - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); for (int a = 0; a < deletedMessages.size(); a++) { int key = deletedMessages.keyAt(a); long dialog_id = -key; @@ -393,12 +393,12 @@ public class NotificationsController { delayedPushMessages.clear(); showOrUpdateNotification(notifyCheck); } else { - scheduleNotificationDelay(lastOnlineFromOtherDevice > ConnectionsManager.getInstance(currentAccount).getCurrentTime()); + scheduleNotificationDelay(lastOnlineFromOtherDevice > getConnectionsManager().getCurrentTime()); } final int pushDialogsCount = pushDialogs.size(); AndroidUtilities.runOnUIThread(() -> { NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.notificationsCountUpdated, currentAccount); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsUnreadCounterChanged, pushDialogsCount); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsUnreadCounterChanged, pushDialogsCount); }); } notifyCheck = false; @@ -412,7 +412,7 @@ public class NotificationsController { final ArrayList popupArrayRemove = new ArrayList<>(0); notificationsQueue.postRunnable(() -> { int old_unread_count = total_unread_count; - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); for (int a = 0; a < deletedMessages.size(); a++) { int key = deletedMessages.keyAt(a); @@ -465,12 +465,12 @@ public class NotificationsController { delayedPushMessages.clear(); showOrUpdateNotification(notifyCheck); } else { - scheduleNotificationDelay(lastOnlineFromOtherDevice > ConnectionsManager.getInstance(currentAccount).getCurrentTime()); + scheduleNotificationDelay(lastOnlineFromOtherDevice > getConnectionsManager().getCurrentTime()); } final int pushDialogsCount = pushDialogs.size(); AndroidUtilities.runOnUIThread(() -> { NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.notificationsCountUpdated, currentAccount); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsUnreadCounterChanged, pushDialogsCount); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsUnreadCounterChanged, pushDialogsCount); }); } notifyCheck = false; @@ -596,7 +596,7 @@ public class NotificationsController { boolean edited = false; LongSparseArray settingsCache = new LongSparseArray<>(); - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); boolean allowPinned = preferences.getBoolean("PinnedMessages", true); int popup = 0; @@ -633,7 +633,7 @@ public class NotificationsController { popup = addToPopupMessages(popupArrayAdd, messageObject, lower_id, dialog_id, isChannel, preferences); } if (isFcm && (edited = messageObject.localEdit)) { - MessagesStorage.getInstance(currentAccount).putPushMessage(messageObject); + getMessagesStorage().putPushMessage(messageObject); } } continue; @@ -642,7 +642,7 @@ public class NotificationsController { continue; } if (isFcm) { - MessagesStorage.getInstance(currentAccount).putPushMessage(messageObject); + getMessagesStorage().putPushMessage(messageObject); } long original_dialog_id = dialog_id; @@ -758,7 +758,7 @@ public class NotificationsController { final int pushDialogsCount = pushDialogs.size(); AndroidUtilities.runOnUIThread(() -> { NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.notificationsCountUpdated, currentAccount); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsUnreadCounterChanged, pushDialogsCount); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsUnreadCounterChanged, pushDialogsCount); }); } notifyCheck = false; @@ -781,7 +781,7 @@ public class NotificationsController { final ArrayList popupArrayToRemove = new ArrayList<>(); notificationsQueue.postRunnable(() -> { int old_unread_count = total_unread_count; - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); for (int b = 0; b < dialogsToUpdate.size(); b++) { long dialog_id = dialogsToUpdate.keyAt(b); @@ -855,12 +855,12 @@ public class NotificationsController { delayedPushMessages.clear(); showOrUpdateNotification(notifyCheck); } else { - scheduleNotificationDelay(lastOnlineFromOtherDevice > ConnectionsManager.getInstance(currentAccount).getCurrentTime()); + scheduleNotificationDelay(lastOnlineFromOtherDevice > getConnectionsManager().getCurrentTime()); } final int pushDialogsCount = pushDialogs.size(); AndroidUtilities.runOnUIThread(() -> { NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.notificationsCountUpdated, currentAccount); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsUnreadCounterChanged, pushDialogsCount); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsUnreadCounterChanged, pushDialogsCount); }); } notifyCheck = false; @@ -871,9 +871,9 @@ public class NotificationsController { } public void processLoadedUnreadMessages(final LongSparseArray dialogs, final ArrayList messages, ArrayList push, final ArrayList users, final ArrayList chats, final ArrayList encryptedChats) { - MessagesController.getInstance(currentAccount).putUsers(users, true); - MessagesController.getInstance(currentAccount).putChats(chats, true); - MessagesController.getInstance(currentAccount).putEncryptedChats(encryptedChats, true); + getMessagesController().putUsers(users, true); + getMessagesController().putChats(chats, true); + getMessagesController().putEncryptedChats(encryptedChats, true); notificationsQueue.postRunnable(() -> { pushDialogs.clear(); @@ -881,7 +881,7 @@ public class NotificationsController { pushMessagesDict.clear(); total_unread_count = 0; personal_count = 0; - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); LongSparseArray settingsCache = new LongSparseArray<>(); if (messages != null) { @@ -1026,7 +1026,7 @@ public class NotificationsController { NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.pushMessagesUpdated); } NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.notificationsCountUpdated, currentAccount); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsUnreadCounterChanged, pushDialogsCount); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsUnreadCounterChanged, pushDialogsCount); }); showOrUpdateNotification(SystemClock.elapsedRealtime() / 1000 < 60); @@ -1101,21 +1101,26 @@ public class NotificationsController { if (preview != null) { preview[0] = true; } + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); + boolean dialogPreviewEnabled = preferences.getBoolean("content_preview_" + dialog_id, true); if (messageObject.isFcmMessage()) { if (chat_id == 0 && from_id != 0) { - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); - if (!preferences.getBoolean("EnablePreviewAll", true)) { - if (preview != null) { - preview[0] = false; - } - return LocaleController.formatString("NotificationMessageNoText", R.string.NotificationMessageNoText, messageObject.localName); - } if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O_MR1) { userName[0] = messageObject.localName; } + if (!dialogPreviewEnabled || !preferences.getBoolean("EnablePreviewAll", true)) { + if (preview != null) { + preview[0] = false; + } + return LocaleController.getString("Message", R.string.Message); + } } else if (chat_id != 0) { - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); - if (!messageObject.localChannel && !preferences.getBoolean("EnablePreviewGroup", true) || messageObject.localChannel && !preferences.getBoolean("EnablePreviewChannel", true)) { + if (messageObject.messageOwner.to_id.channel_id == 0 || messageObject.isMegagroup()) { + userName[0] = messageObject.localUserName; + } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O_MR1) { + userName[0] = messageObject.localName; + } + if (!dialogPreviewEnabled || !messageObject.localChannel && !preferences.getBoolean("EnablePreviewGroup", true) || messageObject.localChannel && !preferences.getBoolean("EnablePreviewChannel", true)) { if (preview != null) { preview[0] = false; } @@ -1125,11 +1130,6 @@ public class NotificationsController { return LocaleController.formatString("NotificationMessageGroupNoText", R.string.NotificationMessageGroupNoText, messageObject.localUserName, messageObject.localName); } } - if (messageObject.messageOwner.to_id.channel_id == 0 || messageObject.isMegagroup()) { - userName[0] = messageObject.localUserName; - } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O_MR1) { - userName[0] = messageObject.localName; - } } return messageObject.messageOwner.message; } @@ -1139,7 +1139,7 @@ public class NotificationsController { } else { from_id = -chat_id; } - } else if (from_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + } else if (from_id == getUserConfig().getClientUserId()) { from_id = messageObject.messageOwner.from_id; } @@ -1153,7 +1153,7 @@ public class NotificationsController { String name = null; if (from_id > 0) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(from_id); + TLRPC.User user = getMessagesController().getUser(from_id); if (user != null) { name = UserObject.getUserName(user); if (chat_id != 0) { @@ -1167,7 +1167,7 @@ public class NotificationsController { } } } else { - TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-from_id); + TLRPC.Chat chat = getMessagesController().getChat(-from_id); if (chat != null) { name = chat.title; userName[0] = name; @@ -1179,7 +1179,7 @@ public class NotificationsController { } TLRPC.Chat chat = null; if (chat_id != 0) { - chat = MessagesController.getInstance(currentAccount).getChat(chat_id); + chat = getMessagesController().getChat(chat_id); if (chat == null) { return null; } else if (ChatObject.isChannel(chat) && !chat.megagroup) { @@ -1194,9 +1194,8 @@ public class NotificationsController { userName[0] = null; return LocaleController.getString("YouHaveNewMessage", R.string.YouHaveNewMessage); } else { - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); boolean isChannel = ChatObject.isChannel(chat) && !chat.megagroup; - if (chat_id == 0 && from_id != 0 && preferences.getBoolean("EnablePreviewAll", true) || chat_id != 0 && (!isChannel && preferences.getBoolean("EnablePreviewGroup", true) || isChannel && preferences.getBoolean("EnablePreviewChannel", true))) { + if (dialogPreviewEnabled && (chat_id == 0 && from_id != 0 && preferences.getBoolean("EnablePreviewAll", true) || chat_id != 0 && (!isChannel && preferences.getBoolean("EnablePreviewGroup", true) || isChannel && preferences.getBoolean("EnablePreviewChannel", true)))) { if (messageObject.messageOwner instanceof TLRPC.TL_messageService) { userName[0] = null; if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionUserJoined || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionContactSignUp) { @@ -1205,7 +1204,7 @@ public class NotificationsController { return LocaleController.formatString("NotificationContactNewPhoto", R.string.NotificationContactNewPhoto, name); } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionLoginUnknownLocation) { String date = LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, LocaleController.getInstance().formatterYear.format(((long) messageObject.messageOwner.date) * 1000), LocaleController.getInstance().formatterDay.format(((long) messageObject.messageOwner.date) * 1000)); - return LocaleController.formatString("NotificationUnrecognizedDevice", R.string.NotificationUnrecognizedDevice, UserConfig.getInstance(currentAccount).getCurrentUser().first_name, date, messageObject.messageOwner.action.title, messageObject.messageOwner.action.address); + return LocaleController.formatString("NotificationUnrecognizedDevice", R.string.NotificationUnrecognizedDevice, getUserConfig().getCurrentUser().first_name, date, messageObject.messageOwner.action.title, messageObject.messageOwner.action.address); } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionGameScore || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { return messageObject.messageText.toString(); } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPhoneCall) { @@ -1222,10 +1221,10 @@ public class NotificationsController { if (messageObject.messageOwner.to_id.channel_id != 0 && !chat.megagroup) { return LocaleController.formatString("ChannelAddedByNotification", R.string.ChannelAddedByNotification, name, chat.title); } else { - if (singleUserId == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (singleUserId == getUserConfig().getClientUserId()) { return LocaleController.formatString("NotificationInvitedToGroup", R.string.NotificationInvitedToGroup, name, chat.title); } else { - TLRPC.User u2 = MessagesController.getInstance(currentAccount).getUser(singleUserId); + TLRPC.User u2 = getMessagesController().getUser(singleUserId); if (u2 == null) { return null; } @@ -1243,7 +1242,7 @@ public class NotificationsController { } else { StringBuilder names = new StringBuilder(); for (int a = 0; a < messageObject.messageOwner.action.users.size(); a++) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(messageObject.messageOwner.action.users.get(a)); + TLRPC.User user = getMessagesController().getUser(messageObject.messageOwner.action.users.get(a)); if (user != null) { String name2 = UserObject.getUserName(user); if (names.length() != 0) { @@ -1265,12 +1264,12 @@ public class NotificationsController { return LocaleController.formatString("NotificationEditedGroupPhoto", R.string.NotificationEditedGroupPhoto, name, chat.title); } } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatDeleteUser) { - if (messageObject.messageOwner.action.user_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (messageObject.messageOwner.action.user_id == getUserConfig().getClientUserId()) { return LocaleController.formatString("NotificationGroupKickYou", R.string.NotificationGroupKickYou, name, chat.title); } else if (messageObject.messageOwner.action.user_id == from_id) { return LocaleController.formatString("NotificationGroupLeftMember", R.string.NotificationGroupLeftMember, name, chat.title); } else { - TLRPC.User u2 = MessagesController.getInstance(currentAccount).getUser(messageObject.messageOwner.action.user_id); + TLRPC.User u2 = getMessagesController().getUser(messageObject.messageOwner.action.user_id); if (u2 == null) { return null; } @@ -1312,7 +1311,7 @@ public class NotificationsController { return LocaleController.formatString("NotificationActionPinnedVoice", R.string.NotificationActionPinnedVoice, name, chat.title); } else if (object.isRoundVideo()) { return LocaleController.formatString("NotificationActionPinnedRound", R.string.NotificationActionPinnedRound, name, chat.title); - } else if (object.isSticker()) { + } else if (object.isSticker() || object.isAnimatedSticker()) { String emoji = object.getStickerEmoji(); if (emoji != null) { return LocaleController.formatString("NotificationActionPinnedStickerEmoji", R.string.NotificationActionPinnedStickerEmoji, name, chat.title, emoji); @@ -1380,7 +1379,7 @@ public class NotificationsController { return LocaleController.formatString("NotificationActionPinnedVoiceChannel", R.string.NotificationActionPinnedVoiceChannel, chat.title); } else if (object.isRoundVideo()) { return LocaleController.formatString("NotificationActionPinnedRoundChannel", R.string.NotificationActionPinnedRoundChannel, chat.title); - } else if (object.isSticker()) { + } else if (object.isSticker() || object.isAnimatedSticker()) { String emoji = object.getStickerEmoji(); if (emoji != null) { return LocaleController.formatString("NotificationActionPinnedStickerEmojiChannel", R.string.NotificationActionPinnedStickerEmojiChannel, chat.title, emoji); @@ -1465,7 +1464,7 @@ public class NotificationsController { } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeoLive) { return LocaleController.getString("AttachLiveLocation", R.string.AttachLiveLocation); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { - if (messageObject.isSticker()) { + if (messageObject.isSticker() || messageObject.isAnimatedSticker()) { String emoji = messageObject.getStickerEmoji(); if (emoji != null) { return emoji + " " + LocaleController.getString("AttachSticker", R.string.AttachSticker); @@ -1507,18 +1506,18 @@ public class NotificationsController { if (preview != null) { preview[0] = true; } + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); + boolean dialogPreviewEnabled = preferences.getBoolean("content_preview_" + dialog_id, true); if (messageObject.isFcmMessage()) { if (chat_id == 0 && from_id != 0) { - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); - if (!preferences.getBoolean("EnablePreviewAll", true)) { + if (!dialogPreviewEnabled || !preferences.getBoolean("EnablePreviewAll", true)) { if (preview != null) { preview[0] = false; } return LocaleController.formatString("NotificationMessageNoText", R.string.NotificationMessageNoText, messageObject.localName); } } else if (chat_id != 0) { - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); - if (!messageObject.localChannel && !preferences.getBoolean("EnablePreviewGroup", true) || messageObject.localChannel && !preferences.getBoolean("EnablePreviewChannel", true)) { + if (!dialogPreviewEnabled || !messageObject.localChannel && !preferences.getBoolean("EnablePreviewGroup", true) || messageObject.localChannel && !preferences.getBoolean("EnablePreviewChannel", true)) { if (preview != null) { preview[0] = false; } @@ -1538,7 +1537,7 @@ public class NotificationsController { } else { from_id = -chat_id; } - } else if (from_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + } else if (from_id == getUserConfig().getClientUserId()) { from_id = messageObject.messageOwner.from_id; } @@ -1552,12 +1551,12 @@ public class NotificationsController { String name = null; if (from_id > 0) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(from_id); + TLRPC.User user = getMessagesController().getUser(from_id); if (user != null) { name = UserObject.getUserName(user); } } else { - TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-from_id); + TLRPC.Chat chat = getMessagesController().getChat(-from_id); if (chat != null) { name = chat.title; } @@ -1568,7 +1567,7 @@ public class NotificationsController { } TLRPC.Chat chat = null; if (chat_id != 0) { - chat = MessagesController.getInstance(currentAccount).getChat(chat_id); + chat = getMessagesController().getChat(chat_id); if (chat == null) { return null; } @@ -1579,8 +1578,7 @@ public class NotificationsController { msg = LocaleController.getString("YouHaveNewMessage", R.string.YouHaveNewMessage); } else { if (chat_id == 0 && from_id != 0) { - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); - if (preferences.getBoolean("EnablePreviewAll", true)) { + if (dialogPreviewEnabled && preferences.getBoolean("EnablePreviewAll", true)) { if (messageObject.messageOwner instanceof TLRPC.TL_messageService) { if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionUserJoined || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionContactSignUp) { msg = LocaleController.formatString("NotificationContactJoined", R.string.NotificationContactJoined, name); @@ -1588,7 +1586,7 @@ public class NotificationsController { msg = LocaleController.formatString("NotificationContactNewPhoto", R.string.NotificationContactNewPhoto, name); } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionLoginUnknownLocation) { String date = LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, LocaleController.getInstance().formatterYear.format(((long) messageObject.messageOwner.date) * 1000), LocaleController.getInstance().formatterDay.format(((long) messageObject.messageOwner.date) * 1000)); - msg = LocaleController.formatString("NotificationUnrecognizedDevice", R.string.NotificationUnrecognizedDevice, UserConfig.getInstance(currentAccount).getCurrentUser().first_name, date, messageObject.messageOwner.action.title, messageObject.messageOwner.action.address); + msg = LocaleController.formatString("NotificationUnrecognizedDevice", R.string.NotificationUnrecognizedDevice, getUserConfig().getCurrentUser().first_name, date, messageObject.messageOwner.action.title, messageObject.messageOwner.action.address); } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionGameScore || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { msg = messageObject.messageText.toString(); } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPhoneCall) { @@ -1650,7 +1648,7 @@ public class NotificationsController { } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeoLive) { msg = LocaleController.formatString("NotificationMessageLiveLocation", R.string.NotificationMessageLiveLocation, name); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { - if (messageObject.isSticker()) { + if (messageObject.isSticker() || messageObject.isAnimatedSticker()) { String emoji = messageObject.getStickerEmoji(); if (emoji != null) { msg = LocaleController.formatString("NotificationMessageStickerEmoji", R.string.NotificationMessageStickerEmoji, name, emoji); @@ -1681,9 +1679,8 @@ public class NotificationsController { msg = LocaleController.formatString("NotificationMessageNoText", R.string.NotificationMessageNoText, name); } } else if (chat_id != 0) { - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); boolean isChannel = ChatObject.isChannel(chat) && !chat.megagroup; - if (!isChannel && preferences.getBoolean("EnablePreviewGroup", true) || isChannel && preferences.getBoolean("EnablePreviewChannel", true)) { + if (dialogPreviewEnabled && (!isChannel && preferences.getBoolean("EnablePreviewGroup", true) || isChannel && preferences.getBoolean("EnablePreviewChannel", true))) { if (messageObject.messageOwner instanceof TLRPC.TL_messageService) { if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatAddUser) { int singleUserId = messageObject.messageOwner.action.user_id; @@ -1694,10 +1691,10 @@ public class NotificationsController { if (messageObject.messageOwner.to_id.channel_id != 0 && !chat.megagroup) { msg = LocaleController.formatString("ChannelAddedByNotification", R.string.ChannelAddedByNotification, name, chat.title); } else { - if (singleUserId == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (singleUserId == getUserConfig().getClientUserId()) { msg = LocaleController.formatString("NotificationInvitedToGroup", R.string.NotificationInvitedToGroup, name, chat.title); } else { - TLRPC.User u2 = MessagesController.getInstance(currentAccount).getUser(singleUserId); + TLRPC.User u2 = getMessagesController().getUser(singleUserId); if (u2 == null) { return null; } @@ -1715,7 +1712,7 @@ public class NotificationsController { } else { StringBuilder names = new StringBuilder(); for (int a = 0; a < messageObject.messageOwner.action.users.size(); a++) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(messageObject.messageOwner.action.users.get(a)); + TLRPC.User user = getMessagesController().getUser(messageObject.messageOwner.action.users.get(a)); if (user != null) { String name2 = UserObject.getUserName(user); if (names.length() != 0) { @@ -1737,12 +1734,12 @@ public class NotificationsController { msg = LocaleController.formatString("NotificationEditedGroupPhoto", R.string.NotificationEditedGroupPhoto, name, chat.title); } } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatDeleteUser) { - if (messageObject.messageOwner.action.user_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (messageObject.messageOwner.action.user_id == getUserConfig().getClientUserId()) { msg = LocaleController.formatString("NotificationGroupKickYou", R.string.NotificationGroupKickYou, name, chat.title); } else if (messageObject.messageOwner.action.user_id == from_id) { msg = LocaleController.formatString("NotificationGroupLeftMember", R.string.NotificationGroupLeftMember, name, chat.title); } else { - TLRPC.User u2 = MessagesController.getInstance(currentAccount).getUser(messageObject.messageOwner.action.user_id); + TLRPC.User u2 = getMessagesController().getUser(messageObject.messageOwner.action.user_id); if (u2 == null) { return null; } @@ -1784,7 +1781,7 @@ public class NotificationsController { msg = LocaleController.formatString("NotificationActionPinnedVoice", R.string.NotificationActionPinnedVoice, name, chat.title); } else if (object.isRoundVideo()) { msg = LocaleController.formatString("NotificationActionPinnedRound", R.string.NotificationActionPinnedRound, name, chat.title); - } else if (object.isSticker()) { + } else if (object.isSticker() || object.isAnimatedSticker()) { String emoji = object.getStickerEmoji(); if (emoji != null) { msg = LocaleController.formatString("NotificationActionPinnedStickerEmoji", R.string.NotificationActionPinnedStickerEmoji, name, chat.title, emoji); @@ -1852,7 +1849,7 @@ public class NotificationsController { msg = LocaleController.formatString("NotificationActionPinnedVoiceChannel", R.string.NotificationActionPinnedVoiceChannel, chat.title); } else if (object.isRoundVideo()) { msg = LocaleController.formatString("NotificationActionPinnedRoundChannel", R.string.NotificationActionPinnedRoundChannel, chat.title); - } else if (object.isSticker()) { + } else if (object.isSticker() || object.isAnimatedSticker()) { String emoji = object.getStickerEmoji(); if (emoji != null) { msg = LocaleController.formatString("NotificationActionPinnedStickerEmojiChannel", R.string.NotificationActionPinnedStickerEmojiChannel, chat.title, emoji); @@ -1938,7 +1935,7 @@ public class NotificationsController { } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeoLive) { msg = LocaleController.formatString("ChannelMessageLiveLocation", R.string.ChannelMessageLiveLocation, name); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { - if (messageObject.isSticker()) { + if (messageObject.isSticker() || messageObject.isAnimatedSticker()) { String emoji = messageObject.getStickerEmoji(); if (emoji != null) { msg = LocaleController.formatString("ChannelMessageStickerEmoji", R.string.ChannelMessageStickerEmoji, name, emoji); @@ -1999,7 +1996,7 @@ public class NotificationsController { } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeoLive) { msg = LocaleController.formatString("NotificationMessageGroupLiveLocation", R.string.NotificationMessageGroupLiveLocation, name, chat.title); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { - if (messageObject.isSticker()) { + if (messageObject.isSticker() || messageObject.isAnimatedSticker()) { String emoji = messageObject.getStickerEmoji(); if (emoji != null) { msg = LocaleController.formatString("NotificationMessageGroupStickerEmoji", R.string.NotificationMessageGroupStickerEmoji, name, chat.title, emoji); @@ -2041,7 +2038,7 @@ public class NotificationsController { Intent intent = new Intent(ApplicationLoader.applicationContext, NotificationRepeat.class); intent.putExtra("currentAccount", currentAccount); PendingIntent pintent = PendingIntent.getService(ApplicationLoader.applicationContext, 0, intent, 0); - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); int minutes = preferences.getInt("repeat_messages", 60); if (minutes > 0 && personal_count > 0) { alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + minutes * 60 * 1000, pintent); @@ -2062,7 +2059,7 @@ public class NotificationsController { int notifyOverride = preferences.getInt("notify2_" + dialog_id, -1); if (notifyOverride == 3) { int muteUntil = preferences.getInt("notifyuntil_" + dialog_id, 0); - if (muteUntil >= ConnectionsManager.getInstance(currentAccount).getCurrentTime()) { + if (muteUntil >= getConnectionsManager().getCurrentTime()) { notifyOverride = 2; } } @@ -2098,7 +2095,7 @@ public class NotificationsController { if (WearDataLayerListenerService.isWatchConnected()) { try { JSONObject o = new JSONObject(); - o.put("id", UserConfig.getInstance(currentAccount).getClientUserId()); + o.put("id", getUserConfig().getClientUserId()); o.put("cancel_all", true); WearDataLayerListenerService.sendMessageToWatch("/notify", o.toString().getBytes(), "remote_notifications"); } catch (JSONException ignore) { @@ -2122,7 +2119,7 @@ public class NotificationsController { } try { - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); int notifyOverride = getNotifyOverride(preferences, opened_dialog_id); if (notifyOverride == 2) { return; @@ -2209,7 +2206,7 @@ public class NotificationsController { return; } try { - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); String key = "org.telegram.key" + dialogId; String channelId = preferences.getString(key, null); if (channelId != null) { @@ -2229,7 +2226,7 @@ public class NotificationsController { return; } try { - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); Map values = preferences.getAll(); SharedPreferences.Editor editor = preferences.edit(); for (Map.Entry entry : values.entrySet()) { @@ -2250,7 +2247,7 @@ public class NotificationsController { @TargetApi(26) private String validateChannelId(long dialogId, String name, long[] vibrationPattern, int ledColor, Uri sound, int importance, long[] configVibrationPattern, Uri configSound, int configImportance) { - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); String key = "org.telegram.key" + dialogId; String channelId = preferences.getString(key, null); String settings = preferences.getString(key + "_s", null); @@ -2390,15 +2387,15 @@ public class NotificationsController { } private void showOrUpdateNotification(boolean notifyAboutLast) { - if (!UserConfig.getInstance(currentAccount).isClientActivated() || pushMessages.isEmpty() || !SharedConfig.showNotificationsForAllAccounts && currentAccount != UserConfig.selectedAccount) { + if (!getUserConfig().isClientActivated() || pushMessages.isEmpty() || !SharedConfig.showNotificationsForAllAccounts && currentAccount != UserConfig.selectedAccount) { dismissNotification(); return; } try { - ConnectionsManager.getInstance(currentAccount).resumeNetworkMaybe(); + getConnectionsManager().resumeNetworkMaybe(); MessageObject lastMessageObject = pushMessages.get(0); - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); int dismissDate = preferences.getInt("dismissDate", 0); if (lastMessageObject.messageOwner.date <= dismissDate) { dismissNotification(); @@ -2416,14 +2413,14 @@ public class NotificationsController { int user_id = lastMessageObject.messageOwner.to_id.user_id; if (user_id == 0) { user_id = lastMessageObject.messageOwner.from_id; - } else if (user_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + } else if (user_id == getUserConfig().getClientUserId()) { user_id = lastMessageObject.messageOwner.from_id; } - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(user_id); + TLRPC.User user = getMessagesController().getUser(user_id); TLRPC.Chat chat = null; if (chat_id != 0) { - chat = MessagesController.getInstance(currentAccount).getChat(chat_id); + chat = getMessagesController().getChat(chat_id); isChannel = ChatObject.isChannel(chat) && !chat.megagroup; } TLRPC.FileLocation photoPath = null; @@ -2659,9 +2656,9 @@ public class NotificationsController { String detailText; if (UserConfig.getActivatedAccountsCount() > 1) { if (pushDialogs.size() == 1) { - detailText = UserObject.getFirstName(UserConfig.getInstance(currentAccount).getCurrentUser()); + detailText = UserObject.getFirstName(getUserConfig().getCurrentUser()); } else { - detailText = UserObject.getFirstName(UserConfig.getInstance(currentAccount).getCurrentUser()) + "・"; + detailText = UserObject.getFirstName(getUserConfig().getCurrentUser()) + "・"; } } else { detailText = ""; @@ -3004,7 +3001,7 @@ public class NotificationsController { if (lowerId != 0) { canReply = lowerId != 777000; if (lowerId > 0) { - user = MessagesController.getInstance(currentAccount).getUser(lowerId); + user = getMessagesController().getUser(lowerId); if (user == null) { if (lastMessageObject.isFcmMessage()) { name = lastMessageObject.localName; @@ -3021,7 +3018,7 @@ public class NotificationsController { } } } else { - chat = MessagesController.getInstance(currentAccount).getChat(-lowerId); + chat = getMessagesController().getChat(-lowerId); if (chat == null) { if (lastMessageObject.isFcmMessage()) { isSupergroup = lastMessageObject.isMegagroup(); @@ -3045,14 +3042,14 @@ public class NotificationsController { } else { canReply = false; if (dialog_id != globalSecretChatId) { - TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance(currentAccount).getEncryptedChat(highId); + TLRPC.EncryptedChat encryptedChat = getMessagesController().getEncryptedChat(highId); if (encryptedChat == null) { if (BuildVars.LOGS_ENABLED) { FileLog.w("not found secret chat to show dialog notification " + highId); } continue; } - user = MessagesController.getInstance(currentAccount).getUser(encryptedChat.user_id); + user = getMessagesController().getUser(encryptedChat.user_id); if (user == null) { if (BuildVars.LOGS_ENABLED) { FileLog.w("not found secret chat user to show dialog notification " + encryptedChat.user_id); @@ -3073,19 +3070,21 @@ public class NotificationsController { if (photoPath != null) { avatalFile = FileLoader.getPathToAttach(photoPath, true); - BitmapDrawable img = ImageLoader.getInstance().getImageFromMemory(photoPath, null, "50_50"); - if (img != null) { - avatarBitmap = img.getBitmap(); - } else if (Build.VERSION.SDK_INT < 28) { - try { - if (avatalFile.exists()) { - float scaleFactor = 160.0f / AndroidUtilities.dp(50); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = scaleFactor < 1 ? 1 : (int) scaleFactor; - avatarBitmap = BitmapFactory.decodeFile(avatalFile.getAbsolutePath(), options); - } - } catch (Throwable ignore) { + if (Build.VERSION.SDK_INT < 28) { + BitmapDrawable img = ImageLoader.getInstance().getImageFromMemory(photoPath, null, "50_50"); + if (img != null) { + avatarBitmap = img.getBitmap(); + } else { + try { + if (avatalFile.exists()) { + float scaleFactor = 160.0f / AndroidUtilities.dp(50); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = scaleFactor < 1 ? 1 : (int) scaleFactor; + avatarBitmap = BitmapFactory.decodeFile(avatalFile.getAbsolutePath(), options); + } + } catch (Throwable ignore) { + } } } } @@ -3174,17 +3173,16 @@ public class NotificationsController { if (person == null) { Person.Builder personBuilder = new Person.Builder().setName(senderName[0] == null ? "" : senderName[0]); if (preview[0] && lowerId != 0 && Build.VERSION.SDK_INT >= 28) { - File avatar = null; if (lowerId > 0 || isChannel) { avatar = avatalFile; } else if (lowerId < 0) { int fromId = messageObject.getFromId(); - TLRPC.User sender = MessagesController.getInstance(currentAccount).getUser(fromId); + TLRPC.User sender = getMessagesController().getUser(fromId); if (sender == null) { - sender = MessagesStorage.getInstance(currentAccount).getUserSync(fromId); + sender = getMessagesStorage().getUserSync(fromId); if (sender != null) { - MessagesController.getInstance(currentAccount).putUser(sender, true); + getMessagesController().putUser(sender, true); } } if (sender != null && sender.photo != null && sender.photo.photo_small != null && sender.photo.photo_small.volume_id != 0 && sender.photo.photo_small.local_id != 0) { @@ -3199,14 +3197,14 @@ public class NotificationsController { if (lowerId != 0) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !((ActivityManager) ApplicationLoader.applicationContext.getSystemService(Context.ACTIVITY_SERVICE)).isLowRamDevice()) { - if (messageObject.type == 1 || messageObject.isSticker()) { + if (!messageObject.isSecretMedia() && (messageObject.type == 1 || messageObject.isSticker())) { File attach = FileLoader.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"; final Uri uri; if (attach.exists()) { uri = FileProvider.getUriForFile(ApplicationLoader.applicationContext, BuildConfig.APPLICATION_ID + ".provider", attach); - } else if (FileLoader.getInstance(currentAccount).isLoadingFile(attach.getName())) { + } else if (getFileLoader().isLoadingFile(attach.getName())) { Uri.Builder _uri = new Uri.Builder() .scheme("content") .authority(NotificationImageProvider.AUTHORITY) @@ -3266,7 +3264,7 @@ public class NotificationsController { jmsg.put("text", message); jmsg.put("date", messageObject.messageOwner.date); if (messageObject.isFromUser() && lowerId < 0) { - TLRPC.User sender = MessagesController.getInstance(currentAccount).getUser(messageObject.getFromId()); + TLRPC.User sender = getMessagesController().getUser(messageObject.getFromId()); if (sender != null) { jmsg.put("fname", sender.first_name); jmsg.put("lname", sender.last_name); @@ -3334,7 +3332,7 @@ public class NotificationsController { summaryExtender.setDismissalId("summary_" + dismissalID); notificationBuilder.extend(summaryExtender); } - wearableExtender.setBridgeTag("tgaccount" + UserConfig.getInstance(currentAccount).getClientUserId()); + wearableExtender.setBridgeTag("tgaccount" + getUserConfig().getClientUserId()); long date = ((long) messageObjects.get(0).messageOwner.date) * 1000; @@ -3370,7 +3368,7 @@ public class NotificationsController { if (lowerId == 0) { builder.setLocalOnly(true); } - if (avatarBitmap != null && Build.VERSION.SDK_INT < 28) { + if (avatarBitmap != null) { builder.setLargeIcon(avatarBitmap); } @@ -3459,7 +3457,7 @@ public class NotificationsController { if (serializedNotifications != null) { try { JSONObject s = new JSONObject(); - s.put("id", UserConfig.getInstance(currentAccount).getClientUserId()); + s.put("id", getUserConfig().getClientUserId()); s.put("n", serializedNotifications); WearDataLayerListenerService.sendMessageToWatch("/notify", s.toString().getBytes(), "remote_notifications"); } catch (Exception ignore) { @@ -3545,17 +3543,17 @@ public class NotificationsController { public static final int SETTING_MUTE_UNMUTE = 4; public void setDialogNotificationsSettings(long dialog_id, int setting) { - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + 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 = NotificationsController.getInstance(currentAccount).isGlobalNotificationsEnabled(dialog_id); + boolean defaultEnabled = isGlobalNotificationsEnabled(dialog_id); if (defaultEnabled) { editor.remove("notify2_" + dialog_id); } else { editor.putInt("notify2_" + dialog_id, 0); } - MessagesStorage.getInstance(currentAccount).setDialogFlags(dialog_id, 0); + getMessagesStorage().setDialogFlags(dialog_id, 0); if (dialog != null) { dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); } @@ -3591,16 +3589,22 @@ public class NotificationsController { } public void updateServerNotificationsSettings(long dialog_id) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.notificationsSettingsUpdated); + updateServerNotificationsSettings(dialog_id, true); + } + + public void updateServerNotificationsSettings(long dialog_id, boolean post) { + if (post) { + getNotificationCenter().postNotificationName(NotificationCenter.notificationsSettingsUpdated); + } if ((int) dialog_id == 0) { return; } - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); TLRPC.TL_account_updateNotifySettings req = new TLRPC.TL_account_updateNotifySettings(); req.settings = new TLRPC.TL_inputPeerNotifySettings(); req.settings.flags |= 1; - req.settings.show_previews = preferences.getBoolean("preview_" + dialog_id, true); + req.settings.show_previews = preferences.getBoolean("content_preview_" + dialog_id, true); req.settings.flags |= 2; req.settings.silent = preferences.getBoolean("silent_" + dialog_id, false); @@ -3616,8 +3620,8 @@ public class NotificationsController { } req.peer = new TLRPC.TL_inputNotifyPeer(); - ((TLRPC.TL_inputNotifyPeer) req.peer).peer = MessagesController.getInstance(currentAccount).getInputPeer((int) dialog_id); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + ((TLRPC.TL_inputNotifyPeer) req.peer).peer = getMessagesController().getInputPeer((int) dialog_id); + getConnectionsManager().sendRequest(req, (response, error) -> { }); } @@ -3627,7 +3631,7 @@ public class NotificationsController { public final static int TYPE_CHANNEL = 2; public void updateServerNotificationsSettings(int type) { - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getAccountInstance().getNotificationsSettings(); TLRPC.TL_account_updateNotifySettings req = new TLRPC.TL_account_updateNotifySettings(); req.settings = new TLRPC.TL_inputPeerNotifySettings(); req.settings.flags = 5; @@ -3644,7 +3648,7 @@ public class NotificationsController { req.settings.mute_until = preferences.getInt("EnableChannel2", 0); req.settings.show_previews = preferences.getBoolean("EnablePreviewChannel", true); } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); } @@ -3653,7 +3657,7 @@ public class NotificationsController { int type; int lower_id = (int) did; if (lower_id < 0) { - TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-lower_id); + TLRPC.Chat chat = getMessagesController().getChat(-lower_id); if (ChatObject.isChannel(chat) && !chat.megagroup) { type = TYPE_CHANNEL; } else { @@ -3666,12 +3670,12 @@ public class NotificationsController { } public boolean isGlobalNotificationsEnabled(int type) { - return MessagesController.getNotificationsSettings(currentAccount).getInt(getGlobalNotificationsKey(type), 0) < ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + return getAccountInstance().getNotificationsSettings().getInt(getGlobalNotificationsKey(type), 0) < getConnectionsManager().getCurrentTime(); } public void setGlobalNotificationsEnabled(int type, int time) { - MessagesController.getNotificationsSettings(currentAccount).edit().putInt(getGlobalNotificationsKey(type), time).commit(); - NotificationsController.getInstance(currentAccount).updateServerNotificationsSettings(type); + getAccountInstance().getNotificationsSettings().edit().putInt(getGlobalNotificationsKey(type), time).commit(); + updateServerNotificationsSettings(type); } public String getGlobalNotificationsKey(int type) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java index 9bd30989b..43cd0216a 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java @@ -29,7 +29,7 @@ import java.util.Collections; import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; -public class SecretChatHelper { +public class SecretChatHelper extends BaseController { public static class TL_decryptedMessageHolder extends TLObject { public static int constructor = 0x555555F9; @@ -63,7 +63,7 @@ public class SecretChatHelper { } } - public static final int CURRENT_SECRET_CHAT_LAYER = 73; + public static final int CURRENT_SECRET_CHAT_LAYER = 101; private ArrayList sendingNotifyLayer = new ArrayList<>(); private SparseArray> secretHolesQueue = new SparseArray<>(); @@ -72,8 +72,7 @@ public class SecretChatHelper { private ArrayList pendingEncMessagesToDelete = new ArrayList<>(); private boolean startingSecretChat = false; - private int currentAccount; - private static volatile SecretChatHelper Instance[] = new SecretChatHelper[UserConfig.MAX_ACCOUNT_COUNT]; + private static volatile SecretChatHelper[] Instance = new SecretChatHelper[UserConfig.MAX_ACCOUNT_COUNT]; public static SecretChatHelper getInstance(int num) { SecretChatHelper localInstance = Instance[num]; @@ -89,7 +88,7 @@ public class SecretChatHelper { } public SecretChatHelper(int instance) { - currentAccount = instance; + super(instance); } public void cleanup() { @@ -107,14 +106,14 @@ public class SecretChatHelper { final ArrayList pendingEncMessagesToDeleteCopy = new ArrayList<>(pendingEncMessagesToDelete); AndroidUtilities.runOnUIThread(() -> { for (int a = 0; a < pendingEncMessagesToDeleteCopy.size(); a++) { - MessageObject messageObject = MessagesController.getInstance(currentAccount).dialogMessagesByRandomIds.get(pendingEncMessagesToDeleteCopy.get(a)); + MessageObject messageObject = getMessagesController().dialogMessagesByRandomIds.get(pendingEncMessagesToDeleteCopy.get(a)); if (messageObject != null) { messageObject.deleted = true; } } }); ArrayList arr = new ArrayList<>(pendingEncMessagesToDelete); - MessagesStorage.getInstance(currentAccount).markMessagesAsDeletedByRandoms(arr); + getMessagesStorage().markMessagesAsDeletedByRandoms(arr); pendingEncMessagesToDelete.clear(); } } @@ -124,30 +123,30 @@ public class SecretChatHelper { newMsg.action = new TLRPC.TL_messageEncryptedAction(); newMsg.action.encryptedAction = decryptedMessage; - newMsg.local_id = newMsg.id = UserConfig.getInstance(currentAccount).getNewMessageId(); - newMsg.from_id = UserConfig.getInstance(currentAccount).getClientUserId(); + newMsg.local_id = newMsg.id = getUserConfig().getNewMessageId(); + newMsg.from_id = getUserConfig().getClientUserId(); newMsg.unread = true; newMsg.out = true; newMsg.flags = TLRPC.MESSAGE_FLAG_HAS_FROM_ID; newMsg.dialog_id = ((long) encryptedChat.id) << 32; newMsg.to_id = new TLRPC.TL_peerUser(); newMsg.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING; - if (encryptedChat.participant_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (encryptedChat.participant_id == getUserConfig().getClientUserId()) { newMsg.to_id.user_id = encryptedChat.admin_id; } else { newMsg.to_id.user_id = encryptedChat.participant_id; } if (decryptedMessage instanceof TLRPC.TL_decryptedMessageActionScreenshotMessages || decryptedMessage instanceof TLRPC.TL_decryptedMessageActionSetMessageTTL) { - newMsg.date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + newMsg.date = getConnectionsManager().getCurrentTime(); } else { newMsg.date = 0; } - newMsg.random_id = SendMessagesHelper.getInstance(currentAccount).getNextRandomId(); - UserConfig.getInstance(currentAccount).saveConfig(false); + newMsg.random_id = getSendMessagesHelper().getNextRandomId(); + getUserConfig().saveConfig(false); ArrayList arr = new ArrayList<>(); arr.add(newMsg); - MessagesStorage.getInstance(currentAccount).putMessages(arr, false, true, true, 0); + getMessagesStorage().putMessages(arr, false, true, true, 0); return newMsg; } @@ -175,14 +174,14 @@ public class SecretChatHelper { protected void processUpdateEncryption(TLRPC.TL_updateEncryption update, ConcurrentHashMap usersDict) { final TLRPC.EncryptedChat newChat = update.chat; long dialog_id = ((long) newChat.id) << 32; - TLRPC.EncryptedChat existingChat = MessagesController.getInstance(currentAccount).getEncryptedChatDB(newChat.id, false); + TLRPC.EncryptedChat existingChat = getMessagesController().getEncryptedChatDB(newChat.id, false); if (newChat instanceof TLRPC.TL_encryptedChatRequested && existingChat == null) { int user_id = newChat.participant_id; - if (user_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (user_id == getUserConfig().getClientUserId()) { user_id = newChat.admin_id; } - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(user_id); + TLRPC.User user = getMessagesController().getUser(user_id); if (user == null) { user = usersDict.get(user_id); } @@ -192,14 +191,14 @@ public class SecretChatHelper { dialog.unread_count = 0; dialog.top_message = 0; dialog.last_message_date = update.date; - MessagesController.getInstance(currentAccount).putEncryptedChat(newChat, false); + getMessagesController().putEncryptedChat(newChat, false); AndroidUtilities.runOnUIThread(() -> { - MessagesController.getInstance(currentAccount).dialogs_dict.put(dialog.id, dialog); - MessagesController.getInstance(currentAccount).allDialogs.add(dialog); - MessagesController.getInstance(currentAccount).sortDialogs(null); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getMessagesController().dialogs_dict.put(dialog.id, dialog); + getMessagesController().allDialogs.add(dialog); + getMessagesController().sortDialogs(null); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); }); - MessagesStorage.getInstance(currentAccount).putEncryptedChat(newChat, user, dialog); + getMessagesStorage().putEncryptedChat(newChat, user, dialog); acceptSecretChat(newChat); } else if (newChat instanceof TLRPC.TL_encryptedChat) { if (existingChat instanceof TLRPC.TL_encryptedChatWaiting && (existingChat.auth_key == null || existingChat.auth_key.length == 1)) { @@ -225,10 +224,10 @@ public class SecretChatHelper { } AndroidUtilities.runOnUIThread(() -> { if (exist != null) { - MessagesController.getInstance(currentAccount).putEncryptedChat(newChat, false); + getMessagesController().putEncryptedChat(newChat, false); } - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(newChat); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.encryptedChatUpdated, newChat); + getMessagesStorage().updateEncryptedChat(newChat); + getNotificationCenter().postNotificationName(NotificationCenter.encryptedChatUpdated, newChat); }); } } @@ -428,8 +427,8 @@ public class SecretChatHelper { newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING; ArrayList objArr = new ArrayList<>(); objArr.add(newMsgObj); - MessagesController.getInstance(currentAccount).updateInterfaceWithMessages(message.dialog_id, objArr); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getMessagesController().updateInterfaceWithMessages(message.dialog_id, objArr); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } reqSend.random_id = message.random_id; @@ -457,8 +456,8 @@ public class SecretChatHelper { newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING; ArrayList objArr = new ArrayList<>(); objArr.add(newMsgObj); - MessagesController.getInstance(currentAccount).updateInterfaceWithMessages(message.dialog_id, objArr); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getMessagesController().updateInterfaceWithMessages(message.dialog_id, objArr); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } reqSend.random_id = message.random_id; @@ -485,9 +484,9 @@ public class SecretChatHelper { ImageLoader.getInstance().replaceImageInCache(fileName, fileName2, ImageLocation.getForPhoto(size, newMsg.media.photo), true); ArrayList arr = new ArrayList<>(); arr.add(newMsg); - MessagesStorage.getInstance(currentAccount).putMessages(arr, false, true, false, 0); + getMessagesStorage().putMessages(arr, false, true, false, 0); - //MessagesStorage.getInstance(currentAccount).putSentFile(originalPath, newMsg.media.photo, 3); + //getMessagesStorage().putSentFile(originalPath, newMsg.media.photo, 3); } else if (newMsg.media instanceof TLRPC.TL_messageMediaDocument && newMsg.media.document != null) { TLRPC.Document document = newMsg.media.document; newMsg.media.document = new TLRPC.TL_documentEncrypted(); @@ -519,7 +518,7 @@ public class SecretChatHelper { ArrayList arr = new ArrayList<>(); arr.add(newMsg); - MessagesStorage.getInstance(currentAccount).putMessages(arr, false, true, false, 0); + getMessagesStorage().putMessages(arr, false, true, false, 0); } } } @@ -542,7 +541,7 @@ public class SecretChatHelper { if (req == null || chat.auth_key == null || chat instanceof TLRPC.TL_encryptedChatRequested || chat instanceof TLRPC.TL_encryptedChatWaiting) { return; } - SendMessagesHelper.getInstance(currentAccount).putToSendingMessages(newMsgObj); + getSendMessagesHelper().putToSendingMessages(newMsgObj); Utilities.stageQueue.postRunnable(() -> { try { TLObject toEncryptObject; @@ -558,7 +557,7 @@ public class SecretChatHelper { int mtprotoVersion = AndroidUtilities.getPeerLayerVersion(chat.layer) >= 73 ? 2 : 1; if (chat.seq_in == 0 && chat.seq_out == 0) { - if (chat.admin_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (chat.admin_id == getUserConfig().getClientUserId()) { chat.seq_out = 1; chat.seq_in = -2; } else { @@ -572,18 +571,18 @@ public class SecretChatHelper { chat.seq_out += 2; if (AndroidUtilities.getPeerLayerVersion(chat.layer) >= 20) { if (chat.key_create_date == 0) { - chat.key_create_date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + chat.key_create_date = getConnectionsManager().getCurrentTime(); } chat.key_use_count_out++; - if ((chat.key_use_count_out >= 100 || chat.key_create_date < ConnectionsManager.getInstance(currentAccount).getCurrentTime() - 60 * 60 * 24 * 7) && chat.exchange_id == 0 && chat.future_key_fingerprint == 0) { + if ((chat.key_use_count_out >= 100 || chat.key_create_date < getConnectionsManager().getCurrentTime() - 60 * 60 * 24 * 7) && chat.exchange_id == 0 && chat.future_key_fingerprint == 0) { requestNewSecretChatKey(chat); } } - MessagesStorage.getInstance(currentAccount).updateEncryptedChatSeq(chat, false); + getMessagesStorage().updateEncryptedChatSeq(chat, false); if (newMsgObj != null) { newMsgObj.seq_in = layer.in_seq_no; newMsgObj.seq_out = layer.out_seq_no; - MessagesStorage.getInstance(currentAccount).setMessageSeq(newMsgObj.id, newMsgObj.seq_in, newMsgObj.seq_out); + getMessagesStorage().setMessageSeq(newMsgObj.id, newMsgObj.seq_in, newMsgObj.seq_out); } } else { layer.in_seq_no = newMsgObj.seq_in; @@ -615,7 +614,7 @@ public class SecretChatHelper { byte[] messageKey = new byte[16]; byte[] messageKeyFull; - boolean incoming = mtprotoVersion == 2 && chat.admin_id != UserConfig.getInstance(currentAccount).getClientUserId(); + boolean incoming = mtprotoVersion == 2 && chat.admin_id != getUserConfig().getClientUserId(); if (mtprotoVersion == 2) { messageKeyFull = Utilities.computeSHA256(chat.auth_key, 88 + (incoming ? 8 : 0), 32, dataForEncryption.buffer, 0, dataForEncryption.buffer.limit()); System.arraycopy(messageKeyFull, 8, messageKey, 0, 16); @@ -667,10 +666,10 @@ public class SecretChatHelper { req2.file = encryptedFile; reqToSend = req2; } - ConnectionsManager.getInstance(currentAccount).sendRequest(reqToSend, (response, error) -> { + getConnectionsManager().sendRequest(reqToSend, (response, error) -> { if (error == null) { if (req.action instanceof TLRPC.TL_decryptedMessageActionNotifyLayer) { - TLRPC.EncryptedChat currentChat = MessagesController.getInstance(currentAccount).getEncryptedChat(chat.id); + TLRPC.EncryptedChat currentChat = getMessagesController().getEncryptedChat(chat.id); if (currentChat == null) { currentChat = chat; } @@ -686,7 +685,7 @@ public class SecretChatHelper { System.arraycopy(chat.key_hash, 0, key_hash, 0, 16); System.arraycopy(sha256, 0, key_hash, 16, 20); currentChat.key_hash = key_hash; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(currentChat); + getMessagesStorage().updateEncryptedChat(currentChat); } catch (Throwable e) { FileLog.e(e); } @@ -694,7 +693,7 @@ public class SecretChatHelper { sendingNotifyLayer.remove((Integer) currentChat.id); currentChat.layer = AndroidUtilities.setMyLayerVersion(currentChat.layer, CURRENT_SECRET_CHAT_LAYER); - MessagesStorage.getInstance(currentAccount).updateEncryptedChatLayer(currentChat); + getMessagesStorage().updateEncryptedChatLayer(currentChat); } } if (newMsgObj != null) { @@ -711,31 +710,31 @@ public class SecretChatHelper { } else { existFlags = 0; } - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { if (isSecretInvisibleMessage(newMsgObj)) { res.date = 0; } - MessagesStorage.getInstance(currentAccount).updateMessageStateAndId(newMsgObj.random_id, newMsgObj.id, newMsgObj.id, res.date, false, 0); + getMessagesStorage().updateMessageStateAndId(newMsgObj.random_id, newMsgObj.id, newMsgObj.id, res.date, false, 0); AndroidUtilities.runOnUIThread(() -> { newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageReceivedByServer, newMsgObj.id, newMsgObj.id, newMsgObj, newMsgObj.dialog_id, 0L, existFlags); - SendMessagesHelper.getInstance(currentAccount).processSentMessage(newMsgObj.id); + getNotificationCenter().postNotificationName(NotificationCenter.messageReceivedByServer, newMsgObj.id, newMsgObj.id, newMsgObj, newMsgObj.dialog_id, 0L, existFlags); + getSendMessagesHelper().processSentMessage(newMsgObj.id); if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj) || MessageObject.isRoundVideoMessage(newMsgObj)) { - SendMessagesHelper.getInstance(currentAccount).stopVideoService(attachPath); + getSendMessagesHelper().stopVideoService(attachPath); } - SendMessagesHelper.getInstance(currentAccount).removeFromSendingMessages(newMsgObj.id); + getSendMessagesHelper().removeFromSendingMessages(newMsgObj.id); }); }); } else { - MessagesStorage.getInstance(currentAccount).markMessageAsSendError(newMsgObj); + getMessagesStorage().markMessageAsSendError(newMsgObj); AndroidUtilities.runOnUIThread(() -> { newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageSendError, newMsgObj.id); - SendMessagesHelper.getInstance(currentAccount).processSentMessage(newMsgObj.id); + getNotificationCenter().postNotificationName(NotificationCenter.messageSendError, newMsgObj.id); + getSendMessagesHelper().processSentMessage(newMsgObj.id); if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj) || MessageObject.isRoundVideoMessage(newMsgObj)) { - SendMessagesHelper.getInstance(currentAccount).stopVideoService(newMsgObj.attachPath); + getSendMessagesHelper().stopVideoService(newMsgObj.attachPath); } - SendMessagesHelper.getInstance(currentAccount).removeFromSendingMessages(newMsgObj.id); + getSendMessagesHelper().removeFromSendingMessages(newMsgObj.id); }); } } @@ -758,23 +757,23 @@ public class SecretChatHelper { System.arraycopy(chat.key_hash, 0, key_hash, 0, 16); System.arraycopy(sha256, 0, key_hash, 16, 20); chat.key_hash = key_hash; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(chat); + getMessagesStorage().updateEncryptedChat(chat); } catch (Throwable e) { FileLog.e(e); } } chat.layer = AndroidUtilities.setPeerLayerVersion(chat.layer, newPeerLayer); - MessagesStorage.getInstance(currentAccount).updateEncryptedChatLayer(chat); + getMessagesStorage().updateEncryptedChatLayer(chat); if (currentPeerLayer < CURRENT_SECRET_CHAT_LAYER) { sendNotifyLayerMessage(chat, null); } - AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.encryptedChatUpdated, chat)); + AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.encryptedChatUpdated, chat)); } public TLRPC.Message processDecryptedObject(final TLRPC.EncryptedChat chat, final TLRPC.EncryptedFile file, int date, TLObject object, boolean new_key_used) { if (object != null) { int from_id = chat.admin_id; - if (from_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (from_id == getUserConfig().getClientUserId()) { from_id = chat.participant_id; } @@ -785,18 +784,18 @@ public class SecretChatHelper { if (chat.exchange_id == 0 && chat.future_key_fingerprint != 0 && !new_key_used) { chat.future_auth_key = new byte[256]; chat.future_key_fingerprint = 0; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(chat); + getMessagesStorage().updateEncryptedChat(chat); } else if (chat.exchange_id != 0 && new_key_used) { chat.key_fingerprint = chat.future_key_fingerprint; chat.auth_key = chat.future_auth_key; - chat.key_create_date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + chat.key_create_date = getConnectionsManager().getCurrentTime(); chat.future_auth_key = new byte[256]; chat.future_key_fingerprint = 0; chat.key_use_count_in = 0; chat.key_use_count_out = 0; chat.exchange_id = 0; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(chat); + getMessagesStorage().updateEncryptedChat(chat); } if (object instanceof TLRPC.TL_decryptedMessage) { @@ -812,12 +811,12 @@ public class SecretChatHelper { } newMessage.message = decryptedMessage.message; newMessage.date = date; - newMessage.local_id = newMessage.id = UserConfig.getInstance(currentAccount).getNewMessageId(); - UserConfig.getInstance(currentAccount).saveConfig(false); + newMessage.local_id = newMessage.id = getUserConfig().getNewMessageId(); + getUserConfig().saveConfig(false); newMessage.from_id = from_id; newMessage.to_id = new TLRPC.TL_peerUser(); newMessage.random_id = decryptedMessage.random_id; - newMessage.to_id.user_id = UserConfig.getInstance(currentAccount).getClientUserId(); + newMessage.to_id.user_id = getUserConfig().getClientUserId(); newMessage.unread = true; newMessage.flags = TLRPC.MESSAGE_FLAG_HAS_MEDIA | TLRPC.MESSAGE_FLAG_HAS_FROM_ID; if (decryptedMessage.via_bot_name != null && decryptedMessage.via_bot_name.length() > 0) { @@ -1058,38 +1057,38 @@ public class SecretChatHelper { } chat.ttl = serviceMessage.action.ttl_seconds; newMessage.action.encryptedAction = serviceMessage.action; - MessagesStorage.getInstance(currentAccount).updateEncryptedChatTTL(chat); + getMessagesStorage().updateEncryptedChatTTL(chat); } else if (serviceMessage.action instanceof TLRPC.TL_decryptedMessageActionScreenshotMessages) { newMessage.action = new TLRPC.TL_messageEncryptedAction(); newMessage.action.encryptedAction = serviceMessage.action; } - newMessage.local_id = newMessage.id = UserConfig.getInstance(currentAccount).getNewMessageId(); - UserConfig.getInstance(currentAccount).saveConfig(false); + newMessage.local_id = newMessage.id = getUserConfig().getNewMessageId(); + getUserConfig().saveConfig(false); newMessage.unread = true; newMessage.flags = TLRPC.MESSAGE_FLAG_HAS_FROM_ID; newMessage.date = date; newMessage.from_id = from_id; newMessage.to_id = new TLRPC.TL_peerUser(); - newMessage.to_id.user_id = UserConfig.getInstance(currentAccount).getClientUserId(); + newMessage.to_id.user_id = getUserConfig().getClientUserId(); newMessage.dialog_id = ((long) chat.id) << 32; return newMessage; } else if (serviceMessage.action instanceof TLRPC.TL_decryptedMessageActionFlushHistory) { final long did = ((long) chat.id) << 32; AndroidUtilities.runOnUIThread(() -> { - TLRPC.Dialog dialog = MessagesController.getInstance(currentAccount).dialogs_dict.get(did); + TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(did); if (dialog != null) { dialog.unread_count = 0; - MessagesController.getInstance(currentAccount).dialogMessage.remove(dialog.id); + getMessagesController().dialogMessage.remove(dialog.id); } - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> { - NotificationsController.getInstance(currentAccount).processReadMessages(null, did, 0, Integer.MAX_VALUE, false); + getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> { + getNotificationsController().processReadMessages(null, did, 0, Integer.MAX_VALUE, false); LongSparseArray dialogsToUpdate = new LongSparseArray<>(1); dialogsToUpdate.put(did, 0); - NotificationsController.getInstance(currentAccount).processDialogsUpdateRead(dialogsToUpdate); + getNotificationsController().processDialogsUpdateRead(dialogsToUpdate); })); - MessagesStorage.getInstance(currentAccount).deleteDialog(did, 1); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.removeAllMessagesFromDialog, did, false); + getMessagesStorage().deleteDialog(did, 1); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.removeAllMessagesFromDialog, did, false); }); return null; } else if (serviceMessage.action instanceof TLRPC.TL_decryptedMessageActionDeleteMessages) { @@ -1099,8 +1098,8 @@ public class SecretChatHelper { return null; } else if (serviceMessage.action instanceof TLRPC.TL_decryptedMessageActionReadMessages) { if (!serviceMessage.action.random_ids.isEmpty()) { - int time = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); - MessagesStorage.getInstance(currentAccount).createTaskForSecretChat(chat.id, time, time, 1, serviceMessage.action.random_ids); + int time = getConnectionsManager().getCurrentTime(); + getMessagesStorage().createTaskForSecretChat(chat.id, time, time, 1, serviceMessage.action.random_ids); } } else if (serviceMessage.action instanceof TLRPC.TL_decryptedMessageActionNotifyLayer) { applyPeerLayer(chat, serviceMessage.action.layer); @@ -1118,8 +1117,8 @@ public class SecretChatHelper { byte[] salt = new byte[256]; Utilities.random.nextBytes(salt); - BigInteger p = new BigInteger(1, MessagesStorage.getInstance(currentAccount).getSecretPBytes()); - BigInteger g_b = BigInteger.valueOf(MessagesStorage.getInstance(currentAccount).getSecretG()); + BigInteger p = new BigInteger(1, getMessagesStorage().getSecretPBytes()); + BigInteger g_b = BigInteger.valueOf(getMessagesStorage().getSecretG()); g_b = g_b.modPow(new BigInteger(1, salt), p); BigInteger g_a = new BigInteger(1, serviceMessage.action.g_a); @@ -1159,20 +1158,20 @@ public class SecretChatHelper { chat.future_key_fingerprint = Utilities.bytesToLong(authKeyId); chat.g_a_or_b = g_b_bytes; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(chat); + getMessagesStorage().updateEncryptedChat(chat); sendAcceptKeyMessage(chat, null); } else if (serviceMessage.action instanceof TLRPC.TL_decryptedMessageActionAcceptKey) { if (chat.exchange_id == serviceMessage.action.exchange_id) { - BigInteger p = new BigInteger(1, MessagesStorage.getInstance(currentAccount).getSecretPBytes()); + BigInteger p = new BigInteger(1, getMessagesStorage().getSecretPBytes()); BigInteger i_authKey = new BigInteger(1, serviceMessage.action.g_b); if (!Utilities.isGoodGaAndGb(i_authKey, p)) { chat.future_auth_key = new byte[256]; chat.future_key_fingerprint = 0; chat.exchange_id = 0; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(chat); + getMessagesStorage().updateEncryptedChat(chat); sendAbortKeyMessage(chat, null, serviceMessage.action.exchange_id); return null; @@ -1200,20 +1199,20 @@ public class SecretChatHelper { if (serviceMessage.action.key_fingerprint == fingerprint) { chat.future_auth_key = authKey; chat.future_key_fingerprint = fingerprint; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(chat); + getMessagesStorage().updateEncryptedChat(chat); sendCommitKeyMessage(chat, null); } else { chat.future_auth_key = new byte[256]; chat.future_key_fingerprint = 0; chat.exchange_id = 0; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(chat); + getMessagesStorage().updateEncryptedChat(chat); sendAbortKeyMessage(chat, null, serviceMessage.action.exchange_id); } } else { chat.future_auth_key = new byte[256]; chat.future_key_fingerprint = 0; chat.exchange_id = 0; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(chat); + getMessagesStorage().updateEncryptedChat(chat); sendAbortKeyMessage(chat, null, serviceMessage.action.exchange_id); } } else if (serviceMessage.action instanceof TLRPC.TL_decryptedMessageActionCommitKey) { @@ -1222,21 +1221,21 @@ public class SecretChatHelper { byte[] old_key = chat.auth_key; chat.key_fingerprint = chat.future_key_fingerprint; chat.auth_key = chat.future_auth_key; - chat.key_create_date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + chat.key_create_date = getConnectionsManager().getCurrentTime(); chat.future_auth_key = old_key; chat.future_key_fingerprint = old_fingerpring; chat.key_use_count_in = 0; chat.key_use_count_out = 0; chat.exchange_id = 0; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(chat); + getMessagesStorage().updateEncryptedChat(chat); sendNoopMessage(chat, null); } else { chat.future_auth_key = new byte[256]; chat.future_key_fingerprint = 0; chat.exchange_id = 0; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(chat); + getMessagesStorage().updateEncryptedChat(chat); sendAbortKeyMessage(chat, null, serviceMessage.action.exchange_id); } } else if (serviceMessage.action instanceof TLRPC.TL_decryptedMessageActionAbortKey) { @@ -1244,7 +1243,7 @@ public class SecretChatHelper { chat.future_auth_key = new byte[256]; chat.future_key_fingerprint = 0; chat.exchange_id = 0; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(chat); + getMessagesStorage().updateEncryptedChat(chat); } } else if (serviceMessage.action instanceof TLRPC.TL_decryptedMessageActionNoop) { //do nothing @@ -1278,7 +1277,7 @@ public class SecretChatHelper { newMsg.action.encryptedAction = new TLRPC.TL_decryptedMessageActionDeleteMessages(); newMsg.action.encryptedAction.random_ids.add(random_id); newMsg.local_id = newMsg.id = mid; - newMsg.from_id = UserConfig.getInstance(currentAccount).getClientUserId(); + newMsg.from_id = getUserConfig().getClientUserId(); newMsg.unread = true; newMsg.out = true; newMsg.flags = TLRPC.MESSAGE_FLAG_HAS_FROM_ID; @@ -1287,7 +1286,7 @@ public class SecretChatHelper { newMsg.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING; newMsg.seq_in = seq_in; newMsg.seq_out = seq_out; - if (encryptedChat.participant_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (encryptedChat.participant_id == getUserConfig().getClientUserId()) { newMsg.to_id.user_id = encryptedChat.admin_id; } else { newMsg.to_id.user_id = encryptedChat.participant_id; @@ -1301,14 +1300,14 @@ public class SecretChatHelper { if (encryptedChat == null || endSeq - startSeq < 0) { return; } - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { try { int sSeq = startSeq; - if (encryptedChat.admin_id == UserConfig.getInstance(currentAccount).getClientUserId() && sSeq % 2 == 0) { + if (encryptedChat.admin_id == getUserConfig().getClientUserId() && sSeq % 2 == 0) { sSeq++; } - SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT uid FROM requested_holes WHERE uid = %d AND ((seq_out_start >= %d AND %d <= seq_out_end) OR (seq_out_start >= %d AND %d <= seq_out_end))", encryptedChat.id, sSeq, sSeq, endSeq, endSeq)); + SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT uid FROM requested_holes WHERE uid = %d AND ((seq_out_start >= %d AND %d <= seq_out_end) OR (seq_out_start >= %d AND %d <= seq_out_end))", encryptedChat.id, sSeq, sSeq, endSeq, endSeq)); boolean exists = cursor.next(); cursor.dispose(); if (exists) { @@ -1321,7 +1320,7 @@ public class SecretChatHelper { for (int a = sSeq; a < endSeq; a += 2) { messagesToResend.put(a, null); } - cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized(String.format(Locale.US, "SELECT m.data, r.random_id, s.seq_in, s.seq_out, m.ttl, s.mid FROM messages_seq as s LEFT JOIN randoms as r ON r.mid = s.mid LEFT JOIN messages as m ON m.mid = s.mid WHERE m.uid = %d AND m.out = 1 AND s.seq_out >= %d AND s.seq_out <= %d ORDER BY seq_out ASC", dialog_id, sSeq, endSeq)); + cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT m.data, r.random_id, s.seq_in, s.seq_out, m.ttl, s.mid FROM messages_seq as s LEFT JOIN randoms as r ON r.mid = s.mid LEFT JOIN messages as m ON m.mid = s.mid WHERE m.uid = %d AND m.out = 1 AND s.seq_out >= %d AND s.seq_out <= %d ORDER BY seq_out ASC", dialog_id, sSeq, endSeq)); while (cursor.next()) { TLRPC.Message message; long random_id = cursor.longValue(1); @@ -1335,7 +1334,7 @@ public class SecretChatHelper { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId); + message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); message.random_id = random_id; message.dialog_id = dialog_id; @@ -1351,9 +1350,9 @@ public class SecretChatHelper { cursor.dispose(); if (messagesToResend.size() != 0) { for (int a = 0; a < messagesToResend.size(); a++) { - messages.add(createDeleteMessage(UserConfig.getInstance(currentAccount).getNewMessageId(), messagesToResend.keyAt(a), 0, Utilities.random.nextLong(), encryptedChat)); + messages.add(createDeleteMessage(getUserConfig().getNewMessageId(), messagesToResend.keyAt(a), 0, Utilities.random.nextLong(), encryptedChat)); } - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().saveConfig(false); } Collections.sort(messages, (lhs, rhs) -> AndroidUtilities.compare(lhs.seq_out, rhs.seq_out)); ArrayList encryptedChats = new ArrayList<>(); @@ -1364,12 +1363,12 @@ public class SecretChatHelper { TLRPC.Message message = messages.get(a); MessageObject messageObject = new MessageObject(currentAccount, message, false); messageObject.resendAsIs = true; - SendMessagesHelper.getInstance(currentAccount).retrySendMessage(messageObject, true); + getSendMessagesHelper().retrySendMessage(messageObject, true); } }); - SendMessagesHelper.getInstance(currentAccount).processUnsentMessages(messages, new ArrayList<>(), new ArrayList<>(), encryptedChats); - MessagesStorage.getInstance(currentAccount).getDatabase().executeFast(String.format(Locale.US, "REPLACE INTO requested_holes VALUES(%d, %d, %d)", encryptedChat.id, sSeq, endSeq)).stepThis().dispose(); + getSendMessagesHelper().processUnsentMessages(messages, new ArrayList<>(), new ArrayList<>(), encryptedChats); + getMessagesStorage().getDatabase().executeFast(String.format(Locale.US, "REPLACE INTO requested_holes VALUES(%d, %d, %d)", encryptedChat.id, sSeq, endSeq)).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } @@ -1417,7 +1416,7 @@ public class SecretChatHelper { secretHolesQueue.remove(chat.id); } if (update) { - MessagesStorage.getInstance(currentAccount).updateEncryptedChatSeq(chat, true); + getMessagesStorage().updateEncryptedChatSeq(chat, true); } } @@ -1465,7 +1464,7 @@ public class SecretChatHelper { } protected ArrayList decryptMessage(TLRPC.EncryptedMessage message) { - final TLRPC.EncryptedChat chat = MessagesController.getInstance(currentAccount).getEncryptedChatDB(message.chat_id, true); + final TLRPC.EncryptedChat chat = getMessagesController().getEncryptedChatDB(message.chat_id, true); if (chat == null || chat instanceof TLRPC.TL_encryptedChatDiscarded) { return null; } @@ -1489,7 +1488,7 @@ public class SecretChatHelper { if (keyToDecrypt != null) { byte[] messageKey = is.readData(16, false); - boolean incoming = chat.admin_id == UserConfig.getInstance(currentAccount).getClientUserId(); + boolean incoming = chat.admin_id == getUserConfig().getClientUserId(); boolean tryAnotherDecrypt = true; if (decryptedWithVersion == 2 && chat.mtproto_seq != 0) { tryAnotherDecrypt = false; @@ -1518,7 +1517,7 @@ public class SecretChatHelper { if (object instanceof TLRPC.TL_decryptedMessageLayer) { final TLRPC.TL_decryptedMessageLayer layer = (TLRPC.TL_decryptedMessageLayer) object; if (chat.seq_in == 0 && chat.seq_out == 0) { - if (chat.admin_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (chat.admin_id == getUserConfig().getClientUserId()) { chat.seq_out = 1; chat.seq_in = -2; } else { @@ -1562,9 +1561,9 @@ public class SecretChatHelper { newChat.seq_in = chat.seq_in; newChat.seq_out = chat.seq_out; AndroidUtilities.runOnUIThread(() -> { - MessagesController.getInstance(currentAccount).putEncryptedChat(newChat, false); - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(newChat); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.encryptedChatUpdated, newChat); + getMessagesController().putEncryptedChat(newChat, false); + getMessagesStorage().updateEncryptedChat(newChat); + getNotificationCenter().postNotificationName(NotificationCenter.encryptedChatUpdated, newChat); }); declineSecretChat(chat.id); return null; @@ -1585,7 +1584,7 @@ public class SecretChatHelper { applyPeerLayer(chat, layer.layer); chat.seq_in = layer.out_seq_no; chat.in_seq_no = layer.in_seq_no; - MessagesStorage.getInstance(currentAccount).updateEncryptedChatSeq(chat, true); + getMessagesStorage().updateEncryptedChatSeq(chat, true); object = layer.message; } else if (!(object instanceof TLRPC.TL_decryptedMessageService && ((TLRPC.TL_decryptedMessageService) object).action instanceof TLRPC.TL_decryptedMessageActionNotifyLayer)) { return null; @@ -1617,8 +1616,8 @@ public class SecretChatHelper { final byte[] salt = new byte[256]; Utilities.random.nextBytes(salt); - BigInteger i_g_a = BigInteger.valueOf(MessagesStorage.getInstance(currentAccount).getSecretG()); - i_g_a = i_g_a.modPow(new BigInteger(1, salt), new BigInteger(1, MessagesStorage.getInstance(currentAccount).getSecretPBytes())); + BigInteger i_g_a = BigInteger.valueOf(getMessagesStorage().getSecretG()); + i_g_a = i_g_a.modPow(new BigInteger(1, salt), new BigInteger(1, getMessagesStorage().getSecretPBytes())); byte[] g_a = i_g_a.toByteArray(); if (g_a.length > 256) { byte[] correctedAuth = new byte[256]; @@ -1626,17 +1625,17 @@ public class SecretChatHelper { g_a = correctedAuth; } - encryptedChat.exchange_id = SendMessagesHelper.getInstance(currentAccount).getNextRandomId(); + encryptedChat.exchange_id = getSendMessagesHelper().getNextRandomId(); encryptedChat.a_or_b = salt; encryptedChat.g_a = g_a; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(encryptedChat); + getMessagesStorage().updateEncryptedChat(encryptedChat); sendRequestKeyMessage(encryptedChat, null); } public void processAcceptedSecretChat(final TLRPC.EncryptedChat encryptedChat) { - BigInteger p = new BigInteger(1, MessagesStorage.getInstance(currentAccount).getSecretPBytes()); + BigInteger p = new BigInteger(1, getMessagesStorage().getSecretPBytes()); BigInteger i_authKey = new BigInteger(1, encryptedChat.g_a_or_b); if (!Utilities.isGoodGaAndGb(i_authKey, p)) { @@ -1665,13 +1664,13 @@ public class SecretChatHelper { long fingerprint = Utilities.bytesToLong(authKeyId); if (encryptedChat.key_fingerprint == fingerprint) { encryptedChat.auth_key = authKey; - encryptedChat.key_create_date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + encryptedChat.key_create_date = getConnectionsManager().getCurrentTime(); encryptedChat.seq_in = -2; encryptedChat.seq_out = 1; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(encryptedChat); - MessagesController.getInstance(currentAccount).putEncryptedChat(encryptedChat, false); + getMessagesStorage().updateEncryptedChat(encryptedChat); + getMessagesController().putEncryptedChat(encryptedChat, false); AndroidUtilities.runOnUIThread(() -> { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.encryptedChatUpdated, encryptedChat); + getNotificationCenter().postNotificationName(NotificationCenter.encryptedChatUpdated, encryptedChat); sendNotifyLayerMessage(encryptedChat, null); }); } else { @@ -1686,10 +1685,10 @@ public class SecretChatHelper { newChat.seq_out = encryptedChat.seq_out; newChat.admin_id = encryptedChat.admin_id; newChat.mtproto_seq = encryptedChat.mtproto_seq; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(newChat); + getMessagesStorage().updateEncryptedChat(newChat); AndroidUtilities.runOnUIThread(() -> { - MessagesController.getInstance(currentAccount).putEncryptedChat(newChat, false); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.encryptedChatUpdated, newChat); + getMessagesController().putEncryptedChat(newChat, false); + getNotificationCenter().postNotificationName(NotificationCenter.encryptedChatUpdated, newChat); }); declineSecretChat(encryptedChat.id); } @@ -1698,7 +1697,7 @@ public class SecretChatHelper { public void declineSecretChat(int chat_id) { TLRPC.TL_messages_discardEncryption req = new TLRPC.TL_messages_discardEncryption(); req.chat_id = chat_id; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { }); } @@ -1710,8 +1709,8 @@ public class SecretChatHelper { acceptingChats.put(encryptedChat.id, encryptedChat); TLRPC.TL_messages_getDhConfig req = new TLRPC.TL_messages_getDhConfig(); req.random_length = 256; - req.version = MessagesStorage.getInstance(currentAccount).getLastSecretVersion(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + req.version = getMessagesStorage().getLastSecretVersion(); + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.messages_DhConfig res = (TLRPC.messages_DhConfig) response; if (response instanceof TLRPC.TL_messages_dhConfig) { @@ -1721,10 +1720,10 @@ public class SecretChatHelper { return; } - MessagesStorage.getInstance(currentAccount).setSecretPBytes(res.p); - MessagesStorage.getInstance(currentAccount).setSecretG(res.g); - MessagesStorage.getInstance(currentAccount).setLastSecretVersion(res.version); - MessagesStorage.getInstance(currentAccount).saveSecretParams(MessagesStorage.getInstance(currentAccount).getLastSecretVersion(), MessagesStorage.getInstance(currentAccount).getSecretG(), MessagesStorage.getInstance(currentAccount).getSecretPBytes()); + getMessagesStorage().setSecretPBytes(res.p); + getMessagesStorage().setSecretG(res.g); + getMessagesStorage().setLastSecretVersion(res.version); + getMessagesStorage().saveSecretParams(getMessagesStorage().getLastSecretVersion(), getMessagesStorage().getSecretG(), getMessagesStorage().getSecretPBytes()); } byte[] salt = new byte[256]; for (int a = 0; a < 256; a++) { @@ -1733,8 +1732,8 @@ public class SecretChatHelper { encryptedChat.a_or_b = salt; encryptedChat.seq_in = -1; encryptedChat.seq_out = 0; - BigInteger p = new BigInteger(1, MessagesStorage.getInstance(currentAccount).getSecretPBytes()); - BigInteger g_b = BigInteger.valueOf(MessagesStorage.getInstance(currentAccount).getSecretG()); + BigInteger p = new BigInteger(1, getMessagesStorage().getSecretPBytes()); + BigInteger g_b = BigInteger.valueOf(getMessagesStorage().getSecretG()); g_b = g_b.modPow(new BigInteger(1, salt), p); BigInteger g_a = new BigInteger(1, encryptedChat.g_a); @@ -1770,7 +1769,7 @@ public class SecretChatHelper { byte[] authKeyId = new byte[8]; System.arraycopy(authKeyHash, authKeyHash.length - 8, authKeyId, 0, 8); encryptedChat.auth_key = authKey; - encryptedChat.key_create_date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + encryptedChat.key_create_date = getConnectionsManager().getCurrentTime(); TLRPC.TL_messages_acceptEncryption req2 = new TLRPC.TL_messages_acceptEncryption(); req2.g_b = g_b_bytes; @@ -1778,7 +1777,7 @@ public class SecretChatHelper { req2.peer.chat_id = encryptedChat.id; req2.peer.access_hash = encryptedChat.access_hash; req2.key_fingerprint = Utilities.bytesToLong(authKeyId); - ConnectionsManager.getInstance(currentAccount).sendRequest(req2, (response1, error1) -> { + getConnectionsManager().sendRequest(req2, (response1, error1) -> { acceptingChats.remove(encryptedChat.id); if (error1 == null) { final TLRPC.EncryptedChat newChat = (TLRPC.EncryptedChat) response1; @@ -1789,10 +1788,10 @@ public class SecretChatHelper { newChat.key_create_date = encryptedChat.key_create_date; newChat.key_use_count_in = encryptedChat.key_use_count_in; newChat.key_use_count_out = encryptedChat.key_use_count_out; - MessagesStorage.getInstance(currentAccount).updateEncryptedChat(newChat); - MessagesController.getInstance(currentAccount).putEncryptedChat(newChat, false); + getMessagesStorage().updateEncryptedChat(newChat); + getMessagesController().putEncryptedChat(newChat, false); AndroidUtilities.runOnUIThread(() -> { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.encryptedChatUpdated, newChat); + getNotificationCenter().postNotificationName(NotificationCenter.encryptedChatUpdated, newChat); sendNotifyLayerMessage(newChat, null); }); } @@ -1811,8 +1810,8 @@ public class SecretChatHelper { final AlertDialog progressDialog = new AlertDialog(context, 3); TLRPC.TL_messages_getDhConfig req = new TLRPC.TL_messages_getDhConfig(); req.random_length = 256; - req.version = MessagesStorage.getInstance(currentAccount).getLastSecretVersion(); - final int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + req.version = getMessagesStorage().getLastSecretVersion(); + final int reqId = getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.messages_DhConfig res = (TLRPC.messages_DhConfig) response; if (response instanceof TLRPC.TL_messages_dhConfig) { @@ -1828,18 +1827,18 @@ public class SecretChatHelper { }); return; } - MessagesStorage.getInstance(currentAccount).setSecretPBytes(res.p); - MessagesStorage.getInstance(currentAccount).setSecretG(res.g); - MessagesStorage.getInstance(currentAccount).setLastSecretVersion(res.version); - MessagesStorage.getInstance(currentAccount).saveSecretParams(MessagesStorage.getInstance(currentAccount).getLastSecretVersion(), MessagesStorage.getInstance(currentAccount).getSecretG(), MessagesStorage.getInstance(currentAccount).getSecretPBytes()); + getMessagesStorage().setSecretPBytes(res.p); + getMessagesStorage().setSecretG(res.g); + getMessagesStorage().setLastSecretVersion(res.version); + getMessagesStorage().saveSecretParams(getMessagesStorage().getLastSecretVersion(), getMessagesStorage().getSecretG(), getMessagesStorage().getSecretPBytes()); } final byte[] salt = new byte[256]; for (int a = 0; a < 256; a++) { salt[a] = (byte) ((byte) (Utilities.random.nextDouble() * 256) ^ res.random[a]); } - BigInteger i_g_a = BigInteger.valueOf(MessagesStorage.getInstance(currentAccount).getSecretG()); - i_g_a = i_g_a.modPow(new BigInteger(1, salt), new BigInteger(1, MessagesStorage.getInstance(currentAccount).getSecretPBytes())); + BigInteger i_g_a = BigInteger.valueOf(getMessagesStorage().getSecretG()); + i_g_a = i_g_a.modPow(new BigInteger(1, salt), new BigInteger(1, getMessagesStorage().getSecretPBytes())); byte[] g_a = i_g_a.toByteArray(); if (g_a.length > 256) { byte[] correctedAuth = new byte[256]; @@ -1849,9 +1848,9 @@ public class SecretChatHelper { TLRPC.TL_messages_requestEncryption req2 = new TLRPC.TL_messages_requestEncryption(); req2.g_a = g_a; - req2.user_id = MessagesController.getInstance(currentAccount).getInputUser(user); + req2.user_id = getMessagesController().getInputUser(user); req2.random_id = Utilities.random.nextInt(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req2, (response1, error1) -> { + getConnectionsManager().sendRequest(req2, (response1, error1) -> { if (error1 == null) { AndroidUtilities.runOnUIThread(() -> { startingSecretChat = false; @@ -1867,21 +1866,21 @@ public class SecretChatHelper { chat.seq_in = -2; chat.seq_out = 1; chat.a_or_b = salt; - MessagesController.getInstance(currentAccount).putEncryptedChat(chat, false); + getMessagesController().putEncryptedChat(chat, false); TLRPC.Dialog dialog = new TLRPC.TL_dialog(); dialog.id = DialogObject.makeSecretDialogId(chat.id); dialog.unread_count = 0; dialog.top_message = 0; - dialog.last_message_date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); - MessagesController.getInstance(currentAccount).dialogs_dict.put(dialog.id, dialog); - MessagesController.getInstance(currentAccount).allDialogs.add(dialog); - MessagesController.getInstance(currentAccount).sortDialogs(null); - MessagesStorage.getInstance(currentAccount).putEncryptedChat(chat, user, dialog); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.encryptedChatCreated, chat); + dialog.last_message_date = getConnectionsManager().getCurrentTime(); + getMessagesController().dialogs_dict.put(dialog.id, dialog); + getMessagesController().allDialogs.add(dialog); + getMessagesController().sortDialogs(null); + getMessagesStorage().putEncryptedChat(chat, user, dialog); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.encryptedChatCreated, chat); Utilities.stageQueue.postRunnable(() -> { if (!delayedEncryptedChatUpdates.isEmpty()) { - MessagesController.getInstance(currentAccount).processUpdateArray(delayedEncryptedChatUpdates, null, null, false); + getMessagesController().processUpdateArray(delayedEncryptedChatUpdates, null, null, false, 0); delayedEncryptedChatUpdates.clear(); } }); @@ -1919,7 +1918,7 @@ public class SecretChatHelper { }); } }, ConnectionsManager.RequestFlagFailOnServerErrors); - progressDialog.setOnCancelListener(dialog -> ConnectionsManager.getInstance(currentAccount).cancelRequest(reqId, true)); + progressDialog.setOnCancelListener(dialog -> getConnectionsManager().cancelRequest(reqId, true)); try { progressDialog.show(); } catch (Exception e) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java index 18fbcb3b0..9319616ab 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java @@ -35,7 +35,6 @@ import android.webkit.MimeTypeMap; import android.widget.Toast; import org.telegram.messenger.audioinfo.AudioInfo; -import org.telegram.messenger.browser.Browser; import org.telegram.messenger.support.SparseLongArray; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.NativeByteBuffer; @@ -63,7 +62,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -public class SendMessagesHelper implements NotificationCenter.NotificationCenterDelegate { +public class SendMessagesHelper extends BaseController implements NotificationCenter.NotificationCenterDelegate { private TLRPC.ChatFull currentChatInfo = null; private HashMap> delayedMessages = new HashMap<>(); @@ -103,7 +102,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter @Override public void onUnableLocationAcquire() { HashMap waitingForLocationCopy = new HashMap<>(waitingForLocation); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.wasUnableToFindCurrentLocation, waitingForLocationCopy); + getNotificationCenter().postNotificationName(NotificationCenter.wasUnableToFindCurrentLocation, waitingForLocationCopy); waitingForLocation.clear(); } }); @@ -344,7 +343,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter for (int a = 0; a < size; a++) { DelayedMessageSendAfterRequest request = requests.get(a); if (request.request instanceof TLRPC.TL_messages_sendEncryptedMultiMedia) { - SecretChatHelper.getInstance(currentAccount).performSendEncryptedRequest((TLRPC.TL_messages_sendEncryptedMultiMedia) request.request, this); + getSecretChatHelper().performSendEncryptedRequest((TLRPC.TL_messages_sendEncryptedMultiMedia) request.request, this); } else if (request.request instanceof TLRPC.TL_messages_sendMultiMedia) { performSendMessageRequestMulti((TLRPC.TL_messages_sendMultiMedia) request.request, request.msgObjs, request.originalPaths, request.parentObjects, request.delayedMessage); } else { @@ -358,23 +357,22 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (type == 4) { for (int a = 0; a < messageObjects.size(); a++) { MessageObject obj = messageObjects.get(a); - MessagesStorage.getInstance(currentAccount).markMessageAsSendError(obj.messageOwner); + getMessagesStorage().markMessageAsSendError(obj.messageOwner); obj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageSendError, obj.getId()); + getNotificationCenter().postNotificationName(NotificationCenter.messageSendError, obj.getId()); processSentMessage(obj.getId()); } delayedMessages.remove( "group_" + groupId); } else { - MessagesStorage.getInstance(currentAccount).markMessageAsSendError(obj.messageOwner); + getMessagesStorage().markMessageAsSendError(obj.messageOwner); obj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageSendError, obj.getId()); + getNotificationCenter().postNotificationName(NotificationCenter.messageSendError, obj.getId()); processSentMessage(obj.getId()); } sendDelayedRequests(); } } - private int currentAccount; private static volatile SendMessagesHelper[] Instance = new SendMessagesHelper[UserConfig.MAX_ACCOUNT_COUNT]; public static SendMessagesHelper getInstance(int num) { SendMessagesHelper localInstance = Instance[num]; @@ -390,18 +388,18 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } public SendMessagesHelper(int instance) { - currentAccount = instance; + super(instance); AndroidUtilities.runOnUIThread(() -> { - NotificationCenter.getInstance(currentAccount).addObserver(SendMessagesHelper.this, NotificationCenter.FileDidUpload); - NotificationCenter.getInstance(currentAccount).addObserver(SendMessagesHelper.this, NotificationCenter.FileDidFailUpload); - NotificationCenter.getInstance(currentAccount).addObserver(SendMessagesHelper.this, NotificationCenter.filePreparingStarted); - NotificationCenter.getInstance(currentAccount).addObserver(SendMessagesHelper.this, NotificationCenter.fileNewChunkAvailable); - NotificationCenter.getInstance(currentAccount).addObserver(SendMessagesHelper.this, NotificationCenter.filePreparingFailed); - NotificationCenter.getInstance(currentAccount).addObserver(SendMessagesHelper.this, NotificationCenter.httpFileDidFailedLoad); - NotificationCenter.getInstance(currentAccount).addObserver(SendMessagesHelper.this, NotificationCenter.httpFileDidLoad); - NotificationCenter.getInstance(currentAccount).addObserver(SendMessagesHelper.this, NotificationCenter.fileDidLoad); - NotificationCenter.getInstance(currentAccount).addObserver(SendMessagesHelper.this, NotificationCenter.fileDidFailedLoad); + getNotificationCenter().addObserver(SendMessagesHelper.this, NotificationCenter.FileDidUpload); + getNotificationCenter().addObserver(SendMessagesHelper.this, NotificationCenter.FileDidFailUpload); + getNotificationCenter().addObserver(SendMessagesHelper.this, NotificationCenter.filePreparingStarted); + getNotificationCenter().addObserver(SendMessagesHelper.this, NotificationCenter.fileNewChunkAvailable); + getNotificationCenter().addObserver(SendMessagesHelper.this, NotificationCenter.filePreparingFailed); + getNotificationCenter().addObserver(SendMessagesHelper.this, NotificationCenter.httpFileDidFailedLoad); + getNotificationCenter().addObserver(SendMessagesHelper.this, NotificationCenter.httpFileDidLoad); + getNotificationCenter().addObserver(SendMessagesHelper.this, NotificationCenter.fileDidLoad); + getNotificationCenter().addObserver(SendMessagesHelper.this, NotificationCenter.fileDidFailedLoad); }); } @@ -530,7 +528,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (message.type == 4) { uploadMultiMedia(message, null, encryptedFile, location); } else { - SecretChatHelper.getInstance(currentAccount).performSendEncryptedRequest(decryptedMessage, message.obj.messageOwner, message.encryptedChat, encryptedFile, message.originalPath, message.obj); + getSecretChatHelper().performSendEncryptedRequest(decryptedMessage, message.obj.messageOwner, message.encryptedChat, encryptedFile, message.originalPath, message.obj); } } arr.remove(a); @@ -596,7 +594,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter long availableSize = (Long) args[2]; long finalSize = (Long) args[3]; boolean isEncrypted = ((int) messageObject.getDialogId()) == 0; - FileLoader.getInstance(currentAccount).checkUploadNewDataAvailable(finalPath, isEncrypted, availableSize, finalSize); + getFileLoader().checkUploadNewDataAvailable(finalPath, isEncrypted, availableSize, finalSize); if (finalSize != 0) { stopVideoService(messageObject.messageOwner.attachPath); ArrayList arr = delayedMessages.get(messageObject.messageOwner.attachPath); @@ -613,7 +611,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter ArrayList messages = new ArrayList<>(); messages.add(obj.messageOwner); - MessagesStorage.getInstance(currentAccount).putMessages(messages, false, true, false, 0); + getMessagesStorage().putMessages(messages, false, true, false, 0); break; } } @@ -624,7 +622,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter ArrayList messages = new ArrayList<>(); messages.add(message.obj.messageOwner); - MessagesStorage.getInstance(currentAccount).putMessages(messages, false, true, false, 0); + getMessagesStorage().putMessages(messages, false, true, false, 0); break; } } @@ -696,8 +694,8 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter messageObject.messageOwner.attachPath = cacheFile.toString(); ArrayList messages = new ArrayList<>(); messages.add(messageObject.messageOwner); - MessagesStorage.getInstance(currentAccount).putMessages(messages, false, true, false, 0); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateMessageMedia, messageObject.messageOwner); + getMessagesStorage().putMessages(messages, false, true, false, 0); + getNotificationCenter().postNotificationName(NotificationCenter.updateMessageMedia, messageObject.messageOwner); message.photoSize = photo.sizes.get(photo.sizes.size() - 1); message.locationParent = photo; message.httpLocation = null; @@ -742,10 +740,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } ArrayList messages = new ArrayList<>(); messages.add(messageObject.messageOwner); - MessagesStorage.getInstance(currentAccount).putMessages(messages, false, true, false, 0); + getMessagesStorage().putMessages(messages, false, true, false, 0); message.performMediaUpload = true; performSendDelayedMessage(message); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateMessageMedia, message.obj.messageOwner); + getNotificationCenter().postNotificationName(NotificationCenter.updateMessageMedia, message.obj.messageOwner); }); }); } @@ -794,11 +792,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter ArrayList arr = new ArrayList<>(); arr.add(object.messageOwner); - MessagesStorage.getInstance(currentAccount).putMessages(arr, false, true, false, 0); + getMessagesStorage().putMessages(arr, false, true, false, 0); ArrayList arrayList = new ArrayList<>(); arrayList.add(object); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.replaceMessagesObjects, object.getDialogId(), arrayList); + getNotificationCenter().postNotificationName(NotificationCenter.replaceMessagesObjects, object.getDialogId(), arrayList); } public void cancelSendingMessage(MessageObject object) { @@ -818,7 +816,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter channelId = object.messageOwner.to_id.channel_id; TLRPC.Message sendingMessage = removeFromSendingMessages(object.getId()); if (sendingMessage != null) { - ConnectionsManager.getInstance(currentAccount).cancelRequest(sendingMessage.reqId, true); + getConnectionsManager().cancelRequest(sendingMessage.reqId, true); } for (HashMap.Entry> entry : delayedMessages.entrySet()) { @@ -863,7 +861,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter TLRPC.TL_messages_messages messagesRes = new TLRPC.TL_messages_messages(); messagesRes.messages.add(prevMessage.messageOwner); - MessagesStorage.getInstance(currentAccount).putMessages(messagesRes, message.peer, -2, 0, false); + getMessagesStorage().putMessages(messagesRes, message.peer, -2, 0, false); } sendReadyToSendGroup(message, false, true); @@ -890,7 +888,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (key.startsWith("http")) { ImageLoader.getInstance().cancelLoadHttpFile(key); } else { - FileLoader.getInstance(currentAccount).cancelUploadFile(key, enc); + getFileLoader().cancelUploadFile(key, enc); } stopVideoService(key); delayedMessages.remove(key); @@ -898,7 +896,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (objects.size() == 1 && objects.get(0).isEditing() && objects.get(0).previousMedia != null) { revertEditingMessageObject(objects.get(0)); } else { - MessagesController.getInstance(currentAccount).deleteMessages(messageIds, null, null, channelId, false); + getMessagesController().deleteMessages(messageIds, null, null, channelId, false); } } @@ -911,11 +909,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } if (messageObject.messageOwner.action instanceof TLRPC.TL_messageEncryptedAction) { int enc_id = (int) (messageObject.getDialogId() >> 32); - TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance(currentAccount).getEncryptedChat(enc_id); + TLRPC.EncryptedChat encryptedChat = getMessagesController().getEncryptedChat(enc_id); if (encryptedChat == null) { - MessagesStorage.getInstance(currentAccount).markMessageAsSendError(messageObject.messageOwner); + getMessagesStorage().markMessageAsSendError(messageObject.messageOwner); messageObject.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageSendError, messageObject.getId()); + getNotificationCenter().postNotificationName(NotificationCenter.messageSendError, messageObject.getId()); processSentMessage(messageObject.getId()); return false; } @@ -923,35 +921,35 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter messageObject.messageOwner.random_id = getNextRandomId(); } if (messageObject.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionSetMessageTTL) { - SecretChatHelper.getInstance(currentAccount).sendTTLMessage(encryptedChat, messageObject.messageOwner); + getSecretChatHelper().sendTTLMessage(encryptedChat, messageObject.messageOwner); } else if (messageObject.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionDeleteMessages) { - SecretChatHelper.getInstance(currentAccount).sendMessagesDeleteMessage(encryptedChat, null, messageObject.messageOwner); + getSecretChatHelper().sendMessagesDeleteMessage(encryptedChat, null, messageObject.messageOwner); } else if (messageObject.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionFlushHistory) { - SecretChatHelper.getInstance(currentAccount).sendClearHistoryMessage(encryptedChat, messageObject.messageOwner); + getSecretChatHelper().sendClearHistoryMessage(encryptedChat, messageObject.messageOwner); } else if (messageObject.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionNotifyLayer) { - SecretChatHelper.getInstance(currentAccount).sendNotifyLayerMessage(encryptedChat, messageObject.messageOwner); + getSecretChatHelper().sendNotifyLayerMessage(encryptedChat, messageObject.messageOwner); } else if (messageObject.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionReadMessages) { - SecretChatHelper.getInstance(currentAccount).sendMessagesReadMessage(encryptedChat, null, messageObject.messageOwner); + getSecretChatHelper().sendMessagesReadMessage(encryptedChat, null, messageObject.messageOwner); } else if (messageObject.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionScreenshotMessages) { - SecretChatHelper.getInstance(currentAccount).sendScreenshotMessage(encryptedChat, null, messageObject.messageOwner); + getSecretChatHelper().sendScreenshotMessage(encryptedChat, null, messageObject.messageOwner); } else if (messageObject.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionTyping) { } else if (messageObject.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionResend) { } else if (messageObject.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionCommitKey) { - SecretChatHelper.getInstance(currentAccount).sendCommitKeyMessage(encryptedChat, messageObject.messageOwner); + getSecretChatHelper().sendCommitKeyMessage(encryptedChat, messageObject.messageOwner); } else if (messageObject.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionAbortKey) { - SecretChatHelper.getInstance(currentAccount).sendAbortKeyMessage(encryptedChat, messageObject.messageOwner, 0); + getSecretChatHelper().sendAbortKeyMessage(encryptedChat, messageObject.messageOwner, 0); } else if (messageObject.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionRequestKey) { - SecretChatHelper.getInstance(currentAccount).sendRequestKeyMessage(encryptedChat, messageObject.messageOwner); + getSecretChatHelper().sendRequestKeyMessage(encryptedChat, messageObject.messageOwner); } else if (messageObject.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionAcceptKey) { - SecretChatHelper.getInstance(currentAccount).sendAcceptKeyMessage(encryptedChat, messageObject.messageOwner); + getSecretChatHelper().sendAcceptKeyMessage(encryptedChat, messageObject.messageOwner); } else if (messageObject.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionNoop) { - SecretChatHelper.getInstance(currentAccount).sendNoopMessage(encryptedChat, messageObject.messageOwner); + getSecretChatHelper().sendNoopMessage(encryptedChat, messageObject.messageOwner); } return true; } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionScreenshotTaken) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser((int) messageObject.getDialogId()); + TLRPC.User user = getMessagesController().getUser((int) messageObject.getDialogId()); sendScreenshotMessage(user, messageObject.messageOwner.reply_to_msg_id, messageObject.messageOwner); } if (unsent) { @@ -1027,7 +1025,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } public void sendScreenshotMessage(TLRPC.User user, int messageId, TLRPC.Message resendMessage) { - if (user == null || messageId == 0 || user.id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (user == null || messageId == 0 || user.id == getUserConfig().getClientUserId()) { return; } @@ -1046,16 +1044,16 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter message.dialog_id = user.id; message.unread = true; message.out = true; - message.local_id = message.id = UserConfig.getInstance(currentAccount).getNewMessageId(); - message.from_id = UserConfig.getInstance(currentAccount).getClientUserId(); + message.local_id = message.id = getUserConfig().getNewMessageId(); + message.from_id = getUserConfig().getClientUserId(); message.flags |= 256; message.flags |= 8; message.reply_to_msg_id = messageId; message.to_id = new TLRPC.TL_peerUser(); message.to_id.user_id = user.id; - message.date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + message.date = getConnectionsManager().getCurrentTime(); message.action = new TLRPC.TL_messageActionScreenshotTaken(); - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().saveConfig(false); } req.random_id = message.random_id; @@ -1063,11 +1061,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING; ArrayList objArr = new ArrayList<>(); objArr.add(newMsgObj); - MessagesController.getInstance(currentAccount).updateInterfaceWithMessages(message.dialog_id, objArr); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getMessagesController().updateInterfaceWithMessages(message.dialog_id, objArr); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); ArrayList arr = new ArrayList<>(); arr.add(message); - MessagesStorage.getInstance(currentAccount).putMessages(arr, false, true, false, 0); + getMessagesStorage().putMessages(arr, false, true, false, 0); performSendMessageRequest(req, newMsgObj, null, null, null); } @@ -1078,7 +1076,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } if ((int) peer == 0) { int high_id = (int) (peer >> 32); - TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance(currentAccount).getEncryptedChat(high_id); + TLRPC.EncryptedChat encryptedChat = getMessagesController().getEncryptedChat(high_id); if (encryptedChat == null) { return; } @@ -1145,7 +1143,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter int lower_id = (int) peer; int sendResult = 0; if (lower_id != 0) { - final TLRPC.Peer to_id = MessagesController.getInstance(currentAccount).getPeer((int) peer); + final TLRPC.Peer to_id = getMessagesController().getPeer((int) peer); boolean isMegagroup = false; boolean isSignature = false; boolean canSendStickers = true; @@ -1154,13 +1152,13 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter boolean canSendPreview = true; TLRPC.Chat chat; if (lower_id > 0) { - TLRPC.User sendToUser = MessagesController.getInstance(currentAccount).getUser(lower_id); + TLRPC.User sendToUser = getMessagesController().getUser(lower_id); if (sendToUser == null) { return 0; } chat = null; } else { - chat = MessagesController.getInstance(currentAccount).getChat(-lower_id); + chat = getMessagesController().getChat(-lower_id); if (ChatObject.isChannel(chat)) { isMegagroup = chat.megagroup; isSignature = chat.signatures; @@ -1177,9 +1175,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter ArrayList randomIds = new ArrayList<>(); ArrayList ids = new ArrayList<>(); LongSparseArray messagesByRandomIds = new LongSparseArray<>(); - TLRPC.InputPeer inputPeer = MessagesController.getInstance(currentAccount).getInputPeer(lower_id); + TLRPC.InputPeer inputPeer = getMessagesController().getInputPeer(lower_id); long lastDialogId = 0; - int myId = UserConfig.getInstance(currentAccount).getClientUserId(); + int myId = getUserConfig().getClientUserId(); final boolean toMyself = peer == myId; long lastGroupedId; for (int a = 0; a < messages.size(); a++) { @@ -1191,7 +1189,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } continue; } - if (!canSendStickers && (msgObj.isSticker() || msgObj.isGif() || msgObj.isGame())) { + if (!canSendStickers && (msgObj.isSticker() || msgObj.isAnimatedSticker() || msgObj.isGif() || msgObj.isGame())) { if (sendResult == 0) { sendResult = ChatObject.isActionBannedByDefault(chat, ChatObject.ACTION_SEND_STICKERS) ? 4 : 1; } @@ -1210,7 +1208,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter boolean groupedIdChanged = false; final TLRPC.Message newMsg = new TLRPC.TL_message(); - boolean forwardFromSaved = msgObj.getDialogId() == myId && msgObj.messageOwner.from_id == UserConfig.getInstance(currentAccount).getClientUserId(); + boolean forwardFromSaved = msgObj.getDialogId() == myId && msgObj.messageOwner.from_id == getUserConfig().getClientUserId(); if (msgObj.isForwarded()) { newMsg.fwd_from = new TLRPC.TL_messageFwdHeader(); newMsg.fwd_from.flags = msgObj.messageOwner.fwd_from.flags; @@ -1240,7 +1238,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter newMsg.fwd_from.post_author = msgObj.messageOwner.post_author; newMsg.fwd_from.flags |= 8; } else if (!msgObj.isOutOwner() && msgObj.messageOwner.from_id > 0 && msgObj.messageOwner.post) { - TLRPC.User signUser = MessagesController.getInstance(currentAccount).getUser(msgObj.messageOwner.from_id); + TLRPC.User signUser = getMessagesController().getUser(msgObj.messageOwner.from_id); if (signUser != null) { newMsg.fwd_from.post_author = ContactsController.formatName(signUser.first_name, signUser.last_name); newMsg.fwd_from.flags |= 8; @@ -1309,7 +1307,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (newMsg.attachPath == null) { newMsg.attachPath = ""; } - newMsg.local_id = newMsg.id = UserConfig.getInstance(currentAccount).getNewMessageId(); + newMsg.local_id = newMsg.id = getUserConfig().getNewMessageId(); newMsg.out = true; if ((lastGroupedId = msgObj.messageOwner.grouped_id) != 0) { Long gId = groupsMap.get(msgObj.messageOwner.grouped_id); @@ -1327,10 +1325,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } if (to_id.channel_id != 0 && !isMegagroup) { - newMsg.from_id = isSignature ? UserConfig.getInstance(currentAccount).getClientUserId() : -to_id.channel_id; + newMsg.from_id = isSignature ? getUserConfig().getClientUserId() : -to_id.channel_id; newMsg.post = true; } else { - newMsg.from_id = UserConfig.getInstance(currentAccount).getClientUserId(); + newMsg.from_id = getUserConfig().getClientUserId(); newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_FROM_ID; } if (newMsg.random_id == 0) { @@ -1339,7 +1337,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter randomIds.add(newMsg.random_id); messagesByRandomIds.put(newMsg.random_id, newMsg); ids.add(newMsg.fwd_msg_id); - newMsg.date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + newMsg.date = getConnectionsManager().getCurrentTime(); if (inputPeer instanceof TLRPC.TL_inputPeerChannel && !isMegagroup) { newMsg.views = 1; newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_VIEWS; @@ -1375,10 +1373,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } if (groupedIdChanged && arr.size() > 0 || arr.size() == 100 || a == messages.size() - 1 || a != messages.size() - 1 && messages.get(a + 1).getDialogId() != msgObj.getDialogId()) { - MessagesStorage.getInstance(currentAccount).putMessages(new ArrayList<>(arr), false, true, false, 0); - MessagesController.getInstance(currentAccount).updateInterfaceWithMessages(peer, objArr); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); - UserConfig.getInstance(currentAccount).saveConfig(false); + getMessagesStorage().putMessages(new ArrayList<>(arr), false, true, false, 0); + getMessagesController().updateInterfaceWithMessages(peer, objArr); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); + getUserConfig().saveConfig(false); final TLRPC.TL_messages_forwardMessages req = new TLRPC.TL_messages_forwardMessages(); req.to_peer = inputPeer; @@ -1387,7 +1385,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter req.silent = MessagesController.getNotificationsSettings(currentAccount).getBoolean("silent_" + peer, false); } if (msgObj.messageOwner.to_id instanceof TLRPC.TL_peerChannel) { - TLRPC.Chat channel = MessagesController.getInstance(currentAccount).getChat(msgObj.messageOwner.to_id.channel_id); + TLRPC.Chat channel = getMessagesController().getChat(msgObj.messageOwner.to_id.channel_id); req.from_peer = new TLRPC.TL_inputPeerChannel(); req.from_peer.channel_id = msgObj.messageOwner.to_id.channel_id; if (channel != null) { @@ -1404,7 +1402,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter final ArrayList newMsgArr = objArr; final LongSparseArray messagesByRandomIdsFinal = messagesByRandomIds; final boolean isMegagroupFinal = isMegagroup; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { SparseLongArray newMessagesByIds = new SparseLongArray(); TLRPC.Updates updates = (TLRPC.Updates) response; @@ -1417,10 +1415,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter a1--; } } - Integer value = MessagesController.getInstance(currentAccount).dialogs_read_outbox_max.get(peer); + Integer value = getMessagesController().dialogs_read_outbox_max.get(peer); if (value == null) { - value = MessagesStorage.getInstance(currentAccount).getDialogReadMax(true, peer); - MessagesController.getInstance(currentAccount).dialogs_read_outbox_max.put(peer, value); + value = getMessagesStorage().getDialogReadMax(true, peer); + getMessagesController().dialogs_read_outbox_max.put(peer, value); } int sentCount = 0; @@ -1433,11 +1431,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (update instanceof TLRPC.TL_updateNewMessage) { TLRPC.TL_updateNewMessage updateNewMessage = (TLRPC.TL_updateNewMessage) update; message = updateNewMessage.message; - MessagesController.getInstance(currentAccount).processNewDifferenceParams(-1, updateNewMessage.pts, -1, updateNewMessage.pts_count); + getMessagesController().processNewDifferenceParams(-1, updateNewMessage.pts, -1, updateNewMessage.pts_count); } else { TLRPC.TL_updateNewChannelMessage updateNewChannelMessage = (TLRPC.TL_updateNewChannelMessage) update; message = updateNewChannelMessage.message; - MessagesController.getInstance(currentAccount).processNewChannelDifferenceParams(updateNewChannelMessage.pts, updateNewChannelMessage.pts_count, message.to_id.channel_id); + getMessagesController().processNewChannelDifferenceParams(updateNewChannelMessage.pts, updateNewChannelMessage.pts_count, message.to_id.channel_id); if (isMegagroupFinal) { message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; } @@ -1470,13 +1468,13 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter int existFlags = msgObj1.getMediaExistanceFlags(); newMsgObj1.id = message.id; sentCount++; - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { - MessagesStorage.getInstance(currentAccount).updateMessageStateAndId(newMsgObj1.random_id, oldId, newMsgObj1.id, 0, false, to_id.channel_id); - MessagesStorage.getInstance(currentAccount).putMessages(sentMessages, true, false, false, 0); + getMessagesStorage().getStorageQueue().postRunnable(() -> { + getMessagesStorage().updateMessageStateAndId(newMsgObj1.random_id, oldId, newMsgObj1.id, 0, false, to_id.channel_id); + getMessagesStorage().putMessages(sentMessages, true, false, false, 0); AndroidUtilities.runOnUIThread(() -> { newMsgObj1.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; - DataQuery.getInstance(currentAccount).increasePeerRaiting(peer); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageReceivedByServer, oldId, message.id, message, peer, 0L, existFlags); + getMediaDataController().increasePeerRaiting(peer); + getNotificationCenter().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, message.id, message, peer, 0L, existFlags); processSentMessage(oldId); removeFromSendingMessages(oldId); }); @@ -1485,18 +1483,18 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } if (!updates.updates.isEmpty()) { - MessagesController.getInstance(currentAccount).processUpdates(updates, false); + getMessagesController().processUpdates(updates, false); } - StatsController.getInstance(currentAccount).incrementSentItemsCount(ApplicationLoader.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, sentCount); + getStatsController().incrementSentItemsCount(ApplicationLoader.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, sentCount); } else { AndroidUtilities.runOnUIThread(() -> AlertsCreator.processError(currentAccount, error, null, req)); } for (int a1 = 0; a1 < newMsgObjArr.size(); a1++) { final TLRPC.Message newMsgObj1 = newMsgObjArr.get(a1); - MessagesStorage.getInstance(currentAccount).markMessageAsSendError(newMsgObj1); + getMessagesStorage().markMessageAsSendError(newMsgObj1); AndroidUtilities.runOnUIThread(() -> { newMsgObj1.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageSendError, newMsgObj1.id); + getNotificationCenter().postNotificationName(NotificationCenter.messageSendError, newMsgObj1.id); processSentMessage(newMsgObj1.id); removeFromSendingMessages(newMsgObj1.id); }); @@ -1625,7 +1623,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter newMsg.entities = messageObject.editingMessageEntities; } else { CharSequence[] message = new CharSequence[]{messageObject.editingMessage}; - ArrayList entities = DataQuery.getInstance(currentAccount).getEntities(message); + ArrayList entities = getMediaDataController().getEntities(message); if (entities != null && !entities.isEmpty()) { newMsg.entities = entities; } @@ -1636,14 +1634,14 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter ArrayList arr = new ArrayList<>(); arr.add(newMsg); - MessagesStorage.getInstance(currentAccount).putMessages(arr, false, true, false, 0); + getMessagesStorage().putMessages(arr, false, true, false, 0); messageObject.type = -1; messageObject.setType(); messageObject.createMessageSendInfo(); ArrayList arrayList = new ArrayList<>(); arrayList.add(messageObject); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.replaceMessagesObjects, peer, arrayList); + getNotificationCenter().postNotificationName(NotificationCenter.replaceMessagesObjects, peer, arrayList); } String originalPath = null; @@ -1786,7 +1784,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter TLRPC.TL_messages_editMessage request = new TLRPC.TL_messages_editMessage(); request.id = messageObject.getId(); - request.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) peer); + request.peer = getMessagesController().getInputPeer((int) peer); request.flags |= 16384; request.media = inputMedia; @@ -1798,7 +1796,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter request.flags |= 8; } else { CharSequence[] message = new CharSequence[]{messageObject.editingMessage}; - ArrayList entities = DataQuery.getInstance(currentAccount).getEntities(message); + ArrayList entities = getMediaDataController().getEntities(message); if (entities != null && !entities.isEmpty()) { request.entities = entities; request.flags |= 8; @@ -1855,7 +1853,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } final TLRPC.TL_messages_editMessage req = new TLRPC.TL_messages_editMessage(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) messageObject.getDialogId()); + req.peer = getMessagesController().getInputPeer((int) messageObject.getDialogId()); req.message = message; req.flags |= 2048; req.id = messageObject.getId(); @@ -1864,9 +1862,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter req.entities = entities; req.flags |= 8; } - return ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + return getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { - MessagesController.getInstance(currentAccount).processUpdates((TLRPC.Updates) response, false); + getMessagesController().processUpdates((TLRPC.Updates) response, false); } else { AndroidUtilities.runOnUIThread(() -> AlertsCreator.processError(currentAccount, error, fragment, req)); } @@ -1909,33 +1907,33 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter waitingForCallback.put(key, true); if (lowerId > 0) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(lowerId); + TLRPC.User user = getMessagesController().getUser(lowerId); if (user == null) { - user = MessagesStorage.getInstance(currentAccount).getUserSync(lowerId); + user = getMessagesStorage().getUserSync(lowerId); if (user != null) { - MessagesController.getInstance(currentAccount).putUser(user, true); + getMessagesController().putUser(user, true); } } } else { - TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-lowerId); + TLRPC.Chat chat = getMessagesController().getChat(-lowerId); if (chat == null) { - chat = MessagesStorage.getInstance(currentAccount).getChatSync(-lowerId); + chat = getMessagesStorage().getChatSync(-lowerId); if (chat != null) { - MessagesController.getInstance(currentAccount).putChat(chat, true); + getMessagesController().putChat(chat, true); } } } TLRPC.TL_messages_getBotCallbackAnswer req = new TLRPC.TL_messages_getBotCallbackAnswer(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer(lowerId); + req.peer = getMessagesController().getInputPeer(lowerId); req.msg_id = msgId; req.game = false; if (data != null) { req.flags |= 1; req.data = data; } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> waitingForCallback.remove(key)), ConnectionsManager.RequestFlagFailOnServerErrors); - MessagesController.getInstance(currentAccount).markDialogAsRead(dialogId, msgId, msgId, 0, false, 0, true); + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> waitingForCallback.remove(key)), ConnectionsManager.RequestFlagFailOnServerErrors); + getMessagesController().markDialogAsRead(dialogId, msgId, msgId, 0, false, 0, true); }); } @@ -1958,14 +1956,14 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter waitingForVote.put(key, answer != null ? answer.option : new byte[0]); TLRPC.TL_messages_sendVote req = new TLRPC.TL_messages_sendVote(); req.msg_id = messageObject.getId(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) messageObject.getDialogId()); + req.peer = getMessagesController().getInputPeer((int) messageObject.getDialogId()); if (answer != null) { req.options.add(answer.option); } - return ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + return getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { voteSendTime.put(messageObject.getPollId(), 0L); - MessagesController.getInstance(currentAccount).processUpdates((TLRPC.Updates) response, false); + getMessagesController().processUpdates((TLRPC.Updates) response, false); voteSendTime.put(messageObject.getPollId(), SystemClock.uptimeMillis()); } AndroidUtilities.runOnUIThread(new Runnable() { @@ -2027,7 +2025,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } else if (button instanceof TLRPC.TL_keyboardButtonBuy) { if (response instanceof TLRPC.TL_payments_paymentForm) { final TLRPC.TL_payments_paymentForm form = (TLRPC.TL_payments_paymentForm) response; - MessagesController.getInstance(currentAccount).putUsers(form.users, false); + getMessagesController().putUsers(form.users, false); parentFragment.presentFragment(new PaymentFormActivity(form, messageObject)); } else if (response instanceof TLRPC.TL_payments_paymentReceipt) { parentFragment.presentFragment(new PaymentFormActivity(messageObject, (TLRPC.TL_payments_paymentReceipt) response)); @@ -2035,7 +2033,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } else { TLRPC.TL_messages_botCallbackAnswer res = (TLRPC.TL_messages_botCallbackAnswer) response; if (!cacheFinal && res.cache_time != 0) { - MessagesStorage.getInstance(currentAccount).saveBotCache(key, res); + getMessagesStorage().saveBotCache(key, res); } if (res.message != null) { int uid = messageObject.messageOwner.from_id; @@ -2044,12 +2042,12 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } String name = null; if (uid > 0) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(uid); + TLRPC.User user = getMessagesController().getUser(uid); if (user != null) { name = ContactsController.formatName(user.first_name, user.last_name); } } else { - TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-uid); + TLRPC.Chat chat = getMessagesController().getChat(-uid); if (chat != null) { name = chat.title; } @@ -2077,7 +2075,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (messageObject.messageOwner.via_bot_id != 0) { uid = messageObject.messageOwner.via_bot_id; } - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(uid); + TLRPC.User user = getMessagesController().getUser(uid); boolean verified = user != null && user.verified; if (button instanceof TLRPC.TL_keyboardButtonGame) { TLRPC.TL_game game = messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame ? messageObject.messageOwner.media.game : null; @@ -2093,35 +2091,35 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } }); if (cacheFinal) { - MessagesStorage.getInstance(currentAccount).getBotCache(key, requestDelegate); + getMessagesStorage().getBotCache(key, requestDelegate); } else { if (button instanceof TLRPC.TL_keyboardButtonUrlAuth) { TLRPC.TL_messages_requestUrlAuth req = new TLRPC.TL_messages_requestUrlAuth(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) messageObject.getDialogId()); + req.peer = getMessagesController().getInputPeer((int) messageObject.getDialogId()); req.msg_id = messageObject.getId(); req.button_id = button.button_id; request[0] = req; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, requestDelegate, ConnectionsManager.RequestFlagFailOnServerErrors); + getConnectionsManager().sendRequest(req, requestDelegate, ConnectionsManager.RequestFlagFailOnServerErrors); } else if (button instanceof TLRPC.TL_keyboardButtonBuy) { if ((messageObject.messageOwner.media.flags & 4) == 0) { TLRPC.TL_payments_getPaymentForm req = new TLRPC.TL_payments_getPaymentForm(); req.msg_id = messageObject.getId(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, requestDelegate, ConnectionsManager.RequestFlagFailOnServerErrors); + getConnectionsManager().sendRequest(req, requestDelegate, ConnectionsManager.RequestFlagFailOnServerErrors); } else { TLRPC.TL_payments_getPaymentReceipt req = new TLRPC.TL_payments_getPaymentReceipt(); req.msg_id = messageObject.messageOwner.media.receipt_msg_id; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, requestDelegate, ConnectionsManager.RequestFlagFailOnServerErrors); + getConnectionsManager().sendRequest(req, requestDelegate, ConnectionsManager.RequestFlagFailOnServerErrors); } } else { TLRPC.TL_messages_getBotCallbackAnswer req = new TLRPC.TL_messages_getBotCallbackAnswer(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) messageObject.getDialogId()); + req.peer = getMessagesController().getInputPeer((int) messageObject.getDialogId()); req.msg_id = messageObject.getId(); req.game = button instanceof TLRPC.TL_keyboardButtonGame; if (button.data != null) { req.flags |= 1; req.data = button.data; } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, requestDelegate, ConnectionsManager.RequestFlagFailOnServerErrors); + getConnectionsManager().sendRequest(req, requestDelegate, ConnectionsManager.RequestFlagFailOnServerErrors); } } } @@ -2168,16 +2166,16 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } catch (Exception e) { FileLog.e(e); } - newTaskId = MessagesStorage.getInstance(currentAccount).createPendingTask(data); + newTaskId = getMessagesStorage().createPendingTask(data); } else { newTaskId = taskId; } - ConnectionsManager.getInstance(currentAccount).sendRequest(request, (response, error) -> { + getConnectionsManager().sendRequest(request, (response, error) -> { if (error == null) { - MessagesController.getInstance(currentAccount).processUpdates((TLRPC.Updates) response, false); + getMessagesController().processUpdates((TLRPC.Updates) response, false); } if (newTaskId != 0) { - MessagesStorage.getInstance(currentAccount).removePendingTask(newTaskId); + getMessagesStorage().removePendingTask(newTaskId); } }); } @@ -2238,21 +2236,21 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter int high_id = (int) (peer >> 32); boolean isChannel = false; TLRPC.EncryptedChat encryptedChat = null; - TLRPC.InputPeer sendToPeer = lower_id != 0 ? MessagesController.getInstance(currentAccount).getInputPeer(lower_id) : null; + TLRPC.InputPeer sendToPeer = lower_id != 0 ? getMessagesController().getInputPeer(lower_id) : null; ArrayList sendToPeers = null; if (lower_id == 0) { - encryptedChat = MessagesController.getInstance(currentAccount).getEncryptedChat(high_id); + encryptedChat = getMessagesController().getEncryptedChat(high_id); if (encryptedChat == null) { if (retryMessageObject != null) { - MessagesStorage.getInstance(currentAccount).markMessageAsSendError(retryMessageObject.messageOwner); + getMessagesStorage().markMessageAsSendError(retryMessageObject.messageOwner); retryMessageObject.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageSendError, retryMessageObject.getId()); + getNotificationCenter().postNotificationName(NotificationCenter.messageSendError, retryMessageObject.getId()); processSentMessage(retryMessageObject.getId()); } return; } } else if (sendToPeer instanceof TLRPC.TL_inputPeerChannel) { - TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(sendToPeer.channel_id); + TLRPC.Chat chat = getMessagesController().getChat(sendToPeer.channel_id); isChannel = chat != null && !chat.megagroup; } @@ -2446,7 +2444,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } else { newMsg.attachPath = path; } - if (encryptedChat != null && MessageObject.isStickerDocument(document)) { + if (encryptedChat != null && (MessageObject.isStickerDocument(document) || MessageObject.isAnimatedStickerDocument(document))) { for (int a = 0; a < document.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = document.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeSticker) { @@ -2459,7 +2457,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (attribute.stickerset instanceof TLRPC.TL_inputStickerSetShortName) { name = attribute.stickerset.short_name; } else { - name = DataQuery.getInstance(currentAccount).getStickerSetName(attribute.stickerset.id); + name = getMediaDataController().getStickerSetName(attribute.stickerset.id); } if (!TextUtils.isEmpty(name)) { attributeSticker.stickerset = new TLRPC.TL_inputStickerSetShortName(); @@ -2487,15 +2485,15 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (newMsg.attachPath == null) { newMsg.attachPath = ""; } - newMsg.local_id = newMsg.id = UserConfig.getInstance(currentAccount).getNewMessageId(); + newMsg.local_id = newMsg.id = getUserConfig().getNewMessageId(); newMsg.out = true; if (isChannel && sendToPeer != null) { newMsg.from_id = -sendToPeer.channel_id; } else { - newMsg.from_id = UserConfig.getInstance(currentAccount).getClientUserId(); + newMsg.from_id = getUserConfig().getClientUserId(); newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_FROM_ID; } - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().saveConfig(false); } if (newMsg.random_id == 0) { newMsg.random_id = getNextRandomId(); @@ -2513,13 +2511,13 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } newMsg.params = params; if (retryMessageObject == null || !retryMessageObject.resendAsIs) { - newMsg.date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + newMsg.date = getConnectionsManager().getCurrentTime(); if (sendToPeer instanceof TLRPC.TL_inputPeerChannel) { if (isChannel) { newMsg.views = 1; newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_VIEWS; } - TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(sendToPeer.channel_id); + TLRPC.Chat chat = getMessagesController().getChat(sendToPeer.channel_id); if (chat != null) { if (chat.megagroup) { newMsg.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; @@ -2527,7 +2525,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } else { newMsg.post = true; if (chat.signatures) { - newMsg.from_id = UserConfig.getInstance(currentAccount).getClientUserId(); + newMsg.from_id = getUserConfig().getClientUserId(); } } } @@ -2553,15 +2551,15 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (lower_id != 0) { if (high_id == 1) { if (currentChatInfo == null) { - MessagesStorage.getInstance(currentAccount).markMessageAsSendError(newMsg); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageSendError, newMsg.id); + getMessagesStorage().markMessageAsSendError(newMsg); + getNotificationCenter().postNotificationName(NotificationCenter.messageSendError, newMsg.id); processSentMessage(newMsg.id); return; } sendToPeers = new ArrayList<>(); for (TLRPC.ChatParticipant participant : currentChatInfo.participants.participants) { - TLRPC.User sendToUser = MessagesController.getInstance(currentAccount).getUser(participant.user_id); - TLRPC.InputUser peerUser = MessagesController.getInstance(currentAccount).getInputUser(sendToUser); + TLRPC.User sendToUser = getMessagesController().getUser(participant.user_id); + TLRPC.InputUser peerUser = getMessagesController().getInputUser(sendToUser); if (peerUser != null) { sendToPeers.add(peerUser); } @@ -2569,9 +2567,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter newMsg.to_id = new TLRPC.TL_peerChat(); newMsg.to_id.chat_id = lower_id; } else { - newMsg.to_id = MessagesController.getInstance(currentAccount).getPeer(lower_id); + newMsg.to_id = getMessagesController().getPeer(lower_id); if (lower_id > 0) { - TLRPC.User sendToUser = MessagesController.getInstance(currentAccount).getUser(lower_id); + TLRPC.User sendToUser = getMessagesController().getUser(lower_id); if (sendToUser == null) { processSentMessage(newMsg.id); return; @@ -2583,7 +2581,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } else { newMsg.to_id = new TLRPC.TL_peerUser(); - if (encryptedChat.participant_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (encryptedChat.participant_id == getUserConfig().getClientUserId()) { newMsg.to_id.user_id = encryptedChat.admin_id; } else { newMsg.to_id.user_id = encryptedChat.participant_id; @@ -2652,9 +2650,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter objArr.add(newMsgObj); ArrayList arr = new ArrayList<>(); arr.add(newMsg); - MessagesStorage.getInstance(currentAccount).putMessages(arr, false, true, false, 0); - MessagesController.getInstance(currentAccount).updateInterfaceWithMessages(peer, objArr); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getMessagesStorage().putMessages(arr, false, true, false, 0); + getMessagesController().updateInterfaceWithMessages(peer, objArr); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } else { String key = "group_" + groupId; ArrayList arrayList = delayedMessages.get(key); @@ -2718,7 +2716,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } performSendMessageRequest(reqSend, newMsgObj, null, null, parentObject); if (retryMessageObject == null) { - DataQuery.getInstance(currentAccount).cleanDraft(peer, false); + getMediaDataController().cleanDraft(peer, false); } } } else { @@ -2750,9 +2748,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } else { reqSend.media = new TLRPC.TL_decryptedMessageMediaEmpty(); } - SecretChatHelper.getInstance(currentAccount).performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null, newMsgObj); + getSecretChatHelper().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null, newMsgObj); if (retryMessageObject == null) { - DataQuery.getInstance(currentAccount).cleanDraft(peer, false); + getMediaDataController().cleanDraft(peer, false); } } } else if (type >= 1 && type <= 3 || type >= 5 && type <= 8 || type == 9 && encryptedChat != null || type == 10) { @@ -2982,7 +2980,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } reqSend = request; if (retryMessageObject == null) { - DataQuery.getInstance(currentAccount).cleanDraft(peer, false); + getMediaDataController().cleanDraft(peer, false); } } else if (groupId != 0) { TLRPC.TL_messages_sendMultiMedia request; @@ -3113,7 +3111,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } reqSend.media.lat = location.geo.lat; reqSend.media._long = location.geo._long; - SecretChatHelper.getInstance(currentAccount).performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null, newMsgObj); + getSecretChatHelper().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null, newMsgObj); } else if (type == 2 || type == 9 && photo != null) { TLRPC.PhotoSize small = photo.sizes.get(0); TLRPC.PhotoSize big = photo.sizes.get(photo.sizes.size() - 1); @@ -3160,7 +3158,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter encryptedFile.access_hash = big.location.secret; reqSend.media.key = big.location.key; reqSend.media.iv = big.location.iv; - SecretChatHelper.getInstance(currentAccount).performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, encryptedFile, null, newMsgObj); + getSecretChatHelper().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, encryptedFile, null, newMsgObj); } } else if (type == 3) { TLRPC.PhotoSize thumb = getThumbForSecretChat(document.thumbs); @@ -3220,7 +3218,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter encryptedFile.access_hash = document.access_hash; reqSend.media.key = document.key; reqSend.media.iv = document.iv; - SecretChatHelper.getInstance(currentAccount).performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, encryptedFile, null, newMsgObj); + getSecretChatHelper().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, encryptedFile, null, newMsgObj); } } else if (type == 6) { reqSend.media = new TLRPC.TL_decryptedMessageMediaContact(); @@ -3228,9 +3226,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter reqSend.media.first_name = user.first_name; reqSend.media.last_name = user.last_name; reqSend.media.user_id = user.id; - SecretChatHelper.getInstance(currentAccount).performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null, newMsgObj); + getSecretChatHelper().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null, newMsgObj); } else if (type == 7 || type == 9 && document != null) { - if (MessageObject.isStickerDocument(document)) { + if (MessageObject.isStickerDocument(document) || MessageObject.isAnimatedStickerDocument(document)) { reqSend.media = new TLRPC.TL_decryptedMessageMediaExternalDocument(); reqSend.media.id = document.id; reqSend.media.date = document.date; @@ -3246,7 +3244,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter ((TLRPC.TL_decryptedMessageMediaExternalDocument) reqSend.media).thumb = new TLRPC.TL_photoSizeEmpty(); ((TLRPC.TL_decryptedMessageMediaExternalDocument) reqSend.media).thumb.type = "s"; } - SecretChatHelper.getInstance(currentAccount).performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null, newMsgObj); + getSecretChatHelper().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null, newMsgObj); } else { reqSend.media = new TLRPC.TL_decryptedMessageMediaDocument(); reqSend.media.attributes = document.attributes; @@ -3288,7 +3286,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter encryptedFile.access_hash = document.access_hash; reqSend.media.key = document.key; reqSend.media.iv = document.iv; - SecretChatHelper.getInstance(currentAccount).performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, encryptedFile, null, newMsgObj); + getSecretChatHelper().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, encryptedFile, null, newMsgObj); } } } else if (type == 8) { @@ -3338,7 +3336,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter performSendDelayedMessage(delayedMessage); } if (retryMessageObject == null) { - DataQuery.getInstance(currentAccount).cleanDraft(peer, false); + getMediaDataController().cleanDraft(peer, false); } } } else if (type == 4) { @@ -3346,7 +3344,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter reqSend.to_peer = sendToPeer; reqSend.with_my_score = retryMessageObject.messageOwner.with_my_score; if (retryMessageObject.messageOwner.ttl != 0) { - TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-retryMessageObject.messageOwner.ttl); + TLRPC.Chat chat = getMessagesController().getChat(-retryMessageObject.messageOwner.ttl); reqSend.from_peer = new TLRPC.TL_inputPeerChannel(); reqSend.from_peer.channel_id = -retryMessageObject.messageOwner.ttl; if (chat != null) { @@ -3385,17 +3383,17 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter reqSend.id = params.get("id"); if (retryMessageObject == null) { reqSend.clear_draft = true; - DataQuery.getInstance(currentAccount).cleanDraft(peer, false); + getMediaDataController().cleanDraft(peer, false); } performSendMessageRequest(reqSend, newMsgObj, null, null, parentObject); } } catch (Exception e) { FileLog.e(e); - MessagesStorage.getInstance(currentAccount).markMessageAsSendError(newMsg); + getMessagesStorage().markMessageAsSendError(newMsg); if (newMsgObj != null) { newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageSendError, newMsg.id); + getNotificationCenter().postNotificationName(NotificationCenter.messageSendError, newMsg.id); processSentMessage(newMsg.id); } } @@ -3441,7 +3439,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (message.sendRequest != null) { String location = FileLoader.getPathToAttach(message.photoSize).toString(); putToDelayedMessages(location, message); - FileLoader.getInstance(currentAccount).uploadFile(location, false, true, ConnectionsManager.FileTypePhoto); + getFileLoader().uploadFile(location, false, true, ConnectionsManager.FileTypePhoto); } else { String location = FileLoader.getPathToAttach(message.photoSize).toString(); if (message.sendEncryptedRequest != null && message.photoSize.location.dc_id != 0) { @@ -3452,12 +3450,12 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } if (!file.exists()) { putToDelayedMessages(FileLoader.getAttachFileName(message.photoSize), message); - FileLoader.getInstance(currentAccount).loadFile(ImageLocation.getForObject(message.photoSize, message.locationParent), message.parentObject, "jpg", 2, 0); + getFileLoader().loadFile(ImageLocation.getForObject(message.photoSize, message.locationParent), message.parentObject, "jpg", 2, 0); return; } } putToDelayedMessages(location, message); - FileLoader.getInstance(currentAccount).uploadFile(location, true, true, ConnectionsManager.FileTypePhoto); + getFileLoader().uploadFile(location, true, true, ConnectionsManager.FileTypePhoto); } } } else if (message.type == 1) { @@ -3487,7 +3485,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter decryptedMessage.media.size = (int) message.videoEditedInfo.estimatedSize; decryptedMessage.media.key = message.videoEditedInfo.key; decryptedMessage.media.iv = message.videoEditedInfo.iv; - SecretChatHelper.getInstance(currentAccount).performSendEncryptedRequest(decryptedMessage, message.obj.messageOwner, message.encryptedChat, message.videoEditedInfo.encryptedFile, message.originalPath, message.obj); + getSecretChatHelper().performSendEncryptedRequest(decryptedMessage, message.obj.messageOwner, message.encryptedChat, message.videoEditedInfo.encryptedFile, message.originalPath, message.obj); message.videoEditedInfo.encryptedFile = null; return; } @@ -3509,14 +3507,14 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } putToDelayedMessages(location, message); if (message.obj.videoEditedInfo != null && message.obj.videoEditedInfo.needConvert()) { - FileLoader.getInstance(currentAccount).uploadFile(location, false, false, document.size, ConnectionsManager.FileTypeVideo); + getFileLoader().uploadFile(location, false, false, document.size, ConnectionsManager.FileTypeVideo); } else { - FileLoader.getInstance(currentAccount).uploadFile(location, false, false, ConnectionsManager.FileTypeVideo); + getFileLoader().uploadFile(location, false, false, ConnectionsManager.FileTypeVideo); } } else { String location = FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + message.photoSize.location.volume_id + "_" + message.photoSize.location.local_id + ".jpg"; putToDelayedMessages(location, message); - FileLoader.getInstance(currentAccount).uploadFile(location, false, true, ConnectionsManager.FileTypePhoto); + getFileLoader().uploadFile(location, false, true, ConnectionsManager.FileTypePhoto); } } else { String location = message.obj.messageOwner.attachPath; @@ -3528,15 +3526,15 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter File file = new File(location); if (!file.exists()) { putToDelayedMessages(FileLoader.getAttachFileName(document), message); - FileLoader.getInstance(currentAccount).loadFile(document, message.parentObject, 2, 0); + getFileLoader().loadFile(document, message.parentObject, 2, 0); return; } } putToDelayedMessages(location, message); if (message.obj.videoEditedInfo != null && message.obj.videoEditedInfo.needConvert()) { - FileLoader.getInstance(currentAccount).uploadFile(location, true, false, document.size, ConnectionsManager.FileTypeVideo); + getFileLoader().uploadFile(location, true, false, document.size, ConnectionsManager.FileTypeVideo); } else { - FileLoader.getInstance(currentAccount).uploadFile(location, true, false, ConnectionsManager.FileTypeVideo); + getFileLoader().uploadFile(location, true, false, ConnectionsManager.FileTypeVideo); } } } @@ -3557,11 +3555,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (media.file == null) { String location = message.obj.messageOwner.attachPath; putToDelayedMessages(location, message); - FileLoader.getInstance(currentAccount).uploadFile(location, message.sendRequest == null, false, ConnectionsManager.FileTypeFile); + getFileLoader().uploadFile(location, message.sendRequest == null, false, ConnectionsManager.FileTypeFile); } else if (media.thumb == null && message.photoSize != null) { String location = FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + message.photoSize.location.volume_id + "_" + message.photoSize.location.local_id + ".jpg"; putToDelayedMessages(location, message); - FileLoader.getInstance(currentAccount).uploadFile(location, false, true, ConnectionsManager.FileTypePhoto); + getFileLoader().uploadFile(location, false, true, ConnectionsManager.FileTypePhoto); } } else { String location = message.obj.messageOwner.attachPath; @@ -3570,18 +3568,18 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter File file = new File(location); if (!file.exists()) { putToDelayedMessages(FileLoader.getAttachFileName(document), message); - FileLoader.getInstance(currentAccount).loadFile(document, message.parentObject, 2, 0); + getFileLoader().loadFile(document, message.parentObject, 2, 0); return; } } putToDelayedMessages(location, message); - FileLoader.getInstance(currentAccount).uploadFile(location, true, false, ConnectionsManager.FileTypeFile); + getFileLoader().uploadFile(location, true, false, ConnectionsManager.FileTypeFile); } } } else if (message.type == 3) { String location = message.obj.messageOwner.attachPath; putToDelayedMessages(location, message); - FileLoader.getInstance(currentAccount).uploadFile(location, message.sendRequest == null, true, ConnectionsManager.FileTypeAudio); + getFileLoader().uploadFile(location, message.sendRequest == null, true, ConnectionsManager.FileTypeAudio); } else if (message.type == 4) { boolean add = index < 0; if (message.performMediaUpload) { @@ -3621,9 +3619,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter message.extraHashMap.put(documentLocation + "_t", message.photoSize); } if (messageObject.videoEditedInfo != null && messageObject.videoEditedInfo.needConvert()) { - FileLoader.getInstance(currentAccount).uploadFile(documentLocation, false, false, document.size, ConnectionsManager.FileTypeVideo); + getFileLoader().uploadFile(documentLocation, false, false, document.size, ConnectionsManager.FileTypeVideo); } else { - FileLoader.getInstance(currentAccount).uploadFile(documentLocation, false, false, ConnectionsManager.FileTypeVideo); + getFileLoader().uploadFile(documentLocation, false, false, ConnectionsManager.FileTypeVideo); } } else { String location = FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + message.photoSize.location.volume_id + "_" + message.photoSize.location.local_id + ".jpg"; @@ -3631,7 +3629,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter message.extraHashMap.put(location + "_o", documentLocation); message.extraHashMap.put(messageObject, location); message.extraHashMap.put(location, media); - FileLoader.getInstance(currentAccount).uploadFile(location, false, true, ConnectionsManager.FileTypePhoto); + getFileLoader().uploadFile(location, false, true, ConnectionsManager.FileTypePhoto); } } else { TLRPC.TL_messages_sendEncryptedMultiMedia request = (TLRPC.TL_messages_sendEncryptedMultiMedia) message.sendEncryptedRequest; @@ -3643,9 +3641,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter message.extraHashMap.put(documentLocation + "_t", message.photoSize); } if (messageObject.videoEditedInfo != null && messageObject.videoEditedInfo.needConvert()) { - FileLoader.getInstance(currentAccount).uploadFile(documentLocation, true, false, document.size, ConnectionsManager.FileTypeVideo); + getFileLoader().uploadFile(documentLocation, true, false, document.size, ConnectionsManager.FileTypeVideo); } else { - FileLoader.getInstance(currentAccount).uploadFile(documentLocation, true, false, ConnectionsManager.FileTypeVideo); + getFileLoader().uploadFile(documentLocation, true, false, ConnectionsManager.FileTypeVideo); } } } @@ -3671,7 +3669,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter putToDelayedMessages(location, message); message.extraHashMap.put(location, inputMedia); message.extraHashMap.put(messageObject, location); - FileLoader.getInstance(currentAccount).uploadFile(location, message.sendEncryptedRequest != null, true, ConnectionsManager.FileTypePhoto); + getFileLoader().uploadFile(location, message.sendEncryptedRequest != null, true, ConnectionsManager.FileTypePhoto); message.photoSize = null; } } @@ -3689,7 +3687,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter for (int a = 0; a < multiMedia.multi_media.size(); a++) { if (multiMedia.multi_media.get(a).media == inputMedia) { putToSendingMessages(message.messages.get(a)); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.FileUploadProgressChanged, key, 1.0f, false); + getNotificationCenter().postNotificationName(NotificationCenter.FileUploadProgressChanged, key, 1.0f, false); break; } } @@ -3697,7 +3695,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter TLRPC.TL_messages_uploadMedia req = new TLRPC.TL_messages_uploadMedia(); req.media = inputMedia; req.peer = ((TLRPC.TL_messages_sendMultiMedia) message.sendRequest).peer; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { TLRPC.InputMedia newInputMedia = null; if (response != null) { TLRPC.MessageMedia messageMedia = (TLRPC.MessageMedia) response; @@ -3739,7 +3737,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter for (int a = 0; a < multiMedia.files.size(); a++) { if (multiMedia.files.get(a) == inputEncryptedFile) { putToSendingMessages(message.messages.get(a)); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.FileUploadProgressChanged, key, 1.0f, false); + getNotificationCenter().postNotificationName(NotificationCenter.FileUploadProgressChanged, key, 1.0f, false); break; } } @@ -3760,9 +3758,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter return; } else if (add) { delayedMessages.remove(key); - MessagesStorage.getInstance(currentAccount).putMessages(message.messages, false, true, false, 0); - MessagesController.getInstance(currentAccount).updateInterfaceWithMessages(message.peer, message.messageObjects); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getMessagesStorage().putMessages(message.messages, false, true, false, 0); + getMessagesController().updateInterfaceWithMessages(message.peer, message.messageObjects); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } if (message.sendRequest instanceof TLRPC.TL_messages_sendMultiMedia) { TLRPC.TL_messages_sendMultiMedia request = (TLRPC.TL_messages_sendMultiMedia) message.sendRequest; @@ -3796,14 +3794,14 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (message.sendRequest instanceof TLRPC.TL_messages_sendMultiMedia) { performSendMessageRequestMulti((TLRPC.TL_messages_sendMultiMedia) message.sendRequest, message.messageObjects, message.originalPaths, message.parentObjects, message); } else { - SecretChatHelper.getInstance(currentAccount).performSendEncryptedRequest((TLRPC.TL_messages_sendEncryptedMultiMedia) message.sendEncryptedRequest, message); + getSecretChatHelper().performSendEncryptedRequest((TLRPC.TL_messages_sendEncryptedMultiMedia) message.sendEncryptedRequest, message); } message.sendDelayedRequests(); } protected void stopVideoService(final String path) { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.stopEncodingService, path, currentAccount))); + getMessagesStorage().getStorageQueue().postRunnable(() -> AndroidUtilities.runOnUIThread(() -> NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.stopEncodingService, path, currentAccount))); } protected void putToSendingMessages(TLRPC.Message message) { @@ -3826,11 +3824,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter for (int a = 0, size = msgObjs.size(); a < size; a++) { putToSendingMessages(msgObjs.get(a).messageOwner); } - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (error != null && FileRefController.isFileRefError(error.text)) { if (parentObjects != null) { ArrayList arrayList = new ArrayList<>(parentObjects); - FileRefController.getInstance(currentAccount).requestReference(arrayList, req, msgObjs, originalPaths, arrayList, delayedMessage); + getFileRefController().requestReference(arrayList, req, msgObjs, originalPaths, arrayList, delayedMessage); return; } else if (delayedMessage != null) { AndroidUtilities.runOnUIThread(new Runnable() { @@ -3874,13 +3872,13 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } else if (update instanceof TLRPC.TL_updateNewMessage) { final TLRPC.TL_updateNewMessage newMessage = (TLRPC.TL_updateNewMessage) update; newMessages.put(newMessage.message.id, newMessage.message); - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).processNewDifferenceParams(-1, newMessage.pts, -1, newMessage.pts_count)); + Utilities.stageQueue.postRunnable(() -> getMessagesController().processNewDifferenceParams(-1, newMessage.pts, -1, newMessage.pts_count)); updatesArr.remove(a); a--; } else if (update instanceof TLRPC.TL_updateNewChannelMessage) { final TLRPC.TL_updateNewChannelMessage newMessage = (TLRPC.TL_updateNewChannelMessage) update; newMessages.put(newMessage.message.id, newMessage.message); - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).processNewChannelDifferenceParams(newMessage.pts, newMessage.pts_count, newMessage.message.to_id.channel_id)); + Utilities.stageQueue.postRunnable(() -> getMessagesController().processNewChannelDifferenceParams(newMessage.pts, newMessage.pts_count, newMessage.message.to_id.channel_id)); updatesArr.remove(a); a--; } @@ -3909,10 +3907,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } grouped_id = message.grouped_id; - Integer value = MessagesController.getInstance(currentAccount).dialogs_read_outbox_max.get(message.dialog_id); + Integer value = getMessagesController().dialogs_read_outbox_max.get(message.dialog_id); if (value == null) { - value = MessagesStorage.getInstance(currentAccount).getDialogReadMax(message.out, message.dialog_id); - MessagesController.getInstance(currentAccount).dialogs_read_outbox_max.put(message.dialog_id, value); + value = getMessagesStorage().getDialogReadMax(message.out, message.dialog_id); + getMessagesController().dialogs_read_outbox_max.put(message.dialog_id, value); } message.unread = value < message.id; } else { @@ -3925,22 +3923,22 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } if (!isSentError) { - StatsController.getInstance(currentAccount).incrementSentItemsCount(ApplicationLoader.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, 1); + getStatsController().incrementSentItemsCount(ApplicationLoader.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, 1); newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newMsgObj.id, newMsgObj, newMsgObj.dialog_id, grouped_id, existFlags); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { - MessagesStorage.getInstance(currentAccount).updateMessageStateAndId(newMsgObj.random_id, oldId, newMsgObj.id, 0, false, newMsgObj.to_id.channel_id); - MessagesStorage.getInstance(currentAccount).putMessages(sentMessages, true, false, false, 0); + getNotificationCenter().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newMsgObj.id, newMsgObj, newMsgObj.dialog_id, grouped_id, existFlags); + getMessagesStorage().getStorageQueue().postRunnable(() -> { + getMessagesStorage().updateMessageStateAndId(newMsgObj.random_id, oldId, newMsgObj.id, 0, false, newMsgObj.to_id.channel_id); + getMessagesStorage().putMessages(sentMessages, true, false, false, 0); AndroidUtilities.runOnUIThread(() -> { - DataQuery.getInstance(currentAccount).increasePeerRaiting(newMsgObj.dialog_id); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newMsgObj.id, newMsgObj, newMsgObj.dialog_id, grouped_id, existFlags); + getMediaDataController().increasePeerRaiting(newMsgObj.dialog_id); + getNotificationCenter().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newMsgObj.id, newMsgObj, newMsgObj.dialog_id, grouped_id, existFlags); processSentMessage(oldId); removeFromSendingMessages(oldId); }); }); } } - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).processUpdates(updates, false)); + Utilities.stageQueue.postRunnable(() -> getMessagesController().processUpdates(updates, false)); } else { AlertsCreator.processError(currentAccount, error, null, req); isSentError = true; @@ -3948,9 +3946,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (isSentError) { for (int i = 0; i < msgObjs.size(); i++) { TLRPC.Message newMsgObj = msgObjs.get(i).messageOwner; - MessagesStorage.getInstance(currentAccount).markMessageAsSendError(newMsgObj); + getMessagesStorage().markMessageAsSendError(newMsgObj); newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageSendError, newMsgObj.id); + getNotificationCenter().postNotificationName(NotificationCenter.messageSendError, newMsgObj.id); processSentMessage(newMsgObj.id); removeFromSendingMessages(newMsgObj.id); } @@ -4004,10 +4002,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } final TLRPC.Message newMsgObj = msgObj.messageOwner; putToSendingMessages(newMsgObj); - newMsgObj.reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + newMsgObj.reqId = getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null && (req instanceof TLRPC.TL_messages_sendMedia || req instanceof TLRPC.TL_messages_editMessage) && FileRefController.isFileRefError(error.text)) { if (parentObject != null) { - FileRefController.getInstance(currentAccount).requestReference(parentObject, req, msgObj, originalPath, parentMessage, check, delayedMessage); + getFileRefController().requestReference(parentObject, req, msgObj, originalPath, parentMessage, check, delayedMessage); return; } else if (delayedMessage != null) { AndroidUtilities.runOnUIThread(new Runnable() { @@ -4061,7 +4059,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter updateMediaPaths(msgObj, message, message.id, originalPath, false); } Utilities.stageQueue.postRunnable(() -> { - MessagesController.getInstance(currentAccount).processUpdates(updates, false); + getMessagesController().processUpdates(updates, false); AndroidUtilities.runOnUIThread(() -> { processSentMessage(newMsgObj.id); removeFromSendingMessages(newMsgObj.id); @@ -4108,7 +4106,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (!newMsgObj.entities.isEmpty()) { newMsgObj.flags |= TLRPC.MESSAGE_FLAG_HAS_ENTITIES; } - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).processNewDifferenceParams(-1, res.pts, res.date, res.pts_count)); + Utilities.stageQueue.postRunnable(() -> getMessagesController().processNewDifferenceParams(-1, res.pts, res.date, res.pts_count)); sentMessages.add(newMsgObj); } else if (response instanceof TLRPC.Updates) { final TLRPC.Updates updates = (TLRPC.Updates) response; @@ -4119,7 +4117,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (update instanceof TLRPC.TL_updateNewMessage) { final TLRPC.TL_updateNewMessage newMessage = (TLRPC.TL_updateNewMessage) update; sentMessages.add(message = newMessage.message); - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).processNewDifferenceParams(-1, newMessage.pts, -1, newMessage.pts_count)); + Utilities.stageQueue.postRunnable(() -> getMessagesController().processNewDifferenceParams(-1, newMessage.pts, -1, newMessage.pts_count)); updatesArr.remove(a); break; } else if (update instanceof TLRPC.TL_updateNewChannelMessage) { @@ -4128,17 +4126,17 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if ((newMsgObj.flags & TLRPC.MESSAGE_FLAG_MEGAGROUP) != 0) { newMessage.message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; } - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).processNewChannelDifferenceParams(newMessage.pts, newMessage.pts_count, newMessage.message.to_id.channel_id)); + Utilities.stageQueue.postRunnable(() -> getMessagesController().processNewChannelDifferenceParams(newMessage.pts, newMessage.pts_count, newMessage.message.to_id.channel_id)); updatesArr.remove(a); break; } } if (message != null) { ImageLoader.saveMessageThumbs(message); - Integer value = MessagesController.getInstance(currentAccount).dialogs_read_outbox_max.get(message.dialog_id); + Integer value = getMessagesController().dialogs_read_outbox_max.get(message.dialog_id); if (value == null) { - value = MessagesStorage.getInstance(currentAccount).getDialogReadMax(message.out, message.dialog_id); - MessagesController.getInstance(currentAccount).dialogs_read_outbox_max.put(message.dialog_id, value); + value = getMessagesStorage().getDialogReadMax(message.out, message.dialog_id); + getMessagesController().dialogs_read_outbox_max.put(message.dialog_id, value); } message.unread = value < message.id; updateMediaPaths(msgObj, message, message.id, originalPath, false); @@ -4148,26 +4146,26 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter isSentError = true; existFlags = 0; } - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).processUpdates(updates, false)); + Utilities.stageQueue.postRunnable(() -> getMessagesController().processUpdates(updates, false)); } else { existFlags = 0; } if (MessageObject.isLiveLocationMessage(newMsgObj)) { - LocationController.getInstance(currentAccount).addSharingLocation(newMsgObj.dialog_id, newMsgObj.id, newMsgObj.media.period, newMsgObj); + getLocationController().addSharingLocation(newMsgObj.dialog_id, newMsgObj.id, newMsgObj.media.period, newMsgObj); } if (!isSentError) { - StatsController.getInstance(currentAccount).incrementSentItemsCount(ApplicationLoader.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, 1); + getStatsController().incrementSentItemsCount(ApplicationLoader.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, 1); newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageReceivedByServer, oldId, (isBroadcast ? oldId : newMsgObj.id), newMsgObj, newMsgObj.dialog_id, 0L, existFlags); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { - MessagesStorage.getInstance(currentAccount).updateMessageStateAndId(newMsgObj.random_id, oldId, (isBroadcast ? oldId : newMsgObj.id), 0, false, newMsgObj.to_id.channel_id); - MessagesStorage.getInstance(currentAccount).putMessages(sentMessages, true, false, isBroadcast, 0); + getNotificationCenter().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, (isBroadcast ? oldId : newMsgObj.id), newMsgObj, newMsgObj.dialog_id, 0L, existFlags); + getMessagesStorage().getStorageQueue().postRunnable(() -> { + getMessagesStorage().updateMessageStateAndId(newMsgObj.random_id, oldId, (isBroadcast ? oldId : newMsgObj.id), 0, false, newMsgObj.to_id.channel_id); + getMessagesStorage().putMessages(sentMessages, true, false, isBroadcast, 0); if (isBroadcast) { ArrayList currentMessage = new ArrayList<>(); currentMessage.add(newMsgObj); - MessagesStorage.getInstance(currentAccount).putMessages(currentMessage, true, false, false, 0); + getMessagesStorage().putMessages(currentMessage, true, false, false, 0); } AndroidUtilities.runOnUIThread(() -> { if (isBroadcast) { @@ -4176,12 +4174,12 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter ArrayList arr = new ArrayList<>(); MessageObject messageObject = new MessageObject(currentAccount, message, false); arr.add(messageObject); - MessagesController.getInstance(currentAccount).updateInterfaceWithMessages(messageObject.getDialogId(), arr, true); + getMessagesController().updateInterfaceWithMessages(messageObject.getDialogId(), arr, true); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogsNeedReload); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } - DataQuery.getInstance(currentAccount).increasePeerRaiting(newMsgObj.dialog_id); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageReceivedByServer, oldId, (isBroadcast ? oldId : newMsgObj.id), newMsgObj, newMsgObj.dialog_id, 0L, existFlags); + getMediaDataController().increasePeerRaiting(newMsgObj.dialog_id); + getNotificationCenter().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, (isBroadcast ? oldId : newMsgObj.id), newMsgObj, newMsgObj.dialog_id, 0L, existFlags); processSentMessage(oldId); removeFromSendingMessages(oldId); }); @@ -4195,9 +4193,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter isSentError = true; } if (isSentError) { - MessagesStorage.getInstance(currentAccount).markMessageAsSendError(newMsgObj); + getMessagesStorage().markMessageAsSendError(newMsgObj); newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageSendError, newMsgObj.id); + getNotificationCenter().postNotificationName(NotificationCenter.messageSendError, newMsgObj.id); processSentMessage(newMsgObj.id); if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isRoundVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) { stopVideoService(newMsgObj.attachPath); @@ -4210,7 +4208,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter final int msg_id = newMsgObj.id; AndroidUtilities.runOnUIThread(() -> { newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageReceivedByAck, msg_id); + getNotificationCenter().postNotificationName(NotificationCenter.messageReceivedByAck, msg_id); }); }, ConnectionsManager.RequestFlagCanCompress | ConnectionsManager.RequestFlagInvokeAfter | (req instanceof TLRPC.TL_messages_sendMessage ? ConnectionsManager.RequestFlagNeedQuickAck : 0)); @@ -4277,7 +4275,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } if (sentMessage.media instanceof TLRPC.TL_messageMediaPhoto && sentMessage.media.photo != null && newMsg.media instanceof TLRPC.TL_messageMediaPhoto && newMsg.media.photo != null) { if (sentMessage.media.ttl_seconds == 0) { - MessagesStorage.getInstance(currentAccount).putSentFile(originalPath, sentMessage.media.photo, 0, "sent_" + sentMessage.to_id.channel_id + "_" + sentMessage.id); + getMessagesStorage().putSentFile(originalPath, sentMessage.media.photo, 0, "sent_" + sentMessage.to_id.channel_id + "_" + sentMessage.id); } if (newMsg.media.photo.sizes.size() == 1 && newMsg.media.photo.sizes.get(0).location instanceof TLRPC.TL_fileLocationUnavailable) { @@ -4323,12 +4321,12 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (sentMessage.media.ttl_seconds == 0) { boolean isVideo = MessageObject.isVideoMessage(sentMessage); if ((isVideo || MessageObject.isGifMessage(sentMessage)) && MessageObject.isGifDocument(sentMessage.media.document) == MessageObject.isGifDocument(newMsg.media.document)) { - MessagesStorage.getInstance(currentAccount).putSentFile(originalPath, sentMessage.media.document, 2, "sent_" + sentMessage.to_id.channel_id + "_" + sentMessage.id); + getMessagesStorage().putSentFile(originalPath, sentMessage.media.document, 2, "sent_" + sentMessage.to_id.channel_id + "_" + sentMessage.id); if (isVideo) { sentMessage.attachPath = newMsg.attachPath; } } else if (!MessageObject.isVoiceMessage(sentMessage) && !MessageObject.isRoundVideoMessage(sentMessage)) { - MessagesStorage.getInstance(currentAccount).putSentFile(originalPath, sentMessage.media.document, 1, "sent_" + sentMessage.to_id.channel_id + "_" + sentMessage.id); + getMessagesStorage().putSentFile(originalPath, sentMessage.media.document, 1, "sent_" + sentMessage.to_id.channel_id + "_" + sentMessage.id); } } @@ -4377,9 +4375,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if ((sentMessage.flags & TLRPC.MESSAGE_FLAG_FWD) == 0 && MessageObject.isOut(sentMessage)) { if (MessageObject.isNewGifDocument(sentMessage.media.document)) { - DataQuery.getInstance(currentAccount).addRecentGif(sentMessage.media.document, sentMessage.date); - } else if (MessageObject.isStickerDocument(sentMessage.media.document)) { - DataQuery.getInstance(currentAccount).addRecentSticker(DataQuery.TYPE_IMAGE, sentMessage, sentMessage.media.document, sentMessage.date, false); + getMediaDataController().addRecentGif(sentMessage.media.document, sentMessage.date); + } else if (MessageObject.isStickerDocument(sentMessage.media.document) || MessageObject.isAnimatedStickerDocument(sentMessage.media.document)) { + getMediaDataController().addRecentSticker(MediaDataController.TYPE_IMAGE, sentMessage, sentMessage.media.document, sentMessage.date, false); } } @@ -4402,7 +4400,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter newMsgObj.attachPathExists = false; newMsg.attachPath = ""; if (originalPath != null && originalPath.startsWith("http")) { - MessagesStorage.getInstance(currentAccount).addRecentLocalFile(originalPath, cacheFile2.toString(), newMsg.media.document); + getMessagesStorage().addRecentLocalFile(originalPath, cacheFile2.toString(), newMsg.media.document); } } } @@ -4450,14 +4448,14 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } public void checkUnsentMessages() { - MessagesStorage.getInstance(currentAccount).getUnsentMessages(1000); + getMessagesStorage().getUnsentMessages(1000); } protected void processUnsentMessages(final ArrayList messages, final ArrayList users, final ArrayList chats, final ArrayList encryptedChats) { AndroidUtilities.runOnUIThread(() -> { - MessagesController.getInstance(currentAccount).putUsers(users, true); - MessagesController.getInstance(currentAccount).putChats(chats, true); - MessagesController.getInstance(currentAccount).putEncryptedChats(encryptedChats, true); + getMessagesController().putUsers(users, true); + getMessagesController().putChats(chats, true); + getMessagesController().putEncryptedChats(encryptedChats, true); for (int a = 0; a < messages.size(); a++) { TLRPC.Message message = messages.get(a); MessageObject messageObject = new MessageObject(currentAccount, message, false); @@ -4490,18 +4488,18 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (sizes.isEmpty()) { return null; } else { - UserConfig.getInstance(currentAccount).saveConfig(false); + getUserConfig().saveConfig(false); if (photo == null) { photo = new TLRPC.TL_photo(); } - photo.date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + photo.date = getConnectionsManager().getCurrentTime(); photo.sizes = sizes; photo.file_reference = new byte[0]; return photo; } } - private static boolean prepareSendingDocumentInternal(final int currentAccount, String path, String originalPath, Uri uri, String mime, final long dialog_id, final MessageObject reply_to_msg, CharSequence caption, final ArrayList entities, final MessageObject editingMessageObject, boolean forceDocument) { + private static boolean prepareSendingDocumentInternal(AccountInstance accountInstance, String path, String originalPath, Uri uri, String mime, final long dialog_id, final MessageObject reply_to_msg, CharSequence caption, final ArrayList entities, final MessageObject editingMessageObject, boolean forceDocument) { if ((path == null || path.length() == 0) && uri == null) { return false; } @@ -4621,24 +4619,24 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter TLRPC.TL_document document = null; String parentObject = null; if (!sendNew && !isEncrypted) { - Object[] sentData = MessagesStorage.getInstance(currentAccount).getSentFile(originalPath, !isEncrypted ? 1 : 4); + Object[] sentData = accountInstance.getMessagesStorage().getSentFile(originalPath, !isEncrypted ? 1 : 4); if (sentData != null) { document = (TLRPC.TL_document) sentData[0]; parentObject = (String) sentData[1]; } if (document == null && !path.equals(originalPath) && !isEncrypted) { - sentData = MessagesStorage.getInstance(currentAccount).getSentFile(path + f.length(), !isEncrypted ? 1 : 4); + sentData = accountInstance.getMessagesStorage().getSentFile(path + f.length(), !isEncrypted ? 1 : 4); if (sentData != null) { document = (TLRPC.TL_document) sentData[0]; parentObject = (String) sentData[1]; } } - ensureMediaThumbExists(currentAccount, isEncrypted, document, path, null, 0); + ensureMediaThumbExists(isEncrypted, document, path, null, 0); } if (document == null) { document = new TLRPC.TL_document(); document.id = 0; - document.date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + document.date = accountInstance.getConnectionsManager().getCurrentTime(); TLRPC.TL_documentAttributeFilename fileName = new TLRPC.TL_documentAttributeFilename(); fileName.file_name = name; document.file_reference = new byte[0]; @@ -4734,16 +4732,16 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter final String parentFinal = parentObject; AndroidUtilities.runOnUIThread(() -> { if (editingMessageObject != null) { - SendMessagesHelper.getInstance(currentAccount).editMessageMedia(editingMessageObject, null, null, documentFinal, pathFinal, params, false, parentFinal); + accountInstance.getSendMessagesHelper().editMessageMedia(editingMessageObject, null, null, documentFinal, pathFinal, params, false, parentFinal); } else { - SendMessagesHelper.getInstance(currentAccount).sendMessage(documentFinal, null, pathFinal, dialog_id, reply_to_msg, captionFinal, entities, null, params, 0, parentFinal); + accountInstance.getSendMessagesHelper().sendMessage(documentFinal, null, pathFinal, dialog_id, reply_to_msg, captionFinal, entities, null, params, 0, parentFinal); } }); return true; } @UiThread - public static void prepareSendingDocument(String path, String originalPath, Uri uri, String caption, String mine, long dialog_id, MessageObject reply_to_msg, InputContentInfoCompat inputContent, MessageObject editingMessageObject) { + public static void prepareSendingDocument(AccountInstance accountInstance, String path, String originalPath, Uri uri, String caption, String mine, long dialog_id, MessageObject reply_to_msg, InputContentInfoCompat inputContent, MessageObject editingMessageObject) { if ((path == null || originalPath == null) && uri == null) { return; } @@ -4758,12 +4756,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter paths.add(path); originalPaths.add(originalPath); } - prepareSendingDocuments(paths, originalPaths, uris, caption, mine, dialog_id, reply_to_msg, inputContent, editingMessageObject); + prepareSendingDocuments(accountInstance, paths, originalPaths, uris, caption, mine, dialog_id, reply_to_msg, inputContent, editingMessageObject); } @UiThread - public static void prepareSendingAudioDocuments(final ArrayList messageObjects, final long dialog_id, final MessageObject reply_to_msg, final MessageObject editingMessageObject) { - final int currentAccount = UserConfig.selectedAccount; + public static void prepareSendingAudioDocuments(AccountInstance accountInstance, final ArrayList messageObjects, final long dialog_id, final MessageObject reply_to_msg, final MessageObject editingMessageObject) { new Thread(() -> { int size = messageObjects.size(); for (int a = 0; a < size; a++) { @@ -4780,11 +4777,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter TLRPC.TL_document document = null; String parentObject = null; if (!isEncrypted) { - Object[] sentData = MessagesStorage.getInstance(currentAccount).getSentFile(originalPath, !isEncrypted ? 1 : 4); + Object[] sentData = accountInstance.getMessagesStorage().getSentFile(originalPath, !isEncrypted ? 1 : 4); if (sentData != null) { document = (TLRPC.TL_document) sentData[0]; parentObject = (String) sentData[1]; - ensureMediaThumbExists(currentAccount, isEncrypted, document, originalPath, null, 0); + ensureMediaThumbExists(isEncrypted, document, originalPath, null, 0); } } if (document == null) { @@ -4793,7 +4790,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (isEncrypted) { int high_id = (int) (dialog_id >> 32); - TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance(currentAccount).getEncryptedChat(high_id); + TLRPC.EncryptedChat encryptedChat = accountInstance.getMessagesController().getEncryptedChat(high_id); if (encryptedChat == null) { return; } @@ -4807,9 +4804,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter final String parentFinal = parentObject; AndroidUtilities.runOnUIThread(() -> { if (editingMessageObject != null) { - SendMessagesHelper.getInstance(currentAccount).editMessageMedia(editingMessageObject, null, null, documentFinal, messageObject.messageOwner.attachPath, params, false, parentFinal); + accountInstance.getSendMessagesHelper().editMessageMedia(editingMessageObject, null, null, documentFinal, messageObject.messageOwner.attachPath, params, false, parentFinal); } else { - SendMessagesHelper.getInstance(currentAccount).sendMessage(documentFinal, null, messageObject.messageOwner.attachPath, dialog_id, reply_to_msg, null, null, null, params, 0, parentFinal); + accountInstance.getSendMessagesHelper().sendMessage(documentFinal, null, messageObject.messageOwner.attachPath, dialog_id, reply_to_msg, null, null, null, params, 0, parentFinal); } }); } @@ -4817,23 +4814,22 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } @UiThread - public static void prepareSendingDocuments(final ArrayList paths, final ArrayList originalPaths, final ArrayList uris, final String caption, final String mime, final long dialog_id, final MessageObject reply_to_msg, final InputContentInfoCompat inputContent, final MessageObject editingMessageObject) { + public static void prepareSendingDocuments(AccountInstance accountInstance, final ArrayList paths, final ArrayList originalPaths, final ArrayList uris, final String caption, final String mime, final long dialog_id, final MessageObject reply_to_msg, final InputContentInfoCompat inputContent, final MessageObject editingMessageObject) { if (paths == null && originalPaths == null && uris == null || paths != null && originalPaths != null && paths.size() != originalPaths.size()) { return; } - final int currentAccount = UserConfig.selectedAccount; new Thread(() -> { boolean error = false; if (paths != null) { for (int a = 0; a < paths.size(); a++) { - if (!prepareSendingDocumentInternal(currentAccount, paths.get(a), originalPaths.get(a), null, mime, dialog_id, reply_to_msg, caption, null, editingMessageObject, false)) { + if (!prepareSendingDocumentInternal(accountInstance, paths.get(a), originalPaths.get(a), null, mime, dialog_id, reply_to_msg, caption, null, editingMessageObject, false)) { error = true; } } } if (uris != null) { for (int a = 0; a < uris.size(); a++) { - if (!prepareSendingDocumentInternal(currentAccount, null, null, uris.get(a), mime, dialog_id, reply_to_msg, caption, null, editingMessageObject, false)) { + if (!prepareSendingDocumentInternal(accountInstance, null, null, uris.get(a), mime, dialog_id, reply_to_msg, caption, null, editingMessageObject, false)) { error = true; } } @@ -4855,7 +4851,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } @UiThread - public static void prepareSendingPhoto(String imageFilePath, Uri imageUri, long dialog_id, MessageObject reply_to_msg, CharSequence caption, ArrayList entities, ArrayList stickers, InputContentInfoCompat inputContent, int ttl, MessageObject editingMessageObject) { + public static void prepareSendingPhoto(AccountInstance accountInstance, String imageFilePath, Uri imageUri, long dialog_id, MessageObject reply_to_msg, CharSequence caption, ArrayList entities, ArrayList stickers, InputContentInfoCompat inputContent, int ttl, MessageObject editingMessageObject) { SendingMediaInfo info = new SendingMediaInfo(); info.path = imageFilePath; info.uri = imageUri; @@ -4869,15 +4865,14 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } ArrayList infos = new ArrayList<>(); infos.add(info); - prepareSendingMedia(infos, dialog_id, reply_to_msg, inputContent, false, false, editingMessageObject); + prepareSendingMedia(accountInstance, infos, dialog_id, reply_to_msg, inputContent, false, false, editingMessageObject); } @UiThread - public static void prepareSendingBotContextResult(final TLRPC.BotInlineResult result, final HashMap params, final long dialog_id, final MessageObject reply_to_msg) { + public static void prepareSendingBotContextResult(AccountInstance accountInstance, final TLRPC.BotInlineResult result, final HashMap params, final long dialog_id, final MessageObject reply_to_msg) { if (result == null) { return; } - final int currentAccount = UserConfig.selectedAccount; if (result.send_message instanceof TLRPC.TL_botInlineMessageMediaAuto) { new Thread(() -> { boolean isEncrypted = (int) dialog_id == 0; @@ -4931,7 +4926,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter document.dc_id = 0; document.mime_type = result.content.mime_type; document.file_reference = new byte[0]; - document.date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + document.date = accountInstance.getConnectionsManager().getCurrentTime(); TLRPC.TL_documentAttributeFilename fileName = new TLRPC.TL_documentAttributeFilename(); document.attributes.add(fileName); @@ -5076,11 +5071,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } case "photo": { if (f.exists()) { - photo = SendMessagesHelper.getInstance(currentAccount).generatePhotoSizes(finalPath, null); + photo = accountInstance.getSendMessagesHelper().generatePhotoSizes(finalPath, null); } if (photo == null) { photo = new TLRPC.TL_photo(); - photo.date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + photo.date = accountInstance.getConnectionsManager().getCurrentTime(); photo.file_reference = new byte[0]; TLRPC.TL_photoSize photoSize = new TLRPC.TL_photoSize(); int wh[] = MessageObject.getInlineResultWidthAndHeight(result); @@ -5104,11 +5099,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } AndroidUtilities.runOnUIThread(() -> { if (finalDocument != null) { - SendMessagesHelper.getInstance(currentAccount).sendMessage(finalDocument, null, finalPathFinal, dialog_id, reply_to_msg, result.send_message.message, result.send_message.entities, result.send_message.reply_markup, params, 0, result); + accountInstance.getSendMessagesHelper().sendMessage(finalDocument, null, finalPathFinal, dialog_id, reply_to_msg, result.send_message.message, result.send_message.entities, result.send_message.reply_markup, params, 0, result); } else if (finalPhoto != null) { - SendMessagesHelper.getInstance(currentAccount).sendMessage(finalPhoto, result.content != null ? result.content.url : null, dialog_id, reply_to_msg, result.send_message.message, result.send_message.entities, result.send_message.reply_markup, params, 0, result); + accountInstance.getSendMessagesHelper().sendMessage(finalPhoto, result.content != null ? result.content.url : null, dialog_id, reply_to_msg, result.send_message.message, result.send_message.entities, result.send_message.reply_markup, params, 0, result); } else if (finalGame != null) { - SendMessagesHelper.getInstance(currentAccount).sendMessage(finalGame, dialog_id, result.send_message.reply_markup, params); + accountInstance.getSendMessagesHelper().sendMessage(finalGame, dialog_id, result.send_message.reply_markup, params); } }); }).run(); @@ -5124,7 +5119,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } } - SendMessagesHelper.getInstance(currentAccount).sendMessage(result.send_message.message, dialog_id, reply_to_msg, webPage, !result.send_message.no_webpage, result.send_message.entities, result.send_message.reply_markup, params); + accountInstance.getSendMessagesHelper().sendMessage(result.send_message.message, dialog_id, reply_to_msg, webPage, !result.send_message.no_webpage, result.send_message.entities, result.send_message.reply_markup, params); } else if (result.send_message instanceof TLRPC.TL_botInlineMessageMediaVenue) { TLRPC.TL_messageMediaVenue venue = new TLRPC.TL_messageMediaVenue(); venue.geo = result.send_message.geo; @@ -5136,17 +5131,17 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (venue.venue_type == null) { venue.venue_type = ""; } - SendMessagesHelper.getInstance(currentAccount).sendMessage(venue, dialog_id, reply_to_msg, result.send_message.reply_markup, params); + accountInstance.getSendMessagesHelper().sendMessage(venue, dialog_id, reply_to_msg, result.send_message.reply_markup, params); } else if (result.send_message instanceof TLRPC.TL_botInlineMessageMediaGeo) { if (result.send_message.period != 0) { TLRPC.TL_messageMediaGeoLive location = new TLRPC.TL_messageMediaGeoLive(); location.period = result.send_message.period; location.geo = result.send_message.geo; - SendMessagesHelper.getInstance(currentAccount).sendMessage(location, dialog_id, reply_to_msg, result.send_message.reply_markup, params); + accountInstance.getSendMessagesHelper().sendMessage(location, dialog_id, reply_to_msg, result.send_message.reply_markup, params); } else { TLRPC.TL_messageMediaGeo location = new TLRPC.TL_messageMediaGeo(); location.geo = result.send_message.geo; - SendMessagesHelper.getInstance(currentAccount).sendMessage(location, dialog_id, reply_to_msg, result.send_message.reply_markup, params); + accountInstance.getSendMessagesHelper().sendMessage(location, dialog_id, reply_to_msg, result.send_message.reply_markup, params); } } else if (result.send_message instanceof TLRPC.TL_botInlineMessageMediaContact) { TLRPC.User user = new TLRPC.TL_user(); @@ -5154,7 +5149,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter user.first_name = result.send_message.first_name; user.last_name = result.send_message.last_name; user.restriction_reason = result.send_message.vcard; - SendMessagesHelper.getInstance(currentAccount).sendMessage(user, dialog_id, reply_to_msg, result.send_message.reply_markup, params); + accountInstance.getSendMessagesHelper().sendMessage(user, dialog_id, reply_to_msg, result.send_message.reply_markup, params); } } @@ -5173,21 +5168,20 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } @UiThread - public static void prepareSendingText(final String text, final long dialog_id) { - final int currentAccount = UserConfig.selectedAccount; - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> Utilities.stageQueue.postRunnable(() -> AndroidUtilities.runOnUIThread(() -> { + public static void prepareSendingText(AccountInstance accountInstance, final String text, final long dialog_id) { + accountInstance.getMessagesStorage().getStorageQueue().postRunnable(() -> Utilities.stageQueue.postRunnable(() -> AndroidUtilities.runOnUIThread(() -> { String textFinal = getTrimmedString(text); if (textFinal.length() != 0) { int count = (int) Math.ceil(textFinal.length() / 4096.0f); for (int a = 0; a < count; a++) { String mess = textFinal.substring(a * 4096, Math.min((a + 1) * 4096, textFinal.length())); - SendMessagesHelper.getInstance(currentAccount).sendMessage(mess, dialog_id, null, null, true, null, null, null); + accountInstance.getSendMessagesHelper().sendMessage(mess, dialog_id, null, null, true, null, null, null); } } }))); } - public static void ensureMediaThumbExists(int currentAccount, boolean isEncrypted, TLObject object, String path, Uri uri, long startTime) { + public static void ensureMediaThumbExists(boolean isEncrypted, TLObject object, String path, Uri uri, long startTime) { if (object instanceof TLRPC.TL_photo) { TLRPC.TL_photo photo = (TLRPC.TL_photo) object; boolean smallExists; @@ -5335,11 +5329,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } @UiThread - public static void prepareSendingMedia(final ArrayList media, final long dialog_id, final MessageObject reply_to_msg, final InputContentInfoCompat inputContent, final boolean forceDocument, final boolean groupPhotos, final MessageObject editingMessageObject) { + public static void prepareSendingMedia(AccountInstance accountInstance, final ArrayList media, final long dialog_id, final MessageObject reply_to_msg, final InputContentInfoCompat inputContent, final boolean forceDocument, final boolean groupPhotos, final MessageObject editingMessageObject) { if (media.isEmpty()) { return; } - final int currentAccount = UserConfig.selectedAccount; mediaSendQueue.postRunnable(() -> { long beginTime = System.currentTimeMillis(); HashMap workers; @@ -5348,7 +5341,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter int enryptedLayer = 0; if (isEncrypted) { int high_id = (int) (dialog_id >> 32); - TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance(currentAccount).getEncryptedChat(high_id); + TLRPC.EncryptedChat encryptedChat = accountInstance.getMessagesController().getEncryptedChat(high_id); if (encryptedChat != null) { enryptedLayer = AndroidUtilities.getPeerLayerVersion(encryptedChat.layer); } @@ -5384,19 +5377,19 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter TLRPC.TL_photo photo = null; String parentObject = null; if (!isEncrypted && info.ttl == 0) { - Object[] sentData = MessagesStorage.getInstance(currentAccount).getSentFile(originalPath, !isEncrypted ? 0 : 3); + Object[] sentData = accountInstance.getMessagesStorage().getSentFile(originalPath, !isEncrypted ? 0 : 3); if (sentData != null) { photo = (TLRPC.TL_photo) sentData[0]; parentObject = (String) sentData[1]; } if (photo == null && info.uri != null) { - sentData = MessagesStorage.getInstance(currentAccount).getSentFile(AndroidUtilities.getPath(info.uri), !isEncrypted ? 0 : 3); + sentData = accountInstance.getMessagesStorage().getSentFile(AndroidUtilities.getPath(info.uri), !isEncrypted ? 0 : 3); if (sentData != null) { photo = (TLRPC.TL_photo) sentData[0]; parentObject = (String) sentData[1]; } } - ensureMediaThumbExists(currentAccount, isEncrypted, photo, info.path, info.uri, 0); + ensureMediaThumbExists(isEncrypted, photo, info.path, info.uri, 0); } final MediaSendPrepareWorker worker = new MediaSendPrepareWorker(); workers.put(info, worker); @@ -5406,7 +5399,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } else { worker.sync = new CountDownLatch(1); mediaSendThreadPool.execute(() -> { - worker.photo = SendMessagesHelper.getInstance(currentAccount).generatePhotoSizes(info.path, info.uri); + worker.photo = accountInstance.getSendMessagesHelper().generatePhotoSizes(info.path, info.uri); if (isEncrypted && info.canDeleteAfter) { new File(info.path).delete(); } @@ -5446,7 +5439,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter cacheFile = FileLoader.getPathToAttach(document, true); } else { /*if (!isEncrypted) { - Object[] sentData = MessagesStorage.getInstance(currentAccount).getSentFile(info.searchImage.imageUrl, !isEncrypted ? 1 : 4); + Object[] sentData = getMessagesStorage().getSentFile(info.searchImage.imageUrl, !isEncrypted ? 1 : 4); if (sentData != null && sentData[0] instanceof TLRPC.TL_document) { document = (TLRPC.TL_document) sentData[0]; parentObject = (String) sentData[1]; @@ -5463,7 +5456,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter document = new TLRPC.TL_document(); document.id = 0; document.file_reference = new byte[0]; - document.date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + document.date = accountInstance.getConnectionsManager().getCurrentTime(); TLRPC.TL_documentAttributeFilename fileName = new TLRPC.TL_documentAttributeFilename(); fileName.file_name = "animation.gif"; document.attributes.add(fileName); @@ -5528,9 +5521,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } AndroidUtilities.runOnUIThread(() -> { if (editingMessageObject != null) { - SendMessagesHelper.getInstance(currentAccount).editMessageMedia(editingMessageObject, null, null, documentFinal, pathFinal, params, false, parentFinal); + accountInstance.getSendMessagesHelper().editMessageMedia(editingMessageObject, null, null, documentFinal, pathFinal, params, false, parentFinal); } else { - SendMessagesHelper.getInstance(currentAccount).sendMessage(documentFinal, null, pathFinal, dialog_id, reply_to_msg, info.caption, info.entities, null, params, 0, parentFinal); + accountInstance.getSendMessagesHelper().sendMessage(documentFinal, null, pathFinal, dialog_id, reply_to_msg, info.caption, info.entities, null, params, 0, parentFinal); } }); } else { @@ -5541,7 +5534,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter photo = (TLRPC.TL_photo) info.searchImage.photo; } else { if (!isEncrypted && info.ttl == 0) { - /*Object[] sentData = MessagesStorage.getInstance(currentAccount).getSentFile(info.searchImage.imageUrl, !isEncrypted ? 0 : 3); + /*Object[] sentData = getMessagesStorage().getSentFile(info.searchImage.imageUrl, !isEncrypted ? 0 : 3); if (sentData != null) { photo = (TLRPC.TL_photo) sentData[0]; parentObject = (String) sentData[1]; @@ -5553,7 +5546,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter String md5 = Utilities.MD5(info.searchImage.imageUrl) + "." + ImageLoader.getHttpUrlExtension(info.searchImage.imageUrl, "jpg"); File cacheFile = new File(FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE), md5); if (cacheFile.exists() && cacheFile.length() != 0) { - photo = SendMessagesHelper.getInstance(currentAccount).generatePhotoSizes(cacheFile.toString(), null); + photo = accountInstance.getSendMessagesHelper().generatePhotoSizes(cacheFile.toString(), null); if (photo != null) { needDownloadHttp = false; } @@ -5562,11 +5555,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter md5 = Utilities.MD5(info.searchImage.thumbUrl) + "." + ImageLoader.getHttpUrlExtension(info.searchImage.thumbUrl, "jpg"); cacheFile = new File(FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE), md5); if (cacheFile.exists()) { - photo = SendMessagesHelper.getInstance(currentAccount).generatePhotoSizes(cacheFile.toString(), null); + photo = accountInstance.getSendMessagesHelper().generatePhotoSizes(cacheFile.toString(), null); } if (photo == null) { photo = new TLRPC.TL_photo(); - photo.date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + photo.date = accountInstance.getConnectionsManager().getCurrentTime(); photo.file_reference = new byte[0]; TLRPC.TL_photoSize photoSize = new TLRPC.TL_photoSize(); photoSize.w = info.searchImage.width; @@ -5596,9 +5589,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } AndroidUtilities.runOnUIThread(() -> { if (editingMessageObject != null) { - SendMessagesHelper.getInstance(currentAccount).editMessageMedia(editingMessageObject, photoFinal, null, null, needDownloadHttpFinal ? info.searchImage.imageUrl : null, params, false, parentFinal); + accountInstance.getSendMessagesHelper().editMessageMedia(editingMessageObject, photoFinal, null, null, needDownloadHttpFinal ? info.searchImage.imageUrl : null, params, false, parentFinal); } else { - SendMessagesHelper.getInstance(currentAccount).sendMessage(photoFinal, needDownloadHttpFinal ? info.searchImage.imageUrl : null, dialog_id, reply_to_msg, info.caption, info.entities, null, params, info.ttl, parentFinal); + accountInstance.getSendMessagesHelper().sendMessage(photoFinal, needDownloadHttpFinal ? info.searchImage.imageUrl : null, dialog_id, reply_to_msg, info.caption, info.entities, null, params, info.ttl, parentFinal); } }); } @@ -5634,11 +5627,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter TLRPC.TL_document document = null; String parentObject = null; if (!isEncrypted && info.ttl == 0) { - Object[] sentData = MessagesStorage.getInstance(currentAccount).getSentFile(originalPath, !isEncrypted ? 2 : 5); + Object[] sentData = accountInstance.getMessagesStorage().getSentFile(originalPath, !isEncrypted ? 2 : 5); if (sentData != null) { document = (TLRPC.TL_document) sentData[0]; parentObject = (String) sentData[1]; - ensureMediaThumbExists(currentAccount, isEncrypted, document, info.path, null, startTime); + ensureMediaThumbExists(isEncrypted, document, info.path, null, startTime); } } if (document == null) { @@ -5659,7 +5652,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter document.flags |= 1; } document.mime_type = "video/mp4"; - UserConfig.getInstance(currentAccount).saveConfig(false); + accountInstance.getUserConfig().saveConfig(false); TLRPC.TL_documentAttributeVideo attributeVideo; if (isEncrypted) { if (enryptedLayer >= 66) { @@ -5736,13 +5729,13 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter ImageLoader.getInstance().putImageToCache(new BitmapDrawable(thumbFinal), thumbKeyFinal); } if (editingMessageObject != null) { - SendMessagesHelper.getInstance(currentAccount).editMessageMedia(editingMessageObject, null, videoEditedInfo, videoFinal, finalPath, params, false, parentFinal); + accountInstance.getSendMessagesHelper().editMessageMedia(editingMessageObject, null, videoEditedInfo, videoFinal, finalPath, params, false, parentFinal); } else { - SendMessagesHelper.getInstance(currentAccount).sendMessage(videoFinal, videoEditedInfo, finalPath, dialog_id, reply_to_msg, info.caption, info.entities, null, params, info.ttl, parentFinal); + accountInstance.getSendMessagesHelper().sendMessage(videoFinal, videoEditedInfo, finalPath, dialog_id, reply_to_msg, info.caption, info.entities, null, params, info.ttl, parentFinal); } }); } else { - prepareSendingDocumentInternal(currentAccount, info.path, info.path, null, null, dialog_id, reply_to_msg, info.caption, info.entities, editingMessageObject, forceDocument); + prepareSendingDocumentInternal(accountInstance, info.path, info.path, null, null, dialog_id, reply_to_msg, info.caption, info.entities, editingMessageObject, forceDocument); } } else { String originalPath = info.path; @@ -5812,22 +5805,22 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } else { if (!isEncrypted && info.ttl == 0) { - Object[] sentData = MessagesStorage.getInstance(currentAccount).getSentFile(originalPath, !isEncrypted ? 0 : 3); + Object[] sentData = accountInstance.getMessagesStorage().getSentFile(originalPath, !isEncrypted ? 0 : 3); if (sentData != null) { photo = (TLRPC.TL_photo) sentData[0]; parentObject = (String) sentData[1]; } if (photo == null && info.uri != null) { - sentData = MessagesStorage.getInstance(currentAccount).getSentFile(AndroidUtilities.getPath(info.uri), !isEncrypted ? 0 : 3); + sentData = accountInstance.getMessagesStorage().getSentFile(AndroidUtilities.getPath(info.uri), !isEncrypted ? 0 : 3); if (sentData != null) { photo = (TLRPC.TL_photo) sentData[0]; parentObject = (String) sentData[1]; } } - ensureMediaThumbExists(currentAccount, isEncrypted, photo, info.path, info.uri, 0); + ensureMediaThumbExists(isEncrypted, photo, info.path, info.uri, 0); } if (photo == null) { - photo = SendMessagesHelper.getInstance(currentAccount).generatePhotoSizes(info.path, info.uri); + photo = accountInstance.getSendMessagesHelper().generatePhotoSizes(info.path, info.uri); if (isEncrypted && info.canDeleteAfter) { new File(info.path).delete(); } @@ -5876,9 +5869,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter ImageLoader.getInstance().putImageToCache(new BitmapDrawable(bitmapFinal[0]), keyFinal[0]); } if (editingMessageObject != null) { - SendMessagesHelper.getInstance(currentAccount).editMessageMedia(editingMessageObject, photoFinal, null, null, null, params, false, parentFinal); + accountInstance.getSendMessagesHelper().editMessageMedia(editingMessageObject, photoFinal, null, null, null, params, false, parentFinal); } else { - SendMessagesHelper.getInstance(currentAccount).sendMessage(photoFinal, null, dialog_id, reply_to_msg, info.caption, info.entities, null, params, info.ttl, parentFinal); + accountInstance.getSendMessagesHelper().sendMessage(photoFinal, null, dialog_id, reply_to_msg, info.caption, info.entities, null, params, info.ttl, parentFinal); } }); } else { @@ -5900,7 +5893,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (lastGroupId != 0) { final long lastGroupIdFinal = lastGroupId; AndroidUtilities.runOnUIThread(() -> { - SendMessagesHelper instance = getInstance(currentAccount); + SendMessagesHelper instance = accountInstance.getSendMessagesHelper(); ArrayList arrayList = instance.delayedMessages.get("group_" + lastGroupIdFinal); if (arrayList != null && !arrayList.isEmpty()) { @@ -5912,7 +5905,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter TLRPC.TL_messages_messages messagesRes = new TLRPC.TL_messages_messages(); messagesRes.messages.add(prevMessage.messageOwner); - MessagesStorage.getInstance(currentAccount).putMessages(messagesRes, message.peer, -2, 0, false); + accountInstance.getMessagesStorage().putMessages(messagesRes, message.peer, -2, 0, false); instance.sendReadyToSendGroup(message, true, true); } }); @@ -5922,7 +5915,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } if (sendAsDocuments != null && !sendAsDocuments.isEmpty()) { for (int a = 0; a < sendAsDocuments.size(); a++) { - prepareSendingDocumentInternal(currentAccount, sendAsDocuments.get(a), sendAsDocumentsOriginal.get(a), null, extension, dialog_id, reply_to_msg, sendAsDocumentsCaptions.get(a), sendAsDocumentsEntities.get(a), editingMessageObject, forceDocument); + prepareSendingDocumentInternal(accountInstance, sendAsDocuments.get(a), sendAsDocumentsOriginal.get(a), null, extension, dialog_id, reply_to_msg, sendAsDocumentsCaptions.get(a), sendAsDocumentsEntities.get(a), editingMessageObject, forceDocument); } } if (BuildVars.LOGS_ENABLED) { @@ -6147,11 +6140,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } @UiThread - public static void prepareSendingVideo(final String videoPath, final long estimatedSize, final long duration, final int width, final int height, final VideoEditedInfo info, final long dialog_id, final MessageObject reply_to_msg, final CharSequence caption, final ArrayList entities, final int ttl, final MessageObject editingMessageObject) { + public static void prepareSendingVideo(AccountInstance accountInstance, final String videoPath, final long estimatedSize, final long duration, final int width, final int height, final VideoEditedInfo info, final long dialog_id, final MessageObject reply_to_msg, final CharSequence caption, final ArrayList entities, final int ttl, final MessageObject editingMessageObject) { if (videoPath == null || videoPath.length() == 0) { return; } - final int currentAccount = UserConfig.selectedAccount; new Thread(() -> { final VideoEditedInfo videoEditedInfo = info != null ? info : createCompressionSettings(videoPath); @@ -6181,11 +6173,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter TLRPC.TL_document document = null; String parentObject = null; if (!isEncrypted && ttl == 0) { - Object[] sentData = MessagesStorage.getInstance(currentAccount).getSentFile(originalPath, !isEncrypted ? 2 : 5); + Object[] sentData = accountInstance.getMessagesStorage().getSentFile(originalPath, !isEncrypted ? 2 : 5); if (sentData != null) { document = (TLRPC.TL_document) sentData[0]; parentObject = (String) sentData[1]; - ensureMediaThumbExists(currentAccount, isEncrypted, document, videoPath, null, startTime); + ensureMediaThumbExists(isEncrypted, document, videoPath, null, startTime); } } if (document == null) { @@ -6215,11 +6207,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } document.file_reference = new byte[0]; document.mime_type = "video/mp4"; - UserConfig.getInstance(currentAccount).saveConfig(false); + accountInstance.getUserConfig().saveConfig(false); TLRPC.TL_documentAttributeVideo attributeVideo; if (isEncrypted) { int high_id = (int) (dialog_id >> 32); - TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance(currentAccount).getEncryptedChat(high_id); + TLRPC.EncryptedChat encryptedChat = accountInstance.getMessagesController().getEncryptedChat(high_id); if (encryptedChat == null) { return; } @@ -6281,13 +6273,13 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter ImageLoader.getInstance().putImageToCache(new BitmapDrawable(thumbFinal), thumbKeyFinal); } if (editingMessageObject != null) { - SendMessagesHelper.getInstance(currentAccount).editMessageMedia(editingMessageObject, null, videoEditedInfo, videoFinal, finalPath, params, false, parentFinal); + accountInstance.getSendMessagesHelper().editMessageMedia(editingMessageObject, null, videoEditedInfo, videoFinal, finalPath, params, false, parentFinal); } else { - SendMessagesHelper.getInstance(currentAccount).sendMessage(videoFinal, videoEditedInfo, finalPath, dialog_id, reply_to_msg, captionFinal, entities, null, params, ttl, parentFinal); + accountInstance.getSendMessagesHelper().sendMessage(videoFinal, videoEditedInfo, finalPath, dialog_id, reply_to_msg, captionFinal, entities, null, params, ttl, parentFinal); } }); } else { - prepareSendingDocumentInternal(currentAccount, videoPath, videoPath, null, null, dialog_id, reply_to_msg, caption, entities, editingMessageObject, false); + prepareSendingDocumentInternal(accountInstance, videoPath, videoPath, null, null, dialog_id, reply_to_msg, caption, entities, editingMessageObject, false); } }).start(); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java b/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java index 0c5734016..261afc784 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java @@ -9,8 +9,10 @@ package org.telegram.messenger; import android.app.Activity; +import android.app.ActivityManager; import android.content.Context; import android.content.SharedPreferences; +import android.os.Build; import android.os.Environment; import android.os.SystemClock; import android.text.TextUtils; @@ -21,6 +23,7 @@ import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.SerializedData; import java.io.File; +import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -28,6 +31,7 @@ import java.util.Iterator; public class SharedConfig { public static String pushString = ""; + public static String pushStringStatus = ""; public static byte[] pushAuthKey; public static byte[] pushAuthKeyId; @@ -74,7 +78,6 @@ public class SharedConfig { public static boolean streamAllVideo = false; public static boolean streamMkv = false; public static boolean saveStreamMedia = true; - public static boolean showAnimatedStickers = BuildVars.DEBUG_VERSION; public static boolean sortContactsByName; public static boolean shuffleMusic; public static boolean playOrderReversed; @@ -84,11 +87,14 @@ public class SharedConfig { public static boolean allowBigEmoji; public static boolean useSystemEmoji; public static int fontSize = AndroidUtilities.dp(16); + private static int devicePerformanceClass; public static boolean drawDialogIcons; public static boolean useThreeLinesLayout; public static boolean archiveHidden; + public static int distanceSystemType; + static { loadConfig(); } @@ -240,6 +246,8 @@ public class SharedConfig { directShareHash = preferences.getLong("directShareHash", 0); useThreeLinesLayout = preferences.getBoolean("useThreeLinesLayout", false); archiveHidden = preferences.getBoolean("archiveHidden", false); + distanceSystemType = preferences.getInt("distanceSystemType", 0); + devicePerformanceClass = preferences.getInt("devicePerformanceClass", -1); preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); showNotificationsForAllAccounts = preferences.getBoolean("AllAccounts", true); @@ -545,6 +553,15 @@ public class SharedConfig { editor.commit(); } + public static void setDistanceSystemType(int type) { + distanceSystemType = type; + SharedPreferences preferences = MessagesController.getGlobalMainSettings(); + SharedPreferences.Editor editor = preferences.edit(); + editor.putInt("distanceSystemType", distanceSystemType); + editor.commit(); + LocaleController.resetImperialSystemType(); + } + public static void loadProxyList() { if (proxyListLoaded) { return; @@ -666,4 +683,39 @@ public class SharedConfig { FileLog.e(e); } } + + public final static int PERFORMANCE_CLASS_LOW = 0; + public final static int PERFORMANCE_CLASS_AVERAGE = 1; + public final static int PERFORMANCE_CLASS_HIGH = 2; + + public static int getDevicePerfomanceClass() { + if (devicePerformanceClass == -1) { + int maxCpuFreq = -1; + try { + RandomAccessFile reader = new RandomAccessFile("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", "r"); + String line = reader.readLine(); + if (line != null) { + maxCpuFreq = Utilities.parseInt(line) / 1000; + } + reader.close(); + } catch (Throwable ignore) { + + } + int androidVersion = Build.VERSION.SDK_INT; + int cpuCount = ConnectionsManager.CPU_COUNT; + int memoryClass = ((ActivityManager) ApplicationLoader.applicationContext.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); + if (androidVersion < 21 || cpuCount <= 2 || memoryClass <= 100 || cpuCount <= 4 && maxCpuFreq != -1 && maxCpuFreq <= 1250 || cpuCount <= 4 && maxCpuFreq <= 1600 && memoryClass <= 128 && androidVersion <= 21) { + devicePerformanceClass = PERFORMANCE_CLASS_LOW; + } else if (cpuCount < 8 || memoryClass <= 160 || maxCpuFreq != -1 && maxCpuFreq <= 1650) { + devicePerformanceClass = PERFORMANCE_CLASS_AVERAGE; + } else { + devicePerformanceClass = PERFORMANCE_CLASS_HIGH; + } + if (BuildVars.DEBUG_VERSION) { + FileLog.d("device performance info (cpu_count = " + cpuCount + ", freq = " + maxCpuFreq + ", memoryClass = " + memoryClass + ", android version " + androidVersion + ")"); + } + } + + return devicePerformanceClass; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/StatsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/StatsController.java index 36cae7847..6b681510e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/StatsController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/StatsController.java @@ -14,7 +14,7 @@ import android.content.SharedPreferences; import java.io.File; import java.io.RandomAccessFile; -public class StatsController { +public class StatsController extends BaseController { public static final int TYPE_MOBILE = 0; public static final int TYPE_WIFI = 1; @@ -32,12 +32,12 @@ public class StatsController { private byte[] buffer = new byte[8]; private long lastInternalStatsSaveTime; - private long sentBytes[][] = new long[3][TYPES_COUNT]; - private long receivedBytes[][] = new long[3][TYPES_COUNT]; - private int sentItems[][] = new int[3][TYPES_COUNT]; - private int receivedItems[][] = new int[3][TYPES_COUNT]; - private long resetStatsDate[] = new long[3]; - private int callsTotalTime[] = new int[3]; + private long[][] sentBytes = new long[3][TYPES_COUNT]; + private long[][] receivedBytes = new long[3][TYPES_COUNT]; + private int[][] sentItems = new int[3][TYPES_COUNT]; + private int[][] receivedItems = new int[3][TYPES_COUNT]; + private long[] resetStatsDate = new long[3]; + private int[] callsTotalTime = new int[3]; private RandomAccessFile statsFile; private static DispatchQueue statsSaveQueue = new DispatchQueue("statsSaveQueue"); @@ -103,7 +103,7 @@ public class StatsController { } }; - private static volatile StatsController Instance[] = new StatsController[UserConfig.MAX_ACCOUNT_COUNT]; + private static volatile StatsController[] Instance = new StatsController[UserConfig.MAX_ACCOUNT_COUNT]; public static StatsController getInstance(int num) { StatsController localInstance = Instance[num]; @@ -119,6 +119,7 @@ public class StatsController { } private StatsController(int account) { + super(account); File filesDir = ApplicationLoader.getFilesDirFixed(); if (account != 0) { filesDir = new File(ApplicationLoader.getFilesDirFixed(), "account" + account + "/"); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java b/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java index 6231fd7a6..14bf2a9ad 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java @@ -19,7 +19,7 @@ import org.telegram.tgnet.TLRPC; import java.io.File; -public class UserConfig { +public class UserConfig extends BaseController { public static int selectedAccount; public final static int MAX_ACCOUNT_COUNT = 3; @@ -66,7 +66,6 @@ public class UserConfig { public volatile byte[] savedSaltedPassword; public volatile long savedPasswordTime; - private int currentAccount; private static volatile UserConfig[] Instance = new UserConfig[UserConfig.MAX_ACCOUNT_COUNT]; public static UserConfig getInstance(int num) { UserConfig localInstance = Instance[num]; @@ -84,7 +83,7 @@ public class UserConfig { public static int getActivatedAccountsCount() { int count = 0; for (int a = 0; a < MAX_ACCOUNT_COUNT; a++) { - if (getInstance(a).isClientActivated()) { + if (AccountInstance.getInstance(a).getUserConfig().isClientActivated()) { count++; } } @@ -92,7 +91,7 @@ public class UserConfig { } public UserConfig(int instance) { - currentAccount = instance; + super(instance); } public int getNewMessageId() { @@ -426,7 +425,7 @@ public class UserConfig { resetSavedPassword(); boolean hasActivated = false; for (int a = 0; a < MAX_ACCOUNT_COUNT; a++) { - if (UserConfig.getInstance(a).isClientActivated()) { + if (AccountInstance.getInstance(a).getUserConfig().isClientActivated()) { hasActivated = true; break; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/WearReplyReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/WearReplyReceiver.java index 9412c42d2..6903e8cde 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/WearReplyReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/WearReplyReceiver.java @@ -12,6 +12,10 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.text.TextUtils; + +import org.telegram.tgnet.TLRPC; + import androidx.core.app.RemoteInput; public class WearReplyReceiver extends BroadcastReceiver { @@ -24,7 +28,7 @@ public class WearReplyReceiver extends BroadcastReceiver { return; } CharSequence text = remoteInput.getCharSequence(NotificationsController.EXTRA_VOICE_REPLY); - if (text == null || text.length() == 0) { + if (TextUtils.isEmpty(text)) { return; } long dialog_id = intent.getLongExtra("dialog_id", 0); @@ -33,7 +37,39 @@ public class WearReplyReceiver extends BroadcastReceiver { if (dialog_id == 0 || max_id == 0) { return; } - SendMessagesHelper.getInstance(currentAccount).sendMessage(text.toString(), dialog_id, null, null, true, null, null, null); - MessagesController.getInstance(currentAccount).markDialogAsRead(dialog_id, max_id, max_id, 0, false, 0, true); + int lowerId = (int) dialog_id; + int highId = (int) (dialog_id >> 32); + AccountInstance accountInstance = AccountInstance.getInstance(currentAccount); + if (lowerId > 0) { + TLRPC.User user = accountInstance.getMessagesController().getUser(lowerId); + if (user == null) { + Utilities.globalQueue.postRunnable(() -> { + TLRPC.User user1 = accountInstance.getMessagesStorage().getUserSync(lowerId); + AndroidUtilities.runOnUIThread(() -> { + accountInstance.getMessagesController().putUser(user1, true); + sendMessage(accountInstance, text, dialog_id, max_id); + }); + }); + return; + } + } else if (lowerId < 0) { + TLRPC.Chat chat = accountInstance.getMessagesController().getChat(-lowerId); + if (chat == null) { + Utilities.globalQueue.postRunnable(() -> { + TLRPC.Chat chat1 = accountInstance.getMessagesStorage().getChatSync(-lowerId); + AndroidUtilities.runOnUIThread(() -> { + accountInstance.getMessagesController().putChat(chat1, true); + sendMessage(accountInstance, text, dialog_id, max_id); + }); + }); + return; + } + } + sendMessage(accountInstance, text, dialog_id, max_id); + } + + private void sendMessage(AccountInstance accountInstance, CharSequence text, long dialog_id, int max_id) { + accountInstance.getSendMessagesHelper().sendMessage(text.toString(), dialog_id, null, null, true, null, null, null); + accountInstance.getMessagesController().markDialogAsRead(dialog_id, max_id, max_id, 0, false, 0, true); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPBaseService.java b/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPBaseService.java index ad5d44ad4..e1bdb9096 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPBaseService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPBaseService.java @@ -47,7 +47,6 @@ import android.os.Build; import android.os.Bundle; import android.os.PowerManager; import android.os.Vibrator; -import android.provider.Settings; import android.telecom.CallAudioState; import android.telecom.Connection; import android.telecom.DisconnectCause; @@ -60,7 +59,6 @@ import android.text.TextUtils; import android.text.style.ForegroundColorSpan; import android.view.View; import android.view.ViewGroup; -import android.widget.FrameLayout; import android.widget.RemoteViews; import org.telegram.messenger.AndroidUtilities; @@ -85,7 +83,6 @@ import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.voip.VoIPHelper; -import org.telegram.ui.VoIPActivity; import org.telegram.ui.VoIPPermissionActivity; import java.lang.reflect.Field; diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java b/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java index ccddde1e2..7a049d112 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java @@ -15,16 +15,18 @@ import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings; import org.json.JSONArray; import org.json.JSONObject; +import org.telegram.messenger.AccountInstance; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.BaseController; import org.telegram.messenger.BuildConfig; import org.telegram.messenger.BuildVars; -import org.telegram.messenger.ContactsController; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.KeepAliveJob; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.SharedConfig; import org.telegram.messenger.StatsController; import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; @@ -44,10 +46,15 @@ import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -public class ConnectionsManager { +public class ConnectionsManager extends BaseController { public final static int ConnectionTypeGeneric = 1; public final static int ConnectionTypeDownload = 2; @@ -90,6 +97,26 @@ public class ConnectionsManager { private static HashMap resolvingHostnameTasks = new HashMap<>(); + public static final Executor DNS_THREAD_POOL_EXECUTOR; + public static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); + private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); + private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; + private static final int KEEP_ALIVE_SECONDS = 30; + private static final BlockingQueue sPoolWorkQueue = new LinkedBlockingQueue<>(128); + private static final ThreadFactory sThreadFactory = new ThreadFactory() { + private final AtomicInteger mCount = new AtomicInteger(1); + + public Thread newThread(Runnable r) { + return new Thread(r, "DnsAsyncTask #" + mCount.getAndIncrement()); + } + }; + + static { + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); + threadPoolExecutor.allowCoreThreadTimeOut(true); + DNS_THREAD_POOL_EXECUTOR = threadPoolExecutor; + } + private static class ResolvedDomain { public ArrayList addresses; @@ -105,11 +132,10 @@ public class ConnectionsManager { } } - private static ConcurrentHashMap dnsCache = new ConcurrentHashMap<>(); + private static HashMap dnsCache = new HashMap<>(); private static int lastClassGuid = 1; - - private int currentAccount; + private static volatile ConnectionsManager[] Instance = new ConnectionsManager[UserConfig.MAX_ACCOUNT_COUNT]; public static ConnectionsManager getInstance(int num) { ConnectionsManager localInstance = Instance[num]; @@ -125,7 +151,7 @@ public class ConnectionsManager { } public ConnectionsManager(int instance) { - currentAccount = instance; + super(instance); connectionState = native_getConnectionState(currentAccount); String deviceModel; String systemLangCode; @@ -166,8 +192,12 @@ public class ConnectionsManager { if (systemVersion.trim().length() == 0) { systemVersion = "SDK Unknown"; } - UserConfig.getInstance(currentAccount).loadConfig(); - init(BuildVars.BUILD_VERSION, TLRPC.LAYER, BuildVars.APP_ID, deviceModel, systemVersion, appVersion, langCode, systemLangCode, configPath, FileLog.getNetworkLogPath(), UserConfig.getInstance(currentAccount).getClientUserId(), enablePushConnection); + getUserConfig().loadConfig(); + String pushString = SharedConfig.pushString; + if (TextUtils.isEmpty(pushString) && !TextUtils.isEmpty(SharedConfig.pushStringStatus)) { + pushString = SharedConfig.pushStringStatus; + } + init(BuildVars.BUILD_VERSION, TLRPC.LAYER, BuildVars.APP_ID, deviceModel, systemVersion, appVersion, langCode, systemLangCode, configPath, FileLog.getNetworkLogPath(), pushString, getUserConfig().getClientUserId(), enablePushConnection); } public long getCurrentTimeMillis() { @@ -290,7 +320,7 @@ public class ConnectionsManager { native_setPushConnectionEnabled(currentAccount, value); } - public void init(int version, int layer, int apiId, String deviceModel, String systemVersion, String appVersion, String langCode, String systemLangCode, String configPath, String logPath, int userId, boolean enablePushConnection) { + public void init(int version, int layer, int apiId, String deviceModel, String systemVersion, String appVersion, String langCode, String systemLangCode, String configPath, String logPath, String regId, int userId, boolean enablePushConnection) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); String proxyAddress = preferences.getString("proxy_ip", ""); String proxyUsername = preferences.getString("proxy_user", ""); @@ -301,7 +331,7 @@ public class ConnectionsManager { native_setProxySettings(currentAccount, proxyAddress, proxyPort, proxyUsername, proxyPassword, proxySecret); } - native_init(currentAccount, version, layer, apiId, deviceModel, systemVersion, appVersion, langCode, systemLangCode, configPath, logPath, userId, enablePushConnection, ApplicationLoader.isNetworkOnline(), ApplicationLoader.getCurrentNetworkType()); + native_init(currentAccount, version, layer, apiId, deviceModel, systemVersion, appVersion, langCode, systemLangCode, configPath, logPath, regId, userId, enablePushConnection, ApplicationLoader.isNetworkOnline(), ApplicationLoader.getCurrentNetworkType()); checkConnection(); } @@ -312,6 +342,16 @@ public class ConnectionsManager { } } + public static void setRegId(String regId, String status) { + String pushString = regId; + if (TextUtils.isEmpty(pushString) && !TextUtils.isEmpty(status)) { + pushString = status; + } + for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { + native_setRegId(a, pushString); + } + } + public static void setSystemLangCode(String langCode) { langCode = langCode.replace('_', '-').toLowerCase(); for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { @@ -387,7 +427,7 @@ public class ConnectionsManager { FileLog.d("reset app pause time"); } if (lastPauseTime != 0 && System.currentTimeMillis() - lastPauseTime > 5000) { - ContactsController.getInstance(currentAccount).checkContacts(); + getContactsController().checkContacts(); } lastPauseTime = 0; native_resumeNetwork(currentAccount, false); @@ -405,7 +445,7 @@ public class ConnectionsManager { FileLog.d("java received " + message); } KeepAliveJob.finishJob(); - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).processUpdates((TLRPC.Updates) message, false)); + Utilities.stageQueue.postRunnable(() -> AccountInstance.getInstance(currentAccount).getMessagesController().processUpdates((TLRPC.Updates) message, false)); } else { if (BuildVars.LOGS_ENABLED) { FileLog.d(String.format("java received unknown constructor 0x%x", constructor)); @@ -417,25 +457,26 @@ public class ConnectionsManager { } public static void onUpdate(final int currentAccount) { - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).updateTimerProc()); + Utilities.stageQueue.postRunnable(() -> AccountInstance.getInstance(currentAccount).getMessagesController().updateTimerProc()); } public static void onSessionCreated(final int currentAccount) { - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).getDifference()); + Utilities.stageQueue.postRunnable(() -> AccountInstance.getInstance(currentAccount).getMessagesController().getDifference()); } public static void onConnectionStateChanged(final int state, final int currentAccount) { AndroidUtilities.runOnUIThread(() -> { getInstance(currentAccount).connectionState = state; - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.didUpdateConnectionState); + AccountInstance.getInstance(currentAccount).getNotificationCenter().postNotificationName(NotificationCenter.didUpdateConnectionState); }); } public static void onLogout(final int currentAccount) { AndroidUtilities.runOnUIThread(() -> { - if (UserConfig.getInstance(currentAccount).getClientUserId() != 0) { - UserConfig.getInstance(currentAccount).clearConfig(); - MessagesController.getInstance(currentAccount).performLogout(0); + AccountInstance accountInstance = AccountInstance.getInstance(currentAccount); + if (accountInstance.getUserConfig().getClientUserId() != 0) { + accountInstance.getUserConfig().clearConfig(); + accountInstance.getMessagesController().performLogout(0); } }); } @@ -446,7 +487,7 @@ public class ConnectionsManager { public static void onBytesSent(int amount, int networkType, final int currentAccount) { try { - StatsController.getInstance(currentAccount).incrementSentBytesCount(networkType, StatsController.TYPE_TOTAL, amount); + AccountInstance.getInstance(currentAccount).getStatsController().incrementSentBytesCount(networkType, StatsController.TYPE_TOTAL, amount); } catch (Exception e) { FileLog.e(e); } @@ -491,18 +532,26 @@ public class ConnectionsManager { } public static void getHostByName(String hostName, long address) { - ResolvedDomain resolvedDomain = dnsCache.get(hostName); - if (resolvedDomain != null && SystemClock.elapsedRealtime() - resolvedDomain.ttl < 5 * 60 * 1000) { - native_onHostNameResolved(hostName, address, resolvedDomain.getAddress()); - } else { - ResolveHostByNameTask task = resolvingHostnameTasks.get(hostName); - if (task == null) { - task = new ResolveHostByNameTask(hostName); - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null); - resolvingHostnameTasks.put(hostName, task); + AndroidUtilities.runOnUIThread(() -> { + ResolvedDomain resolvedDomain = dnsCache.get(hostName); + if (resolvedDomain != null && SystemClock.elapsedRealtime() - resolvedDomain.ttl < 5 * 60 * 1000) { + native_onHostNameResolved(hostName, address, resolvedDomain.getAddress()); + } else { + ResolveHostByNameTask task = resolvingHostnameTasks.get(hostName); + if (task == null) { + task = new ResolveHostByNameTask(hostName); + try { + task.executeOnExecutor(DNS_THREAD_POOL_EXECUTOR, null, null, null); + } catch (Throwable e) { + FileLog.e(e); + native_onHostNameResolved(hostName, address, ""); + return; + } + resolvingHostnameTasks.put(hostName, task); + } + task.addAddress(address); } - task.addAddress(address); - } + }); } public static void onBytesReceived(int amount, int networkType, final int currentAccount) { @@ -519,7 +568,7 @@ public class ConnectionsManager { buff.reused = true; final TLRPC.TL_config message = TLRPC.TL_config.TLdeserialize(buff, buff.readInt32(true), true); if (message != null) { - Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(currentAccount).updateConfig(message)); + Utilities.stageQueue.postRunnable(() -> AccountInstance.getInstance(currentAccount).getMessagesController().updateConfig(message)); } } catch (Exception e) { FileLog.e(e); @@ -549,8 +598,9 @@ public class ConnectionsManager { } else { native_setProxySettings(a, "", 1080, "", "", ""); } - if (UserConfig.getInstance(a).isClientActivated()) { - MessagesController.getInstance(a).checkProxyInfo(true); + AccountInstance accountInstance = AccountInstance.getInstance(a); + if (accountInstance.getUserConfig().isClientActivated()) { + accountInstance.getMessagesController().checkProxyInfo(true); } } } @@ -573,9 +623,10 @@ public class ConnectionsManager { public static native void native_applyDatacenterAddress(int currentAccount, int datacenterId, String ipAddress, int port); public static native int native_getConnectionState(int currentAccount); public static native void native_setUserId(int currentAccount, int id); - public static native void native_init(int currentAccount, int version, int layer, int apiId, String deviceModel, String systemVersion, String appVersion, String langCode, String systemLangCode, String configPath, String logPath, int userId, boolean enablePushConnection, boolean hasNetwork, int networkType); + public static native void native_init(int currentAccount, int version, int layer, int apiId, String deviceModel, String systemVersion, String appVersion, String langCode, String systemLangCode, String configPath, String logPath, String regId, int userId, boolean enablePushConnection, boolean hasNetwork, int networkType); public static native void native_setProxySettings(int currentAccount, String address, int port, String username, String password, String secret); public static native void native_setLangCode(int currentAccount, String langCode); + public static native void native_setRegId(int currentAccount, String regId); public static native void native_setSystemLangCode(int currentAccount, String langCode); public static native void native_seSystemLangCode(int currentAccount, String langCode); public static native void native_setJava(boolean useJavaByteBuffers); @@ -584,8 +635,6 @@ public class ConnectionsManager { public static native long native_checkProxy(int currentAccount, String address, int port, String username, String password, String secret, RequestTimeDelegate requestTimeDelegate); public static native void native_onHostNameResolved(String host, long address, String ip); - - //void onHostNameResolved(JNIEnv *env, jclass c, jlong address, jstring ip) public static int generateClassGuid() { return lastClassGuid++; } @@ -597,7 +646,7 @@ public class ConnectionsManager { } isUpdating = value; if (connectionState == ConnectionStateConnected) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.didUpdateConnectionState); + AccountInstance.getInstance(currentAccount).getNotificationCenter().postNotificationName(NotificationCenter.didUpdateConnectionState); } }); } @@ -675,7 +724,7 @@ public class ConnectionsManager { return false; } - private static class ResolveHostByNameTask extends AsyncTask { + private static class ResolveHostByNameTask extends AsyncTask { private ArrayList addresses = new ArrayList<>(); private String currentHostName; @@ -692,7 +741,7 @@ public class ConnectionsManager { addresses.add(address); } - protected String doInBackground(Void... voids) { + protected ResolvedDomain doInBackground(Void... voids) { ByteArrayOutputStream outbuf = null; InputStream httpConnectionStream = null; boolean done = false; @@ -729,9 +778,7 @@ public class ConnectionsManager { for (int a = 0; a < len; a++) { addresses.add(array.getJSONObject(a).getString("data")); } - ResolvedDomain newResolvedDomain = new ResolvedDomain(addresses, SystemClock.elapsedRealtime()); - dnsCache.put(currentHostName, newResolvedDomain); - return newResolvedDomain.getAddress(); + return new ResolvedDomain(addresses, SystemClock.elapsedRealtime()); } } done = true; @@ -756,18 +803,27 @@ public class ConnectionsManager { if (!done) { try { InetAddress address = InetAddress.getByName(currentHostName); - return address.getHostAddress(); + ArrayList addresses = new ArrayList<>(1); + addresses.add(address.getHostAddress()); + return new ResolvedDomain(addresses, SystemClock.elapsedRealtime()); } catch (Exception e) { FileLog.e(e); } } - return ""; + return null; } @Override - protected void onPostExecute(final String result) { - for (int a = 0, N = addresses.size(); a < N; a++) { - native_onHostNameResolved(currentHostName, addresses.get(a), result); + protected void onPostExecute(final ResolvedDomain result) { + if (result != null) { + dnsCache.put(currentHostName, result); + for (int a = 0, N = addresses.size(); a < N; a++) { + native_onHostNameResolved(currentHostName, addresses.get(a), result.getAddress()); + } + } else { + for (int a = 0, N = addresses.size(); a < N; a++) { + native_onHostNameResolved(currentHostName, addresses.get(a), ""); + } } resolvingHostnameTasks.remove(currentHostName); } @@ -795,7 +851,7 @@ public class ConnectionsManager { } else { googleDomain = "google.com"; } - String domain = native_isTestBackend(currentAccount) != 0 ? "tapv2.stel.com" : MessagesController.getInstance(currentAccount).dcDomainName; + String domain = native_isTestBackend(currentAccount) != 0 ? "tapv2.stel.com" : AccountInstance.getInstance(currentAccount).getMessagesController().dcDomainName; int len = Utilities.random.nextInt(116) + 13; final String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @@ -886,7 +942,7 @@ public class ConnectionsManager { Utilities.stageQueue.postRunnable(() -> { if (result != null) { currentTask = null; - native_applyDnsConfig(currentAccount, result.address, UserConfig.getInstance(currentAccount).getClientPhone()); + native_applyDnsConfig(currentAccount, result.address, AccountInstance.getInstance(currentAccount).getUserConfig().getClientPhone()); } else { if (BuildVars.LOGS_ENABLED) { FileLog.d("failed to get dns txt result"); @@ -937,7 +993,7 @@ public class ConnectionsManager { try { NativeByteBuffer buffer = new NativeByteBuffer(bytes.length); buffer.writeBytes(bytes); - native_applyDnsConfig(currentAccount, buffer.address, UserConfig.getInstance(currentAccount).getClientPhone()); + native_applyDnsConfig(currentAccount, buffer.address, AccountInstance.getInstance(currentAccount).getUserConfig().getClientPhone()); } catch (Exception e) { FileLog.e(e); } @@ -1045,7 +1101,7 @@ public class ConnectionsManager { protected void onPostExecute(final NativeByteBuffer result) { Utilities.stageQueue.postRunnable(() -> { if (result != null) { - native_applyDnsConfig(currentAccount, result.address, UserConfig.getInstance(currentAccount).getClientPhone()); + native_applyDnsConfig(currentAccount, result.address, AccountInstance.getInstance(currentAccount).getUserConfig().getClientPhone()); } else { if (BuildVars.LOGS_ENABLED) { FileLog.d("failed to get azure result"); diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java index 168d89de5..8eeb45c51 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java @@ -61,7 +61,7 @@ public class TLRPC { public static final int MESSAGE_FLAG_EDITED = 0x00008000; public static final int MESSAGE_FLAG_MEGAGROUP = 0x80000000; - public static final int LAYER = 100; + public static final int LAYER = 103; public static class TL_chatBannedRights extends TLObject { public static int constructor = 0x9f120418; @@ -2567,41 +2567,94 @@ public class TLRPC { } } - public static class TL_contacts_link extends TLObject { - public static int constructor = 0x3ace484c; + public static abstract class ContactLink_layer101 extends TLObject { - public ContactLink my_link; - public ContactLink foreign_link; - public User user; + public static ContactLink_layer101 TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + ContactLink_layer101 result = null; + switch (constructor) { + case 0xfeedd3ad: + result = new TL_contactLinkNone(); + break; + case 0xd502c2d0: + result = new TL_contactLinkContact(); + break; + case 0x5f4f9247: + result = new TL_contactLinkUnknown(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in ContactLink", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } - public static TL_contacts_link TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - if (TL_contacts_link.constructor != constructor) { - if (exception) { - throw new RuntimeException(String.format("can't parse magic %x in TL_contacts_link", constructor)); - } else { - return null; - } - } - TL_contacts_link result = new TL_contacts_link(); - result.readParams(stream, exception); - return result; - } + public static class TL_contactLinkNone extends ContactLink_layer101 { + public static int constructor = 0xfeedd3ad; - public void readParams(AbstractSerializedData stream, boolean exception) { - my_link = ContactLink.TLdeserialize(stream, stream.readInt32(exception), exception); - foreign_link = ContactLink.TLdeserialize(stream, stream.readInt32(exception), exception); - user = User.TLdeserialize(stream, stream.readInt32(exception), exception); - } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - my_link.serializeToStream(stream); - foreign_link.serializeToStream(stream); - user.serializeToStream(stream); - } - } + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } - public static abstract class EncryptedFile extends TLObject { + public static class TL_contactLinkContact extends ContactLink_layer101 { + public static int constructor = 0xd502c2d0; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_contactLinkUnknown extends ContactLink_layer101 { + public static int constructor = 0x5f4f9247; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + + public static class TL_contacts_link_layer101 extends TLObject { + public static int constructor = 0x3ace484c; + + public ContactLink_layer101 my_link; + public ContactLink_layer101 foreign_link; + public User user; + + public static TL_contacts_link_layer101 TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_contacts_link_layer101.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_contacts_link", constructor)); + } else { + return null; + } + } + TL_contacts_link_layer101 result = new TL_contacts_link_layer101(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + my_link = ContactLink_layer101.TLdeserialize(stream, stream.readInt32(exception), exception); + foreign_link = ContactLink_layer101.TLdeserialize(stream, stream.readInt32(exception), exception); + user = User.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + my_link.serializeToStream(stream); + foreign_link.serializeToStream(stream); + user.serializeToStream(stream); + } + } + + public static abstract class EncryptedFile extends TLObject { public long id; public long access_hash; public int size; @@ -4656,36 +4709,51 @@ public class TLRPC { } } - public static class TL_peerSettings extends TLObject { - public static int constructor = 0x818426cd; + public static class TL_peerSettings extends TLObject { + public static int constructor = 0x818426cd; - public int flags; - public boolean report_spam; + public int flags; + public boolean report_spam; + public boolean add_contact; + public boolean block_contact; + public boolean share_contact; + public boolean need_contacts_exception; + public boolean report_geo; - public static TL_peerSettings TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - if (TL_peerSettings.constructor != constructor) { - if (exception) { - throw new RuntimeException(String.format("can't parse magic %x in TL_peerSettings", constructor)); - } else { - return null; - } - } - TL_peerSettings result = new TL_peerSettings(); - result.readParams(stream, exception); - return result; - } + public static TL_peerSettings TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_peerSettings.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_peerSettings", constructor)); + } else { + return null; + } + } + TL_peerSettings result = new TL_peerSettings(); + result.readParams(stream, exception); + return result; + } - public void readParams(AbstractSerializedData stream, boolean exception) { - flags = stream.readInt32(exception); - report_spam = (flags & 1) != 0; - } + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + report_spam = (flags & 1) != 0; + add_contact = (flags & 2) != 0; + block_contact = (flags & 4) != 0; + share_contact = (flags & 8) != 0; + need_contacts_exception = (flags & 16) != 0; + report_geo = (flags & 32) != 0; + } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - flags = report_spam ? (flags | 1) : (flags &~ 1); - stream.writeInt32(flags); - } - } + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = report_spam ? (flags | 1) : (flags &~ 1); + flags = add_contact ? (flags | 2) : (flags &~ 2); + flags = block_contact ? (flags | 4) : (flags &~ 4); + flags = share_contact ? (flags | 8) : (flags &~ 8); + flags = need_contacts_exception ? (flags | 16) : (flags &~ 16); + flags = report_geo ? (flags | 32) : (flags &~ 32); + stream.writeInt32(flags); + } + } public static abstract class InputDialogPeer extends TLObject { @@ -7705,12 +7773,14 @@ public class TLRPC { public boolean can_set_stickers; public boolean hidden_prehistory; public boolean can_view_stats; + public boolean can_set_location; public int banned_count; public int online_count; public StickerSet stickerset; public int available_min_id; public int call_msg_id; public int linked_chat_id; + public ChannelLocation location; public int pts; public static ChatFull TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { @@ -7725,9 +7795,12 @@ public class TLRPC { case 0x2e02a614: result = new TL_chatFull_layer87(); break; - case 0x9882e516: + case 0x10916653: result = new TL_channelFull(); break; + case 0x9882e516: + result = new TL_channelFull_layer101(); + break; case 0x17f45fcf: result = new TL_channelFull_layer71(); break; @@ -7773,6 +7846,149 @@ public class TLRPC { } public static class TL_channelFull extends ChatFull { + public static int constructor = 0x10916653; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + can_view_participants = (flags & 8) != 0; + can_set_username = (flags & 64) != 0; + can_set_stickers = (flags & 128) != 0; + hidden_prehistory = (flags & 1024) != 0; + can_view_stats = (flags & 4096) != 0; + can_set_location = (flags & 65536) != 0; + id = stream.readInt32(exception); + about = stream.readString(exception); + if ((flags & 1) != 0) { + participants_count = stream.readInt32(exception); + } + if ((flags & 2) != 0) { + admins_count = stream.readInt32(exception); + } + if ((flags & 4) != 0) { + kicked_count = stream.readInt32(exception); + } + if ((flags & 4) != 0) { + banned_count = stream.readInt32(exception); + } + if ((flags & 8192) != 0) { + online_count = stream.readInt32(exception); + } + read_inbox_max_id = stream.readInt32(exception); + read_outbox_max_id = stream.readInt32(exception); + unread_count = stream.readInt32(exception); + chat_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); + exported_invite = ExportedChatInvite.TLdeserialize(stream, stream.readInt32(exception), 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++) { + BotInfo object = BotInfo.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + bot_info.add(object); + } + if ((flags & 16) != 0) { + migrated_from_chat_id = stream.readInt32(exception); + } + if ((flags & 16) != 0) { + migrated_from_max_id = stream.readInt32(exception); + } + if ((flags & 32) != 0) { + pinned_msg_id = stream.readInt32(exception); + } + if ((flags & 256) != 0) { + stickerset = StickerSet.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 512) != 0) { + available_min_id = stream.readInt32(exception); + } + if ((flags & 2048) != 0) { + folder_id = stream.readInt32(exception); + } + if ((flags & 16384) != 0) { + linked_chat_id = stream.readInt32(exception); + } + if ((flags & 32768) != 0) { + location = ChannelLocation.TLdeserialize(stream, stream.readInt32(exception), exception); + } + pts = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = can_view_participants ? (flags | 8) : (flags &~ 8); + flags = can_set_username ? (flags | 64) : (flags &~ 64); + flags = can_set_stickers ? (flags | 128) : (flags &~ 128); + flags = hidden_prehistory ? (flags | 1024) : (flags &~ 1024); + flags = can_view_stats ? (flags | 4096) : (flags &~ 4096); + flags = can_set_location ? (flags | 65536) : (flags &~ 65536); + stream.writeInt32(flags); + stream.writeInt32(id); + stream.writeString(about); + if ((flags & 1) != 0) { + stream.writeInt32(participants_count); + } + if ((flags & 2) != 0) { + stream.writeInt32(admins_count); + } + if ((flags & 4) != 0) { + stream.writeInt32(kicked_count); + } + if ((flags & 4) != 0) { + stream.writeInt32(banned_count); + } + if ((flags & 8192) != 0) { + stream.writeInt32(online_count); + } + stream.writeInt32(read_inbox_max_id); + stream.writeInt32(read_outbox_max_id); + stream.writeInt32(unread_count); + chat_photo.serializeToStream(stream); + notify_settings.serializeToStream(stream); + exported_invite.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = bot_info.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + bot_info.get(a).serializeToStream(stream); + } + if ((flags & 16) != 0) { + stream.writeInt32(migrated_from_chat_id); + } + if ((flags & 16) != 0) { + stream.writeInt32(migrated_from_max_id); + } + if ((flags & 32) != 0) { + stream.writeInt32(pinned_msg_id); + } + if ((flags & 256) != 0) { + stickerset.serializeToStream(stream); + } + if ((flags & 512) != 0) { + stream.writeInt32(available_min_id); + } + if ((flags & 2048) != 0) { + stream.writeInt32(folder_id); + } + if ((flags & 16384) != 0) { + stream.writeInt32(linked_chat_id); + } + if ((flags & 32768) != 0) { + location.serializeToStream(stream); + } + stream.writeInt32(pts); + } + } + + public static class TL_channelFull_layer101 extends TL_channelFull { public static int constructor = 0x9882e516; @@ -13888,6 +14104,9 @@ public class TLRPC { public static ReportReason TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { ReportReason result = null; switch (constructor) { + case 0xdbd4feed: + result = new TL_inputReportReasonGeoIrrelevant(); + break; case 0xe1746d0a: result = new TL_inputReportReasonOther(); break; @@ -13917,6 +14136,15 @@ public class TLRPC { } } + public static class TL_inputReportReasonGeoIrrelevant extends ReportReason { + public static int constructor = 0xdbd4feed; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + public static class TL_inputReportReasonOther extends ReportReason { public static int constructor = 0xe1746d0a; @@ -15893,58 +16121,6 @@ public class TLRPC { } } - public static abstract class ContactLink extends TLObject { - - public static ContactLink TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - ContactLink result = null; - switch (constructor) { - case 0xfeedd3ad: - result = new TL_contactLinkNone(); - break; - case 0xd502c2d0: - result = new TL_contactLinkContact(); - break; - case 0x5f4f9247: - result = new TL_contactLinkUnknown(); - break; - } - if (result == null && exception) { - throw new RuntimeException(String.format("can't parse magic %x in ContactLink", constructor)); - } - if (result != null) { - result.readParams(stream, exception); - } - return result; - } - } - - public static class TL_contactLinkNone extends ContactLink { - public static int constructor = 0xfeedd3ad; - - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - } - } - - public static class TL_contactLinkContact extends ContactLink { - public static int constructor = 0xd502c2d0; - - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - } - } - - public static class TL_contactLinkUnknown extends ContactLink { - public static int constructor = 0x5f4f9247; - - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - } - } - public static abstract class PageBlock extends TLObject { public boolean first; //custom public boolean bottom; //custom @@ -18242,6 +18418,9 @@ public class TLRPC { case 0x6e6fe51c: result = new TL_updateDialogPinned(); break; + case 0x6a7e7366: + result = new TL_updatePeerSettings(); + break; case 0x12b9417b: result = new TL_updateUserPhone(); break; @@ -18290,6 +18469,9 @@ public class TLRPC { case 0xe511996d: result = new TL_updateFavedStickers(); break; + case 0xb4afcfb0: + result = new TL_updatePeerLocated(); + break; case 0xea4b0e5c: result = new TL_updateChatParticipantAdd(); break; @@ -18353,9 +18535,6 @@ public class TLRPC { case 0x9c974fdf: result = new TL_updateReadHistoryInbox(); break; - case 0x9d2e67c5: - result = new TL_updateContactLink(); - break; case 0x9375341e: result = new TL_updateSavedGifs(); break; @@ -18569,6 +18748,24 @@ public class TLRPC { } } + public static class TL_updatePeerSettings extends Update { + public static int constructor = 0x6a7e7366; + + public Peer peer; + public TL_peerSettings settings; + + public void readParams(AbstractSerializedData stream, boolean exception) { + peer = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + settings = TL_peerSettings.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + settings.serializeToStream(stream); + } + } + public static class TL_updateUserPhone extends Update { public static int constructor = 0x12b9417b; @@ -18879,6 +19076,40 @@ public class TLRPC { } } + public static class TL_updatePeerLocated extends Update { + public static int constructor = 0xb4afcfb0; + + public ArrayList peers = 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++) { + TL_peerLocated object = TL_peerLocated.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + peers.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(0x1cb5c415); + int count = peers.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + peers.get(a).serializeToStream(stream); + } + } + } + public static class TL_updateChatParticipantAdd extends Update { public static int constructor = 0xea4b0e5c; @@ -19413,27 +19644,6 @@ public class TLRPC { } } - public static class TL_updateContactLink extends Update { - public static int constructor = 0x9d2e67c5; - - public int user_id; - public ContactLink my_link; - public ContactLink foreign_link; - - public void readParams(AbstractSerializedData stream, boolean exception) { - user_id = stream.readInt32(exception); - my_link = ContactLink.TLdeserialize(stream, stream.readInt32(exception), exception); - foreign_link = ContactLink.TLdeserialize(stream, stream.readInt32(exception), exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(user_id); - my_link.serializeToStream(stream); - foreign_link.serializeToStream(stream); - } - } - public static class TL_updateSavedGifs extends Update { public static int constructor = 0x9375341e; @@ -20909,6 +21119,55 @@ public class TLRPC { } } + public static abstract class ChannelLocation extends TLObject { + + public static ChannelLocation TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + ChannelLocation result = null; + switch (constructor) { + case 0xbfb5ad8b: + result = new TL_channelLocationEmpty(); + break; + case 0x209b82db: + result = new TL_channelLocation(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in ChannelLocation", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_channelLocationEmpty extends ChannelLocation { + public static int constructor = 0xbfb5ad8b; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_channelLocation extends ChannelLocation { + public static int constructor = 0x209b82db; + + public GeoPoint geo_point; + public String address; + + public void readParams(AbstractSerializedData stream, boolean exception) { + geo_point = GeoPoint.TLdeserialize(stream, stream.readInt32(exception), exception); + address = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + geo_point.serializeToStream(stream); + stream.writeString(address); + } + } + public static abstract class Photo extends TLObject { public int flags; @@ -25155,6 +25414,9 @@ public class TLRPC { case 0x709b2405: result = new TL_channelAdminLogEventActionEditMessage(); break; + case 0xe6b76ae: + result = new TL_channelAdminLogEventActionChangeLocation(); + break; case 0xd5676710: result = new TL_channelAdminLogEventActionParticipantToggleAdmin(); break; @@ -25285,6 +25547,24 @@ public class TLRPC { } } + public static class TL_channelAdminLogEventActionChangeLocation extends ChannelAdminLogEventAction { + public static int constructor = 0xe6b76ae; + + public ChannelLocation prev_value; + public ChannelLocation new_value; + + public void readParams(AbstractSerializedData stream, boolean exception) { + prev_value = ChannelLocation.TLdeserialize(stream, stream.readInt32(exception), exception); + new_value = ChannelLocation.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + prev_value.serializeToStream(stream); + new_value.serializeToStream(stream); + } + } + public static class TL_channelAdminLogEventActionParticipantToggleAdmin extends ChannelAdminLogEventAction { public static int constructor = 0xd5676710; @@ -25564,6 +25844,40 @@ public class TLRPC { } } + public static class TL_peerLocated extends TLObject { + public static int constructor = 0xca461b5d; + + public Peer peer; + public int expires; + public int distance; + + public static TL_peerLocated TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_peerLocated.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_peerLocated", constructor)); + } else { + return null; + } + } + TL_peerLocated result = new TL_peerLocated(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + peer = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + expires = stream.readInt32(exception); + distance = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + stream.writeInt32(expires); + stream.writeInt32(distance); + } + } + public static class TL_autoDownloadSettings extends TLObject { public static int constructor = 0xd246fd47; @@ -25708,6 +26022,7 @@ public class TLRPC { public boolean kicked; public boolean deactivated; public boolean left; + public boolean has_geo; public ChatPhoto photo; public int participants_count; public int version; @@ -26229,6 +26544,7 @@ public class TLRPC { min = (flags & 4096) != 0; scam = (flags & 524288) != 0; has_link = (flags & 1048576) != 0; + has_geo = (flags & 2097152) != 0; id = stream.readInt32(exception); if ((flags & 8192) != 0) { access_hash = stream.readInt64(exception); @@ -26269,6 +26585,7 @@ public class TLRPC { 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); stream.writeInt32(flags); stream.writeInt32(id); if ((flags & 8192) != 0) { @@ -26595,6 +26912,7 @@ public class TLRPC { public boolean installed; public boolean archived; public boolean official; + public boolean animated; public boolean masks; public long id; public long access_hash; @@ -26746,6 +27064,7 @@ public class TLRPC { archived = (flags & 2) != 0; official = (flags & 4) != 0; masks = (flags & 8) != 0; + animated = (flags & 32) != 0; if ((flags & 1) != 0) { installed_date = stream.readInt32(exception); } @@ -26768,6 +27087,7 @@ public class TLRPC { flags = archived ? (flags | 2) : (flags &~ 2); flags = official ? (flags | 4) : (flags &~ 4); flags = masks ? (flags | 8) : (flags &~ 8); + flags = animated ? (flags | 32) : (flags &~ 32); stream.writeInt32(flags); if ((flags & 1) != 0) { stream.writeInt32(installed_date); @@ -28165,23 +28485,27 @@ public class TLRPC { public boolean can_pin_message; public User user; public String about; - public TL_contacts_link link; + public TL_contacts_link_layer101 link; public Photo profile_photo; public PeerNotifySettings notify_settings; public BotInfo bot_info; public int pinned_msg_id; public int common_chats_count; public int folder_id; + public TL_peerSettings settings; public static UserFull TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { UserFull result = null; switch (constructor) { case 0x745559cc: - result = new TL_userFull(); + result = new TL_userFull_layer101(); break; case 0x8ea4a881: result = new TL_userFull_layer98(); break; + case 0xedf17c12: + result = new TL_userFull(); + break; } if (result == null && exception) { throw new RuntimeException(String.format("can't parse magic %x in UserFull", constructor)); @@ -28193,7 +28517,7 @@ public class TLRPC { } } - public static class TL_userFull extends UserFull { + public static class TL_userFull_layer101 extends TL_userFull { public static int constructor = 0x745559cc; @@ -28207,7 +28531,7 @@ public class TLRPC { if ((flags & 2) != 0) { about = stream.readString(exception); } - link = TL_contacts_link.TLdeserialize(stream, stream.readInt32(exception), exception); + link = TL_contacts_link_layer101.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 4) != 0) { profile_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); } @@ -28267,7 +28591,7 @@ public class TLRPC { if ((flags & 2) != 0) { about = stream.readString(exception); } - link = TL_contacts_link.TLdeserialize(stream, stream.readInt32(exception), exception); + link = TL_contacts_link_layer101.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 4) != 0) { profile_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); } @@ -28307,6 +28631,66 @@ public class TLRPC { } } + public static class TL_userFull extends UserFull { + public static int constructor = 0xedf17c12; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + blocked = (flags & 1) != 0; + phone_calls_available = (flags & 16) != 0; + phone_calls_private = (flags & 32) != 0; + can_pin_message = (flags & 128) != 0; + user = User.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 2) != 0) { + about = stream.readString(exception); + } + settings = TL_peerSettings.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 4) != 0) { + profile_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 8) != 0) { + bot_info = BotInfo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 64) != 0) { + pinned_msg_id = stream.readInt32(exception); + } + common_chats_count = stream.readInt32(exception); + if ((flags & 2048) != 0) { + folder_id = stream.readInt32(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = blocked ? (flags | 1) : (flags &~ 1); + flags = phone_calls_available ? (flags | 16) : (flags &~ 16); + flags = phone_calls_private ? (flags | 32) : (flags &~ 32); + flags = can_pin_message ? (flags | 128) : (flags &~ 128); + stream.writeInt32(flags); + user.serializeToStream(stream); + if ((flags & 2) != 0) { + stream.writeString(about); + } + settings.serializeToStream(stream); + if ((flags & 4) != 0) { + profile_photo.serializeToStream(stream); + } + notify_settings.serializeToStream(stream); + if ((flags & 8) != 0) { + bot_info.serializeToStream(stream); + } + if ((flags & 64) != 0) { + stream.writeInt32(pinned_msg_id); + } + stream.writeInt32(common_chats_count); + if ((flags & 2048) != 0) { + stream.writeInt32(folder_id); + } + } + } + public static abstract class Updates extends TLObject { public ArrayList updates = new ArrayList<>(); public ArrayList users = new ArrayList<>(); @@ -30617,33 +31001,37 @@ public class TLRPC { } } - public static class TL_account_registerDevice extends TLObject { - public static int constructor = 0x5cbea590; + public static class TL_account_registerDevice extends TLObject { + public static int constructor = 0x68976c6f; - public int token_type; - public String token; - public boolean app_sandbox; - public byte[] secret; - public ArrayList other_uids = new ArrayList<>(); + public int flags; + public boolean no_muted; + public int token_type; + public String token; + public boolean app_sandbox; + public byte[] secret; + public ArrayList other_uids = new ArrayList<>(); - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return Bool.TLdeserialize(stream, constructor, exception); - } + 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(token_type); - stream.writeString(token); - stream.writeBool(app_sandbox); - stream.writeByteArray(secret); - stream.writeInt32(0x1cb5c415); - int count = other_uids.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - stream.writeInt32(other_uids.get(a)); - } - } - } + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = no_muted ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + stream.writeInt32(token_type); + stream.writeString(token); + stream.writeBool(app_sandbox); + stream.writeByteArray(secret); + stream.writeInt32(0x1cb5c415); + int count = other_uids.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + stream.writeInt32(other_uids.get(a)); + } + } + } public static class TL_account_unregisterDevice extends TLObject { public static int constructor = 0x3076c4bf; @@ -30889,40 +31277,25 @@ public class TLRPC { } } - public static class TL_contacts_deleteContact extends TLObject { - public static int constructor = 0x8e953744; + public static class TL_contacts_deleteContacts extends TLObject { + public static int constructor = 0x96a0e00; - public InputUser id; + public ArrayList id = new ArrayList<>(); - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return TL_contacts_link.TLdeserialize(stream, constructor, exception); - } + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - id.serializeToStream(stream); - } - } - - public static class TL_contacts_deleteContacts extends TLObject { - public static int constructor = 0x59ab389e; - - public ArrayList id = 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); + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); stream.writeInt32(0x1cb5c415); - int count = id.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - id.get(a).serializeToStream(stream); - } - } - } + int count = id.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + id.get(a).serializeToStream(stream); + } + } + } public static class TL_contacts_deleteByPhones extends TLObject { public static int constructor = 0x1013fd9e; @@ -31057,6 +31430,61 @@ public class TLRPC { } } + public static class TL_contacts_addContact extends TLObject { + public static int constructor = 0xe8f463d0; + + public int flags; + public boolean add_phone_privacy_exception; + public InputUser id; + public String first_name; + public String last_name; + public String phone; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = add_phone_privacy_exception ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + id.serializeToStream(stream); + stream.writeString(first_name); + stream.writeString(last_name); + stream.writeString(phone); + } + } + + public static class TL_contacts_acceptContact extends TLObject { + public static int constructor = 0xf831a20f; + + public InputUser id; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + id.serializeToStream(stream); + } + } + + public static class TL_contacts_getLocated extends TLObject { + public static int constructor = 0xa356056; + + public InputGeoPoint geo_point; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + geo_point.serializeToStream(stream); + } + } + public static class TL_messages_getMessages extends TLObject { public static int constructor = 0x4222fa74; @@ -31341,19 +31769,6 @@ public class TLRPC { } } - public static class TL_channels_getBroadcastsForDiscussion extends TLObject { - public static int constructor = 0x1a87f304; - - - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return messages_Chats.TLdeserialize(stream, constructor, exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - } - } - public static class TL_channels_setDiscussionGroup extends TLObject { public static int constructor = 0x40582bb2; @@ -31371,6 +31786,44 @@ public class TLRPC { } } + public static class TL_channels_editCreator extends TLObject { + public static int constructor = 0x8f38cd1f; + + public InputChannel channel; + public InputUser user_id; + public InputCheckPasswordSRP password; + + 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); + user_id.serializeToStream(stream); + password.serializeToStream(stream); + } + } + + public static class TL_channels_editLocation extends TLObject { + public static int constructor = 0x58e63f6d; + + public InputChannel channel; + public InputGeoPoint geo_point; + public String address; + + 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); + geo_point.serializeToStream(stream); + stream.writeString(address); + } + } + public static class TL_messages_editChatAdmin extends TLObject { public static int constructor = 0xa9e69f2e; @@ -31406,8 +31859,10 @@ public class TLRPC { } public static class TL_messages_searchGlobal extends TLObject { - public static int constructor = 0xf79c611; + public static int constructor = 0xbf7225a4; + public int flags; + public int folder_id; public String q; public int offset_rate; public InputPeer offset_peer; @@ -31420,6 +31875,10 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(flags); + if ((flags & 1) != 0) { + stream.writeInt32(folder_id); + } stream.writeString(q); stream.writeInt32(offset_rate); offset_peer.serializeToStream(stream); @@ -31670,21 +32129,6 @@ public class TLRPC { } } - public static class TL_messages_hideReportSpam extends TLObject { - public static int constructor = 0xa8f1709b; - - public InputPeer peer; - - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return Bool.TLdeserialize(stream, constructor, exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - peer.serializeToStream(stream); - } - } - public static class TL_messages_getPeerSettings extends TLObject { public static int constructor = 0x3672e09c; @@ -34502,6 +34946,21 @@ public class TLRPC { } } + public static class TL_messages_hidePeerSettingsBar extends TLObject { + public static int constructor = 0x4facb138; + + public InputPeer peer; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + } + } + public static class TL_help_getAppChangelog extends TLObject { public static int constructor = 0x9010ef6f; @@ -34904,28 +35363,36 @@ public class TLRPC { } } - public static class TL_channels_createChannel extends TLObject { - public static int constructor = 0xf4893d7f; + public static class TL_channels_createChannel extends TLObject { + public static int constructor = 0x3d5fb10f; - public int flags; - public boolean broadcast; - public boolean megagroup; - public String title; - public String about; + public int flags; + public boolean broadcast; + public boolean megagroup; + public String title; + public String about; + public InputGeoPoint geo_point; + public String address; - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return Updates.TLdeserialize(stream, constructor, exception); - } + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - flags = broadcast ? (flags | 1) : (flags &~ 1); - flags = megagroup ? (flags | 2) : (flags &~ 2); - stream.writeInt32(flags); - stream.writeString(title); - stream.writeString(about); - } - } + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = broadcast ? (flags | 1) : (flags &~ 1); + flags = megagroup ? (flags | 2) : (flags &~ 2); + stream.writeInt32(flags); + stream.writeString(title); + stream.writeString(about); + if ((flags & 4) != 0) { + geo_point.serializeToStream(stream); + } + if ((flags & 4) != 0) { + stream.writeString(address); + } + } + } public static class TL_channels_editAdmin extends TLObject { public static int constructor = 0x70f893ba; @@ -35115,18 +35582,24 @@ public class TLRPC { } } - public static class TL_channels_getAdminedPublicChannels extends TLObject { - public static int constructor = 0x8d8d82d7; + public static class TL_channels_getAdminedPublicChannels extends TLObject { + public static int constructor = 0xf8b036af; + public int flags; + public boolean by_location; + public boolean check_limit; - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return TL_messages_chats.TLdeserialize(stream, constructor, exception); - } + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return messages_Chats.TLdeserialize(stream, constructor, exception); + } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - } - } + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = by_location ? (flags | 1) : (flags &~ 1); + flags = check_limit ? (flags | 2) : (flags &~ 2); + stream.writeInt32(flags); + } + } public static class TL_channels_editBanned extends TLObject { public static int constructor = 0x72796912; @@ -38237,7 +38710,7 @@ public class TLRPC { public static class TL_chatChannelParticipant extends ChatParticipant { public static int constructor = 0xc8d7493e; - public TLRPC.ChannelParticipant channelParticipant; + public ChannelParticipant channelParticipant; } //ChatParticipant end diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java index f5bb64ecd..ac2fcd69c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java @@ -648,6 +648,10 @@ public class ActionBar extends FrameLayout { menu.openSearchField(!isSearchFieldVisible, text, animated); } + public void setSearchFieldText(String text) { + menu.setSearchFieldText(text); + } + @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); 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 debd27ed9..88bdef226 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java @@ -167,6 +167,7 @@ public class ActionBarLayout extends FrameLayout { private int[][] animateStartColors = new int[2][]; private int[][] animateEndColors = new int[2][]; private ThemeDescription[][] themeAnimatorDescriptions = new ThemeDescription[2][]; + private ThemeDescription[] presentingFragmentDescriptions; private ThemeDescription.ThemeDescriptionDelegate[] themeAnimatorDelegate = new ThemeDescription.ThemeDescriptionDelegate[2]; private AnimatorSet themeAnimatorSet; private float themeAnimationValue; @@ -452,6 +453,9 @@ public class ActionBarLayout extends FrameLayout { fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); } lastFragment.onResume(); + if (themeAnimatorSet != null) { + presentingFragmentDescriptions = lastFragment.getThemeDescriptions(); + } } public boolean onTouchEvent(MotionEvent ev) { @@ -476,7 +480,7 @@ public class ActionBarLayout extends FrameLayout { int dx = Math.max(0, (int) (ev.getX() - startedTrackingX)); int dy = Math.abs((int) ev.getY() - startedTrackingY); velocityTracker.addMovement(ev); - if (maybeStartTracking && !startedTracking && dx >= AndroidUtilities.getPixelsInCM(0.4f, true) && Math.abs(dx) / 3 > dy) { + if (!inPreviewMode && maybeStartTracking && !startedTracking && dx >= AndroidUtilities.getPixelsInCM(0.4f, true) && Math.abs(dx) / 3 > dy) { BaseFragment currentFragment = fragmentsStack.get(fragmentsStack.size() - 1); if (currentFragment.canBeginSlide()) { prepareForMoving(ev); @@ -832,6 +836,10 @@ public class ActionBarLayout extends FrameLayout { } } + if (themeAnimatorSet != null) { + presentingFragmentDescriptions = fragment.getThemeDescriptions(); + } + if (needAnimation || preview) { if (useAlphaAnimations && fragmentsStack.size() == 1) { presentFragmentInternalRemoveOld(removeLast, currentFragment); @@ -839,6 +847,9 @@ public class ActionBarLayout extends FrameLayout { transitionAnimationStartTime = System.currentTimeMillis(); transitionAnimationInProgress = true; onOpenAnimationEndRunnable = () -> { + if (currentFragment != null) { + currentFragment.onTransitionAnimationEnd(false, false); + } fragment.onTransitionAnimationEnd(true, false); fragment.onBecomeFullyVisible(); }; @@ -848,7 +859,9 @@ public class ActionBarLayout extends FrameLayout { backgroundView.setVisibility(VISIBLE); animators.add(ObjectAnimator.ofFloat(backgroundView, "alpha", 0.0f, 1.0f)); } - + if (currentFragment != null) { + currentFragment.onTransitionAnimationStart(false, false); + } fragment.onTransitionAnimationStart(true, false); currentAnimation = new AnimatorSet(); currentAnimation.playTogether(animators); @@ -875,9 +888,15 @@ public class ActionBarLayout extends FrameLayout { presentFragmentInternalRemoveOld(removeLast, currentFragment); containerView.setTranslationX(0); } + if (currentFragment != null) { + currentFragment.onTransitionAnimationEnd(false, false); + } fragment.onTransitionAnimationEnd(true, false); fragment.onBecomeFullyVisible(); }; + if (currentFragment != null) { + currentFragment.onTransitionAnimationStart(false, false); + } fragment.onTransitionAnimationStart(true, false); AnimatorSet animation = null; if (!preview) { @@ -932,6 +951,10 @@ public class ActionBarLayout extends FrameLayout { backgroundView.setAlpha(1.0f); backgroundView.setVisibility(VISIBLE); } + if (currentFragment != null) { + currentFragment.onTransitionAnimationStart(false, false); + currentFragment.onTransitionAnimationEnd(false, false); + } fragment.onTransitionAnimationStart(true, false); fragment.onTransitionAnimationEnd(true, false); fragment.onBecomeFullyVisible(); @@ -1091,6 +1114,9 @@ public class ActionBarLayout extends FrameLayout { previousFragment.onTransitionAnimationStart(true, true); currentFragment.onTransitionAnimationStart(false, false); previousFragment.onResume(); + if (themeAnimatorSet != null) { + presentingFragmentDescriptions = previousFragment.getThemeDescriptions(); + } currentActionBar = previousFragment.actionBar; if (!previousFragment.hasOwnBackground && fragmentView.getBackground() == null) { fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); @@ -1305,6 +1331,12 @@ public class ActionBarLayout extends FrameLayout { } } } + if (presentingFragmentDescriptions != null) { + for (int i = 0; i < presentingFragmentDescriptions.length; i++) { + String key = presentingFragmentDescriptions[i].getCurrentKey(); + presentingFragmentDescriptions[i].setColor(Theme.getColor(key), false, false); + } + } } @Keep @@ -1371,6 +1403,7 @@ public class ActionBarLayout extends FrameLayout { themeAnimatorDelegate[a] = null; } Theme.setAnimatingColor(false); + presentingFragmentDescriptions = null; themeAnimatorSet = null; } } @@ -1385,6 +1418,7 @@ public class ActionBarLayout extends FrameLayout { themeAnimatorDelegate[a] = null; } Theme.setAnimatingColor(false); + presentingFragmentDescriptions = null; themeAnimatorSet = null; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java index 3316d8af6..249d80d97 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java @@ -52,34 +52,45 @@ public class ActionBarMenu extends LinearLayout { } public ActionBarMenuItem addItem(int id, Drawable drawable) { - return addItem(id, 0, isActionMode ? parentActionBar.itemsActionModeBackgroundColor : parentActionBar.itemsBackgroundColor, drawable, AndroidUtilities.dp(48), null); + return addItem(id, 0, null, isActionMode ? parentActionBar.itemsActionModeBackgroundColor : parentActionBar.itemsBackgroundColor, drawable, AndroidUtilities.dp(48), null); } public ActionBarMenuItem addItem(int id, int icon) { return addItem(id, icon, isActionMode ? parentActionBar.itemsActionModeBackgroundColor : parentActionBar.itemsBackgroundColor); } + public ActionBarMenuItem addItem(int id, CharSequence text) { + return addItem(id, 0, text, isActionMode ? parentActionBar.itemsActionModeBackgroundColor : parentActionBar.itemsBackgroundColor, null, 0, text); + } + public ActionBarMenuItem addItem(int id, int icon, int backgroundColor) { - return addItem(id, icon, backgroundColor, null, AndroidUtilities.dp(48), null); + return addItem(id, icon, null, backgroundColor, null, AndroidUtilities.dp(48), null); } public ActionBarMenuItem addItemWithWidth(int id, int icon, int width) { - return addItem(id, icon, isActionMode ? parentActionBar.itemsActionModeBackgroundColor : parentActionBar.itemsBackgroundColor, null, width, null); + return addItem(id, icon, null, isActionMode ? parentActionBar.itemsActionModeBackgroundColor : parentActionBar.itemsBackgroundColor, null, width, null); } public ActionBarMenuItem addItemWithWidth(int id, int icon, int width, CharSequence title) { - return addItem(id, icon, isActionMode ? parentActionBar.itemsActionModeBackgroundColor : parentActionBar.itemsBackgroundColor, null, width, title); + return addItem(id, icon, null, isActionMode ? parentActionBar.itemsActionModeBackgroundColor : parentActionBar.itemsBackgroundColor, null, width, title); } - public ActionBarMenuItem addItem(int id, int icon, int backgroundColor, Drawable drawable, int width, CharSequence title) { - ActionBarMenuItem menuItem = new ActionBarMenuItem(getContext(), this, backgroundColor, isActionMode ? parentActionBar.itemsActionModeColor : parentActionBar.itemsColor); + public ActionBarMenuItem addItem(int id, int icon, CharSequence text, int backgroundColor, Drawable drawable, int width, CharSequence title) { + ActionBarMenuItem menuItem = new ActionBarMenuItem(getContext(), this, backgroundColor, isActionMode ? parentActionBar.itemsActionModeColor : parentActionBar.itemsColor, text != null); menuItem.setTag(id); - if (drawable != null) { - menuItem.iconView.setImageDrawable(drawable); - } else if (icon != 0) { - menuItem.iconView.setImageResource(icon); + if (text != null) { + menuItem.textView.setText(text); + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(width != 0 ? width : ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT); + layoutParams.leftMargin = layoutParams.rightMargin = AndroidUtilities.dp(14); + addView(menuItem, layoutParams); + } else { + if (drawable != null) { + menuItem.iconView.setImageDrawable(drawable); + } else if (icon != 0) { + menuItem.iconView.setImageResource(icon); + } + addView(menuItem, new LinearLayout.LayoutParams(width, ViewGroup.LayoutParams.MATCH_PARENT)); } - addView(menuItem, new LinearLayout.LayoutParams(width, ViewGroup.LayoutParams.MATCH_PARENT)); menuItem.setOnClickListener(view -> { ActionBarMenuItem item = (ActionBarMenuItem) view; if (item.hasSubMenu()) { @@ -193,6 +204,21 @@ public class ActionBarMenu extends LinearLayout { } } + public void setSearchFieldText(String text) { + int count = getChildCount(); + for (int a = 0; a < count; a++) { + View view = getChildAt(a); + if (view instanceof ActionBarMenuItem) { + ActionBarMenuItem item = (ActionBarMenuItem) view; + if (item.isSearchField()) { + item.setSearchFieldText(text, false); + item.getSearchField().setSelection(text.length()); + break; + } + } + } + } + public void openSearchField(boolean toggle, String text, boolean animated) { int count = getChildCount(); for (int a = 0; a < count; a++) { 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 b039d2ece..a26df9a13 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java @@ -81,6 +81,7 @@ public class ActionBarMenuItem extends FrameLayout { private TextView searchFieldCaption; private ImageView clearButton; protected ImageView iconView; + protected TextView textView; private FrameLayout searchContainer; private boolean isSearchField; private ActionBarMenuItemSearchListener listener; @@ -103,17 +104,33 @@ public class ActionBarMenuItem extends FrameLayout { private boolean animateClear = true; public ActionBarMenuItem(Context context, ActionBarMenu menu, int backgroundColor, int iconColor) { + this(context, menu, backgroundColor, iconColor, false); + } + + public ActionBarMenuItem(Context context, ActionBarMenu menu, int backgroundColor, int iconColor, boolean text) { super(context); if (backgroundColor != 0) { - setBackgroundDrawable(Theme.createSelectorDrawable(backgroundColor)); + setBackgroundDrawable(Theme.createSelectorDrawable(backgroundColor, text ? 5 : 1)); } parentMenu = menu; - iconView = new ImageView(context); - iconView.setScaleType(ImageView.ScaleType.CENTER); - addView(iconView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - if (iconColor != 0) { - iconView.setColorFilter(new PorterDuffColorFilter(iconColor, PorterDuff.Mode.MULTIPLY)); + if (text) { + textView = new TextView(context); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + textView.setGravity(Gravity.CENTER); + textView.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); + if (iconColor != 0) { + textView.setTextColor(iconColor); + } + addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT)); + } else { + iconView = new ImageView(context); + iconView.setScaleType(ImageView.ScaleType.CENTER); + addView(iconView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + if (iconColor != 0) { + iconView.setColorFilter(new PorterDuffColorFilter(iconColor, PorterDuff.Mode.MULTIPLY)); + } } } @@ -200,7 +217,12 @@ public class ActionBarMenuItem extends FrameLayout { } public void setIconColor(int color) { - iconView.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + if (iconView != null) { + iconView.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + if (textView != null) { + textView.setTextColor(color); + } if (clearButton != null) { clearButton.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); } @@ -468,16 +490,29 @@ public class ActionBarMenuItem extends FrameLayout { } } - public void setIcon(int resId) { - iconView.setImageResource(resId); - } - public void setIcon(Drawable drawable) { + if (iconView == null) { + return; + } iconView.setImageDrawable(drawable); } - public ImageView getImageView() { - return iconView; + public void setIcon(int resId) { + if (iconView == null) { + return; + } + iconView.setImageResource(resId); + } + + public void setText(CharSequence text) { + if (textView == null) { + return; + } + textView.setText(text); + } + + public View getContentView() { + return iconView != null ? iconView : textView; } public void setSearchFieldHint(CharSequence hint) { 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 6e5e4b1f0..4cc3d1b30 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java @@ -13,6 +13,7 @@ import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Build; import android.os.Bundle; import android.text.TextUtils; @@ -24,11 +25,17 @@ import android.view.accessibility.AccessibilityManager; import org.telegram.messenger.AccountInstance; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ContactsController; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.DownloadController; +import org.telegram.messenger.FileLoader; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocationController; import org.telegram.messenger.MessagesController; +import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.NotificationsController; +import org.telegram.messenger.SecretChatHelper; +import org.telegram.messenger.SendMessagesHelper; import org.telegram.messenger.UserConfig; import org.telegram.tgnet.ConnectionsManager; @@ -47,6 +54,7 @@ public class BaseFragment { protected Bundle arguments; protected boolean swipeBackEnabled = true; protected boolean hasOwnBackground = false; + protected boolean isPaused = true; public BaseFragment() { classGuid = ConnectionsManager.generateClassGuid(); @@ -126,6 +134,11 @@ public class BaseFragment { } + public void setParentFragment(BaseFragment fragment) { + setParentLayout(fragment.parentLayout); + fragmentView = createView(parentLayout.getContext()); + } + protected void setParentLayout(ActionBarLayout layout) { if (parentLayout != layout) { parentLayout = layout; @@ -227,13 +240,14 @@ public class BaseFragment { } public void onResume() { - + isPaused = false; } public void onPause() { if (actionBar != null) { actionBar.onPause(); } + isPaused = true; try { if (visibleDialog != null && visibleDialog.isShowing() && dismissDialogOnPause(visibleDialog)) { visibleDialog.dismiss(); @@ -436,7 +450,7 @@ public class BaseFragment { return new ThemeDescription[0]; } - protected AccountInstance getAccountInstance() { + public AccountInstance getAccountInstance() { return AccountInstance.getInstance(currentAccount); } @@ -448,18 +462,46 @@ public class BaseFragment { return getAccountInstance().getContactsController(); } - protected DataQuery getDataQuery() { - return getAccountInstance().getDataQuery(); + protected MediaDataController getMediaDataController() { + return getAccountInstance().getMediaDataController(); } protected ConnectionsManager getConnectionsManager() { return getAccountInstance().getConnectionsManager(); } + protected LocationController getLocationController() { + return getAccountInstance().getLocationController(); + } + protected NotificationsController getNotificationsController() { return getAccountInstance().getNotificationsController(); } + protected MessagesStorage getMessagesStorage() { + return getAccountInstance().getMessagesStorage(); + } + + protected SendMessagesHelper getSendMessagesHelper() { + return getAccountInstance().getSendMessagesHelper(); + } + + protected FileLoader getFileLoader() { + return getAccountInstance().getFileLoader(); + } + + protected SecretChatHelper getSecretChatHelper() { + return getAccountInstance().getSecretChatHelper(); + } + + protected DownloadController getDownloadController() { + return getAccountInstance().getDownloadController(); + } + + protected SharedPreferences getNotificationsSettings() { + return getAccountInstance().getNotificationsSettings(); + } + public NotificationCenter getNotificationCenter() { return getAccountInstance().getNotificationCenter(); } 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 65244130c..27851da48 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java @@ -44,6 +44,7 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.messenger.FileLog; +import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.ui.Components.CubicBezierInterpolator; @@ -218,8 +219,10 @@ public class BottomSheet extends Dialog { if (currentAnimation != null && currentAnimation.equals(animation)) { currentAnimation = null; } + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.startAllHeavyOperations, 512); } }); + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.stopAllHeavyOperations, 512); currentAnimation.start(); } } @@ -474,6 +477,7 @@ public class BottomSheet extends Dialog { public BottomSheetCell(Context context, int type) { super(context); + setBackground(null); setBackgroundDrawable(Theme.getSelectorDrawable(false)); //setPadding(AndroidUtilities.dp(16), 0, AndroidUtilities.dp(16), 0); @@ -813,6 +817,7 @@ public class BottomSheet extends Dialog { getWindow().setAttributes(params); } } + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.startAllHeavyOperations, 512); } @Override @@ -822,6 +827,7 @@ public class BottomSheet extends Dialog { } } }); + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.stopAllHeavyOperations, 512); animatorSet.start(); currentSheetAnimation = animatorSet; } @@ -910,6 +916,7 @@ public class BottomSheet extends Dialog { } }); } + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.startAllHeavyOperations, 512); } @Override @@ -919,6 +926,7 @@ public class BottomSheet extends Dialog { } } }); + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.stopAllHeavyOperations, 512); animatorSet.start(); currentSheetAnimation = animatorSet; } @@ -960,6 +968,7 @@ public class BottomSheet extends Dialog { } }); } + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.startAllHeavyOperations, 512); } @Override @@ -969,6 +978,7 @@ public class BottomSheet extends Dialog { } } }); + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.stopAllHeavyOperations, 512); animatorSet.start(); currentSheetAnimation = animatorSet; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DarkAlertDialog.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DarkAlertDialog.java index 1c9f7db76..f5117d2ec 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DarkAlertDialog.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DarkAlertDialog.java @@ -2,18 +2,14 @@ package org.telegram.ui.ActionBar; import android.content.Context; -/** - * Created by grishka on 27.09.2017. - */ - -public class DarkAlertDialog extends AlertDialog{ - public DarkAlertDialog(Context context, int progressStyle){ +public class DarkAlertDialog extends AlertDialog { + public DarkAlertDialog(Context context, int progressStyle) { super(context, progressStyle); } @Override - protected int getThemeColor(String key){ - switch(key){ + protected int getThemeColor(String key) { + switch (key) { case Theme.key_dialogBackground: return 0xFF262626; case Theme.key_dialogTextBlack: @@ -24,13 +20,13 @@ public class DarkAlertDialog extends AlertDialog{ return super.getThemeColor(key); } - public static class Builder extends AlertDialog.Builder{ + public static class Builder extends AlertDialog.Builder { - public Builder(Context context){ + public Builder(Context context) { super(new DarkAlertDialog(context, 0)); } - public Builder(Context context, int progressViewStyle){ + public Builder(Context context, int progressViewStyle) { super(new DarkAlertDialog(context, progressViewStyle)); } } 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 a64e19463..5ab242c2a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java @@ -45,13 +45,6 @@ import android.text.TextPaint; import android.text.TextUtils; import android.util.StateSet; -import com.airbnb.lottie.LottieCompositionFactory; -import com.airbnb.lottie.LottieDrawable; -import com.airbnb.lottie.LottieProperty; -import com.airbnb.lottie.SimpleColorFilter; -import com.airbnb.lottie.model.KeyPath; -import com.airbnb.lottie.value.LottieValueCallback; - import org.json.JSONArray; import org.json.JSONObject; import org.telegram.messenger.AndroidUtilities; @@ -67,6 +60,7 @@ import org.telegram.messenger.SharedConfig; import org.telegram.messenger.Utilities; import org.telegram.messenger.time.SunDate; import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.RLottieDrawable; import org.telegram.ui.Components.ScamDrawable; import org.telegram.ui.Components.ThemeEditorView; @@ -339,6 +333,7 @@ public class Theme { public static Drawable listSelector; public static Drawable avatar_broadcastDrawable; public static Drawable avatar_savedDrawable; + public static Drawable avatar_ghostDrawable; public static Drawable moveUpDrawable; @@ -376,11 +371,11 @@ public class Theme { public static Drawable dialogs_pinnedDrawable; public static Drawable dialogs_mentionDrawable; public static Drawable dialogs_holidayDrawable; - public static LottieDrawable dialogs_archiveAvatarDrawable; - public static Drawable dialogs_archiveDrawable; - public static Drawable dialogs_unarchiveDrawable; - public static Drawable dialogs_pinArchiveDrawable; - public static Drawable dialogs_unpinArchiveDrawable; + public static RLottieDrawable dialogs_archiveAvatarDrawable; + public static RLottieDrawable dialogs_archiveDrawable; + public static RLottieDrawable dialogs_unarchiveDrawable; + public static RLottieDrawable dialogs_pinArchiveDrawable; + public static RLottieDrawable dialogs_unpinArchiveDrawable; public static boolean dialogs_archiveDrawableRecolored; public static boolean dialogs_archiveAvatarDrawableRecolored; private static int dialogs_holidayDrawableOffsetX; @@ -1102,7 +1097,7 @@ public class Theme { public static final String key_stickers_menuSelector = "stickers_menuSelector"; public static final String key_changephoneinfo_image = "changephoneinfo_image"; - public static final String key_changephoneinfo_changeText = "key_changephoneinfo_changeText"; + public static final String key_changephoneinfo_image2 = "changephoneinfo_image2"; public static final String key_groupcreate_hintText = "groupcreate_hintText"; public static final String key_groupcreate_cursor = "groupcreate_cursor"; @@ -1128,7 +1123,6 @@ public class Theme { public static final String key_picker_badge = "picker_badge"; public static final String key_picker_badgeText = "picker_badgeText"; - public static final String key_location_markerX = "location_markerX"; public static final String key_location_sendLocationBackground = "location_sendLocationBackground"; public static final String key_location_sendLiveLocationBackground = "location_sendLiveLocationBackground"; public static final String key_location_sendLocationIcon = "location_sendLocationIcon"; @@ -1678,7 +1672,7 @@ public class Theme { defaultColors.put(key_chat_messagePanelVoiceDuration, 0xffffffff); defaultColors.put(key_chat_inlineResultIcon, 0xff5795cc); defaultColors.put(key_chat_topPanelBackground, 0xffffffff); - defaultColors.put(key_chat_topPanelClose, 0xffa8a8a8); + defaultColors.put(key_chat_topPanelClose, 0xff8c959a); defaultColors.put(key_chat_topPanelLine, 0xff6c9fd2); defaultColors.put(key_chat_topPanelTitle, 0xff3a8ccf); defaultColors.put(key_chat_topPanelMessage, 0xff999999); @@ -1741,7 +1735,6 @@ public class Theme { defaultColors.put(key_passport_authorizeBackgroundSelected, 0xff409ddb); defaultColors.put(key_passport_authorizeText, 0xffffffff); - defaultColors.put(key_location_markerX, 0xff808080); defaultColors.put(key_location_sendLocationBackground, 0xff6da0d4); defaultColors.put(key_location_sendLiveLocationBackground, 0xffff6464); defaultColors.put(key_location_sendLocationIcon, 0xffffffff); @@ -1784,8 +1777,8 @@ public class Theme { defaultColors.put(key_stickers_menu, 0xffb6bdc5); defaultColors.put(key_stickers_menuSelector, 0x0f000000); - defaultColors.put(key_changephoneinfo_image, 0xffa8a8a8); - defaultColors.put(key_changephoneinfo_changeText, 0xff4d83b3); + defaultColors.put(key_changephoneinfo_image, 0xffb8bfc5); + defaultColors.put(key_changephoneinfo_image2, 0xff50a7ea); defaultColors.put(key_groupcreate_hintText, 0xffa1aab3); defaultColors.put(key_groupcreate_cursor, 0xff52a3db); @@ -1823,7 +1816,7 @@ public class Theme { fallbackKeys.put(key_chat_outAudioCacheSeekbar, key_chat_outAudioSeekbar); fallbackKeys.put(key_chat_emojiSearchBackground, key_chat_emojiPanelStickerPackSelector); fallbackKeys.put(key_location_sendLiveLocationIcon, key_location_sendLocationIcon); - fallbackKeys.put(key_changephoneinfo_changeText, key_windowBackgroundWhiteBlueText4); + fallbackKeys.put(key_changephoneinfo_image2, key_featuredStickers_addButton); fallbackKeys.put(key_graySectionText, key_windowBackgroundWhiteGrayText2); fallbackKeys.put(key_chat_inMediaIcon, key_chat_inBubble); fallbackKeys.put(key_chat_outMediaIcon, key_chat_outBubble); @@ -2421,9 +2414,9 @@ public class Theme { Drawable drawable; if (Build.VERSION.SDK_INT >= 21) { Drawable maskDrawable = null; - if (maskType == 1 && Build.VERSION.SDK_INT >= 23) { + if ((maskType == 1 || maskType == 5) && Build.VERSION.SDK_INT >= 23) { maskDrawable = null; - } else if (maskType == 1 || maskType == 3 || maskType == 4) { + } else if (maskType == 1 || maskType == 3 || maskType == 4 || maskType == 5) { maskPaint.setColor(0xffffffff); maskDrawable = new Drawable() { @Override @@ -2463,8 +2456,12 @@ public class Theme { new int[]{color} ); RippleDrawable rippleDrawable = new RippleDrawable(colorStateList, null, maskDrawable); - if (maskType == 1 && Build.VERSION.SDK_INT >= 23) { - rippleDrawable.setRadius(AndroidUtilities.dp(20)); + if (Build.VERSION.SDK_INT >= 23) { + if (maskType == 1) { + rippleDrawable.setRadius(AndroidUtilities.dp(20)); + } else if (maskType == 5) { + rippleDrawable.setRadius(RippleDrawable.RADIUS_AUTO); + } } return rippleDrawable; } else { @@ -3011,32 +3008,30 @@ public class Theme { avatar_broadcastDrawable = resources.getDrawable(R.drawable.broadcast_w); avatar_savedDrawable = resources.getDrawable(R.drawable.chats_saved); + avatar_ghostDrawable = resources.getDrawable(R.drawable.ghost); - dialogs_archiveAvatarDrawable = new LottieDrawable(); - dialogs_archiveAvatarDrawable.setComposition(LottieCompositionFactory.fromRawResSync(context, R.raw.chats_archiveavatar).getValue()); - if (Build.VERSION.SDK_INT == 24) { - dialogs_archiveDrawable = resources.getDrawable(R.drawable.chats_archive); - dialogs_unarchiveDrawable = resources.getDrawable(R.drawable.chats_unarchive); - dialogs_pinArchiveDrawable = resources.getDrawable(R.drawable.chats_archive_hide); - dialogs_unpinArchiveDrawable = resources.getDrawable(R.drawable.chats_archive_show); - } else { - LottieDrawable lottie_dialogs_archiveDrawable = new LottieDrawable(); - lottie_dialogs_archiveDrawable.setComposition(LottieCompositionFactory.fromRawResSync(context, R.raw.chats_archive).getValue()); - dialogs_archiveDrawable = lottie_dialogs_archiveDrawable; - - LottieDrawable lottie_dialogs_unarchiveDrawable = new LottieDrawable(); - lottie_dialogs_unarchiveDrawable.setComposition(LottieCompositionFactory.fromRawResSync(context, R.raw.chats_unarchive).getValue()); - dialogs_unarchiveDrawable = lottie_dialogs_unarchiveDrawable; - - LottieDrawable lottie_dialogs_pinArchiveDrawable = new LottieDrawable(); - lottie_dialogs_pinArchiveDrawable.setComposition(LottieCompositionFactory.fromRawResSync(context, R.raw.chats_hide).getValue()); - dialogs_pinArchiveDrawable = lottie_dialogs_pinArchiveDrawable; - - LottieDrawable lottie_dialogs_unpinArchiveDrawable = new LottieDrawable(); - lottie_dialogs_unpinArchiveDrawable.setComposition(LottieCompositionFactory.fromRawResSync(context, R.raw.chats_unhide).getValue()); - dialogs_unpinArchiveDrawable = lottie_dialogs_unpinArchiveDrawable; + if (dialogs_archiveAvatarDrawable != null) { + dialogs_archiveAvatarDrawable.setCallback(null); + dialogs_archiveAvatarDrawable.recycle(); } - + if (dialogs_archiveDrawable != null) { + dialogs_archiveDrawable.recycle(); + } + if (dialogs_unarchiveDrawable != null) { + dialogs_unarchiveDrawable.recycle(); + } + if (dialogs_pinArchiveDrawable != null) { + dialogs_pinArchiveDrawable.recycle(); + } + if (dialogs_unpinArchiveDrawable != null) { + dialogs_unpinArchiveDrawable.recycle(); + } + dialogs_archiveAvatarDrawable = new RLottieDrawable(R.raw.chats_archiveavatar, "chats_archiveavatar", AndroidUtilities.dp(36), AndroidUtilities.dp(36), false); + dialogs_archiveDrawable = new RLottieDrawable(R.raw.chats_archive, "chats_archive", AndroidUtilities.dp(36), AndroidUtilities.dp(36)); + dialogs_unarchiveDrawable = new RLottieDrawable(R.raw.chats_unarchive, "chats_unarchive", AndroidUtilities.dp(AndroidUtilities.dp(36)), AndroidUtilities.dp(36)); + dialogs_pinArchiveDrawable = new RLottieDrawable(R.raw.chats_hide, "chats_hide", AndroidUtilities.dp(36), AndroidUtilities.dp(36)); + dialogs_unpinArchiveDrawable = new RLottieDrawable(R.raw.chats_unhide, "chats_unhide", AndroidUtilities.dp(36), AndroidUtilities.dp(36)); + applyCommonTheme(); } } @@ -3051,57 +3046,28 @@ public class Theme { setDrawableColorByKey(avatar_broadcastDrawable, key_avatar_text); setDrawableColorByKey(avatar_savedDrawable, key_avatar_text); - dialogs_archiveAvatarDrawable.addValueCallback(new KeyPath("**"), LottieProperty.COLOR_FILTER, (LottieValueCallback) null); - dialogs_archiveAvatarDrawable.addValueCallback(new KeyPath("Arrow1", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(getColor(key_avatar_backgroundArchived)))); - dialogs_archiveAvatarDrawable.addValueCallback(new KeyPath("Arrow2", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(getColor(key_avatar_backgroundArchived)))); - dialogs_archiveAvatarDrawable.addValueCallback(new KeyPath("Box2", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(getColor(key_avatar_text)))); - dialogs_archiveAvatarDrawable.addValueCallback(new KeyPath("Box1", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(getColor(key_avatar_text)))); + dialogs_archiveAvatarDrawable.setLayerColor("Arrow1.**", getColor(key_avatar_backgroundArchived)); + dialogs_archiveAvatarDrawable.setLayerColor("Arrow2.**", getColor(key_avatar_backgroundArchived)); + dialogs_archiveAvatarDrawable.setLayerColor("Box2.**", getColor(key_avatar_text)); + dialogs_archiveAvatarDrawable.setLayerColor("Box1.**", getColor(key_avatar_text)); dialogs_archiveAvatarDrawableRecolored = false; + dialogs_archiveAvatarDrawable.setAllowDecodeSingleFrame(true); + + dialogs_pinArchiveDrawable.setLayerColor("Arrow.**", getColor(key_chats_archiveIcon)); + dialogs_pinArchiveDrawable.setLayerColor("Line.**", getColor(key_chats_archiveIcon)); + + dialogs_unpinArchiveDrawable.setLayerColor("Arrow.**", getColor(key_chats_archiveIcon)); + dialogs_unpinArchiveDrawable.setLayerColor("Line.**", getColor(key_chats_archiveIcon)); - /* - fallbackKeys.put(key_chats_archiveIcon, key_chats_actionIcon); - fallbackKeys.put(key_chats_archiveText, key_chats_actionIcon); - */ + dialogs_archiveDrawable.setLayerColor("Arrow.**", getColor(key_chats_archiveBackground)); + dialogs_archiveDrawable.setLayerColor("Box2.**", getColor(key_chats_archiveIcon)); + dialogs_archiveDrawable.setLayerColor("Box1.**", getColor(key_chats_archiveIcon)); + dialogs_archiveDrawableRecolored = false; - if (dialogs_pinArchiveDrawable instanceof LottieDrawable) { - LottieDrawable lottieDrawable = (LottieDrawable) dialogs_pinArchiveDrawable; - lottieDrawable.addValueCallback(new KeyPath("**"), LottieProperty.COLOR_FILTER, (LottieValueCallback) null); - lottieDrawable.addValueCallback(new KeyPath("Arrow", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(getColor(key_chats_archiveIcon)))); - lottieDrawable.addValueCallback(new KeyPath("Line", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(getColor(key_chats_archiveIcon)))); - } else { - setDrawableColorByKey(dialogs_pinArchiveDrawable, key_chats_archiveIcon); - } - - if (dialogs_unpinArchiveDrawable instanceof LottieDrawable) { - LottieDrawable lottieDrawable = (LottieDrawable) dialogs_unpinArchiveDrawable; - lottieDrawable.addValueCallback(new KeyPath("**"), LottieProperty.COLOR_FILTER, (LottieValueCallback) null); - lottieDrawable.addValueCallback(new KeyPath("Arrow", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(getColor(key_chats_archiveIcon)))); - lottieDrawable.addValueCallback(new KeyPath("Line", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(getColor(key_chats_archiveIcon)))); - } else { - setDrawableColorByKey(dialogs_unpinArchiveDrawable, key_chats_archiveIcon); - } - - if (dialogs_archiveDrawable instanceof LottieDrawable) { - LottieDrawable lottieDrawable = (LottieDrawable) dialogs_archiveDrawable; - lottieDrawable.addValueCallback(new KeyPath("**"), LottieProperty.COLOR_FILTER, (LottieValueCallback) null); - lottieDrawable.addValueCallback(new KeyPath("Arrow", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(getColor(key_chats_archiveBackground)))); - lottieDrawable.addValueCallback(new KeyPath("Box2", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(getColor(key_chats_archiveIcon)))); - lottieDrawable.addValueCallback(new KeyPath("Box1", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(getColor(key_chats_archiveIcon)))); - dialogs_archiveDrawableRecolored = false; - } else { - setDrawableColorByKey(dialogs_archiveDrawable, key_chats_archiveIcon); - } - - if (dialogs_unarchiveDrawable instanceof LottieDrawable) { - LottieDrawable lottieDrawable = (LottieDrawable) dialogs_unarchiveDrawable; - lottieDrawable.addValueCallback(new KeyPath("**"), LottieProperty.COLOR_FILTER, (LottieValueCallback) null); - lottieDrawable.addValueCallback(new KeyPath("Arrow1", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(getColor(key_chats_archiveIcon)))); - lottieDrawable.addValueCallback(new KeyPath("Arrow2", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(getColor(key_chats_archivePinBackground)))); - lottieDrawable.addValueCallback(new KeyPath("Box2", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(getColor(key_chats_archiveIcon)))); - lottieDrawable.addValueCallback(new KeyPath("Box1", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(getColor(key_chats_archiveIcon)))); - } else { - setDrawableColorByKey(dialogs_unarchiveDrawable, key_chats_archiveIcon); - } + dialogs_unarchiveDrawable.setLayerColor("Arrow1.**", getColor(key_chats_archiveIcon)); + dialogs_unarchiveDrawable.setLayerColor("Arrow2.**", getColor(key_chats_archivePinBackground)); + dialogs_unarchiveDrawable.setLayerColor("Box2.**", getColor(key_chats_archiveIcon)); + dialogs_unarchiveDrawable.setLayerColor("Box1.**", getColor(key_chats_archiveIcon)); } public static void createDialogsResources(Context context) { @@ -3857,7 +3823,7 @@ public class Theme { public static int getDefaultColor(String key) { Integer value = defaultColors.get(key); if (value == null) { - if (key.equals(key_chats_menuTopShadow)) { + if (key.equals(key_chats_menuTopShadow) || key.equals(key_chats_menuTopBackground)) { return 0; } return 0xffff0000; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ThemeDescription.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ThemeDescription.java index 5f3751214..c63b03eda 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ThemeDescription.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ThemeDescription.java @@ -27,13 +27,6 @@ import android.widget.ImageView; import android.widget.ScrollView; import android.widget.TextView; -import com.airbnb.lottie.LottieAnimationView; -import com.airbnb.lottie.LottieDrawable; -import com.airbnb.lottie.LottieProperty; -import com.airbnb.lottie.SimpleColorFilter; -import com.airbnb.lottie.model.KeyPath; -import com.airbnb.lottie.value.LottieValueCallback; - import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; import org.telegram.ui.Components.AvatarDrawable; @@ -52,6 +45,8 @@ import org.telegram.ui.Components.LetterDrawable; import org.telegram.ui.Components.LineProgressView; import org.telegram.ui.Components.MessageBackgroundDrawable; import org.telegram.ui.Components.NumberTextView; +import org.telegram.ui.Components.RLottieDrawable; +import org.telegram.ui.Components.RLottieImageView; import org.telegram.ui.Components.RadialProgressView; import org.telegram.ui.Components.RadioButton; import org.telegram.ui.Components.RecyclerListView; @@ -149,7 +144,7 @@ public class ThemeDescription { } } - public ThemeDescription(View view, int flags, Class[] classes, LottieDrawable[] drawables, String layerName, String key) { + public ThemeDescription(View view, int flags, Class[] classes, RLottieDrawable[] drawables, String layerName, String key) { currentKey = key; lottieLayerName = layerName; drawablesToUpdate = drawables; @@ -232,9 +227,9 @@ public class ThemeDescription { } if (drawablesToUpdate[a] instanceof ScamDrawable) { ((ScamDrawable) drawablesToUpdate[a]).setColor(color); - } else if (drawablesToUpdate[a] instanceof LottieDrawable) { + } else if (drawablesToUpdate[a] instanceof RLottieDrawable) { if (lottieLayerName != null) { - ((LottieDrawable) drawablesToUpdate[a]).addValueCallback(new KeyPath(lottieLayerName, "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(color))); + ((RLottieDrawable) drawablesToUpdate[a]).setLayerColor(lottieLayerName + ".**", color); } } else if (drawablesToUpdate[a] instanceof CombinedDrawable) { if ((changeFlags & FLAG_BACKGROUNDFILTER) != 0) { @@ -548,8 +543,8 @@ public class ThemeDescription { if (object instanceof View) { ((View) object).invalidate(); } - if (lottieLayerName != null && object instanceof LottieAnimationView) { - ((LottieAnimationView) object).addValueCallback(new KeyPath(lottieLayerName, "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(color))); + if (lottieLayerName != null && object instanceof RLottieImageView) { + ((RLottieImageView) object).setLayerColor(lottieLayerName + ".**", color); } if ((changeFlags & FLAG_USEBACKGROUNDDRAWABLE) != 0 && object instanceof View) { object = ((View) object).getBackground(); @@ -665,6 +660,8 @@ public class ThemeDescription { } else { ((LineProgressView) object).setBackColor(color); } + } else if (object instanceof RadialProgressView) { + ((RadialProgressView) object).setProgressColor(color); } else if (object instanceof Paint) { ((Paint) object).setColor(color); } else if (object instanceof SeekBarView) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionIntroActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionIntroActivity.java new file mode 100644 index 000000000..6227e82cd --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionIntroActivity.java @@ -0,0 +1,596 @@ +/* + * 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; + +import android.Manifest; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.location.Location; +import android.location.LocationManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.telegram.PhoneFormat.PhoneFormat; +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.ChatObject; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.LocationController; +import org.telegram.messenger.R; +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.ActionBar.ThemeDescription; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.ShareLocationDrawable; + +import java.util.ArrayList; + +@TargetApi(23) +public class ActionIntroActivity extends BaseFragment implements LocationController.LocationFetchCallback { + + private ImageView imageView; + private TextView buttonTextView; + private TextView subtitleTextView; + private TextView titleTextView; + private TextView descriptionText; + private TextView descriptionText2; + private Drawable drawable1; + private Drawable drawable2; + + private int currentType; + + private String currentGroupCreateAddress; + private String currentGroupCreateDisplayAddress; + private Location currentGroupCreateLocation; + + public static final int ACTION_TYPE_CHANNEL_CREATE = 0; + public static final int ACTION_TYPE_NEARBY_LOCATION_ACCESS = 1; + public static final int ACTION_TYPE_NEARBY_GROUP_CREATE = 2; + public static final int ACTION_TYPE_CHANGE_PHONE_NUMBER = 3; + public static final int ACTION_TYPE_NEARBY_LOCATION_ENABLED = 4; + + public ActionIntroActivity(int type) { + super(); + currentType = type; + } + + @Override + public View createView(Context context) { + actionBar.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setItemsColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2), false); + actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_actionBarWhiteSelector), false); + actionBar.setCastShadows(false); + actionBar.setAddToContainer(false); + if (!AndroidUtilities.isTablet()) { + actionBar.showActionModeTop(); + } + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } + } + }); + + fragmentView = new ViewGroup(context) { + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + actionBar.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), heightMeasureSpec); + + switch (currentType) { + case ACTION_TYPE_CHANNEL_CREATE: { + if (width > height) { + imageView.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.45f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) (height * 0.68f), MeasureSpec.EXACTLY)); + 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)); + } else { + imageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) (height * 0.399f), MeasureSpec.EXACTLY)); + titleTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + descriptionText.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + buttonTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(42), MeasureSpec.EXACTLY)); + } + break; + } + case ACTION_TYPE_NEARBY_LOCATION_ACCESS: + case ACTION_TYPE_NEARBY_LOCATION_ENABLED: { + if (width > height) { + imageView.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(100), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(100), MeasureSpec.EXACTLY)); + 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)); + } else { + imageView.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(100), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(100), MeasureSpec.EXACTLY)); + titleTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + descriptionText.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + buttonTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(42), MeasureSpec.EXACTLY)); + } + break; + } + case ACTION_TYPE_NEARBY_GROUP_CREATE: { + if (width > height) { + imageView.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.45f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) (height * 0.78f), MeasureSpec.AT_MOST)); + subtitleTextView.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.45f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + 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)); + descriptionText2.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)); + } else { + imageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) (height * 0.44f), MeasureSpec.AT_MOST)); + titleTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + subtitleTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + descriptionText.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + descriptionText2.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + buttonTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(42), MeasureSpec.EXACTLY)); + } + break; + } + case ACTION_TYPE_CHANGE_PHONE_NUMBER: { + if (width > height) { + imageView.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.45f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) (height * 0.78f), MeasureSpec.AT_MOST)); + subtitleTextView.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.45f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + 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)); + } else { + imageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) (height * 0.44f), MeasureSpec.AT_MOST)); + titleTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + subtitleTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + descriptionText.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + buttonTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(42), MeasureSpec.EXACTLY)); + } + break; + } + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + actionBar.layout(0, 0, r, actionBar.getMeasuredHeight()); + + int width = r - l; + int height = b - t; + + switch (currentType) { + case ACTION_TYPE_CHANNEL_CREATE: { + if (r > b) { + int y = (height - imageView.getMeasuredHeight()) / 2; + imageView.layout(0, y, imageView.getMeasuredWidth(), y + imageView.getMeasuredHeight()); + int x = (int) (width * 0.4f); + y = (int) (height * 0.22f); + titleTextView.layout(x, y, x + titleTextView.getMeasuredWidth(), y + titleTextView.getMeasuredHeight()); + x = (int) (width * 0.4f); + y = (int) (height * 0.39f); + descriptionText.layout(x, y, x + descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight()); + x = (int) (width * 0.4f + (width * 0.6f - buttonTextView.getMeasuredWidth()) / 2); + y = (int) (height * 0.69f); + buttonTextView.layout(x, y, x + buttonTextView.getMeasuredWidth(), y + buttonTextView.getMeasuredHeight()); + } else { + int y = (int) (height * 0.188f); + imageView.layout(0, y, imageView.getMeasuredWidth(), y + imageView.getMeasuredHeight()); + y = (int) (height * 0.651f); + titleTextView.layout(0, y, titleTextView.getMeasuredWidth(), y + titleTextView.getMeasuredHeight()); + y = (int) (height * 0.731f); + descriptionText.layout(0, y, descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight()); + int x = (width - buttonTextView.getMeasuredWidth()) / 2; + y = (int) (height * 0.853f); + buttonTextView.layout(x, y, x + buttonTextView.getMeasuredWidth(), y + buttonTextView.getMeasuredHeight()); + } + break; + } + case ACTION_TYPE_NEARBY_LOCATION_ACCESS: + case ACTION_TYPE_NEARBY_LOCATION_ENABLED: { + if (r > b) { + int y = (height - imageView.getMeasuredHeight()) / 2; + int x = (int) (width * 0.5f - imageView.getMeasuredWidth()) / 2; + imageView.layout(x, y, x + imageView.getMeasuredWidth(), y + imageView.getMeasuredHeight()); + x = (int) (width * 0.4f); + y = (int) (height * 0.14f); + titleTextView.layout(x, y, x + titleTextView.getMeasuredWidth(), y + titleTextView.getMeasuredHeight()); + x = (int) (width * 0.4f); + y = (int) (height * 0.31f); + descriptionText.layout(x, y, x + descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight()); + x = (int) (width * 0.4f + (width * 0.6f - buttonTextView.getMeasuredWidth()) / 2); + y = (int) (height * 0.78f); + buttonTextView.layout(x, y, x + buttonTextView.getMeasuredWidth(), y + buttonTextView.getMeasuredHeight()); + } else { + int y = (int) (height * 0.214f); + int x = (width - imageView.getMeasuredWidth()) / 2; + imageView.layout(x, y, x + imageView.getMeasuredWidth(), y + imageView.getMeasuredHeight()); + y = (int) (height * 0.414f); + titleTextView.layout(0, y, titleTextView.getMeasuredWidth(), y + titleTextView.getMeasuredHeight()); + y = (int) (height * 0.493f); + descriptionText.layout(0, y, descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight()); + x = (width - buttonTextView.getMeasuredWidth()) / 2; + y = (int) (height * 0.71f); + buttonTextView.layout(x, y, x + buttonTextView.getMeasuredWidth(), y + buttonTextView.getMeasuredHeight()); + } + break; + } + case ACTION_TYPE_NEARBY_GROUP_CREATE: { + if (r > b) { + int y = (int) (height * 0.9f - imageView.getMeasuredHeight()) / 2; + imageView.layout(0, y, imageView.getMeasuredWidth(), y + imageView.getMeasuredHeight()); + y += imageView.getMeasuredHeight() + AndroidUtilities.dp(10); + subtitleTextView.layout(0, y, subtitleTextView.getMeasuredWidth(), y + subtitleTextView.getMeasuredHeight()); + int x = (int) (width * 0.4f); + y = (int) (height * 0.12f); + titleTextView.layout(x, y, x + titleTextView.getMeasuredWidth(), y + titleTextView.getMeasuredHeight()); + x = (int) (width * 0.4f); + y = (int) (height * 0.26f); + descriptionText.layout(x, y, x + descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight()); + x = (int) (width * 0.4f + (width * 0.6f - buttonTextView.getMeasuredWidth()) / 2); + y = (int) (height * 0.6f); + buttonTextView.layout(x, y, x + buttonTextView.getMeasuredWidth(), y + buttonTextView.getMeasuredHeight()); + x = (int) (width * 0.4f); + y = getMeasuredHeight() - descriptionText2.getMeasuredHeight() - AndroidUtilities.dp(20); + descriptionText2.layout(x, y, x + descriptionText2.getMeasuredWidth(), y + descriptionText2.getMeasuredHeight()); + } else { + int y = (int) (height * 0.197f); + imageView.layout(0, y, imageView.getMeasuredWidth(), y + imageView.getMeasuredHeight()); + y = (int) (height * 0.421f); + titleTextView.layout(0, y, titleTextView.getMeasuredWidth(), y + titleTextView.getMeasuredHeight()); + y = (int) (height * 0.477f); + subtitleTextView.layout(0, y, subtitleTextView.getMeasuredWidth(), y + subtitleTextView.getMeasuredHeight()); + y = (int) (height * 0.537f); + descriptionText.layout(0, y, descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight()); + int x = (width - buttonTextView.getMeasuredWidth()) / 2; + y = (int) (height * 0.71f); + buttonTextView.layout(x, y, x + buttonTextView.getMeasuredWidth(), y + buttonTextView.getMeasuredHeight()); + y = getMeasuredHeight() - descriptionText2.getMeasuredHeight() - AndroidUtilities.dp(20); + descriptionText2.layout(0, y, descriptionText2.getMeasuredWidth(), y + descriptionText2.getMeasuredHeight()); + } + break; + } + case ACTION_TYPE_CHANGE_PHONE_NUMBER: { + if (r > b) { + int y = (int) (height * 0.95f - imageView.getMeasuredHeight()) / 2; + imageView.layout(0, y, imageView.getMeasuredWidth(), y + imageView.getMeasuredHeight()); + y += imageView.getMeasuredHeight() + AndroidUtilities.dp(10); + subtitleTextView.layout(0, y, subtitleTextView.getMeasuredWidth(), y + subtitleTextView.getMeasuredHeight()); + int x = (int) (width * 0.4f); + y = (int) (height * 0.12f); + titleTextView.layout(x, y, x + titleTextView.getMeasuredWidth(), y + titleTextView.getMeasuredHeight()); + x = (int) (width * 0.4f); + y = (int) (height * 0.24f); + descriptionText.layout(x, y, x + descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight()); + x = (int) (width * 0.4f + (width * 0.6f - buttonTextView.getMeasuredWidth()) / 2); + y = (int) (height * 0.8f); + buttonTextView.layout(x, y, x + buttonTextView.getMeasuredWidth(), y + buttonTextView.getMeasuredHeight()); + } else { + int y = (int) (height * 0.2229f); + imageView.layout(0, y, imageView.getMeasuredWidth(), y + imageView.getMeasuredHeight()); + y = (int) (height * 0.352f); + titleTextView.layout(0, y, titleTextView.getMeasuredWidth(), y + titleTextView.getMeasuredHeight()); + y = (int) (height * 0.409f); + subtitleTextView.layout(0, y, subtitleTextView.getMeasuredWidth(), y + subtitleTextView.getMeasuredHeight()); + y = (int) (height * 0.468f); + descriptionText.layout(0, y, descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight()); + int x = (width - buttonTextView.getMeasuredWidth()) / 2; + y = (int) (height * 0.805f); + buttonTextView.layout(x, y, x + buttonTextView.getMeasuredWidth(), y + buttonTextView.getMeasuredHeight()); + } + break; + } + } + } + }; + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + ViewGroup viewGroup = (ViewGroup) fragmentView; + viewGroup.setOnTouchListener((v, event) -> true); + + viewGroup.addView(actionBar); + + imageView = new ImageView(context); + viewGroup.addView(imageView); + + titleTextView = new TextView(context); + titleTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + titleTextView.setGravity(Gravity.CENTER_HORIZONTAL); + titleTextView.setPadding(AndroidUtilities.dp(32), 0, AndroidUtilities.dp(32), 0); + titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 24); + viewGroup.addView(titleTextView); + + subtitleTextView = new TextView(context); + subtitleTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + subtitleTextView.setGravity(Gravity.CENTER_HORIZONTAL); + subtitleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + subtitleTextView.setSingleLine(true); + subtitleTextView.setEllipsize(TextUtils.TruncateAt.END); + if (currentType == ACTION_TYPE_NEARBY_GROUP_CREATE) { + subtitleTextView.setPadding(AndroidUtilities.dp(24), 0, AndroidUtilities.dp(24), 0); + } else { + subtitleTextView.setPadding(AndroidUtilities.dp(32), 0, AndroidUtilities.dp(32), 0); + } + subtitleTextView.setVisibility(View.GONE); + viewGroup.addView(subtitleTextView); + + descriptionText = new TextView(context); + descriptionText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); + descriptionText.setGravity(Gravity.CENTER_HORIZONTAL); + descriptionText.setLineSpacing(AndroidUtilities.dp(2), 1); + descriptionText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + if (currentType == ACTION_TYPE_NEARBY_GROUP_CREATE) { + descriptionText.setPadding(AndroidUtilities.dp(24), 0, AndroidUtilities.dp(24), 0); + } else { + descriptionText.setPadding(AndroidUtilities.dp(32), 0, AndroidUtilities.dp(32), 0); + } + viewGroup.addView(descriptionText); + + descriptionText2 = new TextView(context); + descriptionText2.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); + descriptionText2.setGravity(Gravity.CENTER_HORIZONTAL); + descriptionText2.setLineSpacing(AndroidUtilities.dp(2), 1); + descriptionText2.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + descriptionText2.setVisibility(View.GONE); + if (currentType == ACTION_TYPE_NEARBY_GROUP_CREATE) { + descriptionText2.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); + } else { + descriptionText2.setPadding(AndroidUtilities.dp(32), 0, AndroidUtilities.dp(32), 0); + } + viewGroup.addView(descriptionText2); + + buttonTextView = new TextView(context); + buttonTextView.setPadding(AndroidUtilities.dp(34), 0, AndroidUtilities.dp(34), 0); + buttonTextView.setGravity(Gravity.CENTER); + buttonTextView.setTextColor(Theme.getColor(Theme.key_featuredStickers_buttonText)); + buttonTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + buttonTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + buttonTextView.setBackgroundDrawable(Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(4), Theme.getColor(Theme.key_featuredStickers_addButton), Theme.getColor(Theme.key_featuredStickers_addButtonPressed))); + viewGroup.addView(buttonTextView); + buttonTextView.setOnClickListener(v -> { + if (getParentActivity() == null) { + return; + } + switch (currentType) { + case ACTION_TYPE_CHANNEL_CREATE: { + Bundle args = new Bundle(); + args.putInt("step", 0); + presentFragment(new ChannelCreateActivity(args), true); + break; + } + case ACTION_TYPE_NEARBY_LOCATION_ACCESS: { + getParentActivity().requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, 2); + break; + } + case ACTION_TYPE_NEARBY_LOCATION_ENABLED: { + try { + getParentActivity().startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); + } catch (Exception e) { + FileLog.e(e); + } + break; + } + case ACTION_TYPE_NEARBY_GROUP_CREATE: { + if (currentGroupCreateAddress == null || currentGroupCreateLocation == null) { + return; + } + Bundle args = new Bundle(); + ArrayList result = new ArrayList<>(); + result.add(getUserConfig().getClientUserId()); + args.putIntegerArrayList("result", result); + args.putInt("chatType", ChatObject.CHAT_TYPE_MEGAGROUP); + args.putString("address", currentGroupCreateAddress); + args.putParcelable("location", currentGroupCreateLocation); + presentFragment(new GroupCreateFinalActivity(args), true); + break; + } + case ACTION_TYPE_CHANGE_PHONE_NUMBER: { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("PhoneNumberChangeTitle", R.string.PhoneNumberChangeTitle)); + builder.setMessage(LocaleController.getString("PhoneNumberAlert", R.string.PhoneNumberAlert)); + builder.setPositiveButton(LocaleController.getString("Change", R.string.Change), (dialogInterface, i) -> presentFragment(new ChangePhoneActivity(), true)); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + break; + } + } + }); + + switch (currentType) { + case ACTION_TYPE_CHANNEL_CREATE: { + imageView.setImageResource(R.drawable.channelintro); + imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); + titleTextView.setText(LocaleController.getString("ChannelAlertTitle", R.string.ChannelAlertTitle)); + descriptionText.setText(LocaleController.getString("ChannelAlertText", R.string.ChannelAlertText)); + buttonTextView.setText(LocaleController.getString("ChannelAlertCreate2", R.string.ChannelAlertCreate2)); + break; + } + case ACTION_TYPE_NEARBY_LOCATION_ACCESS: { + imageView.setBackgroundDrawable(Theme.createCircleDrawable(AndroidUtilities.dp(100), Theme.getColor(Theme.key_chats_archiveBackground))); + imageView.setImageDrawable(new ShareLocationDrawable(context, 3)); + imageView.setScaleType(ImageView.ScaleType.CENTER); + titleTextView.setText(LocaleController.getString("PeopleNearby", R.string.PeopleNearby)); + descriptionText.setText(LocaleController.getString("PeopleNearbyAccessInfo", R.string.PeopleNearbyAccessInfo)); + buttonTextView.setText(LocaleController.getString("PeopleNearbyAllowAccess", R.string.PeopleNearbyAllowAccess)); + break; + } + case ACTION_TYPE_NEARBY_LOCATION_ENABLED: { + imageView.setBackgroundDrawable(Theme.createCircleDrawable(AndroidUtilities.dp(100), Theme.getColor(Theme.key_chats_archiveBackground))); + imageView.setImageDrawable(new ShareLocationDrawable(context, 3)); + imageView.setScaleType(ImageView.ScaleType.CENTER); + titleTextView.setText(LocaleController.getString("PeopleNearby", R.string.PeopleNearby)); + descriptionText.setText(LocaleController.getString("PeopleNearbyGpsInfo", R.string.PeopleNearbyGpsInfo)); + buttonTextView.setText(LocaleController.getString("PeopleNearbyGps", R.string.PeopleNearbyGps)); + break; + } + case ACTION_TYPE_NEARBY_GROUP_CREATE: { + subtitleTextView.setVisibility(View.VISIBLE); + descriptionText2.setVisibility(View.VISIBLE); + imageView.setImageResource(Theme.getCurrentTheme().isDark() ? R.drawable.groupsintro2 : R.drawable.groupsintro); + imageView.setScaleType(ImageView.ScaleType.CENTER); + subtitleTextView.setText(currentGroupCreateDisplayAddress != null ? currentGroupCreateDisplayAddress : ""); + titleTextView.setText(LocaleController.getString("NearbyCreateGroup", R.string.NearbyCreateGroup)); + descriptionText.setText(LocaleController.getString("NearbyCreateGroupInfo", R.string.NearbyCreateGroupInfo)); + descriptionText2.setText(LocaleController.getString("NearbyCreateGroupInfo2", R.string.NearbyCreateGroupInfo2)); + buttonTextView.setText(LocaleController.getString("NearbyStartGroup", R.string.NearbyStartGroup)); + break; + } + case ACTION_TYPE_CHANGE_PHONE_NUMBER: { + subtitleTextView.setVisibility(View.VISIBLE); + drawable1 = context.getResources().getDrawable(R.drawable.sim_old); + drawable2 = context.getResources().getDrawable(R.drawable.sim_new); + drawable1.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_changephoneinfo_image), PorterDuff.Mode.MULTIPLY)); + drawable2.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_changephoneinfo_image2), PorterDuff.Mode.MULTIPLY)); + imageView.setImageDrawable(new CombinedDrawable(drawable1, drawable2)); + imageView.setScaleType(ImageView.ScaleType.CENTER); + subtitleTextView.setText(PhoneFormat.getInstance().format("+" + getUserConfig().getCurrentUser().phone)); + titleTextView.setText(LocaleController.getString("PhoneNumberChange2", R.string.PhoneNumberChange2)); + descriptionText.setText(AndroidUtilities.replaceTags(LocaleController.getString("PhoneNumberHelp", R.string.PhoneNumberHelp))); + buttonTextView.setText(LocaleController.getString("PhoneNumberChange2", R.string.PhoneNumberChange2)); + break; + } + } + + return fragmentView; + } + + @Override + public void onLocationAddressAvailable(String address, String displayAddress, Location location) { + if (subtitleTextView == null) { + return; + } + subtitleTextView.setText(address); + currentGroupCreateAddress = address; + currentGroupCreateDisplayAddress = displayAddress; + currentGroupCreateLocation = location; + } + + @Override + public void onResume() { + super.onResume(); + if (currentType == ACTION_TYPE_NEARBY_LOCATION_ENABLED) { + boolean enabled = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + LocationManager lm = (LocationManager) ApplicationLoader.applicationContext.getSystemService(Context.LOCATION_SERVICE); + enabled = lm.isLocationEnabled(); + } else if (Build.VERSION.SDK_INT >= 19) { + try { + int mode = Settings.Secure.getInt(ApplicationLoader.applicationContext.getContentResolver(), Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); + enabled = (mode != Settings.Secure.LOCATION_MODE_OFF); + } catch (Throwable e) { + FileLog.e(e); + } + } + if (enabled) { + presentFragment(new PeopleNearbyActivity(), true); + } + } + } + + private void showPermissionAlert(boolean byButton) { + if (getParentActivity() == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.getString("PermissionNoLocationPosition", R.string.PermissionNoLocationPosition)); + builder.setNegativeButton(LocaleController.getString("PermissionOpenSettings", R.string.PermissionOpenSettings), (dialog, which) -> { + if (getParentActivity() == null) { + return; + } + try { + Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.parse("package:" + ApplicationLoader.applicationContext.getPackageName())); + getParentActivity().startActivity(intent); + } catch (Exception e) { + FileLog.e(e); + } + }); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + showDialog(builder.create()); + } + + public void setGroupCreateAddress(String address, String displayAddress, Location location) { + currentGroupCreateAddress = address; + currentGroupCreateDisplayAddress = displayAddress; + currentGroupCreateLocation = location; + if (location != null && address == null) { + LocationController.fetchLocationAddress(location, this); + } + } + + @Override + public void onRequestPermissionsResultFragment(int requestCode, String[] permissions, int[] grantResults) { + if (requestCode == 2) { + if (grantResults != null && grantResults.length != 0) { + if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { + if (getParentActivity() == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.getString("PermissionNoLocationPosition", R.string.PermissionNoLocationPosition)); + builder.setNegativeButton(LocaleController.getString("PermissionOpenSettings", R.string.PermissionOpenSettings), (dialog, which) -> { + if (getParentActivity() == null) { + return; + } + try { + Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.parse("package:" + ApplicationLoader.applicationContext.getPackageName())); + getParentActivity().startActivity(intent); + } catch (Exception e) { + FileLog.e(e); + } + }); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + showDialog(builder.create()); + } else { + presentFragment(new PeopleNearbyActivity(), true); + } + } + } + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarWhiteSelector), + + new ThemeDescription(titleTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(subtitleTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(descriptionText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(buttonTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_featuredStickers_buttonText), + new ThemeDescription(buttonTextView, ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, null, null, null, null, Theme.key_featuredStickers_addButton), + new ThemeDescription(buttonTextView, ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_featuredStickers_addButtonPressed), + + new ThemeDescription(null, ThemeDescription.FLAG_TEXTCOLOR, null, null, new Drawable[]{drawable1}, null, Theme.key_changephoneinfo_image), + new ThemeDescription(null, ThemeDescription.FLAG_TEXTCOLOR, null, null, new Drawable[]{drawable2}, null, Theme.key_changephoneinfo_image2), + }; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsAdapter.java index 669a4d696..9456f7b45 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsAdapter.java @@ -48,23 +48,29 @@ public class ContactsAdapter extends RecyclerListView.SectionsAdapter { private boolean isAdmin; private int sortType; private boolean isChannel; + private boolean disableSections; + private boolean hasGps; - public ContactsAdapter(Context context, int onlyUsersType, boolean arg2, SparseArray arg3, int arg4) { + public ContactsAdapter(Context context, int onlyUsersType, boolean arg2, SparseArray arg3, int arg4, boolean gps) { mContext = context; onlyUsers = onlyUsersType; needPhonebook = arg2; ignoreUsers = arg3; isAdmin = arg4 != 0; isChannel = arg4 == 2; + hasGps = gps; + } + + public void setDisableSections(boolean value) { + disableSections = value; } public void setSortType(int value) { sortType = value; if (sortType == 2) { if (onlineContacts == null) { - onlineContacts = new ArrayList<>(); + onlineContacts = new ArrayList<>(ContactsController.getInstance(currentAccount).contacts); int selfId = UserConfig.getInstance(currentAccount).clientUserId; - onlineContacts.addAll(ContactsController.getInstance(currentAccount).contacts); for (int a = 0, N = onlineContacts.size(); a < N; a++) { if (onlineContacts.get(a).user_id == selfId) { onlineContacts.remove(a); @@ -189,8 +195,10 @@ public class ContactsAdapter extends RecyclerListView.SectionsAdapter { return row < arr.size(); } else { if (section == 0) { - if (needPhonebook || isAdmin) { + if (isAdmin) { return row != 1; + } else if (needPhonebook) { + return hasGps && row != 2 || !hasGps && row != 1; } else { return row != 3; } @@ -247,8 +255,10 @@ public class ContactsAdapter extends RecyclerListView.SectionsAdapter { } } else { if (section == 0) { - if (needPhonebook || isAdmin) { + if (isAdmin) { return 2; + } else if (needPhonebook) { + return hasGps ? 3 : 2; } else { return 4; } @@ -284,7 +294,7 @@ public class ContactsAdapter extends RecyclerListView.SectionsAdapter { view = new LetterSectionCell(mContext); } LetterSectionCell cell = (LetterSectionCell) view; - if (sortType == 2) { + if (sortType == 2 || disableSections) { cell.setLetter(""); } else { if (onlyUsers != 0 && !isAdmin) { @@ -333,7 +343,7 @@ public class ContactsAdapter extends RecyclerListView.SectionsAdapter { switch (holder.getItemViewType()) { case 0: UserCell userCell = (UserCell) holder.itemView; - userCell.setAvatarPadding(sortType == 2 ? 6 : 58); + userCell.setAvatarPadding(sortType == 2 || disableSections ? 6 : 58); ArrayList arr; if (sortType == 2) { arr = onlineContacts; @@ -359,7 +369,11 @@ public class ContactsAdapter extends RecyclerListView.SectionsAdapter { TextCell textCell = (TextCell) holder.itemView; if (section == 0) { if (needPhonebook) { - textCell.setTextAndIcon(LocaleController.getString("InviteFriends", R.string.InviteFriends), R.drawable.menu_invite, false); + if (position == 0) { + textCell.setTextAndIcon(LocaleController.getString("InviteFriends", R.string.InviteFriends), R.drawable.menu_invite, false); + } else if (position == 1) { + textCell.setTextAndIcon(LocaleController.getString("AddPeopleNearby", R.string.AddPeopleNearby), R.drawable.menu_location, false); + } } else if (isAdmin) { if (isChannel) { textCell.setTextAndIcon(LocaleController.getString("ChannelInviteViaLink", R.string.ChannelInviteViaLink), R.drawable.profile_link, false); @@ -408,7 +422,15 @@ public class ContactsAdapter extends RecyclerListView.SectionsAdapter { return position < arr.size() ? 0 : 3; } else { if (section == 0) { - if ((needPhonebook || isAdmin) && position == 1 || position == 3) { + if (isAdmin) { + if (position == 1) { + return 2; + } + } else if (needPhonebook) { + if (hasGps && position == 2 || !hasGps && position == 1) { + return 2; + } + } else if (position == 3) { return 2; } } else { @@ -437,8 +459,14 @@ public class ContactsAdapter extends RecyclerListView.SectionsAdapter { if (section == -1) { section = sortedUsersSectionsArray.size() - 1; } - if (section > 0 && section <= sortedUsersSectionsArray.size()) { - return sortedUsersSectionsArray.get(section - 1); + if (onlyUsers != 0 && !isAdmin) { + if (section >= 0 && section < sortedUsersSectionsArray.size()) { + return sortedUsersSectionsArray.get(section); + } + } else { + if (section > 0 && section <= sortedUsersSectionsArray.size()) { + return sortedUsersSectionsArray.get(section - 1); + } } return null; } 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 1ac91bf03..6e5bed245 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java @@ -13,6 +13,7 @@ import android.content.SharedPreferences; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; +import android.os.SystemClock; import android.util.TypedValue; import android.view.Gravity; import android.view.View; @@ -23,11 +24,13 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ContactsController; import org.telegram.messenger.DialogObject; +import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; import org.telegram.messenger.SharedConfig; import org.telegram.messenger.UserConfig; +import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; @@ -55,6 +58,8 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; private ArchiveHintCell archiveHintCell; + private ArrayList onlineContacts; + private int prevContactsCount; private int dialogsType; private int folderId; private long openedDialogId; @@ -63,10 +68,10 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter { private ArrayList selectedDialogs; private boolean hasHints; private int currentAccount = UserConfig.selectedAccount; - private boolean showContacts; private boolean dialogsListFrozen; private boolean showArchiveHint; private boolean isReordering; + private long lastSortTime; public DialogsAdapter(Context context, int type, int folder, boolean onlySelect) { mContext = context; @@ -134,10 +139,10 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter { @Override public int getItemCount() { - showContacts = false; ArrayList array = DialogsActivity.getDialogsArray(currentAccount, dialogsType, folderId, dialogsListFrozen); int dialogsCount = array.size(); if (dialogsCount == 0 && (folderId != 0 || MessagesController.getInstance(currentAccount).isLoadingDialogs(folderId))) { + onlineContacts = null; if (folderId == 1 && showArchiveHint) { return (currentCount = 2); } @@ -147,17 +152,35 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter { if (!MessagesController.getInstance(currentAccount).isDialogsEndReached(folderId) || dialogsCount == 0) { count++; } + boolean hasContacts = false; if (hasHints) { count += 2 + MessagesController.getInstance(currentAccount).hintDialogs.size(); } else if (dialogsType == 0 && dialogsCount == 0 && folderId == 0) { if (ContactsController.getInstance(currentAccount).contacts.isEmpty() && ContactsController.getInstance(currentAccount).isLoadingContacts()) { + onlineContacts = null; return (currentCount = 0); } + if (!ContactsController.getInstance(currentAccount).contacts.isEmpty()) { - count += ContactsController.getInstance(currentAccount).contacts.size() + 2; - showContacts = true; + if (onlineContacts == null || prevContactsCount != ContactsController.getInstance(currentAccount).contacts.size()) { + onlineContacts = new ArrayList<>(ContactsController.getInstance(currentAccount).contacts); + prevContactsCount = onlineContacts.size(); + int selfId = UserConfig.getInstance(currentAccount).clientUserId; + for (int a = 0, N = onlineContacts.size(); a < N; a++) { + if (onlineContacts.get(a).user_id == selfId) { + onlineContacts.remove(a); + break; + } + } + sortOnlineContacts(false); + } + count += onlineContacts.size() + 2; + hasContacts = true; } } + if (!hasContacts && onlineContacts != null) { + onlineContacts = null; + } if (folderId == 1 && showArchiveHint) { count += 2; } @@ -169,12 +192,12 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter { } public TLObject getItem(int i) { - if (showContacts) { + if (onlineContacts != null) { i -= 3; - if (i < 0 || i >= ContactsController.getInstance(currentAccount).contacts.size()) { + if (i < 0 || i >= onlineContacts.size()) { return null; } - return MessagesController.getInstance(currentAccount).getUser(ContactsController.getInstance(currentAccount).contacts.get(i).user_id); + return MessagesController.getInstance(currentAccount).getUser(onlineContacts.get(i).user_id); } if (showArchiveHint) { i -= 2; @@ -194,6 +217,62 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter { return arrayList.get(i); } + public void sortOnlineContacts(boolean notify) { + if (onlineContacts == null || notify && (SystemClock.uptimeMillis() - lastSortTime) < 2000) { + return; + } + lastSortTime = SystemClock.uptimeMillis(); + try { + int currentTime = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + MessagesController messagesController = MessagesController.getInstance(currentAccount); + Collections.sort(onlineContacts, (o1, o2) -> { + TLRPC.User user1 = messagesController.getUser(o2.user_id); + TLRPC.User user2 = messagesController.getUser(o1.user_id); + int status1 = 0; + int status2 = 0; + if (user1 != null) { + if (user1.self) { + status1 = currentTime + 50000; + } else if (user1.status != null) { + status1 = user1.status.expires; + } + } + if (user2 != null) { + if (user2.self) { + status2 = currentTime + 50000; + } else if (user2.status != null) { + status2 = user2.status.expires; + } + } + if (status1 > 0 && status2 > 0) { + if (status1 > status2) { + return 1; + } else if (status1 < status2) { + return -1; + } + return 0; + } else if (status1 < 0 && status2 < 0) { + if (status1 > status2) { + return 1; + } else if (status1 < status2) { + return -1; + } + return 0; + } else if (status1 < 0 && status2 > 0 || status1 == 0 && status2 != 0) { + return -1; + } else if (status2 < 0 && status1 > 0 || status2 == 0 && status1 != 0) { + return 1; + } + return 0; + }); + if (notify) { + notifyDataSetChanged(); + } + } catch (Exception e) { + FileLog.e(e); + } + } + public void setDialogsListFrozen(boolean frozen) { dialogsListFrozen = frozen; } @@ -362,7 +441,7 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter { } case 5: { DialogsEmptyCell cell = (DialogsEmptyCell) holder.itemView; - cell.setType(showContacts ? 1 : 0); + cell.setType(onlineContacts != null ? 1 : 0); break; } case 4: { @@ -372,7 +451,7 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter { } case 6: { UserCell cell = (UserCell) holder.itemView; - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(ContactsController.getInstance(currentAccount).contacts.get(i - 3).user_id); + TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(onlineContacts.get(i - 3).user_id); cell.setData(user, null, null, 0); break; } @@ -381,7 +460,7 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter { @Override public int getItemViewType(int i) { - if (showContacts) { + if (onlineContacts != null) { if (i == 0) { return 5; } else if (i == 1) { 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 3546ae83a..81517db04 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java @@ -18,12 +18,13 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.SQLite.SQLiteCursor; import org.telegram.SQLite.SQLitePreparedStatement; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; @@ -44,6 +45,7 @@ import org.telegram.ui.Cells.HashtagSearchCell; import org.telegram.ui.Cells.HintDialogCell; import org.telegram.ui.Cells.LoadingCell; import org.telegram.ui.Cells.ProfileSearchCell; +import org.telegram.ui.Cells.TextCell; import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; @@ -78,7 +80,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { private SearchAdapterHelper searchAdapterHelper; private RecyclerListView innerListView; private int selfUserId; - + private int currentAccount = UserConfig.selectedAccount; private ArrayList recentSearchObjects = new ArrayList<>(); @@ -98,8 +100,11 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { public interface DialogsSearchAdapterDelegate { void searchStateChanged(boolean searching); + void didPressedOnSubDialog(long did); + void needRemoveHint(int did); + void needClearList(); } @@ -125,7 +130,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { HintDialogCell cell = (HintDialogCell) holder.itemView; - TLRPC.TL_topPeer peer = DataQuery.getInstance(currentAccount).hints.get(position); + TLRPC.TL_topPeer peer = MediaDataController.getInstance(currentAccount).hints.get(position); TLRPC.Dialog dialog = new TLRPC.TL_dialog(); TLRPC.Chat chat = null; TLRPC.User user = null; @@ -152,7 +157,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { @Override public int getItemCount() { - return DataQuery.getInstance(currentAccount).hints.size(); + return MediaDataController.getInstance(currentAccount).hints.size(); } } @@ -184,7 +189,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { dialogsType = type; selfUserId = UserConfig.getInstance(currentAccount).getClientUserId(); loadRecentSearch(); - DataQuery.getInstance(currentAccount).loadHints(true); + MediaDataController.getInstance(currentAccount).loadHints(true); } public RecyclerListView getInnerListView() { @@ -289,11 +294,11 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { } public boolean hasRecentRearch() { - return !recentSearchObjects.isEmpty() || !DataQuery.getInstance(currentAccount).hints.isEmpty(); + return dialogsType != 4 && dialogsType != 5 && dialogsType != 6 && (!recentSearchObjects.isEmpty() || !MediaDataController.getInstance(currentAccount).hints.isEmpty()); } public boolean isRecentSearchDisplayed() { - return needMessagesSearch != 2 && !searchWas && (!recentSearchObjects.isEmpty() || !DataQuery.getInstance(currentAccount).hints.isEmpty()); + return needMessagesSearch != 2 && !searchWas && (!recentSearchObjects.isEmpty() || !MediaDataController.getInstance(currentAccount).hints.isEmpty()) && dialogsType != 4 && dialogsType != 5 && dialogsType != 6; } public void loadRecentSearch() { @@ -511,15 +516,24 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { int high_id = (int) (id >> 32); if (lower_id != 0) { if (high_id == 1) { + if (dialogsType == 4) { + continue; + } if (dialogsType == 0 && !chatsToLoad.contains(lower_id)) { chatsToLoad.add(lower_id); } } else { if (lower_id > 0) { + if (dialogsType == 4 && lower_id == selfUserId) { + continue; + } if (dialogsType != 2 && !usersToLoad.contains(lower_id)) { usersToLoad.add(lower_id); } } else { + if (dialogsType == 4) { + continue; + } if (!chatsToLoad.contains(-lower_id)) { chatsToLoad.add(-lower_id); } @@ -814,6 +828,10 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { }); } + public boolean isHashtagSearch() { + return !searchResultHashtags.isEmpty(); + } + public void clearRecentHashtags() { searchAdapterHelper.clearRecentHashtags(); searchResultHashtags.clear(); @@ -846,7 +864,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { searchResultHashtags.clear(); searchAdapterHelper.mergeResults(null); if (needMessagesSearch != 2) { - searchAdapterHelper.queryServerSearch(null, true, true, true, true, 0, 0); + searchAdapterHelper.queryServerSearch(null, true, true, true, true, 0, dialogsType == 0, 0); } searchWas = false; lastSearchId = -1; @@ -885,7 +903,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { return; } if (needMessagesSearch != 2) { - searchAdapterHelper.queryServerSearch(query, true, true, true, true, 0, 0); + searchAdapterHelper.queryServerSearch(query, true, dialogsType != 4, true, dialogsType != 4, 0, dialogsType == 0, 0); } searchMessagesInternal(query); }); @@ -896,7 +914,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { @Override public int getItemCount() { if (isRecentSearchDisplayed()) { - return (!recentSearchObjects.isEmpty() ? recentSearchObjects.size() + 1 : 0) + (!DataQuery.getInstance(currentAccount).hints.isEmpty() ? 2 : 0); + return (!recentSearchObjects.isEmpty() ? recentSearchObjects.size() + 1 : 0) + (!MediaDataController.getInstance(currentAccount).hints.isEmpty() ? 2 : 0); } if (!searchResultHashtags.isEmpty()) { return searchResultHashtags.size() + 1; @@ -904,11 +922,15 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { int count = searchResult.size(); int localServerCount = searchAdapterHelper.getLocalServerSearch().size(); int globalCount = searchAdapterHelper.getGlobalSearch().size(); + int phoneCount = searchAdapterHelper.getPhoneSearch().size(); int messagesCount = searchResultMessages.size(); count += localServerCount; if (globalCount != 0) { count += globalCount + 1; } + if (phoneCount != 0) { + count += phoneCount; + } if (messagesCount != 0) { count += messagesCount + 1 + (messagesSearchEndReached ? 0 : 1); } @@ -917,7 +939,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { public Object getItem(int i) { if (isRecentSearchDisplayed()) { - int offset = (!DataQuery.getInstance(currentAccount).hints.isEmpty() ? 2 : 0); + int offset = (!MediaDataController.getInstance(currentAccount).hints.isEmpty() ? 2 : 0); if (i > offset && i - 1 - offset < recentSearchObjects.size()) { TLObject object = recentSearchObjects.get(i - 1 - offset).object; if (object instanceof TLRPC.User) { @@ -945,18 +967,34 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { } ArrayList globalSearch = searchAdapterHelper.getGlobalSearch(); ArrayList localServerSearch = searchAdapterHelper.getLocalServerSearch(); + ArrayList phoneSearch = searchAdapterHelper.getPhoneSearch(); int localCount = searchResult.size(); int localServerCount = localServerSearch.size(); + int phoneCount = phoneSearch.size(); int globalCount = globalSearch.isEmpty() ? 0 : globalSearch.size() + 1; int messagesCount = searchResultMessages.isEmpty() ? 0 : searchResultMessages.size() + 1; if (i >= 0 && i < localCount) { return searchResult.get(i); - } else if (i >= localCount && i < localServerCount + localCount) { - return localServerSearch.get(i - localCount); - } else if (i > localCount + localServerCount && i < globalCount + localCount + localServerCount) { - return globalSearch.get(i - localCount - localServerCount - 1); - } else if (i > globalCount + localCount + localServerCount && i < globalCount + localCount + messagesCount + localServerCount) { - return searchResultMessages.get(i - localCount - globalCount - localServerCount - 1); + } else { + i -= localCount; + if (i >= 0 && i < localServerCount) { + return localServerSearch.get(i); + } else { + i -= localServerCount; + if (i >= 0 && i < phoneCount) { + return phoneSearch.get(i); + } else { + i -= phoneCount; + if (i > 0 && i < globalCount) { + return globalSearch.get(i - 1); + } else { + i -= globalCount; + if (i > 0 && i < messagesCount) { + return searchResultMessages.get(i - 1); + } + } + } + } } return null; } @@ -972,16 +1010,32 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { ArrayList localServerSearch = searchAdapterHelper.getLocalServerSearch(); int localCount = searchResult.size(); int localServerCount = localServerSearch.size(); + int phoneCount = searchAdapterHelper.getPhoneSearch().size(); int globalCount = globalSearch.isEmpty() ? 0 : globalSearch.size() + 1; int messagesCount = searchResultMessages.isEmpty() ? 0 : searchResultMessages.size() + 1; + if (i >= 0 && i < localCount) { return false; - } else if (i >= localCount && i < localServerCount + localCount) { - return false; - } else if (i > localCount + localServerCount && i < globalCount + localCount + localServerCount) { - return true; - } else if (i > globalCount + localCount + localServerCount && i < globalCount + localCount + messagesCount + localServerCount) { - return false; + } else { + i -= localCount; + if (i >= 0 && i < localServerCount) { + return false; + } else { + i -= localServerCount; + if (i > 0 && i < phoneCount) { + return false; + } else { + i -= phoneCount; + if (i > 0 && i < globalCount) { + return true; + } else { + i -= globalCount; + if (i > 0 && i < messagesCount) { + return false; + } + } + } + } } return false; } @@ -1053,6 +1107,9 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { view = horizontalListView; innerListView = horizontalListView; break; + case 6: + view = new TextCell(mContext, 16); + break; } if (viewType == 5) { view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, AndroidUtilities.dp(86))); @@ -1096,10 +1153,16 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { cell.useSeparator = position != getItemCount() - 1; } else { ArrayList globalSearch = searchAdapterHelper.getGlobalSearch(); + ArrayList phoneSearch = searchAdapterHelper.getPhoneSearch(); int localCount = searchResult.size(); int localServerCount = searchAdapterHelper.getLocalServerSearch().size(); + int phoneCount = phoneSearch.size(); + int phoneCount2 = phoneCount; + if (phoneCount > 0 && phoneSearch.get(phoneCount - 1) instanceof String) { + phoneCount2 -= 2; + } int globalCount = globalSearch.isEmpty() ? 0 : globalSearch.size() + 1; - cell.useSeparator = (position != getItemCount() - 1 && position != localCount + localServerCount - 1 && position != localCount + globalCount + localServerCount - 1); + cell.useSeparator = (position != getItemCount() - 1 && position != localCount + phoneCount2 + localServerCount - 1 && position != localCount + globalCount + phoneCount + localServerCount - 1); if (position < searchResult.size()) { name = searchResultNames.get(position); @@ -1117,12 +1180,10 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { int index; if (user != null) { nameSearch = ContactsController.formatName(user.first_name, user.last_name); - nameSearchLower = nameSearch.toLowerCase(); } else if (chat != null) { nameSearch = chat.title; - nameSearchLower = nameSearch.toLowerCase(); } - if (nameSearch != null && (index = nameSearchLower.indexOf(foundUserName)) != -1) { + if (nameSearch != null && (index = AndroidUtilities.indexOfIgnoreCase(nameSearch, foundUserName)) != -1) { SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(nameSearch); spannableStringBuilder.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)), index, index + foundUserName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); name = spannableStringBuilder; @@ -1134,7 +1195,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(); spannableStringBuilder.append("@"); spannableStringBuilder.append(un); - if ((index = un.toLowerCase().indexOf(foundUserName)) != -1) { + if ((index = AndroidUtilities.indexOfIgnoreCase(un, foundUserName)) != -1) { int len = foundUserName.length(); if (index == 0) { len++; @@ -1179,7 +1240,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { case 1: { GraySectionCell cell = (GraySectionCell) holder.itemView; if (isRecentSearchDisplayed()) { - int offset = (!DataQuery.getInstance(currentAccount).hints.isEmpty() ? 2 : 0); + int offset = (!MediaDataController.getInstance(currentAccount).hints.isEmpty() ? 2 : 0); if (position < offset) { cell.setText(LocaleController.getString("ChatHints", R.string.ChatHints)); } else { @@ -1195,23 +1256,35 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { delegate.needClearList(); } }); - } else if (!searchAdapterHelper.getGlobalSearch().isEmpty() && position == searchResult.size() + searchAdapterHelper.getLocalServerSearch().size()) { - cell.setText(LocaleController.getString("GlobalSearch", R.string.GlobalSearch)); } else { - cell.setText(LocaleController.getString("SearchMessages", R.string.SearchMessages)); + ArrayList globalSearch = searchAdapterHelper.getGlobalSearch(); + int localCount = searchResult.size(); + int localServerCount = searchAdapterHelper.getLocalServerSearch().size(); + int phoneCount = searchAdapterHelper.getPhoneSearch().size(); + int globalCount = globalSearch.isEmpty() ? 0 : globalSearch.size() + 1; + int messagesCount = searchResultMessages.isEmpty() ? 0 : searchResultMessages.size() + 1; + + position -= localCount + localServerCount; + if (position >= 0 && position < phoneCount) { + cell.setText(LocaleController.getString("PhoneNumberSearch", R.string.PhoneNumberSearch)); + } else { + position -= phoneCount; + if (position >= 0 && position < globalCount) { + cell.setText(LocaleController.getString("GlobalSearch", R.string.GlobalSearch)); + } else { + cell.setText(LocaleController.getString("SearchMessages", R.string.SearchMessages)); + } + } } break; } case 2: { DialogCell cell = (DialogCell) holder.itemView; cell.useSeparator = (position != getItemCount() - 1); - MessageObject messageObject = (MessageObject)getItem(position); + MessageObject messageObject = (MessageObject) getItem(position); cell.setDialog(messageObject.getDialogId(), messageObject, messageObject.messageOwner.date); break; } - case 3: { - break; - } case 4: { HashtagSearchCell cell = (HashtagSearchCell) holder.itemView; cell.setText(searchResultHashtags.get(position - 1)); @@ -1223,13 +1296,20 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { ((CategoryAdapterRecycler) recyclerListView.getAdapter()).setIndex(position / 2); break; } + case 6: { + String str = (String) getItem(position); + TextCell cell = (TextCell) holder.itemView; + cell.setColors(null, Theme.key_windowBackgroundWhiteBlueText2); + cell.setText(LocaleController.formatString("AddContactByPhone", R.string.AddContactByPhone, PhoneFormat.getInstance().format("+" + str)), false); + break; + } } } @Override public int getItemViewType(int i) { if (isRecentSearchDisplayed()) { - int offset = (!DataQuery.getInstance(currentAccount).hints.isEmpty() ? 2 : 0); + int offset = (!MediaDataController.getInstance(currentAccount).hints.isEmpty() ? 2 : 0); if (i <= offset) { if (i == offset || i % 2 == 0) { return 1; @@ -1245,15 +1325,50 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { ArrayList globalSearch = searchAdapterHelper.getGlobalSearch(); int localCount = searchResult.size(); int localServerCount = searchAdapterHelper.getLocalServerSearch().size(); + int phoneCount = searchAdapterHelper.getPhoneSearch().size(); int globalCount = globalSearch.isEmpty() ? 0 : globalSearch.size() + 1; int messagesCount = searchResultMessages.isEmpty() ? 0 : searchResultMessages.size() + 1; - if (i >= 0 && i < localCount + localServerCount || i > localCount + localServerCount && i < globalCount + localCount + localServerCount) { + + if (i >= 0 && i < localCount) { return 0; - } else if (i > globalCount + localCount + localServerCount && i < globalCount + localCount + messagesCount + localServerCount) { - return 2; - } else if (messagesCount != 0 && i == globalCount + localCount + messagesCount + localServerCount) { - return 3; + } else { + i -= localCount; + if (i >= 0 && i < localServerCount) { + return 0; + } else { + i -= localServerCount; + if (i >= 0 && i < phoneCount) { + Object object = getItem(i); + if (object instanceof String) { + String str = (String) object; + if ("section".equals(str)) { + return 1; + } else { + return 6; + } + } + return 0; + } else { + i -= phoneCount; + if (i >= 0 && i < globalCount) { + if (i == 0) { + return 1; + } else { + return 0; + } + } else { + i -= globalCount; + if (i >= 0 && i < messagesCount) { + if (i == 0) { + return 1; + } else { + return 2; + } + } + } + } + } } - return 1; + return 3; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivityAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivityAdapter.java index 9bd1adb30..00756410d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivityAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivityAdapter.java @@ -19,6 +19,7 @@ import org.telegram.messenger.LocationController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; +import org.telegram.tgnet.TLRPC; import org.telegram.ui.Cells.EmptyCell; import org.telegram.ui.Cells.GraySectionCell; import org.telegram.ui.Cells.LocationCell; @@ -34,7 +35,7 @@ import java.util.Locale; import androidx.recyclerview.widget.RecyclerView; -public class LocationActivityAdapter extends BaseLocationAdapter { +public class LocationActivityAdapter extends BaseLocationAdapter implements LocationController.LocationFetchCallback { private int currentAccount = UserConfig.selectedAccount; private Context mContext; @@ -42,17 +43,21 @@ public class LocationActivityAdapter extends BaseLocationAdapter { private SendLocationCell sendLocationCell; private Location gpsLocation; private Location customLocation; - private int liveLocationType; + private String addressName; + private Location previousFetchedLocation; + private int locationType; private long dialogId; private boolean pulledUp; private int shareLiveLocationPotistion = -1; private MessageObject currentMessageObject; + private TLRPC.TL_channelLocation chatLocation; private ArrayList currentLiveLocations = new ArrayList<>(); + private boolean fetchingLocation; - public LocationActivityAdapter(Context context, int live, long did) { + public LocationActivityAdapter(Context context, int type, long did) { super(); mContext = context; - liveLocationType = live; + locationType = type; dialogId = did; } @@ -63,13 +68,16 @@ public class LocationActivityAdapter extends BaseLocationAdapter { public void setGpsLocation(Location location) { boolean notSet = gpsLocation == null; gpsLocation = location; + if (customLocation == null) { + fetchLocationAddress(); + } if (notSet && shareLiveLocationPotistion > 0) { notifyItemChanged(shareLiveLocationPotistion); } if (currentMessageObject != null) { notifyItemChanged(1); updateLiveLocations(); - } else if (liveLocationType != 2) { + } else if (locationType != 2) { updateCell(); } else { updateLiveLocations(); @@ -84,6 +92,7 @@ public class LocationActivityAdapter extends BaseLocationAdapter { public void setCustomLocation(Location location) { customLocation = location; + fetchLocationAddress(); updateCell(); } @@ -104,9 +113,27 @@ public class LocationActivityAdapter extends BaseLocationAdapter { notifyDataSetChanged(); } + public void setChatLocation(TLRPC.TL_channelLocation location) { + chatLocation = location; + } + private void updateCell() { if (sendLocationCell != null) { - if (customLocation != null) { + if (locationType == LocationActivity.LOCATION_TYPE_GROUP) { + String address; + if (addressName != null) { + address = addressName; + } else if (customLocation == null && gpsLocation == null || fetchingLocation) { + address = LocaleController.getString("Loading", R.string.Loading); + } else if (customLocation != null) { + address = String.format(Locale.US, "(%f,%f)", customLocation.getLatitude(), customLocation.getLongitude()); + } else if (gpsLocation != null) { + address = String.format(Locale.US, "(%f,%f)", gpsLocation.getLatitude(), gpsLocation.getLongitude()); + } else { + address = LocaleController.getString("Loading", R.string.Loading); + } + sendLocationCell.setText(LocaleController.getString("ChatSetThisLocation", R.string.ChatSetThisLocation), address); + } else if (customLocation != null) { sendLocationCell.setText(LocaleController.getString("SendSelectedLocation", R.string.SendSelectedLocation), String.format(Locale.US, "(%f,%f)", customLocation.getLatitude(), customLocation.getLongitude())); } else { if (gpsLocation != null) { @@ -118,17 +145,53 @@ public class LocationActivityAdapter extends BaseLocationAdapter { } } + private String getAddressName() { + return addressName; + } + + @Override + public void onLocationAddressAvailable(String address, String displayAddress, Location location) { + fetchingLocation = false; + previousFetchedLocation = location; + addressName = address; + updateCell(); + } + + public void fetchLocationAddress() { + if (locationType != LocationActivity.LOCATION_TYPE_GROUP) { + return; + } + Location location; + if (customLocation != null) { + location = customLocation; + } else if (gpsLocation != null) { + location = gpsLocation; + } else { + return; + } + if (previousFetchedLocation == null || previousFetchedLocation.distanceTo(location) > 100) { + addressName = null; + } + updateCell(); + fetchingLocation = true; + LocationController.fetchLocationAddress(location, this); + } + @Override public int getItemCount() { - if (currentMessageObject != null) { + if (locationType == LocationActivity.LOCATION_TYPE_GROUP_VIEW) { + return 2; + } else if (locationType == LocationActivity.LOCATION_TYPE_GROUP) { + return 2; + } else if (currentMessageObject != null) { return 2 + (currentLiveLocations.isEmpty() ? 0 : currentLiveLocations.size() + 2); - } else if (liveLocationType == 2) { + } else if (locationType == 2) { return 2 + currentLiveLocations.size(); } else { if (searching || !searching && places.isEmpty()) { - return liveLocationType != 0 ? 5 : 4; + return locationType != 0 ? 5 : 4; } - if (liveLocationType == 1) { + if (locationType == 1) { return 4 + places.size() + (places.isEmpty() ? 0 : 1); } else { return 3 + places.size() + (places.isEmpty() ? 0 : 1); @@ -165,7 +228,7 @@ public class LocationActivityAdapter extends BaseLocationAdapter { break; case 7: default: - view = new SharingLiveLocationCell(mContext, true); + view = new SharingLiveLocationCell(mContext, true, locationType == LocationActivity.LOCATION_TYPE_GROUP || locationType == LocationActivity.LOCATION_TYPE_GROUP_VIEW ? 16 : 54); break; } return new RecyclerListView.Holder(view); @@ -176,7 +239,7 @@ public class LocationActivityAdapter extends BaseLocationAdapter { return; } pulledUp = true; - AndroidUtilities.runOnUIThread(() -> notifyItemChanged(liveLocationType == 0 ? 2 : 3)); + AndroidUtilities.runOnUIThread(() -> notifyItemChanged(locationType == 0 ? 2 : 3)); } public boolean isPulledUp() { @@ -203,7 +266,7 @@ public class LocationActivityAdapter extends BaseLocationAdapter { } break; case 3: - if (liveLocationType == 0) { + if (locationType == 0) { ((LocationCell) holder.itemView).setLocation(places.get(position - 3), iconUrls.get(position - 3), true); } else { ((LocationCell) holder.itemView).setLocation(places.get(position - 4), iconUrls.get(position - 4), true); @@ -216,28 +279,47 @@ public class LocationActivityAdapter extends BaseLocationAdapter { ((SendLocationCell) holder.itemView).setHasLocation(gpsLocation != null); break; case 7: - if (currentMessageObject != null && position == 1) { - ((SharingLiveLocationCell) holder.itemView).setDialog(currentMessageObject, gpsLocation); + SharingLiveLocationCell locationCell = (SharingLiveLocationCell) holder.itemView; + if (chatLocation != null) { + locationCell.setDialog(dialogId, chatLocation); + } else if (currentMessageObject != null && position == 1) { + locationCell.setDialog(currentMessageObject, gpsLocation); } else { - ((SharingLiveLocationCell) holder.itemView).setDialog(currentLiveLocations.get(position - (currentMessageObject != null ? 4 : 2)), gpsLocation); + locationCell.setDialog(currentLiveLocations.get(position - (currentMessageObject != null ? 4 : 2)), gpsLocation); } break; } } public Object getItem(int i) { - if (currentMessageObject != null) { + if (locationType == LocationActivity.LOCATION_TYPE_GROUP) { + if (addressName == null) { + return null; + } else { + TLRPC.TL_messageMediaVenue venue = new TLRPC.TL_messageMediaVenue(); + venue.address = addressName; + venue.geo = new TLRPC.TL_geoPoint(); + if (customLocation != null) { + venue.geo.lat = customLocation.getLatitude(); + venue.geo._long = customLocation.getLongitude(); + } else if (gpsLocation != null) { + venue.geo.lat = gpsLocation.getLatitude(); + venue.geo._long = gpsLocation.getLongitude(); + } + return venue; + } + } else if (currentMessageObject != null) { if (i == 1) { return currentMessageObject; } else if (i > 3 && i < places.size() + 3) { return currentLiveLocations.get(i - 4); } - } else if (liveLocationType == 2) { + } else if (locationType == 2) { if (i >= 2) { return currentLiveLocations.get(i - 2); } return null; - } else if (liveLocationType == 1) { + } else if (locationType == 1) { if (i > 3 && i < places.size() + 4) { return places.get(i - 4); } @@ -254,7 +336,11 @@ public class LocationActivityAdapter extends BaseLocationAdapter { if (position == 0) { return 0; } - if (currentMessageObject != null) { + if (locationType == LocationActivity.LOCATION_TYPE_GROUP_VIEW) { + return 7; + } else if (locationType == LocationActivity.LOCATION_TYPE_GROUP) { + return 1; + } else if (currentMessageObject != null) { if (position == 2) { return 2; } else if (position == 3) { @@ -263,14 +349,14 @@ public class LocationActivityAdapter extends BaseLocationAdapter { } else { return 7; } - } else if (liveLocationType == 2) { + } else if (locationType == 2) { if (position == 1) { shareLiveLocationPotistion = position; return 6; } else { return 7; } - } else if (liveLocationType == 1) { + } else if (locationType == 1) { if (position == 1) { return 1; } else if (position == 2) { 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 45b198348..d4b905b72 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java @@ -24,7 +24,7 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; @@ -71,7 +71,7 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { private ArrayList searchResultHashtags; private ArrayList searchResultCommands; private ArrayList searchResultCommandsHelp; - private ArrayList searchResultSuggestions; + private ArrayList searchResultSuggestions; private String[] lastSearchKeyboardLanguage; private ArrayList searchResultCommandsUsers; private ArrayList searchResultBotContext; @@ -686,7 +686,7 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { ArrayList newResult = new ArrayList<>(); final SparseArray newResultsHashMap = new SparseArray<>(); final SparseArray newMap = new SparseArray<>(); - ArrayList inlineBots = DataQuery.getInstance(currentAccount).inlineBots; + ArrayList inlineBots = MediaDataController.getInstance(currentAccount).inlineBots; if (!usernameOnly && needBotContext && dogPostion == 0 && !inlineBots.isEmpty()) { int count = 0; for (int a = 0; a < inlineBots.size(); a++) { @@ -869,10 +869,10 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter { } else if (foundType == 3) { String[] newLanguage = AndroidUtilities.getCurrentKeyboardLanguage(); if (!Arrays.equals(newLanguage, lastSearchKeyboardLanguage)) { - DataQuery.getInstance(currentAccount).fetchNewEmojiKeywords(newLanguage); + MediaDataController.getInstance(currentAccount).fetchNewEmojiKeywords(newLanguage); } lastSearchKeyboardLanguage = newLanguage; - DataQuery.getInstance(currentAccount).getEmojiSuggestions(lastSearchKeyboardLanguage, result.toString(), false, (param, alias) -> { + MediaDataController.getInstance(currentAccount).getEmojiSuggestions(lastSearchKeyboardLanguage, result.toString(), false, (param, alias) -> { searchResultSuggestions = param; searchResultHashtags = null; searchResultUsernames = null; 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 784ef2d89..7271152de 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java @@ -16,6 +16,7 @@ import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; +import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; @@ -29,6 +30,7 @@ import org.telegram.messenger.Utilities; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.GraySectionCell; import org.telegram.ui.Cells.ProfileSearchCell; +import org.telegram.ui.Cells.TextCell; import org.telegram.ui.Cells.UserCell; import org.telegram.ui.Components.RecyclerListView; @@ -53,9 +55,10 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { private boolean onlyMutual; private boolean allowChats; private boolean allowBots; + private boolean allowPhoneNumbers; private int channelId; - public SearchAdapter(Context context, SparseArray arg1, boolean usernameSearch, boolean mutual, boolean chats, boolean bots, int searchChannelId) { + public SearchAdapter(Context context, SparseArray arg1, boolean usernameSearch, boolean mutual, boolean chats, boolean bots, boolean phones, int searchChannelId) { mContext = context; ignoreUsers = arg1; onlyMutual = mutual; @@ -63,6 +66,7 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { allowChats = chats; allowBots = bots; channelId = searchChannelId; + allowPhoneNumbers = phones; searchAdapterHelper = new SearchAdapterHelper(true); searchAdapterHelper.setDelegate(new SearchAdapterHelper.SearchAdapterHelperDelegate() { @Override @@ -102,7 +106,7 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { searchResult.clear(); searchResultNames.clear(); if (allowUsernameSearch) { - searchAdapterHelper.queryServerSearch(null, true, allowChats, allowBots, true, channelId, 0); + searchAdapterHelper.queryServerSearch(null, true, allowChats, allowBots, true, channelId, allowPhoneNumbers, 0); } notifyDataSetChanged(); } else { @@ -125,7 +129,7 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { private void processSearch(final String query) { AndroidUtilities.runOnUIThread(() -> { if (allowUsernameSearch) { - searchAdapterHelper.queryServerSearch(query, true, allowChats, allowBots, true, channelId, -1); + searchAdapterHelper.queryServerSearch(query, true, allowChats, allowBots, true, channelId, allowPhoneNumbers, -1); } final int currentAccount = UserConfig.selectedAccount; final ArrayList contactsCopy = new ArrayList<>(ContactsController.getInstance(currentAccount).contacts); @@ -197,7 +201,8 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { @Override public boolean isEnabled(RecyclerView.ViewHolder holder) { - return holder.getItemViewType() == 0; + int type = holder.getItemViewType(); + return type == 0 || type == 2; } @Override @@ -207,27 +212,43 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { if (globalCount != 0) { count += globalCount + 1; } + int phoneCount = searchAdapterHelper.getPhoneSearch().size(); + if (phoneCount != 0) { + count += phoneCount; + } return count; } public boolean isGlobalSearch(int i) { int localCount = searchResult.size(); int globalCount = searchAdapterHelper.getGlobalSearch().size(); + int phoneCount = searchAdapterHelper.getPhoneSearch().size(); if (i >= 0 && i < localCount) { return false; - } else if (i > localCount && i <= globalCount + localCount) { + } else if (i > localCount && i < localCount + phoneCount) { + return false; + } else if (i > localCount + phoneCount && i <= globalCount + phoneCount + localCount) { return true; } return false; } - public TLObject getItem(int i) { + public Object getItem(int i) { int localCount = searchResult.size(); int globalCount = searchAdapterHelper.getGlobalSearch().size(); + int phoneCount = searchAdapterHelper.getPhoneSearch().size(); if (i >= 0 && i < localCount) { return searchResult.get(i); - } else if (i > localCount && i <= globalCount + localCount) { - return searchAdapterHelper.getGlobalSearch().get(i - localCount - 1); + } else { + i -= localCount; + if (i >= 0 && i < phoneCount) { + return searchAdapterHelper.getPhoneSearch().get(i); + } else { + i -= phoneCount; + if (i > 0 && i <= globalCount) { + return searchAdapterHelper.getGlobalSearch().get(i - 1); + } + } } return null; } @@ -247,9 +268,11 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { } break; case 1: - default: view = new GraySectionCell(mContext); - ((GraySectionCell) view).setText(LocaleController.getString("GlobalSearch", R.string.GlobalSearch)); + break; + case 2: + default: + view = new TextCell(mContext, 16); break; } return new RecyclerListView.Holder(view); @@ -257,81 +280,108 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - if (holder.getItemViewType() == 0) { - TLObject object = getItem(position); - if (object != null) { - int id = 0; - String un = null; - if (object instanceof TLRPC.User) { - un = ((TLRPC.User) object).username; - id = ((TLRPC.User) object).id; - } else if (object instanceof TLRPC.Chat) { - un = ((TLRPC.Chat) object).username; - id = ((TLRPC.Chat) object).id; - } + switch (holder.getItemViewType()) { + case 0: { + TLObject object = (TLObject) getItem(position); + if (object != null) { + int id = 0; + String un = null; + if (object instanceof TLRPC.User) { + un = ((TLRPC.User) object).username; + id = ((TLRPC.User) object).id; + } else if (object instanceof TLRPC.Chat) { + un = ((TLRPC.Chat) object).username; + id = ((TLRPC.Chat) object).id; + } - CharSequence username = null; - CharSequence name = null; - if (position < searchResult.size()) { - name = searchResultNames.get(position); - if (name != null && un != null && un.length() > 0) { - if (name.toString().startsWith("@" + un)) { - username = name; - name = null; - } - } - } else if (position > searchResult.size() && un != null) { - String foundUserName = searchAdapterHelper.getLastFoundUsername(); - if (foundUserName.startsWith("@")) { - foundUserName = foundUserName.substring(1); - } - try { - int index; - SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(); - spannableStringBuilder.append("@"); - spannableStringBuilder.append(un); - if ((index = un.toLowerCase().indexOf(foundUserName)) != -1) { - int len = foundUserName.length(); - if (index == 0) { - len++; - } else { - index++; + CharSequence username = null; + CharSequence name = null; + if (position < searchResult.size()) { + name = searchResultNames.get(position); + if (name != null && un != null && un.length() > 0) { + if (name.toString().startsWith("@" + un)) { + username = name; + name = null; } - spannableStringBuilder.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)), index, index + len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } - username = spannableStringBuilder; - } catch (Exception e) { - username = un; - FileLog.e(e); + } else if (position > searchResult.size() && un != null) { + String foundUserName = searchAdapterHelper.getLastFoundUsername(); + if (foundUserName.startsWith("@")) { + foundUserName = foundUserName.substring(1); + } + try { + int index; + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(); + spannableStringBuilder.append("@"); + spannableStringBuilder.append(un); + if ((index = AndroidUtilities.indexOfIgnoreCase(un, foundUserName)) != -1) { + int len = foundUserName.length(); + if (index == 0) { + len++; + } else { + index++; + } + spannableStringBuilder.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)), index, index + len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + username = spannableStringBuilder; + } catch (Exception e) { + username = un; + FileLog.e(e); + } } - } - if (useUserCell) { - UserCell userCell = (UserCell) holder.itemView; - userCell.setData(object, name, username, 0); - if (checkedMap != null) { - userCell.setChecked(checkedMap.indexOfKey(id) >= 0, false); - } - } else { - ProfileSearchCell profileSearchCell = (ProfileSearchCell) holder.itemView; - profileSearchCell.setData(object, null, name, username, false, false); - profileSearchCell.useSeparator = (position != getItemCount() - 1 && position != searchResult.size() - 1); - /*if (ignoreUsers != null) { - if (ignoreUsers.containsKey(id)) { - profileSearchCell.drawAlpha = 0.5f; - } else { - profileSearchCell.drawAlpha = 1.0f; + if (useUserCell) { + UserCell userCell = (UserCell) holder.itemView; + userCell.setData(object, name, username, 0); + if (checkedMap != null) { + userCell.setChecked(checkedMap.indexOfKey(id) >= 0, false); } - }*/ + } else { + ProfileSearchCell profileSearchCell = (ProfileSearchCell) holder.itemView; + profileSearchCell.setData(object, null, name, username, false, false); + profileSearchCell.useSeparator = (position != getItemCount() - 1 && position != searchResult.size() - 1); + /*if (ignoreUsers != null) { + if (ignoreUsers.containsKey(id)) { + profileSearchCell.drawAlpha = 0.5f; + } else { + profileSearchCell.drawAlpha = 1.0f; + } + }*/ + } } + break; + } + case 1: { + GraySectionCell cell = (GraySectionCell) holder.itemView; + if (getItem(position) == null) { + cell.setText(LocaleController.getString("GlobalSearch", R.string.GlobalSearch)); + } else { + cell.setText(LocaleController.getString("PhoneNumberSearch", R.string.PhoneNumberSearch)); + } + break; + } + case 2: { + String str = (String) getItem(position); + TextCell cell = (TextCell) holder.itemView; + cell.setColors(null, Theme.key_windowBackgroundWhiteBlueText2); + cell.setText(LocaleController.formatString("AddContactByPhone", R.string.AddContactByPhone, PhoneFormat.getInstance().format("+" + str)), false); + break; } } } @Override public int getItemViewType(int i) { - if (i == searchResult.size()) { + Object item = getItem(i); + if (item == null) { return 1; + } else if (item instanceof String) { + String str = (String) item; + if ("section".equals(str)) { + return 1; + } else { + return 2; + } } return 0; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapterHelper.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapterHelper.java index 8888de19c..44870b4dd 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapterHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapterHelper.java @@ -10,9 +10,11 @@ package org.telegram.ui.Adapters; import android.util.SparseArray; +import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.SQLite.SQLiteCursor; import org.telegram.SQLite.SQLitePreparedStatement; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ContactsController; import org.telegram.messenger.FileLog; import org.telegram.messenger.MessagesController; import org.telegram.messenger.MessagesStorage; @@ -37,7 +39,10 @@ public class SearchAdapterHelper { public interface SearchAdapterHelperDelegate { void onDataSetChanged(); - void onSetHashtags(ArrayList arrayList, HashMap hashMap); + + default void onSetHashtags(ArrayList arrayList, HashMap hashMap) { + + } default SparseArray getExcludeUsers() { return null; @@ -54,6 +59,8 @@ public class SearchAdapterHelper { private SparseArray globalSearchMap = new SparseArray<>(); private ArrayList groupSearch = new ArrayList<>(); private SparseArray groupSearchMap = new SparseArray<>(); + private SparseArray phoneSearchMap = new SparseArray<>(); + private ArrayList phonesSearch = new ArrayList<>(); private ArrayList localSearchResults; private int currentAccount = UserConfig.selectedAccount; @@ -82,7 +89,7 @@ public class SearchAdapterHelper { return reqId != 0 || channelReqId != 0; } - public void queryServerSearch(final String query, final boolean allowUsername, final boolean allowChats, final boolean allowBots, final boolean allowSelf, final int channelId, final int type) { + public void queryServerSearch(final String query, final boolean allowUsername, final boolean allowChats, final boolean allowBots, final boolean allowSelf, final int channelId, final boolean phoneNumbers, final int type) { if (reqId != 0) { ConnectionsManager.getInstance(currentAccount).cancelRequest(reqId, true); reqId = 0; @@ -97,6 +104,8 @@ public class SearchAdapterHelper { globalSearch.clear(); globalSearchMap.clear(); localServerSearch.clear(); + phonesSearch.clear(); + phoneSearchMap.clear(); lastReqId = 0; channelLastReqId = 0; delegate.onDataSetChanged(); @@ -162,6 +171,7 @@ public class SearchAdapterHelper { final int currentReqId = ++lastReqId; reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (currentReqId == lastReqId) { + reqId = 0; if (error == null) { TLRPC.TL_contacts_found res = (TLRPC.TL_contacts_found) response; globalSearch.clear(); @@ -229,6 +239,9 @@ public class SearchAdapterHelper { chat = chatsMap.get(peer.channel_id); } if (chat != null) { + if (!allowChats) { + continue; + } localServerSearch.add(chat); globalSearchMap.put(-chat.id, chat); } else if (user != null) { @@ -245,7 +258,6 @@ public class SearchAdapterHelper { delegate.onDataSetChanged(); } } - reqId = 0; }), ConnectionsManager.RequestFlagFailOnServerErrors); } else { globalSearch.clear(); @@ -255,6 +267,32 @@ public class SearchAdapterHelper { delegate.onDataSetChanged(); } } + if (phoneNumbers && query.startsWith("+") && query.length() > 3) { + phonesSearch.clear(); + phoneSearchMap.clear(); + String phone = PhoneFormat.stripExceptNumbers(query); + ArrayList arrayList = ContactsController.getInstance(currentAccount).contacts; + boolean hasFullMatch = false; + for (int a = 0, N = arrayList.size(); a < N; a++) { + TLRPC.TL_contact contact = arrayList.get(a); + TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(contact.user_id); + if (user == null) { + continue; + } + if (user.phone != null && user.phone.startsWith(phone)) { + if (!hasFullMatch) { + hasFullMatch = user.phone.length() == phone.length(); + } + phonesSearch.add(user); + phoneSearchMap.put(user.id, user); + } + } + if (!hasFullMatch) { + phonesSearch.add("section"); + phonesSearch.add(phone); + } + delegate.onDataSetChanged(); + } } public void unloadRecentHashtags() { @@ -316,6 +354,11 @@ public class SearchAdapterHelper { groupSearch.remove(participant); groupSearchMap.remove(user.id); } + Object object = phoneSearchMap.get(user.id); + if (object != null) { + phonesSearch.remove(object); + phoneSearchMap.remove(user.id); + } } else if (obj instanceof TLRPC.Chat) { TLRPC.Chat chat = (TLRPC.Chat) obj; TLRPC.Chat c = (TLRPC.Chat) globalSearchMap.get(-chat.id); @@ -419,6 +462,10 @@ public class SearchAdapterHelper { return globalSearch; } + public ArrayList getPhoneSearch() { + return phonesSearch; + } + public ArrayList getLocalServerSearch() { return localServerSearch; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java index fd249baea..0674cc995 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java @@ -14,9 +14,10 @@ import android.view.View; import android.view.ViewGroup; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.Emoji; import org.telegram.messenger.ImageLocation; +import org.telegram.messenger.MessageObject; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.SharedConfig; import org.telegram.messenger.UserConfig; @@ -38,11 +39,20 @@ import androidx.recyclerview.widget.RecyclerView; public class StickersAdapter extends RecyclerListView.SelectionAdapter implements NotificationCenter.NotificationCenterDelegate { + private class StickerResult { + public TLRPC.Document sticker; + public Object parent; + + public StickerResult(TLRPC.Document s, Object p) { + sticker = s; + parent = p; + } + } + private int currentAccount = UserConfig.selectedAccount; private Context mContext; - private ArrayList keywordResults; - private ArrayList stickers; - private ArrayList stickersParents; + private ArrayList keywordResults; + private ArrayList stickers; private HashMap stickersMap; private ArrayList stickersToLoad = new ArrayList<>(); private StickersAdapterDelegate delegate; @@ -60,8 +70,8 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement public StickersAdapter(Context context, StickersAdapterDelegate delegate) { mContext = context; this.delegate = delegate; - DataQuery.getInstance(currentAccount).checkStickers(DataQuery.TYPE_IMAGE); - DataQuery.getInstance(currentAccount).checkStickers(DataQuery.TYPE_MASK); + MediaDataController.getInstance(currentAccount).checkStickers(MediaDataController.TYPE_IMAGE); + MediaDataController.getInstance(currentAccount).checkStickers(MediaDataController.TYPE_MASK); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.newEmojiSuggestionsAvailable); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.fileDidLoad); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.fileDidFailedLoad); @@ -101,13 +111,13 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement stickersToLoad.clear(); int size = Math.min(6, stickers.size()); for (int a = 0; a < size; a++) { - TLRPC.Document document = stickers.get(a); - TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(document.thumbs, 90); + StickerResult result = stickers.get(a); + TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(result.sticker.thumbs, 90); if (thumb instanceof TLRPC.TL_photoSize) { File f = FileLoader.getPathToAttach(thumb, "webp", true); if (!f.exists()) { stickersToLoad.add(FileLoader.getAttachFileName(thumb, "webp")); - FileLoader.getInstance(currentAccount).loadFile(ImageLocation.getForDocument(thumb, document), stickersParents.get(a), "webp", 1, 1); + FileLoader.getInstance(currentAccount).loadFile(ImageLocation.getForDocument(thumb, result.sticker), result.parent, "webp", 1, 1); } } } @@ -137,11 +147,9 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement } if (stickers == null) { stickers = new ArrayList<>(); - stickersParents = new ArrayList<>(); stickersMap = new HashMap<>(); } - stickers.add(document); - stickersParents.add(parent); + stickers.add(new StickerResult(document, parent)); stickersMap.put(key, document); } @@ -157,22 +165,16 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement } if (stickers == null) { stickers = new ArrayList<>(); - stickersParents = new ArrayList<>(); stickersMap = new HashMap<>(); } - stickers.add(document); - boolean found = false; for (int b = 0, size2 = document.attributes.size(); b < size2; b++) { TLRPC.DocumentAttribute attribute = document.attributes.get(b); if (attribute instanceof TLRPC.TL_documentAttributeSticker) { - stickersParents.add(attribute.stickerset); - found = true; + parent = attribute.stickerset; break; } } - if (!found) { - stickersParents.add(parent); - } + stickers.add(new StickerResult(document, parent)); stickersMap.put(key, document); } } @@ -194,12 +196,12 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement private void searchEmojiByKeyword() { String[] newLanguage = AndroidUtilities.getCurrentKeyboardLanguage(); if (!Arrays.equals(newLanguage, lastSearchKeyboardLanguage)) { - DataQuery.getInstance(currentAccount).fetchNewEmojiKeywords(newLanguage); + MediaDataController.getInstance(currentAccount).fetchNewEmojiKeywords(newLanguage); } lastSearchKeyboardLanguage = newLanguage; String query = lastSticker; cancelEmojiSearch(); - searchRunnable = () -> DataQuery.getInstance(currentAccount).getEmojiSuggestions(lastSearchKeyboardLanguage, query, true, (param, alias) -> { + searchRunnable = () -> MediaDataController.getInstance(currentAccount).getEmojiSuggestions(lastSearchKeyboardLanguage, query, true, (param, alias) -> { if (query.equals(lastSticker)) { if (!param.isEmpty()) { keywordResults = param; @@ -232,6 +234,7 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement } } lastSticker = emoji.toString(); + stickersToLoad.clear(); boolean isValidEmoji = searchEmoji && (Emoji.isValidEmoji(originalEmoji) || Emoji.isValidEmoji(lastSticker)); if (emojiOnly || SharedConfig.suggestStickers == 2 || !isValidEmoji) { if (visible && (keywordResults == null || keywordResults.isEmpty())) { @@ -246,7 +249,6 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement } cancelEmojiSearch(); stickers = null; - stickersParents = null; stickersMap = null; if (lastReqId != 0) { ConnectionsManager.getInstance(currentAccount).cancelRequest(lastReqId, true); @@ -254,8 +256,8 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement } delayLocalResults = false; - final ArrayList recentStickers = DataQuery.getInstance(currentAccount).getRecentStickersNoCopy(DataQuery.TYPE_IMAGE); - final ArrayList favsStickers = DataQuery.getInstance(currentAccount).getRecentStickersNoCopy(DataQuery.TYPE_FAVE); + final ArrayList recentStickers = MediaDataController.getInstance(currentAccount).getRecentStickersNoCopy(MediaDataController.TYPE_IMAGE); + final ArrayList favsStickers = MediaDataController.getInstance(currentAccount).getRecentStickersNoCopy(MediaDataController.TYPE_FAVE); int recentsAdded = 0; for (int a = 0, size = recentStickers.size(); a < size; a++) { TLRPC.Document document = recentStickers.get(a); @@ -274,41 +276,49 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement } } - HashMap> allStickers = DataQuery.getInstance(currentAccount).getAllStickers(); + HashMap> allStickers = MediaDataController.getInstance(currentAccount).getAllStickers(); ArrayList newStickers = allStickers != null ? allStickers.get(lastSticker) : null; if (newStickers != null && !newStickers.isEmpty()) { - ArrayList arrayList = new ArrayList<>(newStickers); - if (!recentStickers.isEmpty()) { - Collections.sort(arrayList, new Comparator() { - private int getIndex(long id) { - for (int a = 0; a < favsStickers.size(); a++) { - if (favsStickers.get(a).id == id) { - return a + 1000; - } + addStickersToResult(newStickers, null); + } + if (stickers != null) { + Collections.sort(stickers, new Comparator() { + private int getIndex(long id) { + for (int a = 0; a < favsStickers.size(); a++) { + if (favsStickers.get(a).id == id) { + return a + 1000; } - for (int a = 0; a < recentStickers.size(); a++) { - if (recentStickers.get(a).id == id) { - return a; - } - } - return -1; } + for (int a = 0; a < recentStickers.size(); a++) { + if (recentStickers.get(a).id == id) { + return a; + } + } + return -1; + } - @Override - public int compare(TLRPC.Document lhs, TLRPC.Document rhs) { - int idx1 = getIndex(lhs.id); - int idx2 = getIndex(rhs.id); + @Override + public int compare(StickerResult lhs, StickerResult rhs) { + boolean isAnimated1 = MessageObject.isAnimatedStickerDocument(lhs.sticker); + boolean isAnimated2 = MessageObject.isAnimatedStickerDocument(rhs.sticker); + if (isAnimated1 == isAnimated2) { + int idx1 = getIndex(lhs.sticker.id); + int idx2 = getIndex(rhs.sticker.id); if (idx1 > idx2) { return -1; } else if (idx1 < idx2) { return 1; } return 0; + } else { + if (isAnimated1 && !isAnimated2) { + return -1; + } else { + return 1; + } } - }); - } - - addStickersToResult(arrayList, null); + } + }); } if (SharedConfig.suggestStickers == 0) { searchServerStickers(lastSticker, originalEmoji); @@ -321,7 +331,7 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement visible = false; } else { checkStickerFilesExistAndDownload(); - boolean show = stickers != null && !stickers.isEmpty() && stickersToLoad.isEmpty(); + boolean show = stickersToLoad.isEmpty(); if (show) { keywordResults = null; } @@ -351,7 +361,7 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement int newCount = stickers != null ? stickers.size() : 0; if (!visible && stickers != null && !stickers.isEmpty()) { checkStickerFilesExistAndDownload(); - boolean show = stickers != null && !stickers.isEmpty() && stickersToLoad.isEmpty(); + boolean show = stickersToLoad.isEmpty(); if (show) { keywordResults = null; } @@ -368,12 +378,12 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement if (delayLocalResults || lastReqId != 0) { return; } - lastSticker = null; - stickers = null; - stickersParents = null; - stickersMap = null; + if (stickersToLoad.isEmpty()) { + lastSticker = null; + stickers = null; + stickersMap = null; + } keywordResults = null; - stickersToLoad.clear(); notifyDataSetChanged(); if (lastReqId != 0) { ConnectionsManager.getInstance(currentAccount).cancelRequest(lastReqId, true); @@ -397,14 +407,14 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement if (keywordResults != null && !keywordResults.isEmpty()) { return keywordResults.get(i).emoji; } - return stickers != null && i >= 0 && i < stickers.size() ? stickers.get(i) : null; + return stickers != null && i >= 0 && i < stickers.size() ? stickers.get(i).sticker : null; } public Object getItemParent(int i) { if (keywordResults != null && !keywordResults.isEmpty()) { return null; } - return stickersParents != null && i >= 0 && i < stickersParents.size() ? stickersParents.get(i) : null; + return stickers != null && i >= 0 && i < stickers.size() ? stickers.get(i).parent : null; } @Override @@ -449,7 +459,8 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement side = 1; } StickerCell stickerCell = (StickerCell) holder.itemView; - stickerCell.setSticker(stickers.get(position), stickersParents.get(position), side); + StickerResult result = stickers.get(position); + stickerCell.setSticker(result.sticker, result.parent, side); stickerCell.setClearsInputField(true); break; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ArchivedStickersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ArchivedStickersActivity.java index 317c4ee3e..e8d524d29 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ArchivedStickersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ArchivedStickersActivity.java @@ -14,7 +14,7 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.LocaleController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; @@ -82,7 +82,7 @@ public class ArchivedStickersActivity extends BaseFragment implements Notificati public View createView(Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setAllowOverlayTitle(true); - if (currentType == DataQuery.TYPE_IMAGE) { + if (currentType == MediaDataController.TYPE_IMAGE) { actionBar.setTitle(LocaleController.getString("ArchivedStickers", R.string.ArchivedStickers)); } else { actionBar.setTitle(LocaleController.getString("ArchivedMasks", R.string.ArchivedMasks)); @@ -103,7 +103,7 @@ public class ArchivedStickersActivity extends BaseFragment implements Notificati frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); emptyView = new EmptyTextProgressView(context); - if (currentType == DataQuery.TYPE_IMAGE) { + if (currentType == MediaDataController.TYPE_IMAGE) { emptyView.setText(LocaleController.getString("ArchivedStickersEmpty", R.string.ArchivedStickersEmpty)); } else { emptyView.setText(LocaleController.getString("ArchivedMasksEmpty", R.string.ArchivedMasksEmpty)); @@ -201,7 +201,7 @@ public class ArchivedStickersActivity extends BaseFragment implements Notificati TLRPC.TL_messages_getArchivedStickers req = new TLRPC.TL_messages_getArchivedStickers(); req.offset_id = sets.isEmpty() ? 0 : sets.get(sets.size() - 1).set.id; req.limit = 15; - req.masks = currentType == DataQuery.TYPE_MASK; + req.masks = currentType == MediaDataController.TYPE_MASK; int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (error == null) { TLRPC.TL_messages_archivedStickers res = (TLRPC.TL_messages_archivedStickers) response; @@ -260,7 +260,7 @@ public class ArchivedStickersActivity extends BaseFragment implements Notificati cell.setTag(position); TLRPC.StickerSetCovered stickerSet = sets.get(position); cell.setStickersSet(stickerSet, position != sets.size() - 1); - cell.setChecked(DataQuery.getInstance(currentAccount).isStickerPackInstalled(stickerSet.set.id)); + cell.setChecked(MediaDataController.getInstance(currentAccount).isStickerPackInstalled(stickerSet.set.id)); } } @@ -283,7 +283,7 @@ public class ArchivedStickersActivity extends BaseFragment implements Notificati return; } TLRPC.StickerSetCovered stickerSet = sets.get(num); - DataQuery.getInstance(currentAccount).removeStickersSet(getParentActivity(), stickerSet.set, !isChecked ? 1 : 2, ArchivedStickersActivity.this, false); + MediaDataController.getInstance(currentAccount).removeStickersSet(getParentActivity(), stickerSet.set, !isChecked ? 1 : 2, ArchivedStickersActivity.this, false); }); break; case 1: diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java index 3069c449e..5e9f50e41 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java @@ -1161,6 +1161,30 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg return; } if (which == 0) { + int index; + if ((index = urlFinal.lastIndexOf('#')) != -1) { + String webPageUrl; + if (!TextUtils.isEmpty(currentPage.cached_page.url)) { + webPageUrl = currentPage.cached_page.url.toLowerCase(); + } else { + webPageUrl = currentPage.url.toLowerCase(); + } + String anchor; + try { + anchor = URLDecoder.decode(urlFinal.substring(index + 1), "UTF-8"); + } catch (Exception ignore) { + anchor = ""; + } + if (urlFinal.toLowerCase().contains(webPageUrl)) { + if (TextUtils.isEmpty(anchor)) { + layoutManager[0].scrollToPositionWithOffset(0, 0); + checkScrollAnimated(); + } else { + scrollToAnchor(anchor); + } + return; + } + } Browser.openUrl(parentActivity, urlFinal); } else if (which == 1) { String url = urlFinal; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java index d807abc20..c730dced3 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java @@ -24,7 +24,7 @@ import org.telegram.SQLite.SQLitePreparedStatement; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ClearCacheService; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.ImageLoader; @@ -345,7 +345,7 @@ public class CacheControlActivity extends BaseFragment { database.executeFast("DELETE FROM media_counts_v2 WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM media_v2 WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM media_holes_v2 WHERE uid = " + did).stepThis().dispose(); - DataQuery.getInstance(currentAccount).clearBotKeyboard(did, null); + MediaDataController.getInstance(currentAccount).clearBotKeyboard(did, null); if (messageId != -1) { MessagesStorage.createFirstHoles(did, state5, state6, messageId); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CallLogActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CallLogActivity.java index 042dc9470..323a8ffd0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CallLogActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CallLogActivity.java @@ -24,6 +24,7 @@ import android.widget.FrameLayout; import android.widget.ImageView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; @@ -137,8 +138,28 @@ public class CallLogActivity extends BaseFragment implements NotificationCenter. private class CustomCell extends FrameLayout { + private ImageView imageView; + private ProfileSearchCell profileSearchCell; + public CustomCell(Context context) { super(context); + + setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + + profileSearchCell = new ProfileSearchCell(context); + profileSearchCell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(32) : 0, 0, LocaleController.isRTL ? 0 : AndroidUtilities.dp(32), 0); + profileSearchCell.setSublabelOffset(AndroidUtilities.dp(LocaleController.isRTL ? 2 : -2), -AndroidUtilities.dp(4)); + addView(profileSearchCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + imageView = new ImageView(context); + imageView.setImageResource(R.drawable.profile_phone); + imageView.setAlpha(214); + imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_featuredStickers_addButton), PorterDuff.Mode.MULTIPLY)); + imageView.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_listSelector), 1)); + imageView.setScaleType(ImageView.ScaleType.CENTER); + imageView.setOnClickListener(callBtnClickListener); + imageView.setContentDescription(LocaleController.getString("Call", R.string.Call)); + addView(imageView, LayoutHelper.createFrame(48, 48, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL, 8, 0, 8, 0)); } } @@ -479,30 +500,12 @@ public class CallLogActivity extends BaseFragment implements NotificationCenter. View view; switch (viewType) { case 0: - CustomCell frameLayout = new CustomCell(mContext); - frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); - - ProfileSearchCell cell = new ProfileSearchCell(mContext); - cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(32) : 0, 0, LocaleController.isRTL ? 0 : AndroidUtilities.dp(32), 0); - cell.setSublabelOffset(AndroidUtilities.dp(LocaleController.isRTL ? 2 : -2), -AndroidUtilities.dp(4)); - frameLayout.addView(cell); - - ImageView imageView = new ImageView(mContext); - imageView.setImageResource(R.drawable.profile_phone); - imageView.setAlpha(214); - imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteGrayIcon), PorterDuff.Mode.MULTIPLY)); - imageView.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, 0)); - imageView.setScaleType(ImageView.ScaleType.CENTER); - imageView.setOnClickListener(callBtnClickListener); - imageView.setContentDescription(LocaleController.getString("Call", R.string.Call)); - frameLayout.addView(imageView, LayoutHelper.createFrame(48, 48, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL, 8, 0, 8, 0)); - - view = frameLayout; - view.setTag(new ViewItem(imageView, cell)); + CustomCell cell = new CustomCell(mContext); + view = cell; + view.setTag(new ViewItem(cell.imageView, cell.profileSearchCell)); break; case 1: view = new LoadingCell(mContext); - view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); break; case 2: default: @@ -581,8 +584,9 @@ public class CallLogActivity extends BaseFragment implements NotificationCenter. int count = listView.getChildCount(); for (int a = 0; a < count; a++) { View child = listView.getChildAt(a); - if (child instanceof ProfileSearchCell) { - ((ProfileSearchCell) child).update(0); + if (child instanceof CustomCell) { + CustomCell cell = (CustomCell) child; + cell.profileSearchCell.update(0); } } } @@ -614,13 +618,14 @@ public class CallLogActivity extends BaseFragment implements NotificationCenter. new ThemeDescription(floatingButton, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chats_actionBackground), new ThemeDescription(floatingButton, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_chats_actionPressedBackground), - new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Drawable[]{Theme.dialogs_verifiedCheckDrawable}, null, Theme.key_chats_verifiedCheck), - new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Drawable[]{Theme.dialogs_verifiedDrawable}, null, Theme.key_chats_verifiedBackground), - new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, Theme.dialogs_offlinePaint, null, null, Theme.key_windowBackgroundWhiteGrayText3), - new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, Theme.dialogs_onlinePaint, null, null, Theme.key_windowBackgroundWhiteBlueText3), - new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Paint[]{Theme.dialogs_namePaint, Theme.dialogs_searchNamePaint}, null, null, Theme.key_chats_name), - new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Paint[]{Theme.dialogs_nameEncryptedPaint, Theme.dialogs_searchNameEncryptedPaint}, null, null, Theme.key_chats_secretName), - new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Drawable[]{Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{CustomCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_featuredStickers_addButton), + new ThemeDescription(listView, 0, new Class[]{CustomCell.class}, null, new Drawable[]{Theme.dialogs_verifiedCheckDrawable}, null, Theme.key_chats_verifiedCheck), + new ThemeDescription(listView, 0, new Class[]{CustomCell.class}, null, new Drawable[]{Theme.dialogs_verifiedDrawable}, null, Theme.key_chats_verifiedBackground), + new ThemeDescription(listView, 0, new Class[]{CustomCell.class}, Theme.dialogs_offlinePaint, null, null, Theme.key_windowBackgroundWhiteGrayText3), + new ThemeDescription(listView, 0, new Class[]{CustomCell.class}, Theme.dialogs_onlinePaint, null, null, Theme.key_windowBackgroundWhiteBlueText3), + new ThemeDescription(listView, 0, new Class[]{CustomCell.class}, null, new Paint[]{Theme.dialogs_namePaint, Theme.dialogs_searchNamePaint}, null, null, Theme.key_chats_name), + new ThemeDescription(listView, 0, new Class[]{CustomCell.class}, null, new Paint[]{Theme.dialogs_nameEncryptedPaint, Theme.dialogs_searchNameEncryptedPaint}, null, null, Theme.key_chats_secretName), + new ThemeDescription(listView, 0, new Class[]{CustomCell.class}, null, new Drawable[]{Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ArchivedStickerSetCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ArchivedStickerSetCell.java index a67f8e818..60ecaad94 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ArchivedStickerSetCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ArchivedStickerSetCell.java @@ -22,6 +22,8 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLoader; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessageObject; +import org.telegram.messenger.SharedConfig; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.BackupImageView; @@ -63,6 +65,7 @@ public class ArchivedStickerSetCell extends FrameLayout { imageView = new BackupImageView(context); imageView.setAspectFit(true); + imageView.setLayerNum(1); addView(imageView, LayoutHelper.createFrame(48, 48, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 12, 8, LocaleController.isRTL ? 12 : 0, 0)); if (needCheckBox) { @@ -99,11 +102,19 @@ public class ArchivedStickerSetCell extends FrameLayout { valueTextView.setText(LocaleController.formatPluralString("Stickers", set.set.count)); TLRPC.PhotoSize thumb = set.cover != null ? FileLoader.getClosestPhotoSizeWithSize(set.cover.thumbs, 90) : null; if (thumb != null && thumb.location != null) { - imageView.setImage(ImageLocation.getForDocument(thumb, set.cover), null, "webp", null, set); + if (MessageObject.canAutoplayAnimatedSticker(set.cover)) { + imageView.setImage(ImageLocation.getForDocument(set.cover), "80_80", ImageLocation.getForDocument(thumb, set.cover), null, 0, set); + } else { + imageView.setImage(ImageLocation.getForDocument(thumb, set.cover), null, "webp", null, set); + } } else if (!set.covers.isEmpty()) { TLRPC.Document document = set.covers.get(0); thumb = FileLoader.getClosestPhotoSizeWithSize(document.thumbs, 90); - imageView.setImage(ImageLocation.getForDocument(thumb, document), null, "webp", null, set); + if (MessageObject.canAutoplayAnimatedSticker(document)) { + imageView.setImage(ImageLocation.getForDocument(document), "80_80", ImageLocation.getForDocument(thumb, document), null, 0, set); + } else { + imageView.setImage(ImageLocation.getForDocument(thumb, document), null, "webp", null, set); + } } else { imageView.setImage(null, null, "webp", null, set); } 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 ce7e4dd53..20978b114 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/BaseCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/BaseCell.java @@ -15,8 +15,6 @@ import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.ViewGroup; -import com.airbnb.lottie.LottieDrawable; - public abstract class BaseCell extends ViewGroup { private final class CheckForTap implements Runnable { 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 8dd416087..02133f1f1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java @@ -13,6 +13,7 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Path; @@ -92,8 +93,10 @@ import org.telegram.ui.Components.SeekBar; import org.telegram.ui.Components.SeekBarWaveform; import org.telegram.ui.Components.StaticLayoutEx; import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.TextStyleSpan; import org.telegram.ui.Components.TypefaceSpan; import org.telegram.ui.Components.URLSpanBotCommand; +import org.telegram.ui.Components.URLSpanBrowser; import org.telegram.ui.Components.URLSpanMono; import org.telegram.ui.Components.URLSpanNoUnderline; import org.telegram.ui.PhotoViewer; @@ -308,6 +311,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private boolean instantPressed; private boolean instantButtonPressed; private Drawable selectorDrawable; + private int selectorDrawableMaskType; private RectF instantButtonRect = new RectF(); private int[] pressedState = new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}; @@ -458,6 +462,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private int backgroundDrawableRight; private int viaWidth; private int viaNameWidth; + private TypefaceSpan viaSpan1; + private TypefaceSpan viaSpan2; private int availableTimeWidth; private int widthBeforeNewTimeLine; @@ -629,6 +635,26 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate return linkPath; } + private int[] getRealSpanStartAndEnd(Spannable buffer, CharacterStyle link) { + int start = 0; + int end = 0; + boolean ok = false; + if (link instanceof URLSpanBrowser) { + URLSpanBrowser span = (URLSpanBrowser) link; + TextStyleSpan.TextStyleRun style = span.getStyle(); + if (style != null && style.urlEntity != null) { + start = style.urlEntity.offset; + end = style.urlEntity.offset + style.urlEntity.length; + ok = true; + } + } + if (!ok) { + start = buffer.getSpanStart(link); + end = buffer.getSpanEnd(link); + } + return new int[]{start, end}; + } + private boolean checkTextBlockMotionEvent(MotionEvent event) { if (currentMessageObject.type != 0 || currentMessageObject.textLayoutBlocks == null || currentMessageObject.textLayoutBlocks.isEmpty() || !(currentMessageObject.messageText instanceof Spannable)) { return false; @@ -673,11 +699,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate resetUrlPaths(false); try { LinkPath path = obtainNewUrlPath(false); - int start = buffer.getSpanStart(pressedLink); - int end = buffer.getSpanEnd(pressedLink); - path.setCurrentLayout(block.textLayout, start, 0); - block.textLayout.getSelectionPath(start, end, path); - if (end >= block.charactersEnd) { + int[] pos = getRealSpanStartAndEnd(buffer, pressedLink); + path.setCurrentLayout(block.textLayout, pos[0], 0); + block.textLayout.getSelectionPath(pos[0], pos[1], path); + if (pos[1] >= block.charactersEnd) { for (int a = blockNum + 1; a < currentMessageObject.textLayoutBlocks.size(); a++) { MessageObject.TextLayoutBlock nextBlock = currentMessageObject.textLayoutBlocks.get(a); CharacterStyle[] nextLink; @@ -691,13 +716,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } path = obtainNewUrlPath(false); path.setCurrentLayout(nextBlock.textLayout, 0, nextBlock.textYOffset - block.textYOffset); - nextBlock.textLayout.getSelectionPath(0, end, path); - if (end < nextBlock.charactersEnd - 1) { + nextBlock.textLayout.getSelectionPath(0, pos[1], path); + if (pos[1] < nextBlock.charactersEnd - 1) { break; } } } - if (start <= block.charactersOffset) { + if (pos[0] <= block.charactersOffset) { int offsetY = 0; for (int a = blockNum - 1; a >= 0; a--) { MessageObject.TextLayoutBlock nextBlock = currentMessageObject.textLayoutBlocks.get(a); @@ -711,11 +736,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate break; } path = obtainNewUrlPath(false); - start = buffer.getSpanStart(pressedLink); offsetY -= nextBlock.height; - path.setCurrentLayout(nextBlock.textLayout, start, offsetY); - nextBlock.textLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), path); - if (start > nextBlock.charactersOffset) { + path.setCurrentLayout(nextBlock.textLayout, pos[0], offsetY); + nextBlock.textLayout.getSelectionPath(pos[0], pos[1], path); + if (pos[0] > nextBlock.charactersOffset) { break; } } @@ -776,9 +800,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate resetUrlPaths(false); try { LinkPath path = obtainNewUrlPath(false); - int start = buffer.getSpanStart(pressedLink); - path.setCurrentLayout(captionLayout, start, 0); - captionLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), path); + int[] pos = getRealSpanStartAndEnd(buffer, pressedLink); + path.setCurrentLayout(captionLayout, pos[0], 0); + captionLayout.getSelectionPath(pos[0], pos[1], path); } catch (Exception e) { FileLog.e(e); } @@ -841,9 +865,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate resetUrlPaths(false); try { LinkPath path = obtainNewUrlPath(false); - int start = buffer.getSpanStart(pressedLink); - path.setCurrentLayout(descriptionLayout, start, 0); - descriptionLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), path); + int[] pos = getRealSpanStartAndEnd(buffer, pressedLink); + path.setCurrentLayout(descriptionLayout, pos[0], 0); + descriptionLayout.getSelectionPath(pos[0], pos[1], path); } catch (Exception e) { FileLog.e(e); } @@ -922,9 +946,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate resetUrlPaths(false); try { LinkPath path = obtainNewUrlPath(false); - int start = buffer.getSpanStart(pressedLink); - path.setCurrentLayout(descriptionLayout, start, 0); - descriptionLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), path); + int[] pos = getRealSpanStartAndEnd(buffer, pressedLink); + path.setCurrentLayout(descriptionLayout, pos[0], 0); + descriptionLayout.getSelectionPath(pos[0], pos[1], path); } catch (Exception e) { FileLog.e(e); } @@ -1530,7 +1554,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { delegate.didPressUserAvatar(this, currentUser, lastTouchX, lastTouchY); } - } else if (currentChat != null) { //TODO + } else if (currentChat != null) { delegate.didPressChannelAvatar(this, currentChat, currentMessageObject.messageOwner.fwd_from.channel_post, lastTouchX, lastTouchY); } } @@ -2738,7 +2762,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } createDocumentLayout(0, messageObject); - } else if (MessageObject.isStickerDocument(document)) { + } else if (MessageObject.isStickerDocument(document) || MessageObject.isAnimatedStickerDocument(document)) { currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(document.thumbs, 90); photoParentObject = document; if (currentPhotoObject != null && (currentPhotoObject.w == 0 || currentPhotoObject.h == 0)) { @@ -4719,7 +4743,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate public void setHighlightedText(String text) { MessageObject messageObject = messageObjectToSet != null ? messageObjectToSet : currentMessageObject; - if (messageObject == null || TextUtils.isEmpty(text)) { + if (messageObject == null || messageObject.messageOwner.message == null || TextUtils.isEmpty(text)) { if (!urlPathSelection.isEmpty()) { linkSelectionBlockNum = -1; resetUrlPaths(true); @@ -4882,7 +4906,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate public void draw(Canvas canvas) { android.graphics.Rect bounds = getBounds(); rect.set(bounds.left, bounds.top, bounds.right, bounds.bottom); - canvas.drawRoundRect(rect, AndroidUtilities.dp(6), AndroidUtilities.dp(6), maskPaint); + canvas.drawRoundRect(rect, selectorDrawableMaskType == 0 ? AndroidUtilities.dp(6) : 0, selectorDrawableMaskType == 0 ? AndroidUtilities.dp(6) : 0, maskPaint); } @Override @@ -4897,7 +4921,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate @Override public int getOpacity() { - return PixelFormat.OPAQUE; + return PixelFormat.TRANSPARENT; } }; ColorStateList colorStateList = new ColorStateList( @@ -5559,6 +5583,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } if (Build.VERSION.SDK_INT >= 21) { + selectorDrawableMaskType = 0; selectorDrawable.setBounds(linkX, instantY, linkX + instantWidth, instantY + AndroidUtilities.dp(36)); selectorDrawable.draw(canvas); } @@ -7015,15 +7040,16 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { color = Theme.getColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outViaBotNameText : Theme.key_chat_inViaBotNameText); } + String viaBotString = LocaleController.getString("ViaBot", R.string.ViaBot); if (currentNameString.length() > 0) { - SpannableStringBuilder stringBuilder = new SpannableStringBuilder(String.format("%s %s %s", nameStringFinal, LocaleController.getString("ViaBot", R.string.ViaBot), viaUsername)); - stringBuilder.setSpan(new TypefaceSpan(Typeface.DEFAULT, 0, color), nameStringFinal.length() + 1, nameStringFinal.length() + 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf"), 0, color), nameStringFinal.length() + 5, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(String.format("%s %s %s", nameStringFinal, viaBotString, viaUsername)); + stringBuilder.setSpan(viaSpan1 = new TypefaceSpan(Typeface.DEFAULT, 0, color), nameStringFinal.length() + 1, nameStringFinal.length() + 1 + viaBotString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.setSpan(viaSpan2 = new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf"), 0, color), nameStringFinal.length() + 2 + viaBotString.length(), stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); nameStringFinal = stringBuilder; } else { - SpannableStringBuilder stringBuilder = new SpannableStringBuilder(String.format("%s %s", LocaleController.getString("ViaBot", R.string.ViaBot), viaUsername)); - stringBuilder.setSpan(new TypefaceSpan(Typeface.DEFAULT, 0, color), 0, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf"), 0, color), 4, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(String.format("%s %s", viaBotString, viaUsername)); + stringBuilder.setSpan(viaSpan1 = new TypefaceSpan(Typeface.DEFAULT, 0, color), 0, viaBotString.length() + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.setSpan(viaSpan2 = new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf"), 0, color), 1 + viaBotString.length(), stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); nameStringFinal = stringBuilder; } nameStringFinal = TextUtils.ellipsize(nameStringFinal, Theme.chat_namePaint, nameWidth, TextUtils.TruncateAt.END); @@ -7242,7 +7268,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate try { replyTextWidth = AndroidUtilities.dp(4 + (needReplyImage ? 44 : 0)); if (stringFinalText != null) { - replyTextLayout = new StaticLayout(stringFinalText, Theme.chat_replyTextPaint, maxWidth + AndroidUtilities.dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + 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); replyTextOffset = replyTextLayout.getLineLeft(0); @@ -7643,6 +7669,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (drawNameLayout && nameLayout != null) { canvas.save(); + int oldAlpha = 255; + if (currentMessageObject.shouldDrawWithoutBackground()) { Theme.chat_namePaint.setColor(Theme.getColor(Theme.key_chat_stickerNameText)); int backWidth; @@ -7652,9 +7680,24 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate nameX = backgroundDrawableLeft + backgroundDrawableRight + AndroidUtilities.dp(22); } nameY = layoutHeight - AndroidUtilities.dp(38); + float alphaProgress = currentMessageObject.isOut() && (checkBoxVisible || checkBoxAnimationInProgress) ? (1.0f - checkBoxAnimationProgress) : 1.0f; + Theme.chat_systemDrawable.setAlpha((int) (alphaProgress * 255)); Theme.chat_systemDrawable.setColorFilter(Theme.colorFilter); Theme.chat_systemDrawable.setBounds((int) nameX - AndroidUtilities.dp(12), (int) nameY - AndroidUtilities.dp(5), (int) nameX + AndroidUtilities.dp(12) + nameWidth, (int) nameY + AndroidUtilities.dp(22)); Theme.chat_systemDrawable.draw(canvas); + if (checkBoxVisible || checkBoxAnimationInProgress) { + Theme.chat_systemDrawable.setAlpha(oldAlpha); + } + nameX -= nameOffsetX; + int color = Theme.getColor(Theme.key_chat_stickerViaBotNameText); + color = (Theme.getColor(Theme.key_chat_stickerViaBotNameText) & 0x00ffffff) | ((int) (Color.alpha(color) * alphaProgress) << 24); + if (viaSpan1 != null) { + viaSpan1.setColor(color); + } + if (viaSpan2 != null) { + viaSpan2.setColor(color); + } + Theme.chat_systemDrawable.setAlpha(255); } else { if (mediaBackground || currentMessageObject.isOutOwner()) { nameX = backgroundDrawableLeft + AndroidUtilities.dp(11) - nameOffsetX; @@ -7837,6 +7880,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (captionLayout == null || selectionOnly && pressedLink == null) { return; } + if (currentMessageObject.isOutOwner()) { + Theme.chat_msgTextPaint.setColor(Theme.getColor(Theme.key_chat_messageTextOut)); + Theme.chat_msgTextPaint.linkColor = Theme.getColor(Theme.key_chat_messageLinkOut); + } else { + Theme.chat_msgTextPaint.setColor(Theme.getColor(Theme.key_chat_messageTextIn)); + Theme.chat_msgTextPaint.linkColor = Theme.getColor(Theme.key_chat_messageLinkIn); + } canvas.save(); canvas.translate(captionX, captionY); if (pressedLink != null) { @@ -8393,6 +8443,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate canvas.restore(); } if (Build.VERSION.SDK_INT >= 21 && selectorDrawable != null) { + selectorDrawableMaskType = 1; selectorDrawable.draw(canvas); } int lastVoteY = 0; @@ -8511,6 +8562,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } if (Build.VERSION.SDK_INT >= 21) { + selectorDrawableMaskType = 0; selectorDrawable.setBounds(textX, instantY, textX + instantWidth, instantY + AndroidUtilities.dp(36)); selectorDrawable.draw(canvas); } @@ -8790,14 +8842,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (link == null) { return null; } - int start = buffer.getSpanStart(link); - int end = buffer.getSpanEnd(link); - String content = buffer.subSequence(start, end).toString(); + int[] linkPos = getRealSpanStartAndEnd(buffer, link); + String content = buffer.subSequence(linkPos[0], linkPos[1]).toString(); info.setText(content); for (MessageObject.TextLayoutBlock block : currentMessageObject.textLayoutBlocks) { int length = block.textLayout.getText().length(); - if (block.charactersOffset <= start && block.charactersOffset + length >= end) { - block.textLayout.getSelectionPath(start - block.charactersOffset, end - block.charactersOffset, linkPath); + if (block.charactersOffset <= linkPos[0] && block.charactersOffset + length >= linkPos[1]) { + block.textLayout.getSelectionPath(linkPos[0] - block.charactersOffset, linkPos[1] - block.charactersOffset, linkPath); linkPath.computeBounds(rectF, true); rect.set((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom); rect.offset(0, (int) block.textYOffset); 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 71657e352..65edf0ba6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ContextLinkCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ContextLinkCell.java @@ -107,6 +107,7 @@ public class ContextLinkCell extends View implements DownloadController.FileDown super(context); linkImageView = new ImageReceiver(this); + linkImageView.setLayerNum(1); linkImageView.setUseSharedAnimationQueue(true); letterDrawable = new LetterDrawable(); radialProgress = new RadialProgress2(this); @@ -182,7 +183,7 @@ public class ContextLinkCell extends View implements DownloadController.FileDown if (documentAttach != null) { if (MessageObject.isGifDocument(documentAttach)) { currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(documentAttach.thumbs, 90); - } else if (MessageObject.isStickerDocument(documentAttach)) { + } else if (MessageObject.isStickerDocument(documentAttach) || MessageObject.isAnimatedStickerDocument(documentAttach)) { currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(documentAttach.thumbs, 90); ext = "webp"; } else { @@ -288,10 +289,14 @@ public class ContextLinkCell extends View implements DownloadController.FileDown } } else { if (currentPhotoObject != null) { - if (documentAttach != null) { - linkImageView.setImage(ImageLocation.getForDocument(currentPhotoObject, documentAttach), currentPhotoFilter, ImageLocation.getForPhoto(currentPhotoObjectThumb, photoAttach), currentPhotoFilterThumb, currentPhotoObject.size, ext, parentObject, 0); + if (MessageObject.canAutoplayAnimatedSticker(documentAttach)) { + linkImageView.setImage(ImageLocation.getForDocument(documentAttach), "80_80", ImageLocation.getForDocument(currentPhotoObject, documentAttach), currentPhotoFilterThumb, currentPhotoObject.size, null, parentObject, 0); } else { - linkImageView.setImage(ImageLocation.getForPhoto(currentPhotoObject, photoAttach), currentPhotoFilter, ImageLocation.getForPhoto(currentPhotoObjectThumb, photoAttach), currentPhotoFilterThumb, currentPhotoObject.size, ext, parentObject, 0); + if (documentAttach != null) { + linkImageView.setImage(ImageLocation.getForDocument(currentPhotoObject, documentAttach), currentPhotoFilter, ImageLocation.getForPhoto(currentPhotoObjectThumb, photoAttach), currentPhotoFilterThumb, currentPhotoObject.size, ext, parentObject, 0); + } else { + linkImageView.setImage(ImageLocation.getForPhoto(currentPhotoObject, photoAttach), currentPhotoFilter, ImageLocation.getForPhoto(currentPhotoObjectThumb, photoAttach), currentPhotoFilterThumb, currentPhotoObject.size, ext, parentObject, 0); + } } } else if (webFile != null) { linkImageView.setImage(ImageLocation.getForWebFile(webFile), currentPhotoFilter, ImageLocation.getForPhoto(currentPhotoObjectThumb, photoAttach), currentPhotoFilterThumb, -1, ext, parentObject, 1); @@ -345,7 +350,7 @@ public class ContextLinkCell extends View implements DownloadController.FileDown if (documentAttach != null) { if (MessageObject.isGifDocument(documentAttach)) { documentAttachType = DOCUMENT_ATTACH_TYPE_GIF; - } else if (MessageObject.isStickerDocument(documentAttach)) { + } else if (MessageObject.isStickerDocument(documentAttach) || MessageObject.isAnimatedStickerDocument(documentAttach)) { documentAttachType = DOCUMENT_ATTACH_TYPE_STICKER; } else if (MessageObject.isMusicDocument(documentAttach)) { documentAttachType = DOCUMENT_ATTACH_TYPE_MUSIC; 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 af6e85196..820266177 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java @@ -28,16 +28,9 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; -import com.airbnb.lottie.LottieDrawable; -import com.airbnb.lottie.LottieProperty; -import com.airbnb.lottie.SimpleColorFilter; -import com.airbnb.lottie.model.KeyPath; -import com.airbnb.lottie.value.LottieValueCallback; - import org.telegram.messenger.AndroidUtilities; -import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.ChatObject; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.DialogObject; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocaleController; @@ -56,6 +49,7 @@ import org.telegram.messenger.ImageReceiver; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.CheckBox2; +import org.telegram.ui.Components.RLottieDrawable; import org.telegram.ui.Components.StaticLayoutEx; import org.telegram.ui.Components.TypefaceSpan; import org.telegram.ui.DialogsActivity; @@ -110,7 +104,7 @@ public class DialogCell extends BaseCell { private int bottomClip; private float translationX; private boolean isSliding; - private Drawable translationDrawable; + private RLottieDrawable translationDrawable; private boolean translationAnimationStarted; private boolean drawRevealBackground; private float currentRevealProgress; @@ -302,12 +296,9 @@ public class DialogCell extends BaseCell { reorderIconProgress = drawPin && drawReorder ? 1.0f : 0.0f; avatarImage.onDetachedFromWindow(); if (translationDrawable != null) { - if (translationDrawable instanceof LottieDrawable) { - LottieDrawable lottieDrawable = (LottieDrawable) translationDrawable; - lottieDrawable.stop(); - lottieDrawable.setProgress(0.0f); - lottieDrawable.setCallback(null); - } + translationDrawable.stop(); + translationDrawable.setProgress(0.0f); + translationDrawable.setCallback(null); translationDrawable = null; translationAnimationStarted = false; } @@ -704,7 +695,7 @@ public class DialogCell extends BaseCell { } if (isDialogCell) { - draftMessage = DataQuery.getInstance(currentAccount).getDraft(currentDialogId); + draftMessage = MediaDataController.getInstance(currentAccount).getDraft(currentDialogId); 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)) { @@ -1014,16 +1005,6 @@ public class DialogCell extends BaseCell { drawPinBackground = true; } nameString = LocaleController.getString("SavedMessages", R.string.SavedMessages); - } else if (user.id / 1000 != 777 && user.id / 1000 != 333 && ContactsController.getInstance(currentAccount).contactsDict.get(user.id) == null) { - if (ContactsController.getInstance(currentAccount).contactsDict.size() == 0 && (!ContactsController.getInstance(currentAccount).contactsLoaded || ContactsController.getInstance(currentAccount).isLoadingContacts())) { - nameString = UserObject.getUserName(user); - } else { - if (user.phone != null && user.phone.length() != 0) { - nameString = PhoneFormat.getInstance().format("+" + user.phone); - } else { - nameString = UserObject.getUserName(user); - } - } } else { nameString = UserObject.getUserName(user); } @@ -1377,7 +1358,7 @@ public class DialogCell extends BaseCell { if (index < dialogsArray.size()) { TLRPC.Dialog dialog = dialogsArray.get(index); TLRPC.Dialog nextDialog = index + 1 < dialogsArray.size() ? dialogsArray.get(index + 1) : null; - TLRPC.DraftMessage newDraftMessage = DataQuery.getInstance(currentAccount).getDraft(currentDialogId); + TLRPC.DraftMessage newDraftMessage = MediaDataController.getInstance(currentAccount).getDraft(currentDialogId); MessageObject newMessageObject; if (currentDialogFolderId != 0) { newMessageObject = findFolderTopMessage(); @@ -1417,7 +1398,6 @@ public class DialogCell extends BaseCell { } animatingArchiveAvatar = true; animatingArchiveAvatarProgress = 0.0f; - Theme.dialogs_archiveAvatarDrawable.setCallback(this); Theme.dialogs_archiveAvatarDrawable.setProgress(0.0f); Theme.dialogs_archiveAvatarDrawable.start(); invalidate(); @@ -1598,6 +1578,7 @@ public class DialogCell extends BaseCell { } if (currentDialogFolderId != 0) { + Theme.dialogs_archiveAvatarDrawable.setCallback(this); avatarDrawable.setAvatarType(AvatarDrawable.AVATAR_TYPE_ARCHIVED); avatarImage.setImage(null, null, avatarDrawable, null, user, 0); } else { @@ -1633,10 +1614,7 @@ public class DialogCell extends BaseCell { public void setTranslationX(float value) { translationX = (int) value; if (translationDrawable != null && translationX == 0) { - if (translationDrawable instanceof LottieDrawable) { - LottieDrawable lottieDrawable = (LottieDrawable) translationDrawable; - lottieDrawable.setProgress(0.0f); - } + translationDrawable.setProgress(0.0f); translationAnimationStarted = false; archiveHidden = SharedConfig.archiveHidden; currentRevealProgress = 0; @@ -1712,12 +1690,9 @@ public class DialogCell extends BaseCell { } if (!translationAnimationStarted && Math.abs(translationX) > AndroidUtilities.dp(43)) { translationAnimationStarted = true; - if (translationDrawable instanceof LottieDrawable) { - LottieDrawable lottieDrawable = (LottieDrawable) translationDrawable; - lottieDrawable.setProgress(0.0f); - lottieDrawable.setCallback(this); - lottieDrawable.start(); - } + translationDrawable.setProgress(0.0f); + translationDrawable.setCallback(this); + translationDrawable.start(); } float tx = getMeasuredWidth() + translationX; @@ -1725,18 +1700,12 @@ public class DialogCell extends BaseCell { Theme.dialogs_pinnedPaint.setColor(backgroundColor); canvas.drawRect(tx - AndroidUtilities.dp(8), 0, getMeasuredWidth(), getMeasuredHeight(), Theme.dialogs_pinnedPaint); if (currentRevealProgress == 0 && Theme.dialogs_archiveDrawableRecolored) { - if (Theme.dialogs_archiveDrawable instanceof LottieDrawable) { - LottieDrawable lottieDrawable = (LottieDrawable) Theme.dialogs_archiveDrawable; - lottieDrawable.addValueCallback(new KeyPath("Arrow", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_chats_archiveBackground)))); - } + Theme.dialogs_archiveDrawable.setLayerColor("Arrow.**", Theme.getColor(Theme.key_chats_archiveBackground)); Theme.dialogs_archiveDrawableRecolored = false; } } int drawableX = getMeasuredWidth() - AndroidUtilities.dp(43) - translationDrawable.getIntrinsicWidth() / 2; int drawableY = AndroidUtilities.dp(useForceThreeLines || SharedConfig.useThreeLinesLayout ? 12 : 9); - if (!(translationDrawable instanceof LottieDrawable)) { - drawableY += AndroidUtilities.dp(2); - } int drawableCx = drawableX + translationDrawable.getIntrinsicWidth() / 2; int drawableCy = drawableY + translationDrawable.getIntrinsicHeight() / 2; @@ -1750,10 +1719,7 @@ public class DialogCell extends BaseCell { canvas.restore(); if (!Theme.dialogs_archiveDrawableRecolored) { - if (Theme.dialogs_archiveDrawable instanceof LottieDrawable) { - LottieDrawable lottieDrawable = (LottieDrawable) Theme.dialogs_archiveDrawable; - lottieDrawable.addValueCallback(new KeyPath("Arrow", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_chats_archivePinBackground)))); - } + Theme.dialogs_archiveDrawable.setLayerColor("Arrow.**", Theme.getColor(Theme.key_chats_archivePinBackground)); Theme.dialogs_archiveDrawableRecolored = true; } } @@ -1775,12 +1741,9 @@ public class DialogCell extends BaseCell { canvas.restore(); } else if (translationDrawable != null) { - if (translationDrawable instanceof LottieDrawable) { - LottieDrawable lottieDrawable = (LottieDrawable) translationDrawable; - lottieDrawable.stop(); - lottieDrawable.setProgress(0.0f); - lottieDrawable.setCallback(null); - } + translationDrawable.stop(); + translationDrawable.setProgress(0.0f); + translationDrawable.setCallback(null); translationDrawable = null; translationAnimationStarted = false; } 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 dd0eae431..27d545443 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java @@ -112,7 +112,7 @@ public class DrawerProfileCell extends FrameLayout { @Override protected void onDraw(Canvas canvas) { Drawable backgroundDrawable = Theme.getCachedWallpaper(); - String backgroundKey = applyBackground(); + String backgroundKey = applyBackground(false); boolean useImageBackground = !backgroundKey.equals(Theme.key_chats_menuTopBackground) && Theme.isCustomTheme() && !Theme.isPatternWallpaper() && backgroundDrawable != null; boolean drawCatsShadow = false; int color; @@ -203,13 +203,13 @@ public class DrawerProfileCell extends FrameLayout { avatarDrawable.setColor(Theme.getColor(Theme.key_avatar_backgroundInProfileBlue)); avatarImageView.setImage(ImageLocation.getForUser(user, false), "50_50", avatarDrawable, user); - applyBackground(); + applyBackground(true); } - public String applyBackground() { + public String applyBackground(boolean force) { String currentTag = (String) getTag(); String backgroundKey = Theme.hasThemeKey(Theme.key_chats_menuTopBackground) && Theme.getColor(Theme.key_chats_menuTopBackground) != 0 ? Theme.key_chats_menuTopBackground : Theme.key_chats_menuTopBackgroundCats; - if (!backgroundKey.equals(currentTag)) { + if (force || !backgroundKey.equals(currentTag)) { setBackgroundColor(Theme.getColor(backgroundKey)); setTag(backgroundKey); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetCell.java index 5ac83b1bd..9761a50fc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetCell.java @@ -30,10 +30,11 @@ import android.widget.ImageView; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.FileLoader; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessageObject; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.tgnet.TLRPC; @@ -96,6 +97,7 @@ public class FeaturedStickerSetCell extends FrameLayout { imageView = new BackupImageView(context); imageView.setAspectFit(true); + imageView.setLayerNum(1); addView(imageView, LayoutHelper.createFrame(48, 48, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 12, 8, LocaleController.isRTL ? 12 : 0, 0)); addButton = new TextView(context) { @@ -140,7 +142,7 @@ public class FeaturedStickerSetCell extends FrameLayout { addButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); addButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); addButton.setBackgroundDrawable(Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(4), Theme.getColor(Theme.key_featuredStickers_addButton), Theme.getColor(Theme.key_featuredStickers_addButtonPressed))); - addButton.setText(LocaleController.getString("Add", R.string.Add).toUpperCase()); + addButton.setText(LocaleController.getString("Add", R.string.Add)); addButton.setPadding(AndroidUtilities.dp(17), 0, AndroidUtilities.dp(17), 0); addView(addButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 28, Gravity.TOP | (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT), LocaleController.isRTL ? 14 : 0, 18, LocaleController.isRTL ? 0 : 14, 0)); @@ -227,19 +229,28 @@ public class FeaturedStickerSetCell extends FrameLayout { valueTextView.setText(LocaleController.formatPluralString("Stickers", set.set.count)); TLRPC.PhotoSize thumb = set.cover != null ? FileLoader.getClosestPhotoSizeWithSize(set.cover.thumbs, 90) : null; + if (thumb != null && thumb.location != null) { - imageView.setImage(ImageLocation.getForDocument(thumb, set.cover), null, "webp", null, set); + if (MessageObject.canAutoplayAnimatedSticker(set.cover)) { + imageView.setImage(ImageLocation.getForDocument(set.cover), "80_80", ImageLocation.getForDocument(thumb, set.cover), null, 0, set); + } else { + imageView.setImage(ImageLocation.getForDocument(thumb, set.cover), null, "webp", null, set); + } } else if (!set.covers.isEmpty()) { TLRPC.Document document = set.covers.get(0); thumb = FileLoader.getClosestPhotoSizeWithSize(document.thumbs, 90); - imageView.setImage(ImageLocation.getForDocument(thumb, document), null, "webp", null, set); + if (MessageObject.canAutoplayAnimatedSticker(document)) { + imageView.setImage(ImageLocation.getForDocument(document), "80_80", ImageLocation.getForDocument(thumb, document), null, 0, set); + } else { + imageView.setImage(ImageLocation.getForDocument(thumb, document), null, "webp", null, set); + } } else { imageView.setImage(null, null, "webp", null, set); } if (sameSet) { boolean wasInstalled = isInstalled; - if (isInstalled = DataQuery.getInstance(currentAccount).isStickerPackInstalled(set.set.id)) { + if (isInstalled = MediaDataController.getInstance(currentAccount).isStickerPackInstalled(set.set.id)) { if (!wasInstalled) { checkImage.setVisibility(VISIBLE); addButton.setClickable(false); @@ -299,7 +310,7 @@ public class FeaturedStickerSetCell extends FrameLayout { } } } else { - if (isInstalled = DataQuery.getInstance(currentAccount).isStickerPackInstalled(set.set.id)) { + if (isInstalled = MediaDataController.getInstance(currentAccount).isStickerPackInstalled(set.set.id)) { addButton.setVisibility(INVISIBLE); addButton.setClickable(false); checkImage.setVisibility(VISIBLE); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetInfoCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetInfoCell.java index 6e6b24855..3c7163943 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetInfoCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetInfoCell.java @@ -23,7 +23,7 @@ import android.widget.FrameLayout; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; @@ -160,12 +160,12 @@ public class FeaturedStickerSetInfoCell extends FrameLayout { isUnread = unread; if (hasOnClick) { addButton.setVisibility(VISIBLE); - if (isInstalled = DataQuery.getInstance(currentAccount).isStickerPackInstalled(stickerSet.set.id)) { + if (isInstalled = MediaDataController.getInstance(currentAccount).isStickerPackInstalled(stickerSet.set.id)) { addButton.setBackgroundDrawable(delDrawable); - addButton.setText(LocaleController.getString("StickersRemove", R.string.StickersRemove).toUpperCase()); + addButton.setText(LocaleController.getString("StickersRemove", R.string.StickersRemove)); } else { addButton.setBackgroundDrawable(addDrawable); - addButton.setText(LocaleController.getString("Add", R.string.Add).toUpperCase()); + addButton.setText(LocaleController.getString("Add", R.string.Add)); } addButton.setPadding(AndroidUtilities.dp(17), 0, AndroidUtilities.dp(17), 0); } else { 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 0c3ec3c61..e7b7c0e19 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCreateUserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCreateUserCell.java @@ -212,6 +212,8 @@ public class GroupCreateUserCell extends FrameLayout { statusTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText)); if (currentChat.participants_count != 0) { 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)) { statusTextView.setText(LocaleController.getString("MegaPrivate", R.string.MegaPrivate)); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/InviteUserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/InviteUserCell.java index f85cdf2a5..8552e6e3a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/InviteUserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/InviteUserCell.java @@ -20,7 +20,6 @@ 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.GroupCreateCheckBox; import org.telegram.ui.Components.LayoutHelper; public class InviteUserCell extends FrameLayout { 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 b5bf2266c..66155cdd9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ManageChatUserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ManageChatUserCell.java @@ -263,6 +263,8 @@ public class ManageChatUserCell extends FrameLayout { statusTextView.setTextColor(statusColor); if (currentChat.participants_count != 0) { 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)) { statusTextView.setText(LocaleController.getString("MegaPrivate", R.string.MegaPrivate)); } else { 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 c7f9dc910..1ff9d296a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java @@ -16,7 +16,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.Emoji; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.UserObject; @@ -102,7 +102,7 @@ public class MentionCell extends LinearLayout { nameTextView.invalidate(); } - public void setEmojiSuggestion(DataQuery.KeywordResult suggestion) { + public void setEmojiSuggestion(MediaDataController.KeywordResult suggestion) { imageView.setVisibility(INVISIBLE); usernameTextView.setVisibility(INVISIBLE); StringBuilder stringBuilder = new StringBuilder(suggestion.emoji.length() + suggestion.keyword.length() + 4); 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 62b2225cd..7765cf5d2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java @@ -399,7 +399,9 @@ public class ProfileSearchCell extends BaseCell { statusString = LocaleController.getString("ChannelPublic", R.string.ChannelPublic).toLowerCase(); } } else { - if (TextUtils.isEmpty(chat.username)) { + if (chat.has_geo) { + statusString = LocaleController.getString("MegaLocation", R.string.MegaLocation); + } else if (TextUtils.isEmpty(chat.username)) { statusString = LocaleController.getString("MegaPrivate", R.string.MegaPrivate).toLowerCase(); } else { statusString = LocaleController.getString("MegaPublic", R.string.MegaPublic).toLowerCase(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/RadioCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/RadioCell.java index 39da2953c..2bc74a456 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/RadioCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/RadioCell.java @@ -93,6 +93,7 @@ public class RadioCell extends FrameLayout { } public void setEnabled(boolean value, ArrayList animators) { + super.setEnabled(value); if (animators != null) { animators.add(ObjectAnimator.ofFloat(textView, "alpha", value ? 1.0f : 0.5f)); animators.add(ObjectAnimator.ofFloat(radioButton, "alpha", value ? 1.0f : 0.5f)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharingLiveLocationCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharingLiveLocationCell.java index 237e998d4..4383efb4d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharingLiveLocationCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharingLiveLocationCell.java @@ -29,6 +29,7 @@ import org.telegram.messenger.LocationController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; import org.telegram.messenger.UserObject; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; @@ -63,7 +64,7 @@ public class SharingLiveLocationCell extends FrameLayout { } }; - public SharingLiveLocationCell(Context context, boolean distance) { + public SharingLiveLocationCell(Context context, boolean distance, int padding) { super(context); avatarImageView = new BackupImageView(context); @@ -80,17 +81,17 @@ public class SharingLiveLocationCell extends FrameLayout { if (distance) { addView(avatarImageView, LayoutHelper.createFrame(40, 40, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 0 : 17, 13, LocaleController.isRTL ? 17 : 0, 0)); - addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 54 : 73, 12, LocaleController.isRTL ? 73 : 54, 0)); + addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? padding : 73, 12, LocaleController.isRTL ? 73 : padding, 0)); distanceTextView = new SimpleTextView(context); distanceTextView.setTextSize(14); distanceTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); distanceTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - addView(distanceTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 54 : 73, 37, LocaleController.isRTL ? 73 : 54, 0)); + addView(distanceTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? padding : 73, 37, LocaleController.isRTL ? 73 : padding, 0)); } else { addView(avatarImageView, LayoutHelper.createFrame(40, 40, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 0 : 17, 7, LocaleController.isRTL ? 17 : 0, 0)); - addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 54 : 74, 17, LocaleController.isRTL ? 74 : 54, 0)); + addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? padding : 74, 17, LocaleController.isRTL ? 74 : padding, 0)); } setWillNotDraw(false); @@ -113,6 +114,34 @@ public class SharingLiveLocationCell extends FrameLayout { AndroidUtilities.runOnUIThread(invalidateRunnable); } + public void setDialog(long dialogId, TLRPC.TL_channelLocation chatLocation) { + currentAccount = UserConfig.selectedAccount; + String address = chatLocation.address; + String name = ""; + avatarDrawable = null; + int lowerId = (int) dialogId; + if (lowerId > 0) { + TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(lowerId); + if (user != null) { + avatarDrawable = new AvatarDrawable(user); + name = UserObject.getUserName(user); + avatarImageView.setImage(ImageLocation.getForUser(user, false), "50_50", avatarDrawable, user); + } + } else { + TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-lowerId); + if (chat != null) { + avatarDrawable = new AvatarDrawable(chat); + name = chat.title; + avatarImageView.setImage(ImageLocation.getForChat(chat, false), "50_50", avatarDrawable, chat); + } + } + nameTextView.setText(name); + + location.setLatitude(chatLocation.geo_point.lat); + location.setLongitude(chatLocation.geo_point._long); + distanceTextView.setText(address); + } + public void setDialog(MessageObject messageObject, Location userLocation) { int fromId = messageObject.messageOwner.from_id; if (messageObject.isForwarded()) { @@ -165,17 +194,9 @@ public class SharingLiveLocationCell extends FrameLayout { if (userLocation != null) { float distance = location.distanceTo(userLocation); if (address != null) { - if (distance < 1000) { - distanceTextView.setText(String.format("%s - %d %s", address, (int) (distance), LocaleController.getString("MetersAway", R.string.MetersAway))); - } else { - distanceTextView.setText(String.format("%s - %.2f %s", address, distance / 1000.0f, LocaleController.getString("KMetersAway", R.string.KMetersAway))); - } + distanceTextView.setText(String.format("%s - %s", address, LocaleController.formatDistance(distance))); } else { - if (distance < 1000) { - distanceTextView.setText(String.format("%d %s", (int) (distance), LocaleController.getString("MetersAway", R.string.MetersAway))); - } else { - distanceTextView.setText(String.format("%.2f %s", distance / 1000.0f, LocaleController.getString("KMetersAway", R.string.KMetersAway))); - } + distanceTextView.setText(LocaleController.formatDistance(distance)); } } else { if (address != null) { @@ -211,12 +232,7 @@ public class SharingLiveLocationCell extends FrameLayout { String time = LocaleController.formatLocationUpdateDate(info.object.edit_date != 0 ? info.object.edit_date : info.object.date); if (userLocation != null) { - float distance = location.distanceTo(userLocation); - if (distance < 1000) { - distanceTextView.setText(String.format("%s - %d %s", time, (int) (distance), LocaleController.getString("MetersAway", R.string.MetersAway))); - } else { - distanceTextView.setText(String.format("%s - %.2f %s", time, distance / 1000.0f, LocaleController.getString("KMetersAway", R.string.KMetersAway))); - } + distanceTextView.setText(String.format("%s - %s", time, LocaleController.formatDistance(location.distanceTo(userLocation)))); } else { distanceTextView.setText(time); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerCell.java index ba78a8521..92f0e70b5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerCell.java @@ -23,6 +23,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLoader; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessageObject; import org.telegram.messenger.R; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; @@ -33,6 +34,7 @@ public class StickerCell extends FrameLayout { private BackupImageView imageView; private TLRPC.Document sticker; + private Object parentObject; private long lastUpdateTime; private boolean scaled; private float scale; @@ -45,6 +47,7 @@ public class StickerCell extends FrameLayout { imageView = new BackupImageView(context); imageView.setAspectFit(true); + imageView.setLayerNum(1); addView(imageView, LayoutHelper.createFrame(66, 66, Gravity.CENTER_HORIZONTAL, 0, 5, 0, 0)); setFocusable(true); } @@ -71,10 +74,19 @@ public class StickerCell extends FrameLayout { return clearsInputField; } - public void setSticker(TLRPC.Document document, Object parentObject, int side) { + public void setSticker(TLRPC.Document document, Object parent, int side) { + parentObject = parent; if (document != null) { TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(document.thumbs, 90); - imageView.setImage(ImageLocation.getForDocument(thumb, document), null, "webp", null, parentObject); + if (MessageObject.canAutoplayAnimatedSticker(document)) { + if (thumb != null) { + imageView.setImage(ImageLocation.getForDocument(document), "80_80", ImageLocation.getForDocument(thumb, document), null, 0, parentObject); + } else { + imageView.setImage(ImageLocation.getForDocument(document), "80_80", null, null, parentObject); + } + } else { + imageView.setImage(ImageLocation.getForDocument(thumb, document), null, "webp", null, parentObject); + } } sticker = document; if (side == -1) { @@ -101,6 +113,10 @@ public class StickerCell extends FrameLayout { return sticker; } + public Object getParentObject() { + return parentObject; + } + public void setScaled(boolean value) { scaled = value; lastUpdateTime = System.currentTimeMillis(); 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 3937c1030..cae26b0a4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerEmojiCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerEmojiCell.java @@ -19,11 +19,12 @@ import android.widget.FrameLayout; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.Emoji; import org.telegram.messenger.FileLoader; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessageObject; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.tgnet.TLRPC; @@ -51,6 +52,7 @@ public class StickerEmojiCell extends FrameLayout { imageView = new BackupImageView(context); imageView.setAspectFit(true); + imageView.setLayerNum(1); addView(imageView, LayoutHelper.createFrame(66, 66, Gravity.CENTER)); emojiTextView = new TextView(context); @@ -84,10 +86,18 @@ public class StickerEmojiCell extends FrameLayout { sticker = document; parentObject = parent; TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(document.thumbs, 90); - if (thumb != null) { - imageView.setImage(ImageLocation.getForDocument(thumb, document), null, "webp", null, parentObject); + if (MessageObject.canAutoplayAnimatedSticker(document)) { + if (thumb != null) { + imageView.setImage(ImageLocation.getForDocument(document), "80_80", ImageLocation.getForDocument(thumb, document), null, 0, parentObject); + } else { + imageView.setImage(ImageLocation.getForDocument(document), "80_80", null, null, parentObject); + } } else { - imageView.setImage(ImageLocation.getForDocument(document), null, "webp", null, parentObject); + if (thumb != null) { + imageView.setImage(ImageLocation.getForDocument(thumb, document), null, "webp", null, parentObject); + } else { + imageView.setImage(ImageLocation.getForDocument(document), null, "webp", null, parentObject); + } } if (emoji != null) { @@ -106,7 +116,7 @@ public class StickerEmojiCell extends FrameLayout { } } if (!set) { - emojiTextView.setText(Emoji.replaceEmoji(DataQuery.getInstance(currentAccount).getEmojiForSticker(sticker.id), emojiTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(16), false)); + emojiTextView.setText(Emoji.replaceEmoji(MediaDataController.getInstance(currentAccount).getEmojiForSticker(sticker.id), emojiTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(16), false)); } emojiTextView.setVisibility(VISIBLE); } else { @@ -139,6 +149,10 @@ public class StickerEmojiCell extends FrameLayout { return imageView.getImageReceiver().getBitmap() != null; } + public BackupImageView getImageView() { + return imageView; + } + @Override public void invalidate() { emojiTextView.invalidate(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetCell.java index d16aea5bf..b7d389e70 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetCell.java @@ -26,6 +26,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLoader; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessageObject; import org.telegram.messenger.R; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; @@ -70,6 +71,7 @@ public class StickerSetCell extends FrameLayout { imageView = new BackupImageView(context); imageView.setAspectFit(true); + imageView.setLayerNum(1); addView(imageView, LayoutHelper.createFrame(48, 48, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 12, 8, LocaleController.isRTL ? 12 : 0, 0)); if (option == 2) { @@ -148,7 +150,11 @@ public class StickerSetCell extends FrameLayout { valueTextView.setText(LocaleController.formatPluralString("Stickers", documents.size())); TLRPC.Document document = documents.get(0); TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(document.thumbs, 90); - imageView.setImage(ImageLocation.getForDocument(thumb, document), null, "webp", null, set); + if (MessageObject.canAutoplayAnimatedSticker(document)) { + imageView.setImage(ImageLocation.getForDocument(document), "80_80", ImageLocation.getForDocument(thumb, document), null, 0, set); + } else { + imageView.setImage(ImageLocation.getForDocument(thumb, document), null, "webp", null, set); + } } else { valueTextView.setText(LocaleController.formatPluralString("Stickers", 0)); } 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 3f7d97d4a..025821925 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCell.java @@ -28,11 +28,18 @@ public class TextCell extends FrameLayout { private SimpleTextView valueTextView; private ImageView imageView; private ImageView valueImageView; + private int leftPadding; private boolean needDivider; public TextCell(Context context) { + this(context, 23); + } + + public TextCell(Context context, int left) { super(context); + leftPadding = left; + textView = new SimpleTextView(context); textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(16); @@ -74,8 +81,8 @@ public class TextCell extends FrameLayout { int width = MeasureSpec.getSize(widthMeasureSpec); int height = AndroidUtilities.dp(48); - valueTextView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(23), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); - textView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(71 + 24) - valueTextView.getTextWidth(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); + 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)); if (imageView.getVisibility() == VISIBLE) { imageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); } @@ -91,14 +98,14 @@ public class TextCell extends FrameLayout { int width = right - left; int viewTop = (height - valueTextView.getTextHeight()) / 2; - int viewLeft = LocaleController.isRTL ? AndroidUtilities.dp(23) : 0; + int viewLeft = LocaleController.isRTL ? AndroidUtilities.dp(leftPadding) : 0; valueTextView.layout(viewLeft, viewTop, viewLeft + valueTextView.getMeasuredWidth(), viewTop + valueTextView.getMeasuredHeight()); viewTop = (height - textView.getTextHeight()) / 2; if (LocaleController.isRTL) { - viewLeft = getMeasuredWidth() - textView.getMeasuredWidth() - AndroidUtilities.dp(imageView.getVisibility() == VISIBLE ? 71 : 23); + viewLeft = getMeasuredWidth() - textView.getMeasuredWidth() - AndroidUtilities.dp(imageView.getVisibility() == VISIBLE ? 71 : leftPadding); } else { - viewLeft = AndroidUtilities.dp(imageView.getVisibility() == VISIBLE ? 71 : 23); + viewLeft = AndroidUtilities.dp(imageView.getVisibility() == VISIBLE ? 71 : leftPadding); } textView.layout(viewLeft, viewTop, viewLeft + textView.getMeasuredWidth(), viewTop + textView.getMeasuredHeight()); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ThemeCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ThemeCell.java index b9d388416..3ddf4ce9e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ThemeCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ThemeCell.java @@ -212,8 +212,6 @@ public class ThemeCell extends FrameLayout { @Override protected void onDraw(Canvas canvas) { if (needDivider) { - int color = Theme.dividerPaint.getColor(); - FileLog.d(String.format("set color %d %d %d %d", Color.alpha(color), Color.red(color), Color.green(color), Color.blue(color))); canvas.drawLine(LocaleController.isRTL ? 0 : AndroidUtilities.dp(20), getMeasuredHeight() - 1, getMeasuredWidth() - (LocaleController.isRTL ? AndroidUtilities.dp(20) : 0), getMeasuredHeight() - 1, Theme.dividerPaint); } int x = AndroidUtilities.dp(16 + 15); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java index 6620c29a6..7443ab592 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java @@ -48,6 +48,7 @@ public class UserCell extends FrameLayout { private CheckBox checkBox; private CheckBoxSquare checkBoxBig; private TextView adminTextView; + private TextView addButton; private AvatarDrawable avatarDrawable; private TLObject currentObject; @@ -70,8 +71,28 @@ public class UserCell extends FrameLayout { private boolean needDivider; public UserCell(Context context, int padding, int checkbox, boolean admin) { + this(context, padding, checkbox, admin, false); + } + + public UserCell(Context context, int padding, int checkbox, boolean admin, boolean needAddButton) { super(context); + int additionalPadding; + if (needAddButton) { + addButton = new TextView(context); + addButton.setGravity(Gravity.CENTER); + addButton.setTextColor(Theme.getColor(Theme.key_featuredStickers_buttonText)); + addButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + addButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + addButton.setBackgroundDrawable(Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(4), Theme.getColor(Theme.key_featuredStickers_addButton), Theme.getColor(Theme.key_featuredStickers_addButtonPressed))); + addButton.setText(LocaleController.getString("Add", R.string.Add)); + addButton.setPadding(AndroidUtilities.dp(17), 0, AndroidUtilities.dp(17), 0); + addView(addButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 28, Gravity.TOP | (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT), LocaleController.isRTL ? 14 : 0, 15, LocaleController.isRTL ? 0 : 14, 0)); + additionalPadding = (int) Math.ceil((addButton.getPaint().measureText(addButton.getText().toString()) + AndroidUtilities.dp(34 + 14)) / AndroidUtilities.density); + } else { + additionalPadding = 0; + } + statusColor = Theme.getColor(Theme.key_windowBackgroundWhiteGrayText); statusOnlineColor = Theme.getColor(Theme.key_windowBackgroundWhiteBlueText); @@ -86,12 +107,12 @@ public class UserCell extends FrameLayout { nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); nameTextView.setTextSize(16); nameTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); - addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 28 + (checkbox == 2 ? 18 : 0) : (64 + padding), 10, LocaleController.isRTL ? (64 + padding) : 28 + (checkbox == 2 ? 18 : 0), 0)); + addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 28 + (checkbox == 2 ? 18 : 0) + additionalPadding : (64 + padding), 10, LocaleController.isRTL ? (64 + padding) : 28 + (checkbox == 2 ? 18 : 0) + additionalPadding, 0)); statusTextView = new SimpleTextView(context); statusTextView.setTextSize(15); statusTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); - addView(statusTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 28 : (64 + padding), 32, LocaleController.isRTL ? (64 + padding) : 28, 0)); + addView(statusTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 28 + additionalPadding : (64 + padding), 32, LocaleController.isRTL ? (64 + padding) : 28 + additionalPadding, 0)); imageView = new ImageView(context); imageView.setScaleType(ImageView.ScaleType.CENTER); @@ -115,6 +136,7 @@ public class UserCell extends FrameLayout { adminTextView.setTextColor(Theme.getColor(Theme.key_profile_creatorIcon)); addView(adminTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP, LocaleController.isRTL ? 23 : 0, 10, LocaleController.isRTL ? 0 : 23, 0)); } + setFocusable(true); } @@ -139,6 +161,13 @@ public class UserCell extends FrameLayout { } } + public void setAddButtonVisible(boolean value) { + if (addButton == null) { + return; + } + addButton.setVisibility(value ? VISIBLE : GONE); + } + public void setIsAdmin(int value) { if (adminTextView == null) { return; @@ -332,7 +361,7 @@ public class UserCell extends FrameLayout { if (mask != 0) { boolean continueUpdate = false; if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0) { - if (lastAvatar != null && photo == null || lastAvatar == null && photo != null && lastAvatar != null && photo != null && (lastAvatar.volume_id != photo.volume_id || lastAvatar.local_id != photo.local_id)) { + if (lastAvatar != null && photo == null || lastAvatar == null && photo != null || lastAvatar != null && photo != null && (lastAvatar.volume_id != photo.volume_id || lastAvatar.local_id != photo.local_id)) { continueUpdate = true; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells2/UserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell2.java similarity index 97% rename from TMessagesProj/src/main/java/org/telegram/ui/Cells2/UserCell.java rename to TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell2.java index 74c271c07..e6a1a6ef6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells2/UserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell2.java @@ -6,7 +6,7 @@ * Copyright Nikolai Kudashov, 2013-2018. */ -package org.telegram.ui.Cells2; +package org.telegram.ui.Cells; import android.content.Context; import android.graphics.PorterDuff; @@ -36,7 +36,7 @@ import org.telegram.ui.Components.CheckBox; import org.telegram.ui.Components.CheckBoxSquare; import org.telegram.ui.Components.LayoutHelper; -public class UserCell extends FrameLayout { +public class UserCell2 extends FrameLayout { private BackupImageView avatarImageView; private SimpleTextView nameTextView; @@ -62,7 +62,7 @@ public class UserCell extends FrameLayout { private int statusColor; private int statusOnlineColor; - public UserCell(Context context, int padding, int checkbox) { + public UserCell2(Context context, int padding, int checkbox) { super(context); statusColor = Theme.getColor(Theme.key_windowBackgroundWhiteGrayText); @@ -185,7 +185,7 @@ public class UserCell extends FrameLayout { if (mask != 0) { boolean continueUpdate = false; if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0) { - if (lastAvatar != null && photo == null || lastAvatar == null && photo != null && lastAvatar != null && photo != null && (lastAvatar.volume_id != photo.volume_id || lastAvatar.local_id != photo.local_id)) { + if (lastAvatar != null && photo == null || lastAvatar == null && photo != null || lastAvatar != null && photo != null && (lastAvatar.volume_id != photo.volume_id || lastAvatar.local_id != photo.local_id)) { continueUpdate = true; } } @@ -274,6 +274,8 @@ public class UserCell extends FrameLayout { } else { if (currentChat.participants_count != 0) { 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)) { statusTextView.setText(LocaleController.getString("MegaPrivate", R.string.MegaPrivate)); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java index 65c31d7cc..daea9354b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java @@ -194,13 +194,10 @@ public class ChangeNameActivity extends BaseFragment { @Override public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - if (firstNameField != null) { - firstNameField.requestFocus(); - AndroidUtilities.showKeyboard(firstNameField); - } + AndroidUtilities.runOnUIThread(() -> { + if (firstNameField != null) { + firstNameField.requestFocus(); + AndroidUtilities.showKeyboard(firstNameField); } }, 100); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneHelpActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneHelpActivity.java deleted file mode 100644 index fdc6ca06a..000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneHelpActivity.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; -import android.widget.ScrollView; -import android.widget.TextView; - -import org.telegram.PhoneFormat.PhoneFormat; -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.LocaleController; -import org.telegram.messenger.FileLog; -import org.telegram.messenger.R; -import org.telegram.tgnet.TLRPC; -import org.telegram.messenger.UserConfig; -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.ActionBar.ThemeDescription; -import org.telegram.ui.Components.LayoutHelper; - -public class ChangePhoneHelpActivity extends BaseFragment { - - private TextView textView1; - private TextView textView2; - private ImageView imageView; - - @Override - public View createView(Context context) { - actionBar.setBackButtonImage(R.drawable.ic_ab_back); - actionBar.setAllowOverlayTitle(true); - - TLRPC.User user = UserConfig.getInstance(currentAccount).getCurrentUser(); - String value; - if (user != null && user.phone != null && user.phone.length() != 0) { - value = PhoneFormat.getInstance().format("+" + user.phone); - } else { - value = LocaleController.getString("NumberUnknown", R.string.NumberUnknown); - } - - actionBar.setTitle(value); - actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { - @Override - public void onItemClick(int id) { - if (id == -1) { - finishFragment(); - } - } - }); - - fragmentView = new RelativeLayout(context); - fragmentView.setOnTouchListener((v, event) -> true); - - RelativeLayout relativeLayout = (RelativeLayout) fragmentView; - - ScrollView scrollView = new ScrollView(context); - relativeLayout.addView(scrollView); - RelativeLayout.LayoutParams layoutParams3 = (RelativeLayout.LayoutParams) scrollView.getLayoutParams(); - layoutParams3.width = LayoutHelper.MATCH_PARENT; - layoutParams3.height = LayoutHelper.WRAP_CONTENT; - layoutParams3.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE); - scrollView.setLayoutParams(layoutParams3); - - LinearLayout linearLayout = new LinearLayout(context); - linearLayout.setOrientation(LinearLayout.VERTICAL); - linearLayout.setPadding(0, AndroidUtilities.dp(20), 0, AndroidUtilities.dp(20)); - scrollView.addView(linearLayout); - ScrollView.LayoutParams layoutParams = (ScrollView.LayoutParams) linearLayout.getLayoutParams(); - layoutParams.width = ScrollView.LayoutParams.MATCH_PARENT; - layoutParams.height = ScrollView.LayoutParams.WRAP_CONTENT; - linearLayout.setLayoutParams(layoutParams); - - imageView = new ImageView(context); - imageView.setImageResource(R.drawable.phone_change); - imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_changephoneinfo_image), PorterDuff.Mode.MULTIPLY)); - linearLayout.addView(imageView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL)); - - textView1 = new TextView(context); - textView1.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - textView1.setGravity(Gravity.CENTER_HORIZONTAL); - textView1.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); - - try { - textView1.setText(AndroidUtilities.replaceTags(LocaleController.getString("PhoneNumberHelp", R.string.PhoneNumberHelp))); - } catch (Exception e) { - FileLog.e(e); - textView1.setText(LocaleController.getString("PhoneNumberHelp", R.string.PhoneNumberHelp)); - } - linearLayout.addView(textView1, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 20, 56, 20, 0)); - - textView2 = new TextView(context); - textView2.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - textView2.setGravity(Gravity.CENTER_HORIZONTAL); - textView2.setTextColor(Theme.getColor(Theme.key_changephoneinfo_changeText)); - textView2.setText(LocaleController.getString("PhoneNumberChange", R.string.PhoneNumberChange)); - textView2.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - textView2.setPadding(0, AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10)); - linearLayout.addView(textView2, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 20, 46, 20, 0)); - - textView2.setOnClickListener(v -> { - if (getParentActivity() == null) { - return; - } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setMessage(LocaleController.getString("PhoneNumberAlert", R.string.PhoneNumberAlert)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> presentFragment(new ChangePhoneActivity(), true)); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - showDialog(builder.create()); - }); - - return fragmentView; - } - - @Override - public ThemeDescription[] getThemeDescriptions() { - return new ThemeDescription[]{ - new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), - - new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), - new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), - new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), - new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), - - new ThemeDescription(textView1, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), - new ThemeDescription(textView2, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_changephoneinfo_changeText), - new ThemeDescription(imageView, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_changephoneinfo_image), - }; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java index 5545c7d0c..3a66c4f84 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java @@ -62,7 +62,7 @@ import org.telegram.messenger.BuildConfig; import org.telegram.messenger.BuildVars; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.ImageReceiver; @@ -336,6 +336,11 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio if (messagesDict.indexOfKey(event.id) >= 0) { continue; } + if (event.action instanceof TLRPC.TL_channelAdminLogEventActionParticipantToggleAdmin) { + if (event.action.prev_participant instanceof TLRPC.TL_channelParticipantCreator && !(event.action.new_participant instanceof TLRPC.TL_channelParticipantCreator)) { + continue; + } + } minEventId = Math.min(minEventId, event.id); added = true; MessageObject messageObject = new MessageObject(currentAccount, event, messages, messagesByDays, currentChat, mid); @@ -1454,14 +1459,14 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio } else { if (messageObject.isVoice()) { return 2; - } else if (messageObject.isSticker()) { + } else if (messageObject.isSticker() || messageObject.isAnimatedSticker()) { TLRPC.InputStickerSet inputStickerSet = messageObject.getInputStickerSet(); if (inputStickerSet instanceof TLRPC.TL_inputStickerSetID) { - if (!DataQuery.getInstance(currentAccount).isStickerPackInstalled(inputStickerSet.id)) { + if (!MediaDataController.getInstance(currentAccount).isStickerPackInstalled(inputStickerSet.id)) { return 7; } } else if (inputStickerSet instanceof TLRPC.TL_inputStickerSetShortName) { - if (!DataQuery.getInstance(currentAccount).isStickerPackInstalled(inputStickerSet.short_name)) { + if (!MediaDataController.getInstance(currentAccount).isStickerPackInstalled(inputStickerSet.short_name)) { return 7; } } @@ -1730,18 +1735,18 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio @Override public void onTransitionAnimationStart(boolean isOpen, boolean backward) { - NotificationCenter.getInstance(currentAccount).setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.chatInfoDidLoad, NotificationCenter.dialogsNeedReload, - NotificationCenter.closeChats, NotificationCenter.messagesDidLoad, NotificationCenter.botKeyboardDidLoad/*, NotificationCenter.botInfoDidLoad*/}); - NotificationCenter.getInstance(currentAccount).setAnimationInProgress(true); if (isOpen) { + NotificationCenter.getInstance(currentAccount).setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.chatInfoDidLoad, NotificationCenter.dialogsNeedReload, + NotificationCenter.closeChats, NotificationCenter.messagesDidLoad, NotificationCenter.botKeyboardDidLoad/*, NotificationCenter.botInfoDidLoad*/}); + NotificationCenter.getInstance(currentAccount).setAnimationInProgress(true); openAnimationEnded = false; } } @Override public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { - NotificationCenter.getInstance(currentAccount).setAnimationInProgress(false); if (isOpen) { + NotificationCenter.getInstance(currentAccount).setAnimationInProgress(false); openAnimationEnded = true; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java index 7f0300dbb..c345a0d04 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java @@ -148,7 +148,6 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC } } - @SuppressWarnings("unchecked") @Override public boolean onFragmentCreate() { NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatDidCreated); @@ -242,7 +241,7 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC progressDialog.show(); return; } - final int reqId = MessagesController.getInstance(currentAccount).createChat(nameTextView.getText().toString(), new ArrayList<>(), descriptionTextView.getText().toString(), ChatObject.CHAT_TYPE_CHANNEL, ChannelCreateActivity.this); + final int reqId = MessagesController.getInstance(currentAccount).createChat(nameTextView.getText().toString(), new ArrayList<>(), descriptionTextView.getText().toString(), ChatObject.CHAT_TYPE_CHANNEL, null, null, ChannelCreateActivity.this); progressDialog = new AlertDialog(getParentActivity(), 3); progressDialog.setOnCancelListener(dialog -> { ConnectionsManager.getInstance(currentAccount).cancelRequest(reqId, true); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelIntroActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelIntroActivity.java deleted file mode 100644 index b77e72cd3..000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelIntroActivity.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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; - -import android.content.Context; -import android.os.Bundle; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.LocaleController; -import org.telegram.messenger.R; -import org.telegram.ui.ActionBar.ActionBar; -import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.ActionBar.Theme; -import org.telegram.ui.ActionBar.ThemeDescription; - -public class ChannelIntroActivity extends BaseFragment { - - private ImageView imageView; - private TextView createChannelText; - private TextView whatIsChannelText; - private TextView descriptionText; - - @Override - public View createView(Context context) { - actionBar.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); - actionBar.setBackButtonImage(R.drawable.ic_ab_back); - actionBar.setItemsColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2), false); - actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_actionBarWhiteSelector), false); - actionBar.setCastShadows(false); - if (!AndroidUtilities.isTablet()) { - actionBar.showActionModeTop(); - } - actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { - @Override - public void onItemClick(int id) { - if (id == -1) { - finishFragment(); - } - } - }); - - fragmentView = new ViewGroup(context) { - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - - if (width > height) { - imageView.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.45f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) (height * 0.78f), MeasureSpec.EXACTLY)); - whatIsChannelText.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.6f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); - descriptionText.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.5f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); - createChannelText.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.6f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(24), MeasureSpec.EXACTLY)); - } else { - imageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) (height * 0.44f), MeasureSpec.EXACTLY)); - whatIsChannelText.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); - descriptionText.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.9f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); - createChannelText.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(24), MeasureSpec.EXACTLY)); - } - - setMeasuredDimension(width, height); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - int width = r - l; - int height = b - t; - - if (r > b) { - int y = (int) (height * 0.05f); - imageView.layout(0, y, imageView.getMeasuredWidth(), y + imageView.getMeasuredHeight()); - int x = (int) (width * 0.4f); - y = (int) (height * 0.14f); - whatIsChannelText.layout(x, y, x + whatIsChannelText.getMeasuredWidth(), y + whatIsChannelText.getMeasuredHeight()); - y = (int) (height * 0.61f); - createChannelText.layout(x, y, x + createChannelText.getMeasuredWidth(), y + createChannelText.getMeasuredHeight()); - x = (int) (width * 0.45f); - y = (int) (height * 0.31f); - descriptionText.layout(x, y, x + descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight()); - } else { - int y = (int) (height * 0.05f); - imageView.layout(0, y, imageView.getMeasuredWidth(), y + imageView.getMeasuredHeight()); - y = (int) (height * 0.59f); - whatIsChannelText.layout(0, y, whatIsChannelText.getMeasuredWidth(), y + whatIsChannelText.getMeasuredHeight()); - y = (int) (height * 0.68f); - int x = (int) (width * 0.05f); - descriptionText.layout(x, y, x + descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight()); - y = (int) (height * 0.86f); - createChannelText.layout(0, y, createChannelText.getMeasuredWidth(), y + createChannelText.getMeasuredHeight()); - } - } - }; - fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); - ViewGroup viewGroup = (ViewGroup) fragmentView; - viewGroup.setOnTouchListener((v, event) -> true); - - imageView = new ImageView(context); - imageView.setImageResource(R.drawable.channelintro); - imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); - viewGroup.addView(imageView); - - whatIsChannelText = new TextView(context); - whatIsChannelText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); - whatIsChannelText.setGravity(Gravity.CENTER_HORIZONTAL); - whatIsChannelText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 24); - whatIsChannelText.setText(LocaleController.getString("ChannelAlertTitle", R.string.ChannelAlertTitle)); - viewGroup.addView(whatIsChannelText); - - descriptionText = new TextView(context); - descriptionText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); - descriptionText.setGravity(Gravity.CENTER_HORIZONTAL); - descriptionText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - descriptionText.setText(LocaleController.getString("ChannelAlertText", R.string.ChannelAlertText)); - viewGroup.addView(descriptionText); - - createChannelText = new TextView(context); - createChannelText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText5)); - createChannelText.setGravity(Gravity.CENTER); - createChannelText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - createChannelText.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - createChannelText.setText(LocaleController.getString("ChannelAlertCreate", R.string.ChannelAlertCreate)); - viewGroup.addView(createChannelText); - createChannelText.setOnClickListener(v -> { - Bundle args = new Bundle(); - args.putInt("step", 0); - presentFragment(new ChannelCreateActivity(args), true); - }); - - return fragmentView; - } - - @Override - public ThemeDescription[] getThemeDescriptions() { - return new ThemeDescription[]{ - new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), - - new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), - new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), - new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarWhiteSelector), - - new ThemeDescription(whatIsChannelText, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), - new ThemeDescription(descriptionText, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), - new ThemeDescription(createChannelText, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText5), - }; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java index 0e5bb04cb..6cc519578 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java @@ -96,14 +96,12 @@ import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.BuildConfig; import org.telegram.messenger.BuildVars; import org.telegram.messenger.ChatObject; -import org.telegram.messenger.DataQuery; -import org.telegram.messenger.DownloadController; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.Emoji; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; import org.telegram.messenger.MessagesStorage; -import org.telegram.messenger.NotificationsController; import org.telegram.messenger.SecretChatHelper; import org.telegram.messenger.SendMessagesHelper; import org.telegram.messenger.SharedConfig; @@ -123,7 +121,6 @@ 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; import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.ActionBarPopupWindow; import org.telegram.ui.ActionBar.AlertDialog; @@ -177,6 +174,7 @@ import org.telegram.ui.Components.Size; import org.telegram.ui.Components.SizeNotifierFrameLayout; import org.telegram.ui.Components.StickersAlert; import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.TextStyleSpan; import org.telegram.ui.Components.TypefaceSpan; import org.telegram.ui.Components.URLSpanBotCommand; import org.telegram.ui.Components.URLSpanMono; @@ -203,7 +201,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not protected TLRPC.Chat currentChat; protected TLRPC.User currentUser; protected TLRPC.EncryptedChat currentEncryptedChat; - private boolean userBlocked = false; + private boolean userBlocked; private ArrayList chatMessageCellsCache = new ArrayList<>(); @@ -264,11 +262,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private ExtendedGridLayoutManager mentionGridLayoutManager; private AnimatorSet mentionListAnimation; private ChatAttachAlert chatAttachAlert; - private LinearLayout reportSpamView; + private FrameLayout topChatPanelView; private AnimatorSet reportSpamViewAnimator; private TextView addToContactsButton; private TextView reportSpamButton; - private FrameLayout reportSpamContainer; private ImageView closeReportSpam; private FragmentContextView fragmentContextView; private View replyLineView; @@ -356,6 +353,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private boolean allowContextBotPanelSecond = true; private AnimatorSet runningAnimation; + private MessageObject selectedObjectToEditCaption; private MessageObject selectedObject; private MessageObject.GroupedMessages selectedObjectGroup; private ArrayList forwardingMessages; @@ -428,6 +426,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private int last_message_id = 0; private long mergeDialogId; + private boolean showScrollToMessageError; private int startLoadFromMessageId; private int startLoadFromMessageOffset = Integer.MAX_VALUE; private boolean needSelectFromMessageId; @@ -601,7 +600,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (view instanceof ChatActionCell && currentChat != null) { object.dialogId = -currentChat.id; } - if (pinnedMessageView != null && pinnedMessageView.getTag() == null || reportSpamView != null && reportSpamView.getTag() == null) { + if (pinnedMessageView != null && pinnedMessageView.getTag() == null || topChatPanelView != null && topChatPanelView.getTag() == null) { object.clipTopAddition = AndroidUtilities.dp(48); } object.clipTopAddition += chatListViewClipTop; @@ -690,6 +689,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private final static int text_mono = 52; private final static int text_link = 53; private final static int text_regular = 54; + private final static int text_strike = 55; + private final static int text_underline = 56; private final static int search = 40; @@ -754,10 +755,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not scrollToTopOnResume = arguments.getBoolean("scrollToTopOnResume", false); if (chatId != 0) { - currentChat = MessagesController.getInstance(currentAccount).getChat(chatId); + currentChat = getMessagesController().getChat(chatId); if (currentChat == null) { final CountDownLatch countDownLatch = new CountDownLatch(1); - final MessagesStorage messagesStorage = MessagesStorage.getInstance(currentAccount); + final MessagesStorage messagesStorage = getMessagesStorage(); messagesStorage.getStorageQueue().postRunnable(() -> { currentChat = messagesStorage.getChat(chatId); countDownLatch.countDown(); @@ -768,7 +769,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not FileLog.e(e); } if (currentChat != null) { - MessagesController.getInstance(currentAccount).putChat(currentChat, true); + getMessagesController().putChat(currentChat, true); } else { return false; } @@ -780,12 +781,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not dialog_id = AndroidUtilities.makeBroadcastId(chatId); } if (ChatObject.isChannel(currentChat)) { - MessagesController.getInstance(currentAccount).startShortPoll(currentChat, false); + getMessagesController().startShortPoll(currentChat, false); } } else if (userId != 0) { - currentUser = MessagesController.getInstance(currentAccount).getUser(userId); + currentUser = getMessagesController().getUser(userId); if (currentUser == null) { - final MessagesStorage messagesStorage = MessagesStorage.getInstance(currentAccount); + final MessagesStorage messagesStorage = getMessagesStorage(); final CountDownLatch countDownLatch = new CountDownLatch(1); messagesStorage.getStorageQueue().postRunnable(() -> { currentUser = messagesStorage.getUser(userId); @@ -797,7 +798,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not FileLog.e(e); } if (currentUser != null) { - MessagesController.getInstance(currentAccount).putUser(currentUser, true); + getMessagesController().putUser(currentUser, true); } else { return false; } @@ -805,11 +806,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not dialog_id = userId; botUser = arguments.getString("botUser"); if (inlineQuery != null) { - MessagesController.getInstance(currentAccount).sendBotStart(currentUser, inlineQuery); + getMessagesController().sendBotStart(currentUser, inlineQuery); } } else if (encId != 0) { - currentEncryptedChat = MessagesController.getInstance(currentAccount).getEncryptedChat(encId); - final MessagesStorage messagesStorage = MessagesStorage.getInstance(currentAccount); + currentEncryptedChat = getMessagesController().getEncryptedChat(encId); + final MessagesStorage messagesStorage = getMessagesStorage(); if (currentEncryptedChat == null) { final CountDownLatch countDownLatch = new CountDownLatch(1); messagesStorage.getStorageQueue().postRunnable(() -> { @@ -822,12 +823,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not FileLog.e(e); } if (currentEncryptedChat != null) { - MessagesController.getInstance(currentAccount).putEncryptedChat(currentEncryptedChat, true); + getMessagesController().putEncryptedChat(currentEncryptedChat, true); } else { return false; } } - currentUser = MessagesController.getInstance(currentAccount).getUser(currentEncryptedChat.user_id); + currentUser = getMessagesController().getUser(currentEncryptedChat.user_id); if (currentUser == null) { final CountDownLatch countDownLatch = new CountDownLatch(1); messagesStorage.getStorageQueue().postRunnable(() -> { @@ -840,7 +841,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not FileLog.e(e); } if (currentUser != null) { - MessagesController.getInstance(currentAccount).putUser(currentUser, true); + getMessagesController().putUser(currentUser, true); } else { return false; } @@ -856,66 +857,66 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not MediaController.getInstance().startMediaObserver(); } - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messagesDidLoad); + getNotificationCenter().addObserver(this, NotificationCenter.messagesDidLoad); NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.emojiDidLoad); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.didUpdateConnectionState); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.updateInterfaces); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.didReceiveNewMessages); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.closeChats); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messagesRead); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messagesDeleted); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.historyCleared); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messageReceivedByServer); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messageReceivedByAck); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messageSendError); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatInfoDidLoad); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.contactsDidLoad); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.encryptedChatUpdated); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messagesReadEncrypted); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.removeAllMessagesFromDialog); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messagePlayingProgressDidChanged); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messagePlayingDidReset); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messagePlayingGoingToStop); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messagePlayingPlayStateChanged); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.screenshotTook); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.blockedUsersDidLoad); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.fileNewChunkAvailable); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.didCreatedNewDeleteTask); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messagePlayingDidStart); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.updateMessageMedia); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.replaceMessagesObjects); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.notificationsSettingsUpdated); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.replyMessagesDidLoad); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.didReceivedWebpages); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.didReceivedWebpagesInUpdates); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messagesReadContent); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.botInfoDidLoad); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.botKeyboardDidLoad); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatSearchResultsAvailable); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatSearchResultsLoading); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.didUpdatedMessagesViews); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatInfoCantLoad); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.pinnedMessageDidLoad); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.peerSettingsDidLoad); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.newDraftReceived); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.userInfoDidLoad); + getNotificationCenter().addObserver(this, NotificationCenter.didUpdateConnectionState); + getNotificationCenter().addObserver(this, NotificationCenter.updateInterfaces); + getNotificationCenter().addObserver(this, NotificationCenter.didReceiveNewMessages); + getNotificationCenter().addObserver(this, NotificationCenter.closeChats); + getNotificationCenter().addObserver(this, NotificationCenter.messagesRead); + getNotificationCenter().addObserver(this, NotificationCenter.messagesDeleted); + getNotificationCenter().addObserver(this, NotificationCenter.historyCleared); + getNotificationCenter().addObserver(this, NotificationCenter.messageReceivedByServer); + getNotificationCenter().addObserver(this, NotificationCenter.messageReceivedByAck); + getNotificationCenter().addObserver(this, NotificationCenter.messageSendError); + getNotificationCenter().addObserver(this, NotificationCenter.chatInfoDidLoad); + getNotificationCenter().addObserver(this, NotificationCenter.contactsDidLoad); + getNotificationCenter().addObserver(this, NotificationCenter.encryptedChatUpdated); + getNotificationCenter().addObserver(this, NotificationCenter.messagesReadEncrypted); + getNotificationCenter().addObserver(this, NotificationCenter.removeAllMessagesFromDialog); + getNotificationCenter().addObserver(this, NotificationCenter.messagePlayingProgressDidChanged); + getNotificationCenter().addObserver(this, NotificationCenter.messagePlayingDidReset); + getNotificationCenter().addObserver(this, NotificationCenter.messagePlayingGoingToStop); + getNotificationCenter().addObserver(this, NotificationCenter.messagePlayingPlayStateChanged); + getNotificationCenter().addObserver(this, NotificationCenter.screenshotTook); + getNotificationCenter().addObserver(this, NotificationCenter.blockedUsersDidLoad); + getNotificationCenter().addObserver(this, NotificationCenter.fileNewChunkAvailable); + getNotificationCenter().addObserver(this, NotificationCenter.didCreatedNewDeleteTask); + getNotificationCenter().addObserver(this, NotificationCenter.messagePlayingDidStart); + getNotificationCenter().addObserver(this, NotificationCenter.updateMessageMedia); + getNotificationCenter().addObserver(this, NotificationCenter.replaceMessagesObjects); + getNotificationCenter().addObserver(this, NotificationCenter.notificationsSettingsUpdated); + getNotificationCenter().addObserver(this, NotificationCenter.replyMessagesDidLoad); + getNotificationCenter().addObserver(this, NotificationCenter.didReceivedWebpages); + getNotificationCenter().addObserver(this, NotificationCenter.didReceivedWebpagesInUpdates); + getNotificationCenter().addObserver(this, NotificationCenter.messagesReadContent); + getNotificationCenter().addObserver(this, NotificationCenter.botInfoDidLoad); + getNotificationCenter().addObserver(this, NotificationCenter.botKeyboardDidLoad); + getNotificationCenter().addObserver(this, NotificationCenter.chatSearchResultsAvailable); + getNotificationCenter().addObserver(this, NotificationCenter.chatSearchResultsLoading); + getNotificationCenter().addObserver(this, NotificationCenter.didUpdatedMessagesViews); + getNotificationCenter().addObserver(this, NotificationCenter.chatInfoCantLoad); + getNotificationCenter().addObserver(this, NotificationCenter.pinnedMessageDidLoad); + getNotificationCenter().addObserver(this, NotificationCenter.peerSettingsDidLoad); + getNotificationCenter().addObserver(this, NotificationCenter.newDraftReceived); + getNotificationCenter().addObserver(this, NotificationCenter.userInfoDidLoad); NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.didSetNewWallpapper); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.channelRightsUpdated); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.updateMentionsCount); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.audioRecordTooShort); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.didUpdatePollResults); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatOnlineCountDidLoad); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.videoLoadingStateChanged); + getNotificationCenter().addObserver(this, NotificationCenter.channelRightsUpdated); + getNotificationCenter().addObserver(this, NotificationCenter.updateMentionsCount); + getNotificationCenter().addObserver(this, NotificationCenter.audioRecordTooShort); + getNotificationCenter().addObserver(this, NotificationCenter.didUpdatePollResults); + getNotificationCenter().addObserver(this, NotificationCenter.chatOnlineCountDidLoad); + getNotificationCenter().addObserver(this, NotificationCenter.videoLoadingStateChanged); super.onFragmentCreate(); if (currentEncryptedChat == null && !isBroadcast) { - DataQuery.getInstance(currentAccount).loadBotKeyboard(dialog_id); + getMediaDataController().loadBotKeyboard(dialog_id); } loading = true; - MessagesController.getInstance(currentAccount).loadPeerSettings(currentUser, currentChat); - MessagesController.getInstance(currentAccount).setLastCreatedDialogId(dialog_id, true); + getMessagesController().loadPeerSettings(currentUser, currentChat); + getMessagesController().setLastCreatedDialogId(dialog_id, true); if (startLoadFromMessageId == 0) { SharedPreferences sharedPreferences = MessagesController.getNotificationsSettings(currentAccount); @@ -927,6 +928,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not startLoadFromMessageId = messageId; } } else { + showScrollToMessageError = true; needSelectFromMessageId = true; } @@ -934,13 +936,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not waitingForLoad.add(lastLoadIndex); if (migrated_to != 0) { mergeDialogId = migrated_to; - MessagesController.getInstance(currentAccount).loadMessages(mergeDialogId, loadingFromOldPosition ? 50 : (AndroidUtilities.isTablet() ? 30 : 20), startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + getMessagesController().loadMessages(mergeDialogId, loadingFromOldPosition ? 50 : (AndroidUtilities.isTablet() ? 30 : 20), startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } else { - MessagesController.getInstance(currentAccount).loadMessages(dialog_id, loadingFromOldPosition ? 50 : (AndroidUtilities.isTablet() ? 30 : 20), startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, loadingFromOldPosition ? 50 : (AndroidUtilities.isTablet() ? 30 : 20), startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } } else { waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance(currentAccount).loadMessages(dialog_id, AndroidUtilities.isTablet() ? 30 : 20, 0, 0, true, 0, classGuid, 2, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, AndroidUtilities.isTablet() ? 30 : 20, 0, 0, true, 0, classGuid, 2, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } if (currentChat != null) { @@ -948,7 +950,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (isBroadcast) { countDownLatch = new CountDownLatch(1); } - MessagesController.getInstance(currentAccount).loadChatInfo(currentChat.id, countDownLatch, true); + getMessagesController().loadChatInfo(currentChat.id, countDownLatch, true); chatInfo = getMessagesController().getChatFull(currentChat.id); if (isBroadcast && countDownLatch != null) { try { @@ -958,31 +960,31 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } } else if (currentUser != null) { - MessagesController.getInstance(currentAccount).loadUserInfo(currentUser, true, classGuid); + getMessagesController().loadUserInfo(currentUser, true, classGuid); } if (userId != 0 && currentUser.bot) { - DataQuery.getInstance(currentAccount).loadBotInfo(userId, true, classGuid); + getMediaDataController().loadBotInfo(userId, true, classGuid); } else if (chatInfo instanceof TLRPC.TL_chatFull) { for (int a = 0; a < chatInfo.participants.participants.size(); a++) { TLRPC.ChatParticipant participant = chatInfo.participants.participants.get(a); - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(participant.user_id); + TLRPC.User user = getMessagesController().getUser(participant.user_id); if (user != null && user.bot) { - DataQuery.getInstance(currentAccount).loadBotInfo(user.id, true, classGuid); + getMediaDataController().loadBotInfo(user.id, true, classGuid); } } } if (currentUser != null) { - userBlocked = MessagesController.getInstance(currentAccount).blockedUsers.indexOfKey(currentUser.id) >= 0; + userBlocked = getMessagesController().blockedUsers.indexOfKey(currentUser.id) >= 0; } if (AndroidUtilities.isTablet()) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.openedChatChanged, dialog_id, false); + getNotificationCenter().postNotificationName(NotificationCenter.openedChatChanged, dialog_id, false); } if (currentEncryptedChat != null && AndroidUtilities.getMyLayerVersion(currentEncryptedChat.layer) != SecretChatHelper.CURRENT_SECRET_CHAT_LAYER) { - SecretChatHelper.getInstance(currentAccount).sendNotifyLayerMessage(currentEncryptedChat, null); + getSecretChatHelper().sendNotifyLayerMessage(currentEncryptedChat, null); } return true; @@ -1003,60 +1005,60 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (undoView != null) { undoView.hide(true, 0); } - MessagesController.getInstance(currentAccount).setLastCreatedDialogId(dialog_id, false); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messagesDidLoad); + getMessagesController().setLastCreatedDialogId(dialog_id, false); + getNotificationCenter().removeObserver(this, NotificationCenter.messagesDidLoad); NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.emojiDidLoad); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.didUpdateConnectionState); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.updateInterfaces); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.didReceiveNewMessages); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.closeChats); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messagesRead); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messagesDeleted); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.historyCleared); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messageReceivedByServer); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messageReceivedByAck); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messageSendError); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.chatInfoDidLoad); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.encryptedChatUpdated); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messagesReadEncrypted); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.removeAllMessagesFromDialog); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.contactsDidLoad); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messagePlayingProgressDidChanged); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messagePlayingDidReset); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.screenshotTook); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.blockedUsersDidLoad); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.fileNewChunkAvailable); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.didCreatedNewDeleteTask); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messagePlayingDidStart); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messagePlayingGoingToStop); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.updateMessageMedia); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.replaceMessagesObjects); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.notificationsSettingsUpdated); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.replyMessagesDidLoad); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.didReceivedWebpages); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.didReceivedWebpagesInUpdates); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messagesReadContent); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.botInfoDidLoad); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.botKeyboardDidLoad); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.chatSearchResultsAvailable); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.chatSearchResultsLoading); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messagePlayingPlayStateChanged); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.didUpdatedMessagesViews); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.chatInfoCantLoad); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.pinnedMessageDidLoad); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.peerSettingsDidLoad); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.newDraftReceived); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.userInfoDidLoad); + getNotificationCenter().removeObserver(this, NotificationCenter.didUpdateConnectionState); + getNotificationCenter().removeObserver(this, NotificationCenter.updateInterfaces); + getNotificationCenter().removeObserver(this, NotificationCenter.didReceiveNewMessages); + getNotificationCenter().removeObserver(this, NotificationCenter.closeChats); + getNotificationCenter().removeObserver(this, NotificationCenter.messagesRead); + getNotificationCenter().removeObserver(this, NotificationCenter.messagesDeleted); + getNotificationCenter().removeObserver(this, NotificationCenter.historyCleared); + getNotificationCenter().removeObserver(this, NotificationCenter.messageReceivedByServer); + getNotificationCenter().removeObserver(this, NotificationCenter.messageReceivedByAck); + getNotificationCenter().removeObserver(this, NotificationCenter.messageSendError); + getNotificationCenter().removeObserver(this, NotificationCenter.chatInfoDidLoad); + getNotificationCenter().removeObserver(this, NotificationCenter.encryptedChatUpdated); + getNotificationCenter().removeObserver(this, NotificationCenter.messagesReadEncrypted); + getNotificationCenter().removeObserver(this, NotificationCenter.removeAllMessagesFromDialog); + getNotificationCenter().removeObserver(this, NotificationCenter.contactsDidLoad); + getNotificationCenter().removeObserver(this, NotificationCenter.messagePlayingProgressDidChanged); + getNotificationCenter().removeObserver(this, NotificationCenter.messagePlayingDidReset); + getNotificationCenter().removeObserver(this, NotificationCenter.screenshotTook); + getNotificationCenter().removeObserver(this, NotificationCenter.blockedUsersDidLoad); + getNotificationCenter().removeObserver(this, NotificationCenter.fileNewChunkAvailable); + getNotificationCenter().removeObserver(this, NotificationCenter.didCreatedNewDeleteTask); + getNotificationCenter().removeObserver(this, NotificationCenter.messagePlayingDidStart); + getNotificationCenter().removeObserver(this, NotificationCenter.messagePlayingGoingToStop); + getNotificationCenter().removeObserver(this, NotificationCenter.updateMessageMedia); + getNotificationCenter().removeObserver(this, NotificationCenter.replaceMessagesObjects); + getNotificationCenter().removeObserver(this, NotificationCenter.notificationsSettingsUpdated); + getNotificationCenter().removeObserver(this, NotificationCenter.replyMessagesDidLoad); + getNotificationCenter().removeObserver(this, NotificationCenter.didReceivedWebpages); + getNotificationCenter().removeObserver(this, NotificationCenter.didReceivedWebpagesInUpdates); + getNotificationCenter().removeObserver(this, NotificationCenter.messagesReadContent); + getNotificationCenter().removeObserver(this, NotificationCenter.botInfoDidLoad); + getNotificationCenter().removeObserver(this, NotificationCenter.botKeyboardDidLoad); + getNotificationCenter().removeObserver(this, NotificationCenter.chatSearchResultsAvailable); + getNotificationCenter().removeObserver(this, NotificationCenter.chatSearchResultsLoading); + getNotificationCenter().removeObserver(this, NotificationCenter.messagePlayingPlayStateChanged); + getNotificationCenter().removeObserver(this, NotificationCenter.didUpdatedMessagesViews); + getNotificationCenter().removeObserver(this, NotificationCenter.chatInfoCantLoad); + getNotificationCenter().removeObserver(this, NotificationCenter.pinnedMessageDidLoad); + getNotificationCenter().removeObserver(this, NotificationCenter.peerSettingsDidLoad); + getNotificationCenter().removeObserver(this, NotificationCenter.newDraftReceived); + getNotificationCenter().removeObserver(this, NotificationCenter.userInfoDidLoad); NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.didSetNewWallpapper); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.channelRightsUpdated); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.updateMentionsCount); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.audioRecordTooShort); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.didUpdatePollResults); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.chatOnlineCountDidLoad); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.videoLoadingStateChanged); + getNotificationCenter().removeObserver(this, NotificationCenter.channelRightsUpdated); + getNotificationCenter().removeObserver(this, NotificationCenter.updateMentionsCount); + getNotificationCenter().removeObserver(this, NotificationCenter.audioRecordTooShort); + getNotificationCenter().removeObserver(this, NotificationCenter.didUpdatePollResults); + getNotificationCenter().removeObserver(this, NotificationCenter.chatOnlineCountDidLoad); + getNotificationCenter().removeObserver(this, NotificationCenter.videoLoadingStateChanged); if (AndroidUtilities.isTablet()) { - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.openedChatChanged, dialog_id, true); + getNotificationCenter().postNotificationName(NotificationCenter.openedChatChanged, dialog_id, true); } if (currentUser != null) { MediaController.getInstance().stopMediaObserver(); @@ -1071,7 +1073,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } if (currentUser != null) { - MessagesController.getInstance(currentAccount).cancelLoadFullUser(currentUser.id); + getMessagesController().cancelLoadFullUser(currentUser.id); } AndroidUtilities.removeAdjustResize(getParentActivity(), classGuid); if (stickersAdapter != null) { @@ -1082,7 +1084,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } AndroidUtilities.unlockOrientation(getParentActivity()); if (ChatObject.isChannel(currentChat)) { - MessagesController.getInstance(currentAccount).startShortPoll(currentChat, true); + getMessagesController().startShortPoll(currentChat, true); } } @@ -1199,15 +1201,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not AlertsCreator.createClearOrDeleteDialogAlert(ChatActivity.this, id == clear_history, currentChat, currentUser, currentEncryptedChat != null, (param) -> { if (id == clear_history && ChatObject.isChannel(currentChat) && (!currentChat.megagroup || !TextUtils.isEmpty(currentChat.username))) { - MessagesController.getInstance(currentAccount).deleteDialog(dialog_id, 2, param); + getMessagesController().deleteDialog(dialog_id, 2, param); } else { if (id != clear_history) { - NotificationCenter.getInstance(currentAccount).removeObserver(ChatActivity.this, NotificationCenter.closeChats); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.closeChats); + getNotificationCenter().removeObserver(ChatActivity.this, NotificationCenter.closeChats); + getNotificationCenter().postNotificationName(NotificationCenter.closeChats); finishFragment(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.needDeleteDialog, dialog_id, currentUser, currentChat, param); + getNotificationCenter().postNotificationName(NotificationCenter.needDeleteDialog, dialog_id, currentUser, currentChat, param); } else { clearingHistory = true; + undoView.setAdditionalTranslationY(0); undoView.showWithAction(dialog_id, id == clear_history ? UndoView.ACTION_CLEAR : UndoView.ACTION_DELETE, () -> { if (id == clear_history) { if (chatInfo != null && chatInfo.pinned_msg_id != 0) { @@ -1219,19 +1222,19 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not preferences.edit().putInt("pin_" + dialog_id, userInfo.pinned_msg_id).commit(); updatePinnedMessageView(true); } - MessagesController.getInstance(currentAccount).deleteDialog(dialog_id, 1, param); + getMessagesController().deleteDialog(dialog_id, 1, param); clearingHistory = false; clearHistory(false); chatAdapter.notifyDataSetChanged(); } else { if (isChat) { if (ChatObject.isNotInChat(currentChat)) { - MessagesController.getInstance(currentAccount).deleteDialog(dialog_id, 0, param); + getMessagesController().deleteDialog(dialog_id, 0, param); } else { - MessagesController.getInstance(currentAccount).deleteUserFromChat((int) -dialog_id, MessagesController.getInstance(currentAccount).getUser(UserConfig.getInstance(currentAccount).getClientUserId()), null); + getMessagesController().deleteUserFromChat((int) -dialog_id, getMessagesController().getUser(getUserConfig().getClientUserId()), null); } } else { - MessagesController.getInstance(currentAccount).deleteDialog(dialog_id, 0, param); + getMessagesController().deleteDialog(dialog_id, 0, param); } finishFragment(); } @@ -1247,19 +1250,19 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (currentUser == null || getParentActivity() == null) { return; } - if (currentUser.phone != null && currentUser.phone.length() != 0) { + if (addToContactsButton.getTag() != null) { + shareMyContact((Integer) addToContactsButton.getTag(), null); + } else { Bundle args = new Bundle(); args.putInt("user_id", currentUser.id); args.putBoolean("addContact", true); presentFragment(new ContactAddActivity(args)); - } else { - shareMyContact(replyingMessageObject); } } else if (id == mute) { toggleMute(false); } else if (id == add_shortcut) { try { - DataQuery.getInstance(currentAccount).installShortcut(currentUser.id); + getMediaDataController().installShortcut(currentUser.id); } catch (Exception e) { FileLog.e(e); } @@ -1269,7 +1272,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not for (int a = 0; a < 2; a++) { for (int b = 0; b < selectedMessagesCanStarIds[a].size(); b++) { MessageObject msg = selectedMessagesCanStarIds[a].valueAt(b); - DataQuery.getInstance(currentAccount).addRecentSticker(DataQuery.TYPE_FAVE, msg, msg.getDocument(), (int) (System.currentTimeMillis() / 1000), !hasUnfavedSelected); + getMediaDataController().addRecentSticker(MediaDataController.TYPE_FAVE, msg, msg.getDocument(), (int) (System.currentTimeMillis() / 1000), !hasUnfavedSelected); } } for (int a = 1; a >= 0; a--) { @@ -1304,14 +1307,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } openAttachMenu(); } else if (id == bot_help) { - SendMessagesHelper.getInstance(currentAccount).sendMessage("/help", dialog_id, null, null, false, null, null, null); + getSendMessagesHelper().sendMessage("/help", dialog_id, null, null, false, null, null, null); } else if (id == bot_settings) { - SendMessagesHelper.getInstance(currentAccount).sendMessage("/settings", dialog_id, null, null, false, null, null, null); + getSendMessagesHelper().sendMessage("/settings", dialog_id, null, null, false, null, null, null); } else if (id == search) { openSearchWithText(null); } else if(id == call) { if (currentUser != null && getParentActivity() != null) { - VoIPHelper.startCall(currentUser, getParentActivity(), MessagesController.getInstance(currentAccount).getUserFull(currentUser.id)); + VoIPHelper.startCall(currentUser, getParentActivity(), getMessagesController().getUserFull(currentUser.id)); } } else if (id == text_bold) { if (chatActivityEnterView != null) { @@ -1328,6 +1331,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatActivityEnterView.getEditField().setSelectionOverride(editTextStart, editTextEnd); chatActivityEnterView.getEditField().makeSelectedMono(); } + } else if (id == text_strike) { + if (chatActivityEnterView != null) { + chatActivityEnterView.getEditField().setSelectionOverride(editTextStart, editTextEnd); + chatActivityEnterView.getEditField().makeSelectedStrike(); + } + } else if (id == text_underline) { + if (chatActivityEnterView != null) { + chatActivityEnterView.getEditField().setSelectionOverride(editTextStart, editTextEnd); + chatActivityEnterView.getEditField().makeSelectedUnderline(); + } } else if (id == text_link) { if (chatActivityEnterView != null) { chatActivityEnterView.getEditField().setSelectionOverride(editTextStart, editTextEnd); @@ -1425,7 +1438,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not public void onSearchPressed(EditText editText) { searchWas = true; updateSearchButtons(0, 0, -1); - DataQuery.getInstance(currentAccount).searchMessagesInChat(editText.getText().toString(), dialog_id, mergeDialogId, classGuid, 0, searchingUserMessages); + getMediaDataController().searchMessagesInChat(editText.getText().toString(), dialog_id, mergeDialogId, classGuid, 0, searchingUserMessages); } @Override @@ -1467,7 +1480,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not headerItem.setContentDescription(LocaleController.getString("AccDescrMoreOptions", R.string.AccDescrMoreOptions)); if (currentUser != null) { headerItem.addSubItem(call, R.drawable.msg_callback, LocaleController.getString("Call", R.string.Call)); - TLRPC.UserFull userFull = MessagesController.getInstance(currentAccount).getUserFull(currentUser.id); + TLRPC.UserFull userFull = getMessagesController().getUserFull(currentUser.id); if (userFull != null && userFull.phone_calls_available) { headerItem.showSubItem(call); } else { @@ -1487,6 +1500,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not stringBuilder = new SpannableStringBuilder(LocaleController.getString("Mono", R.string.Mono)); stringBuilder.setSpan(new TypefaceSpan(Typeface.MONOSPACE), 0, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); editTextItem.addSubItem(text_mono, stringBuilder); + if (currentEncryptedChat != null && AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 101) { + stringBuilder = new SpannableStringBuilder(LocaleController.getString("Strike", R.string.Strike)); + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_STRIKE; + stringBuilder.setSpan(new TextStyleSpan(run), 0, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + editTextItem.addSubItem(text_strike, stringBuilder); + stringBuilder = new SpannableStringBuilder(LocaleController.getString("Underline", R.string.Underline)); + run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_UNDERLINE; + stringBuilder.setSpan(new TextStyleSpan(run), 0, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + editTextItem.addSubItem(text_underline, stringBuilder); + } editTextItem.addSubItem(text_link, LocaleController.getString("CreateLink", R.string.CreateLink)); editTextItem.addSubItem(text_regular, LocaleController.getString("Regular", R.string.Regular)); @@ -1860,7 +1885,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (child == chatListView) { /*globalIgnoreLayout = true; int additionalPadding = inputFieldHeight - AndroidUtilities.dp(51); - if (pinnedMessageView != null && pinnedMessageView.getTag() == null || reportSpamView != null && reportSpamView.getTag() == null) { + if (pinnedMessageView != null && pinnedMessageView.getTag() == null || topChatPanelView != null && topChatPanelView.getTag() == null) { chatListView.setPadding(0, AndroidUtilities.dp(52) + additionalPadding, 0, AndroidUtilities.dp(3)); } else { chatListView.setPadding(0, AndroidUtilities.dp(4) + additionalPadding, 0, AndroidUtilities.dp(3)); @@ -2118,7 +2143,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } else { bigEmptyView = new ChatBigEmptyView(context, ChatBigEmptyView.EMPTY_VIEW_TYPE_SECRET); - if (currentEncryptedChat.admin_id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (currentEncryptedChat.admin_id == getUserConfig().getClientUserId()) { bigEmptyView.setStatusText(LocaleController.formatString("EncryptedPlaceholderTitleOutgoing", R.string.EncryptedPlaceholderTitleOutgoing, UserObject.getFirstName(currentUser))); } else { bigEmptyView.setStatusText(LocaleController.formatString("EncryptedPlaceholderTitleIncoming", R.string.EncryptedPlaceholderTitleIncoming, UserObject.getFirstName(currentUser))); @@ -2854,20 +2879,28 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { - if (newState == RecyclerView.SCROLL_STATE_SETTLING) { - wasManualScroll = true; - scrollingChatListView = true; - } else if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { - wasManualScroll = true; - scrollingFloatingDate = true; - checkTextureViewPosition = true; - scrollingChatListView = true; - } else if (newState == RecyclerView.SCROLL_STATE_IDLE) { + if (newState == RecyclerView.SCROLL_STATE_IDLE) { scrollingFloatingDate = false; scrollingChatListView = false; checkTextureViewPosition = false; hideFloatingDateView(true); checkAutoDownloadMessages(scrollUp); + if (SharedConfig.getDevicePerfomanceClass() == SharedConfig.PERFORMANCE_CLASS_LOW) { + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.startAllHeavyOperations, 512); + } + } else { + if (newState == RecyclerView.SCROLL_STATE_SETTLING) { + wasManualScroll = true; + scrollingChatListView = true; + } else if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { + wasManualScroll = true; + scrollingFloatingDate = true; + checkTextureViewPosition = true; + scrollingChatListView = true; + } + if (SharedConfig.getDevicePerfomanceClass() == SharedConfig.PERFORMANCE_CLASS_LOW) { + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.stopAllHeavyOperations, 512); + } } } @@ -3033,7 +3066,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("UnpinMessageAlertTitle", R.string.UnpinMessageAlertTitle)); builder.setMessage(LocaleController.getString("UnpinMessageAlert", R.string.UnpinMessageAlert)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> MessagesController.getInstance(currentAccount).pinMessage(currentChat, currentUser, 0, false)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> getMessagesController().pinMessage(currentChat, currentUser, 0, false)); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showDialog(builder.create()); } else { @@ -3048,13 +3081,73 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not }); } - reportSpamView = new LinearLayout(context); - reportSpamView.setTag(1); - reportSpamView.setTranslationY(-AndroidUtilities.dp(50)); - reportSpamView.setVisibility(View.GONE); - reportSpamView.setBackgroundResource(R.drawable.blockpanel); - reportSpamView.getBackground().setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_topPanelBackground), PorterDuff.Mode.MULTIPLY)); - contentView.addView(reportSpamView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 50, Gravity.TOP | Gravity.LEFT)); + topChatPanelView = new FrameLayout(context) { + + private boolean ignoreLayout; + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + if (addToContactsButton != null && addToContactsButton.getVisibility() == VISIBLE && reportSpamButton != null && reportSpamButton.getVisibility() == VISIBLE) { + width = (width - AndroidUtilities.dp(31)) / 2; + } + ignoreLayout = true; + if (reportSpamButton != null && reportSpamButton.getVisibility() == VISIBLE) { + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) reportSpamButton.getLayoutParams(); + layoutParams.width = width; + if (addToContactsButton != null && addToContactsButton.getVisibility() == VISIBLE) { + reportSpamButton.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(19), 0); + layoutParams.leftMargin = width; + } else { + reportSpamButton.setPadding(AndroidUtilities.dp(48), 0, AndroidUtilities.dp(48), 0); + layoutParams.leftMargin = 0; + } + } + if (addToContactsButton != null && addToContactsButton.getVisibility() == VISIBLE) { + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) addToContactsButton.getLayoutParams(); + layoutParams.width = width; + if (reportSpamButton != null && reportSpamButton.getVisibility() == VISIBLE) { + addToContactsButton.setPadding(AndroidUtilities.dp(11), 0, AndroidUtilities.dp(4), 0); + } else { + addToContactsButton.setPadding(AndroidUtilities.dp(48), 0, AndroidUtilities.dp(48), 0); + layoutParams.leftMargin = 0; + } + } + ignoreLayout = false; + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + public void requestLayout() { + if (ignoreLayout) { + return; + } + super.requestLayout(); + } + }; + topChatPanelView.setTag(1); + topChatPanelView.setTranslationY(-AndroidUtilities.dp(50)); + topChatPanelView.setVisibility(View.GONE); + topChatPanelView.setBackgroundResource(R.drawable.blockpanel); + topChatPanelView.getBackground().setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_topPanelBackground), PorterDuff.Mode.MULTIPLY)); + contentView.addView(topChatPanelView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 50, Gravity.TOP | Gravity.LEFT)); + + reportSpamButton = new TextView(context); + reportSpamButton.setTextColor(Theme.getColor(Theme.key_chat_reportSpam)); + reportSpamButton.setTag(Theme.key_chat_reportSpam); + reportSpamButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + reportSpamButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + reportSpamButton.setSingleLine(true); + reportSpamButton.setMaxLines(1); + reportSpamButton.setGravity(Gravity.CENTER); + topChatPanelView.addView(reportSpamButton, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, AndroidUtilities.dp(1))); + reportSpamButton.setOnClickListener(v2 -> AlertsCreator.showBlockReportSpamAlert(ChatActivity.this, dialog_id, currentUser, currentChat, currentEncryptedChat, reportSpamButton.getTag(R.id.object_tag) != null, chatInfo, param -> { + if (param == 0) { + updateTopPanel(true); + } else { + finishFragment(); + } + })); addToContactsButton = new TextView(context); addToContactsButton.setTextColor(Theme.getColor(Theme.key_chat_addContact)); @@ -3065,74 +3158,31 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not addToContactsButton.setMaxLines(1); addToContactsButton.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); addToContactsButton.setGravity(Gravity.CENTER); - addToContactsButton.setText(LocaleController.getString("AddContactChat", R.string.AddContactChat)); - reportSpamView.addView(addToContactsButton, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0.5f, Gravity.LEFT | Gravity.TOP, 0, 0, 0, AndroidUtilities.dp(1))); + topChatPanelView.addView(addToContactsButton, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, AndroidUtilities.dp(1))); addToContactsButton.setOnClickListener(v -> { - Bundle args = new Bundle(); - args.putInt("user_id", currentUser.id); - args.putBoolean("addContact", true); - presentFragment(new ContactAddActivity(args)); - }); - - reportSpamContainer = new FrameLayout(context); - reportSpamView.addView(reportSpamContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 1.0f, Gravity.LEFT | Gravity.TOP, 0, 0, 0, AndroidUtilities.dp(1))); - - reportSpamButton = new TextView(context); - reportSpamButton.setTextColor(Theme.getColor(Theme.key_chat_reportSpam)); - reportSpamButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - reportSpamButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - reportSpamButton.setSingleLine(true); - reportSpamButton.setMaxLines(1); - if (currentChat != null) { - reportSpamButton.setText(LocaleController.getString("ReportSpamAndLeave", R.string.ReportSpamAndLeave)); - } else { - reportSpamButton.setText(LocaleController.getString("ReportSpam", R.string.ReportSpam)); - } - reportSpamButton.setGravity(Gravity.CENTER); - reportSpamButton.setPadding(AndroidUtilities.dp(50), 0, AndroidUtilities.dp(50), 0); - reportSpamContainer.addView(reportSpamButton, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP)); - reportSpamButton.setOnClickListener(v -> { - if (getParentActivity() == null) { - return; - } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - if (ChatObject.isChannel(currentChat) && !currentChat.megagroup) { - builder.setMessage(LocaleController.getString("ReportSpamAlertChannel", R.string.ReportSpamAlertChannel)); - } else if (currentChat != null) { - builder.setMessage(LocaleController.getString("ReportSpamAlertGroup", R.string.ReportSpamAlertGroup)); + if (addToContactsButton.getTag() != null) { + shareMyContact(1, null); } else { - builder.setMessage(LocaleController.getString("ReportSpamAlert", R.string.ReportSpamAlert)); + Bundle args = new Bundle(); + args.putInt("user_id", currentUser.id); + args.putBoolean("addContact", true); + ContactAddActivity activity = new ContactAddActivity(args); + activity.setDelegate(() -> { + undoView.setAdditionalTranslationY(AndroidUtilities.dp(51)); + undoView.showWithAction(dialog_id, UndoView.ACTION_CONTACT_ADDED, currentUser); + }); + presentFragment(activity); } - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> { - if (currentUser != null) { - MessagesController.getInstance(currentAccount).blockUser(currentUser.id); - } - MessagesController.getInstance(currentAccount).reportSpam(dialog_id, currentUser, currentChat, currentEncryptedChat); - updateSpamView(); - if (currentChat != null) { - if (ChatObject.isNotInChat(currentChat)) { - MessagesController.getInstance(currentAccount).deleteDialog(dialog_id, 0); - } else { - MessagesController.getInstance(currentAccount).deleteUserFromChat((int) -dialog_id, MessagesController.getInstance(currentAccount).getUser(UserConfig.getInstance(currentAccount).getClientUserId()), null); - } - } else { - MessagesController.getInstance(currentAccount).deleteDialog(dialog_id, 0); - } - finishFragment(); - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - showDialog(builder.create()); }); closeReportSpam = new ImageView(context); closeReportSpam.setImageResource(R.drawable.miniplayer_close); closeReportSpam.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_topPanelClose), PorterDuff.Mode.MULTIPLY)); closeReportSpam.setScaleType(ImageView.ScaleType.CENTER); - reportSpamContainer.addView(closeReportSpam, LayoutHelper.createFrame(48, 48, Gravity.RIGHT | Gravity.TOP)); + topChatPanelView.addView(closeReportSpam, LayoutHelper.createFrame(48, 48, Gravity.RIGHT | Gravity.TOP)); closeReportSpam.setOnClickListener(v -> { - MessagesController.getInstance(currentAccount).hideReportSpam(dialog_id, currentUser, currentChat); - updateSpamView(); + getMessagesController().hidePeerSettingsBar(dialog_id, currentUser, currentChat); + updateTopPanel(true); }); alertView = new FrameLayout(context); @@ -3183,7 +3233,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private void loadLastUnreadMention() { wasManualScroll = true; if (hasAllMentionsLocal) { - MessagesStorage.getInstance(currentAccount).getUnreadMention(dialog_id, param -> { + getMessagesStorage().getUnreadMention(dialog_id, param -> { if (param == 0) { hasAllMentionsLocal = false; loadLastUnreadMention(); @@ -3192,12 +3242,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } }); } else { - final MessagesStorage messagesStorage = MessagesStorage.getInstance(currentAccount); + final MessagesStorage messagesStorage = getMessagesStorage(); TLRPC.TL_messages_getUnreadMentions req = new TLRPC.TL_messages_getUnreadMentions(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) dialog_id); + req.peer = getMessagesController().getInputPeer((int) dialog_id); req.limit = 1; req.add_offset = newMentionsCount - 1; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; if (error != null || res.messages.isEmpty()) { if (res != null) { @@ -3245,7 +3295,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } newMentionsCount = 0; - MessagesController.getInstance(currentAccount).markMentionsAsRead(dialog_id); + getMessagesController().markMentionsAsRead(dialog_id); hasAllMentionsLocal = true; showMentionDownButton(false, true); return true; @@ -3645,7 +3695,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not mentionsAdapter.searchUsernameOrHashtag(null, 0, null, false); searchItem.setSearchFieldHint(null); searchItem.clearSearchText(); - DataQuery.getInstance(currentAccount).searchMessagesInChat("", dialog_id, mergeDialogId, classGuid, 0, searchingUserMessages); + getMediaDataController().searchMessagesInChat("", dialog_id, mergeDialogId, classGuid, 0, searchingUserMessages); } else { TLRPC.User user = (TLRPC.User) object; if (user != null) { @@ -3661,7 +3711,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } else if (object instanceof String) { if (mentionsAdapter.isBotCommands()) { - SendMessagesHelper.getInstance(currentAccount).sendMessage((String) object, dialog_id, replyingMessageObject, null, false, null, null, null); + getSendMessagesHelper().sendMessage((String) object, dialog_id, replyingMessageObject, null, false, null, null, null); chatActivityEnterView.setFieldText(""); hideFieldPanel(false); } else { @@ -3683,8 +3733,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } else if (object instanceof TLRPC.TL_inlineBotSwitchPM) { processInlineBotContextPM((TLRPC.TL_inlineBotSwitchPM) object); - } else if (object instanceof DataQuery.KeywordResult) { - String code = ((DataQuery.KeywordResult) object).emoji; + } else if (object instanceof MediaDataController.KeywordResult) { + String code = ((MediaDataController.KeywordResult) object).emoji; chatActivityEnterView.addEmojiToRecent(code); chatActivityEnterView.replaceWithText(start, len, code, true); } @@ -3954,7 +4004,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void needSendTyping() { - MessagesController.getInstance(currentAccount).sendTyping(dialog_id, 0, classGuid); + getMessagesController().sendTyping(dialog_id, 0, classGuid); } @Override @@ -3990,7 +4040,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } chatActivityEnterView.setAllowStickersAndGifs(currentEncryptedChat == null || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 23, currentEncryptedChat == null || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 46); if (editingMessageObjectReqId != 0) { - ConnectionsManager.getInstance(currentAccount).cancelRequest(editingMessageObjectReqId, true); + getConnectionsManager().cancelRequest(editingMessageObjectReqId, true); editingMessageObjectReqId = 0; } updatePinnedMessageView(true); @@ -4272,7 +4322,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not searchUpButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_searchPanelIcons), PorterDuff.Mode.MULTIPLY)); searchUpButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_actionBarActionModeDefaultSelector), 1)); searchContainer.addView(searchUpButton, LayoutHelper.createFrame(48, 48, Gravity.RIGHT | Gravity.TOP, 0, 0, 48, 0)); - searchUpButton.setOnClickListener(view -> DataQuery.getInstance(currentAccount).searchMessagesInChat(null, dialog_id, mergeDialogId, classGuid, 1, searchingUserMessages)); + searchUpButton.setOnClickListener(view -> getMediaDataController().searchMessagesInChat(null, dialog_id, mergeDialogId, classGuid, 1, searchingUserMessages)); searchUpButton.setContentDescription(LocaleController.getString("AccDescrSearchNext", R.string.AccDescrSearchNext)); searchDownButton = new ImageView(context); @@ -4281,7 +4331,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not searchDownButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_searchPanelIcons), PorterDuff.Mode.MULTIPLY)); searchDownButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_actionBarActionModeDefaultSelector), 1)); searchContainer.addView(searchDownButton, LayoutHelper.createFrame(48, 48, Gravity.RIGHT | Gravity.TOP, 0, 0, 0, 0)); - searchDownButton.setOnClickListener(view -> DataQuery.getInstance(currentAccount).searchMessagesInChat(null, dialog_id, mergeDialogId, classGuid, 2, searchingUserMessages)); + searchDownButton.setOnClickListener(view -> getMediaDataController().searchMessagesInChat(null, dialog_id, mergeDialogId, classGuid, 2, searchingUserMessages)); searchDownButton.setContentDescription(LocaleController.getString("AccDescrSearchPrev", R.string.AccDescrSearchPrev)); if (currentChat != null && (!ChatObject.isChannel(currentChat) || currentChat.megagroup)) { @@ -4329,7 +4379,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not int date = (int) (calendar1.getTime().getTime() / 1000); clearChatData(); waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance(currentAccount).loadMessages(dialog_id, 30, 0, date, true, 0, classGuid, 4, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, 30, 0, date, true, 0, classGuid, 4, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); }, year, monthOfYear, dayOfMonth); final DatePicker datePicker = dialog.getDatePicker(); datePicker.setMinDate(1375315200000L); @@ -4433,25 +4483,25 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (currentUser.bot) { String botUserLast = botUser; botUser = null; - MessagesController.getInstance(currentAccount).unblockUser(currentUser.id); + getMessagesController().unblockUser(currentUser.id); if (botUserLast != null && botUserLast.length() != 0) { - MessagesController.getInstance(currentAccount).sendBotStart(currentUser, botUserLast); + getMessagesController().sendBotStart(currentUser, botUserLast); } else { - SendMessagesHelper.getInstance(currentAccount).sendMessage("/start", dialog_id, null, null, false, null, null, null); + getSendMessagesHelper().sendMessage("/start", dialog_id, null, null, false, null, null, null); } } else { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setMessage(LocaleController.getString("AreYouSureUnblockContact", R.string.AreYouSureUnblockContact)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> MessagesController.getInstance(currentAccount).unblockUser(currentUser.id)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> getMessagesController().unblockUser(currentUser.id)); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showDialog(builder.create()); } } else if (currentUser != null && currentUser.bot && botUser != null) { if (botUser.length() != 0) { - MessagesController.getInstance(currentAccount).sendBotStart(currentUser, botUser); + getMessagesController().sendBotStart(currentUser, botUser); } else { - SendMessagesHelper.getInstance(currentAccount).sendMessage("/start", dialog_id, null, null, false, null, null, null); + getSendMessagesHelper().sendMessage("/start", dialog_id, null, null, false, null, null, null); } botUser = null; updateBottomOverlay(); @@ -4459,17 +4509,23 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (ChatObject.isChannel(currentChat) && !(currentChat instanceof TLRPC.TL_channelForbidden)) { if (ChatObject.isNotInChat(currentChat)) { showBottomOverlayProgress(true, true); - MessagesController.getInstance(currentAccount).addUserToChat(currentChat.id, UserConfig.getInstance(currentAccount).getCurrentUser(), null, 0, null, ChatActivity.this, null); + getMessagesController().addUserToChat(currentChat.id, getUserConfig().getCurrentUser(), null, 0, null, ChatActivity.this, null); NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.closeSearchByActiveAction); + + if (hasReportSpam() && reportSpamButton.getTag(R.id.object_tag) != null) { + SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + preferences.edit().putInt("dialog_bar_vis3" + dialog_id, 3).commit(); + getNotificationCenter().postNotificationName(NotificationCenter.peerSettingsDidLoad, dialog_id); + } } else { toggleMute(true); } } else { AlertsCreator.createClearOrDeleteDialogAlert(ChatActivity.this, false, currentChat, currentUser, currentEncryptedChat != null, (param) -> { - NotificationCenter.getInstance(currentAccount).removeObserver(ChatActivity.this, NotificationCenter.closeChats); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.closeChats); + getNotificationCenter().removeObserver(ChatActivity.this, NotificationCenter.closeChats); + getNotificationCenter().postNotificationName(NotificationCenter.closeChats); finishFragment(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.needDeleteDialog, dialog_id, currentUser, currentChat, param); + getNotificationCenter().postNotificationName(NotificationCenter.needDeleteDialog, dialog_id, currentUser, currentChat, param); }); } } @@ -4488,7 +4544,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } Bundle args = new Bundle(); args.putInt("chat_id", chatInfo.linked_chat_id); - if (!MessagesController.getInstance(currentAccount).checkCanOpenChat(args, ChatActivity.this)) { + if (!getMessagesController().checkCanOpenChat(args, ChatActivity.this)) { return; } presentFragment(new ChatActivity(args)); @@ -4566,10 +4622,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } checkBotKeyboard(); - updateContactStatus(); updateBottomOverlay(); updateSecretStatus(); - updateSpamView(); + updateTopPanel(false); updatePinnedMessageView(true); try { @@ -4783,10 +4838,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not params.put("query_id", "" + result.query_id); params.put("bot", "" + uid); params.put("bot_name", mentionsAdapter.getContextBotName()); - SendMessagesHelper.prepareSendingBotContextResult(result, params, dialog_id, replyingMessageObject); + SendMessagesHelper.prepareSendingBotContextResult(getAccountInstance(), result, params, dialog_id, replyingMessageObject); chatActivityEnterView.setFieldText(""); hideFieldPanel(false); - DataQuery.getInstance(currentAccount).increaseInlineRaiting(uid); + getMediaDataController().increaseInlineRaiting(uid); } private void mentionListViewUpdateLayout() { @@ -4841,7 +4896,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (chatInfo instanceof TLRPC.TL_chatFull) { for (int a = 0; a < chatInfo.participants.participants.size(); a++) { TLRPC.ChatParticipant participant = chatInfo.participants.participants.get(a); - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(participant.user_id); + TLRPC.User user = getMessagesController().getUser(participant.user_id); if (user != null && user.bot) { URLSpanBotCommand.enabled = true; break; @@ -4880,7 +4935,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if ((int) dialog_id != 0) { clearChatData(); waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance(currentAccount).loadMessages(dialog_id, 30, 0, date, true, 0, classGuid, 4, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, 30, 0, date, true, 0, classGuid, 4, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); floatingDateView.setAlpha(0.0f); floatingDateView.setTag(null); } @@ -4897,13 +4952,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatActivityEnterView.setFieldText(""); if (dialog_id == user.id) { inlineReturn = dialog_id; - MessagesController.getInstance(currentAccount).sendBotStart(currentUser, object.start_param); + getMessagesController().sendBotStart(currentUser, object.start_param); } else { Bundle args = new Bundle(); args.putInt("user_id", user.id); args.putString("inline_query", object.start_param); args.putLong("inline_return", dialog_id); - if (!MessagesController.getInstance(currentAccount).checkCanOpenChat(args, ChatActivity.this)) { + if (!getMessagesController().checkCanOpenChat(args, ChatActivity.this)) { return; } presentFragment(new ChatActivity(args)); @@ -4955,10 +5010,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not photoEntry.reset(); } fillEditingMediaWithCaption(photos.get(0).caption, photos.get(0).entities); - SendMessagesHelper.prepareSendingMedia(photos, dialog_id, replyingMessageObject, null, button == 4, SharedConfig.groupPhotosEnabled, editingMessageObject); + SendMessagesHelper.prepareSendingMedia(getAccountInstance(), photos, dialog_id, replyingMessageObject, null, button == 4, SharedConfig.groupPhotosEnabled, editingMessageObject); hideFieldPanel(false); - DataQuery.getInstance(currentAccount).cleanDraft(dialog_id, true); + getMediaDataController().cleanDraft(dialog_id, true); } return; } else if (chatAttachAlert != null) { @@ -4998,9 +5053,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return dialog_id; } + public boolean hasReportSpam() { + return topChatPanelView != null && topChatPanelView.getTag() == null && reportSpamButton.getVisibility() != View.GONE; + } + public void setBotUser(String value) { if (inlineReturn != 0) { - MessagesController.getInstance(currentAccount).sendBotStart(currentUser, value); + getMessagesController().sendBotStart(currentUser, value); } else { botUser = value; updateBottomOverlay(); @@ -5033,19 +5092,24 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } stickersListView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); stickersListView.setAdapter(stickersAdapter = new StickersAdapter(getParentActivity(), show -> { + if (show) { + int newPadding = stickersAdapter.isShowingKeywords() ? AndroidUtilities.dp(24) : 0; + if (newPadding != stickersListView.getPaddingTop() || stickersPanel.getTag() == null) { + stickersListView.setPadding(AndroidUtilities.dp(18), newPadding, AndroidUtilities.dp(18), 0); + stickersListView.scrollToPosition(0); + + boolean isRtl = chatActivityEnterView.isRtlText(); + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) stickersPanelArrow.getLayoutParams(); + layoutParams.gravity = Gravity.BOTTOM | (isRtl ? Gravity.RIGHT : Gravity.LEFT); + stickersPanelArrow.requestLayout(); + } + } if (show && stickersPanel.getTag() != null || !show && stickersPanel.getTag() == null) { return; } if (show) { - stickersListView.setPadding(AndroidUtilities.dp(18), stickersAdapter.isShowingKeywords() ? AndroidUtilities.dp(24) : 0, AndroidUtilities.dp(18), 0); - stickersListView.scrollToPosition(0); stickersPanel.setVisibility(allowStickersPanel ? View.VISIBLE : View.INVISIBLE); stickersPanel.setTag(1); - - boolean isRtl = chatActivityEnterView.isRtlText(); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) stickersPanelArrow.getLayoutParams(); - layoutParams.gravity = Gravity.BOTTOM | (isRtl ? Gravity.RIGHT : Gravity.LEFT); - stickersPanelArrow.requestLayout(); } else { stickersPanel.setTag(null); } @@ -5092,7 +5156,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not Object parent = stickersAdapter.getItemParent(position); if (item instanceof TLRPC.TL_document) { TLRPC.TL_document document = (TLRPC.TL_document) item; - SendMessagesHelper.getInstance(currentAccount).sendSticker(document, dialog_id, replyingMessageObject, parent); + getSendMessagesHelper().sendSticker(document, dialog_id, replyingMessageObject, parent); hideFieldPanel(false); chatActivityEnterView.addStickerToRecent(document); chatActivityEnterView.setFieldText(""); @@ -5100,28 +5164,39 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not String emoji = (String) item; SpannableString string = new SpannableString(emoji); Emoji.replaceEmoji(string, chatActivityEnterView.getEditField().getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false); - stickersAdapter.loadStikersForEmoji("", false); + //stickersAdapter.loadStikersForEmoji("", false); chatActivityEnterView.setFieldText(string, false); } }); } - public void shareMyContact(final MessageObject messageObject) { + public void shareMyContact(int type, MessageObject messageObject) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("ShareYouPhoneNumberTitle", R.string.ShareYouPhoneNumberTitle)); if (currentUser != null) { if (currentUser.bot) { builder.setMessage(LocaleController.getString("AreYouSureShareMyContactInfoBot", R.string.AreYouSureShareMyContactInfoBot)); } else { - builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("AreYouSureShareMyContactInfoUser", R.string.AreYouSureShareMyContactInfoUser, PhoneFormat.getInstance().format("+" + UserConfig.getInstance(currentAccount).getCurrentUser().phone), ContactsController.formatName(currentUser.first_name, currentUser.last_name)))); + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("AreYouSureShareMyContactInfoUser", R.string.AreYouSureShareMyContactInfoUser, PhoneFormat.getInstance().format("+" + getUserConfig().getCurrentUser().phone), ContactsController.formatName(currentUser.first_name, currentUser.last_name)))); } } else { builder.setMessage(LocaleController.getString("AreYouSureShareMyContactInfo", R.string.AreYouSureShareMyContactInfo)); } - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> { - SendMessagesHelper.getInstance(currentAccount).sendMessage(UserConfig.getInstance(currentAccount).getCurrentUser(), dialog_id, messageObject, null, null); - moveScrollToLastMessage(); - hideFieldPanel(false); + builder.setPositiveButton(LocaleController.getString("ShareContact", R.string.ShareContact), (dialogInterface, i) -> { + if (type == 1) { + TLRPC.TL_contacts_acceptContact req = new TLRPC.TL_contacts_acceptContact(); + req.id = getMessagesController().getInputUser(currentUser); + getConnectionsManager().sendRequest(req, (response, error) -> { + if (error != null) { + return; + } + getMessagesController().processUpdates((TLRPC.Updates) response, false); + }); + } else { + SendMessagesHelper.getInstance(currentAccount).sendMessage(getUserConfig().getCurrentUser(), dialog_id, messageObject, null, null); + moveScrollToLastMessage(); + hideFieldPanel(false); + } }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showDialog(builder.create()); @@ -5533,14 +5608,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not continue; } int canDownload; - if (!MessageObject.isStickerDocument(document) && !MessageObject.isGifDocument(document) && !MessageObject.isRoundVideoDocument(document) - && (canDownload = DownloadController.getInstance(currentAccount).canDownloadMedia(object.messageOwner)) != 0) { + if (!MessageObject.isStickerDocument(document) && !MessageObject.isAnimatedStickerDocument(document) && !MessageObject.isGifDocument(document) && !MessageObject.isRoundVideoDocument(document) + && (canDownload = getDownloadController().canDownloadMedia(object.messageOwner)) != 0) { if (canDownload == 2) { if (currentEncryptedChat == null && !object.shouldEncryptPhotoOrVideo() && object.canStreamVideo()) { - FileLoader.getInstance(currentAccount).loadFile(document, object, 0, 10); + getFileLoader().loadFile(document, object, 0, 10); } } else { - FileLoader.getInstance(currentAccount).loadFile(document, object, 0, MessageObject.isVideoDocument(document) && object.shouldEncryptPhotoOrVideo() ? 2 : 0); + getFileLoader().loadFile(document, object, 0, MessageObject.isVideoDocument(document) && object.shouldEncryptPhotoOrVideo() ? 2 : 0); cell.updateButtonState(false, true, false); } } @@ -5584,7 +5659,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return; } TLRPC.Message message = object.messageOwner; - int canDownload = DownloadController.getInstance(currentAccount).canDownloadMedia(message); + int canDownload = getDownloadController().canDownloadMedia(message); if (canDownload == 0) { return; } @@ -5595,13 +5670,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (canDownload == 2 || canDownload == 1 && object.isVideo()) { if (document != null && currentEncryptedChat == null && !object.shouldEncryptPhotoOrVideo() && object.canStreamVideo()) { - FileLoader.getInstance(currentAccount).loadFile(document, object, 0, 10); + getFileLoader().loadFile(document, object, 0, 10); } } else { if (document != null) { - FileLoader.getInstance(currentAccount).loadFile(document, object, 0, MessageObject.isVideoDocument(document) && object.shouldEncryptPhotoOrVideo() ? 2 : 0); + getFileLoader().loadFile(document, object, 0, MessageObject.isVideoDocument(document) && object.shouldEncryptPhotoOrVideo() ? 2 : 0); } else { - FileLoader.getInstance(currentAccount).loadFile(ImageLocation.getForObject(photo, object.photoThumbsObject), object, null, 0, object.shouldEncryptPhotoOrVideo() ? 2 : 0); + getFileLoader().loadFile(ImageLocation.getForObject(photo, object.photoThumbsObject), object, null, 0, object.shouldEncryptPhotoOrVideo() ? 2 : 0); } } } @@ -5691,24 +5766,24 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not loading = true; waitingForLoad.add(lastLoadIndex); if (messagesByDays.size() != 0) { - MessagesController.getInstance(currentAccount).loadMessages(dialog_id, 50, maxMessageId[0], 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, 50, maxMessageId[0], 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } else { - MessagesController.getInstance(currentAccount).loadMessages(dialog_id, 50, 0, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, 50, 0, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } } else if (mergeDialogId != 0 && !endReached[1]) { loading = true; waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance(currentAccount).loadMessages(mergeDialogId, 50, maxMessageId[1], 0, !cacheEndReached[1], minDate[1], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + getMessagesController().loadMessages(mergeDialogId, 50, maxMessageId[1], 0, !cacheEndReached[1], minDate[1], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } } if (visibleItemCount > 0 && !loadingForward && firstVisibleItem <= 10) { if (mergeDialogId != 0 && !forwardEndReached[1]) { waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance(currentAccount).loadMessages(mergeDialogId, 50, minMessageId[1], 0, true, maxDate[1], classGuid, 1, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + getMessagesController().loadMessages(mergeDialogId, 50, minMessageId[1], 0, true, maxDate[1], classGuid, 1, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); loadingForward = true; } else if (!forwardEndReached[0]) { waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance(currentAccount).loadMessages(dialog_id, 50, minMessageId[0], 0, true, maxDate[0], classGuid, 1, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, 50, minMessageId[0], 0, true, maxDate[0], classGuid, 1, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); loadingForward = true; } } @@ -5761,9 +5836,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return; } fillEditingMediaWithCaption(photos.get(0).caption, photos.get(0).entities); - SendMessagesHelper.prepareSendingMedia(photos, dialog_id, replyingMessageObject, null, false, SharedConfig.groupPhotosEnabled, editingMessageObject); + SendMessagesHelper.prepareSendingMedia(getAccountInstance(), photos, dialog_id, replyingMessageObject, null, false, SharedConfig.groupPhotosEnabled, editingMessageObject); hideFieldPanel(false); - DataQuery.getInstance(currentAccount).cleanDraft(dialog_id, true); + getMediaDataController().cleanDraft(dialog_id, true); } @Override @@ -5837,9 +5912,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not public void didSelectFiles(DocumentSelectActivity activity, ArrayList files) { activity.finishFragment(); fillEditingMediaWithCaption(null, null); - SendMessagesHelper.prepareSendingDocuments(files, files, null, null, null, dialog_id, replyingMessageObject, null, editingMessageObject); + SendMessagesHelper.prepareSendingDocuments(getAccountInstance(), files, files, null, null, null, dialog_id, replyingMessageObject, null, editingMessageObject); hideFieldPanel(false); - DataQuery.getInstance(currentAccount).cleanDraft(dialog_id, true); + getMediaDataController().cleanDraft(dialog_id, true); } @Override @@ -5862,9 +5937,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not fragment.setDelegate(audios -> { parentFragment.removeSelfFromStack(); fillEditingMediaWithCaption(null, null); - SendMessagesHelper.prepareSendingAudioDocuments(audios, dialog_id, replyingMessageObject, editingMessageObject); + SendMessagesHelper.prepareSendingAudioDocuments(getAccountInstance(), audios, dialog_id, replyingMessageObject, editingMessageObject); hideFieldPanel(false); - DataQuery.getInstance(currentAccount).cleanDraft(dialog_id, true); + getMediaDataController().cleanDraft(dialog_id, true); }); presentFragment(fragment); } @@ -5878,9 +5953,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not AudioSelectActivity fragment = new AudioSelectActivity(); fragment.setDelegate(audios -> { fillEditingMediaWithCaption(null, null); - SendMessagesHelper.prepareSendingAudioDocuments(audios, dialog_id, replyingMessageObject, editingMessageObject); + SendMessagesHelper.prepareSendingAudioDocuments(getAccountInstance(), audios, dialog_id, replyingMessageObject, editingMessageObject); hideFieldPanel(false); - DataQuery.getInstance(currentAccount).cleanDraft(dialog_id, true); + getMediaDataController().cleanDraft(dialog_id, true); }); presentFragment(fragment); } else if (which == attach_contact) { @@ -5892,9 +5967,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } PhonebookSelectActivity activity = new PhonebookSelectActivity(); activity.setDelegate(user -> { - SendMessagesHelper.getInstance(currentAccount).sendMessage(user, dialog_id, replyingMessageObject, null, null); + getSendMessagesHelper().sendMessage(user, dialog_id, replyingMessageObject, null, null); hideFieldPanel(false); - DataQuery.getInstance(currentAccount).cleanDraft(dialog_id, true); + getMediaDataController().cleanDraft(dialog_id, true); }); presentFragment(activity); } else if (which == attach_poll) { @@ -5903,9 +5978,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } PollCreateActivity pollCreateActivity = new PollCreateActivity(); pollCreateActivity.setDelegate(poll -> { - SendMessagesHelper.getInstance(currentAccount).sendMessage(poll, dialog_id, replyingMessageObject, null, null); + getSendMessagesHelper().sendMessage(poll, dialog_id, replyingMessageObject, null, null); hideFieldPanel(false); - DataQuery.getInstance(currentAccount).cleanDraft(dialog_id, true); + getMediaDataController().cleanDraft(dialog_id, true); }); presentFragment(pollCreateActivity); } @@ -5917,7 +5992,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } private void searchLinks(final CharSequence charSequence, final boolean force) { - if (currentEncryptedChat != null && (MessagesController.getInstance(currentAccount).secretWebpagePreview == 0 || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) < 46)) { + if (currentEncryptedChat != null && (getMessagesController().secretWebpagePreview == 0 || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) < 46)) { return; } if (force && foundWebPage != null) { @@ -5943,10 +6018,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not foundUrls = null; showFieldPanelForWebPage(false, foundWebPage, false); } - final MessagesController messagesController = MessagesController.getInstance(currentAccount); + final MessagesController messagesController = getMessagesController(); Utilities.searchQueue.postRunnable(() -> { if (linkSearchRequestId != 0) { - ConnectionsManager.getInstance(currentAccount).cancelRequest(linkSearchRequestId, true); + getConnectionsManager().cancelRequest(linkSearchRequestId, true); linkSearchRequestId = 0; } ArrayList urls = null; @@ -6018,7 +6093,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialog, which) -> { messagesController.secretWebpagePreview = 1; - MessagesController.getGlobalMainSettings().edit().putInt("secretWebpage2", MessagesController.getInstance(currentAccount).secretWebpagePreview).commit(); + MessagesController.getGlobalMainSettings().edit().putInt("secretWebpage2", getMessagesController().secretWebpagePreview).commit(); foundUrls = null; searchLinks(charSequence, force); }); @@ -6038,7 +6113,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { req.message = textToCheck.toString(); } - linkSearchRequestId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + linkSearchRequestId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { linkSearchRequestId = 0; if (error == null) { if (response instanceof TLRPC.TL_messageMediaWebPage) { @@ -6066,7 +6141,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } })); - ConnectionsManager.getInstance(currentAccount).bindRequestToGuid(linkSearchRequestId, classGuid); + getConnectionsManager().bindRequestToGuid(linkSearchRequestId, classGuid); }); } @@ -6075,10 +6150,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return; } if (!fromMyName) { - AlertsCreator.showSendMediaAlert(SendMessagesHelper.getInstance(currentAccount).sendMessage(arrayList, dialog_id), this); + AlertsCreator.showSendMediaAlert(getSendMessagesHelper().sendMessage(arrayList, dialog_id), this); } else { for (MessageObject object : arrayList) { - SendMessagesHelper.getInstance(currentAccount).processForwardFromMyName(object, dialog_id); + getSendMessagesHelper().processForwardFromMyName(object, dialog_id); } } } @@ -6193,7 +6268,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatActivityEnterView.setForceShowSendButton(false, false); String name; if (messageObjectToReply.isFromUser()) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(messageObjectToReply.messageOwner.from_id); + TLRPC.User user = getMessagesController().getUser(messageObjectToReply.messageOwner.from_id); if (user == null) { return; } @@ -6201,9 +6276,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { TLRPC.Chat chat; if (ChatObject.isChannel(currentChat) && currentChat.megagroup && messageObjectToReply.isForwardedChannelPost()) { - chat = MessagesController.getInstance(currentAccount).getChat(messageObjectToReply.messageOwner.fwd_from.channel_id); + chat = getMessagesController().getChat(messageObjectToReply.messageOwner.fwd_from.channel_id); } else { - chat = MessagesController.getInstance(currentAccount).getChat(messageObjectToReply.messageOwner.to_id.channel_id); + chat = getMessagesController().getChat(messageObjectToReply.messageOwner.to_id.channel_id); } if (chat == null) { return; @@ -6280,9 +6355,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not TLRPC.Chat chat = null; TLRPC.User user = null; if (uid > 0) { - user = MessagesController.getInstance(currentAccount).getUser(uid); + user = getMessagesController().getUser(uid); } else { - chat = MessagesController.getInstance(currentAccount).getChat(-uid); + chat = getMessagesController().getChat(-uid); } if (user == null && chat == null) { continue; @@ -6352,7 +6427,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedRound", messageObjectsToForward.size())); } else if (type == 14) { replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedMusic", messageObjectsToForward.size())); - } else if (type == 13) { + } else if (type == MessageObject.TYPE_STICKER || type == MessageObject.TYPE_ANIMATED_STICKER) { replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedSticker", messageObjectsToForward.size())); } else if (type == 17) { replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedPoll", messageObjectsToForward.size())); @@ -6435,7 +6510,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (photoSize == thumbPhotoSize) { thumbPhotoSize = null; } - if (photoSize == null || photoSize instanceof TLRPC.TL_photoSizeEmpty || photoSize.location instanceof TLRPC.TL_fileLocationUnavailable || thumbMediaMessageObject.type == 13 || thumbMediaMessageObject != null && thumbMediaMessageObject.isSecretMedia()) { + if (photoSize == null || photoSize instanceof TLRPC.TL_photoSizeEmpty || photoSize.location instanceof TLRPC.TL_fileLocationUnavailable || thumbMediaMessageObject.isAnyKindOfSticker() || thumbMediaMessageObject != null && thumbMediaMessageObject.isSecretMedia()) { replyImageView.setImageBitmap(null); replyImageLocation = null; replyImageLocationObject = null; @@ -6482,6 +6557,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatActivityEnterView.hideTopView(animated); chatActivityEnterView.setReplyingMessageObject(null); chatActivityEnterView.setEditingMessageObject(null, false); + topViewWasVisible = 0; replyingMessageObject = null; editingMessageObject = null; forwardingMessages = null; @@ -6501,11 +6577,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return false; } if (currentEncryptedChat != null) { - MessagesController.getInstance(currentAccount).markMessageAsRead(dialog_id, messageObject.messageOwner.random_id, messageObject.messageOwner.ttl); + getMessagesController().markMessageAsRead(dialog_id, messageObject.messageOwner.random_id, messageObject.messageOwner.ttl); } else { - MessagesController.getInstance(currentAccount).markMessageAsRead(messageObject.getId(), ChatObject.isChannel(currentChat) ? currentChat.id : 0, null, messageObject.messageOwner.ttl, 0); + getMessagesController().markMessageAsRead(messageObject.getId(), ChatObject.isChannel(currentChat) ? currentChat.id : 0, null, messageObject.messageOwner.ttl, 0); } - messageObject.messageOwner.destroyTime = messageObject.messageOwner.ttl + ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + messageObject.messageOwner.destroyTime = messageObject.messageOwner.ttl + getConnectionsManager().getCurrentTime(); return true; } @@ -6538,6 +6614,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not loadingForward = false; waitingForReplyMessageLoad = false; startLoadFromMessageId = 0; + showScrollToMessageError = false; last_message_id = 0; unreadMessageObject = null; createUnreadMessageAfterId = 0; @@ -6558,7 +6635,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { clearChatData(); waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance(currentAccount).loadMessages(dialog_id, 30, 0, 0, true, 0, classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, 30, 0, 0, true, 0, classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } } @@ -6715,7 +6792,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } } - MessagesController.getInstance(currentAccount).addToPollsQueue(dialog_id, pollsToCheck); + getMessagesController().addToPollsQueue(dialog_id, pollsToCheck); if (videoPlayerContainer != null) { if (!foundTextureViewMessage) { MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); @@ -6810,7 +6887,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { inlineUpdate1(); } - MessagesController.getInstance(currentAccount).markDialogAsRead(dialog_id, maxPositiveUnreadId, maxNegativeUnreadId, maxUnreadDate, false, counterDicrement, maxPositiveUnreadId == minMessageId[0] || maxNegativeUnreadId == minMessageId[0]); + getMessagesController().markDialogAsRead(dialog_id, maxPositiveUnreadId, maxNegativeUnreadId, maxUnreadDate, false, counterDicrement, maxPositiveUnreadId == minMessageId[0] || maxNegativeUnreadId == minMessageId[0]); firstUnreadSent = true; } else if (!firstUnreadSent) { if (chatLayoutManager.findFirstVisibleItemPosition() == 0) { @@ -6820,7 +6897,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { inlineUpdate2(); } - MessagesController.getInstance(currentAccount).markDialogAsRead(dialog_id, minMessageId[0], minMessageId[0], maxDate[0], false, 0, true); + getMessagesController().markDialogAsRead(dialog_id, minMessageId[0], minMessageId[0], maxDate[0], false, 0, true); firstUnreadSent = true; } } @@ -6854,7 +6931,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } private void toggleMute(boolean instant) { - boolean muted = MessagesController.getInstance(currentAccount).isDialogMuted(dialog_id); + boolean muted = getMessagesController().isDialogMuted(dialog_id); if (!muted) { if (instant) { long flags; @@ -6862,15 +6939,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not SharedPreferences.Editor editor = preferences.edit(); editor.putInt("notify2_" + dialog_id, 2); flags = 1; - MessagesStorage.getInstance(currentAccount).setDialogFlags(dialog_id, flags); + getMessagesStorage().setDialogFlags(dialog_id, flags); editor.commit(); - TLRPC.Dialog dialog = MessagesController.getInstance(currentAccount).dialogs_dict.get(dialog_id); + TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(dialog_id); if (dialog != null) { dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); dialog.notify_settings.mute_until = Integer.MAX_VALUE; } - NotificationsController.getInstance(currentAccount).updateServerNotificationsSettings(dialog_id); - NotificationsController.getInstance(currentAccount).removeNotificationsForDialog(dialog_id); + getNotificationsController().updateServerNotificationsSettings(dialog_id); + getNotificationsController().removeNotificationsForDialog(dialog_id); } else { showDialog(AlertsCreator.createMuteAlert(getParentActivity(), dialog_id)); } @@ -6878,13 +6955,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); SharedPreferences.Editor editor = preferences.edit(); editor.putInt("notify2_" + dialog_id, 0); - MessagesStorage.getInstance(currentAccount).setDialogFlags(dialog_id, 0); + getMessagesStorage().setDialogFlags(dialog_id, 0); editor.commit(); - TLRPC.Dialog dialog = MessagesController.getInstance(currentAccount).dialogs_dict.get(dialog_id); + TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(dialog_id); if (dialog != null) { dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); } - NotificationsController.getInstance(currentAccount).updateServerNotificationsSettings(dialog_id); + getNotificationsController().updateServerNotificationsSettings(dialog_id); } } @@ -7004,7 +7081,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (query) { - if (currentEncryptedChat != null && !MessagesStorage.getInstance(currentAccount).checkMessageId(dialog_id, startLoadFromMessageId)) { + if (currentEncryptedChat != null && !getMessagesStorage().checkMessageId(dialog_id, startLoadFromMessageId)) { return; } /*clearChatData(); @@ -7019,11 +7096,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not removeSelectedMessageHighlight(); scrollToMessagePosition = -10000; startLoadFromMessageId = id; + showScrollToMessageError = true; if (id == createUnreadMessageAfterId) { createUnreadMessageAfterIdLoading = true; } waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance(currentAccount).loadMessages(loadIndex == 0 ? dialog_id : mergeDialogId, AndroidUtilities.isTablet() ? 30 : 20, startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + getMessagesController().loadMessages(loadIndex == 0 ? dialog_id : mergeDialogId, AndroidUtilities.isTablet() ? 30 : 20, startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); //emptyViewContainer.setVisibility(View.INVISIBLE); } else { showFloatingDateView(false); @@ -7199,7 +7277,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not bottomOverlayText.setText(LocaleController.getString("EncryptionRejected", R.string.EncryptionRejected)); bottomOverlay.setVisibility(View.VISIBLE); chatActivityEnterView.setFieldText(""); - DataQuery.getInstance(currentAccount).cleanDraft(dialog_id, false); + getMediaDataController().cleanDraft(dialog_id, false); hideKeyboard = true; } else if (currentEncryptedChat instanceof TLRPC.TL_encryptedChat) { bottomOverlay.setVisibility(View.INVISIBLE); @@ -7254,7 +7332,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not processSelectedAttach(attach_video); } else if (requestCode == 101 && currentUser != null) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - VoIPHelper.startCall(currentUser, getParentActivity(), MessagesController.getInstance(currentAccount).getUserFull(currentUser.id)); + VoIPHelper.startCall(currentUser, getParentActivity(), getMessagesController().getUserFull(currentUser.id)); } else { VoIPHelper.permissionDenied(getParentActivity(), null); } @@ -7314,14 +7392,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { if (messageObject.isVoice()) { return 2; - } else if (messageObject.isSticker()) { + } else if (messageObject.isSticker() || messageObject.isAnimatedSticker()) { TLRPC.InputStickerSet inputStickerSet = messageObject.getInputStickerSet(); if (inputStickerSet instanceof TLRPC.TL_inputStickerSetID) { - if (!DataQuery.getInstance(currentAccount).isStickerPackInstalled(inputStickerSet.id)) { + if (!getMediaDataController().isStickerPackInstalled(inputStickerSet.id)) { return 7; } } else if (inputStickerSet instanceof TLRPC.TL_inputStickerSetShortName) { - if (!DataQuery.getInstance(currentAccount).isStickerPackInstalled(inputStickerSet.short_name)) { + if (!getMediaDataController().isStickerPackInstalled(inputStickerSet.short_name)) { return 7; } } @@ -7384,10 +7462,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { if (messageObject.isVoice()) { return 2; - } else if (messageObject.isSticker()) { + } else if (messageObject.isSticker() || messageObject.isAnimatedSticker()) { TLRPC.InputStickerSet inputStickerSet = messageObject.getInputStickerSet(); if (inputStickerSet instanceof TLRPC.TL_inputStickerSetShortName) { - if (!DataQuery.getInstance(currentAccount).isStickerPackInstalled(inputStickerSet.short_name)) { + if (!getMediaDataController().isStickerPackInstalled(inputStickerSet.short_name)) { return 7; } } @@ -7465,7 +7543,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (messageObject.type == 0 || messageObject.caption != null) { selectedMessagesCanCopyIds[index].remove(messageObject.getId()); } - if (messageObject.isSticker()) { + if (messageObject.isSticker() || messageObject.isAnimatedSticker()) { selectedMessagesCanStarIds[index].remove(messageObject.getId()); } if (messageObject.canEditMessage(currentChat)) { @@ -7485,7 +7563,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (messageObject.type == 0 || messageObject.caption != null) { selectedMessagesCanCopyIds[index].put(messageObject.getId(), messageObject); } - if (messageObject.isSticker()) { + if (messageObject.isSticker() || messageObject.isAnimatedSticker()) { selectedMessagesCanStarIds[index].put(messageObject.getId(), messageObject); } if (messageObject.canEditMessage(currentChat)) { @@ -7548,7 +7626,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not int copyVisible = copyItem.getVisibility(); int starVisible = starItem.getVisibility(); copyItem.setVisibility(selectedMessagesCanCopyIds[0].size() + selectedMessagesCanCopyIds[1].size() != 0 ? View.VISIBLE : View.GONE); - starItem.setVisibility(DataQuery.getInstance(currentAccount).canAddStickerToFavorites() && (selectedMessagesCanStarIds[0].size() + selectedMessagesCanStarIds[1].size()) == selectedCount ? View.VISIBLE : View.GONE); + starItem.setVisibility(getMediaDataController().canAddStickerToFavorites() && (selectedMessagesCanStarIds[0].size() + selectedMessagesCanStarIds[1].size()) == selectedCount ? View.VISIBLE : View.GONE); int newCopyVisible = copyItem.getVisibility(); int newStarVisible = starItem.getVisibility(); actionBar.createActionMode().getItem(delete).setVisibility(cantDeleteMessagesCount == 0 ? View.VISIBLE : View.GONE); @@ -7556,7 +7634,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not for (int a = 0; a < 2; a++) { for (int b = 0; b < selectedMessagesCanStarIds[a].size(); b++) { MessageObject msg = selectedMessagesCanStarIds[a].valueAt(b); - if (!DataQuery.getInstance(currentAccount).isStickerInFavorites(msg.getDocument())) { + if (!getMediaDataController().isStickerInFavorites(msg.getDocument())) { hasUnfavedSelected = true; break; } @@ -7731,7 +7809,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (currentUser != null) { if (currentUser.self) { avatarContainer.setTitle(LocaleController.getString("SavedMessages", R.string.SavedMessages)); - } else if (!MessagesController.isSupportUser(currentUser) && ContactsController.getInstance(currentAccount).contactsDict.get(currentUser.id) == null && (ContactsController.getInstance(currentAccount).contactsDict.size() != 0 || !ContactsController.getInstance(currentAccount).isLoadingContacts())) { + } else if (!MessagesController.isSupportUser(currentUser) && getContactsController().contactsDict.get(currentUser.id) == null && (getContactsController().contactsDict.size() != 0 || !getContactsController().isLoadingContacts())) { if (!TextUtils.isEmpty(currentUser.phone)) { avatarContainer.setTitle(PhoneFormat.getInstance().format("+" + currentUser.phone)); } else { @@ -7782,7 +7860,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (avatarContainer == null) { return; } - Drawable rightIcon = MessagesController.getInstance(currentAccount).isDialogMuted(dialog_id) ? Theme.chat_muteIconDrawable : null; + Drawable rightIcon = getMessagesController().isDialogMuted(dialog_id) ? Theme.chat_muteIconDrawable : null; avatarContainer.setTitleIcons(currentEncryptedChat != null ? Theme.chat_lockIconDrawable : null, rightIcon); if (muteItem != null) { if (rightIcon != null) { @@ -7795,13 +7873,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private void checkAndUpdateAvatar() { if (currentUser != null) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(currentUser.id); + TLRPC.User user = getMessagesController().getUser(currentUser.id); if (user == null) { return; } currentUser = user; } else if (currentChat != null) { - TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(currentChat.id); + TLRPC.Chat chat = getMessagesController().getChat(currentChat.id); if (chat == null) { return; } @@ -7838,9 +7916,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not }, this); } else { fillEditingMediaWithCaption(caption, null); - SendMessagesHelper.prepareSendingVideo(videoPath, 0, 0, 0, 0, null, dialog_id, replyingMessageObject, null, null, 0, editingMessageObject); + SendMessagesHelper.prepareSendingVideo(getAccountInstance(), videoPath, 0, 0, 0, 0, null, dialog_id, replyingMessageObject, null, null, 0, editingMessageObject); hideFieldPanel(false); - DataQuery.getInstance(currentAccount).cleanDraft(dialog_id, true); + getMediaDataController().cleanDraft(dialog_id, true); } } @@ -7896,7 +7974,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return; } fillEditingMediaWithCaption(null, null); - SendMessagesHelper.prepareSendingDocument(tempPath, originalPath, null, null, null, dialog_id, replyingMessageObject, null, editingMessageObject); + SendMessagesHelper.prepareSendingDocument(getAccountInstance(), tempPath, originalPath, null, null, null, dialog_id, replyingMessageObject, null, editingMessageObject); hideFieldPanel(false); } @@ -7932,10 +8010,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } else { fillEditingMediaWithCaption(null, null); - SendMessagesHelper.prepareSendingPhoto(null, uri, dialog_id, replyingMessageObject, null, null, null, null, 0, editingMessageObject); + SendMessagesHelper.prepareSendingPhoto(getAccountInstance(), null, uri, dialog_id, replyingMessageObject, null, null, null, null, 0, editingMessageObject); } hideFieldPanel(false); - DataQuery.getInstance(currentAccount).cleanDraft(dialog_id, true); + getMediaDataController().cleanDraft(dialog_id, true); } else if (requestCode == 21) { if (data == null) { showAttachmentError(); @@ -7952,7 +8030,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not showAttachmentError(); } hideFieldPanel(false); - DataQuery.getInstance(currentAccount).cleanDraft(dialog_id, true); + getMediaDataController().cleanDraft(dialog_id, true); } } } @@ -7994,12 +8072,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (guid == classGuid) { setItemAnimationsEnabled(false); if (!openAnimationEnded) { - NotificationCenter.getInstance(currentAccount).setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.chatInfoDidLoad, NotificationCenter.dialogsNeedReload, + getNotificationCenter().setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.chatInfoDidLoad, NotificationCenter.dialogsNeedReload, NotificationCenter.closeChats, NotificationCenter.botKeyboardDidLoad, NotificationCenter.userInfoDidLoad, NotificationCenter.needDeleteDialog/*, NotificationCenter.botInfoDidLoad*/}); } int queryLoadIndex = (Integer) args[11]; int index = waitingForLoad.indexOf(queryLoadIndex); - int currentUserId = UserConfig.getInstance(currentAccount).getClientUserId(); + int currentUserId = getUserConfig().getClientUserId(); if (index == -1) { return; } else { @@ -8132,7 +8210,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not Collections.reverse(messArr); } if (currentEncryptedChat == null) { - DataQuery.getInstance(currentAccount).loadReplyMessagesForMessages(messArr, dialog_id); + getMediaDataController().loadReplyMessagesForMessages(messArr, dialog_id); } int approximateHeightSum = 0; if ((load_type == 2 || load_type == 1) && messArr.isEmpty() && !isCache) { @@ -8156,6 +8234,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } for (int a = 0; a < messArr.size(); a++) { MessageObject obj = messArr.get(a); + int messageId = obj.getId(); approximateHeightSum += obj.getApproximateHeight(); if (currentUser != null) { @@ -8166,7 +8245,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not obj.setIsRead(); } } - if (messagesDict[loadIndex].indexOfKey(obj.getId()) >= 0) { + if (messagesDict[loadIndex].indexOfKey(messageId) >= 0) { continue; } addToPolls(obj, null); @@ -8179,16 +8258,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not obj.audioProgressSec = player.audioProgressSec; obj.audioPlayerDuration = player.audioPlayerDuration; } - if (loadIndex == 0 && ChatObject.isChannel(currentChat) && obj.getId() == 1) { + if (loadIndex == 0 && ChatObject.isChannel(currentChat) && messageId == 1) { endReached[loadIndex] = true; cacheEndReached[loadIndex] = true; } - if (obj.getId() > 0) { - maxMessageId[loadIndex] = Math.min(obj.getId(), maxMessageId[loadIndex]); - minMessageId[loadIndex] = Math.max(obj.getId(), minMessageId[loadIndex]); + if (messageId > 0) { + maxMessageId[loadIndex] = Math.min(messageId, maxMessageId[loadIndex]); + minMessageId[loadIndex] = Math.max(messageId, minMessageId[loadIndex]); } else if (currentEncryptedChat != null) { - maxMessageId[loadIndex] = Math.max(obj.getId(), maxMessageId[loadIndex]); - minMessageId[loadIndex] = Math.min(obj.getId(), minMessageId[loadIndex]); + maxMessageId[loadIndex] = Math.max(messageId, maxMessageId[loadIndex]); + minMessageId[loadIndex] = Math.min(messageId, minMessageId[loadIndex]); } if (obj.messageOwner.date != 0) { maxDate[loadIndex] = Math.max(maxDate[loadIndex], obj.messageOwner.date); @@ -8197,7 +8276,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } - if (obj.getId() == last_message_id) { + if (messageId == last_message_id) { forwardEndReached[loadIndex] = true; } @@ -8213,13 +8292,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not continue; } - if (needAnimateToMessage != null && needAnimateToMessage.getId() == obj.getId() && obj.getId() < 0 && obj.type == 5) { + if (needAnimateToMessage != null && needAnimateToMessage.getId() == messageId && messageId < 0 && obj.type == 5) { obj = needAnimateToMessage; animatingMessageObjects.add(obj); needAnimateToMessage = null; } - messagesDict[loadIndex].put(obj.getId(), obj); + messagesDict[loadIndex].put(messageId, obj); ArrayList dayArray = messagesByDays.get(obj.dateKey); if (dayArray == null) { @@ -8314,7 +8393,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not prevObj = null; } } - if (load_type == 2 && obj.getId() == first_unread_id) { + if (load_type == 2 && messageId == first_unread_id) { if (approximateHeightSum > AndroidUtilities.displaySize.y / 2 || !forwardEndReached[0]) { TLRPC.Message dateMsg = new TLRPC.TL_message(); dateMsg.message = ""; @@ -8328,10 +8407,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not scrollToMessagePosition = -10000; newRowsCount++; } - } else if ((load_type == 3 || load_type == 4) && obj.getId() == startLoadFromMessageId) { + } else if ((load_type == 3 || load_type == 4) && (startLoadFromMessageId < 0 && messageId == startLoadFromMessageId || startLoadFromMessageId > 0 && messageId > 0 && messageId <= startLoadFromMessageId)) { removeSelectedMessageHighlight(); - if (needSelectFromMessageId) { - highlightMessageId = obj.getId(); + if (needSelectFromMessageId && messageId == startLoadFromMessageId) { + highlightMessageId = messageId; + } + if (showScrollToMessageError && messageId != startLoadFromMessageId) { + AlertsCreator.showSimpleToast(ChatActivity.this, LocaleController.getString("MessageNotFound", R.string.MessageNotFound)); } scrollToMessage = obj; startLoadFromMessageId = 0; @@ -8340,7 +8422,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } if (load_type != 2 && unreadMessageObject == null && createUnreadMessageAfterId != 0 && - (currentEncryptedChat == null && !obj.isOut() && obj.getId() >= createUnreadMessageAfterId || currentEncryptedChat != null && !obj.isOut() && obj.getId() <= createUnreadMessageAfterId) && + (currentEncryptedChat == null && !obj.isOut() && messageId >= createUnreadMessageAfterId || currentEncryptedChat != null && !obj.isOut() && messageId <= createUnreadMessageAfterId) && (load_type == 1 || prevObj != null || prevObj == null && createUnreadLoading && a == messArr.size() - 1)) { TLRPC.Message dateMsg = new TLRPC.TL_message(); dateMsg.message = ""; @@ -8604,7 +8686,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } if (newRowsCount == 0 && mergeDialogId != 0 && loadIndex == 0) { - NotificationCenter.getInstance(currentAccount).setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.chatInfoDidLoad, NotificationCenter.dialogsNeedReload, + getNotificationCenter().setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.chatInfoDidLoad, NotificationCenter.dialogsNeedReload, NotificationCenter.closeChats, NotificationCenter.messagesDidLoad, NotificationCenter.botKeyboardDidLoad, NotificationCenter.userInfoDidLoad, NotificationCenter.needDeleteDialog/*, NotificationCenter.botInfoDidLoad*/}); } if (showDateAfter) { @@ -8651,12 +8733,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not int updateMask = (Integer) args[0]; if ((updateMask & MessagesController.UPDATE_MASK_NAME) != 0 || (updateMask & MessagesController.UPDATE_MASK_CHAT_NAME) != 0) { if (currentChat != null) { - TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(currentChat.id); + TLRPC.Chat chat = getMessagesController().getChat(currentChat.id); if (chat != null) { currentChat = chat; } } else if (currentUser != null) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(currentUser.id); + TLRPC.User user = getMessagesController().getUser(currentUser.id); if (user != null) { currentUser = user; } @@ -8678,7 +8760,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not updateSubtitle = true; } if ((updateMask & MessagesController.UPDATE_MASK_CHAT) != 0 && currentChat != null) { - TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(currentChat.id); + TLRPC.Chat chat = getMessagesController().getChat(currentChat.id); if (chat == null) { return; } @@ -8698,12 +8780,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not avatarContainer.updateSubtitle(); } if ((updateMask & MessagesController.UPDATE_MASK_USER_PHONE) != 0) { - updateContactStatus(); + updateTopPanel(true); } } else if (id == NotificationCenter.didReceiveNewMessages) { long did = (Long) args[0]; if (did == dialog_id) { - int currentUserId = UserConfig.getInstance(currentAccount).getClientUserId(); + int currentUserId = getUserConfig().getClientUserId(); boolean updateChat = false; boolean hasFromMe = false; ArrayList arr = (ArrayList) args[1]; @@ -8732,7 +8814,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (currentChat != null) { if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatDeleteUser && messageObject.messageOwner.action.user_id == currentUserId || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatAddUser && messageObject.messageOwner.action.users.contains(currentUserId)) { - TLRPC.Chat newChat = MessagesController.getInstance(currentAccount).getChat(currentChat.id); + TLRPC.Chat newChat = getMessagesController().getChat(currentChat.id); if (newChat != null) { currentChat = newChat; checkActionBarMenu(); @@ -8781,6 +8863,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not for (int a = 0; a < arr.size(); a++) { MessageObject obj = arr.get(a); + int messageId = obj.getId(); if (currentUser != null && (currentUser.bot && obj.isOut() || currentUser.id == currentUserId)) { obj.setIsRead(); } @@ -8801,7 +8884,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not scrollToLastMessage(false); return; } - if (obj.type < 0 || messagesDict[0].indexOfKey(obj.getId()) >= 0) { + if (obj.type < 0 || messagesDict[0].indexOfKey(messageId) >= 0) { continue; } if (currentChat != null && currentChat.creator && (action instanceof TLRPC.TL_messageActionChatCreate || action instanceof TLRPC.TL_messageActionChatEditPhoto && messages.size() < 4)) { @@ -8813,12 +8896,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not addToPolls(obj, null); obj.checkLayout(); currentMaxDate = Math.max(currentMaxDate, obj.messageOwner.date); - if (obj.getId() > 0) { - currentMinMsgId = Math.max(obj.getId(), currentMinMsgId); - last_message_id = Math.max(last_message_id, obj.getId()); + if (messageId > 0) { + currentMinMsgId = Math.max(messageId, currentMinMsgId); + last_message_id = Math.max(last_message_id, messageId); } else if (currentEncryptedChat != null) { - currentMinMsgId = Math.min(obj.getId(), currentMinMsgId); - last_message_id = Math.min(last_message_id, obj.getId()); + currentMinMsgId = Math.min(messageId, currentMinMsgId); + last_message_id = Math.min(last_message_id, messageId); } if (obj.messageOwner.mentioned && obj.isContentUnread()) { @@ -8852,6 +8935,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not for (int a = 0; a < arr.size(); a++) { int placeToPaste = -1; MessageObject obj = arr.get(a); + int messageId = obj.getId(); if (isSecretChat()) { checkSecretMessageForLocation(obj); } @@ -8862,7 +8946,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (avatarContainer != null && currentEncryptedChat != null && action instanceof TLRPC.TL_messageEncryptedAction && action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionSetMessageTTL) { avatarContainer.setTime(((TLRPC.TL_decryptedMessageActionSetMessageTTL) action.encryptedAction).ttl_seconds); } - if (obj.type < 0 || messagesDict[0].indexOfKey(obj.getId()) >= 0) { + if (obj.type < 0 || messagesDict[0].indexOfKey(messageId) >= 0) { continue; } if (currentChat != null && currentChat.creator && (action instanceof TLRPC.TL_messageActionChatCreate || action instanceof TLRPC.TL_messageActionChatEditPhoto && messages.size() < 4)) { @@ -8965,15 +9049,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not hasFromMe = true; } - if (obj.getId() > 0) { - maxMessageId[0] = Math.min(obj.getId(), maxMessageId[0]); - minMessageId[0] = Math.max(obj.getId(), minMessageId[0]); + if (messageId > 0) { + maxMessageId[0] = Math.min(messageId, maxMessageId[0]); + minMessageId[0] = Math.max(messageId, minMessageId[0]); } else if (currentEncryptedChat != null) { - maxMessageId[0] = Math.max(obj.getId(), maxMessageId[0]); - minMessageId[0] = Math.min(obj.getId(), minMessageId[0]); + maxMessageId[0] = Math.max(messageId, maxMessageId[0]); + minMessageId[0] = Math.min(messageId, minMessageId[0]); } maxDate[0] = Math.max(maxDate[0], obj.messageOwner.date); - messagesDict[0].put(obj.getId(), obj); + messagesDict[0].put(messageId, obj); ArrayList dayArray = messagesByDays.get(obj.dateKey); if (placeToPaste > messages.size()) { placeToPaste = messages.size(); @@ -9038,7 +9122,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } if (webpagesToReload != null) { - MessagesController.getInstance(currentAccount).reloadWebPages(dialog_id, webpagesToReload); + getMessagesController().reloadWebPages(dialog_id, webpagesToReload); } if (newGroups != null) { for (int a = 0; a < newGroups.size(); a++) { @@ -9112,7 +9196,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not checkAndUpdateAvatar(); } if (reloadMegagroup) { - MessagesController.getInstance(currentAccount).loadFullChat(currentChat.id, 0, true); + getMessagesController().loadFullChat(currentChat.id, 0, true); } } } else if (id == NotificationCenter.closeChats) { @@ -9213,12 +9297,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (chatInfo != null && chatInfo.pinned_msg_id == mid) { pinnedMessageObject = null; chatInfo.pinned_msg_id = 0; - MessagesStorage.getInstance(currentAccount).updateChatPinnedMessage(chatInfo.id, 0); + getMessagesStorage().updateChatPinnedMessage(chatInfo.id, 0); updatePinnedMessageView(true); } else if (userInfo != null && userInfo.pinned_msg_id == mid) { pinnedMessageObject = null; userInfo.pinned_msg_id = 0; - MessagesStorage.getInstance(currentAccount).updateUserPinnedMessage(chatInfo.id, 0); + getMessagesStorage().updateUserPinnedMessage(chatInfo.id, 0); updatePinnedMessageView(true); } messages.remove(b); @@ -9256,7 +9340,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not maxDate[0] = maxDate[1] = Integer.MIN_VALUE; minDate[0] = minDate[1] = 0; waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance(currentAccount).loadMessages(dialog_id, 30, 0, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, 30, 0, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); loading = true; } else { if (botButtons != null) { @@ -9307,7 +9391,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (userInfo != null) { userInfo.pinned_msg_id = 0; } - MessagesStorage.getInstance(currentAccount).updateChatPinnedMessage(channelId, 0); + getMessagesStorage().updateChatPinnedMessage(channelId, 0); updatePinnedMessageView(true); } if (obj != null) { @@ -9388,7 +9472,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not maxDate[0] = maxDate[1] = Integer.MIN_VALUE; minDate[0] = minDate[1] = 0; waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance(currentAccount).loadMessages(dialog_id, 30, 0, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, 30, 0, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); loading = true; } else { if (botButtons != null) { @@ -9521,7 +9605,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not ArrayList messArr = new ArrayList<>(); messArr.add(obj); if (currentEncryptedChat == null) { - DataQuery.getInstance(currentAccount).loadReplyMessagesForMessages(messArr, dialog_id); + getMediaDataController().loadReplyMessagesForMessages(messArr, dialog_id); } if (chatAdapter != null) { chatAdapter.updateRowWithMessageObject(obj, true); @@ -9531,7 +9615,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not moveScrollToLastMessage(); } } - NotificationsController.getInstance(currentAccount).playOutChatSound(); + getNotificationsController().playOutChatSound(); } } else if (id == NotificationCenter.messageReceivedByAck) { Integer msgId = (Integer) args[0]; @@ -9561,7 +9645,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } if (lastDate == 0 || Math.abs(System.currentTimeMillis() / 1000 - lastDate) > 60 * 60) { - MessagesController.getInstance(currentAccount).loadChannelParticipants(currentChat.id); + getMessagesController().loadChannelParticipants(currentChat.id); } } if (chatFull.participants == null && chatInfo != null) { @@ -9586,7 +9670,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not avatarContainer.updateSubtitle(); } if (isBroadcast) { - SendMessagesHelper.getInstance(currentAccount).setCurrentChatInfo(chatInfo); + getSendMessagesHelper().setCurrentChatInfo(chatInfo); } if (chatInfo instanceof TLRPC.TL_chatFull) { hasBotsCommands = false; @@ -9595,11 +9679,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not URLSpanBotCommand.enabled = false; for (int a = 0; a < chatInfo.participants.participants.size(); a++) { TLRPC.ChatParticipant participant = chatInfo.participants.participants.get(a); - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(participant.user_id); + TLRPC.User user = getMessagesController().getUser(participant.user_id); if (user != null && user.bot) { URLSpanBotCommand.enabled = true; botsCount++; - DataQuery.getInstance(currentAccount).loadBotInfo(user.id, true, classGuid); + getMediaDataController().loadBotInfo(user.id, true, classGuid); } } if (chatListView != null) { @@ -9673,10 +9757,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } } else if (id == NotificationCenter.contactsDidLoad) { - updateContactStatus(); - if (currentEncryptedChat != null) { - updateSpamView(); - } + updateTopPanel(true); if (avatarContainer != null) { avatarContainer.updateSubtitle(); } @@ -9684,7 +9765,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not TLRPC.EncryptedChat chat = (TLRPC.EncryptedChat) args[0]; if (currentEncryptedChat != null && chat.id == currentEncryptedChat.id) { currentEncryptedChat = chat; - updateContactStatus(); + updateTopPanel(true); updateSecretStatus(); initStickers(); if (chatActivityEnterView != null) { @@ -9721,7 +9802,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (id == NotificationCenter.blockedUsersDidLoad) { if (currentUser != null) { boolean oldValue = userBlocked; - userBlocked = MessagesController.getInstance(currentAccount).blockedUsers.indexOfKey(currentUser.id) >= 0; + userBlocked = getMessagesController().blockedUsers.indexOfKey(currentUser.id) >= 0; if (oldValue != userBlocked) { updateBottomOverlay(); } @@ -9848,7 +9929,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not FileLog.e(e); } } - animation.seekTo(messageObject.audioProgressMs, !FileLoader.getInstance(currentAccount).isLoadingVideo(messageObject.getDocument(), true)); + animation.seekTo(messageObject.audioProgressMs, !getFileLoader().isLoadingVideo(messageObject.getDocument(), true)); } break; } @@ -10284,8 +10365,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } else if (id == NotificationCenter.peerSettingsDidLoad) { long did = (Long) args[0]; - if (did == dialog_id) { - updateSpamView(); + if (did == dialog_id || currentUser != null && currentUser.id == did) { + updateTopPanel(!paused); } } else if (id == NotificationCenter.newDraftReceived) { long did = (Long) args[0]; @@ -10433,7 +10514,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not startLoadFromMessageId = 0; needSelectFromMessageId = false; waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance(currentAccount).loadMessages(dialog_id, AndroidUtilities.isTablet() ? 30 : 20, 0, 0, true, 0, classGuid, 2, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + getMessagesController().loadMessages(dialog_id, AndroidUtilities.isTablet() ? 30 : 20, 0, 0, true, 0, classGuid, 2, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } else { if (progressView != null) { progressView.setVisibility(View.INVISIBLE); @@ -10459,7 +10540,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not inlineReturn = 0; chatActivityEnterView.setFieldText(query); } else { - DataQuery.getInstance(currentAccount).saveDraft(inlineReturn, query, null, null, false); + getMediaDataController().saveDraft(inlineReturn, query, null, null, false); if (parentLayout.fragmentsStack.size() > 1) { BaseFragment prevFragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2); if (prevFragment instanceof ChatActivity && ((ChatActivity) prevFragment).dialog_id == inlineReturn) { @@ -10479,9 +10560,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } /*ActionBarLayout parentLayout = ChatActivity.this.parentLayout; if (lastFragment != null) { - NotificationCenter.getInstance(currentAccount).removeObserver(lastFragment, NotificationCenter.closeChats); + getNotificationCenter().removeObserver(lastFragment, NotificationCenter.closeChats); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.closeChats);*/ + getNotificationCenter().postNotificationName(NotificationCenter.closeChats);*/ presentFragment(new ChatActivity(bundle), true); } } @@ -10530,15 +10611,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { AndroidUtilities.runOnUIThread(() -> { if (lastFragment != null) { - NotificationCenter.getInstance(currentAccount).removeObserver(lastFragment, NotificationCenter.closeChats); + getNotificationCenter().removeObserver(lastFragment, NotificationCenter.closeChats); } - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.closeChats); + getNotificationCenter().postNotificationName(NotificationCenter.closeChats); final Bundle bundle = new Bundle(); bundle.putInt("chat_id", obj.messageOwner.action.channel_id); actionBarLayout.presentFragment(new ChatActivity(bundle), true); }); } - AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).loadFullChat(channelId, 0, true), 1000); + AndroidUtilities.runOnUIThread(() -> getMessagesController().loadFullChat(channelId, 0, true), 1000); } private void addToPolls(MessageObject obj, MessageObject old) { @@ -10586,17 +10667,21 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void onTransitionAnimationStart(boolean isOpen, boolean backward) { - NotificationCenter.getInstance(currentAccount).setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.chatInfoDidLoad, NotificationCenter.dialogsNeedReload, - NotificationCenter.closeChats, NotificationCenter.messagesDidLoad, NotificationCenter.botKeyboardDidLoad, NotificationCenter.userInfoDidLoad, NotificationCenter.needDeleteDialog/*, NotificationCenter.botInfoDidLoad*/}); - NotificationCenter.getInstance(currentAccount).setAnimationInProgress(true); if (isOpen) { + getNotificationCenter().setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.chatInfoDidLoad, NotificationCenter.dialogsNeedReload, + NotificationCenter.closeChats, NotificationCenter.messagesDidLoad, NotificationCenter.botKeyboardDidLoad, NotificationCenter.userInfoDidLoad, NotificationCenter.needDeleteDialog/*, NotificationCenter.botInfoDidLoad*/}); openAnimationEnded = false; + } else { + if (chatActivityEnterView != null) { + chatActivityEnterView.onBeginHide(); + } } + getNotificationCenter().setAnimationInProgress(true); } @Override public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { - NotificationCenter.getInstance(currentAccount).setAnimationInProgress(false); + getNotificationCenter().setAnimationInProgress(false); if (isOpen) { openAnimationEnded = true; if (Build.VERSION.SDK_INT >= 21) { @@ -10636,7 +10721,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override protected void onDialogDismiss(Dialog dialog) { if (closeChatDialog != null && dialog == closeChatDialog) { - MessagesController.getInstance(currentAccount).deleteDialog(dialog_id, 0); + 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); removeSelfFromStack(); @@ -10670,8 +10755,20 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not stringBuilder = new SpannableStringBuilder(LocaleController.getString("Mono", R.string.Mono)); stringBuilder.setSpan(new TypefaceSpan(Typeface.MONOSPACE), 0, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); menu.add(R.id.menu_groupbolditalic, R.id.menu_mono, 8, stringBuilder); - menu.add(R.id.menu_groupbolditalic, R.id.menu_link, 9, LocaleController.getString("CreateLink", R.string.CreateLink)); - menu.add(R.id.menu_groupbolditalic, R.id.menu_regular, 10, LocaleController.getString("Regular", R.string.Regular)); + if (currentEncryptedChat != null && AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 101) { + stringBuilder = new SpannableStringBuilder(LocaleController.getString("Strike", R.string.Strike)); + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_STRIKE; + stringBuilder.setSpan(new TextStyleSpan(run), 0, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + menu.add(R.id.menu_groupbolditalic, R.id.menu_strike, 9, stringBuilder); + stringBuilder = new SpannableStringBuilder(LocaleController.getString("Underline", R.string.Underline)); + run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_UNDERLINE; + stringBuilder.setSpan(new TextStyleSpan(run), 0, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + menu.add(R.id.menu_groupbolditalic, R.id.menu_underline, 10, stringBuilder); + } + menu.add(R.id.menu_groupbolditalic, R.id.menu_link, 11, LocaleController.getString("CreateLink", R.string.CreateLink)); + menu.add(R.id.menu_groupbolditalic, R.id.menu_regular, 12, LocaleController.getString("Regular", R.string.Regular)); return true; } @@ -10682,14 +10779,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (currentChat != null) { if (ChatObject.isChannel(currentChat) && !(currentChat instanceof TLRPC.TL_channelForbidden)) { if (ChatObject.isNotInChat(currentChat)) { - if (MessagesController.getInstance(currentAccount).isJoiningChannel(currentChat.id)) { + if (getMessagesController().isJoiningChannel(currentChat.id)) { showBottomOverlayProgress(true, false); } else { bottomOverlayChatText.setText(LocaleController.getString("ChannelJoin", R.string.ChannelJoin)); showBottomOverlayProgress(false, false); } } else { - if (!MessagesController.getInstance(currentAccount).isDialogMuted(dialog_id)) { + if (!getMessagesController().isDialogMuted(dialog_id)) { bottomOverlayChatText.setText(LocaleController.getString("ChannelMute", R.string.ChannelMute)); } else { bottomOverlayChatText.setText(LocaleController.getString("ChannelUnmute", R.string.ChannelUnmute)); @@ -10991,7 +11088,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (photoSize == thumbPhotoSize) { thumbPhotoSize = null; } - if (photoSize == null || photoSize instanceof TLRPC.TL_photoSizeEmpty || photoSize.location instanceof TLRPC.TL_fileLocationUnavailable || pinnedMessageObject.type == 13) { + if (photoSize == null || photoSize instanceof TLRPC.TL_photoSizeEmpty || photoSize.location instanceof TLRPC.TL_fileLocationUnavailable || pinnedMessageObject.isAnyKindOfSticker()) { pinnedMessageImageView.setImageBitmap(null); pinnedImageLocation = null; pinnedImageLocationObject = null; @@ -11053,124 +11150,164 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not hidePinnedMessageView(animated); if (loadingPinnedMessage != pinned_msg_id) { loadingPinnedMessage = pinned_msg_id; - DataQuery.getInstance(currentAccount).loadPinnedMessage(dialog_id, ChatObject.isChannel(currentChat) ? currentChat.id : 0, pinned_msg_id, true); + getMediaDataController().loadPinnedMessage(dialog_id, ChatObject.isChannel(currentChat) ? currentChat.id : 0, pinned_msg_id, true); } } } checkListViewPaddings(); } - private void updateSpamView() { - if (reportSpamView == null) { - if (BuildVars.LOGS_ENABLED) { - FileLog.d("no spam view found"); - } + private void updateTopPanel(boolean animated) { + if (topChatPanelView == null) { return; } + SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); boolean show; + long did = dialog_id; if (currentEncryptedChat != null) { - show = !(currentEncryptedChat.admin_id == UserConfig.getInstance(currentAccount).getClientUserId() || ContactsController.getInstance(currentAccount).isLoadingContacts()) && ContactsController.getInstance(currentAccount).contactsDict.get(currentUser.id) == null; - if (show && preferences.getInt("spam3_" + dialog_id, 0) == 1) { + show = !(currentEncryptedChat.admin_id == getUserConfig().getClientUserId() || getContactsController().isLoadingContacts()) && getContactsController().contactsDict.get(currentUser.id) == null; + did = currentUser.id; + int vis = preferences.getInt("dialog_bar_vis3" + did, 0); + if (show && (vis == 1 || vis == 3)) { show = false; } } else { - show = preferences.getInt("spam3_" + dialog_id, 0) == 2; + show = preferences.getInt("dialog_bar_vis3" + did, 0) == 2; } - if (!show) { - if (reportSpamView.getTag() == null) { - if (BuildVars.LOGS_ENABLED) { - FileLog.d("hide spam button"); - } - reportSpamView.setTag(1); + boolean showShare = preferences.getBoolean("dialog_bar_share" + did, false); + boolean showReport = preferences.getBoolean("dialog_bar_report" + did, false); + boolean showBlock = preferences.getBoolean("dialog_bar_block" + did, false); + boolean showAdd = preferences.getBoolean("dialog_bar_add" + did, false); + boolean showGeo = preferences.getBoolean("dialog_bar_location" + did, false); - if (reportSpamViewAnimator != null) { - reportSpamViewAnimator.cancel(); - } - reportSpamViewAnimator = new AnimatorSet(); - reportSpamViewAnimator.playTogether(ObjectAnimator.ofFloat(reportSpamView, View.TRANSLATION_Y, -AndroidUtilities.dp(50))); - reportSpamViewAnimator.setDuration(200); - reportSpamViewAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (reportSpamViewAnimator != null && reportSpamViewAnimator.equals(animation)) { - reportSpamView.setVisibility(View.GONE); - reportSpamViewAnimator = null; - } - } - - @Override - public void onAnimationCancel(Animator animation) { - if (reportSpamViewAnimator != null && reportSpamViewAnimator.equals(animation)) { - reportSpamViewAnimator = null; - } - } - }); - reportSpamViewAnimator.start(); - } + if (showReport || showBlock || showGeo) { + reportSpamButton.setVisibility(View.VISIBLE); } else { - if (reportSpamView.getTag() != null) { + reportSpamButton.setVisibility(View.GONE); + } + + TLRPC.User user = currentUser != null ? getMessagesController().getUser(currentUser.id) : null; + if (user != null) { + if (!user.contact && showAdd) { + addContactItem.setVisibility(View.VISIBLE); + addToContactsButton.setVisibility(View.VISIBLE); + addContactItem.setText(LocaleController.getString("AddToContacts", R.string.AddToContacts)); + if (reportSpamButton.getVisibility() == View.VISIBLE) { + addToContactsButton.setText(LocaleController.getString("AddContactChat", R.string.AddContactChat)); + } else { + addToContactsButton.setText(LocaleController.formatString("AddContactFullChat", R.string.AddContactFullChat, UserObject.getFirstName(user)).toUpperCase()); + } + addToContactsButton.setTag(null); + addToContactsButton.setVisibility(View.VISIBLE); + } else if (showShare) { + addContactItem.setVisibility(View.VISIBLE); + addToContactsButton.setVisibility(View.VISIBLE); + addContactItem.setText(LocaleController.getString("ShareMyContactInfo", R.string.ShareMyContactInfo)); + addToContactsButton.setText(LocaleController.getString("ShareMyPhone", R.string.ShareMyPhone).toUpperCase()); + addToContactsButton.setTag(1); + addToContactsButton.setVisibility(View.VISIBLE); + } else { + if (!user.contact && !show) { + addContactItem.setVisibility(View.VISIBLE); + addContactItem.setText(LocaleController.getString("ShareMyContactInfo", R.string.ShareMyContactInfo)); + addToContactsButton.setTag(2); + } else { + addContactItem.setVisibility(View.GONE); + } + addToContactsButton.setVisibility(View.GONE); + } + reportSpamButton.setText(LocaleController.getString("ReportSpamUser", R.string.ReportSpamUser)); + } else { + if (showGeo) { + reportSpamButton.setText(LocaleController.getString("ReportSpamLocation", R.string.ReportSpamLocation)); + reportSpamButton.setTag(R.id.object_tag, 1); + reportSpamButton.setTextColor(Theme.getColor(Theme.key_chat_addContact)); + reportSpamButton.setTag(Theme.key_chat_addContact); + } else { + reportSpamButton.setText(LocaleController.getString("ReportSpamAndLeave", R.string.ReportSpamAndLeave)); + reportSpamButton.setTag(R.id.object_tag, null); + reportSpamButton.setTextColor(Theme.getColor(Theme.key_chat_reportSpam)); + reportSpamButton.setTag(Theme.key_chat_reportSpam); + } + if (addContactItem != null) { + addContactItem.setVisibility(View.GONE); + } + addToContactsButton.setVisibility(View.GONE); + } + if (userBlocked || addToContactsButton.getVisibility() == View.GONE && reportSpamButton.getVisibility() == View.GONE) { + show = false; + } + + if (show) { + if (topChatPanelView.getTag() != null) { if (BuildVars.LOGS_ENABLED) { FileLog.d("show spam button"); } - reportSpamView.setTag(null); - reportSpamView.setVisibility(View.VISIBLE); + topChatPanelView.setTag(null); + topChatPanelView.setVisibility(View.VISIBLE); if (reportSpamViewAnimator != null) { reportSpamViewAnimator.cancel(); + reportSpamViewAnimator = null; } - reportSpamViewAnimator = new AnimatorSet(); - reportSpamViewAnimator.playTogether(ObjectAnimator.ofFloat(reportSpamView, View.TRANSLATION_Y, 0)); - reportSpamViewAnimator.setDuration(200); - reportSpamViewAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (reportSpamViewAnimator != null && reportSpamViewAnimator.equals(animation)) { - reportSpamViewAnimator = null; + if (animated) { + reportSpamViewAnimator = new AnimatorSet(); + reportSpamViewAnimator.playTogether(ObjectAnimator.ofFloat(topChatPanelView, View.TRANSLATION_Y, 0)); + reportSpamViewAnimator.setDuration(200); + reportSpamViewAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (reportSpamViewAnimator != null && reportSpamViewAnimator.equals(animation)) { + reportSpamViewAnimator = null; + } } - } - @Override - public void onAnimationCancel(Animator animation) { - if (reportSpamViewAnimator != null && reportSpamViewAnimator.equals(animation)) { - reportSpamViewAnimator = null; + @Override + public void onAnimationCancel(Animator animation) { + if (reportSpamViewAnimator != null && reportSpamViewAnimator.equals(animation)) { + reportSpamViewAnimator = null; + } } - } - }); - reportSpamViewAnimator.start(); - } - } - checkListViewPaddings(); - } - - private void updateContactStatus() { - if (addContactItem == null) { - return; - } - if (currentUser == null) { - addContactItem.setVisibility(View.GONE); - } else { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(currentUser.id); - if (user != null) { - currentUser = user; - } - if (currentEncryptedChat != null && !(currentEncryptedChat instanceof TLRPC.TL_encryptedChat) - || MessagesController.isSupportUser(currentUser) - || UserObject.isDeleted(currentUser) - || ContactsController.getInstance(currentAccount).isLoadingContacts() - || (!TextUtils.isEmpty(currentUser.phone) && ContactsController.getInstance(currentAccount).contactsDict.get(currentUser.id) != null && (ContactsController.getInstance(currentAccount).contactsDict.size() != 0 || !ContactsController.getInstance(currentAccount).isLoadingContacts()))) { - addContactItem.setVisibility(View.GONE); - } else { - addContactItem.setVisibility(View.VISIBLE); - if (!TextUtils.isEmpty(currentUser.phone)) { - addContactItem.setText(LocaleController.getString("AddToContacts", R.string.AddToContacts)); - reportSpamButton.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(50), 0); - addToContactsButton.setVisibility(View.VISIBLE); - reportSpamContainer.setLayoutParams(LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0.5f, Gravity.LEFT | Gravity.TOP, 0, 0, 0, AndroidUtilities.dp(1))); + }); + reportSpamViewAnimator.start(); } else { - addContactItem.setText(LocaleController.getString("ShareMyContactInfo", R.string.ShareMyContactInfo)); - addToContactsButton.setVisibility(View.GONE); - reportSpamButton.setPadding(AndroidUtilities.dp(50), 0, AndroidUtilities.dp(50), 0); - reportSpamContainer.setLayoutParams(LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 1.0f, Gravity.LEFT | Gravity.TOP, 0, 0, 0, AndroidUtilities.dp(1))); + topChatPanelView.setTranslationY(0); + } + } + } else { + if (topChatPanelView.getTag() == null) { + if (BuildVars.LOGS_ENABLED) { + FileLog.d("hide spam button"); + } + topChatPanelView.setTag(1); + + if (reportSpamViewAnimator != null) { + reportSpamViewAnimator.cancel(); + reportSpamViewAnimator = null; + } + if (animated) { + reportSpamViewAnimator = new AnimatorSet(); + reportSpamViewAnimator.playTogether(ObjectAnimator.ofFloat(topChatPanelView, View.TRANSLATION_Y, -AndroidUtilities.dp(50))); + reportSpamViewAnimator.setDuration(200); + reportSpamViewAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (reportSpamViewAnimator != null && reportSpamViewAnimator.equals(animation)) { + topChatPanelView.setVisibility(View.GONE); + reportSpamViewAnimator = null; + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (reportSpamViewAnimator != null && reportSpamViewAnimator.equals(animation)) { + reportSpamViewAnimator = null; + } + } + }); + reportSpamViewAnimator.start(); + } else { + topChatPanelView.setTranslationY(-AndroidUtilities.dp(50)); } } } @@ -11196,13 +11333,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not View firstVisView = chatLayoutManager.findViewByPosition(firstVisPos); top = ((firstVisView == null) ? 0 : chatListView.getMeasuredHeight() - firstVisView.getBottom() - chatListView.getPaddingBottom()); } - if (chatListView.getPaddingTop() != AndroidUtilities.dp(52) && (pinnedMessageView != null && pinnedMessageView.getTag() == null || reportSpamView != null && reportSpamView.getTag() == null)) { + if (chatListView.getPaddingTop() != AndroidUtilities.dp(52) && (pinnedMessageView != null && pinnedMessageView.getTag() == null || topChatPanelView != null && topChatPanelView.getTag() == null)) { chatListView.setPadding(0, AndroidUtilities.dp(52), 0, AndroidUtilities.dp(3)); FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) floatingDateView.getLayoutParams(); layoutParams.topMargin = AndroidUtilities.dp(52); floatingDateView.setLayoutParams(layoutParams); chatListView.setTopGlowOffset(AndroidUtilities.dp(48)); - } else if (chatListView.getPaddingTop() != AndroidUtilities.dp(4) && (pinnedMessageView == null || pinnedMessageView.getTag() != null) && (reportSpamView == null || reportSpamView.getTag() != null)) { + } else if (chatListView.getPaddingTop() != AndroidUtilities.dp(4) && (pinnedMessageView == null || pinnedMessageView.getTag() != null) && (topChatPanelView == null || topChatPanelView.getTag() != null)) { chatListView.setPadding(0, AndroidUtilities.dp(4), 0, AndroidUtilities.dp(3)); FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) floatingDateView.getLayoutParams(); layoutParams.topMargin = AndroidUtilities.dp(4); @@ -11295,7 +11432,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { mentiondownButtonCounter.setText(String.format("%d", newMentionsCount)); } - MessagesController.getInstance(currentAccount).markMentionMessageAsRead(message.getId(), ChatObject.isChannel(currentChat) ? currentChat.id : 0, dialog_id); + getMessagesController().markMentionMessageAsRead(message.getId(), ChatObject.isChannel(currentChat) ? currentChat.id : 0, dialog_id); message.setContentIsRead(); } if (view instanceof ChatMessageCell) { @@ -11324,7 +11461,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (firstOpen) { - if (MessagesController.getInstance(currentAccount).isProxyDialog(dialog_id, true)) { + if (getMessagesController().isProxyDialog(dialog_id, true)) { SharedPreferences preferences = MessagesController.getGlobalNotificationsSettings(); if (preferences.getLong("proxychannel", 0) != dialog_id) { preferences.edit().putLong("proxychannel", dialog_id).commit(); @@ -11345,8 +11482,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not pinnedMessageImageView.setImage(ImageLocation.getForObject(pinnedImageLocation, pinnedImageLocationObject), "50_50", ImageLocation.getForObject(pinnedImageThumbLocation, pinnedImageLocationObject), "50_50_b", null, pinnedImageSize, pinnedImageCacheType, pinnedMessageObject); } - NotificationsController.getInstance(currentAccount).setOpenedDialogId(dialog_id); - MessagesController.getInstance(currentAccount).setLastVisibleDialogId(dialog_id, true); + getNotificationsController().setOpenedDialogId(dialog_id); + getMessagesController().setLastVisibleDialogId(dialog_id, true); if (scrollToTopOnResume) { if (scrollToTopUnReadOnResume && scrollToMessage != null) { if (chatListView != null) { @@ -11423,12 +11560,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (scrimPopupWindow != null) { scrimPopupWindow.dismiss(); } - MessagesController.getInstance(currentAccount).markDialogAsReadNow(dialog_id); + getMessagesController().markDialogAsReadNow(dialog_id); MediaController.getInstance().stopRaiseToEarSensors(this, true); paused = true; wasPaused = true; - NotificationsController.getInstance(currentAccount).setOpenedDialogId(0); - MessagesController.getInstance(currentAccount).setLastVisibleDialogId(dialog_id, false); + getNotificationsController().setOpenedDialogId(0); + getMessagesController().setLastVisibleDialogId(dialog_id, false); CharSequence draftMessage = null; MessageObject replyMessage = null; boolean searchWebpage = true; @@ -11452,10 +11589,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not contentView.onPause(); } CharSequence[] message = new CharSequence[] {draftMessage}; - ArrayList entities = DataQuery.getInstance(currentAccount).getEntities(message); - DataQuery.getInstance(currentAccount).saveDraft(dialog_id, message[0], entities, replyMessage != null ? replyMessage.messageOwner : null, !searchWebpage); + ArrayList entities = getMediaDataController().getEntities(message); + getMediaDataController().saveDraft(dialog_id, message[0], entities, replyMessage != null ? replyMessage.messageOwner : null, !searchWebpage); - MessagesController.getInstance(currentAccount).cancelTyping(0, dialog_id); + getMessagesController().cancelTyping(0, dialog_id); if (!pausedOnLastMessage) { SharedPreferences.Editor editor = MessagesController.getNotificationsSettings(currentAccount).edit(); @@ -11533,16 +11670,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (chatActivityEnterView == null) { return; } - TLRPC.DraftMessage draftMessage = DataQuery.getInstance(currentAccount).getDraft(dialog_id); - TLRPC.Message draftReplyMessage = draftMessage != null && draftMessage.reply_to_msg_id != 0 ? DataQuery.getInstance(currentAccount).getDraftMessage(dialog_id) : null; + TLRPC.DraftMessage draftMessage = getMediaDataController().getDraft(dialog_id); + TLRPC.Message draftReplyMessage = draftMessage != null && draftMessage.reply_to_msg_id != 0 ? getMediaDataController().getDraftMessage(dialog_id) : null; if (chatActivityEnterView.getFieldText() == null) { if (draftMessage != null) { chatActivityEnterView.setWebPage(null, !draftMessage.no_webpage); CharSequence message; if (!draftMessage.entities.isEmpty()) { SpannableStringBuilder stringBuilder = SpannableStringBuilder.valueOf(draftMessage.message); - DataQuery.sortEntities(draftMessage.entities); - int addToOffset = 0; + MediaDataController.sortEntities(draftMessage.entities); for (int a = 0; a < draftMessage.entities.size(); a++) { TLRPC.MessageEntity entity = draftMessage.entities.get(a); if (entity instanceof TLRPC.TL_inputMessageEntityMentionName || entity instanceof TLRPC.TL_messageEntityMentionName) { @@ -11552,24 +11688,32 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { user_id = ((TLRPC.TL_messageEntityMentionName) entity).user_id; } - if (entity.offset + addToOffset + entity.length < stringBuilder.length() && stringBuilder.charAt(entity.offset + addToOffset + entity.length) == ' ') { + if (entity.offset + entity.length < stringBuilder.length() && stringBuilder.charAt(entity.offset + entity.length) == ' ') { entity.length++; } - stringBuilder.setSpan(new URLSpanUserMention("" + user_id, 1), entity.offset + addToOffset, entity.offset + addToOffset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else if (entity instanceof TLRPC.TL_messageEntityCode) { - stringBuilder.insert(entity.offset + entity.length + addToOffset, "`"); - stringBuilder.insert(entity.offset + addToOffset, "`"); - addToOffset += 2; - } else if (entity instanceof TLRPC.TL_messageEntityPre) { - stringBuilder.insert(entity.offset + entity.length + addToOffset, "```"); - stringBuilder.insert(entity.offset + addToOffset, "```"); - addToOffset += 6; + stringBuilder.setSpan(new URLSpanUserMention("" + user_id, 1), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (entity instanceof TLRPC.TL_messageEntityCode || entity instanceof TLRPC.TL_messageEntityPre) { + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_MONO; + MediaDataController.addStyleToText(new TextStyleSpan(run), entity.offset, entity.offset + entity.length, stringBuilder, true); } else if (entity instanceof TLRPC.TL_messageEntityBold) { - stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), entity.offset + addToOffset, entity.offset + entity.length + addToOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_BOLD; + MediaDataController.addStyleToText(new TextStyleSpan(run), entity.offset, entity.offset + entity.length, stringBuilder, true); } else if (entity instanceof TLRPC.TL_messageEntityItalic) { - stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/ritalic.ttf")), entity.offset + addToOffset, entity.offset + entity.length + addToOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_ITALIC; + MediaDataController.addStyleToText(new TextStyleSpan(run), entity.offset, entity.offset + entity.length, stringBuilder, true); + } else if (entity instanceof TLRPC.TL_messageEntityStrike) { + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_STRIKE; + MediaDataController.addStyleToText(new TextStyleSpan(run), entity.offset, entity.offset + entity.length, stringBuilder, true); + } else if (entity instanceof TLRPC.TL_messageEntityUnderline) { + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_UNDERLINE; + MediaDataController.addStyleToText(new TextStyleSpan(run), entity.offset, entity.offset + entity.length, stringBuilder, true); } else if (entity instanceof TLRPC.TL_messageEntityTextUrl) { - stringBuilder.setSpan(new URLSpanReplacement(entity.url), entity.offset + addToOffset, entity.offset + entity.length + addToOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.setSpan(new URLSpanReplacement(entity.url), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } message = stringBuilder; @@ -11592,7 +11736,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not hideFieldPanel(true); } if (replyingMessageObject == null && draftReplyMessage != null) { - replyingMessageObject = new MessageObject(currentAccount, draftReplyMessage, MessagesController.getInstance(currentAccount).getUsers(), false); + replyingMessageObject = new MessageObject(currentAccount, draftReplyMessage, getMessagesController().getUsers(), false); showFieldPanelForReply(replyingMessageObject); } } @@ -11790,7 +11934,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not MessageObject message = MediaController.getInstance().getPlayingMessageObject(); if (message != null && message.isVideo()) { PhotoViewer.getInstance().setParentActivity(getParentActivity()); - FileLoader.getInstance(currentAccount).setLoadingVideoForPlayer(message.getDocument(), false); + 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)) { @@ -11869,6 +12013,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not selectedObjectGroup = null; forwardingMessage = null; forwardingMessageGroup = null; + selectedObjectToEditCaption = null; for (int a = 1; a >= 0; a--) { selectedMessagesCanCopyIds[a].clear(); selectedMessagesCanStarIds[a].clear(); @@ -11899,7 +12044,20 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } allowPin = allowPin && message.getId() > 0 && (message.messageOwner.action == null || message.messageOwner.action instanceof TLRPC.TL_messageActionEmpty); boolean allowUnpin = message.getDialogId() != mergeDialogId && allowPin && (chatInfo != null && chatInfo.pinned_msg_id == message.getId() || userInfo != null && userInfo.pinned_msg_id == message.getId()); - boolean allowEdit = groupedMessages == null && message.canEditMessage(currentChat) && !chatActivityEnterView.hasAudioToSend() && message.getDialogId() != mergeDialogId; + boolean allowEdit = message.canEditMessage(currentChat) && !chatActivityEnterView.hasAudioToSend() && message.getDialogId() != mergeDialogId; + if (allowEdit && groupedMessages != null) { + int captionsCount = 0; + for (int a = 0, N = groupedMessages.messages.size(); a < N; a++) { + MessageObject messageObject = groupedMessages.messages.get(a); + if (a == 0 || !TextUtils.isEmpty(messageObject.caption)) { + selectedObjectToEditCaption = messageObject; + if (!TextUtils.isEmpty(messageObject.caption)) { + captionsCount++; + } + } + } + allowEdit = captionsCount < 2; + } if (currentEncryptedChat != null && AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) < 46 || type == 1 && (message.getDialogId() == mergeDialogId || message.needDrawBluredPreview()) || message.messageOwner.action instanceof TLRPC.TL_messageActionSecureValuesSent || @@ -12115,8 +12273,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not items.add(LocaleController.getString("AddToStickers", R.string.AddToStickers)); options.add(9); icons.add(R.drawable.msg_sticker); - if (!DataQuery.getInstance(currentAccount).isStickerInFavorites(selectedObject.getDocument())) { - if (DataQuery.getInstance(currentAccount).canAddStickerToFavorites()) { + if (!getMediaDataController().isStickerInFavorites(selectedObject.getDocument())) { + if (getMediaDataController().canAddStickerToFavorites()) { items.add(LocaleController.getString("AddToFavorites", R.string.AddToFavorites)); options.add(20); icons.add(R.drawable.msg_fave); @@ -12128,8 +12286,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } } else if (type == 8) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(selectedObject.messageOwner.media.user_id); - if (user != null && user.id != UserConfig.getInstance(currentAccount).getClientUserId() && ContactsController.getInstance(currentAccount).contactsDict.get(user.id) == null) { + TLRPC.User user = getMessagesController().getUser(selectedObject.messageOwner.media.user_id); + if (user != null && user.id != getUserConfig().getClientUserId() && getContactsController().contactsDict.get(user.id) == null) { items.add(LocaleController.getString("AddContactTitle", R.string.AddContactTitle)); options.add(15); icons.add(R.drawable.msg_addcontact); @@ -12143,7 +12301,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not icons.add(R.drawable.msg_callback); } } else if (type == 9) { - if (!DataQuery.getInstance(currentAccount).isStickerInFavorites(selectedObject.getDocument())) { + if (!getMediaDataController().isStickerInFavorites(selectedObject.getDocument())) { items.add(LocaleController.getString("AddToFavorites", R.string.AddToFavorites)); options.add(20); icons.add(R.drawable.msg_fave); @@ -12233,8 +12391,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not options.add(9); icons.add(R.drawable.msg_sticker); } else if (type == 8) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(selectedObject.messageOwner.media.user_id); - if (user != null && user.id != UserConfig.getInstance(currentAccount).getClientUserId() && ContactsController.getInstance(currentAccount).contactsDict.get(user.id) == null) { + TLRPC.User user = getMessagesController().getUser(selectedObject.messageOwner.media.user_id); + if (user != null && user.id != getUserConfig().getClientUserId() && getContactsController().contactsDict.get(user.id) == null) { items.add(LocaleController.getString("AddContactTitle", R.string.AddContactTitle)); options.add(15); icons.add(R.drawable.msg_addcontact); @@ -12530,9 +12688,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not updateVisibleRows(); TLRPC.TL_messages_getMessageEditData req = new TLRPC.TL_messages_getMessageEditData(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) dialog_id); + req.peer = getMessagesController().getInputPeer((int) dialog_id); req.id = messageObject.getId(); - editingMessageObjectReqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + editingMessageObjectReqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { editingMessageObjectReqId = 0; if (response == null) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); @@ -12558,12 +12716,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (name) { if (previousUid != messageObject.messageOwner.from_id) { if (messageObject.messageOwner.from_id > 0) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(messageObject.messageOwner.from_id); + TLRPC.User user = getMessagesController().getUser(messageObject.messageOwner.from_id); if (user != null) { str = ContactsController.formatName(user.first_name, user.last_name) + ":\n"; } } else if (messageObject.messageOwner.from_id < 0) { - TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-messageObject.messageOwner.from_id); + TLRPC.Chat chat = getMessagesController().getChat(-messageObject.messageOwner.from_id); if (chat != null) { str = chat.title + ":\n"; } @@ -12603,7 +12761,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (selectedObjectGroup != null) { boolean success = true; for (int a = 0; a < selectedObjectGroup.messages.size(); a++) { - if (!SendMessagesHelper.getInstance(currentAccount).retrySendMessage(selectedObjectGroup.messages.get(a), false)) { + if (!getSendMessagesHelper().retrySendMessage(selectedObjectGroup.messages.get(a), false)) { success = false; } } @@ -12611,7 +12769,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not moveScrollToLastMessage(); } } else { - if (SendMessagesHelper.getInstance(currentAccount).retrySendMessage(selectedObject, false)) { + if (getSendMessagesHelper().retrySendMessage(selectedObject, false)) { updateVisibleRows(); moveScrollToLastMessage(); } @@ -12621,6 +12779,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not case 1: { if (getParentActivity() == null) { selectedObject = null; + selectedObjectToEditCaption = null; selectedObjectGroup = null; return; } @@ -12647,6 +12806,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not getParentActivity().requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 4); selectedObject = null; selectedObjectGroup = null; + selectedObjectToEditCaption = null; return; } if (selectedObjectGroup != null) { @@ -12697,6 +12857,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (getParentActivity() == null) { selectedObject = null; selectedObjectGroup = null; + selectedObjectToEditCaption = null; return; } AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); @@ -12712,6 +12873,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (getParentActivity() == null) { selectedObject = null; selectedObjectGroup = null; + selectedObjectToEditCaption = null; return; } AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); @@ -12770,6 +12932,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not getParentActivity().requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 4); selectedObject = null; selectedObjectGroup = null; + selectedObjectToEditCaption = null; return; } MediaController.saveFile(path, getParentActivity(), 0, null, null); @@ -12788,6 +12951,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not getParentActivity().requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 4); selectedObject = null; selectedObjectGroup = null; + selectedObjectToEditCaption = null; return; } String fileName = FileLoader.getDocumentFileName(selectedObject.getDocument()); @@ -12809,15 +12973,20 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } case 11: { TLRPC.Document document = selectedObject.getDocument(); - MessagesController.getInstance(currentAccount).saveGif(selectedObject, document); + getMessagesController().saveGif(selectedObject, document); showGifHint(); chatActivityEnterView.addRecentGif(document); break; } case 12: { - startEditingMessageObject(selectedObject); + if (selectedObjectToEditCaption != null) { + startEditingMessageObject(selectedObjectToEditCaption); + } else { + startEditingMessageObject(selectedObject); + } selectedObject = null; selectedObjectGroup = null; + selectedObjectToEditCaption = null; break; } case 13: { @@ -12835,7 +13004,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not FrameLayout frameLayout = new FrameLayout(getParentActivity()); CheckBoxCell cell = new CheckBoxCell(getParentActivity(), 1); cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); - cell.setText(LocaleController.getString("PinNotify", R.string.PinNotify), "", true, false); + cell.setText(LocaleController.formatString("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.setOnClickListener(v -> { @@ -12848,7 +13017,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not builder.setMessage(LocaleController.getString("PinMessageAlertChannel", R.string.PinMessageAlertChannel)); checks = new boolean[]{false}; } - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> MessagesController.getInstance(currentAccount).pinMessage(currentChat, currentUser, mid, checks[0])); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> getMessagesController().pinMessage(currentChat, currentUser, mid, checks[0])); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showDialog(builder.create()); break; @@ -12857,7 +13026,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("UnpinMessageAlertTitle", R.string.UnpinMessageAlertTitle)); builder.setMessage(LocaleController.getString("UnpinMessageAlert", R.string.UnpinMessageAlert)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> MessagesController.getInstance(currentAccount).pinMessage(currentChat, currentUser, 0, false)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> getMessagesController().pinMessage(currentChat, currentUser, 0, false)); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showDialog(builder.create()); break; @@ -12890,7 +13059,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } case 18: { if (currentUser != null) { - VoIPHelper.startCall(currentUser, getParentActivity(), MessagesController.getInstance(currentAccount).getUserFull(currentUser.id)); + VoIPHelper.startCall(currentUser, getParentActivity(), getMessagesController().getUserFull(currentUser.id)); } break; } @@ -12899,18 +13068,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not break; } case 20: { - DataQuery.getInstance(currentAccount).addRecentSticker(DataQuery.TYPE_FAVE, selectedObject, selectedObject.getDocument(), (int) (System.currentTimeMillis() / 1000), false); + getMediaDataController().addRecentSticker(MediaDataController.TYPE_FAVE, selectedObject, selectedObject.getDocument(), (int) (System.currentTimeMillis() / 1000), false); break; } case 21: { - DataQuery.getInstance(currentAccount).addRecentSticker(DataQuery.TYPE_FAVE, selectedObject, selectedObject.getDocument(), (int) (System.currentTimeMillis() / 1000), true); + getMediaDataController().addRecentSticker(MediaDataController.TYPE_FAVE, selectedObject, selectedObject.getDocument(), (int) (System.currentTimeMillis() / 1000), true); break; } case 22: { TLRPC.TL_channels_exportMessageLink req = new TLRPC.TL_channels_exportMessageLink(); req.id = selectedObject.getId(); req.channel = MessagesController.getInputChannel(currentChat); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (response != null) { TLRPC.TL_exportedMessageLink exportedMessageLink = (TLRPC.TL_exportedMessageLink) response; try { @@ -12936,17 +13105,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } case 24: { if (selectedObject.isEditing() || selectedObject.isSending() && selectedObjectGroup == null) { - SendMessagesHelper.getInstance(currentAccount).cancelSendingMessage(selectedObject); + getSendMessagesHelper().cancelSendingMessage(selectedObject); } else if (selectedObject.isSending() && selectedObjectGroup != null) { for (int a = 0; a < selectedObjectGroup.messages.size(); a++) { - SendMessagesHelper.getInstance(currentAccount).cancelSendingMessage(new ArrayList<>(selectedObjectGroup.messages)); + getSendMessagesHelper().cancelSendingMessage(new ArrayList<>(selectedObjectGroup.messages)); } } break; } case 25: { final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(getParentActivity(), 3)}; - int requestId = SendMessagesHelper.getInstance(currentAccount).sendVote(selectedObject, null, () -> { + int requestId = getSendMessagesHelper().sendVote(selectedObject, null, () -> { try { progressDialog[0].dismiss(); } catch (Throwable ignore) { @@ -12959,7 +13128,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (progressDialog[0] == null) { return; } - progressDialog[0].setOnCancelListener(dialog -> ConnectionsManager.getInstance(currentAccount).cancelRequest(requestId, true)); + progressDialog[0].setOnCancelListener(dialog -> getConnectionsManager().cancelRequest(requestId, true)); showDialog(progressDialog[0]); }, 500); } @@ -12981,10 +13150,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not poll.poll.answers = mediaPoll.poll.answers; poll.poll.closed = true; req.media = poll; - req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) dialog_id); + req.peer = getMessagesController().getInputPeer((int) dialog_id); req.id = object.getId(); req.flags |= 16384; - int requestId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + int requestId = getConnectionsManager().sendRequest(req, (response, error) -> { AndroidUtilities.runOnUIThread(() -> { try { progressDialog[0].dismiss(); @@ -12994,7 +13163,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not progressDialog[0] = null; }); if (error == null) { - MessagesController.getInstance(currentAccount).processUpdates((TLRPC.Updates) response, false); + getMessagesController().processUpdates((TLRPC.Updates) response, false); } else { AndroidUtilities.runOnUIThread(() -> AlertsCreator.processError(currentAccount, error, ChatActivity.this, req)); } @@ -13003,7 +13172,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (progressDialog[0] == null) { return; } - progressDialog[0].setOnCancelListener(dialog -> ConnectionsManager.getInstance(currentAccount).cancelRequest(requestId, true)); + progressDialog[0].setOnCancelListener(dialog -> getConnectionsManager().cancelRequest(requestId, true)); showDialog(progressDialog[0]); }, 500); }); @@ -13014,6 +13183,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } selectedObject = null; selectedObjectGroup = null; + selectedObjectToEditCaption = null; } @Override @@ -13052,13 +13222,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not updatePinnedMessageView(true); } - if (dids.size() > 1 || dids.get(0) == UserConfig.getInstance(currentAccount).getClientUserId() || message != null) { + if (dids.size() > 1 || dids.get(0) == getUserConfig().getClientUserId() || message != null) { for (int a = 0; a < dids.size(); a++) { long did = dids.get(a); if (message != null) { - SendMessagesHelper.getInstance(currentAccount).sendMessage(message.toString(), did, null, null, true, null, null, null); + getSendMessagesHelper().sendMessage(message.toString(), did, null, null, true, null, null, null); } - SendMessagesHelper.getInstance(currentAccount).sendMessage(fmessages, did); + getSendMessagesHelper().sendMessage(fmessages, did); } fragment.finishFragment(); } else { @@ -13078,7 +13248,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not args.putInt("enc_id", high_part); } if (lower_part != 0) { - if (!MessagesController.getInstance(currentAccount).checkCanOpenChat(args, fragment)) { + if (!getMessagesController().checkCanOpenChat(args, fragment)) { return; } } @@ -13196,8 +13366,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (highlightMessageId != Integer.MAX_VALUE) { startMessageUnselect(); } - if (searchContainer != null && searchContainer.getVisibility() == View.VISIBLE && DataQuery.getInstance(currentAccount).isMessageFound(messageObject.getId(), messageObject.getDialogId() == mergeDialogId) && DataQuery.getInstance(currentAccount).getLastSearchQuery() != null) { - cell.setHighlightedText(DataQuery.getInstance(currentAccount).getLastSearchQuery()); + if (searchContainer != null && searchContainer.getVisibility() == View.VISIBLE && getMediaDataController().isMessageFound(messageObject.getId(), messageObject.getDialogId() == mergeDialogId) && getMediaDataController().getLastSearchQuery() != null) { + cell.setHighlightedText(getMediaDataController().getLastSearchQuery()); } else { cell.setHighlightedText(null); } @@ -13224,7 +13394,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (currentUser != null && currentUser.self) { return; } - int dt = messageObject.canEditMessageAnytime(currentChat) ? 6 * 60 : MessagesController.getInstance(currentAccount).maxEditTime + 5 * 60 - Math.abs(ConnectionsManager.getInstance(currentAccount).getCurrentTime() - messageObject.messageOwner.date); + int dt = messageObject.canEditMessageAnytime(currentChat) ? 6 * 60 : getMessagesController().maxEditTime + 5 * 60 - Math.abs(getConnectionsManager().getCurrentTime() - messageObject.messageOwner.date); if (dt > 0) { if (dt <= 5 * 60) { replyObjectTextView.setText(LocaleController.formatString("TimeToEdit", R.string.TimeToEdit, String.format("%d:%02d", dt / 60, dt % 60))); @@ -13288,18 +13458,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not searchItem.openSearch(openSearchKeyboard); if (text != null) { searchItem.setSearchFieldText(text, false); - DataQuery.getInstance(currentAccount).searchMessagesInChat(text, dialog_id, mergeDialogId, classGuid, 0, searchingUserMessages); + getMediaDataController().searchMessagesInChat(text, dialog_id, mergeDialogId, classGuid, 0, searchingUserMessages); } updatePinnedMessageView(true); } @Override public void didSelectLocation(TLRPC.MessageMedia location, int live) { - SendMessagesHelper.getInstance(currentAccount).sendMessage(location, dialog_id, replyingMessageObject, null, null); + getSendMessagesHelper().sendMessage(location, dialog_id, replyingMessageObject, null, null); moveScrollToLastMessage(); if (live == 1) { hideFieldPanel(false); - DataQuery.getInstance(currentAccount).cleanDraft(dialog_id, true); + getMediaDataController().cleanDraft(dialog_id, true); } if (paused) { scrollToTopOnResume = true; @@ -13345,27 +13515,27 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not fillEditingMediaWithCaption(photoEntry.caption, photoEntry.entities); if (photoEntry.isVideo) { if (videoEditedInfo != null) { - SendMessagesHelper.prepareSendingVideo(photoEntry.path, videoEditedInfo.estimatedSize, videoEditedInfo.estimatedDuration, videoEditedInfo.resultWidth, videoEditedInfo.resultHeight, videoEditedInfo, dialog_id, replyingMessageObject, photoEntry.caption, photoEntry.entities, photoEntry.ttl, editingMessageObject); + SendMessagesHelper.prepareSendingVideo(getAccountInstance(), photoEntry.path, videoEditedInfo.estimatedSize, videoEditedInfo.estimatedDuration, videoEditedInfo.resultWidth, videoEditedInfo.resultHeight, videoEditedInfo, dialog_id, replyingMessageObject, photoEntry.caption, photoEntry.entities, photoEntry.ttl, editingMessageObject); } else { - SendMessagesHelper.prepareSendingVideo(photoEntry.path, 0, 0, 0, 0, null, dialog_id, replyingMessageObject, photoEntry.caption, photoEntry.entities, photoEntry.ttl, editingMessageObject); + SendMessagesHelper.prepareSendingVideo(getAccountInstance(), photoEntry.path, 0, 0, 0, 0, null, dialog_id, replyingMessageObject, photoEntry.caption, photoEntry.entities, photoEntry.ttl, editingMessageObject); } hideFieldPanel(false); - DataQuery.getInstance(currentAccount).cleanDraft(dialog_id, true); + getMediaDataController().cleanDraft(dialog_id, true); } else { if (photoEntry.imagePath != null) { - SendMessagesHelper.prepareSendingPhoto(photoEntry.imagePath, null, dialog_id, replyingMessageObject, photoEntry.caption, photoEntry.entities, photoEntry.stickers, null, photoEntry.ttl, editingMessageObject); + SendMessagesHelper.prepareSendingPhoto(getAccountInstance(), photoEntry.imagePath, null, dialog_id, replyingMessageObject, photoEntry.caption, photoEntry.entities, photoEntry.stickers, null, photoEntry.ttl, editingMessageObject); hideFieldPanel(false); - DataQuery.getInstance(currentAccount).cleanDraft(dialog_id, true); + getMediaDataController().cleanDraft(dialog_id, true); } else if (photoEntry.path != null) { - SendMessagesHelper.prepareSendingPhoto(photoEntry.path, null, dialog_id, replyingMessageObject, photoEntry.caption, photoEntry.entities, photoEntry.stickers, null, photoEntry.ttl, editingMessageObject); + SendMessagesHelper.prepareSendingPhoto(getAccountInstance(), photoEntry.path, null, dialog_id, replyingMessageObject, photoEntry.caption, photoEntry.entities, photoEntry.stickers, null, photoEntry.ttl, editingMessageObject); hideFieldPanel(false); - DataQuery.getInstance(currentAccount).cleanDraft(dialog_id, true); + getMediaDataController().cleanDraft(dialog_id, true); } } } public void showOpenGameAlert(final TLRPC.TL_game game, final MessageObject messageObject, final String urlStr, boolean ask, final int uid) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(uid); + TLRPC.User user = getMessagesController().getUser(uid); if (ask) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); @@ -13501,7 +13671,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (progressDialog[0] == null) { return; } - progressDialog[0].setOnCancelListener(dialog -> ConnectionsManager.getInstance(currentAccount).cancelRequest(requestId, true)); + progressDialog[0].setOnCancelListener(dialog -> getConnectionsManager().cancelRequest(requestId, true)); showDialog(progressDialog[0]); }, 500); } @@ -13674,7 +13844,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not args.putInt("user_id", messageObject.messageOwner.fwd_from.saved_from_peer.user_id); } args.putInt("message_id", messageObject.messageOwner.fwd_from.saved_from_msg_id); - if (MessagesController.getInstance(currentAccount).checkCanOpenChat(args, ChatActivity.this)) { + if (getMessagesController().checkCanOpenChat(args, ChatActivity.this)) { presentFragment(new ChatActivity(args)); } } else { @@ -13733,7 +13903,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (postId != 0) { args.putInt("message_id", postId); } - if (MessagesController.getInstance(currentAccount).checkCanOpenChat(args, ChatActivity.this, cell.getMessageObject())) { + if (getMessagesController().checkCanOpenChat(args, ChatActivity.this, cell.getMessageObject())) { presentFragment(new ChatActivity(args)); } } @@ -13748,7 +13918,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not public void didPressOther(ChatMessageCell cell, float otherX, float otherY) { if (cell.getMessageObject().type == 16) { if (currentUser != null) { - VoIPHelper.startCall(currentUser, getParentActivity(), MessagesController.getInstance(currentAccount).getUserFull(currentUser.id)); + VoIPHelper.startCall(currentUser, getParentActivity(), getMessagesController().getUserFull(currentUser.id)); } } else { createMenu(cell, true, false, otherX, otherY, false); @@ -13761,7 +13931,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not processRowSelect(cell, true, touchX, touchY); return; } - if (user != null && user.id != UserConfig.getInstance(currentAccount).getClientUserId()) { + if (user != null && user.id != getUserConfig().getClientUserId()) { Bundle args = new Bundle(); args.putInt("user_id", user.id); ProfileActivity fragment = new ProfileActivity(args); @@ -13783,14 +13953,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void didPressVoteButton(ChatMessageCell cell, TLRPC.TL_pollAnswer button) { - SendMessagesHelper.getInstance(currentAccount).sendVote(cell.getMessageObject(), button, null); + getSendMessagesHelper().sendVote(cell.getMessageObject(), button, null); } @Override public void didPressCancelSendButton(ChatMessageCell cell) { MessageObject message = cell.getMessageObject(); if (message.messageOwner.send_state != 0) { - SendMessagesHelper.getInstance(currentAccount).cancelSendingMessage(message); + getSendMessagesHelper().cancelSendingMessage(message); } } @@ -13813,7 +13983,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not ((URLSpanMono) url).copyToClipboard(); Toast.makeText(getParentActivity(), LocaleController.getString("TextCopied", R.string.TextCopied), Toast.LENGTH_SHORT).show(); } else if (url instanceof URLSpanUserMention) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(Utilities.parseInt(((URLSpanUserMention) url).getURL())); + TLRPC.User user = getMessagesController().getUser(Utilities.parseInt(((URLSpanUserMention) url).getURL())); if (user != null) { MessagesController.openChatOrProfileWith(user, null, ChatActivity.this, 0, false); } @@ -13838,7 +14008,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not fragment.setUserInfo(userInfo); presentFragment(fragment); } else { - MessagesController.getInstance(currentAccount).openByUserName(username, ChatActivity.this, 0); + getMessagesController().openByUserName(username, ChatActivity.this, 0); } } else if (str.startsWith("#") || str.startsWith("$")) { if (ChatObject.isChannel(currentChat)) { @@ -13944,7 +14114,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } SecretMediaViewer.getInstance().setParentActivity(getParentActivity()); SecretMediaViewer.getInstance().openMedia(message, photoViewerProvider); - } else if (message.type == 13) { + } else if (message.type == MessageObject.TYPE_STICKER || message.type == MessageObject.TYPE_ANIMATED_STICKER) { showDialog(new StickersAlert(getParentActivity(), ChatActivity.this, message.getInputStickerSet(), null, bottomOverlayChat.getVisibility() != View.VISIBLE && (currentChat == null || ChatObject.canSendStickers(currentChat)) ? chatActivityEnterView : null)); } else if (message.isVideo() || message.type == 1 || message.type == 0 && !message.isWebpageDocument() || message.isGif()) { if (message.isVideo()) { @@ -13953,7 +14123,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not PhotoViewer.getInstance().setParentActivity(getParentActivity()); MessageObject playingObject = MediaController.getInstance().getPlayingMessageObject(); if (playingObject != null && playingObject.isVideo()) { - FileLoader.getInstance(currentAccount).setLoadingVideoForPlayer(playingObject.getDocument(), false); + getFileLoader().setLoadingVideoForPlayer(playingObject.getDocument(), false); if (playingObject.equals(message)) { AnimatedFileDrawable animation = cell.getPhotoImage().getAnimation(); if (animation != null && videoTextureView != null && videoPlayerContainer.getTag() != null) { @@ -14093,7 +14263,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public boolean isChatAdminCell(int uid) { if (ChatObject.isChannel(currentChat) && currentChat.megagroup) { - return MessagesController.getInstance(currentAccount).isChannelAdmin(currentChat.id, uid); + return getMessagesController().isChannelAdmin(currentChat.id, uid); } return false; } @@ -14126,10 +14296,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (uid < 0) { Bundle args = new Bundle(); args.putInt("chat_id", -uid); - if (MessagesController.getInstance(currentAccount).checkCanOpenChat(args, ChatActivity.this)) { + if (getMessagesController().checkCanOpenChat(args, ChatActivity.this)) { presentFragment(new ChatActivity(args)); } - } else if (uid != UserConfig.getInstance(currentAccount).getClientUserId()) { + } else if (uid != getUserConfig().getClientUserId()) { Bundle args = new Bundle(); args.putInt("user_id", uid); if (currentEncryptedChat != null && uid == currentUser.id) { @@ -14164,7 +14334,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not view = new BotHelpCell(mContext); ((BotHelpCell) view).setDelegate(url -> { if (url.startsWith("@")) { - MessagesController.getInstance(currentAccount).openByUserName(url.substring(1), ChatActivity.this, 0); + getMessagesController().openByUserName(url.substring(1), ChatActivity.this, 0); } else if (url.startsWith("#") || url.startsWith("$")) { DialogsActivity fragment = new DialogsActivity(null); fragment.setSearchString(url); @@ -14389,8 +14559,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } messageCell.setCheckPressed(!disableSelection, disableSelection && selected); - if (searchContainer != null && searchContainer.getVisibility() == View.VISIBLE && DataQuery.getInstance(currentAccount).isMessageFound(message.getId(), message.getDialogId() == mergeDialogId) && DataQuery.getInstance(currentAccount).getLastSearchQuery() != null) { - messageCell.setHighlightedText(DataQuery.getInstance(currentAccount).getLastSearchQuery()); + if (searchContainer != null && searchContainer.getVisibility() == View.VISIBLE && getMediaDataController().isMessageFound(message.getId(), message.getDialogId() == mergeDialogId) && getMediaDataController().getLastSearchQuery() != null) { + messageCell.setHighlightedText(getMediaDataController().getLastSearchQuery()); } else { messageCell.setHighlightedText(null); } @@ -14436,7 +14606,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { mentiondownButtonCounter.setText(String.format("%d", newMentionsCount)); } - MessagesController.getInstance(currentAccount).markMentionMessageAsRead(message.getId(), ChatObject.isChannel(currentChat) ? currentChat.id : 0, dialog_id); + getMessagesController().markMentionMessageAsRead(message.getId(), ChatObject.isChannel(currentChat) ? currentChat.id : 0, dialog_id); message.setContentIsRead(); } } @@ -14906,11 +15076,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not new ThemeDescription(alertTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_topPanelMessage), new ThemeDescription(closePinned, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_topPanelClose), new ThemeDescription(closeReportSpam, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_topPanelClose), - new ThemeDescription(reportSpamView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chat_topPanelBackground), + new ThemeDescription(topChatPanelView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chat_topPanelBackground), new ThemeDescription(alertView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chat_topPanelBackground), new ThemeDescription(pinnedMessageView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chat_topPanelBackground), new ThemeDescription(addToContactsButton, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_addContact), - new ThemeDescription(reportSpamButton, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_reportSpam), + new ThemeDescription(reportSpamButton, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_chat_reportSpam), + new ThemeDescription(reportSpamButton, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_chat_addContact), new ThemeDescription(replyLineView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_chat_replyPanelLine), new ThemeDescription(replyNameTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_replyPanelName), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java index 6a3a54559..2b44d647b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java @@ -93,6 +93,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image private LinearLayout typeEditContainer; private ShadowSectionCell settingsTopSectionCell; + private TextDetailCell locationCell; private TextDetailCell typeCell; private TextDetailCell linkedCell; private TextDetailCell historyCell; @@ -526,12 +527,39 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image 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)); + typeEditContainer.addView(locationCell, LayoutHelper.createLinear(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + locationCell.setOnClickListener(v -> { + if (!AndroidUtilities.isGoogleMapsInstalled(ChatEditActivity.this)) { + return; + } + LocationActivity fragment = new LocationActivity(LocationActivity.LOCATION_TYPE_GROUP); + fragment.setDialogId(-chatId); + if (info != null && info.location instanceof TLRPC.TL_channelLocation) { + fragment.setInitialLocation((TLRPC.TL_channelLocation) info.location); + } + fragment.setDelegate((location, live) -> { + TLRPC.TL_channelLocation channelLocation = new TLRPC.TL_channelLocation(); + channelLocation.address = location.address; + channelLocation.geo_point = location.geo; + + info.location = channelLocation; + info.flags |= 32768; + updateFields(false); + getMessagesController().loadFullChat(chatId, 0, true); + }); + presentFragment(fragment); + }); + } + if (currentChat.creator && (info == null || info.can_set_username)) { typeCell = new TextDetailCell(context); typeCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); typeEditContainer.addView(typeCell, LayoutHelper.createLinear(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); typeCell.setOnClickListener(v -> { - ChatEditTypeActivity fragment = new ChatEditTypeActivity(chatId); + ChatEditTypeActivity fragment = new ChatEditTypeActivity(chatId, locationCell != null && locationCell.getVisibility() == View.VISIBLE); fragment.setInfo(info); presentFragment(fragment); }); @@ -616,7 +644,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image doneButton.setContentDescription(LocaleController.getString("Done", R.string.Done)); } - if (signCell != null || historyCell != null || typeCell != null || linkedCell != null) { + if (locationCell != null || signCell != null || historyCell != null || typeCell != null || linkedCell != null) { settingsSectionCell = new ShadowSectionCell(context); linearLayout1.addView(settingsSectionCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); } @@ -1021,31 +1049,21 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image boolean isPrivate = TextUtils.isEmpty(currentChat.username); if (historyCell != null) { - historyCell.setVisibility(isPrivate && (info == null || info.linked_chat_id == 0) ? View.VISIBLE : View.GONE); + 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); + } } if (settingsSectionCell != null) { - settingsSectionCell.setVisibility(signCell == null && typeCell == null && linkedCell == null && (historyCell == null || historyCell.getVisibility() != View.VISIBLE) ? View.GONE : View.VISIBLE); + 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); } if (logCell != null) { logCell.setVisibility(!currentChat.megagroup || info != null && info.participants_count > 200 ? View.VISIBLE : View.GONE); } - if (typeCell != null) { - String type; - if (isChannel) { - type = isPrivate ? LocaleController.getString("TypePrivate", R.string.TypePrivate) : LocaleController.getString("TypePublic", R.string.TypePublic); - } else { - type = isPrivate ? LocaleController.getString("TypePrivateGroup", R.string.TypePrivateGroup) : LocaleController.getString("TypePublicGroup", R.string.TypePublicGroup); - } - if (isChannel) { - typeCell.setTextAndValue(LocaleController.getString("ChannelType", R.string.ChannelType), type, true); - } else { - typeCell.setTextAndValue(LocaleController.getString("GroupType", R.string.GroupType), type, true); - } - } - if (linkedCell != null) { if (info == null || !isChannel && info.linked_chat_id == 0) { linkedCell.setVisibility(View.GONE); @@ -1076,6 +1094,44 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image } } + if (locationCell != null) { + if (info != null && info.can_set_location) { + 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); + } else { + locationCell.setTextAndValue(LocaleController.getString("AttachLocation", R.string.AttachLocation), "Unknown address", true); + } + } else { + locationCell.setVisibility(View.GONE); + } + } + + if (typeCell != null) { + if (info != null && info.location instanceof TLRPC.TL_channelLocation) { + String link; + if (isPrivate) { + link = LocaleController.getString("TypeLocationGroupEdit", R.string.TypeLocationGroupEdit); + } else { + link = String.format("https://" + MessagesController.getInstance(currentAccount).linkPrefix + "/%s", currentChat.username); + } + typeCell.setTextAndValue(LocaleController.getString("TypeLocationGroup", R.string.TypeLocationGroup), link, historyCell != null && historyCell.getVisibility() == View.VISIBLE || linkedCell != null && linkedCell.getVisibility() == View.VISIBLE); + } else { + String type; + if (isChannel) { + type = isPrivate ? LocaleController.getString("TypePrivate", R.string.TypePrivate) : LocaleController.getString("TypePublic", R.string.TypePublic); + } else { + type = isPrivate ? 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); + } else { + typeCell.setTextAndValue(LocaleController.getString("GroupType", R.string.GroupType), type, historyCell != null && historyCell.getVisibility() == View.VISIBLE || linkedCell != null && linkedCell.getVisibility() == View.VISIBLE); + } + } + } + if (info != null && 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); @@ -1188,6 +1244,9 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image new ThemeDescription(historyCell, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), new ThemeDescription(historyCell, 0, new Class[]{TextDetailCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(historyCell, 0, new Class[]{TextDetailCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(locationCell, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(locationCell, 0, new Class[]{TextDetailCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(locationCell, 0, new Class[]{TextDetailCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), new ThemeDescription(nameTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(nameTextView, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatEditTypeActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatEditTypeActivity.java index a240937e0..2a67b6189 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatEditTypeActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatEditTypeActivity.java @@ -32,7 +32,6 @@ import org.telegram.messenger.ChatObject; 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.R; import org.telegram.tgnet.ConnectionsManager; @@ -104,20 +103,24 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe private boolean loadingInvite; private TLRPC.ExportedChatInvite invite; + private boolean ignoreTextChanges; + + private boolean isForcePublic; + private final static int done_button = 1; - public ChatEditTypeActivity(int id) { + public ChatEditTypeActivity(int id, boolean forcePublic) { chatId = id; + isForcePublic = forcePublic; } - @SuppressWarnings("unchecked") @Override public boolean onFragmentCreate() { - currentChat = MessagesController.getInstance(currentAccount).getChat(chatId); + currentChat = getMessagesController().getChat(chatId); if (currentChat == null) { final CountDownLatch countDownLatch = new CountDownLatch(1); - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { - currentChat = MessagesStorage.getInstance(currentAccount).getChat(chatId); + getMessagesStorage().getStorageQueue().postRunnable(() -> { + currentChat = getMessagesStorage().getChat(chatId); countDownLatch.countDown(); }); try { @@ -126,12 +129,12 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe FileLog.e(e); } if (currentChat != null) { - MessagesController.getInstance(currentAccount).putChat(currentChat, true); + getMessagesController().putChat(currentChat, true); } else { return false; } if (info == null) { - MessagesStorage.getInstance(currentAccount).loadChatInfo(chatId, countDownLatch, false, false); + getMessagesStorage().loadChatInfo(chatId, countDownLatch, false, false); try { countDownLatch.await(); } catch (Exception e) { @@ -142,27 +145,27 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe } } } - isPrivate = TextUtils.isEmpty(currentChat.username); + isPrivate = !isForcePublic && TextUtils.isEmpty(currentChat.username); isChannel = ChatObject.isChannel(currentChat) && !currentChat.megagroup; - if (isPrivate && currentChat.creator) { + if (isForcePublic && TextUtils.isEmpty(currentChat.username) || isPrivate && currentChat.creator) { TLRPC.TL_channels_checkUsername req = new TLRPC.TL_channels_checkUsername(); req.username = "1"; req.channel = new TLRPC.TL_inputChannelEmpty(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { canCreatePublic = error == null || !error.text.equals("CHANNELS_ADMIN_PUBLIC_TOO_MUCH"); if (!canCreatePublic) { loadAdminedChannels(); } })); } - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatInfoDidLoad); + getNotificationCenter().addObserver(this, NotificationCenter.chatInfoDidLoad); return super.onFragmentCreate(); } @Override public void onFragmentDestroy() { super.onFragmentDestroy(); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.chatInfoDidLoad); + getNotificationCenter().removeObserver(this, NotificationCenter.chatInfoDidLoad); AndroidUtilities.removeAdjustResize(getParentActivity(), classGuid); } @@ -179,6 +182,15 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe } } + @Override + protected void onBecomeFullyVisible() { + super.onBecomeFullyVisible(); + if (isForcePublic && usernameTextView != null) { + usernameTextView.requestFocus(); + AndroidUtilities.showKeyboard(usernameTextView); + } + } + @Override public View createView(Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); @@ -213,7 +225,9 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe linearLayout.setOrientation(LinearLayout.VERTICAL); - if (isChannel) { + if (isForcePublic) { + actionBar.setTitle(LocaleController.getString("TypeLocationGroup", R.string.TypeLocationGroup)); + } else if (isChannel) { actionBar.setTitle(LocaleController.getString("ChannelSettingsTitle", R.string.ChannelSettingsTitle)); } else { actionBar.setTitle(LocaleController.getString("GroupSettingsTitle", R.string.GroupSettingsTitle)); @@ -268,6 +282,13 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe sectionCell2 = new ShadowSectionCell(context); linearLayout.addView(sectionCell2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + if (isForcePublic) { + radioButtonCell2.setVisibility(View.GONE); + radioButtonCell1.setVisibility(View.GONE); + sectionCell2.setVisibility(View.GONE); + headerCell2.setVisibility(View.GONE); + } + linkContainer = new LinearLayout(context); linkContainer.setOrientation(LinearLayout.VERTICAL); linkContainer.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); @@ -281,7 +302,7 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe linkContainer.addView(publicContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 23, 7, 23, 0)); editText = new EditText(context); - editText.setText(MessagesController.getInstance(currentAccount).linkPrefix + "/"); + editText.setText(getMessagesController().linkPrefix + "/"); editText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); editText.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); editText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); @@ -297,9 +318,6 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe usernameTextView = new EditTextBoldCursor(context); usernameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - if (!isPrivate) { - usernameTextView.setText(currentChat.username); - } usernameTextView.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); usernameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); usernameTextView.setMaxLines(1); @@ -322,6 +340,9 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe @Override public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + if (ignoreTextChanges) { + return; + } checkUserName(usernameTextView.getText().toString()); } @@ -420,6 +441,12 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe adminedInfoCell = new ShadowSectionCell(context); linearLayout.addView(adminedInfoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + if (!isPrivate && currentChat.username != null) { + ignoreTextChanges = true; + usernameTextView.setText(currentChat.username); + usernameTextView.setSelection(currentChat.username.length()); + ignoreTextChanges = false; + } updatePrivatePublic(); return fragmentView; @@ -449,6 +476,12 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe } private void processDone() { + if (trySetUsername()) { + finishFragment(); + } + } + + private boolean trySetUsername() { if (!isPrivate && ((currentChat.username == null && usernameTextView.length() != 0) || (currentChat.username != null && !currentChat.username.equalsIgnoreCase(usernameTextView.getText().toString())))) { if (usernameTextView.length() != 0 && !lastNameAvailable) { Vibrator v = (Vibrator) getParentActivity().getSystemService(Context.VIBRATOR_SERVICE); @@ -456,25 +489,26 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe v.vibrate(200); } AndroidUtilities.shakeView(checkTextView, 2, 0); - return; + return false; } } + String oldUserName = currentChat.username != null ? currentChat.username : ""; String newUserName = isPrivate ? "" : usernameTextView.getText().toString(); if (!oldUserName.equals(newUserName)) { if (!ChatObject.isChannel(currentChat)) { - MessagesController.getInstance(currentAccount).convertToMegaGroup(getParentActivity(), chatId, param -> { + getMessagesController().convertToMegaGroup(getParentActivity(), chatId, param -> { chatId = param; - currentChat = MessagesController.getInstance(currentAccount).getChat(param); + currentChat = getMessagesController().getChat(param); processDone(); }); - return; + return false; } else { - MessagesController.getInstance(currentAccount).updateChannelUserName(chatId, newUserName); + getMessagesController().updateChannelUserName(chatId, newUserName); currentChat.username = newUserName; } } - finishFragment(); + return true; } private void loadAdminedChannels() { @@ -484,7 +518,7 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe loadingAdminedChannels = true; updatePrivatePublic(); TLRPC.TL_channels_getAdminedPublicChannels req = new TLRPC.TL_channels_getAdminedPublicChannels(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { loadingAdminedChannels = false; if (response != null) { if (getParentActivity() == null) { @@ -503,16 +537,16 @@ 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, MessagesController.getInstance(currentAccount).linkPrefix + "/" + channel.username, channel.title))); + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlertChannel", R.string.RevokeLinkAlertChannel, getMessagesController().linkPrefix + "/" + channel.username, 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, getMessagesController().linkPrefix + "/" + channel.username, channel.title))); } builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); builder.setPositiveButton(LocaleController.getString("RevokeButton", R.string.RevokeButton), (dialogInterface, i) -> { TLRPC.TL_channels_updateUsername req1 = new TLRPC.TL_channels_updateUsername(); req1.channel = MessagesController.getInputChannel(channel); req1.username = ""; - ConnectionsManager.getInstance(currentAccount).sendRequest(req1, (response1, error1) -> { + getConnectionsManager().sendRequest(req1, (response1, error1) -> { if (response1 instanceof TLRPC.TL_boolTrue) { AndroidUtilities.runOnUIThread(() -> { canCreatePublic = true; @@ -544,6 +578,7 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe typeInfoCell.setTag(Theme.key_windowBackgroundWhiteRedText4); typeInfoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); linkContainer.setVisibility(View.GONE); + checkTextView.setVisibility(View.GONE); sectionCell2.setVisibility(View.GONE); adminedInfoCell.setVisibility(View.VISIBLE); if (loadingAdminedChannels) { @@ -560,7 +595,11 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe } else { typeInfoCell.setTag(Theme.key_windowBackgroundWhiteGrayText4); typeInfoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4)); - sectionCell2.setVisibility(View.VISIBLE); + if (isForcePublic) { + sectionCell2.setVisibility(View.GONE); + } else { + sectionCell2.setVisibility(View.VISIBLE); + } adminedInfoCell.setVisibility(View.GONE); typeInfoCell.setBackgroundDrawable(Theme.getThemedDrawable(typeInfoCell.getContext(), R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); adminnedChannelsLayout.setVisibility(View.GONE); @@ -597,7 +636,7 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe checkRunnable = null; lastCheckName = null; if (checkReqId != 0) { - ConnectionsManager.getInstance(currentAccount).cancelRequest(checkReqId, true); + getConnectionsManager().cancelRequest(checkReqId, true); } } lastNameAvailable = false; @@ -646,8 +685,8 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe checkRunnable = () -> { TLRPC.TL_channels_checkUsername req = new TLRPC.TL_channels_checkUsername(); req.username = name; - req.channel = MessagesController.getInstance(currentAccount).getInputChannel(chatId); - checkReqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + req.channel = getMessagesController().getInputChannel(chatId); + checkReqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { checkReqId = 0; if (lastCheckName != null && lastCheckName.equals(name)) { if (error == null && response instanceof TLRPC.TL_boolTrue) { @@ -674,8 +713,8 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe private void generateLink(final boolean newRequest) { loadingInvite = true; TLRPC.TL_messages_exportChatInvite req = new TLRPC.TL_messages_exportChatInvite(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer(-chatId); - final int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + req.peer = getMessagesController().getInputPeer(-chatId); + final int reqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (error == null) { invite = (TLRPC.ExportedChatInvite) response; if (info != null) { @@ -697,7 +736,7 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe privateTextView.setText(invite != null ? invite.link : LocaleController.getString("Loading", R.string.Loading), true); } })); - ConnectionsManager.getInstance(currentAccount).bindRequestToGuid(reqId, classGuid); + getConnectionsManager().bindRequestToGuid(reqId, classGuid); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatLinkActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatLinkActivity.java index 9b4cafffd..9494ddd7f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatLinkActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatLinkActivity.java @@ -865,6 +865,8 @@ public class ChatLinkActivity extends BaseFragment implements NotificationCenter new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundBlue), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundPink), + new ThemeDescription(listView, 0, new Class[]{HintInnerCell.class}, new String[]{"messageTextView"}, null, null, null, Theme.key_chats_message), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{ManageChatTextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{ManageChatTextCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayIcon), new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{ManageChatTextCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueButton), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatRightsEditActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatRightsEditActivity.java index f7b01e3b0..554d91e29 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatRightsEditActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatRightsEditActivity.java @@ -12,14 +12,20 @@ import android.app.DatePickerDialog; import android.app.TimePickerDialog; import android.content.Context; import android.content.DialogInterface; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; +import android.util.TypedValue; +import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.DatePicker; import android.widget.FrameLayout; +import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.TextView; import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; @@ -28,6 +34,7 @@ import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; +import org.telegram.messenger.UserObject; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; @@ -44,12 +51,14 @@ import org.telegram.ui.Cells.TextCheckCell2; import org.telegram.ui.Cells.TextDetailCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; -import org.telegram.ui.Cells2.UserCell; +import org.telegram.ui.Cells.UserCell2; +import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RecyclerListView; import java.util.Calendar; +import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -85,6 +94,8 @@ public class ChatRightsEditActivity extends BaseFragment { private int removeAdminRow; private int removeAdminShadowRow; private int cantEditInfoRow; + private int transferOwnerShadowRow; + private int transferOwnerRow; private int sendMessagesRow; private int sendMediaRow; @@ -97,12 +108,14 @@ public class ChatRightsEditActivity extends BaseFragment { private ChatRightsEditActivityDelegate delegate; private boolean isAddingNew; + private boolean initialIsSet; public static final int TYPE_ADMIN = 0; public static final int TYPE_BANNED = 1; public interface ChatRightsEditActivityDelegate { void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned); + void didChangeOwner(TLRPC.User user); } private final static int done_button = 1; @@ -114,7 +127,6 @@ public class ChatRightsEditActivity extends BaseFragment { currentUser = MessagesController.getInstance(currentAccount).getUser(userId); currentType = type; canEdit = edit; - boolean initialIsSet; currentChat = MessagesController.getInstance(currentAccount).getChat(chatId); if (currentChat != null) { isChannel = ChatObject.isChannel(currentChat) && !currentChat.megagroup; @@ -223,51 +235,7 @@ public class ChatRightsEditActivity extends BaseFragment { initialIsSet = rightsBanned == null || !rightsBanned.view_messages; } - rowCount += 3; - if (type == TYPE_ADMIN) { - if (isChannel) { - changeInfoRow = rowCount++; - postMessagesRow = rowCount++; - editMesagesRow = rowCount++; - deleteMessagesRow = rowCount++; - addUsersRow = rowCount++; - addAdminsRow = rowCount++; - } else { - changeInfoRow = rowCount++; - deleteMessagesRow = rowCount++; - banUsersRow = rowCount++; - addUsersRow = rowCount++; - pinMessagesRow = rowCount++; - addAdminsRow = rowCount++; - } - } else if (type == TYPE_BANNED) { - sendMessagesRow = rowCount++; - sendMediaRow = rowCount++; - sendStickersRow = rowCount++; - sendPollsRow = rowCount++; - embedLinksRow = rowCount++; - addUsersRow = rowCount++; - pinMessagesRow = rowCount++; - changeInfoRow = rowCount++; - untilSectionRow = rowCount++; - untilDateRow = rowCount++; - } - - if (canEdit && initialIsSet) { - rightsShadowRow = rowCount++; - removeAdminRow = rowCount++; - removeAdminShadowRow = rowCount++; - cantEditInfoRow = -1; - } else { - removeAdminRow = -1; - removeAdminShadowRow = -1; - if (type == TYPE_ADMIN && !canEdit) { - rightsShadowRow = -1; - cantEditInfoRow = rowCount++; - } else { - rightsShadowRow = rowCount++; - } - } + updateRows(false); } @Override @@ -303,14 +271,8 @@ public class ChatRightsEditActivity extends BaseFragment { FrameLayout frameLayout = (FrameLayout) fragmentView; listView = new RecyclerListView(context); - LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) { - @Override - public boolean supportsPredictiveItemAnimations() { - return false; - } - }; - listView.setItemAnimator(null); - listView.setLayoutAnimation(null); + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false); + ((DefaultItemAnimator) listView.getItemAnimator()).setDelayAnimations(false); listView.setLayoutManager(linearLayoutManager); listView.setAdapter(listViewAdapter = new ListAdapter(context)); listView.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); @@ -348,6 +310,8 @@ public class ChatRightsEditActivity extends BaseFragment { delegate.didSetRights(0, adminRights, bannedRights); } finishFragment(); + } else if (position == transferOwnerRow) { + initTransfer(null, null); } else if (position == untilDateRow) { if (getParentActivity() == null) { return; @@ -371,7 +335,7 @@ public class ChatRightsEditActivity extends BaseFragment { for (int a = 0; a < buttons.length; a++) { buttons[a] = new BottomSheet.BottomSheetCell(context, 0); - buttons[a].setPadding(AndroidUtilities.dp(23), 0, AndroidUtilities.dp(23), 0); + buttons[a].setPadding(AndroidUtilities.dp(7), 0, AndroidUtilities.dp(7), 0); buttons[a].setTag(a); buttons[a].setBackgroundDrawable(Theme.getSelectorDrawable(false)); String text; @@ -390,7 +354,7 @@ public class ChatRightsEditActivity extends BaseFragment { break; case 4: default: - text = LocaleController.getString("NotificationsCustom", R.string.NotificationsCustom); + text = LocaleController.getString("UserRestrictionsCustom", R.string.UserRestrictionsCustom); break; } buttons[a].setTextAndIcon(text, 0); @@ -584,6 +548,7 @@ public class ChatRightsEditActivity extends BaseFragment { } } } + updateRows(true); } }); return fragmentView; @@ -602,6 +567,253 @@ public class ChatRightsEditActivity extends BaseFragment { !adminRights.change_info && !adminRights.delete_messages && !adminRights.ban_users && !adminRights.invite_users && !adminRights.pin_messages && !adminRights.add_admins; } + private boolean hasAllAdminRights() { + if (isChannel) { + return adminRights.change_info && adminRights.post_messages && adminRights.edit_messages && adminRights.delete_messages && adminRights.invite_users && adminRights.add_admins; + } else { + return adminRights.change_info && adminRights.delete_messages && adminRights.ban_users && adminRights.invite_users && adminRights.pin_messages && adminRights.add_admins; + } + } + + private void initTransfer(TLRPC.InputCheckPasswordSRP srp, TwoStepVerificationActivity passwordFragment) { + if (getParentActivity() == null) { + return; + } + if (srp != null && !ChatObject.isChannel(currentChat)) { + MessagesController.getInstance(currentAccount).convertToMegaGroup(getParentActivity(), chatId, param -> { + chatId = param; + currentChat = MessagesController.getInstance(currentAccount).getChat(param); + initTransfer(srp, passwordFragment); + }); + return; + } + TLRPC.TL_channels_editCreator req = new TLRPC.TL_channels_editCreator(); + if (ChatObject.isChannel(currentChat)) { + req.channel = new TLRPC.TL_inputChannel(); + req.channel.channel_id = currentChat.id; + req.channel.access_hash = currentChat.access_hash; + } else { + req.channel = new TLRPC.TL_inputChannelEmpty(); + } + req.password = srp != null ? srp : new TLRPC.TL_inputCheckPasswordEmpty(); + req.user_id = getMessagesController().getInputUser(currentUser); + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + if (error != null) { + if (getParentActivity() == null) { + return; + } + if ("PASSWORD_HASH_INVALID".equals(error.text)) { + if (srp == null) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + if (isChannel) { + builder.setTitle(LocaleController.getString("EditAdminChannelTransfer", R.string.EditAdminChannelTransfer)); + } else { + builder.setTitle(LocaleController.getString("EditAdminGroupTransfer", R.string.EditAdminGroupTransfer)); + } + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("EditAdminTransferReadyAlertText", R.string.EditAdminTransferReadyAlertText, currentChat.title, UserObject.getFirstName(currentUser)))); + builder.setPositiveButton(LocaleController.getString("EditAdminTransferChangeOwner", R.string.EditAdminTransferChangeOwner), (dialogInterface, i) -> { + TwoStepVerificationActivity fragment = new TwoStepVerificationActivity(0); + fragment.setDelegate(password -> initTransfer(password, fragment)); + presentFragment(fragment); + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } + } else if ("PASSWORD_MISSING".equals(error.text) || error.text.startsWith("PASSWORD_TOO_FRESH_") || error.text.startsWith("SESSION_TOO_FRESH_")) { + if (passwordFragment != null) { + passwordFragment.needHideProgress(); + } + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("EditAdminTransferAlertTitle", R.string.EditAdminTransferAlertTitle)); + + LinearLayout linearLayout = new LinearLayout(getParentActivity()); + linearLayout.setPadding(AndroidUtilities.dp(24), AndroidUtilities.dp(2), AndroidUtilities.dp(24), 0); + linearLayout.setOrientation(LinearLayout.VERTICAL); + builder.setView(linearLayout); + + TextView messageTextView = new TextView(getParentActivity()); + messageTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + messageTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + messageTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + if (isChannel) { + messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("EditChannelAdminTransferAlertText", R.string.EditChannelAdminTransferAlertText, UserObject.getFirstName(currentUser)))); + } else { + messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("EditAdminTransferAlertText", R.string.EditAdminTransferAlertText, UserObject.getFirstName(currentUser)))); + } + linearLayout.addView(messageTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + LinearLayout linearLayout2 = new LinearLayout(getParentActivity()); + linearLayout2.setOrientation(LinearLayout.HORIZONTAL); + linearLayout.addView(linearLayout2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 11, 0, 0)); + + ImageView dotImageView = new ImageView(getParentActivity()); + dotImageView.setImageResource(R.drawable.list_circle); + dotImageView.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(11) : 0, AndroidUtilities.dp(9), LocaleController.isRTL ? 0 : AndroidUtilities.dp(11), 0); + dotImageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogTextBlack), PorterDuff.Mode.MULTIPLY)); + + messageTextView = new TextView(getParentActivity()); + messageTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + messageTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + messageTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.getString("EditAdminTransferAlertText1", R.string.EditAdminTransferAlertText1))); + if (LocaleController.isRTL) { + linearLayout2.addView(messageTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + linearLayout2.addView(dotImageView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.RIGHT)); + } else { + linearLayout2.addView(dotImageView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); + linearLayout2.addView(messageTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + + linearLayout2 = new LinearLayout(getParentActivity()); + linearLayout2.setOrientation(LinearLayout.HORIZONTAL); + linearLayout.addView(linearLayout2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 11, 0, 0)); + + dotImageView = new ImageView(getParentActivity()); + dotImageView.setImageResource(R.drawable.list_circle); + dotImageView.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(11) : 0, AndroidUtilities.dp(9), LocaleController.isRTL ? 0 : AndroidUtilities.dp(11), 0); + dotImageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogTextBlack), PorterDuff.Mode.MULTIPLY)); + + messageTextView = new TextView(getParentActivity()); + messageTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + messageTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + messageTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.getString("EditAdminTransferAlertText2", R.string.EditAdminTransferAlertText2))); + if (LocaleController.isRTL) { + linearLayout2.addView(messageTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + linearLayout2.addView(dotImageView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.RIGHT)); + } else { + linearLayout2.addView(dotImageView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); + linearLayout2.addView(messageTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + + if ("PASSWORD_MISSING".equals(error.text)) { + builder.setPositiveButton(LocaleController.getString("EditAdminTransferSetPassword", R.string.EditAdminTransferSetPassword), (dialogInterface, i) -> presentFragment(new TwoStepVerificationActivity(0))); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + } else { + messageTextView = new TextView(getParentActivity()); + messageTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + messageTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + messageTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + messageTextView.setText(LocaleController.getString("EditAdminTransferAlertText3", R.string.EditAdminTransferAlertText3)); + linearLayout.addView(messageTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 11, 0, 0)); + + builder.setNegativeButton(LocaleController.getString("OK", R.string.OK), null); + } + showDialog(builder.create()); + } else if ("SRP_ID_INVALID".equals(error.text)) { + TLRPC.TL_account_getPassword getPasswordReq = new TLRPC.TL_account_getPassword(); + ConnectionsManager.getInstance(currentAccount).sendRequest(getPasswordReq, (response2, error2) -> AndroidUtilities.runOnUIThread(() -> { + if (error2 == null) { + TLRPC.TL_account_password currentPassword = (TLRPC.TL_account_password) response2; + passwordFragment.setCurrentPasswordInfo(null, currentPassword); + TwoStepVerificationActivity.initPasswordNewAlgo(currentPassword); + initTransfer(passwordFragment.getNewSrpPassword(), passwordFragment); + } + }), ConnectionsManager.RequestFlagWithoutLogin); + } else { + if (passwordFragment != null) { + passwordFragment.needHideProgress(); + passwordFragment.finishFragment(); + } + AlertsCreator.showAddUserAlert(error.text, ChatRightsEditActivity.this, isChannel); + } + } else { + if (srp != null) { + delegate.didChangeOwner(currentUser); + removeSelfFromStack(); + passwordFragment.needHideProgress(); + passwordFragment.finishFragment(); + } + } + })); + } + + private void updateRows(boolean update) { + int transferOwnerShadowRowPrev = transferOwnerShadowRow; + + changeInfoRow = -1; + postMessagesRow = -1; + editMesagesRow = -1; + deleteMessagesRow = -1; + addAdminsRow = -1; + banUsersRow = -1; + addUsersRow = -1; + pinMessagesRow = -1; + rightsShadowRow = -1; + removeAdminRow = -1; + removeAdminShadowRow = -1; + cantEditInfoRow = -1; + transferOwnerShadowRow = -1; + transferOwnerRow = -1; + + sendMessagesRow = -1; + sendMediaRow = -1; + sendStickersRow = -1; + sendPollsRow = -1; + embedLinksRow = -1; + untilSectionRow = -1; + untilDateRow = -1; + + rowCount = 3; + if (currentType == TYPE_ADMIN) { + if (isChannel) { + changeInfoRow = rowCount++; + postMessagesRow = rowCount++; + editMesagesRow = rowCount++; + deleteMessagesRow = rowCount++; + addUsersRow = rowCount++; + addAdminsRow = rowCount++; + } else { + changeInfoRow = rowCount++; + deleteMessagesRow = rowCount++; + banUsersRow = rowCount++; + addUsersRow = rowCount++; + pinMessagesRow = rowCount++; + addAdminsRow = rowCount++; + } + } else if (currentType == TYPE_BANNED) { + sendMessagesRow = rowCount++; + sendMediaRow = rowCount++; + sendStickersRow = rowCount++; + sendPollsRow = rowCount++; + embedLinksRow = rowCount++; + addUsersRow = rowCount++; + pinMessagesRow = rowCount++; + changeInfoRow = rowCount++; + untilSectionRow = rowCount++; + untilDateRow = rowCount++; + } + + if (canEdit) { + if (currentChat != null && currentChat.creator && currentType == TYPE_ADMIN && hasAllAdminRights() && !currentUser.bot) { + transferOwnerShadowRow = rowCount++; + transferOwnerRow = rowCount++; + } + if (initialIsSet) { + rightsShadowRow = rowCount++; + removeAdminRow = rowCount++; + removeAdminShadowRow = rowCount++; + cantEditInfoRow = -1; + } + } else { + removeAdminRow = -1; + removeAdminShadowRow = -1; + if (currentType == TYPE_ADMIN && !canEdit) { + rightsShadowRow = -1; + cantEditInfoRow = rowCount++; + } else { + rightsShadowRow = rowCount++; + } + } + if (update) { + if (transferOwnerShadowRowPrev == -1 && transferOwnerShadowRow != -1) { + listViewAdapter.notifyItemRangeInserted(transferOwnerShadowRow, 2); + } else if (transferOwnerShadowRowPrev != -1 && transferOwnerShadowRow == -1) { + listViewAdapter.notifyItemRangeRemoved(transferOwnerShadowRowPrev, 2); + } + } + } + private void onDonePressed() { if (!ChatObject.isChannel(currentChat) && (currentType == TYPE_BANNED || currentType == TYPE_ADMIN && !isDefaultAdminRights())) { MessagesController.getInstance(currentAccount).convertToMegaGroup(getParentActivity(), chatId, param -> { @@ -715,7 +927,7 @@ public class ChatRightsEditActivity extends BaseFragment { View view; switch (viewType) { case 0: - view = new UserCell(mContext, 4, 0); + view = new UserCell2(mContext, 4, 0); view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); break; case 1: @@ -750,8 +962,8 @@ public class ChatRightsEditActivity extends BaseFragment { public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (holder.getItemViewType()) { case 0: - UserCell userCell = (UserCell) holder.itemView; - userCell.setData(currentUser, null, null, 0); + UserCell2 userCell2 = (UserCell2) holder.itemView; + userCell2.setData(currentUser, null, null, 0); break; case 1: TextInfoPrivacyCell privacyCell = (TextInfoPrivacyCell) holder.itemView; @@ -769,6 +981,14 @@ public class ChatRightsEditActivity extends BaseFragment { } else if (currentType == TYPE_BANNED) { actionCell.setText(LocaleController.getString("UserRestrictionsBlock", R.string.UserRestrictionsBlock), false); } + } else if (position == transferOwnerRow) { + actionCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + actionCell.setTag(Theme.key_windowBackgroundWhiteBlackText); + if (isChannel) { + actionCell.setText(LocaleController.getString("EditAdminChannelTransfer", R.string.EditAdminChannelTransfer), false); + } else { + actionCell.setText(LocaleController.getString("EditAdminGroupTransfer", R.string.EditAdminGroupTransfer), false); + } } break; case 3: @@ -876,7 +1096,7 @@ public class ChatRightsEditActivity extends BaseFragment { public int getItemViewType(int position) { if (position == 0) { return 0; - } else if (position == 1 || position == rightsShadowRow || position == removeAdminShadowRow || position == untilSectionRow) { + } else if (position == 1 || position == rightsShadowRow || position == removeAdminShadowRow || position == untilSectionRow || position == transferOwnerShadowRow) { return 5; } else if (position == 2) { return 3; @@ -902,15 +1122,15 @@ public class ChatRightsEditActivity extends BaseFragment { int count = listView.getChildCount(); for (int a = 0; a < count; a++) { View child = listView.getChildAt(a); - if (child instanceof UserCell) { - ((UserCell) child).update(0); + if (child instanceof UserCell2) { + ((UserCell2) child).update(0); } } } }; return new ThemeDescription[]{ - new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{UserCell.class, TextSettingsCell.class, TextCheckCell2.class, HeaderCell.class, TextDetailCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{UserCell2.class, TextSettingsCell.class, TextCheckCell2.class, HeaderCell.class, TextDetailCell.class}, null, null, null, Theme.key_windowBackgroundWhite), new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), @@ -943,10 +1163,10 @@ public class ChatRightsEditActivity extends BaseFragment { new ThemeDescription(listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), - new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), - new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusColor"}, null, null, cellDelegate, Theme.key_windowBackgroundWhiteGrayText), - new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusOnlineColor"}, null, null, cellDelegate, Theme.key_windowBackgroundWhiteBlueText), - new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{UserCell2.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{UserCell2.class}, new String[]{"statusColor"}, null, null, cellDelegate, Theme.key_windowBackgroundWhiteGrayText), + new ThemeDescription(listView, 0, new Class[]{UserCell2.class}, new String[]{"statusOnlineColor"}, null, null, cellDelegate, Theme.key_windowBackgroundWhiteBlueText), + new ThemeDescription(listView, 0, new Class[]{UserCell2.class}, null, new Drawable[]{Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java index fb139c3a8..d30227028 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java @@ -62,6 +62,7 @@ import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.UndoView; import java.util.ArrayList; import java.util.Collections; @@ -78,6 +79,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente private SearchAdapter searchListViewAdapter; private ActionBarMenuItem searchItem; private ActionBarMenuItem doneItem; + private UndoView undoView; private TLRPC.Chat currentChat; private TLRPC.ChatFull info; @@ -151,6 +153,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente public interface ChatUsersActivityDelegate { void didAddParticipantToList(int uid, TLObject participant); + void didChangeOwner(TLRPC.User user); } public ChatUsersActivity(Bundle args) { @@ -461,21 +464,31 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente bundle.putInt("type", ChatUsersActivity.TYPE_USERS); bundle.putInt("selectType", 1); ChatUsersActivity fragment = new ChatUsersActivity(bundle); - fragment.setDelegate((uid, participant) -> { - if (participant != null && participantsMap.get(uid) == null) { - participants.add(participant); - Collections.sort(participants, (lhs, rhs) -> { - int type1 = getChannelAdminParticipantType(lhs); - int type2 = getChannelAdminParticipantType(rhs); - if (type1 > type2) { - return 1; - } else if (type1 < type2) { - return -1; + fragment.setDelegate(new ChatUsersActivityDelegate() { + @Override + public void didAddParticipantToList(int uid, TLObject participant) { + if (participant != null && participantsMap.get(uid) == null) { + participants.add(participant); + Collections.sort(participants, (lhs, rhs) -> { + int type1 = getChannelAdminParticipantType(lhs); + int type2 = getChannelAdminParticipantType(rhs); + if (type1 > type2) { + return 1; + } else if (type1 < type2) { + return -1; + } + return 0; + }); + updateRows(); + if (listViewAdapter != null) { + listViewAdapter.notifyDataSetChanged(); } - return 0; - }); - updateRows(); - listViewAdapter.notifyDataSetChanged(); + } + } + + @Override + public void didChangeOwner(TLRPC.User user) { + onOwnerChaged(user); } }); fragment.setInfo(info); @@ -485,6 +498,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente args.putBoolean("addToGroup", true); args.putInt(isChannel ? "channelId" : "chatId", currentChat.id); GroupCreateActivity fragment = new GroupCreateActivity(args); + fragment.setInfo(info); fragment.setIgnoreUsers(contactsMap != null && contactsMap.size() != 0 ? contactsMap : participantsMap); fragment.setDelegate(new GroupCreateActivity.ContactsAddActivityDelegate() { @Override @@ -715,12 +729,20 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente bannedRights.change_info = true; } ChatRightsEditActivity fragment = new ChatRightsEditActivity(user_id, chatId, adminRights, defaultBannedRights, bannedRights, type == TYPE_ADMIN ? ChatRightsEditActivity.TYPE_ADMIN : ChatRightsEditActivity.TYPE_BANNED, canEdit, participant == null); - fragment.setDelegate((rights, rightsAdmin, rightsBanned) -> { - if (participant instanceof TLRPC.ChannelParticipant) { - TLRPC.ChannelParticipant channelParticipant = (TLRPC.ChannelParticipant) participant; - channelParticipant.admin_rights = rightsAdmin; - channelParticipant.banned_rights = rightsBanned; - updateParticipantWithRights(channelParticipant, rightsAdmin, rightsBanned, 0, false); + fragment.setDelegate(new ChatRightsEditActivity.ChatRightsEditActivityDelegate() { + @Override + public void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned) { + if (participant instanceof TLRPC.ChannelParticipant) { + TLRPC.ChannelParticipant channelParticipant = (TLRPC.ChannelParticipant) participant; + channelParticipant.admin_rights = rightsAdmin; + channelParticipant.banned_rights = rightsBanned; + updateParticipantWithRights(channelParticipant, rightsAdmin, rightsBanned, 0, false); + } + } + + @Override + public void didChangeOwner(TLRPC.User user) { + onOwnerChaged(user); } }); presentFragment(fragment); @@ -746,6 +768,9 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente }); } + undoView = new UndoView(context); + frameLayout.addView(undoView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT, 8, 0, 8, 8)); + if (loadingUsers) { emptyView.showProgress(); } else { @@ -754,52 +779,147 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente updateRows(); return fragmentView; } + + private void onOwnerChaged(TLRPC.User user) { + undoView.showWithAction(-chatId, isChannel ? UndoView.ACTION_OWNER_TRANSFERED_CHANNEL : UndoView.ACTION_OWNER_TRANSFERED_GROUP, user); + boolean foundAny = false; + currentChat.creator = false; + for (int a = 0; a < 3; a++) { + SparseArray map; + ArrayList arrayList; + boolean found = false; + if (a == 0) { + map = contactsMap; + arrayList = contacts; + } else if (a == 1) { + map = botsMap; + arrayList = bots; + } else { + map = participantsMap; + arrayList = participants; + } + TLObject object = map.get(user.id); + if (object instanceof TLRPC.ChannelParticipant) { + TLRPC.TL_channelParticipantCreator creator = new TLRPC.TL_channelParticipantCreator(); + creator.user_id = user.id; + map.put(user.id, creator); + int index = arrayList.indexOf(object); + if (index >= 0) { + arrayList.set(index, creator); + } + found = true; + foundAny = true; + } + int selfUserId = getUserConfig().getClientUserId(); + object = map.get(selfUserId); + if (object instanceof TLRPC.ChannelParticipant) { + TLRPC.TL_channelParticipantAdmin admin = new TLRPC.TL_channelParticipantAdmin(); + admin.user_id = selfUserId; + admin.self = true; + admin.inviter_id = selfUserId; + admin.promoted_by = selfUserId; + admin.date = (int) (System.currentTimeMillis() / 1000); + 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; + map.put(selfUserId, admin); + + int index = arrayList.indexOf(object); + if (index >= 0) { + arrayList.set(index, admin); + } + found = true; + } + if (found) { + Collections.sort(arrayList, (lhs, rhs) -> { + int type1 = getChannelAdminParticipantType(lhs); + int type2 = getChannelAdminParticipantType(rhs); + if (type1 > type2) { + return 1; + } else if (type1 < type2) { + return -1; + } + return 0; + }); + } + } + if (!foundAny) { + TLRPC.TL_channelParticipantCreator creator = new TLRPC.TL_channelParticipantCreator(); + creator.user_id = user.id; + participantsMap.put(user.id, creator); + participants.add(creator); + Collections.sort(participants, (lhs, rhs) -> { + int type1 = getChannelAdminParticipantType(lhs); + int type2 = getChannelAdminParticipantType(rhs); + if (type1 > type2) { + return 1; + } else if (type1 < type2) { + return -1; + } + return 0; + }); + updateRows(); + } + listViewAdapter.notifyDataSetChanged(); + if (delegate != null) { + delegate.didChangeOwner(user); + } + } private void openRightsEdit2(int userId, int date, TLObject participant, TLRPC.TL_chatAdminRights adminRights, TLRPC.TL_chatBannedRights bannedRights, boolean canEditAdmin, int type, boolean removeFragment) { ChatRightsEditActivity fragment = new ChatRightsEditActivity(userId, chatId, adminRights, defaultBannedRights, bannedRights, type, true, false); - fragment.setDelegate((rights, rightsAdmin, rightsBanned) -> { - if (type == 0) { - for (int a = 0; a < participants.size(); a++) { - TLObject p = participants.get(a); - if (p instanceof TLRPC.ChannelParticipant) { - TLRPC.ChannelParticipant p2 = (TLRPC.ChannelParticipant) p; - if (p2.user_id == userId) { - TLRPC.ChannelParticipant newPart; - if (rights == 1) { - newPart = new TLRPC.TL_channelParticipantAdmin(); - } else { - newPart = new TLRPC.TL_channelParticipant(); + fragment.setDelegate(new ChatRightsEditActivity.ChatRightsEditActivityDelegate() { + @Override + public void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned) { + if (type == 0) { + for (int a = 0; a < participants.size(); a++) { + TLObject p = participants.get(a); + if (p instanceof TLRPC.ChannelParticipant) { + TLRPC.ChannelParticipant p2 = (TLRPC.ChannelParticipant) p; + if (p2.user_id == userId) { + TLRPC.ChannelParticipant newPart; + if (rights == 1) { + newPart = new TLRPC.TL_channelParticipantAdmin(); + } else { + newPart = new TLRPC.TL_channelParticipant(); + } + newPart.admin_rights = rightsAdmin; + newPart.banned_rights = rightsBanned; + newPart.inviter_id = UserConfig.getInstance(currentAccount).getClientUserId(); + newPart.user_id = userId; + newPart.date = date; + participants.set(a, newPart); + break; } - newPart.admin_rights = rightsAdmin; - newPart.banned_rights = rightsBanned; - newPart.inviter_id = UserConfig.getInstance(currentAccount).getClientUserId(); - newPart.user_id = userId; - newPart.date = date; - participants.set(a, newPart); - break; + } else if (p instanceof TLRPC.ChatParticipant) { + TLRPC.ChatParticipant chatParticipant = (TLRPC.ChatParticipant) p; + TLRPC.ChatParticipant newParticipant; + if (rights == 1) { + newParticipant = new TLRPC.TL_chatParticipantAdmin(); + } else { + newParticipant = new TLRPC.TL_chatParticipant(); + } + newParticipant.user_id = chatParticipant.user_id; + newParticipant.date = chatParticipant.date; + newParticipant.inviter_id = chatParticipant.inviter_id; + int index = info.participants.participants.indexOf(chatParticipant); + if (index >= 0) { + info.participants.participants.set(index, newParticipant); + } + loadChatParticipants(0, 200); } - } else if (p instanceof TLRPC.ChatParticipant) { - TLRPC.ChatParticipant chatParticipant = (TLRPC.ChatParticipant) p; - TLRPC.ChatParticipant newParticipant; - if (rights == 1) { - newParticipant = new TLRPC.TL_chatParticipantAdmin(); - } else { - newParticipant = new TLRPC.TL_chatParticipant(); - } - newParticipant.user_id = chatParticipant.user_id; - newParticipant.date = chatParticipant.date; - newParticipant.inviter_id = chatParticipant.inviter_id; - int index = info.participants.participants.indexOf(chatParticipant); - if (index >= 0) { - info.participants.participants.set(index, newParticipant); - } - loadChatParticipants(0, 200); + } + } else if (type == 1) { + if (rights == 0) { + removeParticipants(userId); } } - } else if (type == 1) { - if (rights == 0) { - removeParticipants(userId); - } + } + + @Override + public void didChangeOwner(TLRPC.User user) { + onOwnerChaged(user); } }); presentFragment(fragment); @@ -807,14 +927,22 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente private void openRightsEdit(int user_id, TLObject participant, TLRPC.TL_chatAdminRights adminRights, TLRPC.TL_chatBannedRights bannedRights, boolean canEditAdmin, int type, boolean removeFragment) { ChatRightsEditActivity fragment = new ChatRightsEditActivity(user_id, chatId, adminRights, defaultBannedRights, bannedRights, type, canEditAdmin, participant == null); - fragment.setDelegate((rights, rightsAdmin, rightsBanned) -> { - if (participant instanceof TLRPC.ChannelParticipant) { - TLRPC.ChannelParticipant channelParticipant = (TLRPC.ChannelParticipant) participant; - channelParticipant.admin_rights = rightsAdmin; - channelParticipant.banned_rights = rightsBanned; + fragment.setDelegate(new ChatRightsEditActivity.ChatRightsEditActivityDelegate() { + @Override + public void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned) { + if (participant instanceof TLRPC.ChannelParticipant) { + TLRPC.ChannelParticipant channelParticipant = (TLRPC.ChannelParticipant) participant; + channelParticipant.admin_rights = rightsAdmin; + channelParticipant.banned_rights = rightsBanned; + } + if (removeFragment) { + removeSelfFromStack(); + } } - if (removeFragment) { - removeSelfFromStack(); + + @Override + public void didChangeOwner(TLRPC.User user) { + onOwnerChaged(user); } }); presentFragment(fragment, removeFragment); @@ -833,7 +961,6 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente boolean updated = false; for (int a = 0; a < 3; a++) { SparseArray map; - ArrayList arrayList; if (a == 0) { map = contactsMap; } else if (a == 1) { @@ -1008,7 +1135,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente actionBar.closeSearchField(); } } else { - if (canEditAdmin && (participant instanceof TLRPC.TL_channelParticipantAdmin || participant instanceof TLRPC.TL_chatParticipantAdmin)) { + if (actions.get(i) == 1 && canEditAdmin && (participant instanceof TLRPC.TL_channelParticipantAdmin || participant instanceof TLRPC.TL_chatParticipantAdmin)) { AlertDialog.Builder builder2 = new AlertDialog.Builder(getParentActivity()); builder2.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder2.setMessage(LocaleController.formatString("AdminWillBeRemoved", R.string.AdminWillBeRemoved, ContactsController.formatName(user.first_name, user.last_name))); @@ -1077,12 +1204,20 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente if (type == TYPE_ADMIN) { if (i == 0 && items.length == 2) { ChatRightsEditActivity fragment = new ChatRightsEditActivity(userId, chatId, adminRights, null, null, ChatRightsEditActivity.TYPE_ADMIN, true, false); - fragment.setDelegate((rights, rightsAdmin, rightsBanned) -> { - if (participant instanceof TLRPC.ChannelParticipant) { - TLRPC.ChannelParticipant channelParticipant = (TLRPC.ChannelParticipant) participant; - channelParticipant.admin_rights = rightsAdmin; - channelParticipant.banned_rights = rightsBanned; - updateParticipantWithRights(channelParticipant, rightsAdmin, rightsBanned, 0, false); + fragment.setDelegate(new ChatRightsEditActivity.ChatRightsEditActivityDelegate() { + @Override + public void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned) { + if (participant instanceof TLRPC.ChannelParticipant) { + TLRPC.ChannelParticipant channelParticipant = (TLRPC.ChannelParticipant) participant; + channelParticipant.admin_rights = rightsAdmin; + channelParticipant.banned_rights = rightsBanned; + updateParticipantWithRights(channelParticipant, rightsAdmin, rightsBanned, 0, false); + } + } + + @Override + public void didChangeOwner(TLRPC.User user) { + onOwnerChaged(user); } }); presentFragment(fragment); @@ -1094,12 +1229,20 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente if (i == 0) { if (type == TYPE_KICKED) { ChatRightsEditActivity fragment = new ChatRightsEditActivity(userId, chatId, null, defaultBannedRights, bannedRights, ChatRightsEditActivity.TYPE_BANNED, true, false); - fragment.setDelegate((rights, rightsAdmin, rightsBanned) -> { - if (participant instanceof TLRPC.ChannelParticipant) { - TLRPC.ChannelParticipant channelParticipant = (TLRPC.ChannelParticipant) participant; - channelParticipant.admin_rights = rightsAdmin; - channelParticipant.banned_rights = rightsBanned; - updateParticipantWithRights(channelParticipant, rightsAdmin, rightsBanned, 0, false); + fragment.setDelegate(new ChatRightsEditActivity.ChatRightsEditActivityDelegate() { + @Override + public void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned) { + if (participant instanceof TLRPC.ChannelParticipant) { + TLRPC.ChannelParticipant channelParticipant = (TLRPC.ChannelParticipant) participant; + channelParticipant.admin_rights = rightsAdmin; + channelParticipant.banned_rights = rightsBanned; + updateParticipantWithRights(channelParticipant, rightsAdmin, rightsBanned, 0, false); + } + } + + @Override + public void didChangeOwner(TLRPC.User user) { + onOwnerChaged(user); } }); presentFragment(fragment); @@ -1540,6 +1683,21 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente } } + @Override + public void onPause() { + super.onPause(); + if (undoView != null) { + undoView.hide(true, 0); + } + } + + @Override + protected void onBecomeFullyHidden() { + if (undoView != null) { + undoView.hide(true, 0); + } + } + @Override protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen && !backward && needOpenSearch) { @@ -1585,7 +1743,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente searchResult.clear(); searchResultNames.clear(); searchAdapterHelper.mergeResults(null); - searchAdapterHelper.queryServerSearch(null, type != 0, false, true, false, ChatObject.isChannel(currentChat) ? chatId : 0, type); + searchAdapterHelper.queryServerSearch(null, type != 0, false, true, false, ChatObject.isChannel(currentChat) ? chatId : 0, false, type); notifyDataSetChanged(); } else { Utilities.searchQueue.postRunnable(searchRunnable = () -> processSearch(query), 300); @@ -1600,7 +1758,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente final ArrayList participantsCopy = !ChatObject.isChannel(currentChat) && info != null ? new ArrayList<>(info.participants.participants) : null; final ArrayList contactsCopy = selectType == 1 ? new ArrayList<>(ContactsController.getInstance(currentAccount).contacts) : null; - searchAdapterHelper.queryServerSearch(query, selectType != 0, false, true, false, ChatObject.isChannel(currentChat) ? chatId : 0, type); + searchAdapterHelper.queryServerSearch(query, selectType != 0, false, true, false, ChatObject.isChannel(currentChat) ? chatId : 0, false, type); if (participantsCopy != null || contactsCopy != null) { Utilities.searchQueue.postRunnable(() -> { String search1 = query.trim().toLowerCase(); @@ -1884,7 +2042,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(); spannableStringBuilder.append("@"); spannableStringBuilder.append(un); - if ((index = un.toLowerCase().indexOf(foundUserName)) != -1) { + if ((index = AndroidUtilities.indexOfIgnoreCase(un, foundUserName)) != -1) { int len = foundUserName.length(); if (index == 0) { len++; @@ -1905,7 +2063,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente if (nameSearch != null) { String u = UserObject.getUserName(user); name = new SpannableStringBuilder(u); - int idx = u.toLowerCase().indexOf(nameSearch); + int idx = AndroidUtilities.indexOfIgnoreCase(u, nameSearch); if (idx != -1) { ((SpannableStringBuilder) name).setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)), idx, idx + nameSearch.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } @@ -2140,7 +2298,11 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente } else if (admin) { TLRPC.User user1 = MessagesController.getInstance(currentAccount).getUser(promotedBy); if (user1 != null) { - role = LocaleController.formatString("EditAdminPromotedBy", R.string.EditAdminPromotedBy, ContactsController.formatName(user1.first_name, user1.last_name)); + if (user1.id == user.id) { + role = LocaleController.getString("ChannelAdministrator", R.string.ChannelAdministrator); + } else { + role = LocaleController.formatString("EditAdminPromotedBy", R.string.EditAdminPromotedBy, ContactsController.formatName(user1.first_name, user1.last_name)); + } } } userCell.setData(user, null, role, position != lastRow - 1); @@ -2406,6 +2568,14 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundBlue), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundPink), + new ThemeDescription(undoView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_undo_background), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"undoImageView"}, null, null, null, Theme.key_undo_cancelColor), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"undoTextView"}, null, null, null, Theme.key_undo_cancelColor), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"infoTextView"}, null, null, null, Theme.key_undo_infoColor), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"textPaint"}, null, null, null, Theme.key_undo_infoColor), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"progressPaint"}, null, null, null, Theme.key_undo_infoColor), + new ThemeDescription(undoView, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{UndoView.class}, new String[]{"leftImageView"}, null, null, null, Theme.key_undo_infoColor), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{ManageChatTextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{ManageChatTextCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayIcon), new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{ManageChatTextCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueButton), 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 259d36d76..2e8c0df7f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java @@ -33,6 +33,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import org.telegram.messenger.AccountInstance; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.BuildVars; @@ -408,6 +409,96 @@ public class AlertsCreator { return dialog; } + public static void showBlockReportSpamAlert(BaseFragment fragment, long dialog_id, TLRPC.User currentUser, TLRPC.Chat currentChat, TLRPC.EncryptedChat encryptedChat, boolean isLocation, TLRPC.ChatFull chatInfo, MessagesStorage.IntCallback callback) { + if (fragment == null || fragment.getParentActivity() == null) { + return; + } + AccountInstance accountInstance = fragment.getAccountInstance(); + AlertDialog.Builder builder = new AlertDialog.Builder(fragment.getParentActivity()); + CharSequence reportText; + CheckBoxCell[] cells; + SharedPreferences preferences = MessagesController.getNotificationsSettings(fragment.getCurrentAccount()); + boolean showReport = preferences.getBoolean("dialog_bar_report" + dialog_id, false); + if (currentUser != null) { + builder.setTitle(LocaleController.formatString("BlockUserTitle", R.string.BlockUserTitle, UserObject.getFirstName(currentUser))); + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("BlockUserAlert", R.string.BlockUserAlert, UserObject.getFirstName(currentUser)))); + reportText = LocaleController.getString("BlockContact", R.string.BlockContact); + + cells = new CheckBoxCell[2]; + LinearLayout linearLayout = new LinearLayout(fragment.getParentActivity()); + linearLayout.setOrientation(LinearLayout.VERTICAL); + for (int a = 0; a < 2; a++) { + if (a == 0 && !showReport) { + continue; + } + cells[a] = new CheckBoxCell(fragment.getParentActivity(), 1); + cells[a].setBackgroundDrawable(Theme.getSelectorDrawable(false)); + cells[a].setTag(a); + if (a == 0) { + cells[a].setText(LocaleController.getString("DeleteReportSpam", R.string.DeleteReportSpam), "", true, false); + } else if (a == 1) { + cells[a].setText(LocaleController.formatString("DeleteThisChat", R.string.DeleteThisChat), "", true, false); + } + cells[a].setPadding(LocaleController.isRTL ? AndroidUtilities.dp(16) : AndroidUtilities.dp(8), 0, LocaleController.isRTL ? AndroidUtilities.dp(8) : AndroidUtilities.dp(16), 0); + linearLayout.addView(cells[a], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + cells[a].setOnClickListener(v -> { + Integer num = (Integer) v.getTag(); + cells[num].setChecked(!cells[num].isChecked(), true); + }); + } + builder.setCustomViewOffset(12); + builder.setView(linearLayout); + } else { + cells = null; + if (currentChat != null && isLocation) { + builder.setTitle(LocaleController.getString("ReportUnrelatedGroup", R.string.ReportUnrelatedGroup)); + if (chatInfo != null && chatInfo.location instanceof TLRPC.TL_channelLocation) { + TLRPC.TL_channelLocation location = (TLRPC.TL_channelLocation) chatInfo.location; + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("ReportUnrelatedGroupText", R.string.ReportUnrelatedGroupText, location.address))); + } else { + builder.setMessage(LocaleController.getString("ReportUnrelatedGroupTextNoAddress", R.string.ReportUnrelatedGroupTextNoAddress)); + } + } else { + builder.setTitle(LocaleController.getString("ReportSpamTitle", R.string.ReportSpamTitle)); + if (ChatObject.isChannel(currentChat) && !currentChat.megagroup) { + builder.setMessage(LocaleController.getString("ReportSpamAlertChannel", R.string.ReportSpamAlertChannel)); + } else { + builder.setMessage(LocaleController.getString("ReportSpamAlertGroup", R.string.ReportSpamAlertGroup)); + } + } + reportText = LocaleController.getString("ReportChat", R.string.ReportChat); + } + builder.setPositiveButton(reportText, (dialogInterface, i) -> { + if (currentUser != null) { + accountInstance.getMessagesController().blockUser(currentUser.id); + } + if (cells == null || cells[0] != null && cells[0].isChecked()) { + accountInstance.getMessagesController().reportSpam(dialog_id, currentUser, currentChat, encryptedChat, currentChat != null && isLocation); + } + if (cells == null || cells[1].isChecked()) { + if (currentChat != null) { + if (ChatObject.isNotInChat(currentChat)) { + accountInstance.getMessagesController().deleteDialog(dialog_id, 0); + } else { + accountInstance.getMessagesController().deleteUserFromChat((int) -dialog_id, accountInstance.getMessagesController().getUser(accountInstance.getUserConfig().getClientUserId()), null); + } + } else { + accountInstance.getMessagesController().deleteDialog(dialog_id, 0); + } + callback.run(1); + } else { + callback.run(0); + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + AlertDialog dialog = builder.create(); + fragment.showDialog(dialog); + TextView button = (TextView) dialog.getButton(DialogInterface.BUTTON_POSITIVE); + if (button != null) { + button.setTextColor(Theme.getColor(Theme.key_dialogTextRed2)); + } + } + 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); } @@ -1270,6 +1361,15 @@ public class AlertsCreator { case "USER_ADMIN_INVALID": builder.setMessage(LocaleController.getString("AddBannedErrorAdmin", R.string.AddBannedErrorAdmin)); break; + case "CHANNELS_ADMIN_PUBLIC_TOO_MUCH": + builder.setMessage(LocaleController.getString("PublicChannelsTooMuch", R.string.PublicChannelsTooMuch)); + break; + case "CHANNELS_ADMIN_LOCATED_TOO_MUCH": + builder.setMessage(LocaleController.getString("LocatedChannelsTooMuch", R.string.LocatedChannelsTooMuch)); + break; + case "CHANNELS_TOO_MUCH": + builder.setMessage(LocaleController.getString("ChannelTooMuch", R.string.ChannelTooMuch)); + break; default: builder.setMessage(LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred) + "\n" + error); break; @@ -1517,7 +1617,7 @@ public class AlertsCreator { }); } AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); - builder.setTopImage(new ShareLocationDrawable(parentActivity, false), Theme.getColor(Theme.key_dialogTopBackground)); + builder.setTopImage(new ShareLocationDrawable(parentActivity, 0), Theme.getColor(Theme.key_dialogTopBackground)); builder.setView(linearLayout); builder.setPositiveButton(LocaleController.getString("ShareFile", R.string.ShareFile), (dialog, which) -> { int time; 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 154626606..5325ed8db 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java @@ -19,13 +19,9 @@ import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; -import com.airbnb.lottie.LottieProperty; -import com.airbnb.lottie.SimpleColorFilter; -import com.airbnb.lottie.model.KeyPath; -import com.airbnb.lottie.value.LottieValueCallback; - import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; +import org.telegram.messenger.UserObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; @@ -39,6 +35,7 @@ public class AvatarDrawable extends Drawable { private float textLeft; private boolean isProfile; private boolean drawBrodcast; + private boolean drawDeleted; private int avatarType; private float archivedAvatarProgress; private StringBuilder stringBuilder = new StringBuilder(5); @@ -69,6 +66,7 @@ public class AvatarDrawable extends Drawable { isProfile = profile; if (user != null) { setInfo(user.id, user.first_name, user.last_name, false, null); + drawDeleted = UserObject.isDeleted(user); } } @@ -122,6 +120,7 @@ public class AvatarDrawable extends Drawable { public void setInfo(TLRPC.User user) { if (user != null) { setInfo(user.id, user.first_name, user.last_name, false, null); + drawDeleted = UserObject.isDeleted(user); } } @@ -173,6 +172,7 @@ public class AvatarDrawable extends Drawable { drawBrodcast = isBroadcast; avatarType = AVATAR_TYPE_NORMAL; + drawDeleted = false; if (firstName == null || firstName.length() == 0) { firstName = lastName; @@ -248,14 +248,18 @@ public class AvatarDrawable extends Drawable { Theme.avatar_backgroundPaint.setColor(Theme.getColor(Theme.key_avatar_backgroundArchived)); canvas.drawCircle(size / 2.0f, size / 2.0f, size / 2.0f * archivedAvatarProgress, Theme.avatar_backgroundPaint); if (Theme.dialogs_archiveAvatarDrawableRecolored) { - Theme.dialogs_archiveAvatarDrawable.addValueCallback(new KeyPath("Arrow1", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_avatar_backgroundArchived)))); - Theme.dialogs_archiveAvatarDrawable.addValueCallback(new KeyPath("Arrow2", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_avatar_backgroundArchived)))); + Theme.dialogs_archiveAvatarDrawable.beginApplyLayerColors(); + Theme.dialogs_archiveAvatarDrawable.setLayerColor("Arrow1.**", Theme.getColor(Theme.key_avatar_backgroundArchived)); + Theme.dialogs_archiveAvatarDrawable.setLayerColor("Arrow2.**", Theme.getColor(Theme.key_avatar_backgroundArchived)); + Theme.dialogs_archiveAvatarDrawable.commitApplyLayerColors(); Theme.dialogs_archiveAvatarDrawableRecolored = false; } } else { if (!Theme.dialogs_archiveAvatarDrawableRecolored) { - Theme.dialogs_archiveAvatarDrawable.addValueCallback(new KeyPath("Arrow1", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_avatar_backgroundArchivedHidden)))); - Theme.dialogs_archiveAvatarDrawable.addValueCallback(new KeyPath("Arrow2", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_avatar_backgroundArchivedHidden)))); + Theme.dialogs_archiveAvatarDrawable.beginApplyLayerColors(); + Theme.dialogs_archiveAvatarDrawable.setLayerColor("Arrow1.**", Theme.getColor(Theme.key_avatar_backgroundArchivedHidden)); + Theme.dialogs_archiveAvatarDrawable.setLayerColor("Arrow2.**", Theme.getColor(Theme.key_avatar_backgroundArchivedHidden)); + Theme.dialogs_archiveAvatarDrawable.commitApplyLayerColors(); Theme.dialogs_archiveAvatarDrawableRecolored = true; } } @@ -264,7 +268,6 @@ public class AvatarDrawable extends Drawable { int x = (size - w) / 2; int y = (size - h) / 2; canvas.save(); - canvas.translate(x, y); Theme.dialogs_archiveAvatarDrawable.setBounds(x, y, x + w, y + h); Theme.dialogs_archiveAvatarDrawable.draw(canvas); canvas.restore(); @@ -284,6 +287,11 @@ public class AvatarDrawable extends Drawable { int y = (size - Theme.avatar_broadcastDrawable.getIntrinsicHeight()) / 2; Theme.avatar_broadcastDrawable.setBounds(x, y, x + Theme.avatar_broadcastDrawable.getIntrinsicWidth(), y + Theme.avatar_broadcastDrawable.getIntrinsicHeight()); Theme.avatar_broadcastDrawable.draw(canvas); + } else if (drawDeleted && Theme.avatar_ghostDrawable != null) { + int x = (size - Theme.avatar_ghostDrawable.getIntrinsicWidth()) / 2; + int y = (size - Theme.avatar_ghostDrawable.getIntrinsicHeight()) / 2; + Theme.avatar_ghostDrawable.setBounds(x, y, x + Theme.avatar_ghostDrawable.getIntrinsicWidth(), y + Theme.avatar_ghostDrawable.getIntrinsicHeight()); + Theme.avatar_ghostDrawable.draw(canvas); } else { if (textLayout != null) { canvas.translate((size - textWidth) / 2 - textLeft, (size - textHeight) / 2); 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 b1f80b0e4..cddee82d7 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java @@ -121,6 +121,10 @@ public class BackupImageView extends View { imageReceiver.setImageBitmap(drawable); } + public void setLayerNum(int value) { + imageReceiver.setLayerNum(value); + } + public void setRoundRadius(int value) { imageReceiver.setRoundRadius(value); invalidate(); 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 185c2c7eb..0e389f2fd 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java @@ -68,9 +68,10 @@ import android.widget.PopupWindow; import android.widget.TextView; import android.widget.Toast; +import org.telegram.messenger.AccountInstance; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.Emoji; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; @@ -127,6 +128,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe } private int currentAccount = UserConfig.selectedAccount; + private AccountInstance accountInstance = AccountInstance.getInstance(UserConfig.selectedAccount); private SeekBarWaveform seekBarWaveform; @@ -247,6 +249,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe private Drawable playDrawable; private Drawable pauseDrawable; private int searchingType; + private Runnable focusRunnable; private boolean destroyed; @@ -845,7 +848,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - if (getTag() != null && attachLayout != null && !emojiViewVisible && !DataQuery.getInstance(currentAccount).getUnreadStickerSets().isEmpty() && dotPaint != null) { + if (getTag() != null && attachLayout != null && !emojiViewVisible && !MediaDataController.getInstance(currentAccount).getUnreadStickerSets().isEmpty() && dotPaint != null) { int x = getWidth() / 2 + AndroidUtilities.dp(4 + 5); int y = getHeight() / 2 - AndroidUtilities.dp(13 - 5); canvas.drawCircle(x, y, AndroidUtilities.dp(5), dotPaint); @@ -882,28 +885,33 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe @Override public InputConnection onCreateInputConnection(EditorInfo editorInfo) { final InputConnection ic = super.onCreateInputConnection(editorInfo); - EditorInfoCompat.setContentMimeTypes(editorInfo, new String[]{"image/gif", "image/*", "image/jpg", "image/png"}); + try { + EditorInfoCompat.setContentMimeTypes(editorInfo, new String[]{"image/gif", "image/*", "image/jpg", "image/png"}); - final InputConnectionCompat.OnCommitContentListener callback = (inputContentInfo, flags, opts) -> { - if (BuildCompat.isAtLeastNMR1() && (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { - try { - inputContentInfo.requestPermission(); - } catch (Exception e) { - return false; + final InputConnectionCompat.OnCommitContentListener callback = (inputContentInfo, flags, opts) -> { + if (BuildCompat.isAtLeastNMR1() && (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { + try { + inputContentInfo.requestPermission(); + } catch (Exception e) { + return false; + } } - } - ClipDescription description = inputContentInfo.getDescription(); - if (description.hasMimeType("image/gif")) { - SendMessagesHelper.prepareSendingDocument(null, null, inputContentInfo.getContentUri(), null, "image/gif", dialog_id, replyingMessageObject, inputContentInfo, null); - } else { - SendMessagesHelper.prepareSendingPhoto(null, inputContentInfo.getContentUri(), dialog_id, replyingMessageObject, null, null, null, inputContentInfo, 0, null); - } - if (delegate != null) { - delegate.onMessageSend(null); - } - return true; - }; - return InputConnectionCompat.createWrapper(ic, editorInfo, callback); + ClipDescription description = inputContentInfo.getDescription(); + if (description.hasMimeType("image/gif")) { + SendMessagesHelper.prepareSendingDocument(accountInstance, null, null, inputContentInfo.getContentUri(), null, "image/gif", dialog_id, replyingMessageObject, inputContentInfo, null); + } else { + SendMessagesHelper.prepareSendingPhoto(accountInstance, null, inputContentInfo.getContentUri(), dialog_id, replyingMessageObject, null, null, null, inputContentInfo, 0, null); + } + if (delegate != null) { + delegate.onMessageSend(null); + } + return true; + }; + return InputConnectionCompat.createWrapper(ic, editorInfo, callback); + } catch (Throwable e) { + FileLog.e(e); + } + return ic; } @Override @@ -937,9 +945,11 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe delegate.onTextSpansChanged(messageEditText.getText()); } }); + TLRPC.EncryptedChat encryptedChat = parentFragment != null ? parentFragment.getCurrentEncryptedChat() : null; + messageEditText.setAllowTextEntitiesIntersection(encryptedChat != null && AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 101); updateFieldHint(); int flags = EditorInfo.IME_FLAG_NO_EXTRACT_UI; - if (parentFragment != null && parentFragment.getCurrentEncryptedChat() != null) { + if (encryptedChat != null) { flags |= 0x01000000; //EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING; } messageEditText.setImeOptions(flags); @@ -1719,7 +1729,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe public void setOpenGifsTabFirst() { createEmojiView(); - DataQuery.getInstance(currentAccount).loadRecents(DataQuery.TYPE_IMAGE, true, true, false); + MediaDataController.getInstance(currentAccount).loadRecents(MediaDataController.TYPE_IMAGE, true, true, false); emojiView.switchToGifRecent(); } @@ -1988,6 +1998,13 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe } } + public void onBeginHide() { + if (focusRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(focusRunnable); + focusRunnable = null; + } + } + public void onPause() { isPaused = true; closeKeyboard(); @@ -1995,6 +2012,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe public void onResume() { isPaused = false; + int visibility = getVisibility(); if (showKeyboardOnResume) { showKeyboardOnResume = false; if (searchingType == 0) { @@ -2009,6 +2027,12 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe } } + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + messageEditText.setEnabled(visibility == VISIBLE); + } + public void setDialogId(long id, int account) { dialog_id = id; if (currentAccount != account) { @@ -2222,7 +2246,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe delegate.onMessageEditEnd(true); showEditDoneProgress(true, true); CharSequence[] message = new CharSequence[]{messageEditText.getText()}; - ArrayList entities = DataQuery.getInstance(currentAccount).getEntities(message); + ArrayList entities = MediaDataController.getInstance(currentAccount).getEntities(message); editingMessageReqId = SendMessagesHelper.getInstance(currentAccount).editMessage(editingMessageObject, message[0].toString(), messageWebPageSearch, parentFragment, entities, () -> { editingMessageReqId = 0; setEditingMessageObject(null, false); @@ -2237,7 +2261,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe int count = (int) Math.ceil(text.length() / (float) maxLength); for (int a = 0; a < count; a++) { CharSequence[] message = new CharSequence[]{text.subSequence(a * maxLength, Math.min((a + 1) * maxLength, text.length()))}; - ArrayList entities = DataQuery.getInstance(currentAccount).getEntities(message); + ArrayList entities = MediaDataController.getInstance(currentAccount).getEntities(message); SendMessagesHelper.getInstance(currentAccount).sendMessage(message[0].toString(), dialog_id, replyingMessageObject, messageWebPage, messageWebPageSearch, entities, null, null); } return true; @@ -2387,6 +2411,12 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe sendButton.setVisibility(VISIBLE); cancelBotButton.setVisibility(GONE); } + if (expandStickersButton.getVisibility() == VISIBLE) { + expandStickersButton.setScaleX(0.1f); + expandStickersButton.setScaleY(0.1f); + expandStickersButton.setAlpha(0.0f); + expandStickersButton.setVisibility(GONE); + } audioVideoButtonContainer.setVisibility(GONE); if (attachLayout != null) { attachLayout.setVisibility(GONE); @@ -2797,7 +2827,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe } if (editingText != null) { ArrayList entities = editingMessageObject.messageOwner.entities; - DataQuery.sortEntities(entities); + MediaDataController.sortEntities(entities); SpannableStringBuilder stringBuilder = new SpannableStringBuilder(editingText); Object[] spansToRemove = stringBuilder.getSpans(0, stringBuilder.length(), Object.class); if (spansToRemove != null && spansToRemove.length > 0) { @@ -2806,37 +2836,44 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe } } if (entities != null) { - int addToOffset = 0; try { for (int a = 0; a < entities.size(); a++) { TLRPC.MessageEntity entity = entities.get(a); - if (entity.offset + entity.length + addToOffset > stringBuilder.length()) { + if (entity.offset + entity.length > stringBuilder.length()) { continue; } if (entity instanceof TLRPC.TL_inputMessageEntityMentionName) { - if (entity.offset + entity.length + addToOffset < stringBuilder.length() && stringBuilder.charAt(entity.offset + entity.length + addToOffset) == ' ') { + if (entity.offset + entity.length < stringBuilder.length() && stringBuilder.charAt(entity.offset + entity.length) == ' ') { entity.length++; } - stringBuilder.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_inputMessageEntityMentionName) entity).user_id.user_id, 1), entity.offset + addToOffset, entity.offset + entity.length + addToOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_inputMessageEntityMentionName) entity).user_id.user_id, 1), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (entity instanceof TLRPC.TL_messageEntityMentionName) { - if (entity.offset + entity.length + addToOffset < stringBuilder.length() && stringBuilder.charAt(entity.offset + entity.length + addToOffset) == ' ') { + if (entity.offset + entity.length < stringBuilder.length() && stringBuilder.charAt(entity.offset + entity.length) == ' ') { entity.length++; } - stringBuilder.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_messageEntityMentionName) entity).user_id, 1), entity.offset + addToOffset, entity.offset + entity.length + addToOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else if (entity instanceof TLRPC.TL_messageEntityCode) { - stringBuilder.insert(entity.offset + entity.length + addToOffset, "`"); - stringBuilder.insert(entity.offset + addToOffset, "`"); - addToOffset += 2; - } else if (entity instanceof TLRPC.TL_messageEntityPre) { - stringBuilder.insert(entity.offset + entity.length + addToOffset, "```"); - stringBuilder.insert(entity.offset + addToOffset, "```"); - addToOffset += 6; + stringBuilder.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_messageEntityMentionName) entity).user_id, 1), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (entity instanceof TLRPC.TL_messageEntityCode || entity instanceof TLRPC.TL_messageEntityPre) { + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_MONO; + MediaDataController.addStyleToText(new TextStyleSpan(run), entity.offset, entity.offset + entity.length, stringBuilder, true); } else if (entity instanceof TLRPC.TL_messageEntityBold) { - stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), entity.offset + addToOffset, entity.offset + entity.length + addToOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_BOLD; + MediaDataController.addStyleToText(new TextStyleSpan(run), entity.offset, entity.offset + entity.length, stringBuilder, true); } else if (entity instanceof TLRPC.TL_messageEntityItalic) { - stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/ritalic.ttf")), entity.offset + addToOffset, entity.offset + entity.length + addToOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_ITALIC; + MediaDataController.addStyleToText(new TextStyleSpan(run), entity.offset, entity.offset + entity.length, stringBuilder, true); + } else if (entity instanceof TLRPC.TL_messageEntityStrike) { + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_STRIKE; + MediaDataController.addStyleToText(new TextStyleSpan(run), entity.offset, entity.offset + entity.length, stringBuilder, true); + } else if (entity instanceof TLRPC.TL_messageEntityUnderline) { + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_UNDERLINE; + MediaDataController.addStyleToText(new TextStyleSpan(run), entity.offset, entity.offset + entity.length, stringBuilder, true); } else if (entity instanceof TLRPC.TL_messageEntityTextUrl) { - stringBuilder.setSpan(new URLSpanReplacement(entity.url), entity.offset + addToOffset, entity.offset + entity.length + addToOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.setSpan(new URLSpanReplacement(entity.url), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } } catch (Exception e) { @@ -2906,7 +2943,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe messageEditText.setText(text); messageEditText.setSelection(messageEditText.getText().length()); ignoreTextChange = false; - if (delegate != null) { + if (ignoreChange && delegate != null) { delegate.onTextChanged(messageEditText.getText(), true); } } @@ -2969,7 +3006,8 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe } if (focus) { if (searchingType == 0 && !messageEditText.isFocused()) { - messageEditText.postDelayed(() -> { + AndroidUtilities.runOnUIThread(focusRunnable = () -> { + focusRunnable = null; boolean allowFocus; if (AndroidUtilities.isTablet()) { if (parentActivity instanceof LaunchActivity) { @@ -2986,7 +3024,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe } else { allowFocus = true; } - if (allowFocus && messageEditText != null) { + if (!isPaused && allowFocus && messageEditText != null) { try { messageEditText.requestFocus(); } catch (Exception e) { @@ -3131,7 +3169,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe } else if (button instanceof TLRPC.TL_keyboardButtonUrl) { parentFragment.showOpenUrlAlert(button.url, true); } else if (button instanceof TLRPC.TL_keyboardButtonRequestPhone) { - parentFragment.shareMyContact(messageObject); + parentFragment.shareMyContact(2, messageObject); } else if (button instanceof TLRPC.TL_keyboardButtonRequestGeoLocation) { AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); builder.setTitle(LocaleController.getString("ShareYouLocationTitle", R.string.ShareYouLocationTitle)); @@ -3179,7 +3217,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe return; } long did = dids.get(0); - DataQuery.getInstance(currentAccount).saveDraft(did, "@" + user.username + " " + button.query, null, null, true); + MediaDataController.getInstance(currentAccount).saveDraft(did, "@" + user.username + " " + button.query, null, null, true); if (did != dialog_id) { int lower_part = (int) did; if (lower_part != 0) { @@ -3275,7 +3313,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe @Override public void onStickersSettingsClick() { if (parentFragment != null) { - parentFragment.presentFragment(new StickersActivity(DataQuery.TYPE_IMAGE)); + parentFragment.presentFragment(new StickersActivity(MediaDataController.TYPE_IMAGE)); } } @@ -3290,7 +3328,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe if (gif instanceof TLRPC.Document) { TLRPC.Document document = (TLRPC.Document) gif; SendMessagesHelper.getInstance(currentAccount).sendSticker(document, dialog_id, replyingMessageObject, parent); - DataQuery.getInstance(currentAccount).addRecentGif(document, (int) (System.currentTimeMillis() / 1000)); + MediaDataController.getInstance(currentAccount).addRecentGif(document, (int) (System.currentTimeMillis() / 1000)); if ((int) dialog_id == 0) { MessagesController.getInstance(currentAccount).saveGif(parent, document); } @@ -3298,7 +3336,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe TLRPC.BotInlineResult result = (TLRPC.BotInlineResult) gif; if (result.document != null) { - DataQuery.getInstance(currentAccount).addRecentGif(result.document, (int) (System.currentTimeMillis() / 1000)); + MediaDataController.getInstance(currentAccount).addRecentGif(result.document, (int) (System.currentTimeMillis() / 1000)); if ((int) dialog_id == 0) { MessagesController.getInstance(currentAccount).saveGif(parent, result.document); } @@ -3310,7 +3348,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe params.put("id", result.id); params.put("query_id", "" + result.query_id); - SendMessagesHelper.prepareSendingBotContextResult(result, params, dialog_id, replyingMessageObject); + SendMessagesHelper.prepareSendingBotContextResult(accountInstance, result, params, dialog_id, replyingMessageObject); if (searchingType != 0) { searchingType = 0; @@ -3357,12 +3395,12 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe @Override public void onStickerSetAdd(TLRPC.StickerSetCovered stickerSet) { - DataQuery.getInstance(currentAccount).removeStickersSet(parentActivity, stickerSet.set, 2, parentFragment, false); + MediaDataController.getInstance(currentAccount).removeStickersSet(parentActivity, stickerSet.set, 2, parentFragment, false); } @Override public void onStickerSetRemove(TLRPC.StickerSetCovered stickerSet) { - DataQuery.getInstance(currentAccount).removeStickersSet(parentActivity, stickerSet.set, 0, parentFragment, false); + MediaDataController.getInstance(currentAccount).removeStickersSet(parentActivity, stickerSet.set, 0, parentFragment, false); } @Override @@ -3412,6 +3450,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe stickersDragging = true; wasExpanded = stickersExpanded; stickersExpanded = true; + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.stopAllHeavyOperations, 1); stickersExpandedHeight = sizeNotifierLayout.getHeight() - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? AndroidUtilities.statusBarHeight : 0) - ActionBar.getCurrentActionBarHeight() - getHeight() + Theme.chat_composeShadowDrawable.getIntrinsicHeight(); if (searchingType == 2) { stickersExpandedHeight = Math.min(stickersExpandedHeight, AndroidUtilities.dp(120) + (AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y ? keyboardHeightLand : keyboardHeight)); @@ -3485,7 +3524,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe if (clearsInputField) { setFieldText(""); } - DataQuery.getInstance(currentAccount).addRecentSticker(DataQuery.TYPE_IMAGE, parent, sticker, (int) (System.currentTimeMillis() / 1000), false); + MediaDataController.getInstance(currentAccount).addRecentSticker(MediaDataController.TYPE_IMAGE, parent, sticker, (int) (System.currentTimeMillis() / 1000), false); } public void addStickerToRecent(TLRPC.Document sticker) { @@ -3495,6 +3534,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe public void hideEmojiView() { if (!emojiViewVisible && emojiView != null && emojiView.getVisibility() != GONE) { + sizeNotifierLayout.removeView(emojiView); emojiView.setVisibility(GONE); } } @@ -3510,6 +3550,9 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe View currentView = null; if (contentType == 0) { + if (emojiView.getParent() == null) { + sizeNotifierLayout.addView(emojiView); + } emojiView.setVisibility(VISIBLE); emojiViewVisible = true; if (botKeyboardView != null && botKeyboardView.getVisibility() != GONE) { @@ -3518,6 +3561,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe currentView = emojiView; } else if (contentType == 1) { if (emojiView != null && emojiView.getVisibility() != GONE) { + sizeNotifierLayout.removeView(emojiView); emojiView.setVisibility(GONE); emojiViewVisible = false; } @@ -3559,7 +3603,8 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe currentPopupContentType = -1; if (emojiView != null) { emojiViewVisible = false; - if (AndroidUtilities.usingHardwareInput || AndroidUtilities.isInMultiwindow) { + if (show != 2 || AndroidUtilities.usingHardwareInput || AndroidUtilities.isInMultiwindow) { + sizeNotifierLayout.removeView(emojiView); emojiView.setVisibility(GONE); } } @@ -3725,7 +3770,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe } public void addRecentGif(TLRPC.Document searchImage) { - DataQuery.getInstance(currentAccount).addRecentGif(searchImage, (int) (System.currentTimeMillis() / 1000)); + MediaDataController.getInstance(currentAccount).addRecentGif(searchImage, (int) (System.currentTimeMillis() / 1000)); if (emojiView != null) { emojiView.addRecentGif(searchImage); } @@ -4084,6 +4129,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe stickersExpansionAnim = null; } if (stickersExpanded) { + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.stopAllHeavyOperations, 1); originalViewHeight = sizeNotifierLayout.getHeight(); stickersExpandedHeight = originalViewHeight - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? AndroidUtilities.statusBarHeight : 0) - ActionBar.getCurrentActionBarHeight() - getHeight() + Theme.chat_composeShadowDrawable.getIntrinsicHeight(); if (searchingType == 2) { @@ -4114,10 +4160,12 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe public void onAnimationEnd(Animator animation) { stickersExpansionAnim = null; emojiView.setLayerType(LAYER_TYPE_NONE, null); + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.startAllHeavyOperations, 512); } }); stickersExpansionAnim = anims; emojiView.setLayerType(LAYER_TYPE_HARDWARE, null); + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.stopAllHeavyOperations, 512); anims.start(); } else { stickersExpansionProgress = 1; @@ -4126,6 +4174,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe stickersArrow.setAnimationProgress(1); } } else { + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.startAllHeavyOperations, 1); if (animated) { closeAnimationInProgress = true; AnimatorSet anims = new AnimatorSet(); @@ -4154,10 +4203,12 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe sizeNotifierLayout.setForeground(null); sizeNotifierLayout.setWillNotDraw(false); } + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.startAllHeavyOperations, 512); } }); stickersExpansionAnim = anims; emojiView.setLayerType(LAYER_TYPE_HARDWARE, null); + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.stopAllHeavyOperations, 512); anims.start(); } else { stickersExpansionProgress = 0; 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 b0ed28de9..201490108 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java @@ -57,7 +57,7 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.BuildVars; import org.telegram.messenger.ChatObject; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.FileLoader; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.ImageReceiver; @@ -563,7 +563,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setMessage(LocaleController.formatString("ChatHintsDelete", R.string.ChatHintsDelete, ContactsController.formatName(currentUser.first_name, currentUser.last_name))); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> DataQuery.getInstance(currentAccount).removeInline(currentUser.id)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> MediaDataController.getInstance(currentAccount).removeInline(currentUser.id)); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); builder.show(); } @@ -593,7 +593,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N getParent().requestDisallowInterceptTouchEvent(true); pressed = false; playSoundEffect(SoundEffectConstants.CLICK); - delegate.didSelectBot(MessagesController.getInstance(currentAccount).getUser(DataQuery.getInstance(currentAccount).inlineBots.get((Integer) getTag()).peer.user_id)); + delegate.didSelectBot(MessagesController.getInstance(currentAccount).getUser(MediaDataController.getInstance(currentAccount).inlineBots.get((Integer) getTag()).peer.user_id)); setUseRevealAnimation(false); dismiss(); setUseRevealAnimation(true); @@ -700,7 +700,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } else { h = 203; } - int contentSize = backgroundPaddingTop + AndroidUtilities.dp(h) + (DataQuery.getInstance(currentAccount).inlineBots.isEmpty() ? 0 : ((int) Math.ceil(DataQuery.getInstance(currentAccount).inlineBots.size() / 4.0f) * AndroidUtilities.dp(100) + AndroidUtilities.dp(12))); + int contentSize = backgroundPaddingTop + AndroidUtilities.dp(h) + (MediaDataController.getInstance(currentAccount).inlineBots.isEmpty() ? 0 : ((int) Math.ceil(MediaDataController.getInstance(currentAccount).inlineBots.size() / 4.0f) * AndroidUtilities.dp(100) + AndroidUtilities.dp(12))); int padding = contentSize == AndroidUtilities.dp(h) ? 0 : Math.max(0, (height - AndroidUtilities.dp(h))); if (padding != 0 && contentSize < height) { padding -= (height - contentSize); @@ -2267,7 +2267,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } private void showHint() { - if (editingMessageObject != null || !(baseFragment instanceof ChatActivity) || DataQuery.getInstance(currentAccount).inlineBots.isEmpty()) { + if (editingMessageObject != null || !(baseFragment instanceof ChatActivity) || MediaDataController.getInstance(currentAccount).inlineBots.isEmpty()) { return; } SharedPreferences preferences = MessagesController.getGlobalMainSettings(); @@ -2606,12 +2606,12 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N FrameLayout frameLayout = (FrameLayout) holder.itemView; for (int a = 0; a < 4; a++) { AttachBotButton child = (AttachBotButton) frameLayout.getChildAt(a); - if (position + a >= DataQuery.getInstance(currentAccount).inlineBots.size()) { + if (position + a >= MediaDataController.getInstance(currentAccount).inlineBots.size()) { child.setVisibility(View.INVISIBLE); } else { child.setVisibility(View.VISIBLE); child.setTag(position + a); - child.setUser(MessagesController.getInstance(currentAccount).getUser(DataQuery.getInstance(currentAccount).inlineBots.get(position + a).peer.user_id)); + child.setUser(MessagesController.getInstance(currentAccount).getUser(MediaDataController.getInstance(currentAccount).inlineBots.get(position + a).peer.user_id)); } } } @@ -2625,7 +2625,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N @Override public int getItemCount() { if (editingMessageObject == null && baseFragment instanceof ChatActivity) { - return 1 + (!DataQuery.getInstance(currentAccount).inlineBots.isEmpty() ? 1 + (int) Math.ceil(DataQuery.getInstance(currentAccount).inlineBots.size() / 4.0f) : 0); + return 1 + (!MediaDataController.getInstance(currentAccount).inlineBots.isEmpty() ? 1 + (int) Math.ceil(MediaDataController.getInstance(currentAccount).inlineBots.size() / 4.0f) : 0); } else { return 1; } @@ -2947,6 +2947,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } } } + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.startAllHeavyOperations, 512); } @Override @@ -3025,6 +3026,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } } currentSheetAnimation = animatorSet; + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.stopAllHeavyOperations, 512); animatorSet.start(); } 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 2696e5c1c..6fbcf3942 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAvatarContainer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAvatarContainer.java @@ -54,6 +54,7 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent private int onlineCount = -1; private int currentConnectionState; private CharSequence lastSubtitle; + private String lastSubtitleColorKey; public ChatAvatarContainer(Context context, ChatActivity chatActivity, boolean needTime) { super(context); @@ -101,6 +102,7 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent parentFragment.presentFragment(fragment); } else { args.putInt("user_id", user.id); + args.putBoolean("reportSpam", parentFragment.hasReportSpam()); if (timeItem != null) { args.putLong("dialog_id", parentFragment.getDialogId()); } @@ -299,7 +301,17 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent } } else { if (chat.megagroup) { - newSubtitle = LocaleController.getString("Loading", R.string.Loading).toLowerCase(); + if (info == null) { + newSubtitle = LocaleController.getString("Loading", R.string.Loading).toLowerCase(); + } else { + if (chat.has_geo) { + newSubtitle = LocaleController.getString("MegaLocation", R.string.MegaLocation).toLowerCase(); + } else if (!TextUtils.isEmpty(chat.username)) { + 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) { newSubtitle = LocaleController.getString("ChannelPublic", R.string.ChannelPublic).toLowerCase(); @@ -353,11 +365,11 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent useOnlineColor = true; setTypingAnimation(true); } + lastSubtitleColorKey = useOnlineColor ? Theme.key_chat_status : Theme.key_actionBarDefaultSubtitle; if (lastSubtitle == null) { subtitleTextView.setText(newSubtitle); - String key = useOnlineColor ? Theme.key_chat_status : Theme.key_actionBarDefaultSubtitle; - subtitleTextView.setTextColor(Theme.getColor(key)); - subtitleTextView.setTag(key); + subtitleTextView.setTextColor(Theme.getColor(lastSubtitleColorKey)); + subtitleTextView.setTag(lastSubtitleColorKey); } else { lastSubtitle = newSubtitle; } @@ -478,10 +490,18 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent if (lastSubtitle != null) { subtitleTextView.setText(lastSubtitle); lastSubtitle = null; + if (lastSubtitleColorKey != null) { + subtitleTextView.setTextColor(Theme.getColor(lastSubtitleColorKey)); + subtitleTextView.setTag(lastSubtitleColorKey); + } } } else { - lastSubtitle = subtitleTextView.getText(); + if (lastSubtitle == null) { + lastSubtitle = subtitleTextView.getText(); + } subtitleTextView.setText(title); + subtitleTextView.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSubtitle)); + subtitleTextView.setTag(Theme.key_actionBarDefaultSubtitle); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/DialogsItemAnimator.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/DialogsItemAnimator.java index b27ccc3c4..f49c9c109 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/DialogsItemAnimator.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/DialogsItemAnimator.java @@ -310,6 +310,21 @@ public class DialogsItemAnimator extends SimpleItemAnimator { return true; } + public void onListScroll(int dy) { + if (!mPendingRemovals.isEmpty()) { + for (int a = 0, N = mPendingRemovals.size(); a < N; a++) { + ViewHolder holder = mPendingRemovals.get(a); + holder.itemView.setTranslationY(holder.itemView.getTranslationY() + dy); + } + } + if (!mRemoveAnimations.isEmpty()) { + for (int a = 0, N = mRemoveAnimations.size(); a < N; a++) { + ViewHolder holder = mRemoveAnimations.get(a); + holder.itemView.setTranslationY(holder.itemView.getTranslationY() + dy); + } + } + } + void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { final View view = holder.itemView; final int deltaX = toX - fromX; @@ -596,6 +611,9 @@ public class DialogsItemAnimator extends SimpleItemAnimator { count = mPendingRemovals.size(); for (int i = count - 1; i >= 0; i--) { ViewHolder item = mPendingRemovals.get(i); + View view = item.itemView; + view.setTranslationY(0); + view.setTranslationX(0); dispatchRemoveFinished(item); mPendingRemovals.remove(i); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextBoldCursor.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextBoldCursor.java index 449db5a6b..cee202f51 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextBoldCursor.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextBoldCursor.java @@ -471,34 +471,33 @@ public class EditTextBoldCursor extends EditText { mCursorDrawable = ((Drawable[]) mCursorDrawableField.get(editor))[0]; } } - if (mCursorDrawable == null) { - return; - } - long mShowCursor = mShowCursorField.getLong(editor); - boolean showCursor = (SystemClock.uptimeMillis() - mShowCursor) % (2 * 500) < 500 && isFocused(); - if (showCursor) { - canvas.save(); - int voffsetCursor = 0; - if ((getGravity() & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { - voffsetCursor = (int) getVerticalOffsetMethod.invoke(this, true); + if (mCursorDrawable != null) { + long mShowCursor = mShowCursorField.getLong(editor); + boolean showCursor = (SystemClock.uptimeMillis() - mShowCursor) % (2 * 500) < 500 && isFocused(); + if (showCursor) { + canvas.save(); + int voffsetCursor = 0; + if ((getGravity() & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { + voffsetCursor = (int) getVerticalOffsetMethod.invoke(this, true); + } + canvas.translate(getPaddingLeft(), getExtendedPaddingTop() + voffsetCursor); + Layout layout = getLayout(); + int line = layout.getLineForOffset(getSelectionStart()); + int lineCount = layout.getLineCount(); + Rect bounds = mCursorDrawable.getBounds(); + rect.left = bounds.left; + rect.right = bounds.left + AndroidUtilities.dp(cursorWidth); + rect.bottom = bounds.bottom; + rect.top = bounds.top; + if (lineSpacingExtra != 0 && line < lineCount - 1) { + rect.bottom -= lineSpacingExtra; + } + rect.top = rect.centerY() - cursorSize / 2; + rect.bottom = rect.top + cursorSize; + gradientDrawable.setBounds(rect); + gradientDrawable.draw(canvas); + canvas.restore(); } - canvas.translate(getPaddingLeft(), getExtendedPaddingTop() + voffsetCursor); - Layout layout = getLayout(); - int line = layout.getLineForOffset(getSelectionStart()); - int lineCount = layout.getLineCount(); - Rect bounds = mCursorDrawable.getBounds(); - rect.left = bounds.left; - rect.right = bounds.left + AndroidUtilities.dp(cursorWidth); - rect.bottom = bounds.bottom; - rect.top = bounds.top; - if (lineSpacingExtra != 0 && line < lineCount - 1) { - rect.bottom -= lineSpacingExtra; - } - rect.top = rect.centerY() - cursorSize / 2; - rect.bottom = rect.top + cursorSize; - gradientDrawable.setBounds(rect); - gradientDrawable.draw(canvas); - canvas.restore(); } } } catch (Throwable ignore) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextCaption.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextCaption.java index cb63b1a54..e4374a275 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextCaption.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextCaption.java @@ -10,10 +10,8 @@ package org.telegram.ui.Components; import android.annotation.SuppressLint; import android.content.Context; -import android.content.DialogInterface; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.Typeface; import android.os.Build; import android.text.Editable; import android.text.Layout; @@ -35,6 +33,7 @@ import android.widget.FrameLayout; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.R; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.Theme; @@ -52,6 +51,7 @@ public class EditTextCaption extends EditTextBoldCursor { private EditTextCaptionDelegate delegate; private int selectionStart = -1; private int selectionEnd = -1; + private boolean allowTextEntitiesIntersection; public interface EditTextCaptionDelegate { void onSpansChanged(); @@ -62,7 +62,7 @@ public class EditTextCaption extends EditTextBoldCursor { } public void setCaption(String value) { - if ((caption == null || caption.length() == 0) && (value == null || value.length() == 0) || caption != null && value != null && caption.equals(value)) { + if ((caption == null || caption.length() == 0) && (value == null || value.length() == 0) || caption != null && caption.equals(value)) { return; } caption = value; @@ -76,16 +76,38 @@ public class EditTextCaption extends EditTextBoldCursor { delegate = editTextCaptionDelegate; } + public void setAllowTextEntitiesIntersection(boolean value) { + allowTextEntitiesIntersection = value; + } + public void makeSelectedBold() { - applyTextStyleToSelection(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf"))); + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_BOLD; + applyTextStyleToSelection(new TextStyleSpan(run)); } public void makeSelectedItalic() { - applyTextStyleToSelection(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/ritalic.ttf"))); + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_ITALIC; + applyTextStyleToSelection(new TextStyleSpan(run)); } public void makeSelectedMono() { - applyTextStyleToSelection(new TypefaceSpan(Typeface.MONOSPACE)); + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_MONO; + applyTextStyleToSelection(new TextStyleSpan(run)); + } + + public void makeSelectedStrike() { + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_STRIKE; + applyTextStyleToSelection(new TextStyleSpan(run)); + } + + public void makeSelectedUnderline() { + TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); + run.flags |= TextStyleSpan.FLAG_STYLE_UNDERLINE; + applyTextStyleToSelection(new TextStyleSpan(run)); } public void makeSelectedUrl() { @@ -126,7 +148,7 @@ public class EditTextCaption extends EditTextBoldCursor { builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> { Editable editable = getText(); - CharacterStyle spans[] = editable.getSpans(start, end, CharacterStyle.class); + CharacterStyle[] spans = editable.getSpans(start, end, CharacterStyle.class); if (spans != null && spans.length > 0) { for (int a = 0; a < spans.length; a++) { CharacterStyle oldSpan = spans[a]; @@ -143,7 +165,7 @@ public class EditTextCaption extends EditTextBoldCursor { } try { editable.setSpan(new URLSpanReplacement(editText.getText().toString()), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } catch (Exception ingore) { + } catch (Exception ignore) { } if (delegate != null) { @@ -178,7 +200,7 @@ public class EditTextCaption extends EditTextBoldCursor { selectionEnd = end; } - private void applyTextStyleToSelection(TypefaceSpan span) { + private void applyTextStyleToSelection(TextStyleSpan span) { int start; int end; if (selectionStart >= 0 && selectionEnd >= 0) { @@ -189,26 +211,7 @@ public class EditTextCaption extends EditTextBoldCursor { start = getSelectionStart(); end = getSelectionEnd(); } - Editable editable = getText(); - - CharacterStyle spans[] = editable.getSpans(start, end, CharacterStyle.class); - if (spans != null && spans.length > 0) { - for (int a = 0; a < spans.length; a++) { - CharacterStyle oldSpan = spans[a]; - int spanStart = editable.getSpanStart(oldSpan); - int spanEnd = editable.getSpanEnd(oldSpan); - editable.removeSpan(oldSpan); - if (spanStart < start) { - editable.setSpan(oldSpan, spanStart, start, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - if (spanEnd > end) { - editable.setSpan(oldSpan, end, spanEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - } - if (span != null) { - editable.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } + MediaDataController.addStyleToText(span, start, end, getText(), allowTextEntitiesIntersection); if (delegate != null) { delegate.onSpansChanged(); } @@ -257,6 +260,14 @@ public class EditTextCaption extends EditTextBoldCursor { makeSelectedUrl(); mode.finish(); return true; + } else if (item.getItemId() == R.id.menu_strike) { + makeSelectedStrike(); + mode.finish(); + return true; + } else if (item.getItemId() == R.id.menu_underline) { + makeSelectedUnderline(); + mode.finish(); + return true; } try { return callback.onActionItemClicked(mode, item); 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 1e884ff76..174a6465f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java @@ -26,7 +26,7 @@ import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; import android.os.Build; -import androidx.annotation.NonNull; + import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager.widget.PagerAdapter; @@ -59,7 +59,7 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.Emoji; import org.telegram.messenger.EmojiData; import org.telegram.messenger.FileLoader; @@ -314,7 +314,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific @Override public void gifAddedOrDeleted() { - recentGifs = DataQuery.getInstance(currentAccount).getRecentGifs(); + recentGifs = MediaDataController.getInstance(currentAccount).getRecentGifs(); if (gifAdapter != null) { gifAdapter.notifyDataSetChanged(); } @@ -1077,7 +1077,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { lastSearchKeyboardLanguage = AndroidUtilities.getCurrentKeyboardLanguage(); - DataQuery.getInstance(currentAccount).fetchNewEmojiKeywords(lastSearchKeyboardLanguage); + MediaDataController.getInstance(currentAccount).fetchNewEmojiKeywords(lastSearchKeyboardLanguage); } } }); @@ -1270,7 +1270,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific return; } delegate.onGifSelected(gifSearchAdapter.results.get(position), gifSearchAdapter.bot); - recentGifs = DataQuery.getInstance(currentAccount).getRecentGifs(); + recentGifs = MediaDataController.getInstance(currentAccount).getRecentGifs(); if (gifAdapter != null) { gifAdapter.notifyDataSetChanged(); } @@ -1286,8 +1286,8 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific stickersContainer = new FrameLayout(context); - DataQuery.getInstance(currentAccount).checkStickers(DataQuery.TYPE_IMAGE); - DataQuery.getInstance(currentAccount).checkFeaturedStickers(); + MediaDataController.getInstance(currentAccount).checkStickers(MediaDataController.TYPE_IMAGE); + MediaDataController.getInstance(currentAccount).checkFeaturedStickers(); stickersGridView = new RecyclerListView(context) { boolean ignoreLayout; @@ -1746,6 +1746,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific field.searchEditText.setText(currentFieldText); field.searchEditText.setSelection(currentFieldText.length()); } + startStopVisibleGifs((position == 0 && positionOffset > 0) || position == 1); } @Override @@ -2122,7 +2123,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific int currentItem = pager.getCurrentItem(); if (currentItem == 2 && scrollToSet != -1) { - TLRPC.TL_messages_stickerSet set = DataQuery.getInstance(currentAccount).getStickerSetById(scrollToSet); + TLRPC.TL_messages_stickerSet set = MediaDataController.getInstance(currentAccount).getStickerSetById(scrollToSet); if (set != null) { int pos = stickersGridAdapter.getPositionForPack(set); if (pos >= 0) { @@ -2601,7 +2602,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific int lastPosition = stickersTab.getCurrentPosition(); stickersTab.removeTabs(); - ArrayList unread = DataQuery.getInstance(currentAccount).getUnreadStickerSets(); + ArrayList unread = MediaDataController.getInstance(currentAccount).getUnreadStickerSets(); boolean hasStickers = false; if (trendingGridAdapter != null && trendingGridAdapter.getItemCount() != 0 && !unread.isEmpty()) { @@ -2629,7 +2630,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific groupStickerSet = null; groupStickerPackPosition = -1; groupStickerPackNum = -10; - ArrayList packs = DataQuery.getInstance(currentAccount).getStickerSets(DataQuery.TYPE_IMAGE); + ArrayList packs = MediaDataController.getInstance(currentAccount).getStickerSets(MediaDataController.TYPE_IMAGE); for (int a = 0; a < packs.size(); a++) { TLRPC.TL_messages_stickerSet pack = packs.get(a); if (pack.set.archived || pack.documents == null || pack.documents.isEmpty()) { @@ -2647,7 +2648,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific groupStickersHidden = hiddenStickerSetId == info.stickerset.id; } if (info.stickerset != null) { - TLRPC.TL_messages_stickerSet pack = DataQuery.getInstance(currentAccount).getGroupStickerSetById(info.stickerset); + TLRPC.TL_messages_stickerSet pack = MediaDataController.getInstance(currentAccount).getGroupStickerSetById(info.stickerset); if (pack != null && pack.documents != null && !pack.documents.isEmpty() && pack.set != null) { TLRPC.TL_messages_stickerSet set = new TLRPC.TL_messages_stickerSet(); set.documents = pack.documents; @@ -2705,7 +2706,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific stickersTab.onPageScrolled(lastPosition, lastPosition); } checkPanels(); - if ((!hasStickers || trendingTabNum == 0 && DataQuery.getInstance(currentAccount).areAllTrendingStickerSetsUnread()) && trendingTabNum >= 0) { + if ((!hasStickers || trendingTabNum == 0 && MediaDataController.getInstance(currentAccount).areAllTrendingStickerSetsUnread()) && trendingTabNum >= 0) { if (scrolledToTrending == 0) { showTrendingTab(true); scrolledToTrending = hasStickers ? 2 : 1; @@ -2748,9 +2749,9 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific if (document == null) { return; } - DataQuery.getInstance(currentAccount).addRecentSticker(DataQuery.TYPE_IMAGE, null, document, (int) (System.currentTimeMillis() / 1000), false); + MediaDataController.getInstance(currentAccount).addRecentSticker(MediaDataController.TYPE_IMAGE, null, document, (int) (System.currentTimeMillis() / 1000), false); boolean wasEmpty = recentStickers.isEmpty(); - recentStickers = DataQuery.getInstance(currentAccount).getRecentStickers(DataQuery.TYPE_IMAGE); + recentStickers = MediaDataController.getInstance(currentAccount).getRecentStickers(MediaDataController.TYPE_IMAGE); if (stickersGridAdapter != null) { stickersGridAdapter.notifyDataSetChanged(); } @@ -2764,7 +2765,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific return; } boolean wasEmpty = recentGifs.isEmpty(); - recentGifs = DataQuery.getInstance(currentAccount).getRecentGifs(); + recentGifs = MediaDataController.getInstance(currentAccount).getRecentGifs(); if (gifAdapter != null) { gifAdapter.notifyDataSetChanged(); } @@ -2991,7 +2992,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific pager.setCurrentItem(2, false); } if (stickersTab != null) { - if (trendingTabNum == 0 && DataQuery.getInstance(currentAccount).areAllTrendingStickerSetsUnread()) { + if (trendingTabNum == 0 && MediaDataController.getInstance(currentAccount).areAllTrendingStickerSetsUnread()) { showTrendingTab(true); } else if (recentTabBum >= 0) { stickersTab.selectTab(recentTabBum); @@ -3048,9 +3049,9 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } checkDocuments(true); checkDocuments(false); - DataQuery.getInstance(currentAccount).loadRecents(DataQuery.TYPE_IMAGE, true, true, false); - DataQuery.getInstance(currentAccount).loadRecents(DataQuery.TYPE_IMAGE, false, true, false); - DataQuery.getInstance(currentAccount).loadRecents(DataQuery.TYPE_FAVE, false, true, false); + MediaDataController.getInstance(currentAccount).loadRecents(MediaDataController.TYPE_IMAGE, true, true, false); + MediaDataController.getInstance(currentAccount).loadRecents(MediaDataController.TYPE_IMAGE, false, true, false); + MediaDataController.getInstance(currentAccount).loadRecents(MediaDataController.TYPE_FAVE, false, true, false); } } @@ -3079,15 +3080,15 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific private void checkDocuments(boolean isGif) { if (isGif) { - recentGifs = DataQuery.getInstance(currentAccount).getRecentGifs(); + recentGifs = MediaDataController.getInstance(currentAccount).getRecentGifs(); if (gifAdapter != null) { gifAdapter.notifyDataSetChanged(); } } else { int previousCount = recentStickers.size(); int previousCount2 = favouriteStickers.size(); - recentStickers = DataQuery.getInstance(currentAccount).getRecentStickers(DataQuery.TYPE_IMAGE); - favouriteStickers = DataQuery.getInstance(currentAccount).getRecentStickers(DataQuery.TYPE_FAVE); + recentStickers = MediaDataController.getInstance(currentAccount).getRecentStickers(MediaDataController.TYPE_IMAGE); + favouriteStickers = MediaDataController.getInstance(currentAccount).getRecentStickers(MediaDataController.TYPE_FAVE); for (int a = 0; a < favouriteStickers.size(); a++) { TLRPC.Document favSticker = favouriteStickers.get(a); for (int b = 0; b < recentStickers.size(); b++) { @@ -3211,12 +3212,12 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific continue; } FeaturedStickerSetInfoCell cell = (FeaturedStickerSetInfoCell) child; - ArrayList unreadStickers = DataQuery.getInstance(currentAccount).getUnreadStickerSets(); + ArrayList unreadStickers = MediaDataController.getInstance(currentAccount).getUnreadStickerSets(); TLRPC.StickerSetCovered stickerSetCovered = cell.getStickerSet(); boolean unread = unreadStickers != null && unreadStickers.contains(stickerSetCovered.set.id); cell.setStickerSet(stickerSetCovered, unread); if (unread) { - DataQuery.getInstance(currentAccount).markFaturedStickersByIdAsRead(stickerSetCovered.set.id); + MediaDataController.getInstance(currentAccount).markFaturedStickersByIdAsRead(stickerSetCovered.set.id); } boolean installing = installingStickerSets.indexOfKey(stickerSetCovered.set.id) >= 0; boolean removing = removingStickerSets.indexOfKey(stickerSetCovered.set.id) >= 0; @@ -3246,7 +3247,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific @Override public void didReceivedNotification(int id, int account, Object... args) { if (id == NotificationCenter.stickersDidLoad) { - if ((Integer) args[0] == DataQuery.TYPE_IMAGE) { + if ((Integer) args[0] == MediaDataController.TYPE_IMAGE) { if (trendingGridAdapter != null) { if (trendingLoaded) { updateVisibleTrendingSets(); @@ -3261,12 +3262,12 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } else if (id == NotificationCenter.recentDocumentsDidLoad) { boolean isGif = (Boolean) args[0]; int type = (Integer) args[1]; - if (isGif || type == DataQuery.TYPE_IMAGE || type == DataQuery.TYPE_FAVE) { + if (isGif || type == MediaDataController.TYPE_IMAGE || type == MediaDataController.TYPE_FAVE) { checkDocuments(isGif); } } else if (id == NotificationCenter.featuredStickersDidLoad) { if (trendingGridAdapter != null) { - if (featuredStickersHash != DataQuery.getInstance(currentAccount).getFeaturesStickersHashWithoutUnread()) { + if (featuredStickersHash != MediaDataController.getInstance(currentAccount).getFeaturesStickersHashWithoutUnread()) { trendingLoaded = false; } if (trendingLoaded) { @@ -3391,13 +3392,13 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific ((EmptyCell) holder.itemView).setHeight(AndroidUtilities.dp(82)); break; case 2: - ArrayList unreadStickers = DataQuery.getInstance(currentAccount).getUnreadStickerSets(); + ArrayList unreadStickers = MediaDataController.getInstance(currentAccount).getUnreadStickerSets(); TLRPC.StickerSetCovered stickerSetCovered = sets.get((Integer) cache.get(position)); boolean unread = unreadStickers != null && unreadStickers.contains(stickerSetCovered.set.id); FeaturedStickerSetInfoCell cell = (FeaturedStickerSetInfoCell) holder.itemView; cell.setStickerSet(stickerSetCovered, unread); if (unread) { - DataQuery.getInstance(currentAccount).markFaturedStickersByIdAsRead(stickerSetCovered.set.id); + MediaDataController.getInstance(currentAccount).markFaturedStickersByIdAsRead(stickerSetCovered.set.id); } boolean installing = installingStickerSets.indexOfKey(stickerSetCovered.set.id) >= 0; boolean removing = removingStickerSets.indexOfKey(stickerSetCovered.set.id) >= 0; @@ -3433,7 +3434,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific width = 1080; } } - stickersPerRow = Math.max(1, width / AndroidUtilities.dp(72)); + stickersPerRow = Math.max(5, width / AndroidUtilities.dp(72)); trendingLayoutManager.setSpanCount(stickersPerRow); if (trendingLoaded) { return; @@ -3444,11 +3445,11 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific totalItems = 0; int num = 0; - ArrayList packs = DataQuery.getInstance(currentAccount).getFeaturedStickerSets(); + ArrayList packs = MediaDataController.getInstance(currentAccount).getFeaturedStickerSets(); for (int a = 0; a < packs.size(); a++) { TLRPC.StickerSetCovered pack = packs.get(a); - if (DataQuery.getInstance(currentAccount).isStickerPackInstalled(pack.set.id) || pack.covers.isEmpty() && pack.cover == null) { + if (MediaDataController.getInstance(currentAccount).isStickerPackInstalled(pack.set.id) || pack.covers.isEmpty() && pack.cover == null) { continue; } sets.add(pack); @@ -3472,7 +3473,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } if (totalItems != 0) { trendingLoaded = true; - featuredStickersHash = DataQuery.getInstance(currentAccount).getFeaturesStickersHashWithoutUnread(); + featuredStickersHash = MediaDataController.getInstance(currentAccount).getFeaturesStickersHashWithoutUnread(); } super.notifyDataSetChanged(); } @@ -3884,7 +3885,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific private class EmojiSearchAdapter extends RecyclerListView.SelectionAdapter { - private ArrayList result = new ArrayList<>(); + private ArrayList result = new ArrayList<>(); private String lastSearchEmojiString; private String lastSearchAlias; private Runnable searchRunnable; @@ -4086,12 +4087,12 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific String query = lastSearchEmojiString; String[] newLanguage = AndroidUtilities.getCurrentKeyboardLanguage(); if (!Arrays.equals(lastSearchKeyboardLanguage, newLanguage)) { - DataQuery.getInstance(currentAccount).fetchNewEmojiKeywords(newLanguage); + MediaDataController.getInstance(currentAccount).fetchNewEmojiKeywords(newLanguage); } lastSearchKeyboardLanguage = newLanguage; - DataQuery.getInstance(currentAccount).getEmojiSuggestions(lastSearchKeyboardLanguage, lastSearchEmojiString, false, new DataQuery.KeywordResultCallback() { + MediaDataController.getInstance(currentAccount).getEmojiSuggestions(lastSearchKeyboardLanguage, lastSearchEmojiString, false, new MediaDataController.KeywordResultCallback() { @Override - public void run(ArrayList param, String alias) { + public void run(ArrayList param, String alias) { if (query.equals(lastSearchEmojiString)) { lastSearchAlias = alias; emojiSearchField.progressDrawable.stopAnimation(); @@ -4147,7 +4148,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific @Override public void customOnDraw(Canvas canvas, int position) { - if (position == 2 && !DataQuery.getInstance(currentAccount).getUnreadStickerSets().isEmpty() && dotPaint != null) { + if (position == 2 && !MediaDataController.getInstance(currentAccount).getUnreadStickerSets().isEmpty() && dotPaint != null) { int x = canvas.getWidth() / 2 + AndroidUtilities.dp(4 + 5); int y = canvas.getHeight() / 2 - AndroidUtilities.dp(13 - 5); canvas.drawCircle(x, y, AndroidUtilities.dp(5), dotPaint); @@ -4449,7 +4450,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific resultsMap.put(result.id, result); addedCount++; } - searchEndReached = oldCount == results.size(); + searchEndReached = oldCount == results.size() || TextUtils.isEmpty(nextSearchOffset); if (addedCount != 0) { if (oldCount != 0) { notifyItemChanged(oldCount); @@ -4522,7 +4523,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific final ArrayList emojiStickersArray = new ArrayList<>(0); final LongSparseArray emojiStickersMap = new LongSparseArray<>(0); - HashMap> allStickers = DataQuery.getInstance(currentAccount).getAllStickers(); + HashMap> allStickers = MediaDataController.getInstance(currentAccount).getAllStickers(); if (searchQuery.length() <= 14) { CharSequence emoji = searchQuery; int length = emoji.length(); @@ -4552,12 +4553,12 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific if (allStickers != null && !allStickers.isEmpty() && searchQuery.length() > 1) { String[] newLanguage = AndroidUtilities.getCurrentKeyboardLanguage(); if (!Arrays.equals(lastSearchKeyboardLanguage, newLanguage)) { - DataQuery.getInstance(currentAccount).fetchNewEmojiKeywords(newLanguage); + MediaDataController.getInstance(currentAccount).fetchNewEmojiKeywords(newLanguage); } lastSearchKeyboardLanguage = newLanguage; - DataQuery.getInstance(currentAccount).getEmojiSuggestions(lastSearchKeyboardLanguage, searchQuery, false, new DataQuery.KeywordResultCallback() { + MediaDataController.getInstance(currentAccount).getEmojiSuggestions(lastSearchKeyboardLanguage, searchQuery, false, new MediaDataController.KeywordResultCallback() { @Override - public void run(ArrayList param, String alias) { + public void run(ArrayList param, String alias) { if (lastId != emojiSearchId) { return; } @@ -4580,17 +4581,17 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } }); } - ArrayList local = DataQuery.getInstance(currentAccount).getStickerSets(DataQuery.TYPE_IMAGE); + ArrayList local = MediaDataController.getInstance(currentAccount).getStickerSets(MediaDataController.TYPE_IMAGE); int index; for (int a = 0, size = local.size(); a < size; a++) { TLRPC.TL_messages_stickerSet set = local.get(a); - if ((index = set.set.title.toLowerCase().indexOf(searchQuery)) >= 0) { + if ((index = AndroidUtilities.indexOfIgnoreCase(set.set.title, searchQuery)) >= 0) { if (index == 0 || set.set.title.charAt(index - 1) == ' ') { clear(); localPacks.add(set); localPacksByName.put(set, index); } - } else if (set.set.short_name != null && (index = set.set.short_name.toLowerCase().indexOf(searchQuery)) >= 0) { + } else if (set.set.short_name != null && (index = AndroidUtilities.indexOfIgnoreCase(set.set.short_name, searchQuery)) >= 0) { if (index == 0 || set.set.short_name.charAt(index - 1) == ' ') { clear(); localPacks.add(set); @@ -4598,16 +4599,16 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } } } - local = DataQuery.getInstance(currentAccount).getStickerSets(DataQuery.TYPE_FEATURED); + local = MediaDataController.getInstance(currentAccount).getStickerSets(MediaDataController.TYPE_FEATURED); for (int a = 0, size = local.size(); a < size; a++) { TLRPC.TL_messages_stickerSet set = local.get(a); - if ((index = set.set.title.toLowerCase().indexOf(searchQuery)) >= 0) { + if ((index = AndroidUtilities.indexOfIgnoreCase(set.set.title, searchQuery)) >= 0) { if (index == 0 || set.set.title.charAt(index - 1) == ' ') { clear(); localPacks.add(set); localPacksByName.put(set, index); } - } else if (set.set.short_name != null && (index = set.set.short_name.toLowerCase().indexOf(searchQuery)) >= 0) { + } else if (set.set.short_name != null && (index = AndroidUtilities.indexOfIgnoreCase(set.set.short_name, searchQuery)) >= 0) { if (index == 0 || set.set.short_name.charAt(index - 1) == ' ') { clear(); localPacks.add(set); @@ -4884,12 +4885,12 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } } cell.setDrawProgress(installing || removing); - int idx = TextUtils.isEmpty(searchQuery) ? -1 : stickerSetCovered.set.title.toLowerCase().indexOf(searchQuery); + int idx = TextUtils.isEmpty(searchQuery) ? -1 : AndroidUtilities.indexOfIgnoreCase(stickerSetCovered.set.title, searchQuery); if (idx >= 0) { cell.setStickerSet(stickerSetCovered, false, idx, searchQuery.length()); } else { cell.setStickerSet(stickerSetCovered, false); - if (!TextUtils.isEmpty(searchQuery) && stickerSetCovered.set.short_name.toLowerCase().startsWith(searchQuery)) { + if (!TextUtils.isEmpty(searchQuery) && AndroidUtilities.indexOfIgnoreCase(stickerSetCovered.set.short_name, searchQuery) == 0) { cell.setUrl(stickerSetCovered.set.short_name, searchQuery.length()); } } @@ -4939,7 +4940,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific TLRPC.Document document = documents.get(b); cache.put(num, document); - Object parent = DataQuery.getInstance(currentAccount).getStickerSetById(DataQuery.getStickerSetId(document)); + Object parent = MediaDataController.getInstance(currentAccount).getStickerSetById(MediaDataController.getStickerSetId(document)); if (parent != null) { cacheParent.put(num, parent); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmptyTextProgressView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmptyTextProgressView.java index adf4cc8f6..bb2edc2bf 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmptyTextProgressView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmptyTextProgressView.java @@ -108,9 +108,9 @@ public class EmptyTextProgressView extends FrameLayout { int x = (width - child.getMeasuredWidth()) / 2; int y; if (showAtCenter) { - y = (height / 2 - child.getMeasuredHeight()) / 2; + y = (height / 2 - child.getMeasuredHeight()) / 2 + getPaddingTop(); } else { - y = (height - child.getMeasuredHeight()) / 2; + y = (height - child.getMeasuredHeight()) / 2 + getPaddingTop(); } child.layout(x, y, x + child.getMeasuredWidth(), y + child.getMeasuredHeight()); } 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 922ba114b..0de9bf642 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextView.java @@ -488,7 +488,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent } } else { updateStyle(2); - playButton.setImageDrawable(new ShareLocationDrawable(getContext(), true)); + playButton.setImageDrawable(new ShareLocationDrawable(getContext(), 1)); if (create && topPadding == 0) { setTopPadding(AndroidUtilities.dp2(36)); yPosition = 0; 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 9b8e8bf00..1f8073ba6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/InstantCameraView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/InstantCameraView.java @@ -128,8 +128,8 @@ public class InstantCameraView extends FrameLayout implements NotificationCenter private Bitmap lastBitmap; private int[] position = new int[2]; - private int cameraTexture[] = new int[1]; - private int oldCameraTexture[] = new int[1]; + private int[] cameraTexture = new int[1]; + private int[] oldCameraTexture = new int[1]; private float cameraTextureAlpha = 1.0f; private AnimatorSet animatorSet; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActionDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActionDrawable.java index 246e7374b..08e321129 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActionDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActionDrawable.java @@ -169,6 +169,9 @@ public class MediaActionDrawable extends Drawable { } else { transitionAnimationTime = 220.0f; } + if (animatingTransition) { + currentIcon = nextIcon; + } animatingTransition = true; nextIcon = icon; savedTransitionProgress = transitionProgress; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoPaintView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoPaintView.java index ccb432be8..93fb86011 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoPaintView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoPaintView.java @@ -1011,6 +1011,7 @@ public class PhotoPaintView extends FrameLayout implements EntityView.EntityView ImageView check = new ImageView(getContext()); check.setImageResource(R.drawable.ic_ab_done); check.setScaleType(ImageView.ScaleType.CENTER); + check.setColorFilter(new PorterDuffColorFilter(0xff2f8cc9, PorterDuff.Mode.MULTIPLY)); button.addView(check, LayoutHelper.createFrame(50, LayoutHelper.MATCH_PARENT)); } @@ -1078,6 +1079,7 @@ public class PhotoPaintView extends FrameLayout implements EntityView.EntityView ImageView check = new ImageView(getContext()); check.setImageResource(R.drawable.ic_ab_done); check.setScaleType(ImageView.ScaleType.CENTER); + check.setColorFilter(new PorterDuffColorFilter(0xff2f8cc9, PorterDuff.Mode.MULTIPLY)); button.addView(check, LayoutHelper.createFrame(50, LayoutHelper.MATCH_PARENT)); } 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 76b1b77f8..7810d6e8c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java @@ -42,6 +42,7 @@ import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; +import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; import java.lang.reflect.Field; @@ -686,4 +687,8 @@ public class PhotoViewerCaptionEnterView extends FrameLayout implements Notifica } } } + + public void setAllowTextEntitiesIntersection(boolean value) { + messageEditText.setAllowTextEntitiesIntersection(value); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/RLottieDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/RLottieDrawable.java new file mode 100644 index 000000000..33424b5e2 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/RLottieDrawable.java @@ -0,0 +1,601 @@ +/* + * 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.Components; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.BitmapDrawable; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; +import android.view.View; + +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.FileLog; + +import java.io.File; +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class RLottieDrawable extends BitmapDrawable implements Animatable { + + private static native long create(String src, int[] params, boolean precache); + private static native long createWithJson(String json, String name, int[] params); + private static native void destroy(long ptr); + private static native void setLayerColor(long ptr, String layer, int color); + private static native int getFrame(long ptr, int frame, Bitmap bitmap, int w, int h, int stride); + private static native void createCache(long ptr, Bitmap bitmap, int w, int h, int stride); + + private int width; + private int height; + private final int[] metaData = new int[3]; + private int timeBetweenFrames; + private HashMap newColorUpdates = new HashMap<>(); + private volatile HashMap pendingColorUpdates = new HashMap<>(); + + private View currentParentView; + + private boolean autoRepeat = true; + + private long lastFrameTime; + private volatile boolean nextFrameIsLast; + + private Runnable cacheGenerateTask; + private Runnable loadFrameTask; + private volatile Bitmap renderingBitmap; + private volatile Bitmap nextRenderingBitmap; + private volatile Bitmap backgroundBitmap; + + private boolean destroyWhenDone; + private boolean decodeSingleFrame; + private boolean singleFrameDecoded; + private boolean forceFrameRedraw; + private boolean applyingLayerColors; + private int currentFrame; + private boolean shouldLimitFps; + private boolean needGenerateCache; + + private float scaleX = 1.0f; + private float scaleY = 1.0f; + private boolean applyTransformation; + private final Rect dstRect = new Rect(); + private static final Handler uiHandler = new Handler(Looper.getMainLooper()); + private volatile boolean isRunning; + private volatile boolean isRecycled; + private volatile long nativePtr; + + private static byte[] readBuffer = new byte[64 * 1024]; + private static byte[] buffer = new byte[4096]; + + private ArrayList> parentViews = new ArrayList<>(); + private static ExecutorService loadFrameRunnableQueue = Executors.newCachedThreadPool(); + private static ThreadPoolExecutor lottieCacheGenerateQueue; + + private Runnable uiRunnableNoFrame = new Runnable() { + @Override + public void run() { + loadFrameTask = null; + decodeFrameFinishedInternal(); + } + }; + + private Runnable uiRunnableCacheFinished = new Runnable() { + @Override + public void run() { + cacheGenerateTask = null; + decodeFrameFinishedInternal(); + } + }; + + private Runnable uiRunnable = new Runnable() { + @Override + public void run() { + singleFrameDecoded = true; + invalidateInternal(); + decodeFrameFinishedInternal(); + } + }; + + private Runnable uiRunnableLastFrame = new Runnable() { + @Override + public void run() { + singleFrameDecoded = true; + isRunning = false; + invalidateInternal(); + decodeFrameFinishedInternal(); + } + }; + + private Runnable uiRunnableGenerateCache = new Runnable() { + @Override + public void run() { + if (!isRecycled && !destroyWhenDone && nativePtr != 0) { + lottieCacheGenerateQueue.execute(cacheGenerateTask = () -> { + if (cacheGenerateTask == null) { + return; + } + createCache(nativePtr, backgroundBitmap, width, height, backgroundBitmap.getRowBytes()); + uiHandler.post(uiRunnableCacheFinished); + }); + } + loadFrameTask = null; + decodeFrameFinishedInternal(); + } + }; + + private void checkRunningTasks() { + if (cacheGenerateTask != null) { + if (lottieCacheGenerateQueue.remove(cacheGenerateTask)) { + cacheGenerateTask = null; + } + } + if (!hasParentView() && nextRenderingBitmap != null && loadFrameTask != null) { + loadFrameTask = null; + nextRenderingBitmap = null; + } + } + + private void decodeFrameFinishedInternal() { + if (destroyWhenDone) { + checkRunningTasks(); + if (loadFrameTask == null && cacheGenerateTask == null && nativePtr != 0) { + destroy(nativePtr); + nativePtr = 0; + } + } + if (nativePtr == 0) { + recycleResources(); + return; + } + if (!hasParentView()) { + stop(); + } + scheduleNextGetFrame(); + } + + private void recycleResources() { + if (renderingBitmap != null) { + renderingBitmap.recycle(); + renderingBitmap = null; + } + if (backgroundBitmap != null) { + backgroundBitmap.recycle(); + backgroundBitmap = null; + } + } + + private Runnable loadFrameRunnable = new Runnable() { + @Override + public void run() { + if (isRecycled) { + return; + } + if (nativePtr == 0) { + uiHandler.post(uiRunnableNoFrame); + return; + } + if (backgroundBitmap == null) { + try { + backgroundBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + } catch (Throwable e) { + FileLog.e(e); + } + } + if (backgroundBitmap != null) { + if (needGenerateCache) { + uiHandler.post(uiRunnableGenerateCache); + needGenerateCache = false; + return; + } + + if (!pendingColorUpdates.isEmpty()) { + for (HashMap.Entry entry : pendingColorUpdates.entrySet()) { + setLayerColor(nativePtr, entry.getKey(), entry.getValue()); + } + pendingColorUpdates.clear(); + } + getFrame(nativePtr, currentFrame, backgroundBitmap, width, height, backgroundBitmap.getRowBytes()); + if (metaData[2] != 0) { + needGenerateCache = true; + metaData[2] = 0; + } + nextRenderingBitmap = backgroundBitmap; + int framesPerUpdates = shouldLimitFps ? 2 : 1; + if (currentFrame + framesPerUpdates < metaData[0]) { + currentFrame += framesPerUpdates; + nextFrameIsLast = false; + } else if (autoRepeat) { + currentFrame = 0; + nextFrameIsLast = false; + } else { + nextFrameIsLast = true; + } + } + //FileLog.d("frame time = " + (SystemClock.uptimeMillis() - time)); + uiHandler.post(uiRunnable); + } + }; + + public RLottieDrawable(File file, int w, int h, boolean precache, boolean limitFps) { + width = w; + height = h; + shouldLimitFps = limitFps; + getPaint().setFlags(Paint.FILTER_BITMAP_FLAG); + + nativePtr = create(file.getAbsolutePath(), metaData, precache); + if (precache && lottieCacheGenerateQueue == null) { + lottieCacheGenerateQueue = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); + } + if (nativePtr == 0) { + file.delete(); + } + if (shouldLimitFps && metaData[1] < 60) { + shouldLimitFps = false; + } + timeBetweenFrames = Math.max(shouldLimitFps ? 33 : 16, (int) (1000.0f / metaData[1])); + } + + public RLottieDrawable(int rawRes, String name, int w, int h) { + this(rawRes, name, w, h, true); + } + + public RLottieDrawable(int rawRes, String name, int w, int h, boolean startDecode) { + try { + InputStream inputStream = ApplicationLoader.applicationContext.getResources().openRawResource(rawRes); + int readLen; + int totalRead = 0; + while ((readLen = inputStream.read(buffer, 0, buffer.length)) > 0) { + if (readBuffer.length < totalRead + readLen) { + byte[] newBuffer = new byte[readBuffer.length * 2]; + System.arraycopy(readBuffer, 0, newBuffer, 0, totalRead); + readBuffer = newBuffer; + } + System.arraycopy(buffer, 0, readBuffer, totalRead, readLen); + totalRead += readLen; + } + String jsonString = new String(readBuffer, 0, totalRead); + inputStream.close(); + + width = w; + height = h; + getPaint().setFlags(Paint.FILTER_BITMAP_FLAG); + nativePtr = createWithJson(jsonString, name, metaData); + timeBetweenFrames = Math.max(16, (int) (1000.0f / metaData[1])); + autoRepeat = false; + if (startDecode) { + setAllowDecodeSingleFrame(true); + } + } catch (Throwable e) { + FileLog.e(e); + } + } + + public void addParentView(View view) { + if (view == null) { + return; + } + for (int a = 0, N = parentViews.size(); a < N; a++) { + if (parentViews.get(a).get() == view) { + return; + } else if (parentViews.get(a).get() == null) { + parentViews.remove(a); + N--; + a--; + } + } + parentViews.add(0, new WeakReference<>(view)); + } + + public void removeParentView(View view) { + if (view == null) { + return; + } + for (int a = 0, N = parentViews.size(); a < N; a++) { + View v = parentViews.get(a).get(); + if (v == view || v == null) { + parentViews.remove(a); + N--; + a--; + } + } + } + + private boolean hasParentView() { + if (getCallback() != null) { + return true; + } + for (int a = 0, N = parentViews.size(); a < N; a++) { + View view = parentViews.get(a).get(); + if (view != null) { + return true; + } else { + parentViews.remove(a); + N--; + a--; + } + } + return false; + } + + private void invalidateInternal() { + for (int a = 0, N = parentViews.size(); a < N; a++) { + View view = parentViews.get(a).get(); + if (view != null) { + view.invalidate(); + } else { + parentViews.remove(a); + N--; + a--; + } + } + if (getCallback() != null) { + invalidateSelf(); + } + } + + public void setAllowDecodeSingleFrame(boolean value) { + decodeSingleFrame = value; + if (decodeSingleFrame) { + scheduleNextGetFrame(); + } + } + + public void recycle() { + isRunning = false; + isRecycled = true; + checkRunningTasks(); + if (loadFrameTask == null && cacheGenerateTask == null) { + if (nativePtr != 0) { + destroy(nativePtr); + nativePtr = 0; + } + recycleResources(); + } else { + destroyWhenDone = true; + } + } + + public void setAutoRepeat(boolean value) { + autoRepeat = value; + } + + @Override + protected void finalize() throws Throwable { + try { + recycle(); + } finally { + super.finalize(); + } + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSPARENT; + } + + @Override + public void start() { + if (isRunning) { + return; + } + isRunning = true; + scheduleNextGetFrame(); + invalidateInternal(); + } + + public void beginApplyLayerColors() { + applyingLayerColors = true; + } + + public void commitApplyLayerColors() { + if (!applyingLayerColors) { + return; + } + applyingLayerColors = false; + if (!isRunning && decodeSingleFrame) { + if (currentFrame <= 2) { + currentFrame = 0; + } + nextFrameIsLast = false; + singleFrameDecoded = false; + if (!scheduleNextGetFrame()) { + forceFrameRedraw = true; + } + } + invalidateInternal(); + } + + public void setLayerColor(String layerName, int color) { + newColorUpdates.put(layerName, color); + if (!applyingLayerColors && !isRunning && decodeSingleFrame) { + if (currentFrame <= 2) { + currentFrame = 0; + } + nextFrameIsLast = false; + singleFrameDecoded = false; + if (!scheduleNextGetFrame()) { + forceFrameRedraw = true; + } + } + invalidateInternal(); + } + + private boolean scheduleNextGetFrame() { + if (cacheGenerateTask != null || loadFrameTask != null || nextRenderingBitmap != null || nativePtr == 0 || destroyWhenDone || !isRunning && (!decodeSingleFrame || decodeSingleFrame && singleFrameDecoded)) { + return false; + } + if (!newColorUpdates.isEmpty()) { + pendingColorUpdates.putAll(newColorUpdates); + newColorUpdates.clear(); + } + loadFrameRunnableQueue.execute(loadFrameTask = loadFrameRunnable); + return true; + } + + @Override + public void stop() { + isRunning = false; + } + + public void setProgress(float progress) { + if (progress < 0.0f) { + progress = 0.0f; + } else if (progress > 1.0f) { + progress = 1.0f; + } + currentFrame = (int) (metaData[0] * progress); + nextFrameIsLast = false; + invalidateSelf(); + } + + public void setCurrentFrame(int frame) { + currentFrame = frame; + nextFrameIsLast = false; + invalidateSelf(); + } + + public void setCurrentParentView(View view) { + currentParentView = view; + } + + private boolean isCurrentParentViewMaster() { + if (getCallback() != null) { + return true; + } + for (int a = 0, N = parentViews.size(); a < N; a++) { + if (parentViews.get(a).get() == null) { + parentViews.remove(a); + N--; + a--; + continue; + } + return parentViews.get(a).get() == currentParentView; + } + return true; + } + + @Override + public boolean isRunning() { + return isRunning; + } + + @Override + public int getIntrinsicHeight() { + return height; + } + + @Override + public int getIntrinsicWidth() { + return width; + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + applyTransformation = true; + } + + @Override + public void draw(Canvas canvas) { + if (nativePtr == 0 || destroyWhenDone) { + return; + } + long now = SystemClock.uptimeMillis(); + long timeDiff = Math.abs(now - lastFrameTime); + if (isRunning) { + if (renderingBitmap == null && nextRenderingBitmap == null) { + scheduleNextGetFrame(); + } else if (nextRenderingBitmap != null && (renderingBitmap == null || timeDiff >= timeBetweenFrames - 6) && isCurrentParentViewMaster()) { + backgroundBitmap = renderingBitmap; + renderingBitmap = nextRenderingBitmap; + if (nextFrameIsLast) { + stop(); + } + loadFrameTask = null; + singleFrameDecoded = true; + nextRenderingBitmap = null; + lastFrameTime = now; + scheduleNextGetFrame(); + } + } else if (forceFrameRedraw || decodeSingleFrame && timeDiff >= timeBetweenFrames - 6 && nextRenderingBitmap != null) { + backgroundBitmap = renderingBitmap; + renderingBitmap = nextRenderingBitmap; + loadFrameTask = null; + singleFrameDecoded = true; + nextRenderingBitmap = null; + lastFrameTime = now; + if (forceFrameRedraw) { + singleFrameDecoded = false; + forceFrameRedraw = false; + } + scheduleNextGetFrame(); + } + + if (renderingBitmap != null) { + if (applyTransformation) { + dstRect.set(getBounds()); + scaleX = (float) dstRect.width() / width; + scaleY = (float) dstRect.height() / height; + applyTransformation = false; + } + canvas.translate(dstRect.left, dstRect.top); + canvas.scale(scaleX, scaleY); + canvas.drawBitmap(renderingBitmap, 0, 0, getPaint()); + if (isRunning) { + invalidateInternal(); + } + } + } + + @Override + public int getMinimumHeight() { + return height; + } + + @Override + public int getMinimumWidth() { + return width; + } + + public Bitmap getRenderingBitmap() { + return renderingBitmap; + } + + public Bitmap getNextRenderingBitmap() { + return nextRenderingBitmap; + } + + public Bitmap getBackgroundBitmap() { + return backgroundBitmap; + } + + public Bitmap getAnimatedBitmap() { + if (renderingBitmap != null) { + return renderingBitmap; + } else if (nextRenderingBitmap != null) { + return nextRenderingBitmap; + } + return null; + } + + public boolean hasBitmap() { + return nativePtr != 0 && (renderingBitmap != null || nextRenderingBitmap != null); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/RLottieImageView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/RLottieImageView.java new file mode 100644 index 000000000..419540396 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/RLottieImageView.java @@ -0,0 +1,55 @@ +package org.telegram.ui.Components; + +import android.content.Context; +import android.widget.ImageView; + +import org.telegram.messenger.AndroidUtilities; + +import java.util.HashMap; + +public class RLottieImageView extends ImageView { + + private HashMap layerColors; + private RLottieDrawable drawable; + + public RLottieImageView(Context context) { + super(context); + } + + public void setLayerColor(String layer, int color) { + if (layerColors == null) { + layerColors = new HashMap<>(); + } + layerColors.put(layer, color); + if (drawable != null) { + drawable.setLayerColor(layer, color); + } + } + + public void setAnimation(int resId, int w, int h) { + drawable = new RLottieDrawable(resId, "" + resId, AndroidUtilities.dp(w), AndroidUtilities.dp(h), false); + drawable.beginApplyLayerColors(); + if (layerColors != null) { + for (HashMap.Entry entry : layerColors.entrySet()) { + drawable.setLayerColor(entry.getKey(), entry.getValue()); + } + } + drawable.commitApplyLayerColors(); + drawable.setAllowDecodeSingleFrame(true); + setImageDrawable(drawable); + } + + public void setProgress(float progress) { + if (drawable == null) { + return; + } + drawable.setProgress(progress); + } + + public void playAnimation() { + if (drawable == null) { + return; + } + drawable.start(); + } +} 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 5b44a89d3..076795097 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java @@ -337,11 +337,11 @@ public class RecyclerListView extends RecyclerView { case MotionEvent.ACTION_DOWN: float x = event.getX(); lastY = event.getY(); - float currectY = (float) Math.ceil((getMeasuredHeight() - AndroidUtilities.dp(24 + 30)) * progress) + AndroidUtilities.dp(12); - if (LocaleController.isRTL && x > AndroidUtilities.dp(25) || !LocaleController.isRTL && x < AndroidUtilities.dp(107) || lastY < currectY || lastY > currectY + AndroidUtilities.dp(30)) { + float currentY = (float) Math.ceil((getMeasuredHeight() - AndroidUtilities.dp(24 + 30)) * progress) + AndroidUtilities.dp(12); + if (LocaleController.isRTL && x > AndroidUtilities.dp(25) || !LocaleController.isRTL && x < AndroidUtilities.dp(107) || lastY < currentY || lastY > currentY + AndroidUtilities.dp(30)) { return false; } - startDy = lastY - currectY; + startDy = lastY - currentY; pressed = true; lastUpdateTime = System.currentTimeMillis(); getCurrentLetter(); @@ -389,7 +389,7 @@ public class RecyclerListView extends RecyclerView { if (adapter instanceof FastScrollAdapter) { FastScrollAdapter fastScrollAdapter = (FastScrollAdapter) adapter; int position = fastScrollAdapter.getPositionForScrollProgress(progress); - linearLayoutManager.scrollToPositionWithOffset(position, 0); + linearLayoutManager.scrollToPositionWithOffset(position, sectionOffset); String newLetter = fastScrollAdapter.getLetter(position); if (newLetter == null) { if (letterLayout != null) { @@ -904,7 +904,9 @@ public class RecyclerListView extends RecyclerView { protected void onMeasure(int widthSpec, int heightSpec) { super.onMeasure(widthSpec, heightSpec); if (fastScroll != null) { - fastScroll.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(132), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); + int height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); + fastScroll.getLayoutParams().height = height; + fastScroll.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(132), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); } } @@ -913,6 +915,7 @@ public class RecyclerListView extends RecyclerView { super.onLayout(changed, l, t, r, b); if (fastScroll != null) { selfOnLayout = true; + t += getPaddingTop(); if (LocaleController.isRTL) { fastScroll.layout(0, t, fastScroll.getMeasuredWidth(), t + fastScroll.getMeasuredHeight()); } else { @@ -942,13 +945,44 @@ public class RecyclerListView extends RecyclerView { LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; if (linearLayoutManager.getOrientation() == LinearLayoutManager.VERTICAL) { if (sectionsAdapter != null) { + int paddingTop = getPaddingTop(); if (sectionsType == 1) { - int firstVisibleItem = linearLayoutManager.findFirstVisibleItemPosition(); - int lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition(); - int visibleItemCount = Math.abs(lastVisibleItem - firstVisibleItem) + 1; - if (firstVisibleItem == NO_POSITION) { + int childCount = getChildCount(); + int maxBottom = 0; + int minBottom = Integer.MAX_VALUE; + View minChild = null; + + int minBottomSection = Integer.MAX_VALUE; + for (int a = 0; a < childCount; a++) { + View child = getChildAt(a); + int bottom = child.getBottom(); + if (bottom <= sectionOffset + paddingTop) { + continue; + } + if (bottom < minBottom) { + minBottom = bottom; + minChild = child; + } + maxBottom = Math.max(maxBottom, bottom); + if (bottom < sectionOffset + paddingTop + AndroidUtilities.dp(32)) { + continue; + } + if (bottom < minBottomSection) { + minBottomSection = bottom; + } + } + if (minChild == null) { return; } + ViewHolder holder = getChildViewHolder(minChild); + if (holder == null) { + return; + } + + int firstVisibleItem = holder.getAdapterPosition(); + int lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition(); + int visibleItemCount = Math.abs(lastVisibleItem - firstVisibleItem) + 1; + if (scrollingByUser && fastScroll != null) { Adapter adapter = getAdapter(); if (adapter instanceof FastScrollAdapter) { @@ -987,12 +1021,12 @@ public class RecyclerListView extends RecyclerView { if (a == startSection) { int pos = sectionsAdapter.getPositionInSectionForPosition(itemNum); if (pos == count - 1) { - header.setTag(-header.getHeight()); + header.setTag(-header.getHeight() + paddingTop); } else if (pos == count - 2) { View child = getChildAt(itemNum - firstVisibleItem); int headerTop; if (child != null) { - headerTop = child.getTop(); + headerTop = child.getTop() + paddingTop; } else { headerTop = -AndroidUtilities.dp(100); } @@ -1008,7 +1042,7 @@ public class RecyclerListView extends RecyclerView { } else { View child = getChildAt(itemNum - firstVisibleItem); if (child != null) { - header.setTag(child.getTop()); + header.setTag(child.getTop() + paddingTop); } else { header.setTag(-AndroidUtilities.dp(100)); } @@ -1030,7 +1064,7 @@ public class RecyclerListView extends RecyclerView { for (int a = 0; a < childCount; a++) { View child = getChildAt(a); int bottom = child.getBottom(); - if (bottom <= sectionOffset + getPaddingTop()) { + if (bottom <= sectionOffset + paddingTop) { continue; } if (bottom < minBottom) { @@ -1038,7 +1072,7 @@ public class RecyclerListView extends RecyclerView { minChild = child; } maxBottom = Math.max(maxBottom, bottom); - if (bottom < sectionOffset + getPaddingTop() + AndroidUtilities.dp(32)) { + if (bottom < sectionOffset + paddingTop + AndroidUtilities.dp(32)) { continue; } if (bottom < minBottomSection) { @@ -1068,7 +1102,6 @@ public class RecyclerListView extends RecyclerView { int count = sectionsAdapter.getCountForSection(startSection); int pos = sectionsAdapter.getPositionInSectionForPosition(firstVisibleItem); - int paddingTop = getPaddingTop(); int sectionOffsetY = maxBottom != 0 && maxBottom < (getMeasuredHeight() - getPaddingBottom()) ? 0 : sectionOffset; if (pos == count - 1) { @@ -1262,6 +1295,10 @@ public class RecyclerListView extends RecyclerView { onScrollListener = listener; } + public OnScrollListener getOnScrollListener() { + return onScrollListener; + } + public void setOnInterceptTouchListener(OnInterceptTouchListener listener) { onInterceptTouchListener = listener; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ScrollSlidingTabStrip.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ScrollSlidingTabStrip.java index 5d24acb48..34c5105c6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ScrollSlidingTabStrip.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ScrollSlidingTabStrip.java @@ -27,6 +27,7 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLoader; import org.telegram.messenger.ImageLocation; +import org.telegram.messenger.MessageObject; import org.telegram.messenger.R; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; @@ -153,6 +154,7 @@ public class ScrollSlidingTabStrip extends HorizontalScrollView { tabsContainer.addView(tab); tab.setSelected(position == currentPosition); BackupImageView imageView = new BackupImageView(getContext()); + imageView.setLayerNum(1); imageView.setRoundRadius(AndroidUtilities.dp(15)); AvatarDrawable avatarDrawable = new AvatarDrawable(); @@ -175,6 +177,7 @@ public class ScrollSlidingTabStrip extends HorizontalScrollView { tabsContainer.addView(tab); tab.setSelected(position == currentPosition); BackupImageView imageView = new BackupImageView(getContext()); + imageView.setLayerNum(1); imageView.setAspectFit(true); tab.addView(imageView, LayoutHelper.createFrame(30, 30, Gravity.CENTER)); @@ -229,6 +232,7 @@ public class ScrollSlidingTabStrip extends HorizontalScrollView { Object parentObject = child.getTag(R.id.parent_tag); TLRPC.Document sticker = (TLRPC.Document) child.getTag(R.id.object_tag); ImageLocation imageLocation; + if (object instanceof TLRPC.Document) { TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(sticker.thumbs, 90); imageLocation = ImageLocation.getForDocument(thumb, sticker); @@ -238,8 +242,17 @@ public class ScrollSlidingTabStrip extends HorizontalScrollView { } else { continue; } + if (imageLocation == null) { + continue; + } BackupImageView imageView = (BackupImageView) ((FrameLayout) child).getChildAt(0); - imageView.setImage(imageLocation, null, "webp", null, parentObject); + if (object instanceof TLRPC.Document && MessageObject.isAnimatedStickerDocument(sticker)) { + imageView.setImage(ImageLocation.getForDocument(sticker), "30_30", imageLocation, null, 0, parentObject); + } else if (imageLocation.lottieAnimation) { + imageView.setImage(imageLocation, "30_30", "tgs", null, parentObject); + } else { + imageView.setImage(imageLocation, null, "webp", null, parentObject); + } } } @@ -273,11 +286,20 @@ public class ScrollSlidingTabStrip extends HorizontalScrollView { } else { continue; } + if (imageLocation == null) { + continue; + } BackupImageView imageView = (BackupImageView) ((FrameLayout) child).getChildAt(0); if (a < newStart || a >= newStart + count) { imageView.setImageDrawable(null); } else { - imageView.setImage(imageLocation, null, "webp", null, parentObject); + if (object instanceof TLRPC.Document && MessageObject.isAnimatedStickerDocument(sticker)) { + imageView.setImage(ImageLocation.getForDocument(sticker), "30_30", imageLocation, null, 0, parentObject); + } else if (imageLocation.lottieAnimation) { + imageView.setImage(imageLocation, "30_30", "tgs", null, parentObject); + } else { + imageView.setImage(imageLocation, null, "webp", null, parentObject); + } } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareLocationDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareLocationDrawable.java index 37dc7c336..16a14bfc2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareLocationDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareLocationDrawable.java @@ -11,6 +11,7 @@ package org.telegram.ui.Components; import android.content.Context; import android.graphics.Canvas; import android.graphics.ColorFilter; +import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import org.telegram.messenger.AndroidUtilities; @@ -19,15 +20,23 @@ import org.telegram.messenger.R; public class ShareLocationDrawable extends Drawable { private long lastUpdateTime = 0; - private float progress[] = new float[] {0.0f, -0.5f}; + private float[] progress = new float[]{0.0f, -0.5f}; private Drawable drawable; private Drawable drawableLeft; private Drawable drawableRight; - private boolean isSmall; + private int currentType; - public ShareLocationDrawable(Context context, boolean small) { - isSmall = small; - if (small) { + public ShareLocationDrawable(Context context, int type) { + currentType = type; + if (type == 3) { + drawable = context.getResources().getDrawable(R.drawable.nearby_l); + drawableLeft = context.getResources().getDrawable(R.drawable.animationpinleft); + drawableRight = context.getResources().getDrawable(R.drawable.animationpinright); + } else if (type == 2) { + drawable = context.getResources().getDrawable(R.drawable.nearby_m); + drawableLeft = context.getResources().getDrawable(R.drawable.animationpinleft); + drawableRight = context.getResources().getDrawable(R.drawable.animationpinright); + } else if (type == 1) { drawable = context.getResources().getDrawable(R.drawable.smallanimationpin); drawableLeft = context.getResources().getDrawable(R.drawable.smallanimationpinleft); drawableRight = context.getResources().getDrawable(R.drawable.smallanimationpinright); @@ -59,7 +68,16 @@ public class ShareLocationDrawable extends Drawable { @Override public void draw(Canvas canvas) { - int size = AndroidUtilities.dp(isSmall ? 30 : 120); + int size; + if (currentType == 3) { + size = AndroidUtilities.dp(44); + } else if (currentType == 2) { + size = AndroidUtilities.dp(32); + } else if (currentType == 1) { + size = AndroidUtilities.dp(30); + } else { + size = AndroidUtilities.dp(120); + } int y = getBounds().top + (getIntrinsicHeight() - size) / 2; int x = getBounds().left + (getIntrinsicWidth() - size) / 2; @@ -71,9 +89,45 @@ public class ShareLocationDrawable extends Drawable { continue; } float scale = 0.5f + 0.5f * progress[a]; - int w = AndroidUtilities.dp((isSmall ? 2.5f : 5) * scale); - int h = AndroidUtilities.dp((isSmall ? 6.5f : 18) * scale); - int tx = AndroidUtilities.dp((isSmall ? 6.0f : 15) * progress[a]); + int w; + int h; + int tx; + int cx; + int cx2; + int cy; + if (currentType == 3) { + w = AndroidUtilities.dp((5) * scale); + h = AndroidUtilities.dp((18) * scale); + tx = AndroidUtilities.dp((15) * progress[a]); + + cx = x + AndroidUtilities.dp(2) - tx; + cy = y + drawable.getIntrinsicHeight() / 2 - AndroidUtilities.dp(7); + cx2 = x + drawable.getIntrinsicWidth() - AndroidUtilities.dp(2) + tx; + } else if (currentType == 2) { + w = AndroidUtilities.dp((5) * scale); + h = AndroidUtilities.dp((18) * scale); + tx = AndroidUtilities.dp((15) * progress[a]); + + cx = x + AndroidUtilities.dp(2) - tx; + cy = y + drawable.getIntrinsicHeight() / 2; + cx2 = x + drawable.getIntrinsicWidth() - AndroidUtilities.dp(2) + tx; + } else if (currentType == 1) { + w = AndroidUtilities.dp((2.5f) * scale); + h = AndroidUtilities.dp((6.5f) * scale); + tx = AndroidUtilities.dp((6.0f) * progress[a]); + + cx = x + AndroidUtilities.dp(7) - tx; + cy = y + drawable.getIntrinsicHeight() / 2; + cx2 = x + drawable.getIntrinsicWidth() - AndroidUtilities.dp(7) + tx; + } else { + w = AndroidUtilities.dp((5) * scale); + h = AndroidUtilities.dp((18) * scale); + tx = AndroidUtilities.dp((15) * progress[a]); + + cx = x + AndroidUtilities.dp(42) - tx; + cy = y + drawable.getIntrinsicHeight() / 2 - AndroidUtilities.dp(7); + cx2 = x + drawable.getIntrinsicWidth() - AndroidUtilities.dp(42) + tx; + } float alpha; if (progress[a] < 0.5f) { alpha = progress[a] / 0.5f; @@ -81,17 +135,12 @@ public class ShareLocationDrawable extends Drawable { alpha = 1.0f - (progress[a] - 0.5f) / 0.5f; } - int cx = x + AndroidUtilities.dp(isSmall ? 7 : 42) - tx; - int cy = y + drawable.getIntrinsicHeight() / 2 - (isSmall ? 0 : AndroidUtilities.dp(7)); - drawableLeft.setAlpha((int) (alpha * 255)); drawableLeft.setBounds(cx - w, cy - h, cx + w, cy + h); drawableLeft.draw(canvas); - cx = x + drawable.getIntrinsicWidth() - AndroidUtilities.dp(isSmall ? 7 : 42) + tx; - drawableRight.setAlpha((int) (alpha * 255)); - drawableRight.setBounds(cx - w, cy - h, cx + w, cy + h); + drawableRight.setBounds(cx2 - w, cy - h, cx2 + w, cy + h); drawableRight.draw(canvas); } @@ -112,16 +161,30 @@ public class ShareLocationDrawable extends Drawable { @Override public int getOpacity() { - return 0; + return PixelFormat.TRANSPARENT; } @Override public int getIntrinsicWidth() { - return AndroidUtilities.dp(isSmall ? 40 : 120); + if (currentType == 3) { + return AndroidUtilities.dp(100); + } else if (currentType == 2) { + return AndroidUtilities.dp(74); + } else if (currentType == 1) { + return AndroidUtilities.dp(40); + } + return AndroidUtilities.dp(120); } @Override public int getIntrinsicHeight() { - return AndroidUtilities.dp(isSmall ? 40 : 180); + if (currentType == 3) { + return AndroidUtilities.dp(100); + } else if (currentType == 2) { + return AndroidUtilities.dp(74); + } else if (currentType == 1) { + return AndroidUtilities.dp(40); + } + return AndroidUtilities.dp(180); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SharingLocationsAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SharingLocationsAlert.java index 9bd2f8919..3275d2259 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SharingLocationsAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SharingLocationsAlert.java @@ -276,7 +276,7 @@ public class SharingLocationsAlert extends BottomSheet implements NotificationCe View view; switch (viewType) { case 0: - view = new SharingLiveLocationCell(context, false); + view = new SharingLiveLocationCell(context, false, 54); //view.setBackgroundDrawable(Theme.getSelectorDrawable(false)); break; case 1: 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 6dc3792ab..a307d629e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java @@ -112,10 +112,10 @@ public class SizeNotifierFrameLayout extends FrameLayout { } public void notifyHeightChanged() { + if (parallaxEffect != null) { + parallaxScale = parallaxEffect.getScale(getMeasuredWidth(), getMeasuredHeight()); + } if (delegate != null) { - if (parallaxEffect != null) { - parallaxScale = parallaxEffect.getScale(getMeasuredWidth(), getMeasuredHeight()); - } keyboardHeight = getKeyboardHeight(); final boolean isWidthGreater = AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y; post(() -> { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksView.java index abd78c087..55a362e18 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksView.java @@ -20,7 +20,7 @@ import android.widget.FrameLayout; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; @@ -49,7 +49,7 @@ public class StickerMasksView extends FrameLayout implements NotificationCenter. private int currentAccount = UserConfig.selectedAccount; private ArrayList[] stickerSets = new ArrayList[]{new ArrayList<>(), new ArrayList<>()}; private ArrayList[] recentStickers = new ArrayList[]{new ArrayList<>(), new ArrayList<>()}; - private int currentType = DataQuery.TYPE_MASK; + private int currentType = MediaDataController.TYPE_MASK; private Listener listener; private StickersGridAdapter stickersGridAdapter; @@ -69,8 +69,8 @@ public class StickerMasksView extends FrameLayout implements NotificationCenter. setBackgroundColor(0xff222222); setClickable(true); - DataQuery.getInstance(currentAccount).checkStickers(DataQuery.TYPE_IMAGE); - DataQuery.getInstance(currentAccount).checkStickers(DataQuery.TYPE_MASK); + MediaDataController.getInstance(currentAccount).checkStickers(MediaDataController.TYPE_IMAGE); + MediaDataController.getInstance(currentAccount).checkStickers(MediaDataController.TYPE_MASK); stickersGridView = new RecyclerListView(context) { @Override public boolean onInterceptTouchEvent(MotionEvent event) { @@ -105,7 +105,7 @@ public class StickerMasksView extends FrameLayout implements NotificationCenter. TLRPC.Document document = cell.getSticker(); Object parent = cell.getParentObject(); listener.onStickerSelected(parent, document); - DataQuery.getInstance(currentAccount).addRecentSticker(DataQuery.TYPE_MASK, parent, document, (int) (System.currentTimeMillis() / 1000), false); + MediaDataController.getInstance(currentAccount).addRecentSticker(MediaDataController.TYPE_MASK, parent, document, (int) (System.currentTimeMillis() / 1000), false); MessagesController.getInstance(currentAccount).saveRecentSticker(parent, document, true); }; stickersGridView.setOnItemClickListener(stickersOnItemClickListener); @@ -128,15 +128,15 @@ public class StickerMasksView extends FrameLayout implements NotificationCenter. updateStickerTabs(); scrollSlidingTabStrip.setDelegate(page -> { if (page == 0) { - if (currentType == DataQuery.TYPE_IMAGE) { - currentType = DataQuery.TYPE_MASK; + if (currentType == MediaDataController.TYPE_IMAGE) { + currentType = MediaDataController.TYPE_MASK; } else { - currentType = DataQuery.TYPE_IMAGE; + currentType = MediaDataController.TYPE_IMAGE; } if (listener != null) { listener.onTypeChanged(); } - recentStickers[currentType] = DataQuery.getInstance(currentAccount).getRecentStickers(currentType); + recentStickers[currentType] = MediaDataController.getInstance(currentAccount).getRecentStickers(currentType); stickersLayoutManager.scrollToPositionWithOffset(0, 0); updateStickerTabs(); reloadStickersAdapter(); @@ -192,7 +192,7 @@ public class StickerMasksView extends FrameLayout implements NotificationCenter. stickersTabOffset = 0; int lastPosition = scrollSlidingTabStrip.getCurrentPosition(); scrollSlidingTabStrip.removeTabs(); - if (currentType == DataQuery.TYPE_IMAGE) { + if (currentType == MediaDataController.TYPE_IMAGE) { Drawable drawable = getContext().getResources().getDrawable(R.drawable.ic_masks_msk1); Theme.setDrawableColorByKey(drawable, Theme.key_chat_emojiPanelIcon); scrollSlidingTabStrip.addIconTab(drawable); @@ -211,7 +211,7 @@ public class StickerMasksView extends FrameLayout implements NotificationCenter. } stickerSets[currentType].clear(); - ArrayList packs = DataQuery.getInstance(currentAccount).getStickerSets(currentType); + ArrayList packs = MediaDataController.getInstance(currentAccount).getStickerSets(currentType); for (int a = 0; a < packs.size(); a++) { TLRPC.TL_messages_stickerSet pack = packs.get(a); if (pack.set.archived || pack.documents == null || pack.documents.isEmpty()) { @@ -245,9 +245,9 @@ public class StickerMasksView extends FrameLayout implements NotificationCenter. if (document == null) { return; } - DataQuery.getInstance(currentAccount).addRecentSticker(currentType, null, document, (int) (System.currentTimeMillis() / 1000), false); + MediaDataController.getInstance(currentAccount).addRecentSticker(currentType, null, document, (int) (System.currentTimeMillis() / 1000), false); boolean wasEmpty = recentStickers[currentType].isEmpty(); - recentStickers[currentType] = DataQuery.getInstance(currentAccount).getRecentStickers(currentType); + recentStickers[currentType] = MediaDataController.getInstance(currentAccount).getRecentStickers(currentType); if (stickersGridAdapter != null) { stickersGridAdapter.notifyDataSetChanged(); } @@ -299,9 +299,9 @@ public class StickerMasksView extends FrameLayout implements NotificationCenter. updateStickerTabs(); reloadStickersAdapter(); checkDocuments(); - DataQuery.getInstance(currentAccount).loadRecents(DataQuery.TYPE_IMAGE, false, true, false); - DataQuery.getInstance(currentAccount).loadRecents(DataQuery.TYPE_MASK, false, true, false); - DataQuery.getInstance(currentAccount).loadRecents(DataQuery.TYPE_FAVE, false, true, false); + MediaDataController.getInstance(currentAccount).loadRecents(MediaDataController.TYPE_IMAGE, false, true, false); + MediaDataController.getInstance(currentAccount).loadRecents(MediaDataController.TYPE_MASK, false, true, false); + MediaDataController.getInstance(currentAccount).loadRecents(MediaDataController.TYPE_FAVE, false, true, false); } } @@ -314,7 +314,7 @@ public class StickerMasksView extends FrameLayout implements NotificationCenter. private void checkDocuments() { int previousCount = recentStickers[currentType].size(); - recentStickers[currentType] = DataQuery.getInstance(currentAccount).getRecentStickers(currentType); + recentStickers[currentType] = MediaDataController.getInstance(currentAccount).getRecentStickers(currentType); if (stickersGridAdapter != null) { stickersGridAdapter.notifyDataSetChanged(); } 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 8854fdebb..cf66b6853 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java @@ -36,7 +36,7 @@ import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.Emoji; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; @@ -211,6 +211,12 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not init(context); } + @Override + public void show() { + super.show(); + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.stopAllHeavyOperations, 2); + } + public void setClearsInputField(boolean value) { clearsInputField = value; } @@ -222,10 +228,10 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not private void loadStickerSet() { if (inputStickerSet != null) { if (stickerSet == null && inputStickerSet.short_name != null) { - stickerSet = DataQuery.getInstance(currentAccount).getStickerSetByName(inputStickerSet.short_name); + stickerSet = MediaDataController.getInstance(currentAccount).getStickerSetByName(inputStickerSet.short_name); } if (stickerSet == null) { - stickerSet = DataQuery.getInstance(currentAccount).getStickerSetById(inputStickerSet.id); + stickerSet = MediaDataController.getInstance(currentAccount).getStickerSetById(inputStickerSet.id); } if (stickerSet == null) { TLRPC.TL_messages_getStickerSet req = new TLRPC.TL_messages_getStickerSet(); @@ -471,7 +477,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not } } if (!set) { - stickerEmojiTextView.setText(Emoji.replaceEmoji(DataQuery.getInstance(currentAccount).getEmojiForSticker(selectedSticker.id), stickerEmojiTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(30), false)); + stickerEmojiTextView.setText(Emoji.replaceEmoji(MediaDataController.getInstance(currentAccount).getEmojiForSticker(selectedSticker.id), stickerEmojiTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(30), false)); } TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(selectedSticker.thumbs, 90); @@ -521,7 +527,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not optionsButton.setIcon(R.drawable.ic_ab_other); optionsButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_player_actionBarSelector), 1)); containerView.addView(optionsButton, LayoutHelper.createFrame(40, 40, Gravity.TOP | Gravity.RIGHT, 0, 5, 5, 0)); - optionsButton.addSubItem(1, R.drawable.msg_shareout, LocaleController.getString("StickersShare", R.string.StickersShare)); + optionsButton.addSubItem(1, R.drawable.msg_share, LocaleController.getString("StickersShare", R.string.StickersShare)); optionsButton.addSubItem(2, R.drawable.msg_link, LocaleController.getString("CopyLink", R.string.CopyLink)); optionsButton.setOnClickListener(v -> optionsButton.toggleSubMenu()); optionsButton.setDelegate(this::onSubItemClick); @@ -555,6 +561,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not stickerImageView = new BackupImageView(context); stickerImageView.setAspectFit(true); + stickerImageView.setLayerNum(3); stickerPreviewLayout.addView(stickerImageView); stickerEmojiTextView = new TextView(context); @@ -668,7 +675,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not } titleTextView.setText(stringBuilder != null ? stringBuilder : stickerSet.set.title); - if (stickerSet.set == null || !DataQuery.getInstance(currentAccount).isStickerPackInstalled(stickerSet.set.id)) { + if (stickerSet.set == null || !MediaDataController.getInstance(currentAccount).isStickerPackInstalled(stickerSet.set.id)) { String text; if (stickerSet.set.masks) { text = LocaleController.formatString("AddStickersCount", R.string.AddStickersCount, LocaleController.formatPluralString("MasksCount", stickerSet.documents.size())).toUpperCase(); @@ -703,7 +710,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not } catch (Exception e) { FileLog.e(e); } - DataQuery.getInstance(currentAccount).loadStickers(stickerSet.set.masks ? DataQuery.TYPE_MASK : DataQuery.TYPE_IMAGE, false, true); + MediaDataController.getInstance(currentAccount).loadStickers(stickerSet.set.masks ? MediaDataController.TYPE_MASK : MediaDataController.TYPE_IMAGE, false, true); })); }, text, Theme.getColor(Theme.key_dialogTextBlue2)); } else { @@ -719,7 +726,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not installDelegate.onStickerSetUninstalled(); } dismiss(); - DataQuery.getInstance(currentAccount).removeStickersSet(getContext(), stickerSet.set, 1, parentFragment, true); + MediaDataController.getInstance(currentAccount).removeStickersSet(getContext(), stickerSet.set, 1, parentFragment, true); }, text, Theme.getColor(Theme.key_dialogTextRed)); } else { setButton(v -> { @@ -727,7 +734,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not installDelegate.onStickerSetUninstalled(); } dismiss(); - DataQuery.getInstance(currentAccount).removeStickersSet(getContext(), stickerSet.set, 0, parentFragment, true); + MediaDataController.getInstance(currentAccount).removeStickersSet(getContext(), stickerSet.set, 0, parentFragment, true); }, text, Theme.getColor(Theme.key_dialogTextRed)); } } @@ -834,6 +841,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not reqId = 0; } NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.emojiDidLoad); + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.startAllHeavyOperations, 2); } @Override @@ -903,11 +911,13 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not View view = null; switch (viewType) { case 0: - view = new StickerEmojiCell(context) { + StickerEmojiCell cell = new StickerEmojiCell(context) { public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(MeasureSpec.makeMeasureSpec(itemSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(82), MeasureSpec.EXACTLY)); } }; + cell.getImageView().setLayerNum(3); + view = cell; break; case 1: view = new EmptyCell(context); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersArchiveAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersArchiveAlert.java index b9972eb08..3e4ed92f5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersArchiveAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersArchiveAlert.java @@ -16,7 +16,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; import org.telegram.tgnet.TLRPC; @@ -46,10 +46,10 @@ public class StickersArchiveAlert extends AlertDialog.Builder { TLRPC.StickerSetCovered set = sets.get(0); if (set.set.masks) { - currentType = DataQuery.TYPE_MASK; + currentType = MediaDataController.TYPE_MASK; setTitle(LocaleController.getString("ArchivedMasksAlertTitle", R.string.ArchivedMasksAlertTitle)); } else { - currentType = DataQuery.TYPE_IMAGE; + currentType = MediaDataController.TYPE_IMAGE; setTitle(LocaleController.getString("ArchivedStickersAlertTitle", R.string.ArchivedStickersAlertTitle)); } stickerSets = new ArrayList<>(sets); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/TermsOfServiceView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/TermsOfServiceView.java index 569a176a0..90691625b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/TermsOfServiceView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/TermsOfServiceView.java @@ -104,7 +104,7 @@ public class TermsOfServiceView extends FrameLayout { } if (response instanceof TLRPC.TL_boolTrue) { MessagesController.getInstance(currentAccount).performLogout(0); - } else { + } else if (error == null || error.code != -1000) { String errorText = LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred); if (error != null) { errorText += "\n" + error.text; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/TextStyleSpan.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/TextStyleSpan.java new file mode 100644 index 000000000..aeee06202 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/TextStyleSpan.java @@ -0,0 +1,164 @@ +/* + * 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.Components; + +import android.graphics.Paint; +import android.graphics.Typeface; +import android.text.TextPaint; +import android.text.style.MetricAffectingSpan; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.tgnet.TLRPC; + +public class TextStyleSpan extends MetricAffectingSpan { + + private int textSize; + private int color; + private TextStyleRun style; + + public static class TextStyleRun { + + public int flags; + public int start; + public int end; + public TLRPC.MessageEntity urlEntity; + + public TextStyleRun() { + + } + + public TextStyleRun(TextStyleRun run) { + flags = run.flags; + start = run.start; + end = run.end; + urlEntity = run.urlEntity; + } + + public void merge(TextStyleRun run) { + flags |= run.flags; + if (urlEntity == null && run.urlEntity != null) { + urlEntity = run.urlEntity; + } + } + + public void replace(TextStyleRun run) { + flags = run.flags; + urlEntity = run.urlEntity; + } + + public void applyStyle(TextPaint p) { + Typeface typeface = getTypeface(); + if (typeface != null) { + p.setTypeface(typeface); + } + if ((flags & FLAG_STYLE_UNDERLINE) != 0) { + p.setFlags(p.getFlags() | Paint.UNDERLINE_TEXT_FLAG); + } else { + p.setFlags(p.getFlags() &~ Paint.UNDERLINE_TEXT_FLAG); + } + if ((flags & FLAG_STYLE_STRIKE) != 0) { + p.setFlags(p.getFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + } else { + p.setFlags(p.getFlags() &~ Paint.STRIKE_THRU_TEXT_FLAG); + } + } + + public Typeface getTypeface() { + if ((flags & FLAG_STYLE_MONO) != 0 || (flags & FLAG_STYLE_QUOTE) != 0) { + return Typeface.MONOSPACE; + } else if ((flags & FLAG_STYLE_BOLD) != 0 && (flags & FLAG_STYLE_ITALIC) != 0) { + return AndroidUtilities.getTypeface("fonts/rmediumitalic.ttf"); + } else if ((flags & FLAG_STYLE_BOLD) != 0) { + return AndroidUtilities.getTypeface("fonts/rmedium.ttf"); + } else if ((flags & FLAG_STYLE_ITALIC) != 0) { + return AndroidUtilities.getTypeface("fonts/ritalic.ttf"); + } else { + return null; + } + } + } + + public final static int FLAG_STYLE_BOLD = 1; + public final static int FLAG_STYLE_ITALIC = 2; + public final static int FLAG_STYLE_MONO = 4; + public final static int FLAG_STYLE_STRIKE = 8; + public final static int FLAG_STYLE_UNDERLINE = 16; + public final static int FLAG_STYLE_QUOTE = 32; + public final static int FLAG_STYLE_MENTION = 64; + public final static int FLAG_STYLE_URL = 128; + + public TextStyleSpan(TextStyleRun run) { + this(run, 0, 0); + } + + public TextStyleSpan(TextStyleRun run, int size) { + this(run, size, 0); + } + + public TextStyleSpan(TextStyleRun run, int size, int textColor) { + style = run; + if (size > 0) { + textSize = size; + } + color = textColor; + } + + public int getStyleFlags() { + return style.flags; + } + + public TextStyleRun getTextStyleRun() { + return style; + } + + public Typeface getTypeface() { + return style.getTypeface(); + } + + public void setColor(int value) { + color = value; + } + + public boolean isMono() { + return style.getTypeface() == Typeface.MONOSPACE; + } + + public boolean isBold() { + return style.getTypeface() == AndroidUtilities.getTypeface("fonts/rmedium.ttf"); + } + + public boolean isItalic() { + return style.getTypeface() == AndroidUtilities.getTypeface("fonts/ritalic.ttf"); + } + + public boolean isBoldItalic() { + return style.getTypeface() == AndroidUtilities.getTypeface("fonts/rmediumitalic.ttf"); + } + + @Override + public void updateMeasureState(TextPaint p) { + if (textSize != 0) { + p.setTextSize(textSize); + } + p.setFlags(p.getFlags() | Paint.SUBPIXEL_TEXT_FLAG); + style.applyStyle(p); + } + + @Override + public void updateDrawState(TextPaint p) { + if (textSize != 0) { + p.setTextSize(textSize); + } + if (color != 0) { + p.setColor(color); + } + p.setFlags(p.getFlags() | Paint.SUBPIXEL_TEXT_FLAG); + style.applyStyle(p); + } +} 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 5cbbfbb9a..63e119ca1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeEditorView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeEditorView.java @@ -1015,7 +1015,7 @@ public class ThemeEditorView { } }); animatorSet.start(); - listAdapter.notifyItemChanged(currentThemeDesriptionPosition); + listView.getAdapter().notifyItemChanged(currentThemeDesriptionPosition); } } @@ -1149,12 +1149,13 @@ public class ThemeEditorView { ArrayList names = new ArrayList<>(); for (int a = 0, N = listAdapter.items.size(); a < N; a++) { ArrayList themeDescriptions = listAdapter.items.get(a); - String name = themeDescriptions.get(0).getCurrentKey().toLowerCase(); + String key = themeDescriptions.get(0).getCurrentKey(); + String name = key.toLowerCase(); int found = 0; for (String q : search) { if (name.contains(q)) { searchResults.add(themeDescriptions); - names.add(generateSearchName(name, q)); + names.add(generateSearchName(key, q)); break; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanBotCommand.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanBotCommand.java index dfc9f6c0f..fe2732de2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanBotCommand.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanBotCommand.java @@ -16,22 +16,32 @@ public class URLSpanBotCommand extends URLSpanNoUnderline { public static boolean enabled = true; public int currentType; + private TextStyleSpan.TextStyleRun style; public URLSpanBotCommand(String url, int type) { + this(url, type, null); + } + + public URLSpanBotCommand(String url, int type, TextStyleSpan.TextStyleRun run) { super(url); currentType = type; + style = run; } @Override - public void updateDrawState(TextPaint ds) { - super.updateDrawState(ds); + public void updateDrawState(TextPaint p) { + super.updateDrawState(p); if (currentType == 2) { - ds.setColor(0xffffffff); + p.setColor(0xffffffff); } else if (currentType == 1) { - ds.setColor(Theme.getColor(enabled ? Theme.key_chat_messageLinkOut : Theme.key_chat_messageTextOut)); + p.setColor(Theme.getColor(enabled ? Theme.key_chat_messageLinkOut : Theme.key_chat_messageTextOut)); } else { - ds.setColor(Theme.getColor(enabled ? Theme.key_chat_messageLinkIn : Theme.key_chat_messageTextIn)); + p.setColor(Theme.getColor(enabled ? Theme.key_chat_messageLinkIn : Theme.key_chat_messageTextIn)); + } + if (style != null) { + style.applyStyle(p); + } else { + p.setUnderlineText(false); } - ds.setUnderlineText(false); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanBrowser.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanBrowser.java index 517bf9879..e244d83e1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanBrowser.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanBrowser.java @@ -9,6 +9,7 @@ package org.telegram.ui.Components; import android.net.Uri; +import android.text.TextPaint; import android.text.style.URLSpan; import android.view.View; @@ -16,8 +17,19 @@ import org.telegram.messenger.browser.Browser; public class URLSpanBrowser extends URLSpan { + private TextStyleSpan.TextStyleRun style; + public URLSpanBrowser(String url) { + this(url, null); + } + + public URLSpanBrowser(String url, TextStyleSpan.TextStyleRun run) { super(url); + style = run; + } + + public TextStyleSpan.TextStyleRun getStyle() { + return style; } @Override @@ -25,4 +37,13 @@ public class URLSpanBrowser extends URLSpan { Uri uri = Uri.parse(getURL()); Browser.openUrl(widget.getContext(), uri); } + + @Override + public void updateDrawState(TextPaint p) { + super.updateDrawState(p); + if (style != null) { + style.applyStyle(p); + } + p.setUnderlineText(true); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanMono.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanMono.java index fbb78d104..e9b181627 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanMono.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanMono.java @@ -23,12 +23,18 @@ public class URLSpanMono extends MetricAffectingSpan { private int currentStart; private int currentEnd; private byte currentType; + private TextStyleSpan.TextStyleRun style; public URLSpanMono(CharSequence message, int start, int end, byte type) { + this(message, start, end, type, null); + } + + public URLSpanMono(CharSequence message, int start, int end, byte type, TextStyleSpan.TextStyleRun run) { currentMessage = message; currentStart = start; currentEnd = end; currentType = type; + style = run; } public void copyToClipboard() { @@ -37,22 +43,30 @@ public class URLSpanMono extends MetricAffectingSpan { @Override public void updateMeasureState(TextPaint p) { - p.setTypeface(Typeface.MONOSPACE); p.setTextSize(AndroidUtilities.dp(SharedConfig.fontSize - 1)); p.setFlags(p.getFlags() | Paint.SUBPIXEL_TEXT_FLAG); + if (style != null) { + style.applyStyle(p); + } else { + p.setTypeface(Typeface.MONOSPACE); + } } @Override - public void updateDrawState(TextPaint ds) { - ds.setTextSize(AndroidUtilities.dp(SharedConfig.fontSize - 1)); - ds.setTypeface(Typeface.MONOSPACE); - ds.setUnderlineText(false); + public void updateDrawState(TextPaint p) { + p.setTextSize(AndroidUtilities.dp(SharedConfig.fontSize - 1)); if (currentType == 2) { - ds.setColor(0xffffffff); + p.setColor(0xffffffff); } else if (currentType == 1) { - ds.setColor(Theme.getColor(Theme.key_chat_messageTextOut)); + p.setColor(Theme.getColor(Theme.key_chat_messageTextOut)); } else { - ds.setColor(Theme.getColor(Theme.key_chat_messageTextIn)); + p.setColor(Theme.getColor(Theme.key_chat_messageTextIn)); + } + if (style != null) { + style.applyStyle(p); + } else { + p.setTypeface(Typeface.MONOSPACE); + p.setUnderlineText(false); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanNoUnderline.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanNoUnderline.java index 672a616e1..a22b8345c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanNoUnderline.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanNoUnderline.java @@ -17,8 +17,15 @@ import org.telegram.messenger.browser.Browser; public class URLSpanNoUnderline extends URLSpan { + private TextStyleSpan.TextStyleRun style; + public URLSpanNoUnderline(String url) { + this(url, null); + } + + public URLSpanNoUnderline(String url, TextStyleSpan.TextStyleRun run) { super(url); + style = run; } @Override @@ -33,8 +40,12 @@ public class URLSpanNoUnderline extends URLSpan { } @Override - public void updateDrawState(TextPaint ds) { - super.updateDrawState(ds); - ds.setUnderlineText(false); + public void updateDrawState(TextPaint p) { + super.updateDrawState(p); + if (style != null) { + style.applyStyle(p); + } else { + p.setUnderlineText(false); + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanReplacement.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanReplacement.java index 93eb012c0..074c75442 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanReplacement.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanReplacement.java @@ -9,6 +9,7 @@ package org.telegram.ui.Components; import android.net.Uri; +import android.text.TextPaint; import android.text.style.URLSpan; import android.view.View; @@ -16,8 +17,19 @@ import org.telegram.messenger.browser.Browser; public class URLSpanReplacement extends URLSpan { + private TextStyleSpan.TextStyleRun style; + public URLSpanReplacement(String url) { + this(url, null); + } + + public URLSpanReplacement(String url, TextStyleSpan.TextStyleRun run) { super(url); + style = run; + } + + public TextStyleSpan.TextStyleRun getTextStyleRun() { + return style; } @Override @@ -25,4 +37,12 @@ public class URLSpanReplacement extends URLSpan { Uri uri = Uri.parse(getURL()); Browser.openUrl(widget.getContext(), uri); } + + @Override + public void updateDrawState(TextPaint p) { + super.updateDrawState(p); + if (style != null) { + style.applyStyle(p); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanUserMention.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanUserMention.java index 9207cee63..723e2efa5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanUserMention.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanUserMention.java @@ -16,10 +16,16 @@ import org.telegram.ui.ActionBar.Theme; public class URLSpanUserMention extends URLSpanNoUnderline { private int currentType; + private TextStyleSpan.TextStyleRun style; public URLSpanUserMention(String url, int type) { + this(url, type, null); + } + + public URLSpanUserMention(String url, int type, TextStyleSpan.TextStyleRun run) { super(url); currentType = type; + style = run; } @Override @@ -28,16 +34,19 @@ public class URLSpanUserMention extends URLSpanNoUnderline { } @Override - public void updateDrawState(TextPaint ds) { - super.updateDrawState(ds); + public void updateDrawState(TextPaint p) { + super.updateDrawState(p); if (currentType == 2) { - ds.setColor(0xffffffff); + p.setColor(0xffffffff); } else if (currentType == 1) { - ds.setColor(Theme.getColor(Theme.key_chat_messageLinkOut)); + p.setColor(Theme.getColor(Theme.key_chat_messageLinkOut)); } else { - ds.setColor(Theme.getColor(Theme.key_chat_messageLinkIn)); + p.setColor(Theme.getColor(Theme.key_chat_messageLinkIn)); + } + if (style != null) { + style.applyStyle(p); + } else { + p.setUnderlineText(false); } - - ds.setUnderlineText(false); } } 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 2f18a131c..d699bd1ee 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/UndoView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/UndoView.java @@ -23,18 +23,13 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import com.airbnb.lottie.LottieAnimationView; -import com.airbnb.lottie.LottieProperty; -import com.airbnb.lottie.SimpleColorFilter; -import com.airbnb.lottie.model.KeyPath; -import com.airbnb.lottie.value.LottieValueCallback; - 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; import org.telegram.messenger.UserConfig; +import org.telegram.messenger.UserObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; @@ -45,7 +40,7 @@ public class UndoView extends FrameLayout { private TextView subinfoTextView; private TextView undoTextView; private ImageView undoImageView; - private LottieAnimationView leftImageView; + private RLottieImageView leftImageView; private LinearLayout undoButton; private int currentAccount = UserConfig.selectedAccount; @@ -66,6 +61,8 @@ public class UndoView extends FrameLayout { private long lastUpdateTime; + private float additionalTranslationY; + private boolean isShowed; public final static int ACTION_CLEAR = 0; @@ -76,6 +73,9 @@ public class UndoView extends FrameLayout { public final static int ACTION_ARCHIVE_FEW_HINT = 5; public final static int ACTION_ARCHIVE_HIDDEN = 6; public final static int ACTION_ARCHIVE_PINNED = 7; + public final static int ACTION_CONTACT_ADDED = 8; + public final static int ACTION_OWNER_TRANSFERED_CHANNEL = 9; + public final static int ACTION_OWNER_TRANSFERED_GROUP = 10; public UndoView(Context context) { super(context); @@ -92,23 +92,23 @@ public class UndoView extends FrameLayout { subinfoTextView.setEllipsize(TextUtils.TruncateAt.END); addView(subinfoTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 58, 27, 8, 0)); - leftImageView = new LottieAnimationView(context); + leftImageView = new RLottieImageView(context); leftImageView.setScaleType(ImageView.ScaleType.CENTER); - leftImageView.addValueCallback(new KeyPath("info1", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_undo_background) | 0xff000000))); - leftImageView.addValueCallback(new KeyPath("info2", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_undo_background) | 0xff000000))); - leftImageView.addValueCallback(new KeyPath("luc12", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_undo_infoColor)))); - leftImageView.addValueCallback(new KeyPath("luc11", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_undo_infoColor)))); - leftImageView.addValueCallback(new KeyPath("luc10", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_undo_infoColor)))); - leftImageView.addValueCallback(new KeyPath("luc9", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_undo_infoColor)))); - leftImageView.addValueCallback(new KeyPath("luc8", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_undo_infoColor)))); - leftImageView.addValueCallback(new KeyPath("luc7", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_undo_infoColor)))); - leftImageView.addValueCallback(new KeyPath("luc6", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_undo_infoColor)))); - leftImageView.addValueCallback(new KeyPath("luc5", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_undo_infoColor)))); - leftImageView.addValueCallback(new KeyPath("luc4", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_undo_infoColor)))); - leftImageView.addValueCallback(new KeyPath("luc3", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_undo_infoColor)))); - leftImageView.addValueCallback(new KeyPath("luc2", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_undo_infoColor)))); - leftImageView.addValueCallback(new KeyPath("luc1", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_undo_infoColor)))); - leftImageView.addValueCallback(new KeyPath("Oval", "**"), LottieProperty.COLOR_FILTER, new LottieValueCallback<>(new SimpleColorFilter(Theme.getColor(Theme.key_undo_infoColor)))); + leftImageView.setLayerColor("info1.**", Theme.getColor(Theme.key_undo_background) | 0xff000000); + leftImageView.setLayerColor("info2.**", Theme.getColor(Theme.key_undo_background) | 0xff000000); + leftImageView.setLayerColor("luc12.**", Theme.getColor(Theme.key_undo_infoColor)); + leftImageView.setLayerColor("luc11.**", Theme.getColor(Theme.key_undo_infoColor)); + leftImageView.setLayerColor("luc10.**", Theme.getColor(Theme.key_undo_infoColor)); + leftImageView.setLayerColor("luc9.**", Theme.getColor(Theme.key_undo_infoColor)); + leftImageView.setLayerColor("luc8.**", Theme.getColor(Theme.key_undo_infoColor)); + leftImageView.setLayerColor("luc7.**", Theme.getColor(Theme.key_undo_infoColor)); + leftImageView.setLayerColor("luc6.**", Theme.getColor(Theme.key_undo_infoColor)); + leftImageView.setLayerColor("luc5.**", Theme.getColor(Theme.key_undo_infoColor)); + leftImageView.setLayerColor("luc4.**", Theme.getColor(Theme.key_undo_infoColor)); + leftImageView.setLayerColor("luc3.**", Theme.getColor(Theme.key_undo_infoColor)); + leftImageView.setLayerColor("luc2.**", Theme.getColor(Theme.key_undo_infoColor)); + leftImageView.setLayerColor("luc1.**", Theme.getColor(Theme.key_undo_infoColor)); + leftImageView.setLayerColor("Oval.**", Theme.getColor(Theme.key_undo_infoColor)); addView(leftImageView, LayoutHelper.createFrame(54, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL | Gravity.LEFT, 3, 0, 0, 0)); undoButton = new LinearLayout(context); @@ -154,9 +154,19 @@ public class UndoView extends FrameLayout { } private boolean isTooltipAction() { + return currentAction == ACTION_ARCHIVE_HIDDEN || currentAction == ACTION_ARCHIVE_HINT || currentAction == ACTION_ARCHIVE_FEW_HINT || + currentAction == ACTION_ARCHIVE_PINNED || currentAction == ACTION_CONTACT_ADDED || currentAction == ACTION_OWNER_TRANSFERED_CHANNEL || + currentAction == ACTION_OWNER_TRANSFERED_GROUP; + } + + private boolean hasSubInfo() { return currentAction == ACTION_ARCHIVE_HIDDEN || currentAction == ACTION_ARCHIVE_HINT || currentAction == ACTION_ARCHIVE_FEW_HINT || currentAction == ACTION_ARCHIVE_PINNED; } + public void setAdditionalTranslationY(float value) { + additionalTranslationY = value; + } + public void hide(boolean apply, int animated) { if (getVisibility() != VISIBLE || !isShowed) { return; @@ -180,7 +190,7 @@ public class UndoView extends FrameLayout { if (animated != 0) { AnimatorSet animatorSet = new AnimatorSet(); if (animated == 1) { - animatorSet.playTogether(ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, AndroidUtilities.dp(8 + (isTooltipAction() ? 52 : 48)))); + animatorSet.playTogether(ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, AndroidUtilities.dp(8 + (hasSubInfo() ? 52 : 48)))); animatorSet.setDuration(250); } else { animatorSet.playTogether( @@ -201,16 +211,24 @@ public class UndoView extends FrameLayout { }); animatorSet.start(); } else { - setTranslationY(AndroidUtilities.dp(8 + (isTooltipAction() ? 52 : 48))); + setTranslationY(AndroidUtilities.dp(8 + (hasSubInfo() ? 52 : 48))); setVisibility(INVISIBLE); } } public void showWithAction(long did, int action, Runnable actionRunnable) { - showWithAction(did, action, actionRunnable, null); + showWithAction(did, action, null, actionRunnable, null); + } + + public void showWithAction(long did, int action, Object infoObject) { + showWithAction(did, action, infoObject, null, null); } public void showWithAction(long did, int action, Runnable actionRunnable, Runnable cancelRunnable) { + showWithAction(did, action, null, actionRunnable, cancelRunnable); + } + + public void showWithAction(long did, int action, Object infoObject, Runnable actionRunnable, Runnable cancelRunnable) { if (currentActionRunnable != null) { currentActionRunnable.run(); } @@ -223,33 +241,64 @@ public class UndoView extends FrameLayout { lastUpdateTime = SystemClock.uptimeMillis(); if (isTooltipAction()) { - if (action == ACTION_ARCHIVE_HIDDEN) { - infoTextView.setText(LocaleController.getString("ArchiveHidden", R.string.ArchiveHidden)); - subinfoTextView.setText(LocaleController.getString("ArchiveHiddenInfo", R.string.ArchiveHiddenInfo)); - leftImageView.setAnimation(R.raw.chats_swipearchive); + CharSequence infoText; + String subInfoText; + int icon; + int size = 36; + if (action == ACTION_OWNER_TRANSFERED_CHANNEL || action == ACTION_OWNER_TRANSFERED_GROUP) { + TLRPC.User user = (TLRPC.User) infoObject; + if (action == ACTION_OWNER_TRANSFERED_CHANNEL) { + infoText = AndroidUtilities.replaceTags(LocaleController.formatString("EditAdminTransferChannelToast", R.string.EditAdminTransferChannelToast, UserObject.getFirstName(user))); + } else { + infoText = AndroidUtilities.replaceTags(LocaleController.formatString("EditAdminTransferGroupToast", R.string.EditAdminTransferGroupToast, UserObject.getFirstName(user))); + } + subInfoText = null; + icon = R.raw.contact_check; + } else if (action == ACTION_CONTACT_ADDED) { + TLRPC.User user = (TLRPC.User) infoObject; + infoText = LocaleController.formatString("NowInContacts", R.string.NowInContacts, UserObject.getFirstName(user)); + subInfoText = null; + icon = R.raw.contact_check; + } else if (action == ACTION_ARCHIVE_HIDDEN) { + infoText = LocaleController.getString("ArchiveHidden", R.string.ArchiveHidden); + subInfoText = LocaleController.getString("ArchiveHiddenInfo", R.string.ArchiveHiddenInfo); + icon = R.raw.chats_swipearchive; + size = 48; } else if (action == ACTION_ARCHIVE_PINNED) { - infoTextView.setText(LocaleController.getString("ArchivePinned", R.string.ArchivePinned)); - subinfoTextView.setText(LocaleController.getString("ArchivePinnedInfo", R.string.ArchivePinnedInfo)); - leftImageView.setAnimation(R.raw.chats_infotip); + infoText = LocaleController.getString("ArchivePinned", R.string.ArchivePinned); + subInfoText = LocaleController.getString("ArchivePinnedInfo", R.string.ArchivePinnedInfo); + icon = R.raw.chats_infotip; } else { if (action == ACTION_ARCHIVE_HINT) { - infoTextView.setText(LocaleController.getString("ChatArchived", R.string.ChatArchived)); + infoText = LocaleController.getString("ChatArchived", R.string.ChatArchived); } else { - infoTextView.setText(LocaleController.getString("ChatsArchived", R.string.ChatsArchived)); + infoText = LocaleController.getString("ChatsArchived", R.string.ChatsArchived); } - subinfoTextView.setText(LocaleController.getString("ChatArchivedInfo", R.string.ChatArchivedInfo)); - leftImageView.setAnimation(R.raw.chats_infotip); + subInfoText = LocaleController.getString("ChatArchivedInfo", R.string.ChatArchivedInfo); + icon = R.raw.chats_infotip; } - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) infoTextView.getLayoutParams(); - layoutParams.leftMargin = AndroidUtilities.dp(58); - layoutParams.topMargin = AndroidUtilities.dp(6); + infoTextView.setText(infoText); + leftImageView.setAnimation(icon, size, size); + + if (subInfoText != null) { + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) infoTextView.getLayoutParams(); + layoutParams.leftMargin = AndroidUtilities.dp(58); + layoutParams.topMargin = AndroidUtilities.dp(6); + subinfoTextView.setText(subInfoText); + subinfoTextView.setVisibility(VISIBLE); + infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + infoTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + } else { + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) infoTextView.getLayoutParams(); + layoutParams.leftMargin = AndroidUtilities.dp(58); + layoutParams.topMargin = AndroidUtilities.dp(13); + subinfoTextView.setVisibility(GONE); + infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + infoTextView.setTypeface(Typeface.DEFAULT); + } - infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - infoTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - subinfoTextView.setVisibility(VISIBLE); undoButton.setVisibility(GONE); - leftImageView.setVisibility(VISIBLE); leftImageView.setProgress(0); @@ -271,7 +320,7 @@ public class UndoView extends FrameLayout { subinfoTextView.setVisibility(GONE); leftImageView.setVisibility(VISIBLE); - leftImageView.setAnimation(R.raw.chats_archived); + leftImageView.setAnimation(R.raw.chats_archived, 36, 36); leftImageView.setProgress(0); leftImageView.playAnimation(); } else { @@ -307,9 +356,9 @@ public class UndoView extends FrameLayout { if (getVisibility() != VISIBLE) { setVisibility(VISIBLE); - setTranslationY(AndroidUtilities.dp(8 + (isTooltipAction() ? 52 : 48))); + setTranslationY(AndroidUtilities.dp(8 + (hasSubInfo() ? 52 : 48))); AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, AndroidUtilities.dp(8 + (isTooltipAction() ? 52 : 48)), 0)); + animatorSet.playTogether(ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, AndroidUtilities.dp(8 + (hasSubInfo() ? 52 : 48)), -additionalTranslationY)); animatorSet.setInterpolator(new DecelerateInterpolator()); animatorSet.setDuration(180); animatorSet.start(); @@ -322,7 +371,7 @@ public class UndoView extends FrameLayout { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(isTooltipAction() ? 52 : 48), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(hasSubInfo() ? 52 : 48), MeasureSpec.EXACTLY)); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/VoIPHelper.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/VoIPHelper.java index 9b69b1ea0..0d3f5af01 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/VoIPHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/VoIPHelper.java @@ -22,6 +22,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import org.telegram.messenger.AccountInstance; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.BuildVars; @@ -53,14 +54,14 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Set; -public class VoIPHelper{ +public class VoIPHelper { - public static long lastCallTime=0; + public static long lastCallTime = 0; - private static final int VOIP_SUPPORT_ID=4244000; + private static final int VOIP_SUPPORT_ID = 4244000; - public static void startCall(TLRPC.User user, final Activity activity, TLRPC.UserFull userFull){ - if(userFull!=null && userFull.phone_calls_private){ + public static void startCall(TLRPC.User user, final Activity activity, TLRPC.UserFull userFull) { + if (userFull != null && userFull.phone_calls_private) { new AlertDialog.Builder(activity) .setTitle(LocaleController.getString("VoipFailed", R.string.VoipFailed)) .setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("CallNotAvailable", R.string.CallNotAvailable, @@ -97,7 +98,7 @@ public class VoIPHelper{ } private static void initiateCall(final TLRPC.User user, final Activity activity) { - if (activity == null || user==null) { + if (activity == null || user == null) { return; } if (VoIPService.getSharedInstance() != null) { @@ -128,18 +129,18 @@ public class VoIPHelper{ } else { activity.startActivity(new Intent(activity, VoIPActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } - } else if(VoIPService.callIShouldHavePutIntoIntent==null) { + } else if (VoIPService.callIShouldHavePutIntoIntent == null) { doInitiateCall(user, activity); } } private static void doInitiateCall(TLRPC.User user, Activity activity) { - if (activity == null || user==null) { + if (activity == null || user == null) { return; } - if(System.currentTimeMillis()-lastCallTime<2000) + if (System.currentTimeMillis() - lastCallTime < 2000) return; - lastCallTime=System.currentTimeMillis(); + lastCallTime = System.currentTimeMillis(); Intent intent = new Intent(activity, VoIPService.class); intent.putExtra("user_id", user.id); intent.putExtra("is_outgoing", true); @@ -153,49 +154,49 @@ public class VoIPHelper{ } @TargetApi(Build.VERSION_CODES.M) - public static void permissionDenied(final Activity activity, final Runnable onFinish){ - if(!activity.shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO)){ - AlertDialog dlg=new AlertDialog.Builder(activity) + public static void permissionDenied(final Activity activity, final Runnable onFinish) { + if (!activity.shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO)) { + AlertDialog dlg = new AlertDialog.Builder(activity) .setTitle(LocaleController.getString("AppName", R.string.AppName)) .setMessage(LocaleController.getString("VoipNeedMicPermission", R.string.VoipNeedMicPermission)) .setPositiveButton(LocaleController.getString("OK", R.string.OK), null) - .setNegativeButton(LocaleController.getString("Settings", R.string.Settings), new DialogInterface.OnClickListener(){ + .setNegativeButton(LocaleController.getString("Settings", R.string.Settings), new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int which){ - Intent intent=new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - Uri uri=Uri.fromParts("package", activity.getPackageName(), null); + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + Uri uri = Uri.fromParts("package", activity.getPackageName(), null); intent.setData(uri); activity.startActivity(intent); } }) .show(); - dlg.setOnDismissListener(new DialogInterface.OnDismissListener(){ + dlg.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override - public void onDismiss(DialogInterface dialog){ - if(onFinish!=null) + public void onDismiss(DialogInterface dialog) { + if (onFinish != null) onFinish.run(); } }); } } - public static File getLogsDir(){ + public static File getLogsDir() { //File logsDir=new File(ApplicationLoader.applicationContext.getExternalCacheDir(), "voip_logs"); - File logsDir=new File(ApplicationLoader.applicationContext.getCacheDir(), "voip_logs"); - if(!logsDir.exists()) + File logsDir = new File(ApplicationLoader.applicationContext.getCacheDir(), "voip_logs"); + if (!logsDir.exists()) logsDir.mkdirs(); return logsDir; } - public static boolean canRateCall(TLRPC.TL_messageActionPhoneCall call){ - if(!(call.reason instanceof TLRPC.TL_phoneCallDiscardReasonBusy) && !(call.reason instanceof TLRPC.TL_phoneCallDiscardReasonMissed)){ - SharedPreferences prefs=MessagesController.getNotificationsSettings(UserConfig.selectedAccount); // always called from chat UI - Set hashes=prefs.getStringSet("calls_access_hashes", (Set)Collections.EMPTY_SET); - for(String hash:hashes){ - String[] d=hash.split(" "); - if(d.length<2) + public static boolean canRateCall(TLRPC.TL_messageActionPhoneCall call) { + if (!(call.reason instanceof TLRPC.TL_phoneCallDiscardReasonBusy) && !(call.reason instanceof TLRPC.TL_phoneCallDiscardReasonMissed)) { + SharedPreferences prefs = MessagesController.getNotificationsSettings(UserConfig.selectedAccount); // always called from chat UI + Set hashes = prefs.getStringSet("calls_access_hashes", (Set) Collections.EMPTY_SET); + for (String hash : hashes) { + String[] d = hash.split(" "); + if (d.length < 2) continue; - if(d[0].equals(call.call_id+"")){ + if (d[0].equals(call.call_id + "")) { return true; } } @@ -203,27 +204,28 @@ public class VoIPHelper{ return false; } - public static void showRateAlert(Context context, TLRPC.TL_messageActionPhoneCall call){ - SharedPreferences prefs=MessagesController.getNotificationsSettings(UserConfig.selectedAccount); // always called from chat UI - Set hashes=prefs.getStringSet("calls_access_hashes", (Set)Collections.EMPTY_SET); - for(String hash:hashes){ - String[] d=hash.split(" "); - if(d.length<2) + public static void showRateAlert(Context context, TLRPC.TL_messageActionPhoneCall call) { + SharedPreferences prefs = MessagesController.getNotificationsSettings(UserConfig.selectedAccount); // always called from chat UI + Set hashes = prefs.getStringSet("calls_access_hashes", (Set) Collections.EMPTY_SET); + for (String hash : hashes) { + String[] d = hash.split(" "); + if (d.length < 2) continue; - if(d[0].equals(call.call_id+"")){ - try{ - long accessHash=Long.parseLong(d[1]); + if (d[0].equals(call.call_id + "")) { + try { + long accessHash = Long.parseLong(d[1]); showRateAlert(context, null, call.call_id, accessHash, UserConfig.selectedAccount, true); - }catch(Exception x){} + } catch (Exception x) { + } return; } } } - public static void showRateAlert(final Context context, final Runnable onDismiss, final long callID, final long accessHash, final int account, final boolean userInitiative){ - final File log=getLogFile(callID); - final int[] page={0}; - LinearLayout alertView=new LinearLayout(context); + public static void showRateAlert(final Context context, final Runnable onDismiss, final long callID, final long accessHash, final int account, final boolean userInitiative) { + final File log = getLogFile(callID); + final int[] page = {0}; + LinearLayout alertView = new LinearLayout(context); alertView.setOrientation(LinearLayout.VERTICAL); int pad = AndroidUtilities.dp(16); @@ -239,44 +241,44 @@ public class VoIPHelper{ final BetterRatingView bar = new BetterRatingView(context); alertView.addView(bar, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 0, 16, 0, 0)); - final LinearLayout problemsWrap=new LinearLayout(context); + final LinearLayout problemsWrap = new LinearLayout(context); problemsWrap.setOrientation(LinearLayout.VERTICAL); - View.OnClickListener problemCheckboxClickListener=new View.OnClickListener(){ + View.OnClickListener problemCheckboxClickListener = new View.OnClickListener() { @Override - public void onClick(View v){ - CheckBoxCell check=(CheckBoxCell)v; + public void onClick(View v) { + CheckBoxCell check = (CheckBoxCell) v; check.setChecked(!check.isChecked(), true); } }; - final String[] problems={"echo", "noise", "interruptions", "distorted_speech", "silent_local", "silent_remote", "dropped"}; - for(int i=0;i=4 || page[0]==1){ - final int currentAccount=UserConfig.selectedAccount; - final TLRPC.TL_phone_setCallRating req=new TLRPC.TL_phone_setCallRating(); - req.rating=bar.getRating(); - ArrayList problemTags=new ArrayList<>(); - for(int i=0;i= 4 || page[0] == 1) { + final int currentAccount = UserConfig.selectedAccount; + final TLRPC.TL_phone_setCallRating req = new TLRPC.TL_phone_setCallRating(); + req.rating = bar.getRating(); + ArrayList problemTags = new ArrayList<>(); + for (int i = 0; i < problemsWrap.getChildCount(); i++) { + CheckBoxCell check = (CheckBoxCell) problemsWrap.getChildAt(i); + if (check.isChecked()) + problemTags.add("#" + check.getTag()); } - if(req.rating<5) - req.comment=commentBox.getText().toString(); + if (req.rating < 5) + req.comment = commentBox.getText().toString(); else - req.comment=""; - if(!problemTags.isEmpty() && !includeLogs[0]){ - req.comment+=" "+TextUtils.join(" ", problemTags); + req.comment = ""; + if (!problemTags.isEmpty() && !includeLogs[0]) { + req.comment += " " + TextUtils.join(" ", problemTags); } - req.peer=new TLRPC.TL_inputPhoneCall(); - req.peer.access_hash=accessHash; - req.peer.id=callID; - req.user_initiative=userInitiative; - ConnectionsManager.getInstance(account).sendRequest(req, new RequestDelegate(){ + req.peer = new TLRPC.TL_inputPhoneCall(); + req.peer.access_hash = accessHash; + req.peer.id = callID; + req.user_initiative = userInitiative; + ConnectionsManager.getInstance(account).sendRequest(req, new RequestDelegate() { @Override - public void run(TLObject response, TLRPC.TL_error error){ - if(response instanceof TLRPC.TL_updates){ - TLRPC.TL_updates updates=(TLRPC.TL_updates) response; + public void run(TLObject response, TLRPC.TL_error error) { + if (response instanceof TLRPC.TL_updates) { + TLRPC.TL_updates updates = (TLRPC.TL_updates) response; MessagesController.getInstance(currentAccount).processUpdates(updates, false); } - if(includeLogs[0] && log.exists() && req.rating<4){ - SendMessagesHelper.prepareSendingDocument(log.getAbsolutePath(), log.getAbsolutePath(), null, TextUtils.join(" ", problemTags), "text/plain", VOIP_SUPPORT_ID, null, null, null); + if (includeLogs[0] && log.exists() && req.rating < 4) { + AccountInstance accountInstance = AccountInstance.getInstance(UserConfig.selectedAccount); + SendMessagesHelper.prepareSendingDocument(accountInstance, log.getAbsolutePath(), log.getAbsolutePath(), null, TextUtils.join(" ", problemTags), "text/plain", VOIP_SUPPORT_ID, null, null, null); Toast.makeText(context, LocaleController.getString("CallReportSent", R.string.CallReportSent), Toast.LENGTH_LONG).show(); } } }); alert.dismiss(); - }else{ - page[0]=1; + } else { + page[0] = 1; bar.setVisibility(View.GONE); //text.setText(LocaleController.getString("CallReportHint", R.string.CallReportHint)); text.setVisibility(View.GONE); alert.setTitle(LocaleController.getString("CallReportHint", R.string.CallReportHint)); commentBox.setVisibility(View.VISIBLE); - if(log.exists()){ + if (log.exists()) { checkbox.setVisibility(View.VISIBLE); logsText.setVisibility(View.VISIBLE); } problemsWrap.setVisibility(View.VISIBLE); - ((TextView)btn).setText(LocaleController.getString("Send", R.string.Send).toUpperCase()); + ((TextView) btn).setText(LocaleController.getString("Send", R.string.Send).toUpperCase()); } } }); } - private static File getLogFile(long callID){ - if(BuildVars.DEBUG_VERSION){ - File debugLogsDir=new File(ApplicationLoader.applicationContext.getExternalFilesDir(null), "logs"); - String[] logs=debugLogsDir.list(); - if(logs!=null){ - for(String log : logs){ - if(log.endsWith("voip"+callID+".txt")){ + private static File getLogFile(long callID) { + if (BuildVars.DEBUG_VERSION) { + File debugLogsDir = new File(ApplicationLoader.applicationContext.getExternalFilesDir(null), "logs"); + String[] logs = debugLogsDir.list(); + if (logs != null) { + for (String log : logs) { + if (log.endsWith("voip" + callID + ".txt")) { return new File(debugLogsDir, log); } } } } - return new File(getLogsDir(), callID+".log"); + return new File(getLogsDir(), callID + ".log"); } - public static void showCallDebugSettings(final Context context){ + public static void showCallDebugSettings(final Context context) { final SharedPreferences preferences = MessagesController.getGlobalMainSettings(); - LinearLayout ll=new LinearLayout(context); + LinearLayout ll = new LinearLayout(context); ll.setOrientation(LinearLayout.VERTICAL); - TextView warning=new TextView(context); + TextView warning = new TextView(context); warning.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); warning.setText("Please only change these settings if you know exactly what they do."); warning.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); ll.addView(warning, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 16, 8, 16, 8)); - final TextCheckCell tcpCell=new TextCheckCell(context); + final TextCheckCell tcpCell = new TextCheckCell(context); tcpCell.setTextAndCheck("Force TCP", preferences.getBoolean("dbg_force_tcp_in_calls", false), false); - tcpCell.setOnClickListener(new View.OnClickListener(){ + tcpCell.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v){ - boolean force= preferences.getBoolean("dbg_force_tcp_in_calls", false); + public void onClick(View v) { + boolean force = preferences.getBoolean("dbg_force_tcp_in_calls", false); SharedPreferences.Editor editor = preferences.edit(); editor.putBoolean("dbg_force_tcp_in_calls", !force); editor.commit(); @@ -471,13 +474,13 @@ public class VoIPHelper{ }); ll.addView(tcpCell); - if(BuildVars.DEBUG_VERSION && BuildVars.LOGS_ENABLED){ - final TextCheckCell dumpCell=new TextCheckCell(context); + if (BuildVars.DEBUG_VERSION && BuildVars.LOGS_ENABLED) { + final TextCheckCell dumpCell = new TextCheckCell(context); dumpCell.setTextAndCheck("Dump detailed stats", preferences.getBoolean("dbg_dump_call_stats", false), false); - dumpCell.setOnClickListener(new View.OnClickListener(){ + dumpCell.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v){ - boolean force= preferences.getBoolean("dbg_dump_call_stats", false); + public void onClick(View v) { + boolean force = preferences.getBoolean("dbg_dump_call_stats", false); SharedPreferences.Editor editor = preferences.edit(); editor.putBoolean("dbg_dump_call_stats", !force); editor.commit(); @@ -487,13 +490,13 @@ public class VoIPHelper{ ll.addView(dumpCell); } - if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){ - final TextCheckCell connectionServiceCell=new TextCheckCell(context); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + final TextCheckCell connectionServiceCell = new TextCheckCell(context); connectionServiceCell.setTextAndCheck("Enable ConnectionService", preferences.getBoolean("dbg_force_connection_service", false), false); - connectionServiceCell.setOnClickListener(new View.OnClickListener(){ + connectionServiceCell.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v){ - boolean force= preferences.getBoolean("dbg_force_connection_service", false); + public void onClick(View v) { + boolean force = preferences.getBoolean("dbg_force_connection_service", false); SharedPreferences.Editor editor = preferences.edit(); editor.putBoolean("dbg_force_connection_service", !force); editor.commit(); @@ -509,21 +512,21 @@ public class VoIPHelper{ .show(); } - public static int getDataSavingDefault(){ - boolean low=DownloadController.getInstance(0).lowPreset.lessCallData, - medium=DownloadController.getInstance(0).mediumPreset.lessCallData, - high=DownloadController.getInstance(0).highPreset.lessCallData; - if(!low && !medium && !high){ + public static int getDataSavingDefault() { + boolean low = DownloadController.getInstance(0).lowPreset.lessCallData, + medium = DownloadController.getInstance(0).mediumPreset.lessCallData, + high = DownloadController.getInstance(0).highPreset.lessCallData; + if (!low && !medium && !high) { return VoIPController.DATA_SAVING_NEVER; - }else if(low && !medium && !high){ + } else if (low && !medium && !high) { return VoIPController.DATA_SAVING_ROAMING; - }else if(low && medium && !high){ + } else if (low && medium && !high) { return VoIPController.DATA_SAVING_MOBILE; - }else if(low && medium && high){ + } else if (low && medium && high) { return VoIPController.DATA_SAVING_ALWAYS; } - if(BuildVars.LOGS_ENABLED) - FileLog.w("Invalid call data saving preset configuration: "+low+"/"+medium+"/"+high); + if (BuildVars.LOGS_ENABLED) + FileLog.w("Invalid call data saving preset configuration: " + low + "/" + medium + "/" + high); return VoIPController.DATA_SAVING_NEVER; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java index 4decaa9e3..4175f8866 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java @@ -25,9 +25,10 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.PhoneFormat.PhoneFormat; -import org.telegram.messenger.ContactsController; +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; @@ -36,6 +37,7 @@ import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; 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; @@ -51,31 +53,41 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent private TextView nameTextView; private TextView onlineTextView; private AvatarDrawable avatarDrawable; + private TextView infoTextView; + private CheckBoxCell checkBoxCell; private int user_id; private boolean addContact; - private String phone = null; + private boolean needAddException; + private String phone; + + private ContactAddActivityDelegate delegate; private final static int done_button = 1; + public interface ContactAddActivityDelegate { + void didAddToContacts(); + } + public ContactAddActivity(Bundle args) { super(args); } @Override public boolean onFragmentCreate() { - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.updateInterfaces); + getNotificationCenter().addObserver(this, NotificationCenter.updateInterfaces); user_id = getArguments().getInt("user_id", 0); phone = getArguments().getString("phone"); addContact = getArguments().getBoolean("addContact", false); - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(user_id); + needAddException = MessagesController.getNotificationsSettings(currentAccount).getBoolean("dialog_bar_exception" + user_id, false); + TLRPC.User user = getMessagesController().getUser(user_id); return user != null && super.onFragmentCreate(); } @Override public void onFragmentDestroy() { super.onFragmentDestroy(); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.updateInterfaces); + getNotificationCenter().removeObserver(this, NotificationCenter.updateInterfaces); } @Override @@ -83,7 +95,7 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setAllowOverlayTitle(true); if (addContact) { - actionBar.setTitle(LocaleController.getString("AddContactTitle", R.string.AddContactTitle)); + actionBar.setTitle(LocaleController.getString("NewContact", R.string.NewContact)); } else { actionBar.setTitle(LocaleController.getString("EditName", R.string.EditName)); } @@ -94,22 +106,25 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent finishFragment(); } else if (id == done_button) { if (firstNameField.getText().length() != 0) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(user_id); + TLRPC.User user = getMessagesController().getUser(user_id); user.first_name = firstNameField.getText().toString(); user.last_name = lastNameField.getText().toString(); - ContactsController.getInstance(currentAccount).addContact(user); - finishFragment(); + getContactsController().addContact(user, checkBoxCell != null && checkBoxCell.isChecked()); SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); - preferences.edit().putInt("spam3_" + user_id, 1).commit(); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, MessagesController.UPDATE_MASK_NAME); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.peerSettingsDidLoad, (long) user_id); + preferences.edit().putInt("dialog_bar_vis3" + user_id, 3).commit(); + getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, MessagesController.UPDATE_MASK_NAME); + getNotificationCenter().postNotificationName(NotificationCenter.peerSettingsDidLoad, (long) user_id); + finishFragment(); + if (delegate != null) { + delegate.didAddToContacts(); + } } } } }); ActionBarMenu menu = actionBar.createMenu(); - doneButton = menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); + doneButton = menu.addItem(done_button, LocaleController.getString("Done", R.string.Done).toUpperCase()); fragmentView = new ScrollView(context); @@ -170,6 +185,16 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent } return false; }); + firstNameField.setOnFocusChangeListener(new View.OnFocusChangeListener() { + boolean focued; + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!paused && !hasFocus && focued) { + FileLog.d("changed"); + } + focued = hasFocus; + } + }); lastNameField = new EditTextBoldCursor(context); lastNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); @@ -195,7 +220,7 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent return false; }); - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(user_id); + TLRPC.User user = getMessagesController().getUser(user_id); if (user != null) { if (user.phone == null) { if (phone != null) { @@ -207,18 +232,49 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent lastNameField.setText(user.last_name); } + infoTextView = new TextView(context); + infoTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4)); + infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + infoTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + if (addContact) { + if (!needAddException || TextUtils.isEmpty(user.phone)) { + linearLayout.addView(infoTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 24, 18, 24, 0)); + } + + if (needAddException) { + checkBoxCell = new CheckBoxCell(getParentActivity(), 0); + checkBoxCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + checkBoxCell.setText(LocaleController.formatString("SharePhoneNumberWith", R.string.SharePhoneNumberWith, UserObject.getFirstName(user)), "", true, false); + checkBoxCell.setPadding(AndroidUtilities.dp(7), 0, AndroidUtilities.dp(7), 0); + checkBoxCell.setOnClickListener(v -> checkBoxCell.setChecked(!checkBoxCell.isChecked(), true)); + linearLayout.addView(checkBoxCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 10, 0, 0)); + } + } + return fragmentView; } + public void setDelegate(ContactAddActivityDelegate contactAddActivityDelegate) { + delegate = contactAddActivityDelegate; + } + private void updateAvatarLayout() { if (nameTextView == null) { return; } - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(user_id); + TLRPC.User user = getMessagesController().getUser(user_id); if (user == null) { return; } - nameTextView.setText(PhoneFormat.getInstance().format("+" + user.phone)); + if (TextUtils.isEmpty(user.phone)) { + 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)); + if (needAddException) { + infoTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("MobileVisibleInfo", R.string.MobileVisibleInfo, UserObject.getFirstName(user)))); + } + } onlineTextView.setText(LocaleController.formatUserStatus(currentAccount, user)); avatarImage.setImage(ImageLocation.getForUser(user, false), "50_50", avatarDrawable = new AvatarDrawable(user), user); } @@ -232,15 +288,24 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent } } + boolean paused; + @Override + public void onPause() { + super.onPause(); + paused = true; + } + @Override public void onResume() { super.onResume(); updateAvatarLayout(); - SharedPreferences preferences = MessagesController.getGlobalMainSettings(); - boolean animations = preferences.getBoolean("view_animations", true); - if (!animations) { + if (firstNameField != null) { firstNameField.requestFocus(); - AndroidUtilities.showKeyboard(firstNameField); + SharedPreferences preferences = MessagesController.getGlobalMainSettings(); + boolean animations = preferences.getBoolean("view_animations", true); + if (!animations) { + AndroidUtilities.showKeyboard(firstNameField); + } } } @@ -256,7 +321,7 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent public ThemeDescription[] getThemeDescriptions() { ThemeDescription.ThemeDescriptionDelegate cellDelegate = () -> { if (avatarImage != null) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(user_id); + TLRPC.User user = getMessagesController().getUser(user_id); if (user == null) { return; } @@ -284,6 +349,8 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent new ThemeDescription(lastNameField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), new ThemeDescription(lastNameField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(infoTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, cellDelegate, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundOrange), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java index de1f2b398..d5a27fc22 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java @@ -26,9 +26,11 @@ import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; +import android.location.LocationManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.provider.Settings; import android.text.Editable; import android.text.InputType; import android.text.TextUtils; @@ -48,6 +50,7 @@ import android.widget.ImageView; import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.BuildVars; import org.telegram.messenger.ChatObject; import org.telegram.messenger.LocaleController; @@ -107,6 +110,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter private boolean scrollUpdated; private boolean floatingHidden; + private boolean hasGps; private boolean searchWas; private boolean searching; private boolean onlyUsers; @@ -118,6 +122,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter private boolean allowBots = true; private boolean needForwardCount = true; private boolean needFinishFragment = true; + private boolean resetDelegate = true; private int channelId; private int chatId; private String selectAlertString = null; @@ -128,6 +133,8 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter private AlertDialog permissionDialog; private boolean askAboutContacts = true; + private boolean disableSections; + private boolean checkPermission = true; private final static int search_button = 0; @@ -150,7 +157,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.closeChats); checkPermission = UserConfig.getInstance(currentAccount).syncContacts; if (arguments != null) { - onlyUsers = getArguments().getBoolean("onlyUsers", false); + onlyUsers = arguments.getBoolean("onlyUsers", false); destroyAfterSelect = arguments.getBoolean("destroyAfterSelect", false); returnAsResult = arguments.getBoolean("returnAsResult", false); createSecretChat = arguments.getBoolean("createSecretChat", false); @@ -161,6 +168,8 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter channelId = arguments.getInt("channelId", 0); needFinishFragment = arguments.getBoolean("needFinishFragment", true); chatId = arguments.getInt("chat_id", 0); + disableSections = arguments.getBoolean("disableSections", false); + resetDelegate = arguments.getBoolean("resetDelegate", false); } else { needPhonebook = true; } @@ -285,7 +294,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter sortItem.setContentDescription(LocaleController.getString("AccDescrContactSorting", R.string.AccDescrContactSorting)); } - searchListViewAdapter = new SearchAdapter(context, ignoreUsers, allowUsernameSearch, false, false, allowBots, 0); + searchListViewAdapter = new SearchAdapter(context, ignoreUsers, allowUsernameSearch, false, false, allowBots, true, 0); int inviteViaLink; if (chatId != 0) { TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(chatId); @@ -296,7 +305,12 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter } else { inviteViaLink = 0; } - listViewAdapter = new ContactsAdapter(context, onlyUsers ? 1 : 0, needPhonebook, ignoreUsers, inviteViaLink) { + try { + hasGps = ApplicationLoader.applicationContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS); + } catch (Throwable e) { + hasGps = false; + } + listViewAdapter = new ContactsAdapter(context, onlyUsers ? 1 : 0, needPhonebook, ignoreUsers, inviteViaLink, hasGps) { @Override public void notifyDataSetChanged() { super.notifyDataSetChanged(); @@ -313,6 +327,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter } }; listViewAdapter.setSortType(sortItem != null ? (sortByName ? 1 : 2) : 0); + listViewAdapter.setDisableSections(disableSections); fragmentView = new FrameLayout(context) { @Override @@ -335,7 +350,15 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter emptyView.showTextView(); frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - listView = new RecyclerListView(context); + listView = new RecyclerListView(context) { + @Override + public void setPadding(int left, int top, int right, int bottom) { + super.setPadding(left, top, right, bottom); + if (emptyView != null) { + emptyView.setPadding(left, top, right, bottom); + } + } + }; listView.setSectionsType(1); listView.setVerticalScrollBarEnabled(false); listView.setFastScrollEnabled(); @@ -345,35 +368,45 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter listView.setOnItemClickListener((view, position) -> { if (searching && searchWas) { - TLRPC.User user = (TLRPC.User) searchListViewAdapter.getItem(position); - if (user == null) { - return; - } - if (searchListViewAdapter.isGlobalSearch(position)) { - ArrayList users = new ArrayList<>(); - users.add(user); - MessagesController.getInstance(currentAccount).putUsers(users, false); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(users, null, false, true); - } - if (returnAsResult) { - if (ignoreUsers != null && ignoreUsers.indexOfKey(user.id) >= 0) { + Object object = searchListViewAdapter.getItem(position); + if (object instanceof TLRPC.User) { + TLRPC.User user = (TLRPC.User) object; + if (user == null) { return; } - didSelectResult(user, true, null); - } else { - if (createSecretChat) { - if (user.id == UserConfig.getInstance(currentAccount).getClientUserId()) { + if (searchListViewAdapter.isGlobalSearch(position)) { + ArrayList users = new ArrayList<>(); + users.add(user); + MessagesController.getInstance(currentAccount).putUsers(users, false); + MessagesStorage.getInstance(currentAccount).putUsersAndChats(users, null, false, true); + } + if (returnAsResult) { + if (ignoreUsers != null && ignoreUsers.indexOfKey(user.id) >= 0) { return; } - creatingChat = true; - SecretChatHelper.getInstance(currentAccount).startSecretChat(getParentActivity(), user); + didSelectResult(user, true, null); } else { - Bundle args = new Bundle(); - args.putInt("user_id", user.id); - if (MessagesController.getInstance(currentAccount).checkCanOpenChat(args, ContactsActivity.this)) { - presentFragment(new ChatActivity(args), true); + if (createSecretChat) { + if (user.id == UserConfig.getInstance(currentAccount).getClientUserId()) { + return; + } + creatingChat = true; + SecretChatHelper.getInstance(currentAccount).startSecretChat(getParentActivity(), user); + } else { + Bundle args = new Bundle(); + args.putInt("user_id", user.id); + if (MessagesController.getInstance(currentAccount).checkCanOpenChat(args, ContactsActivity.this)) { + presentFragment(new ChatActivity(args), true); + } } } + } else if (object instanceof String) { + String str = (String) object; + if (!str.equals("section")) { + NewContactActivity activity = new NewContactActivity(); + activity.setInitialPhoneNumber(str); + presentFragment(activity); + } } } else { int section = listViewAdapter.getSectionForPosition(position); @@ -385,6 +418,33 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter if (needPhonebook) { if (row == 0) { presentFragment(new InviteContactsActivity()); + } else if (row == 1 && hasGps) { + if (Build.VERSION.SDK_INT >= 23) { + Activity activity = getParentActivity(); + if (activity != null) { + if (activity.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + presentFragment(new ActionIntroActivity(ActionIntroActivity.ACTION_TYPE_NEARBY_LOCATION_ACCESS)); + return; + } + } + } + boolean enabled = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + LocationManager lm = (LocationManager) ApplicationLoader.applicationContext.getSystemService(Context.LOCATION_SERVICE); + enabled = lm.isLocationEnabled(); + } else if (Build.VERSION.SDK_INT >= 19) { + try { + int mode = Settings.Secure.getInt(ApplicationLoader.applicationContext.getContentResolver(), Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); + enabled = (mode != Settings.Secure.LOCATION_MODE_OFF); + } catch (Throwable e) { + FileLog.e(e); + } + } + if (!enabled) { + presentFragment(new ActionIntroActivity(ActionIntroActivity.ACTION_TYPE_NEARBY_LOCATION_ENABLED)); + return; + } + presentFragment(new PeopleNearbyActivity()); } } else if (inviteViaLink != 0) { if (row == 0) { @@ -408,7 +468,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter args.putInt("step", 0); presentFragment(new ChannelCreateActivity(args)); } else { - presentFragment(new ChannelIntroActivity()); + presentFragment(new ActionIntroActivity(ActionIntroActivity.ACTION_TYPE_CHANNEL_CREATE)); preferences.edit().putBoolean("channel_intro", true).commit(); } } @@ -511,7 +571,6 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter }); if (!createSecretChat && !returnAsResult) { - floatingButtonContainer = new FrameLayout(context); frameLayout.addView(floatingButtonContainer, LayoutHelper.createFrame((Build.VERSION.SDK_INT >= 21 ? 56 : 60) + 20, (Build.VERSION.SDK_INT >= 21 ? 56 : 60) + 14, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 4 : 0, 0, LocaleController.isRTL ? 0 : 4, 0)); floatingButtonContainer.setOnClickListener(v -> presentFragment(new NewContactActivity())); @@ -655,7 +714,9 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter } else { if (delegate != null) { delegate.didSelectContact(user, param, this); - delegate = null; + if (resetDelegate) { + delegate = null; + } } if (needFinishFragment) { finishFragment(); @@ -691,6 +752,10 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter } } + protected RecyclerListView getListView() { + return listView; + } + @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -774,7 +839,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter listViewAdapter.notifyDataSetChanged(); } } else if (id == NotificationCenter.updateInterfaces) { - int mask = (Integer)args[0]; + int mask = (Integer) args[0]; if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0 || (mask & MessagesController.UPDATE_MASK_NAME) != 0 || (mask & MessagesController.UPDATE_MASK_STATUS) != 0) { updateVisibleRows(mask); } @@ -783,7 +848,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter } } else if (id == NotificationCenter.encryptedChatCreated) { if (createSecretChat && creatingChat) { - TLRPC.EncryptedChat encryptedChat = (TLRPC.EncryptedChat)args[0]; + TLRPC.EncryptedChat encryptedChat = (TLRPC.EncryptedChat) args[0]; Bundle args2 = new Bundle(); args2.putInt("enc_id", encryptedChat.id); NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.closeChats); @@ -880,7 +945,8 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundBlue), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundPink), - new ThemeDescription(listView, 0, new Class[]{TextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{TextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{TextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueText2), new ThemeDescription(listView, 0, new Class[]{TextCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayIcon), new ThemeDescription(floatingButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chats_actionIcon), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ContentPreviewViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/ContentPreviewViewer.java index 23259ff2f..8fa4ec554 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ContentPreviewViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ContentPreviewViewer.java @@ -32,7 +32,7 @@ import android.view.WindowManager; import android.widget.FrameLayout; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.Emoji; import org.telegram.messenger.FileLoader; import org.telegram.messenger.ImageLocation; @@ -41,6 +41,7 @@ import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; 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; import org.telegram.messenger.WebFile; @@ -131,10 +132,7 @@ public class ContentPreviewViewer { return; } if (currentContentType == CONTENT_TYPE_STICKER) { - if (currentStickerSet == null) { - return; - } - final boolean inFavs = DataQuery.getInstance(currentAccount).isStickerInFavorites(currentDocument); + final boolean inFavs = MediaDataController.getInstance(currentAccount).isStickerInFavorites(currentDocument); BottomSheet.Builder builder = new BottomSheet.Builder(parentActivity); ArrayList items = new ArrayList<>(); final ArrayList actions = new ArrayList<>(); @@ -145,13 +143,13 @@ public class ContentPreviewViewer { icons.add(R.drawable.outline_send); actions.add(0); } - if (delegate.needOpen()) { + if (currentStickerSet != null && delegate.needOpen()) { items.add(LocaleController.formatString("ViewPackPreview", R.string.ViewPackPreview)); icons.add(R.drawable.outline_pack); actions.add(1); } } - if (!MessageObject.isMaskDocument(currentDocument) && (inFavs || DataQuery.getInstance(currentAccount).canAddStickerToFavorites())) { + if (!MessageObject.isMaskDocument(currentDocument) && (inFavs || MediaDataController.getInstance(currentAccount).canAddStickerToFavorites())) { items.add(inFavs ? LocaleController.getString("DeleteFromFavorites", R.string.DeleteFromFavorites) : LocaleController.getString("AddToFavorites", R.string.AddToFavorites)); icons.add(inFavs ? R.drawable.outline_unfave : R.drawable.outline_fave); actions.add(2); @@ -169,14 +167,14 @@ public class ContentPreviewViewer { } if (actions.get(which) == 0) { if (delegate != null) { - delegate.sendSticker(currentDocument, currentStickerSet); + delegate.sendSticker(currentDocument, parentObject); } } else if (actions.get(which) == 1) { if (delegate != null) { delegate.openSet(currentStickerSet, clearsInputField); } } else if (actions.get(which) == 2) { - DataQuery.getInstance(currentAccount).addRecentSticker(DataQuery.TYPE_FAVE, currentStickerSet, currentDocument, (int) (System.currentTimeMillis() / 1000), inFavs); + MediaDataController.getInstance(currentAccount).addRecentSticker(MediaDataController.TYPE_FAVE, parentObject, currentDocument, (int) (System.currentTimeMillis() / 1000), inFavs); } }); builder.setDimBehind(false); @@ -219,7 +217,7 @@ public class ContentPreviewViewer { boolean canDelete; if (currentDocument != null) { - if (canDelete = DataQuery.getInstance(currentAccount).hasRecentGif(currentDocument)) { + if (canDelete = MediaDataController.getInstance(currentAccount).hasRecentGif(currentDocument)) { items.add(LocaleController.formatString("Delete", R.string.Delete)); icons.add(R.drawable.chats_delete); actions.add(1); @@ -245,10 +243,10 @@ public class ContentPreviewViewer { delegate.sendGif(currentDocument != null ? currentDocument : inlineResult); } } else if (actions.get(which) == 1) { - DataQuery.getInstance(currentAccount).removeRecentGif(currentDocument); + MediaDataController.getInstance(currentAccount).removeRecentGif(currentDocument); delegate.gifAddedOrDeleted(); } else if (actions.get(which) == 2) { - DataQuery.getInstance(currentAccount).addRecentGif(currentDocument, (int) (System.currentTimeMillis() / 1000)); + MediaDataController.getInstance(currentAccount).addRecentGif(currentDocument, (int) (System.currentTimeMillis() / 1000)); MessagesController.getInstance(currentAccount).saveGif("gif", currentDocument); delegate.gifAddedOrDeleted(); } @@ -271,6 +269,7 @@ public class ContentPreviewViewer { private TLRPC.Document currentDocument; private TLRPC.BotInlineResult inlineResult; private TLRPC.InputStickerSet currentStickerSet; + private Object parentObject; @SuppressLint("StaticFieldLeak") private static volatile ContentPreviewViewer Instance = null; @@ -412,16 +411,16 @@ public class ContentPreviewViewer { clearsInputField = false; if (currentPreviewCell instanceof StickerEmojiCell) { StickerEmojiCell stickerEmojiCell = (StickerEmojiCell) currentPreviewCell; - open(stickerEmojiCell.getSticker(), null, contentType, ((StickerEmojiCell) currentPreviewCell).isRecent()); + open(stickerEmojiCell.getSticker(), null, contentType, stickerEmojiCell.isRecent(), stickerEmojiCell.getParentObject()); stickerEmojiCell.setScaled(true); } else if (currentPreviewCell instanceof StickerCell) { StickerCell stickerCell = (StickerCell) currentPreviewCell; - open(stickerCell.getSticker(), null, contentType, false); + open(stickerCell.getSticker(), null, contentType, false, stickerCell.getParentObject()); stickerCell.setScaled(true); clearsInputField = stickerCell.isClearsInputField(); } else if (currentPreviewCell instanceof ContextLinkCell) { ContextLinkCell contextLinkCell = (ContextLinkCell) currentPreviewCell; - open(contextLinkCell.getDocument(), contextLinkCell.getBotInlineResult(), contentType, false); + open(contextLinkCell.getDocument(), contextLinkCell.getBotInlineResult(), contentType, false, null); if (contentType != CONTENT_TYPE_GIF) { contextLinkCell.setScaled(true); } @@ -509,16 +508,16 @@ public class ContentPreviewViewer { clearsInputField = false; if (currentPreviewCell instanceof StickerEmojiCell) { StickerEmojiCell stickerEmojiCell = (StickerEmojiCell) currentPreviewCell; - open(stickerEmojiCell.getSticker(), null, contentTypeFinal, ((StickerEmojiCell) currentPreviewCell).isRecent()); + open(stickerEmojiCell.getSticker(), null, contentTypeFinal, stickerEmojiCell.isRecent(), stickerEmojiCell.getParentObject()); stickerEmojiCell.setScaled(true); } else if (currentPreviewCell instanceof StickerCell) { StickerCell stickerCell = (StickerCell) currentPreviewCell; - open(stickerCell.getSticker(), null, contentTypeFinal, false); + open(stickerCell.getSticker(), null, contentTypeFinal, false, stickerCell.getParentObject()); stickerCell.setScaled(true); clearsInputField = stickerCell.isClearsInputField(); } else if (currentPreviewCell instanceof ContextLinkCell) { ContextLinkCell contextLinkCell = (ContextLinkCell) currentPreviewCell; - open(contextLinkCell.getDocument(), contextLinkCell.getBotInlineResult(), contentTypeFinal, false); + open(contextLinkCell.getDocument(), contextLinkCell.getBotInlineResult(), contentTypeFinal, false, null); if (contentTypeFinal != CONTENT_TYPE_GIF) { contextLinkCell.setScaled(true); } @@ -538,6 +537,7 @@ public class ContentPreviewViewer { public void setParentActivity(Activity activity) { currentAccount = UserConfig.selectedAccount; centerImage.setCurrentAccount(currentAccount); + centerImage.setLayerNum(7); if (parentActivity == activity) { return; } @@ -586,7 +586,7 @@ public class ContentPreviewViewer { keyboardHeight = height; } - public void open(TLRPC.Document document, TLRPC.BotInlineResult botInlineResult, int contentType, boolean isRecent) { + public void open(TLRPC.Document document, TLRPC.BotInlineResult botInlineResult, int contentType, boolean isRecent, Object parent) { if (parentActivity == null || windowView == null) { return; } @@ -622,6 +622,7 @@ public class ContentPreviewViewer { AndroidUtilities.runOnUIThread(showSheetRunnable, 1300); } currentStickerSet = newSet; + parentObject = parent; TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(document.thumbs, 90); centerImage.setImage(ImageLocation.getForDocument(document), null, ImageLocation.getForDocument(thumb, document), null, "webp", currentStickerSet, 1); for (int a = 0; a < document.attributes.size(); a++) { @@ -675,6 +676,7 @@ public class ContentPreviewViewer { currentMoveY = 0; moveY = 0; lastUpdateTime = System.currentTimeMillis(); + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.stopAllHeavyOperations, 4); } } @@ -702,6 +704,7 @@ public class ContentPreviewViewer { currentStickerSet = null; delegate = null; isVisible = false; + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.startAllHeavyOperations, 4); } public void destroy() { @@ -730,6 +733,7 @@ public class ContentPreviewViewer { FileLog.e(e); } Instance = null; + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.startAllHeavyOperations, 4); } private float rubberYPoisition(float offset, float factor) { @@ -764,22 +768,19 @@ public class ContentPreviewViewer { } canvas.translate(containerView.getWidth() / 2, moveY + Math.max(size / 2 + top + (stickerEmojiLayout != null ? AndroidUtilities.dp(40) : 0), (containerView.getHeight() - insets - keyboardHeight) / 2)); - Bitmap bitmap = centerImage.getBitmap(); - if (bitmap != null) { - float scale = 0.8f * showProgress / 0.8f; - size = (int) (size * scale); - centerImage.setAlpha(showProgress); - centerImage.setImageCoords(-size / 2, -size / 2, size, size); - centerImage.draw(canvas); + float scale = 0.8f * showProgress / 0.8f; + size = (int) (size * scale); + centerImage.setAlpha(showProgress); + centerImage.setImageCoords(-size / 2, -size / 2, size, size); + centerImage.draw(canvas); - if (currentContentType == CONTENT_TYPE_GIF && slideUpDrawable != null) { - int w = slideUpDrawable.getIntrinsicWidth(); - int h = slideUpDrawable.getIntrinsicHeight(); - int y = (int) (centerImage.getDrawRegion().top - AndroidUtilities.dp(17 + 6 * (currentMoveY / (float) AndroidUtilities.dp(60)))); - slideUpDrawable.setAlpha((int) (255 * (1.0f - currentMoveYProgress))); - slideUpDrawable.setBounds(-w / 2, -h + y, w / 2, y); - slideUpDrawable.draw(canvas); - } + if (currentContentType == CONTENT_TYPE_GIF && slideUpDrawable != null) { + int w = slideUpDrawable.getIntrinsicWidth(); + int h = slideUpDrawable.getIntrinsicHeight(); + int y = (int) (centerImage.getDrawRegion().top - AndroidUtilities.dp(17 + 6 * (currentMoveY / (float) AndroidUtilities.dp(60)))); + slideUpDrawable.setAlpha((int) (255 * (1.0f - currentMoveYProgress))); + slideUpDrawable.setBounds(-w / 2, -h + y, w / 2, y); + slideUpDrawable.draw(canvas); } if (stickerEmojiLayout != null) { canvas.translate(-AndroidUtilities.dp(50), -centerImage.getImageHeight() / 2 - AndroidUtilities.dp(30)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DialogOrContactPickerActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DialogOrContactPickerActivity.java new file mode 100644 index 000000000..bd7c7f6e1 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/DialogOrContactPickerActivity.java @@ -0,0 +1,674 @@ +package org.telegram.ui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.animation.Interpolator; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.R; +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.AlertDialog; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Components.AlertsCreator; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; +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 class ViewPage extends FrameLayout { + private BaseFragment parentFragment; + private FrameLayout fragmentView; + private ActionBar actionBar; + private RecyclerListView listView; + private int selectedType; + + public ViewPage(Context context) { + super(context); + } + } + + private DialogsActivity dialogsActivity; + private ContactsActivity contactsActivity; + private ActionBarMenuItem searchItem; + + private final static int search_button = 0; + + private Paint backgroundPaint = new Paint(); + private ScrollSlidingTextTabStrip scrollSlidingTextTabStrip; + private ViewPage[] viewPages = new ViewPage[2]; + private AnimatorSet tabsAnimation; + private boolean tabsAnimationInProgress; + private boolean animatingForward; + private boolean backAnimation; + private int maximumVelocity; + private static final Interpolator interpolator = t -> { + --t; + return t * t * t * t * t + 1.0F; + }; + + public DialogOrContactPickerActivity() { + super(); + + Bundle args = new Bundle(); + args.putBoolean("onlySelect", true); + args.putBoolean("checkCanWrite", false); + args.putBoolean("resetDelegate", false); + args.putInt("dialogsType", 4); + dialogsActivity = new DialogsActivity(args); + dialogsActivity.setDelegate((fragment, dids, message, param) -> { + if (dids.isEmpty()) { + return; + } + long did = dids.get(0); + int lowerId = (int) did; + if (did <= 0) { + return; + } + TLRPC.User user = getMessagesController().getUser(lowerId); + showBlockAlert(user); + }); + dialogsActivity.onFragmentCreate(); + + args = new Bundle(); + args.putBoolean("onlyUsers", true); + args.putBoolean("destroyAfterSelect", true); + args.putBoolean("returnAsResult", true); + args.putBoolean("disableSections", true); + args.putBoolean("needFinishFragment", false); + args.putBoolean("resetDelegate", false); + contactsActivity = new ContactsActivity(args); + contactsActivity.setDelegate((user, param, activity) -> showBlockAlert(user)); + contactsActivity.onFragmentCreate(); + } + + @Override + public View createView(Context context) { + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setTitle(LocaleController.getString("BlockUserMultiTitle", R.string.BlockUserMultiTitle)); + if (AndroidUtilities.isTablet()) { + actionBar.setOccupyStatusBar(false); + } + actionBar.setExtraHeight(AndroidUtilities.dp(44)); + actionBar.setAllowOverlayTitle(false); + actionBar.setAddToContainer(false); + actionBar.setClipContent(true); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } + } + }); + hasOwnBackground = true; + + ActionBarMenu menu = actionBar.createMenu(); + searchItem = menu.addItem(search_button, R.drawable.ic_ab_search).setIsSearchField(true).setActionBarMenuItemSearchListener(new ActionBarMenuItem.ActionBarMenuItemSearchListener() { + @Override + public void onSearchExpand() { + dialogsActivity.getActionBar().openSearchField("", false); + contactsActivity.getActionBar().openSearchField("", false); + searchItem.getSearchField().requestFocus(); + + } + + @Override + public void onSearchCollapse() { + dialogsActivity.getActionBar().closeSearchField(false); + contactsActivity.getActionBar().closeSearchField(false); + } + + @Override + public void onTextChanged(EditText editText) { + dialogsActivity.getActionBar().setSearchFieldText(editText.getText().toString()); + contactsActivity.getActionBar().setSearchFieldText(editText.getText().toString()); + } + }); + + scrollSlidingTextTabStrip = new ScrollSlidingTextTabStrip(context); + scrollSlidingTextTabStrip.setUseSameWidth(true); + actionBar.addView(scrollSlidingTextTabStrip, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 44, Gravity.LEFT | Gravity.BOTTOM)); + scrollSlidingTextTabStrip.setDelegate(new ScrollSlidingTextTabStrip.ScrollSlidingTabStripDelegate() { + @Override + public void onPageSelected(int id, boolean forward) { + if (viewPages[0].selectedType == id) { + return; + } + swipeBackEnabled = id == scrollSlidingTextTabStrip.getFirstTabId(); + viewPages[1].selectedType = id; + viewPages[1].setVisibility(View.VISIBLE); + switchToCurrentSelectedMode(true); + animatingForward = forward; + } + + @Override + public void onPageScrolled(float progress) { + if (progress == 1 && viewPages[1].getVisibility() != View.VISIBLE) { + return; + } + if (animatingForward) { + viewPages[0].setTranslationX(-progress * viewPages[0].getMeasuredWidth()); + viewPages[1].setTranslationX(viewPages[0].getMeasuredWidth() - progress * viewPages[0].getMeasuredWidth()); + } else { + viewPages[0].setTranslationX(progress * viewPages[0].getMeasuredWidth()); + viewPages[1].setTranslationX(progress * viewPages[0].getMeasuredWidth() - viewPages[0].getMeasuredWidth()); + } + if (progress == 1) { + ViewPage tempPage = viewPages[0]; + viewPages[0] = viewPages[1]; + viewPages[1] = tempPage; + viewPages[1].setVisibility(View.GONE); + } + } + }); + + ViewConfiguration configuration = ViewConfiguration.get(context); + maximumVelocity = configuration.getScaledMaximumFlingVelocity(); + + FrameLayout frameLayout; + fragmentView = frameLayout = new FrameLayout(context) { + + private int startedTrackingPointerId; + private boolean startedTracking; + private boolean maybeStartTracking; + private int startedTrackingX; + private int startedTrackingY; + private VelocityTracker velocityTracker; + private boolean globalIgnoreLayout; + + private boolean prepareForMoving(MotionEvent ev, boolean forward) { + int id = scrollSlidingTextTabStrip.getNextPageId(forward); + if (id < 0) { + return false; + } + getParent().requestDisallowInterceptTouchEvent(true); + maybeStartTracking = false; + startedTracking = true; + startedTrackingX = (int) ev.getX(); + actionBar.setEnabled(false); + scrollSlidingTextTabStrip.setEnabled(false); + viewPages[1].selectedType = id; + viewPages[1].setVisibility(View.VISIBLE); + animatingForward = forward; + switchToCurrentSelectedMode(true); + if (forward) { + viewPages[1].setTranslationX(viewPages[0].getMeasuredWidth()); + } else { + viewPages[1].setTranslationX(-viewPages[0].getMeasuredWidth()); + } + return true; + } + + @Override + public void forceHasOverlappingRendering(boolean hasOverlappingRendering) { + super.forceHasOverlappingRendering(hasOverlappingRendering); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + setMeasuredDimension(widthSize, heightSize); + + measureChildWithMargins(actionBar, widthMeasureSpec, 0, heightMeasureSpec, 0); + int actionBarHeight = actionBar.getMeasuredHeight(); + globalIgnoreLayout = true; + for (int a = 0; a < viewPages.length; a++) { + if (viewPages[a] == null) { + continue; + } + if (viewPages[a].listView != null) { + viewPages[a].listView.setPadding(0, actionBarHeight, 0, 0); + } + } + globalIgnoreLayout = false; + + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child == null || child.getVisibility() == GONE || child == actionBar) { + continue; + } + measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + if (parentLayout != null) { + parentLayout.drawHeaderShadow(canvas, actionBar.getMeasuredHeight() + (int) actionBar.getTranslationY()); + } + } + + @Override + public void requestLayout() { + if (globalIgnoreLayout) { + return; + } + super.requestLayout(); + } + + public boolean checkTabsAnimationInProgress() { + if (tabsAnimationInProgress) { + boolean cancel = false; + if (backAnimation) { + if (Math.abs(viewPages[0].getTranslationX()) < 1) { + viewPages[0].setTranslationX(0); + viewPages[1].setTranslationX(viewPages[0].getMeasuredWidth() * (animatingForward ? 1 : -1)); + cancel = true; + } + } else if (Math.abs(viewPages[1].getTranslationX()) < 1) { + viewPages[0].setTranslationX(viewPages[0].getMeasuredWidth() * (animatingForward ? -1 : 1)); + viewPages[1].setTranslationX(0); + cancel = true; + } + if (cancel) { + if (tabsAnimation != null) { + tabsAnimation.cancel(); + tabsAnimation = null; + } + tabsAnimationInProgress = false; + } + return tabsAnimationInProgress; + } + return false; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return checkTabsAnimationInProgress() || scrollSlidingTextTabStrip.isAnimatingIndicator() || onTouchEvent(ev); + } + + @Override + protected void onDraw(Canvas canvas) { + backgroundPaint.setColor(Theme.getColor(Theme.key_windowBackgroundGray)); + canvas.drawRect(0, actionBar.getMeasuredHeight() + actionBar.getTranslationY(), getMeasuredWidth(), getMeasuredHeight(), backgroundPaint); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (!parentLayout.checkTransitionAnimation() && !checkTabsAnimationInProgress()) { + if (ev != null && ev.getAction() == MotionEvent.ACTION_DOWN && !startedTracking && !maybeStartTracking) { + startedTrackingPointerId = ev.getPointerId(0); + maybeStartTracking = true; + startedTrackingX = (int) ev.getX(); + startedTrackingY = (int) ev.getY(); + if (velocityTracker != null) { + velocityTracker.clear(); + } + } else if (ev != null && ev.getAction() == MotionEvent.ACTION_MOVE && ev.getPointerId(0) == startedTrackingPointerId) { + if (velocityTracker == null) { + velocityTracker = VelocityTracker.obtain(); + } + int dx = (int) (ev.getX() - startedTrackingX); + int dy = Math.abs((int) ev.getY() - startedTrackingY); + velocityTracker.addMovement(ev); + if (startedTracking && (animatingForward && dx > 0 || !animatingForward && dx < 0)) { + if (!prepareForMoving(ev, dx < 0)) { + maybeStartTracking = true; + startedTracking = false; + } + } + if (maybeStartTracking && !startedTracking) { + float touchSlop = AndroidUtilities.getPixelsInCM(0.3f, true); + if (Math.abs(dx) >= touchSlop && Math.abs(dx) / 3 > dy) { + prepareForMoving(ev, dx < 0); + } + } else if (startedTracking) { + if (animatingForward) { + viewPages[0].setTranslationX(dx); + viewPages[1].setTranslationX(viewPages[0].getMeasuredWidth() + dx); + } else { + viewPages[0].setTranslationX(dx); + viewPages[1].setTranslationX(dx - viewPages[0].getMeasuredWidth()); + } + float scrollProgress = Math.abs(dx) / (float) viewPages[0].getMeasuredWidth(); + scrollSlidingTextTabStrip.selectTabWithId(viewPages[1].selectedType, scrollProgress); + } + } else if (ev != null && ev.getPointerId(0) == startedTrackingPointerId && (ev.getAction() == MotionEvent.ACTION_CANCEL || ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_POINTER_UP)) { + if (velocityTracker == null) { + velocityTracker = VelocityTracker.obtain(); + } + velocityTracker.computeCurrentVelocity(1000, maximumVelocity); + if (!startedTracking) { + float velX = velocityTracker.getXVelocity(); + float velY = velocityTracker.getYVelocity(); + if (Math.abs(velX) >= 3000 && Math.abs(velX) > Math.abs(velY)) { + prepareForMoving(ev, velX < 0); + } + } + if (startedTracking) { + float x = viewPages[0].getX(); + tabsAnimation = new AnimatorSet(); + float velX = velocityTracker.getXVelocity(); + float velY = velocityTracker.getYVelocity(); + backAnimation = Math.abs(x) < viewPages[0].getMeasuredWidth() / 3.0f && (Math.abs(velX) < 3500 || Math.abs(velX) < Math.abs(velY)); + float distToMove; + float dx; + if (backAnimation) { + dx = Math.abs(x); + if (animatingForward) { + tabsAnimation.playTogether( + ObjectAnimator.ofFloat(viewPages[0], View.TRANSLATION_X, 0), + ObjectAnimator.ofFloat(viewPages[1], View.TRANSLATION_X, viewPages[1].getMeasuredWidth()) + ); + } else { + tabsAnimation.playTogether( + ObjectAnimator.ofFloat(viewPages[0], View.TRANSLATION_X, 0), + ObjectAnimator.ofFloat(viewPages[1], View.TRANSLATION_X, -viewPages[1].getMeasuredWidth()) + ); + } + } else { + dx = viewPages[0].getMeasuredWidth() - Math.abs(x); + if (animatingForward) { + tabsAnimation.playTogether( + ObjectAnimator.ofFloat(viewPages[0], View.TRANSLATION_X, -viewPages[0].getMeasuredWidth()), + ObjectAnimator.ofFloat(viewPages[1], View.TRANSLATION_X, 0) + ); + } else { + tabsAnimation.playTogether( + ObjectAnimator.ofFloat(viewPages[0], View.TRANSLATION_X, viewPages[0].getMeasuredWidth()), + ObjectAnimator.ofFloat(viewPages[1], View.TRANSLATION_X, 0) + ); + } + } + tabsAnimation.setInterpolator(interpolator); + + int width = getMeasuredWidth(); + int halfWidth = width / 2; + float distanceRatio = Math.min(1.0f, 1.0f * dx / (float) width); + float distance = (float) halfWidth + (float) halfWidth * AndroidUtilities.distanceInfluenceForSnapDuration(distanceRatio); + velX = Math.abs(velX); + int duration; + if (velX > 0) { + duration = 4 * Math.round(1000.0f * Math.abs(distance / velX)); + } else { + float pageDelta = dx / getMeasuredWidth(); + duration = (int) ((pageDelta + 1.0f) * 100.0f); + } + duration = Math.max(150, Math.min(duration, 600)); + + tabsAnimation.setDuration(duration); + tabsAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + tabsAnimation = null; + if (backAnimation) { + viewPages[1].setVisibility(View.GONE); + } else { + ViewPage tempPage = viewPages[0]; + viewPages[0] = viewPages[1]; + viewPages[1] = tempPage; + viewPages[1].setVisibility(View.GONE); + swipeBackEnabled = viewPages[0].selectedType == scrollSlidingTextTabStrip.getFirstTabId(); + scrollSlidingTextTabStrip.selectTabWithId(viewPages[0].selectedType, 1.0f); + } + tabsAnimationInProgress = false; + maybeStartTracking = false; + startedTracking = false; + actionBar.setEnabled(true); + scrollSlidingTextTabStrip.setEnabled(true); + } + }); + tabsAnimation.start(); + tabsAnimationInProgress = true; + } else { + maybeStartTracking = false; + startedTracking = false; + actionBar.setEnabled(true); + scrollSlidingTextTabStrip.setEnabled(true); + } + if (velocityTracker != null) { + velocityTracker.recycle(); + velocityTracker = null; + } + } + return startedTracking; + } + return false; + } + }; + frameLayout.setWillNotDraw(false); + + dialogsActivity.setParentFragment(this); + contactsActivity.setParentFragment(this); + + + for (int a = 0; a < viewPages.length; a++) { + viewPages[a] = new ViewPage(context) { + @Override + public void setTranslationX(float translationX) { + super.setTranslationX(translationX); + if (tabsAnimationInProgress) { + if (viewPages[0] == this) { + float scrollProgress = Math.abs(viewPages[0].getTranslationX()) / (float) viewPages[0].getMeasuredWidth(); + scrollSlidingTextTabStrip.selectTabWithId(viewPages[1].selectedType, scrollProgress); + } + } + } + }; + frameLayout.addView(viewPages[a], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + if (a == 0) { + viewPages[a].parentFragment = dialogsActivity; + viewPages[a].listView = dialogsActivity.getListView(); + } else if (a == 1) { + viewPages[a].parentFragment = contactsActivity; + viewPages[a].listView = contactsActivity.getListView(); + viewPages[a].setVisibility(View.GONE); + } + viewPages[a].fragmentView = (FrameLayout) viewPages[a].parentFragment.getFragmentView(); + viewPages[a].listView.setClipToPadding(false); + viewPages[a].actionBar = viewPages[a].parentFragment.getActionBar(); + viewPages[a].addView(viewPages[a].fragmentView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + viewPages[a].addView(viewPages[a].actionBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + viewPages[a].actionBar.setVisibility(View.GONE); + + RecyclerView.OnScrollListener onScrollListener = viewPages[a].listView.getOnScrollListener(); + viewPages[a].listView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + onScrollListener.onScrollStateChanged(recyclerView, newState); + if (newState != RecyclerView.SCROLL_STATE_DRAGGING) { + int scrollY = (int) -actionBar.getTranslationY(); + int actionBarHeight = ActionBar.getCurrentActionBarHeight(); + if (scrollY != 0 && scrollY != actionBarHeight) { + if (scrollY < actionBarHeight / 2) { + viewPages[0].listView.smoothScrollBy(0, -scrollY); + } else { + viewPages[0].listView.smoothScrollBy(0, actionBarHeight - scrollY); + } + } + } + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + onScrollListener.onScrolled(recyclerView, dx, dy); + if (recyclerView == viewPages[0].listView) { + float currentTranslation = actionBar.getTranslationY(); + float newTranslation = currentTranslation - dy; + if (newTranslation < -ActionBar.getCurrentActionBarHeight()) { + newTranslation = -ActionBar.getCurrentActionBarHeight(); + } else if (newTranslation > 0) { + newTranslation = 0; + } + if (newTranslation != currentTranslation) { + setScrollY(newTranslation); + } + } + } + }); + } + + frameLayout.addView(actionBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + updateTabs(); + switchToCurrentSelectedMode(false); + swipeBackEnabled = scrollSlidingTextTabStrip.getCurrentTabId() == scrollSlidingTextTabStrip.getFirstTabId(); + + return fragmentView; + } + + @Override + public void onResume() { + super.onResume(); + if (dialogsActivity != null) { + dialogsActivity.onResume(); + } + if (contactsActivity != null) { + contactsActivity.onResume(); + } + } + + @Override + public void onPause() { + super.onPause(); + if (dialogsActivity != null) { + dialogsActivity.onPause(); + } + if (contactsActivity != null) { + contactsActivity.onPause(); + } + } + + @Override + public void onFragmentDestroy() { + if (dialogsActivity != null) { + dialogsActivity.onFragmentDestroy(); + } + if (contactsActivity != null) { + contactsActivity.onFragmentDestroy(); + } + super.onFragmentDestroy(); + } + + private void setScrollY(float value) { + actionBar.setTranslationY(value); + for (int a = 0; a < viewPages.length; a++) { + viewPages[a].listView.setPinnedSectionOffsetY((int) value); + } + fragmentView.invalidate(); + } + + private void showBlockAlert(TLRPC.User user) { + if (user == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("BlockUser", R.string.BlockUser)); + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("AreYouSureBlockContact2", R.string.AreYouSureBlockContact2, ContactsController.formatName(user.first_name, user.last_name)))); + builder.setPositiveButton(LocaleController.getString("BlockContact", R.string.BlockContact), (dialogInterface, i) -> { + if (MessagesController.isSupportUser(user)) { + AlertsCreator.showSimpleToast(DialogOrContactPickerActivity.this, LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred)); + } else { + MessagesController.getInstance(currentAccount).blockUser(user.id); + AlertsCreator.showSimpleToast(DialogOrContactPickerActivity.this, LocaleController.getString("UserBlocked", R.string.UserBlocked)); + } + finishFragment(); + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + AlertDialog dialog = builder.create(); + showDialog(dialog); + TextView button = (TextView) dialog.getButton(DialogInterface.BUTTON_POSITIVE); + if (button != null) { + button.setTextColor(Theme.getColor(Theme.key_dialogTextRed2)); + } + } + + private void updateTabs() { + if (scrollSlidingTextTabStrip == null) { + return; + } + scrollSlidingTextTabStrip.addTextTab(0, LocaleController.getString("BlockUserChatsTitle", R.string.BlockUserChatsTitle)); + scrollSlidingTextTabStrip.addTextTab(1, LocaleController.getString("BlockUserContactsTitle", R.string.BlockUserContactsTitle)); + scrollSlidingTextTabStrip.setVisibility(View.VISIBLE); + actionBar.setExtraHeight(AndroidUtilities.dp(44)); + int id = scrollSlidingTextTabStrip.getCurrentTabId(); + if (id >= 0) { + viewPages[0].selectedType = id; + } + scrollSlidingTextTabStrip.finishAddingTabs(); + } + + private void switchToCurrentSelectedMode(boolean animated) { + for (int a = 0; a < viewPages.length; a++) { + viewPages[a].listView.stopScroll(); + } + int a = animated ? 1 : 0; + RecyclerView.Adapter currentAdapter = viewPages[a].listView.getAdapter(); + viewPages[a].listView.setPinnedHeaderShadowDrawable(null); + if (actionBar.getTranslationY() != 0) { + LinearLayoutManager layoutManager = (LinearLayoutManager) viewPages[a].listView.getLayoutManager(); + layoutManager.scrollToPositionWithOffset(0, (int) actionBar.getTranslationY()); + } + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ArrayList arrayList = new ArrayList<>(); + + arrayList.add(new ThemeDescription(fragmentView, 0, null, null, null, null, Theme.key_windowBackgroundGray)); + arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault)); + arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon)); + arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle)); + arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector)); + + arrayList.add(new ThemeDescription(scrollSlidingTextTabStrip.getTabsContainer(), ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{TextView.class}, null, null, null, Theme.key_actionBarTabActiveText)); + arrayList.add(new ThemeDescription(scrollSlidingTextTabStrip.getTabsContainer(), ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{TextView.class}, null, null, null, Theme.key_actionBarTabUnactiveText)); + arrayList.add(new ThemeDescription(scrollSlidingTextTabStrip.getTabsContainer(), ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, new Class[]{TextView.class}, null, null, null, Theme.key_actionBarTabLine)); + arrayList.add(new ThemeDescription(null, 0, null, null, new Drawable[]{scrollSlidingTextTabStrip.getSelectorDrawable()}, null, Theme.key_actionBarTabSelector)); + + /*for (int a = 0; a < viewPages.length; a++) { TODO + arrayList.add(new ThemeDescription(viewPages[a].listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextSettingsCell.class, HeaderCell.class}, null, null, null, Theme.key_windowBackgroundWhite)); + arrayList.add(new ThemeDescription(viewPages[a].listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault)); + arrayList.add(new ThemeDescription(viewPages[a].listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector)); + + arrayList.add(new ThemeDescription(viewPages[a].listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider)); + + arrayList.add(new ThemeDescription(viewPages[a].listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow)); + + arrayList.add(new ThemeDescription(viewPages[a].listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader)); + + arrayList.add(new ThemeDescription(viewPages[a].listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow)); + arrayList.add(new ThemeDescription(viewPages[a].listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4)); + + arrayList.add(new ThemeDescription(viewPages[a].listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); + arrayList.add(new ThemeDescription(viewPages[a].listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText)); + arrayList.add(new ThemeDescription(viewPages[a].listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteRedText2)); + }*/ + + return arrayList.toArray(new ThemeDescription[0]); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java index 87278c673..c26421253 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java @@ -55,14 +55,12 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; -import com.airbnb.lottie.LottieDrawable; - import org.telegram.messenger.AccountInstance; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.BuildVars; import org.telegram.messenger.ChatObject; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.DialogObject; import org.telegram.messenger.ImageLoader; import org.telegram.messenger.ImageLocation; @@ -102,6 +100,7 @@ import org.telegram.ui.Cells.HintDialogCell; import org.telegram.ui.Cells.LoadingCell; import org.telegram.ui.Cells.ProfileSearchCell; import org.telegram.ui.Cells.ShadowSectionCell; +import org.telegram.ui.Cells.TextCell; import org.telegram.ui.Cells.UserCell; import org.telegram.ui.Cells.DialogCell; import org.telegram.ui.ActionBar.ActionBar; @@ -124,6 +123,7 @@ import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.NumberTextView; import org.telegram.ui.Components.PacmanAnimation; import org.telegram.ui.Components.ProxyDrawable; +import org.telegram.ui.Components.RLottieDrawable; import org.telegram.ui.Components.RadialProgressView; import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.ActionBar.Theme; @@ -213,6 +213,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. private String selectAlertString; private String selectAlertStringGroup; private String addToGroupAlertString; + private boolean resetDelegate = true; private int dialogsType; public static boolean[] dialogsLoaded = new boolean[UserConfig.MAX_ACCOUNT_COUNT]; @@ -509,6 +510,9 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } else { lastItemsCount++; dialogsAdapter.notifyItemInserted(0); + if (!SharedConfig.archiveHidden && layoutManager.findFirstVisibleItemPosition() == 0) { + listView.smoothScrollBy(0, -AndroidUtilities.dp(SharedConfig.useThreeLinesLayout ? 78 : 72)); + } } ArrayList dialogs = getDialogsArray(currentAccount, dialogsType, folderId, false); frozenDialogsList.add(0, dialogs.get(0)); @@ -636,6 +640,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. allowSwitchAccount = arguments.getBoolean("allowSwitchAccount"); checkCanWrite = arguments.getBoolean("checkCanWrite", true); folderId = arguments.getInt("folderId", 0); + resetDelegate = arguments.getBoolean("resetDelegate", true); } if (dialogsType == 0) { @@ -677,8 +682,8 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. getMessagesController().loadDialogs(folderId, 0, 100, true); getMessagesController().loadHintDialogs(); getContactsController().checkInviteText(); - getDataQuery().loadRecents(DataQuery.TYPE_FAVE, false, true, false); - getDataQuery().checkFeaturedStickers(); + getMediaDataController().loadRecents(MediaDataController.TYPE_FAVE, false, true, false); + getMediaDataController().checkFeaturedStickers(); dialogsLoaded[currentAccount] = true; } getMessagesController().loadPinnedDialogs(folderId, 0, null); @@ -979,6 +984,14 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } } + @Override + public void setPadding(int left, int top, int right, int bottom) { + super.setPadding(left, top, right, bottom); + if (searchEmptyView != null) { + searchEmptyView.setPadding(left, top, right, bottom); + } + } + @Override protected void onMeasure(int widthSpec, int heightSpec) { if (firstLayout && getMessagesController().dialogsLoaded) { @@ -1236,7 +1249,14 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. message_id = messageObject.getId(); dialogsSearchAdapter.addHashtagsFromMessage(dialogsSearchAdapter.getLastSearchString()); } else if (obj instanceof String) { - actionBar.openSearchField((String) obj, false); + String str = (String) obj; + if (dialogsSearchAdapter.isHashtagSearch()) { + actionBar.openSearchField(str, false); + } else if (!str.equals("section")) { + NewContactActivity activity = new NewContactActivity(); + activity.setInitialPhoneNumber(str); + presentFragment(activity); + } } } @@ -1593,6 +1613,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. int firstVisibleItem = layoutManager.findFirstVisibleItemPosition(); int visibleItemCount = Math.abs(layoutManager.findLastVisibleItemPosition() - firstVisibleItem) + 1; int totalItemCount = recyclerView.getAdapter().getItemCount(); + dialogsItemAnimator.onListScroll(-dy); if (searching && searchWas) { if (visibleItemCount > 0 && layoutManager.findLastVisibleItemPosition() == totalItemCount - 1 && !dialogsSearchAdapter.isMessagesSearchEndReached()) { @@ -1718,7 +1739,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("ChatHintsDeleteAlertTitle", R.string.ChatHintsDeleteAlertTitle)); builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("ChatHintsDeleteAlert", R.string.ChatHintsDeleteAlert, ContactsController.formatName(user.first_name, user.last_name)))); - builder.setPositiveButton(LocaleController.getString("StickersRemove", R.string.StickersRemove), (dialogInterface, i) -> getDataQuery().removePeer(did)); + builder.setPositiveButton(LocaleController.getString("StickersRemove", R.string.StickersRemove), (dialogInterface, i) -> getMediaDataController().removePeer(did)); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); AlertDialog dialog = builder.create(); showDialog(dialog); @@ -2631,6 +2652,10 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } }*/ + protected RecyclerListView getListView() { + return listView; + } + private UndoView getUndoView() { if (undoView[0].getVisibility() == View.VISIBLE) { UndoView old = undoView[0]; @@ -2841,6 +2866,9 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } else if (id == NotificationCenter.updateInterfaces) { Integer mask = (Integer) args[0]; updateVisibleRows(mask); + if ((mask & MessagesController.UPDATE_MASK_STATUS) != 0 && dialogsAdapter != null) { + dialogsAdapter.sortOnlineContacts(true); + } /*if ((mask & MessagesController.UPDATE_MASK_NEW_MESSAGE) != 0 || (mask & MessagesController.UPDATE_MASK_READ_DIALOG_MESSAGE) != 0) { checkUnreadCount(true); }*/ @@ -2917,7 +2945,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } }*/ } else if (id == NotificationCenter.needDeleteDialog) { - if (fragmentView == null) { + if (fragmentView == null || isPaused) { return; } long dialogId = (Long) args[0]; @@ -3178,7 +3206,9 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. ArrayList dids = new ArrayList<>(); dids.add(dialog_id); delegate.didSelectDialogs(DialogsActivity.this, dids, null, param); - delegate = null; + if (resetDelegate) { + delegate = null; + } } else { finishFragment(); } @@ -3215,7 +3245,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. View child = sideMenu.getChildAt(0); if (child instanceof DrawerProfileCell) { DrawerProfileCell profileCell = (DrawerProfileCell) child; - profileCell.applyBackground(); + profileCell.applyBackground(true); } } }; @@ -3325,49 +3355,29 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, null, null, null, Theme.key_chats_archiveBackground)); if (SharedConfig.archiveHidden) { - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{Theme.dialogs_archiveAvatarDrawable}, "Arrow1", Theme.key_avatar_backgroundArchivedHidden)); - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{Theme.dialogs_archiveAvatarDrawable}, "Arrow2", Theme.key_avatar_backgroundArchivedHidden)); + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_archiveAvatarDrawable}, "Arrow1", Theme.key_avatar_backgroundArchivedHidden)); + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_archiveAvatarDrawable}, "Arrow2", Theme.key_avatar_backgroundArchivedHidden)); } else { - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{Theme.dialogs_archiveAvatarDrawable}, "Arrow1", Theme.key_avatar_backgroundArchived)); - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{Theme.dialogs_archiveAvatarDrawable}, "Arrow2", Theme.key_avatar_backgroundArchived)); + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_archiveAvatarDrawable}, "Arrow1", Theme.key_avatar_backgroundArchived)); + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_archiveAvatarDrawable}, "Arrow2", Theme.key_avatar_backgroundArchived)); } - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{Theme.dialogs_archiveAvatarDrawable}, "Box2", Theme.key_avatar_text)); - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{Theme.dialogs_archiveAvatarDrawable}, "Box1", Theme.key_avatar_text)); + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_archiveAvatarDrawable}, "Box2", Theme.key_avatar_text)); + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_archiveAvatarDrawable}, "Box1", Theme.key_avatar_text)); - if (Theme.dialogs_pinArchiveDrawable instanceof LottieDrawable) { - LottieDrawable lottieDrawable = (LottieDrawable) Theme.dialogs_pinArchiveDrawable; - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{lottieDrawable}, "Arrow", Theme.key_chats_archiveIcon)); - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{lottieDrawable}, "Line", Theme.key_chats_archiveIcon)); - } else { - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, null, new Drawable[]{Theme.dialogs_pinArchiveDrawable}, null, Theme.key_chats_archiveIcon)); - } + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_pinArchiveDrawable}, "Arrow", Theme.key_chats_archiveIcon)); + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_pinArchiveDrawable}, "Line", Theme.key_chats_archiveIcon)); - if (Theme.dialogs_unpinArchiveDrawable instanceof LottieDrawable) { - LottieDrawable lottieDrawable = (LottieDrawable) Theme.dialogs_unpinArchiveDrawable; - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{lottieDrawable}, "Arrow", Theme.key_chats_archiveIcon)); - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{lottieDrawable}, "Line", Theme.key_chats_archiveIcon)); - } else { - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, null, new Drawable[]{Theme.dialogs_unpinArchiveDrawable}, null, Theme.key_chats_archiveIcon)); - } + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_unpinArchiveDrawable}, "Arrow", Theme.key_chats_archiveIcon)); + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_unpinArchiveDrawable}, "Line", Theme.key_chats_archiveIcon)); - if (Theme.dialogs_archiveDrawable instanceof LottieDrawable) { - LottieDrawable lottieDrawable = (LottieDrawable) Theme.dialogs_archiveDrawable; - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{lottieDrawable}, "Arrow", Theme.key_chats_archiveBackground)); - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{lottieDrawable}, "Box2", Theme.key_chats_archiveIcon)); - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{lottieDrawable}, "Box1", Theme.key_chats_archiveIcon)); - } else { - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, null, new Drawable[]{Theme.dialogs_archiveDrawable}, null, Theme.key_chats_archiveIcon)); - } + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_archiveDrawable}, "Arrow", Theme.key_chats_archiveBackground)); + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_archiveDrawable}, "Box2", Theme.key_chats_archiveIcon)); + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_archiveDrawable}, "Box1", Theme.key_chats_archiveIcon)); - if (Theme.dialogs_unarchiveDrawable instanceof LottieDrawable) { - LottieDrawable lottieDrawable = (LottieDrawable) Theme.dialogs_unarchiveDrawable; - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{lottieDrawable}, "Arrow1", Theme.key_chats_archiveIcon)); - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{lottieDrawable}, "Arrow2", Theme.key_chats_archivePinBackground)); - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{lottieDrawable}, "Box2", Theme.key_chats_archiveIcon)); - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new LottieDrawable[]{lottieDrawable}, "Box1", Theme.key_chats_archiveIcon)); - } else { - arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, null, new Drawable[]{Theme.dialogs_unarchiveDrawable}, null, Theme.key_chats_archiveIcon)); - } + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_unarchiveDrawable}, "Arrow1", Theme.key_chats_archiveIcon)); + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_unarchiveDrawable}, "Arrow2", Theme.key_chats_archivePinBackground)); + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_unarchiveDrawable}, "Box2", Theme.key_chats_archiveIcon)); + arrayList.add(new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, new RLottieDrawable[]{Theme.dialogs_unarchiveDrawable}, "Box1", Theme.key_chats_archiveIcon)); arrayList.add(new ThemeDescription(sideMenu, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_chats_menuBackground)); arrayList.add(new ThemeDescription(sideMenu, 0, new Class[]{DrawerProfileCell.class}, null, null, null, Theme.key_chats_menuName)); @@ -3429,6 +3439,8 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. arrayList.add(new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND | ThemeDescription.FLAG_CHECKTAG, new Class[]{FragmentContextView.class}, new String[]{"frameLayout"}, null, null, null, Theme.key_returnToCallBackground)); arrayList.add(new ThemeDescription(fragmentView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{FragmentContextView.class}, new String[]{"titleTextView"}, null, null, null, Theme.key_returnToCallText)); + arrayList.add(new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{TextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueText2)); + for (int a = 0; a < undoView.length; a++) { arrayList.add(new ThemeDescription(undoView[a], ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_undo_background)); arrayList.add(new ThemeDescription(undoView[a], 0, new Class[]{UndoView.class}, new String[]{"undoImageView"}, null, null, null, Theme.key_undo_cancelColor)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/FeaturedStickersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/FeaturedStickersActivity.java index 76ef178aa..1c625a6c8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/FeaturedStickersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/FeaturedStickersActivity.java @@ -14,7 +14,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.LocaleController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; @@ -51,10 +51,10 @@ public class FeaturedStickersActivity extends BaseFragment implements Notificati @Override public boolean onFragmentCreate() { super.onFragmentCreate(); - DataQuery.getInstance(currentAccount).checkFeaturedStickers(); + MediaDataController.getInstance(currentAccount).checkFeaturedStickers(); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.featuredStickersDidLoad); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.stickersDidLoad); - ArrayList arrayList = DataQuery.getInstance(currentAccount).getUnreadStickerSets(); + ArrayList arrayList = MediaDataController.getInstance(currentAccount).getUnreadStickerSets(); if (arrayList != null) { unreadStickers = new ArrayList<>(arrayList); } @@ -107,7 +107,7 @@ public class FeaturedStickersActivity extends BaseFragment implements Notificati listView.setAdapter(listAdapter); listView.setOnItemClickListener((view, position) -> { if (position >= stickersStartRow && position < stickersEndRow && getParentActivity() != null) { - final TLRPC.StickerSetCovered stickerSet = DataQuery.getInstance(currentAccount).getFeaturedStickerSets().get(position); + final TLRPC.StickerSetCovered stickerSet = MediaDataController.getInstance(currentAccount).getFeaturedStickerSets().get(position); TLRPC.InputStickerSet inputStickerSet; if (stickerSet.set.id != 0) { inputStickerSet = new TLRPC.TL_inputStickerSetID(); @@ -141,7 +141,7 @@ public class FeaturedStickersActivity extends BaseFragment implements Notificati public void didReceivedNotification(int id, int account, Object... args) { if (id == NotificationCenter.featuredStickersDidLoad) { if (unreadStickers == null) { - unreadStickers = DataQuery.getInstance(currentAccount).getUnreadStickerSets(); + unreadStickers = MediaDataController.getInstance(currentAccount).getUnreadStickerSets(); } updateRows(); } else if (id == NotificationCenter.stickersDidLoad) { @@ -166,7 +166,7 @@ public class FeaturedStickersActivity extends BaseFragment implements Notificati private void updateRows() { rowCount = 0; - ArrayList stickerSets = DataQuery.getInstance(currentAccount).getFeaturedStickerSets(); + ArrayList stickerSets = MediaDataController.getInstance(currentAccount).getFeaturedStickerSets(); if (!stickerSets.isEmpty()) { stickersStartRow = rowCount; stickersEndRow = rowCount + stickerSets.size(); @@ -180,7 +180,7 @@ public class FeaturedStickersActivity extends BaseFragment implements Notificati if (listAdapter != null) { listAdapter.notifyDataSetChanged(); } - DataQuery.getInstance(currentAccount).markFaturedStickersAsRead(true); + MediaDataController.getInstance(currentAccount).markFaturedStickersAsRead(true); } @Override @@ -207,7 +207,7 @@ public class FeaturedStickersActivity extends BaseFragment implements Notificati @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (getItemViewType(position) == 0) { - ArrayList arrayList = DataQuery.getInstance(currentAccount).getFeaturedStickerSets(); + ArrayList arrayList = MediaDataController.getInstance(currentAccount).getFeaturedStickerSets(); FeaturedStickerSetCell cell = (FeaturedStickerSetCell) holder.itemView; cell.setTag(position); TLRPC.StickerSetCovered stickerSet = arrayList.get(position); @@ -241,7 +241,7 @@ public class FeaturedStickersActivity extends BaseFragment implements Notificati return; } installingStickerSets.put(pack.set.id, pack); - DataQuery.getInstance(currentAccount).removeStickersSet(getParentActivity(), pack.set, 2, FeaturedStickersActivity.this, false); + MediaDataController.getInstance(currentAccount).removeStickersSet(getParentActivity(), pack.set, 2, FeaturedStickersActivity.this, false); parent1.setDrawProgress(true); }); break; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java index e3fe674bf..6ba55ba6e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java @@ -86,9 +86,6 @@ import org.telegram.ui.Components.TypefaceSpan; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; -import java.util.Timer; -import java.util.TimerTask; public class GroupCreateActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, View.OnClickListener { @@ -110,6 +107,7 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen private int chatId; private int channelId; + private TLRPC.ChatFull info; private SparseArray ignoreUsers; @@ -387,7 +385,7 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen actionBar.setTitle(LocaleController.getString("ChannelAddMembers", R.string.ChannelAddMembers)); } else { if (addToGroup) { - actionBar.setTitle(LocaleController.getString("SelectContacts", R.string.SelectContacts)); + actionBar.setTitle(LocaleController.getString("GroupAddMembers", R.string.GroupAddMembers)); } else if (isAlwaysShare) { if (isGroup) { actionBar.setTitle(LocaleController.getString("AlwaysAllow", R.string.AlwaysAllow)); @@ -620,7 +618,15 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen frameLayout.addView(listView); listView.setOnItemClickListener((view, position) -> { if (position == 0 && adapter.inviteViaLink != 0 && !adapter.searching) { - presentFragment(new GroupInviteActivity(chatId != 0 ? chatId : channelId)); + int id = chatId != 0 ? chatId : channelId; + TLRPC.Chat chat = getMessagesController().getChat(id); + if (chat != null && chat.has_geo && !TextUtils.isEmpty(chat.username)) { + ChatEditTypeActivity activity = new ChatEditTypeActivity(id, true); + activity.setInfo(info); + presentFragment(activity); + return; + } + presentFragment(new GroupInviteActivity(id)); } else if (view instanceof GroupCreateUserCell) { GroupCreateUserCell cell = (GroupCreateUserCell) view; TLObject object = cell.getObject(); @@ -654,7 +660,7 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen if (object instanceof TLRPC.User) { TLRPC.User user = (TLRPC.User) object; if (addToGroup && user.bot) { - if (user.bot_nochats) { + if (channelId == 0 && user.bot_nochats) { try { Toast.makeText(getParentActivity(), LocaleController.getString("BotCantJoinGroups", R.string.BotCantJoinGroups), Toast.LENGTH_SHORT).show(); } catch (Exception e) { @@ -799,6 +805,10 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen ignoreUsers = users; } + public void setInfo(TLRPC.ChatFull chatFull) { + info = chatFull; + } + @Keep public void setContainerHeight(int value) { containerHeight = value; @@ -852,7 +862,7 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen } private boolean onDonePressed(boolean alert) { - if (selectedContacts.size() == 0) { + if (selectedContacts.size() == 0 && chatType != ChatObject.CHAT_TYPE_CHANNEL) { return false; } if (alert && addToGroup) { @@ -1025,7 +1035,7 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen private ArrayList searchResult = new ArrayList<>(); private ArrayList searchResultNames = new ArrayList<>(); private SearchAdapterHelper searchAdapterHelper; - private Timer searchTimer; + private Runnable searchRunnable; private boolean searching; private ArrayList contacts = new ArrayList<>(); private int usersStartRow; @@ -1075,16 +1085,11 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen } searchAdapterHelper = new SearchAdapterHelper(false); - searchAdapterHelper.setDelegate(new SearchAdapterHelper.SearchAdapterHelperDelegate() { - @Override - public void onDataSetChanged() { - notifyDataSetChanged(); - } - - @Override - public void onSetHashtags(ArrayList arrayList, HashMap hashMap) { - + searchAdapterHelper.setDelegate(() -> { + if (searchRunnable == null && !searchAdapterHelper.isSearchInProgress()) { + emptyView.showTextView(); } + notifyDataSetChanged(); }); } @@ -1234,7 +1239,7 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(); spannableStringBuilder.append("@"); spannableStringBuilder.append(objectUserName); - if ((index = objectUserName.toLowerCase().indexOf(foundUserName)) != -1) { + if ((index = AndroidUtilities.indexOfIgnoreCase(objectUserName, foundUserName)) != -1) { int len = foundUserName.length(); if (index == 0) { len++; @@ -1325,108 +1330,92 @@ public class GroupCreateActivity extends BaseFragment implements NotificationCen } public void searchDialogs(final String query) { - try { - if (searchTimer != null) { - searchTimer.cancel(); - } - } catch (Exception e) { - FileLog.e(e); + if (searchRunnable != null) { + Utilities.searchQueue.cancelRunnable(searchRunnable); + searchRunnable = null; } if (query == null) { searchResult.clear(); searchResultNames.clear(); searchAdapterHelper.mergeResults(null); - searchAdapterHelper.queryServerSearch(null, true, isAlwaysShare || isNeverShare, false, false, 0, 0); + searchAdapterHelper.queryServerSearch(null, true, isAlwaysShare || isNeverShare, false, false, 0, false, 0); notifyDataSetChanged(); } else { - searchTimer = new Timer(); - searchTimer.schedule(new TimerTask() { - @Override - public void run() { - try { - searchTimer.cancel(); - searchTimer = null; - } catch (Exception e) { - FileLog.e(e); + Utilities.searchQueue.postRunnable(searchRunnable = () -> AndroidUtilities.runOnUIThread(() -> { + searchAdapterHelper.queryServerSearch(query, true, isAlwaysShare || isNeverShare, true, false, 0, false, 0); + Utilities.searchQueue.postRunnable(searchRunnable = () -> { + String search1 = query.trim().toLowerCase(); + if (search1.length() == 0) { + updateSearchResults(new ArrayList<>(), new ArrayList<>()); + return; + } + String search2 = LocaleController.getInstance().getTranslitString(search1); + if (search1.equals(search2) || search2.length() == 0) { + search2 = null; + } + String[] search = new String[1 + (search2 != null ? 1 : 0)]; + search[0] = search1; + if (search2 != null) { + search[1] = search2; } - AndroidUtilities.runOnUIThread(() -> { - searchAdapterHelper.queryServerSearch(query, true, isAlwaysShare || isNeverShare, true, false, 0, 0); - Utilities.searchQueue.postRunnable(() -> { - String search1 = query.trim().toLowerCase(); - if (search1.length() == 0) { - updateSearchResults(new ArrayList<>(), new ArrayList<>()); - return; - } - String search2 = LocaleController.getInstance().getTranslitString(search1); - if (search1.equals(search2) || search2.length() == 0) { - search2 = null; - } - String[] search = new String[1 + (search2 != null ? 1 : 0)]; - search[0] = search1; - if (search2 != null) { - search[1] = search2; + ArrayList resultArray = new ArrayList<>(); + ArrayList resultArrayNames = new ArrayList<>(); + + for (int a = 0; a < contacts.size(); a++) { + TLObject object = contacts.get(a); + + String name; + String username; + + if (object instanceof TLRPC.User) { + TLRPC.User user = (TLRPC.User) object; + name = ContactsController.formatName(user.first_name, user.last_name).toLowerCase(); + username = user.username; + } else { + TLRPC.Chat chat = (TLRPC.Chat) object; + name = chat.title; + username = chat.username; + } + String tName = LocaleController.getInstance().getTranslitString(name); + if (name.equals(tName)) { + tName = 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 (username != null && username.startsWith(q)) { + found = 2; } - ArrayList resultArray = new ArrayList<>(); - ArrayList resultArrayNames = new ArrayList<>(); - - for (int a = 0; a < contacts.size(); a++) { - TLObject object = contacts.get(a); - - String name; - String username; - - if (object instanceof TLRPC.User) { - TLRPC.User user = (TLRPC.User) object; - name = ContactsController.formatName(user.first_name, user.last_name).toLowerCase(); - username = user.username; + if (found != 0) { + if (found == 1) { + if (object instanceof TLRPC.User) { + TLRPC.User user = (TLRPC.User) object; + resultArrayNames.add(AndroidUtilities.generateSearchName(user.first_name, user.last_name, q)); + } else { + TLRPC.Chat chat = (TLRPC.Chat) object; + resultArrayNames.add(AndroidUtilities.generateSearchName(chat.title, null, q)); + } } else { - TLRPC.Chat chat = (TLRPC.Chat) object; - name = chat.title; - username = chat.username; - } - String tName = LocaleController.getInstance().getTranslitString(name); - if (name.equals(tName)) { - tName = 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 (username != null && username.startsWith(q)) { - found = 2; - } - - if (found != 0) { - if (found == 1) { - if (object instanceof TLRPC.User) { - TLRPC.User user = (TLRPC.User) object; - resultArrayNames.add(AndroidUtilities.generateSearchName(user.first_name, user.last_name, q)); - } else { - TLRPC.Chat chat = (TLRPC.Chat) object; - resultArrayNames.add(AndroidUtilities.generateSearchName(chat.title, null, q)); - } - } else { - resultArrayNames.add(AndroidUtilities.generateSearchName("@" + username, null, "@" + q)); - } - resultArray.add(object); - break; - } + resultArrayNames.add(AndroidUtilities.generateSearchName("@" + username, null, "@" + q)); } + resultArray.add(object); + break; } - updateSearchResults(resultArray, resultArrayNames); - }); - }); - - } - }, 200, 300); + } + } + updateSearchResults(resultArray, resultArrayNames); + }); + }), 300); } } private void updateSearchResults(final ArrayList users, final ArrayList names) { AndroidUtilities.runOnUIThread(() -> { + searchRunnable = null; searchResult = users; searchResultNames = names; searchAdapterHelper.mergeResults(searchResult); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java index 23b6403c0..23fa1f4c3 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java @@ -23,6 +23,7 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.location.Location; import android.os.Build; import android.os.Bundle; import android.os.Vibrator; @@ -52,6 +53,7 @@ import org.telegram.ui.Cells.GroupCreateUserCell; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.Cells.HeaderCell; import org.telegram.ui.Cells.ShadowSectionCell; +import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.CombinedDrawable; import org.telegram.ui.Components.EditTextEmoji; @@ -74,7 +76,7 @@ import androidx.recyclerview.widget.RecyclerView; public class GroupCreateFinalActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, ImageUpdater.ImageUpdaterDelegate { private GroupCreateAdapter adapter; - private RecyclerView listView; + private RecyclerListView listView; private EditTextEmoji editText; private BackupImageView avatarImage; private View avatarOverlay; @@ -100,6 +102,9 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati private String nameToSet; private int chatType; + private String currentGroupCreateAddress; + private Location currentGroupCreateLocation; + private int reqId; private final static int done_button = 1; @@ -116,6 +121,8 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati super(args); chatType = args.getInt("chatType", ChatObject.CHAT_TYPE_CHAT); avatarDrawable = new AvatarDrawable(); + currentGroupCreateAddress = args.getString("address"); + currentGroupCreateLocation = args.getParcelable("location"); } @Override @@ -463,7 +470,7 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati listView.setVerticalScrollBarEnabled(false); listView.setVerticalScrollbarPosition(LocaleController.isRTL ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT); GroupCreateDividerItemDecoration decoration = new GroupCreateDividerItemDecoration(); - decoration.setSkipRows(2); + decoration.setSkipRows(currentGroupCreateAddress != null ? 5 : 2); listView.addItemDecoration(decoration); linearLayout.addView(listView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setOnScrollListener(new RecyclerView.OnScrollListener() { @@ -474,6 +481,21 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati } } }); + listView.setOnItemClickListener((view, position) -> { + if (view instanceof TextSettingsCell) { + if (!AndroidUtilities.isGoogleMapsInstalled(GroupCreateFinalActivity.this)) { + return; + } + LocationActivity fragment = new LocationActivity(LocationActivity.LOCATION_TYPE_GROUP); + fragment.setDialogId(0); + fragment.setDelegate((location, live) -> { + currentGroupCreateLocation.setLatitude(location.geo.lat); + currentGroupCreateLocation.setLongitude(location.geo._long); + currentGroupCreateAddress = location.address; + }); + presentFragment(fragment); + } + }); floatingButtonContainer = new FrameLayout(context); Drawable drawable = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(56), Theme.getColor(Theme.key_chats_actionBackground), Theme.getColor(Theme.key_chats_actionPressedBackground)); @@ -519,7 +541,7 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati createAfterUpload = true; } else { showEditDoneProgress(true); - reqId = MessagesController.getInstance(currentAccount).createChat(editText.getText().toString(), selectedContacts, null, chatType, GroupCreateFinalActivity.this); + reqId = MessagesController.getInstance(currentAccount).createChat(editText.getText().toString(), selectedContacts, null, chatType, currentGroupCreateLocation, currentGroupCreateAddress, GroupCreateFinalActivity.this); } }); @@ -550,7 +572,7 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati if (delegate != null) { delegate.didStartChatCreation(); } - MessagesController.getInstance(currentAccount).createChat(editText.getText().toString(), selectedContacts, null, chatType, GroupCreateFinalActivity.this); + MessagesController.getInstance(currentAccount).createChat(editText.getText().toString(), selectedContacts, null, chatType, currentGroupCreateLocation, currentGroupCreateAddress, GroupCreateFinalActivity.this); } showAvatarProgress(false, true); avatarEditor.setImageDrawable(null); @@ -768,6 +790,7 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati public class GroupCreateAdapter extends RecyclerListView.SelectionAdapter { private Context context; + private int usersStartRow; public GroupCreateAdapter(Context ctx) { context = ctx; @@ -775,12 +798,16 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati @Override public int getItemCount() { - return 2 + selectedContacts.size(); + int count = 2 + selectedContacts.size(); + if (currentGroupCreateAddress != null) { + count += 3; + } + return count; } @Override public boolean isEnabled(RecyclerView.ViewHolder holder) { - return false; + return holder.getItemViewType() == 3; } @Override @@ -801,9 +828,12 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati view = headerCell; break; case 2: - default: view = new GroupCreateUserCell(context, false, 3); break; + case 3: + default: + view = new TextSettingsCell(context); + break; } return new RecyclerListView.Holder(view); } @@ -813,20 +843,43 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati switch (holder.getItemViewType()) { case 1: { HeaderCell cell = (HeaderCell) holder.itemView; - cell.setText(LocaleController.formatPluralString("Members", selectedContacts.size())); + if (currentGroupCreateAddress != null && position == 1) { + cell.setText(LocaleController.getString("AttachLocation", R.string.AttachLocation)); + } else { + cell.setText(LocaleController.formatPluralString("Members", selectedContacts.size())); + } break; } case 2: { GroupCreateUserCell cell = (GroupCreateUserCell) holder.itemView; - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(selectedContacts.get(position - 2)); + TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(selectedContacts.get(position - usersStartRow)); cell.setObject(user, null, null); break; } + case 3: { + TextSettingsCell cell = (TextSettingsCell) holder.itemView; + cell.setText(currentGroupCreateAddress, false); + break; + } } } @Override public int getItemViewType(int position) { + if (currentGroupCreateAddress != null) { + if (position == 0) { + return 0; + } else if (position == 1) { + return 1; + } else if (position == 2) { + return 3; + } else { + position -= 3; + } + usersStartRow = 5; + } else { + usersStartRow = 2; + } switch (position) { case 0: return 0; @@ -900,6 +953,8 @@ public class GroupCreateFinalActivity extends BaseFragment implements Notificati new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundBlue), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundPink), + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(progressView, 0, null, null, null, null, Theme.key_contextProgressInner2), new ThemeDescription(progressView, 0, null, null, null, null, Theme.key_contextProgressOuter2), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GroupStickersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GroupStickersActivity.java index c97bcfbc0..6f767a31b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GroupStickersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GroupStickersActivity.java @@ -36,7 +36,7 @@ import android.widget.LinearLayout; import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; @@ -117,7 +117,7 @@ public class GroupStickersActivity extends BaseFragment implements NotificationC @Override public boolean onFragmentCreate() { super.onFragmentCreate(); - DataQuery.getInstance(currentAccount).checkStickers(DataQuery.TYPE_IMAGE); + MediaDataController.getInstance(currentAccount).checkStickers(MediaDataController.TYPE_IMAGE); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.stickersDidLoad); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatInfoDidLoad); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.groupStickersDidLoad); @@ -328,7 +328,7 @@ public class GroupStickersActivity extends BaseFragment implements NotificationC if (holder != null) { top = holder.itemView.getTop(); } - selectedStickerSet = DataQuery.getInstance(currentAccount).getStickerSets(DataQuery.TYPE_IMAGE).get(position - stickersStartRow); + selectedStickerSet = MediaDataController.getInstance(currentAccount).getStickerSets(MediaDataController.TYPE_IMAGE).get(position - stickersStartRow); ignoreTextChanges = true; usernameTextView.setText(selectedStickerSet.set.short_name); usernameTextView.setSelection(usernameTextView.length()); @@ -360,14 +360,14 @@ public class GroupStickersActivity extends BaseFragment implements NotificationC @Override public void didReceivedNotification(int id, int account, Object... args) { if (id == NotificationCenter.stickersDidLoad) { - if ((Integer) args[0] == DataQuery.TYPE_IMAGE) { + if ((Integer) args[0] == MediaDataController.TYPE_IMAGE) { updateRows(); } } else if (id == NotificationCenter.chatInfoDidLoad) { TLRPC.ChatFull chatFull = (TLRPC.ChatFull) args[0]; if (chatFull.id == chatId) { if (info == null && chatFull.stickerset != null) { - selectedStickerSet = DataQuery.getInstance(currentAccount).getGroupStickerSetById(chatFull.stickerset); + selectedStickerSet = MediaDataController.getInstance(currentAccount).getGroupStickerSetById(chatFull.stickerset); } info = chatFull; updateRows(); @@ -383,7 +383,7 @@ public class GroupStickersActivity extends BaseFragment implements NotificationC public void setInfo(TLRPC.ChatFull chatFull) { info = chatFull; if (info != null && info.stickerset != null) { - selectedStickerSet = DataQuery.getInstance(currentAccount).getGroupStickerSetById(info.stickerset); + selectedStickerSet = MediaDataController.getInstance(currentAccount).getGroupStickerSetById(info.stickerset); } } @@ -411,7 +411,7 @@ public class GroupStickersActivity extends BaseFragment implements NotificationC searching = true; searchWas = true; final String query = usernameTextView.getText().toString(); - TLRPC.TL_messages_stickerSet existingSet = DataQuery.getInstance(currentAccount).getStickerSetByName(query); + TLRPC.TL_messages_stickerSet existingSet = MediaDataController.getInstance(currentAccount).getStickerSetByName(query); if (existingSet != null) { selectedStickerSet = existingSet; } @@ -495,7 +495,7 @@ public class GroupStickersActivity extends BaseFragment implements NotificationC info.stickerset = null; } else { info.stickerset = selectedStickerSet.set; - DataQuery.getInstance(currentAccount).putGroupStickerSet(selectedStickerSet); + MediaDataController.getInstance(currentAccount).putGroupStickerSet(selectedStickerSet); } if (info.stickerset == null) { info.flags |= 256; @@ -522,7 +522,7 @@ public class GroupStickersActivity extends BaseFragment implements NotificationC selectedStickerRow = -1; } infoRow = rowCount++; - ArrayList stickerSets = DataQuery.getInstance(currentAccount).getStickerSets(DataQuery.TYPE_IMAGE); + ArrayList stickerSets = MediaDataController.getInstance(currentAccount).getStickerSets(MediaDataController.TYPE_IMAGE); if (!stickerSets.isEmpty()) { headerRow = rowCount++; stickersStartRow = rowCount; @@ -569,22 +569,22 @@ public class GroupStickersActivity extends BaseFragment implements NotificationC progressView.setVisibility(View.VISIBLE); doneItem.setEnabled(false); doneItemAnimation.playTogether( - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleX", 0.1f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleY", 0.1f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "alpha", 0.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleX", 0.1f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleY", 0.1f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "alpha", 0.0f), ObjectAnimator.ofFloat(progressView, "scaleX", 1.0f), ObjectAnimator.ofFloat(progressView, "scaleY", 1.0f), ObjectAnimator.ofFloat(progressView, "alpha", 1.0f)); } else { - doneItem.getImageView().setVisibility(View.VISIBLE); + doneItem.getContentView().setVisibility(View.VISIBLE); doneItem.setEnabled(true); doneItemAnimation.playTogether( ObjectAnimator.ofFloat(progressView, "scaleX", 0.1f), ObjectAnimator.ofFloat(progressView, "scaleY", 0.1f), ObjectAnimator.ofFloat(progressView, "alpha", 0.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleX", 1.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleY", 1.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "alpha", 1.0f)); + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleX", 1.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleY", 1.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "alpha", 1.0f)); } doneItemAnimation.addListener(new AnimatorListenerAdapter() { @@ -594,7 +594,7 @@ public class GroupStickersActivity extends BaseFragment implements NotificationC if (!show) { progressView.setVisibility(View.INVISIBLE); } else { - doneItem.getImageView().setVisibility(View.INVISIBLE); + doneItem.getContentView().setVisibility(View.INVISIBLE); } } } @@ -627,7 +627,7 @@ public class GroupStickersActivity extends BaseFragment implements NotificationC public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (holder.getItemViewType()) { case 0: { - ArrayList arrayList = DataQuery.getInstance(currentAccount).getStickerSets(DataQuery.TYPE_IMAGE); + ArrayList arrayList = MediaDataController.getInstance(currentAccount).getStickerSets(MediaDataController.TYPE_IMAGE); int row = position - stickersStartRow; StickerSetCell cell = (StickerSetCell) holder.itemView; TLRPC.TL_messages_stickerSet set = arrayList.get(row); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java index e36a7696d..d98448761 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java @@ -41,11 +41,15 @@ import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.Toast; +import com.google.android.gms.common.api.Status; + +import org.telegram.messenger.AccountInstance; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.BuildVars; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.LocationController; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.FileLoader; import org.telegram.messenger.ImageLoader; import org.telegram.messenger.MediaController; @@ -149,6 +153,8 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa private Runnable lockRunnable; + private static final int PLAY_SERVICES_REQUEST_CHECK_SETTINGS = 140; + @Override protected void onCreate(Bundle savedInstanceState) { ApplicationLoader.postInitApplication(); @@ -415,7 +421,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa args.putInt("step", 0); presentFragment(new ChannelCreateActivity(args)); } else { - presentFragment(new ChannelIntroActivity()); + presentFragment(new ActionIntroActivity(ActionIntroActivity.ACTION_TYPE_CHANNEL_CREATE)); preferences.edit().putBoolean("channel_intro", true).commit(); } drawerLayoutContainer.closeDrawer(false); @@ -570,6 +576,9 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } else { os2 = ""; } + if (BuildVars.DEBUG_VERSION) { + FileLog.d("OS name " + os1 + " " + os2); + } if (os1.contains("flyme") || os2.contains("flyme")) { AndroidUtilities.incorrectDisplaySizeFix = true; final View view = getWindow().getDecorView().getRootView(); @@ -687,6 +696,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.wasUnableToFindCurrentLocation); NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.openArticle); NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.hasNewContactsToImport); + NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.needShowPlayServicesAlert); } currentAccount = UserConfig.selectedAccount; NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.appDidLogout); @@ -696,6 +706,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.wasUnableToFindCurrentLocation); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.openArticle); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.hasNewContactsToImport); + NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.needShowPlayServicesAlert); updateCurrentConnectionState(currentAccount); } @@ -1169,7 +1180,10 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } } } else if (path.length() >= 1) { - List segments = data.getPathSegments(); + ArrayList segments = new ArrayList<>(data.getPathSegments()); + if (segments.size() > 0 && segments.get(0).equals("s")) { + segments.remove(0); + } if (segments.size() > 0) { username = segments.get(0); if (segments.size() > 1) { @@ -1907,7 +1921,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } if (MessagesController.getInstance(intentAccount).checkCanOpenChat(args13, fragment13)) { NotificationCenter.getInstance(intentAccount).postNotificationName(NotificationCenter.closeChats); - DataQuery.getInstance(intentAccount).saveDraft(did, message, null, null, false); + MediaDataController.getInstance(intentAccount).saveDraft(did, message, null, null, false); actionBarLayout.presentFragment(new ChatActivity(args13), true, false, true, false); } }); @@ -2218,6 +2232,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa actionBarLayout.presentFragment(contactFragment, dialogsFragment != null, dialogsFragment == null, true, false); } } else { + AccountInstance accountInstance = AccountInstance.getInstance(UserConfig.selectedAccount); actionBarLayout.presentFragment(fragment, dialogsFragment != null, dialogsFragment == null, true, false); if (videoPath != null) { fragment.openVideoEditor(videoPath, sendingText); @@ -2228,7 +2243,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa photoPathsArray.get(0).caption = sendingText; sendingText = null; } - SendMessagesHelper.prepareSendingMedia(photoPathsArray, did, null, null, false, false, null); + SendMessagesHelper.prepareSendingMedia(accountInstance, photoPathsArray, did, null, null, false, false, null); } if (documentsPathsArray != null || documentsUrisArray != null) { String caption = null; @@ -2236,10 +2251,10 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa caption = sendingText; sendingText = null; } - SendMessagesHelper.prepareSendingDocuments(documentsPathsArray, documentsOriginalPathsArray, documentsUrisArray, caption, documentsMimeType, did, null, null, null); + SendMessagesHelper.prepareSendingDocuments(accountInstance, documentsPathsArray, documentsOriginalPathsArray, documentsUrisArray, caption, documentsMimeType, did, null, null, null); } if (sendingText != null) { - SendMessagesHelper.prepareSendingText(sendingText, did); + SendMessagesHelper.prepareSendingText(accountInstance, sendingText, did); } if (contactsToSend != null && !contactsToSend.isEmpty()) { for (int a = 0; a < contactsToSend.size(); a++) { @@ -2315,22 +2330,26 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa UserConfig.getInstance(currentAccount).saveConfig(false); } super.onActivityResult(requestCode, resultCode, data); - ThemeEditorView editorView = ThemeEditorView.getInstance(); - if (editorView != null) { - editorView.onActivityResult(requestCode, resultCode, data); - } - if (actionBarLayout.fragmentsStack.size() != 0) { - BaseFragment fragment = actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.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 (requestCode == PLAY_SERVICES_REQUEST_CHECK_SETTINGS) { + LocationController.getInstance(currentAccount).startFusedLocationRequest(resultCode == Activity.RESULT_OK); + } else { + ThemeEditorView editorView = ThemeEditorView.getInstance(); + if (editorView != null) { + editorView.onActivityResult(requestCode, resultCode, data); + } + if (actionBarLayout.fragmentsStack.size() != 0) { + BaseFragment fragment = actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1); fragment.onActivityResultFragment(requestCode, resultCode, data); } - if (layersActionBarLayout.fragmentsStack.size() != 0) { - BaseFragment fragment = layersActionBarLayout.fragmentsStack.get(layersActionBarLayout.fragmentsStack.size() - 1); - fragment.onActivityResultFragment(requestCode, resultCode, data); + if (AndroidUtilities.isTablet()) { + if (rightActionBarLayout.fragmentsStack.size() != 0) { + BaseFragment fragment = rightActionBarLayout.fragmentsStack.get(rightActionBarLayout.fragmentsStack.size() - 1); + fragment.onActivityResultFragment(requestCode, resultCode, data); + } + if (layersActionBarLayout.fragmentsStack.size() != 0) { + BaseFragment fragment = layersActionBarLayout.fragmentsStack.get(layersActionBarLayout.fragmentsStack.size() - 1); + fragment.onActivityResultFragment(requestCode, resultCode, data); + } } } } @@ -2751,6 +2770,13 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } } } + } else if (id == NotificationCenter.needShowPlayServicesAlert) { + try { + final Status status = (Status) args[0]; + status.startResolutionForResult(this, PLAY_SERVICES_REQUEST_CHECK_SETTINGS); + } catch (Throwable ignore) { + + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java index bd33231d5..467570ea7 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java @@ -60,16 +60,13 @@ import com.google.android.gms.maps.model.MarkerOptions; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; import org.telegram.messenger.FileLoader; +import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocationController; -import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; -import org.telegram.messenger.UserConfig; -import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.MessageObject; -import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.ui.ActionBar.ActionBar; @@ -88,6 +85,7 @@ import org.telegram.ui.Cells.SendLocationCell; import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.CombinedDrawable; import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; @@ -120,8 +118,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter private RecyclerListView listView; private RecyclerListView searchListView; private LocationActivitySearchAdapter searchAdapter; - private ImageView markerImageView; - private ImageView markerXImageView; + private View markerImageView; private ImageView locationButton; private ImageView routeButton; private LinearLayoutManager layoutManager; @@ -155,13 +152,15 @@ public class LocationActivity extends BaseFragment implements NotificationCenter private Location userLocation; private int markerTop; + private TLRPC.TL_channelLocation chatLocation; + private TLRPC.TL_channelLocation initialLocation; private MessageObject messageObject; - private boolean userLocationMoved = false; - private boolean firstWas = false; + private boolean userLocationMoved; + private boolean firstWas; private CircleOptions circleOptions; private LocationActivityDelegate delegate; - private int liveLocationType; + private int locationType; private int overScrollHeight = AndroidUtilities.displaySize.x - ActionBar.getCurrentActionBarHeight() - AndroidUtilities.dp(66); @@ -170,25 +169,29 @@ public class LocationActivity extends BaseFragment implements NotificationCenter private final static int map_list_menu_satellite = 3; private final static int map_list_menu_hybrid = 4; + public final static int LOCATION_TYPE_SEND = 0; + public final static int LOCATION_TYPE_GROUP = 4; + public final static int LOCATION_TYPE_GROUP_VIEW = 5; + public interface LocationActivityDelegate { void didSelectLocation(TLRPC.MessageMedia location, int live); } - public LocationActivity(int liveLocation) { + public LocationActivity(int type) { super(); - liveLocationType = liveLocation; + locationType = type; } @Override public boolean onFragmentCreate() { super.onFragmentCreate(); swipeBackEnabled = false; - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.closeChats); + getNotificationCenter().addObserver(this, NotificationCenter.closeChats); NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.locationPermissionGranted); if (messageObject != null && messageObject.isLiveLocation()) { - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.didReceiveNewMessages); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messagesDeleted); - NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.replaceMessagesObjects); + getNotificationCenter().addObserver(this, NotificationCenter.didReceiveNewMessages); + getNotificationCenter().addObserver(this, NotificationCenter.messagesDeleted); + getNotificationCenter().addObserver(this, NotificationCenter.replaceMessagesObjects); } return true; } @@ -197,10 +200,10 @@ public class LocationActivity extends BaseFragment implements NotificationCenter public void onFragmentDestroy() { super.onFragmentDestroy(); NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.locationPermissionGranted); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.closeChats); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.didReceiveNewMessages); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messagesDeleted); - NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.replaceMessagesObjects); + getNotificationCenter().removeObserver(this, NotificationCenter.closeChats); + getNotificationCenter().removeObserver(this, NotificationCenter.didReceiveNewMessages); + getNotificationCenter().removeObserver(this, NotificationCenter.messagesDeleted); + getNotificationCenter().removeObserver(this, NotificationCenter.replaceMessagesObjects); try { if (googleMap != null) { googleMap.setMyLocationEnabled(false); @@ -266,7 +269,9 @@ public class LocationActivity extends BaseFragment implements NotificationCenter }); ActionBarMenu menu = actionBar.createMenu(); - if (messageObject != null) { + if (chatLocation != null) { + actionBar.setTitle(LocaleController.getString("ChatLocation", R.string.ChatLocation)); + } else if (messageObject != null) { if (messageObject.isLiveLocation()) { actionBar.setTitle(LocaleController.getString("AttachLiveLocation", R.string.AttachLiveLocation)); } else { @@ -280,45 +285,47 @@ public class LocationActivity extends BaseFragment implements NotificationCenter } else { actionBar.setTitle(LocaleController.getString("ShareLocation", R.string.ShareLocation)); - ActionBarMenuItem item = menu.addItem(0, R.drawable.ic_ab_search).setIsSearchField(true).setActionBarMenuItemSearchListener(new ActionBarMenuItem.ActionBarMenuItemSearchListener() { - @Override - public void onSearchExpand() { - searching = true; - otherItem.setVisibility(View.GONE); - listView.setVisibility(View.GONE); - mapViewClip.setVisibility(View.GONE); - searchListView.setVisibility(View.VISIBLE); - searchListView.setEmptyView(emptyView); - emptyView.showTextView(); - } - - @Override - public void onSearchCollapse() { - searching = false; - searchWas = false; - otherItem.setVisibility(View.VISIBLE); - searchListView.setEmptyView(null); - listView.setVisibility(View.VISIBLE); - mapViewClip.setVisibility(View.VISIBLE); - searchListView.setVisibility(View.GONE); - emptyView.setVisibility(View.GONE); - searchAdapter.searchDelayed(null, null); - } - - @Override - public void onTextChanged(EditText editText) { - if (searchAdapter == null) { - return; + if (locationType != LOCATION_TYPE_GROUP) { + ActionBarMenuItem item = menu.addItem(0, R.drawable.ic_ab_search).setIsSearchField(true).setActionBarMenuItemSearchListener(new ActionBarMenuItem.ActionBarMenuItemSearchListener() { + @Override + public void onSearchExpand() { + searching = true; + otherItem.setVisibility(View.GONE); + listView.setVisibility(View.GONE); + mapViewClip.setVisibility(View.GONE); + searchListView.setVisibility(View.VISIBLE); + searchListView.setEmptyView(emptyView); + emptyView.showTextView(); } - String text = editText.getText().toString(); - if (text.length() != 0) { - searchWas = true; + + @Override + public void onSearchCollapse() { + searching = false; + searchWas = false; + otherItem.setVisibility(View.VISIBLE); + searchListView.setEmptyView(null); + listView.setVisibility(View.VISIBLE); + mapViewClip.setVisibility(View.VISIBLE); + searchListView.setVisibility(View.GONE); + emptyView.setVisibility(View.GONE); + searchAdapter.searchDelayed(null, null); } - emptyView.showProgress(); - searchAdapter.searchDelayed(text, userLocation); - } - }); - item.setSearchFieldHint(LocaleController.getString("Search", R.string.Search)); + + @Override + public void onTextChanged(EditText editText) { + if (searchAdapter == null) { + return; + } + String text = editText.getText().toString(); + if (text.length() != 0) { + searchWas = true; + } + emptyView.showProgress(); + searchAdapter.searchDelayed(text, userLocation); + } + }); + item.setSearchFieldHint(LocaleController.getString("Search", R.string.Search)); + } } otherItem = menu.addItem(0, R.drawable.ic_ab_other); @@ -358,8 +365,8 @@ public class LocationActivity extends BaseFragment implements NotificationCenter locationButton.setContentDescription(LocaleController.getString("AccDescrMyLocation", R.string.AccDescrMyLocation)); if (Build.VERSION.SDK_INT >= 21) { StateListAnimator animator = new StateListAnimator(); - animator.addState(new int[]{android.R.attr.state_pressed}, ObjectAnimator.ofFloat(locationButton, "translationZ", AndroidUtilities.dp(2), AndroidUtilities.dp(4)).setDuration(200)); - animator.addState(new int[]{}, ObjectAnimator.ofFloat(locationButton, "translationZ", AndroidUtilities.dp(4), AndroidUtilities.dp(2)).setDuration(200)); + animator.addState(new int[]{android.R.attr.state_pressed}, ObjectAnimator.ofFloat(locationButton, View.TRANSLATION_Z, AndroidUtilities.dp(2), AndroidUtilities.dp(4)).setDuration(200)); + animator.addState(new int[]{}, ObjectAnimator.ofFloat(locationButton, View.TRANSLATION_Z, AndroidUtilities.dp(4), AndroidUtilities.dp(2)).setDuration(200)); locationButton.setStateListAnimator(animator); locationButton.setOutlineProvider(new ViewOutlineProvider() { @SuppressLint("NewApi") @@ -370,7 +377,11 @@ public class LocationActivity extends BaseFragment implements NotificationCenter }); } - if (messageObject != null) { + if (chatLocation != null) { + userLocation = new Location("network"); + userLocation.setLatitude(chatLocation.geo_point.lat); + userLocation.setLongitude(chatLocation.geo_point._long); + } else if (messageObject != null) { userLocation = new Location("network"); userLocation.setLatitude(messageObject.messageOwner.media.geo.lat); userLocation.setLongitude(messageObject.messageOwner.media.geo._long); @@ -390,7 +401,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter listView = new RecyclerListView(context); listView.setItemAnimator(null); listView.setLayoutAnimation(null); - listView.setAdapter(adapter = new LocationActivityAdapter(context, liveLocationType, dialogId)); + listView.setAdapter(adapter = new LocationActivityAdapter(context, locationType, dialogId)); listView.setVerticalScrollBarEnabled(false); listView.setLayoutManager(layoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) { @Override @@ -411,33 +422,70 @@ public class LocationActivity extends BaseFragment implements NotificationCenter return; } updateClipView(position); - if (dy > 0) { - if (!adapter.isPulledUp()) { - adapter.setPulledUp(); - if (myLocation != null) { - AndroidUtilities.runOnUIThread(() -> adapter.searchPlacesWithQuery(null, myLocation, true)); + if (locationType != LOCATION_TYPE_GROUP) { + if (dy > 0) { + if (!adapter.isPulledUp()) { + adapter.setPulledUp(); + if (myLocation != null) { + AndroidUtilities.runOnUIThread(() -> adapter.searchPlacesWithQuery(null, myLocation, true)); + } } } } } }); listView.setOnItemClickListener((view, position) -> { - if (position == 1 && messageObject != null && !messageObject.isLiveLocation()) { + if (locationType == LOCATION_TYPE_GROUP) { + if (position == 1) { + TLRPC.TL_messageMediaVenue venue = (TLRPC.TL_messageMediaVenue) adapter.getItem(position); + if (venue == null) { + return; + } + if (dialogId == 0) { + delegate.didSelectLocation(venue, LOCATION_TYPE_GROUP); + finishFragment(); + } else { + final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(getParentActivity(), 3)}; + TLRPC.TL_channels_editLocation req = new TLRPC.TL_channels_editLocation(); + req.address = venue.address; + req.channel = getMessagesController().getInputChannel(-(int) dialogId); + req.geo_point = new TLRPC.TL_inputGeoPoint(); + req.geo_point.lat = venue.geo.lat; + req.geo_point._long = venue.geo._long; + int requestId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + try { + progressDialog[0].dismiss(); + } catch (Throwable ignore) { + + } + progressDialog[0] = null; + delegate.didSelectLocation(venue, LOCATION_TYPE_GROUP); + finishFragment(); + })); + progressDialog[0].setOnCancelListener(dialog -> getConnectionsManager().cancelRequest(requestId, true)); + showDialog(progressDialog[0]); + } + } + } else if (locationType == LOCATION_TYPE_GROUP_VIEW) { + if (googleMap != null) { + googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(chatLocation.geo_point.lat, chatLocation.geo_point._long), googleMap.getMaxZoomLevel() - 4)); + } + } else if (position == 1 && messageObject != null && !messageObject.isLiveLocation()) { if (googleMap != null) { googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(messageObject.messageOwner.media.geo.lat, messageObject.messageOwner.media.geo._long), googleMap.getMaxZoomLevel() - 4)); } - } else if (position == 1 && liveLocationType != 2) { + } else if (position == 1 && locationType != 2) { if (delegate != null && userLocation != null) { TLRPC.TL_messageMediaGeo location = new TLRPC.TL_messageMediaGeo(); location.geo = new TLRPC.TL_geoPoint(); location.geo.lat = AndroidUtilities.fixLocationCoord(userLocation.getLatitude()); location.geo._long = AndroidUtilities.fixLocationCoord(userLocation.getLongitude()); - delegate.didSelectLocation(location, liveLocationType); + delegate.didSelectLocation(location, locationType); } finishFragment(); - } else if (position == 2 && liveLocationType == 1 || position == 1 && liveLocationType == 2 || position == 3 && liveLocationType == 3) { - if (LocationController.getInstance(currentAccount).isSharingLocation(dialogId)) { - LocationController.getInstance(currentAccount).removeSharingLocation(dialogId); + } else if (position == 2 && locationType == 1 || position == 1 && locationType == 2 || position == 3 && locationType == 3) { + if (getLocationController().isSharingLocation(dialogId)) { + getLocationController().removeSharingLocation(dialogId); finishFragment(); } else { if (delegate == null || getParentActivity() == null) { @@ -446,7 +494,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter if (myLocation != null) { TLRPC.User user = null; if ((int) dialogId > 0) { - user = MessagesController.getInstance(currentAccount).getUser((int) dialogId); + user = getMessagesController().getUser((int) dialogId); } showDialog(AlertsCreator.createLocationUpdateDialog(getParentActivity(), user, param -> { TLRPC.TL_messageMediaGeoLive location = new TLRPC.TL_messageMediaGeoLive(); @@ -454,7 +502,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter location.geo.lat = AndroidUtilities.fixLocationCoord(myLocation.getLatitude()); location.geo._long = AndroidUtilities.fixLocationCoord(myLocation.getLongitude()); location.period = param; - delegate.didSelectLocation(location, liveLocationType); + delegate.didSelectLocation(location, locationType); finishFragment(); })); } @@ -463,11 +511,12 @@ public class LocationActivity extends BaseFragment implements NotificationCenter Object object = adapter.getItem(position); if (object instanceof TLRPC.TL_messageMediaVenue) { if (object != null && delegate != null) { - delegate.didSelectLocation((TLRPC.TL_messageMediaVenue) object, liveLocationType); + delegate.didSelectLocation((TLRPC.TL_messageMediaVenue) object, locationType); } finishFragment(); } else if (object instanceof LiveLocation) { - googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(((LiveLocation) object).marker.getPosition(), googleMap.getMaxZoomLevel() - 4)); + LiveLocation liveLocation = (LiveLocation) object; + googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(liveLocation.marker.getPosition(), googleMap.getMaxZoomLevel() - 4)); } } }); @@ -484,7 +533,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter mapView = new MapView(context) { @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (messageObject == null) { + if (messageObject == null && chatLocation == null) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { if (animatorSet != null) { animatorSet.cancel(); @@ -492,8 +541,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter animatorSet = new AnimatorSet(); animatorSet.setDuration(200); animatorSet.playTogether( - ObjectAnimator.ofFloat(markerImageView, "translationY", markerTop + -AndroidUtilities.dp(10)), - ObjectAnimator.ofFloat(markerXImageView, "alpha", 1.0f)); + ObjectAnimator.ofFloat(markerImageView, View.TRANSLATION_Y, markerTop - AndroidUtilities.dp(10))); animatorSet.start(); } else if (ev.getAction() == MotionEvent.ACTION_UP) { if (animatorSet != null) { @@ -502,15 +550,15 @@ public class LocationActivity extends BaseFragment implements NotificationCenter animatorSet = new AnimatorSet(); animatorSet.setDuration(200); animatorSet.playTogether( - ObjectAnimator.ofFloat(markerImageView, "translationY", markerTop), - ObjectAnimator.ofFloat(markerXImageView, "alpha", 0.0f)); + ObjectAnimator.ofFloat(markerImageView, View.TRANSLATION_Y, markerTop)); animatorSet.start(); + adapter.fetchLocationAddress(); } if (ev.getAction() == MotionEvent.ACTION_MOVE) { if (!userLocationMoved) { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.setDuration(200); - animatorSet.play(ObjectAnimator.ofFloat(locationButton, "alpha", 1.0f)); + animatorSet.play(ObjectAnimator.ofFloat(locationButton, View.ALPHA, 1.0f)); animatorSet.start(); userLocationMoved = true; } @@ -556,16 +604,30 @@ public class LocationActivity extends BaseFragment implements NotificationCenter shadow.setBackgroundResource(R.drawable.header_shadow_reverse); mapViewClip.addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.LEFT | Gravity.BOTTOM)); - if (messageObject == null) { - markerImageView = new ImageView(context); - markerImageView.setImageResource(R.drawable.map_pin); - mapViewClip.addView(markerImageView, LayoutHelper.createFrame(24, 42, Gravity.TOP | Gravity.CENTER_HORIZONTAL)); + if (messageObject == null && chatLocation == null) { + if (locationType == LOCATION_TYPE_GROUP && dialogId != 0) { + TLRPC.Chat chat = getMessagesController().getChat(-(int) dialogId); + if (chat != null) { + FrameLayout frameLayout1 = new FrameLayout(context); + frameLayout1.setBackgroundResource(R.drawable.livepin); + mapViewClip.addView(frameLayout1, LayoutHelper.createFrame(62, 76, Gravity.TOP | Gravity.CENTER_HORIZONTAL)); - markerXImageView = new ImageView(context); - markerXImageView.setAlpha(0.0f); - markerXImageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_location_markerX), PorterDuff.Mode.MULTIPLY)); - markerXImageView.setImageResource(R.drawable.place_x); - mapViewClip.addView(markerXImageView, LayoutHelper.createFrame(14, 14, Gravity.TOP | Gravity.CENTER_HORIZONTAL)); + BackupImageView backupImageView = new BackupImageView(context); + backupImageView.setRoundRadius(AndroidUtilities.dp(26)); + backupImageView.setImage(ImageLocation.getForChat(chat, false), "50_50", new AvatarDrawable(chat), chat); + frameLayout1.addView(backupImageView, LayoutHelper.createFrame(52, 52, Gravity.LEFT | Gravity.TOP, 5, 5, 0, 0)); + + markerImageView = frameLayout1; + markerImageView.setTag(1); + } + } + + if (markerImageView == null) { + ImageView imageView = new ImageView(context); + imageView.setImageResource(R.drawable.map_pin2); + mapViewClip.addView(imageView, LayoutHelper.createFrame(28, 48, Gravity.TOP | Gravity.CENTER_HORIZONTAL)); + markerImageView = imageView; + } emptyView = new EmptyTextProgressView(context); emptyView.setText(LocaleController.getString("NoResult", R.string.NoResult)); @@ -589,11 +651,11 @@ public class LocationActivity extends BaseFragment implements NotificationCenter searchListView.setOnItemClickListener((view, position) -> { TLRPC.TL_messageMediaVenue object = searchAdapter.getItem(position); if (object != null && delegate != null) { - delegate.didSelectLocation(object, liveLocationType); + delegate.didSelectLocation(object, locationType); } finishFragment(); }); - } else if (!messageObject.isLiveLocation()) { + } else if (messageObject != null && !messageObject.isLiveLocation() || chatLocation != null) { routeButton = new ImageView(context); drawable = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(56), Theme.getColor(Theme.key_chats_actionBackground), Theme.getColor(Theme.key_chats_actionPressedBackground)); if (Build.VERSION.SDK_INT < 21) { @@ -609,8 +671,8 @@ public class LocationActivity extends BaseFragment implements NotificationCenter routeButton.setScaleType(ImageView.ScaleType.CENTER); if (Build.VERSION.SDK_INT >= 21) { StateListAnimator animator = new StateListAnimator(); - animator.addState(new int[]{android.R.attr.state_pressed}, ObjectAnimator.ofFloat(routeButton, "translationZ", AndroidUtilities.dp(2), AndroidUtilities.dp(4)).setDuration(200)); - animator.addState(new int[]{}, ObjectAnimator.ofFloat(routeButton, "translationZ", AndroidUtilities.dp(4), AndroidUtilities.dp(2)).setDuration(200)); + animator.addState(new int[]{android.R.attr.state_pressed}, ObjectAnimator.ofFloat(routeButton, View.TRANSLATION_Z, AndroidUtilities.dp(2), AndroidUtilities.dp(4)).setDuration(200)); + animator.addState(new int[]{}, ObjectAnimator.ofFloat(routeButton, View.TRANSLATION_Z, AndroidUtilities.dp(4), AndroidUtilities.dp(2)).setDuration(200)); routeButton.setStateListAnimator(animator); routeButton.setOutlineProvider(new ViewOutlineProvider() { @SuppressLint("NewApi") @@ -633,7 +695,12 @@ public class LocationActivity extends BaseFragment implements NotificationCenter } if (myLocation != null) { try { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(String.format(Locale.US, "http://maps.google.com/maps?saddr=%f,%f&daddr=%f,%f", myLocation.getLatitude(), myLocation.getLongitude(), messageObject.messageOwner.media.geo.lat, messageObject.messageOwner.media.geo._long))); + Intent intent; + if (messageObject != null) { + intent = new Intent(Intent.ACTION_VIEW, Uri.parse(String.format(Locale.US, "http://maps.google.com/maps?saddr=%f,%f&daddr=%f,%f", myLocation.getLatitude(), myLocation.getLongitude(), messageObject.messageOwner.media.geo.lat, messageObject.messageOwner.media.geo._long))); + } else { + intent = new Intent(Intent.ACTION_VIEW, Uri.parse(String.format(Locale.US, "http://maps.google.com/maps?saddr=%f,%f&daddr=%f,%f", myLocation.getLatitude(), myLocation.getLongitude(), chatLocation.geo_point.lat, chatLocation.geo_point._long))); + } getParentActivity().startActivity(intent); } catch (Exception e) { FileLog.e(e); @@ -641,10 +708,14 @@ public class LocationActivity extends BaseFragment implements NotificationCenter } }); - adapter.setMessageObject(messageObject); + if (chatLocation != null) { + adapter.setChatLocation(chatLocation); + } else if (messageObject != null) { + adapter.setMessageObject(messageObject); + } } - if (messageObject != null && !messageObject.isLiveLocation()) { + if (messageObject != null && !messageObject.isLiveLocation() || chatLocation != null) { mapViewClip.addView(locationButton, 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, 43)); } else { mapViewClip.addView(locationButton, 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)); @@ -659,7 +730,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter } } } - if (messageObject != null) { + if (messageObject != null || chatLocation != null) { if (myLocation != null && googleMap != null) { googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(myLocation.getLatitude(), myLocation.getLongitude()), googleMap.getMaxZoomLevel() - 4)); } @@ -667,7 +738,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter if (myLocation != null && googleMap != null) { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.setDuration(200); - animatorSet.play(ObjectAnimator.ofFloat(locationButton, "alpha", 0.0f)); + animatorSet.play(ObjectAnimator.ofFloat(locationButton, View.ALPHA, 0.0f)); animatorSet.start(); adapter.setCustomLocation(null); userLocationMoved = false; @@ -675,8 +746,12 @@ public class LocationActivity extends BaseFragment implements NotificationCenter } } }); - if (messageObject == null) { - locationButton.setAlpha(0.0f); + if (messageObject == null && chatLocation == null) { + if (initialLocation == null) { + locationButton.setAlpha(0.0f); + } else { + userLocationMoved = true; + } } frameLayout.addView(actionBar); @@ -755,15 +830,15 @@ public class LocationActivity extends BaseFragment implements NotificationCenter liveLocation = new LiveLocation(); liveLocation.object = message; if (liveLocation.object.from_id != 0) { - liveLocation.user = MessagesController.getInstance(currentAccount).getUser(liveLocation.object.from_id); + liveLocation.user = getMessagesController().getUser(liveLocation.object.from_id); liveLocation.id = liveLocation.object.from_id; } else { int did = (int) MessageObject.getDialogId(message); if (did > 0) { - liveLocation.user = MessagesController.getInstance(currentAccount).getUser(did); + liveLocation.user = getMessagesController().getUser(did); liveLocation.id = did; } else { - liveLocation.chat = MessagesController.getInstance(currentAccount).getChat(-did); + liveLocation.chat = getMessagesController().getChat(-did); liveLocation.id = did; } } @@ -777,8 +852,8 @@ public class LocationActivity extends BaseFragment implements NotificationCenter liveLocation.marker = googleMap.addMarker(options); markers.add(liveLocation); markersMap.put(liveLocation.id, liveLocation); - LocationController.SharingLocationInfo myInfo = LocationController.getInstance(currentAccount).getSharingLocationInfo(dialogId); - if (liveLocation.id == UserConfig.getInstance(currentAccount).getClientUserId() && myInfo != null && liveLocation.object.id == myInfo.mid && myLocation != null) { + LocationController.SharingLocationInfo myInfo = getLocationController().getSharingLocationInfo(dialogId); + if (liveLocation.id == getUserConfig().getClientUserId() && myInfo != null && liveLocation.object.id == myInfo.mid && myLocation != null) { liveLocation.marker.setPosition(new LatLng(myLocation.getLatitude(), myLocation.getLongitude())); } } @@ -792,12 +867,44 @@ public class LocationActivity extends BaseFragment implements NotificationCenter return liveLocation; } + private LiveLocation addUserMarker(TLRPC.TL_channelLocation location) { + LatLng latLng = new LatLng(location.geo_point.lat, location.geo_point._long); + LiveLocation liveLocation = new LiveLocation(); + int did = (int) dialogId; + if (did > 0) { + liveLocation.user = getMessagesController().getUser(did); + liveLocation.id = did; + } else { + liveLocation.chat = getMessagesController().getChat(-did); + liveLocation.id = did; + } + + try { + MarkerOptions options = new MarkerOptions().position(latLng); + Bitmap bitmap = createUserBitmap(liveLocation); + if (bitmap != null) { + options.icon(BitmapDescriptorFactory.fromBitmap(bitmap)); + options.anchor(0.5f, 0.907f); + liveLocation.marker = googleMap.addMarker(options); + markers.add(liveLocation); + markersMap.put(liveLocation.id, liveLocation); + } + } catch (Exception e) { + FileLog.e(e); + } + + return liveLocation; + } + private void onMapInit() { if (googleMap == null) { return; } - if (messageObject != null) { + if (chatLocation != null) { + LiveLocation liveLocation = addUserMarker(chatLocation); + googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(liveLocation.marker.getPosition(), googleMap.getMaxZoomLevel() - 4)); + } else if (messageObject != null) { if (messageObject.isLiveLocation()) { LiveLocation liveLocation = addUserMarker(messageObject.messageOwner); if (!getRecentLocations()) { @@ -806,7 +913,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter } else { LatLng latLng = new LatLng(userLocation.getLatitude(), userLocation.getLongitude()); try { - googleMap.addMarker(new MarkerOptions().position(latLng).icon(BitmapDescriptorFactory.fromResource(R.drawable.map_pin))); + googleMap.addMarker(new MarkerOptions().position(latLng).icon(BitmapDescriptorFactory.fromResource(R.drawable.map_pin2))); } catch (Exception e) { FileLog.e(e); } @@ -817,8 +924,16 @@ public class LocationActivity extends BaseFragment implements NotificationCenter } } else { userLocation = new Location("network"); - userLocation.setLatitude(20.659322); - userLocation.setLongitude(-11.406250); + if (initialLocation != null) { + LatLng latLng = new LatLng(initialLocation.geo_point.lat, initialLocation.geo_point._long); + googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, googleMap.getMaxZoomLevel() - 4)); + userLocation.setLatitude(initialLocation.geo_point.lat); + userLocation.setLongitude(initialLocation.geo_point._long); + adapter.setCustomLocation(userLocation); + } else { + userLocation.setLatitude(20.659322); + userLocation.setLongitude(-11.406250); + } } try { @@ -832,7 +947,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter //googleMap.getUiSettings().setMapToolbarEnabled(false); googleMap.setOnMyLocationChangeListener(location -> { positionMarker(location); - LocationController.getInstance(currentAccount).setGoogleMapLocation(location, isFirstLocation); + getLocationController().setGoogleMapLocation(location, isFirstLocation); isFirstLocation = false; }); positionMarker(myLocation = getLastLocation()); @@ -943,8 +1058,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter mapViewClip.setTranslationY(Math.min(0, top)); mapView.setTranslationY(Math.max(0, -top / 2)); if (markerImageView != null) { - markerImageView.setTranslationY(markerTop = -top - AndroidUtilities.dp(42) + height / 2); - markerXImageView.setTranslationY(-top - AndroidUtilities.dp(7) + height / 2); + markerImageView.setTranslationY(markerTop = -top - AndroidUtilities.dp(markerImageView.getTag() == null ? 48 : 69) + height / 2); } if (routeButton != null) { routeButton.setTranslationY(top); @@ -995,10 +1109,10 @@ public class LocationActivity extends BaseFragment implements NotificationCenter adapter.notifyDataSetChanged(); if (resume) { - layoutManager.scrollToPositionWithOffset(0, -AndroidUtilities.dp(32 + (liveLocationType == 1 || liveLocationType == 2 ? 66 : 0))); + layoutManager.scrollToPositionWithOffset(0, -AndroidUtilities.dp(32 + (locationType == 1 || locationType == 2 ? 66 : 0))); updateClipView(layoutManager.findFirstVisibleItemPosition()); listView.post(() -> { - layoutManager.scrollToPositionWithOffset(0, -AndroidUtilities.dp(32 + (liveLocationType == 1 || liveLocationType == 2 ? 66 : 0))); + layoutManager.scrollToPositionWithOffset(0, -AndroidUtilities.dp(32 + (locationType == 1 || locationType == 2 ? 66 : 0))); updateClipView(layoutManager.findFirstVisibleItemPosition()); }); } else { @@ -1025,12 +1139,12 @@ public class LocationActivity extends BaseFragment implements NotificationCenter return; } myLocation = new Location(location); - LiveLocation liveLocation = markersMap.get(UserConfig.getInstance(currentAccount).getClientUserId()); - LocationController.SharingLocationInfo myInfo = LocationController.getInstance(currentAccount).getSharingLocationInfo(dialogId); + LiveLocation liveLocation = markersMap.get(getUserConfig().getClientUserId()); + LocationController.SharingLocationInfo myInfo = getLocationController().getSharingLocationInfo(dialogId); if (liveLocation != null && myInfo != null && liveLocation.object.id == myInfo.mid) { liveLocation.marker.setPosition(new LatLng(location.getLatitude(), location.getLongitude())); } - if (messageObject == null && googleMap != null) { + if (messageObject == null && chatLocation == null && googleMap != null) { LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); if (adapter != null) { if (adapter.isPulledUp()) { @@ -1059,16 +1173,25 @@ public class LocationActivity extends BaseFragment implements NotificationCenter dialogId = messageObject.getDialogId(); } + public void setChatLocation(int chatId, TLRPC.TL_channelLocation location) { + dialogId = -chatId; + chatLocation = location; + } + public void setDialogId(long did) { dialogId = did; } + public void setInitialLocation(TLRPC.TL_channelLocation location) { + initialLocation = location; + } + private void fetchRecentLocations(ArrayList messages) { LatLngBounds.Builder builder = null; if (firstFocus) { builder = new LatLngBounds.Builder(); } - int date = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + int date = getConnectionsManager().getCurrentTime(); for (int a = 0; a < messages.size(); a++) { TLRPC.Message message = messages.get(a); if (message.date + message.media.period > date) { @@ -1100,7 +1223,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter } private boolean getRecentLocations() { - ArrayList messages = LocationController.getInstance(currentAccount).locationsCache.get(messageObject.getDialogId()); + ArrayList messages = getLocationController().locationsCache.get(messageObject.getDialogId()); if (messages != null && messages.isEmpty()) { fetchRecentLocations(messages); } else { @@ -1108,16 +1231,16 @@ public class LocationActivity extends BaseFragment implements NotificationCenter } int lower_id = (int) dialogId; if (lower_id < 0) { - TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-lower_id); + TLRPC.Chat chat = getMessagesController().getChat(-lower_id); if (ChatObject.isChannel(chat) && !chat.megagroup) { return false; } } TLRPC.TL_messages_getRecentLocations req = new TLRPC.TL_messages_getRecentLocations(); final long dialog_id = messageObject.getDialogId(); - req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) dialog_id); + req.peer = getMessagesController().getInputPeer((int) dialog_id); req.limit = 100; - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { + getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { AndroidUtilities.runOnUIThread(() -> { if (googleMap == null) { @@ -1130,11 +1253,11 @@ public class LocationActivity extends BaseFragment implements NotificationCenter a--; } } - MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, res.chats, true, true); - MessagesController.getInstance(currentAccount).putUsers(res.users, false); - MessagesController.getInstance(currentAccount).putChats(res.chats, false); - LocationController.getInstance(currentAccount).locationsCache.put(dialog_id, res.messages); - NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.liveLocationsCacheChanged, dialog_id); + getMessagesStorage().putUsersAndChats(res.users, res.chats, true, true); + getMessagesController().putUsers(res.users, false); + getMessagesController().putChats(res.chats, false); + getLocationController().locationsCache.put(dialog_id, res.messages); + getNotificationCenter().postNotificationName(NotificationCenter.liveLocationsCacheChanged, dialog_id); fetchRecentLocations(res.messages); }); } @@ -1188,7 +1311,7 @@ public class LocationActivity extends BaseFragment implements NotificationCenter } LiveLocation liveLocation = markersMap.get(getMessageId(messageObject.messageOwner)); if (liveLocation != null) { - LocationController.SharingLocationInfo myInfo = LocationController.getInstance(currentAccount).getSharingLocationInfo(did); + LocationController.SharingLocationInfo myInfo = getLocationController().getSharingLocationInfo(did); if (myInfo == null || myInfo.mid != messageObject.getId()) { liveLocation.marker.setPosition(new LatLng(messageObject.messageOwner.media.geo.lat, messageObject.messageOwner.media.geo._long)); } @@ -1298,8 +1421,6 @@ public class LocationActivity extends BaseFragment implements NotificationCenter new ThemeDescription(routeButton, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chats_actionBackground), new ThemeDescription(routeButton, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_chats_actionPressedBackground), - new ThemeDescription(markerXImageView, 0, null, null, null, null, Theme.key_location_markerX), - new ThemeDescription(listView, 0, new Class[]{GraySectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_graySectionText), new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{GraySectionCell.class}, null, null, null, Theme.key_graySection), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java index 3ed9ce6c5..2259ec71d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java @@ -523,22 +523,22 @@ public class LoginActivity extends BaseFragment { doneProgressView.setTag(1); doneProgressView.setVisibility(View.VISIBLE); doneItemAnimation.playTogether( - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleX", 0.1f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleY", 0.1f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "alpha", 0.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleX", 0.1f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleY", 0.1f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "alpha", 0.0f), ObjectAnimator.ofFloat(doneProgressView, "scaleX", 1.0f), ObjectAnimator.ofFloat(doneProgressView, "scaleY", 1.0f), ObjectAnimator.ofFloat(doneProgressView, "alpha", 1.0f)); } else { doneProgressView.setTag(null); - doneItem.getImageView().setVisibility(View.VISIBLE); + doneItem.getContentView().setVisibility(View.VISIBLE); doneItemAnimation.playTogether( ObjectAnimator.ofFloat(doneProgressView, "scaleX", 0.1f), ObjectAnimator.ofFloat(doneProgressView, "scaleY", 0.1f), ObjectAnimator.ofFloat(doneProgressView, "alpha", 0.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleX", 1.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleY", 1.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "alpha", 1.0f)); + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleX", 1.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleY", 1.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "alpha", 1.0f)); } doneItemAnimation.addListener(new AnimatorListenerAdapter() { @Override @@ -547,7 +547,7 @@ public class LoginActivity extends BaseFragment { if (!show) { doneProgressView.setVisibility(View.INVISIBLE); } else { - doneItem.getImageView().setVisibility(View.INVISIBLE); + doneItem.getContentView().setVisibility(View.INVISIBLE); } } } @@ -2553,7 +2553,7 @@ public class LoginActivity extends BaseFragment { needShowProgress(0); Utilities.globalQueue.postRunnable(() -> { - final byte x_bytes[]; + final byte[] x_bytes; TLRPC.PasswordKdfAlgo current_algo = null; if (passwordType == 1) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LogoutActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LogoutActivity.java index 6a735f4c1..8a3971492 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LogoutActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LogoutActivity.java @@ -128,7 +128,7 @@ public class LogoutActivity extends BaseFragment { } else if (position == cacheRow) { presentFragment(new CacheControlActivity()); } else if (position == phoneRow) { - presentFragment(new ChangePhoneHelpActivity()); + presentFragment(new ActionIntroActivity(ActionIntroActivity.ACTION_TYPE_CHANGE_PHONE_NUMBER)); } else if (position == supportRow) { showDialog(AlertsCreator.createSupportAlert(LogoutActivity.this)); } else if (position == logoutRow) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java index cebacbe1d..e271187f8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java @@ -46,7 +46,7 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; import org.telegram.messenger.MessagesController; @@ -327,7 +327,7 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No private final static int gotochat = 7; public MediaActivity(Bundle args, int[] media) { - this(args, media, null, DataQuery.MEDIA_PHOTOVIDEO); + this(args, media, null, MediaDataController.MEDIA_PHOTOVIDEO); } public MediaActivity(Bundle args, int[] media, SharedMediaData[] mediaData, int initTab) { @@ -387,16 +387,16 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No public View createView(Context context) { for (int a = 0; a < 10; a++) { cellCache.add(new SharedPhotoVideoCell(context)); - if (initialTab == DataQuery.MEDIA_MUSIC) { + if (initialTab == MediaDataController.MEDIA_MUSIC) { SharedAudioCell cell = new SharedAudioCell(context) { @Override public boolean needPlayMessage(MessageObject messageObject) { if (messageObject.isVoice() || messageObject.isRoundVideo()) { boolean result = MediaController.getInstance().playMessage(messageObject); - MediaController.getInstance().setVoiceMessagesPlaylist(result ? sharedMediaData[DataQuery.MEDIA_MUSIC].messages : null, false); + MediaController.getInstance().setVoiceMessagesPlaylist(result ? sharedMediaData[MediaDataController.MEDIA_MUSIC].messages : null, false); return result; } else if (messageObject.isMusic()) { - return MediaController.getInstance().setPlaylist(sharedMediaData[DataQuery.MEDIA_MUSIC].messages, messageObject); + return MediaController.getInstance().setPlaylist(sharedMediaData[MediaDataController.MEDIA_MUSIC].messages, messageObject); } return false; } @@ -1181,22 +1181,22 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No if (visibleItemCount != 0 && firstVisibleItem + visibleItemCount > totalItemCount - 2 && !sharedMediaData[mediaPage.selectedType].loading) { int type; if (mediaPage.selectedType == 0) { - type = DataQuery.MEDIA_PHOTOVIDEO; + type = MediaDataController.MEDIA_PHOTOVIDEO; } else if (mediaPage.selectedType == 1) { - type = DataQuery.MEDIA_FILE; + type = MediaDataController.MEDIA_FILE; } else if (mediaPage.selectedType == 2) { - type = DataQuery.MEDIA_AUDIO; + type = MediaDataController.MEDIA_AUDIO; } else if (mediaPage.selectedType == 4) { - type = DataQuery.MEDIA_MUSIC; + type = MediaDataController.MEDIA_MUSIC; } else { - type = DataQuery.MEDIA_URL; + type = MediaDataController.MEDIA_URL; } if (!sharedMediaData[mediaPage.selectedType].endReached[0]) { sharedMediaData[mediaPage.selectedType].loading = true; - DataQuery.getInstance(currentAccount).loadMedia(dialog_id, 50, sharedMediaData[mediaPage.selectedType].max_id[0], type, 1, classGuid); + MediaDataController.getInstance(currentAccount).loadMedia(dialog_id, 50, sharedMediaData[mediaPage.selectedType].max_id[0], type, 1, classGuid); } else if (mergeDialogId != 0 && !sharedMediaData[mediaPage.selectedType].endReached[1]) { sharedMediaData[mediaPage.selectedType].loading = true; - DataQuery.getInstance(currentAccount).loadMedia(mergeDialogId, 50, sharedMediaData[mediaPage.selectedType].max_id[1], type, 1, classGuid); + MediaDataController.getInstance(currentAccount).loadMedia(mergeDialogId, 50, sharedMediaData[mediaPage.selectedType].max_id[1], type, 1, classGuid); } } if (recyclerView == mediaPages[0].listView && !searching && !actionBar.isActionModeShowed()) { @@ -1353,7 +1353,7 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No sharedMediaData[type].endReached[loadIndex] = (Boolean) args[5]; if (loadIndex == 0 && sharedMediaData[type].endReached[loadIndex] && mergeDialogId != 0) { sharedMediaData[type].loading = true; - DataQuery.getInstance(currentAccount).loadMedia(mergeDialogId, 50, sharedMediaData[type].max_id[1], type, 1, classGuid); + MediaDataController.getInstance(currentAccount).loadMedia(mergeDialogId, 50, sharedMediaData[type].max_id[1], type, 1, classGuid); } if (adapter != null) { for (int a = 0; a < mediaPages.length; a++) { @@ -1446,7 +1446,7 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No if (obj.messageOwner.media == null || obj.needDrawBluredPreview()) { continue; } - int type = DataQuery.getMediaType(obj.messageOwner); + int type = MediaDataController.getMediaType(obj.messageOwner); if (type == -1) { return; } @@ -1912,7 +1912,7 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No } if (!sharedMediaData[mediaPages[a].selectedType].loading && !sharedMediaData[mediaPages[a].selectedType].endReached[0] && sharedMediaData[mediaPages[a].selectedType].messages.isEmpty()) { sharedMediaData[mediaPages[a].selectedType].loading = true; - DataQuery.getInstance(currentAccount).loadMedia(dialog_id, 50, 0, mediaPages[a].selectedType, 1, classGuid); + MediaDataController.getInstance(currentAccount).loadMedia(dialog_id, 50, 0, mediaPages[a].selectedType, 1, classGuid); } if (sharedMediaData[mediaPages[a].selectedType].loading && sharedMediaData[mediaPages[a].selectedType].messages.isEmpty()) { mediaPages[a].progressView.setVisibility(View.VISIBLE); @@ -2329,7 +2329,7 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No break; case 3: default: - if (currentType == DataQuery.MEDIA_MUSIC && !audioCellCache.isEmpty()) { + if (currentType == MediaDataController.MEDIA_MUSIC && !audioCellCache.isEmpty()) { view = audioCellCache.get(0); audioCellCache.remove(0); ViewGroup p = (ViewGroup) view.getParent(); @@ -2351,7 +2351,7 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No } }; } - if (currentType == DataQuery.MEDIA_MUSIC) { + if (currentType == MediaDataController.MEDIA_MUSIC) { audioCache.add((SharedAudioCell) view); } break; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java index 1e7e73ffc..cfc1b4f7e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java @@ -92,6 +92,7 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt private int countryState; private boolean ignoreSelection; private boolean donePressed; + private String initialPhoneNumber; private final static int done_button = 1; @@ -374,14 +375,14 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt country = codesMap.get(sub); if (country != null) { ok = true; - textToSet = text.substring(a, text.length()) + phoneField.getText().toString(); + textToSet = text.substring(a) + phoneField.getText().toString(); codeField.setText(text = sub); break; } } if (!ok) { ignoreOnTextChange = true; - textToSet = text.substring(1, text.length()) + phoneField.getText().toString(); + textToSet = text.substring(1) + phoneField.getText().toString(); codeField.setText(text = text.substring(0, 1)); } } @@ -408,7 +409,9 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt codeField.setSelection(codeField.getText().length()); } if (textToSet != null) { - phoneField.requestFocus(); + if (initialPhoneNumber == null) { + phoneField.requestFocus(); + } phoneField.setText(textToSet); phoneField.setSelection(phoneField.length()); } @@ -474,7 +477,7 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt String phoneChars = "0123456789"; String str = phoneField.getText().toString(); if (characterAction == 3) { - str = str.substring(0, actionPosition) + str.substring(actionPosition + 1, str.length()); + str = str.substring(0, actionPosition) + str.substring(actionPosition + 1); start--; } StringBuilder builder = new StringBuilder(str.length()); @@ -551,31 +554,35 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt Collections.sort(countriesArray, String::compareTo); - String country = null; - - try { - TelephonyManager telephonyManager = (TelephonyManager) ApplicationLoader.applicationContext.getSystemService(Context.TELEPHONY_SERVICE); - if (telephonyManager != null) { - country = telephonyManager.getSimCountryIso().toUpperCase(); + if (!TextUtils.isEmpty(initialPhoneNumber)) { + codeField.setText(initialPhoneNumber); + initialPhoneNumber = null; + } else { + String country = null; + try { + TelephonyManager telephonyManager = (TelephonyManager) ApplicationLoader.applicationContext.getSystemService(Context.TELEPHONY_SERVICE); + if (telephonyManager != null) { + country = telephonyManager.getSimCountryIso().toUpperCase(); + } + } catch (Exception e) { + FileLog.e(e); } - } catch (Exception e) { - FileLog.e(e); - } - if (country != null) { - String countryName = languageMap.get(country); - if (countryName != null) { - int index = countriesArray.indexOf(countryName); - if (index != -1) { - codeField.setText(countriesMap.get(countryName)); - countryState = 0; + if (country != null) { + String countryName = languageMap.get(country); + if (countryName != null) { + int index = countriesArray.indexOf(countryName); + if (index != -1) { + codeField.setText(countriesMap.get(countryName)); + countryState = 0; + } } } - } - if (codeField.length() == 0) { - countryButton.setText(LocaleController.getString("ChooseCountry", R.string.ChooseCountry)); - phoneField.setHintText(null); - countryState = 1; + if (codeField.length() == 0) { + countryButton.setText(LocaleController.getString("ChooseCountry", R.string.ChooseCountry)); + phoneField.setHintText(null); + countryState = 1; + } } return fragmentView; @@ -600,6 +607,10 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt } } + public void setInitialPhoneNumber(String value) { + initialPhoneNumber = value; + } + public void selectCountry(String name) { int index = countriesArray.indexOf(name); if (index != -1) { @@ -637,23 +648,23 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt } if (!animated) { if (show) { - editDoneItem.getImageView().setScaleX(0.1f); - editDoneItem.getImageView().setScaleY(0.1f); - editDoneItem.getImageView().setAlpha(0.0f); + editDoneItem.getContentView().setScaleX(0.1f); + editDoneItem.getContentView().setScaleY(0.1f); + editDoneItem.getContentView().setAlpha(0.0f); editDoneItemProgress.setScaleX(1.0f); editDoneItemProgress.setScaleY(1.0f); editDoneItemProgress.setAlpha(1.0f); - editDoneItem.getImageView().setVisibility(View.INVISIBLE); + editDoneItem.getContentView().setVisibility(View.INVISIBLE); editDoneItemProgress.setVisibility(View.VISIBLE); editDoneItem.setEnabled(false); } else { editDoneItemProgress.setScaleX(0.1f); editDoneItemProgress.setScaleY(0.1f); editDoneItemProgress.setAlpha(0.0f); - editDoneItem.getImageView().setScaleX(1.0f); - editDoneItem.getImageView().setScaleY(1.0f); - editDoneItem.getImageView().setAlpha(1.0f); - editDoneItem.getImageView().setVisibility(View.VISIBLE); + editDoneItem.getContentView().setScaleX(1.0f); + editDoneItem.getContentView().setScaleY(1.0f); + editDoneItem.getContentView().setAlpha(1.0f); + editDoneItem.getContentView().setVisibility(View.VISIBLE); editDoneItemProgress.setVisibility(View.INVISIBLE); editDoneItem.setEnabled(true); } @@ -663,22 +674,22 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt editDoneItemProgress.setVisibility(View.VISIBLE); editDoneItem.setEnabled(false); editDoneItemAnimation.playTogether( - ObjectAnimator.ofFloat(editDoneItem.getImageView(), "scaleX", 0.1f), - ObjectAnimator.ofFloat(editDoneItem.getImageView(), "scaleY", 0.1f), - ObjectAnimator.ofFloat(editDoneItem.getImageView(), "alpha", 0.0f), + ObjectAnimator.ofFloat(editDoneItem.getContentView(), "scaleX", 0.1f), + ObjectAnimator.ofFloat(editDoneItem.getContentView(), "scaleY", 0.1f), + ObjectAnimator.ofFloat(editDoneItem.getContentView(), "alpha", 0.0f), ObjectAnimator.ofFloat(editDoneItemProgress, "scaleX", 1.0f), ObjectAnimator.ofFloat(editDoneItemProgress, "scaleY", 1.0f), ObjectAnimator.ofFloat(editDoneItemProgress, "alpha", 1.0f)); } else { - editDoneItem.getImageView().setVisibility(View.VISIBLE); + editDoneItem.getContentView().setVisibility(View.VISIBLE); editDoneItem.setEnabled(true); editDoneItemAnimation.playTogether( ObjectAnimator.ofFloat(editDoneItemProgress, "scaleX", 0.1f), ObjectAnimator.ofFloat(editDoneItemProgress, "scaleY", 0.1f), ObjectAnimator.ofFloat(editDoneItemProgress, "alpha", 0.0f), - ObjectAnimator.ofFloat(editDoneItem.getImageView(), "scaleX", 1.0f), - ObjectAnimator.ofFloat(editDoneItem.getImageView(), "scaleY", 1.0f), - ObjectAnimator.ofFloat(editDoneItem.getImageView(), "alpha", 1.0f)); + ObjectAnimator.ofFloat(editDoneItem.getContentView(), "scaleX", 1.0f), + ObjectAnimator.ofFloat(editDoneItem.getContentView(), "scaleY", 1.0f), + ObjectAnimator.ofFloat(editDoneItem.getContentView(), "alpha", 1.0f)); } editDoneItemAnimation.addListener(new AnimatorListenerAdapter() { @@ -688,7 +699,7 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt if (!show) { editDoneItemProgress.setVisibility(View.INVISIBLE); } else { - editDoneItem.getImageView().setVisibility(View.INVISIBLE); + editDoneItem.getContentView().setVisibility(View.INVISIBLE); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsCustomSettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsCustomSettingsActivity.java index 24d178461..d4041825c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsCustomSettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsCustomSettingsActivity.java @@ -13,6 +13,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.app.Activity; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.drawable.Drawable; @@ -28,26 +29,28 @@ import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.FrameLayout; +import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; 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.NotificationsController; import org.telegram.messenger.R; -import org.telegram.messenger.UserConfig; 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.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Adapters.SearchAdapterHelper; +import org.telegram.ui.Cells.GraySectionCell; import org.telegram.ui.Cells.HeaderCell; import org.telegram.ui.Cells.NotificationsCheckCell; import org.telegram.ui.Cells.ShadowSectionCell; @@ -62,9 +65,8 @@ import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; +import java.util.HashMap; import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -74,7 +76,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { private RecyclerListView listView; private ListAdapter adapter; private EmptyTextProgressView emptyView; - private SearchAdapter searchListViewAdapter; + private SearchAdapter searchAdapter; private AnimatorSet animatorSet; private boolean searchWas; @@ -96,10 +98,13 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { private int exceptionsStartRow; private int exceptionsEndRow; private int exceptionsSection2Row; + private int deleteAllRow; + private int deleteAllSectionRow; private int rowCount = 0; private int currentType; private ArrayList exceptions; + private HashMap exceptionsDict = new HashMap<>(); public NotificationsCustomSettingsActivity(int type, ArrayList notificationExceptions) { this(type, notificationExceptions, false); @@ -109,6 +114,10 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { super(); currentType = type; exceptions = notificationExceptions; + for (int a = 0, N = exceptions.size(); a < N; a++) { + NotificationsSettingsActivity.NotificationException exception = exceptions.get(a); + exceptionsDict.put(exception.did, exception); + } if (load) { loadExceptions(); } @@ -151,7 +160,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { @Override public void onSearchCollapse() { - searchListViewAdapter.searchDialogs(null); + searchAdapter.searchDialogs(null); searching = false; searchWas = false; emptyView.setText(LocaleController.getString("NoExceptions", R.string.NoExceptions)); @@ -164,7 +173,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { @Override public void onTextChanged(EditText editText) { - if (searchListViewAdapter == null) { + if (searchAdapter == null) { return; } String text = editText.getText().toString(); @@ -172,19 +181,20 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { searchWas = true; if (listView != null) { emptyView.setText(LocaleController.getString("NoResult", R.string.NoResult)); - listView.setAdapter(searchListViewAdapter); - searchListViewAdapter.notifyDataSetChanged(); + emptyView.showProgress(); + listView.setAdapter(searchAdapter); + searchAdapter.notifyDataSetChanged(); listView.setFastScrollVisible(false); listView.setVerticalScrollBarEnabled(true); } } - searchListViewAdapter.searchDialogs(text); + searchAdapter.searchDialogs(text); } }); searchItem.setSearchFieldHint(LocaleController.getString("Search", R.string.Search)); } - searchListViewAdapter = new SearchAdapter(context); + searchAdapter = new SearchAdapter(context); fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; @@ -207,37 +217,80 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { if (getParentActivity() == null) { return; } - if (listView.getAdapter() == searchListViewAdapter || position >= exceptionsStartRow && position < exceptionsEndRow) { + if (listView.getAdapter() == searchAdapter || position >= exceptionsStartRow && position < exceptionsEndRow) { ArrayList arrayList; - int index = position; - if (listView.getAdapter() == searchListViewAdapter) { - arrayList = searchListViewAdapter.searchResult; + NotificationsSettingsActivity.NotificationException exception; + boolean newException; + if (listView.getAdapter() == searchAdapter) { + Object object = searchAdapter.getObject(position); + if (object instanceof NotificationsSettingsActivity.NotificationException) { + arrayList = searchAdapter.searchResult; + exception = (NotificationsSettingsActivity.NotificationException) object; + newException = false; + } else { + long did; + if (object instanceof TLRPC.User) { + TLRPC.User user = (TLRPC.User) object; + did = user.id; + } else { + TLRPC.Chat chat = (TLRPC.Chat) object; + did = -chat.id; + } + if (exceptionsDict.containsKey(did)) { + exception = exceptionsDict.get(did); + newException = false; + } else { + newException = true; + exception = new NotificationsSettingsActivity.NotificationException(); + exception.did = did; + if (object instanceof TLRPC.User) { + TLRPC.User user = (TLRPC.User) object; + exception.did = user.id; + } else { + TLRPC.Chat chat = (TLRPC.Chat) object; + exception.did = -chat.id; + } + } + arrayList = exceptions; + } } else { arrayList = exceptions; - index -= exceptionsStartRow; + int index = position - exceptionsStartRow; + if (index < 0 || index >= arrayList.size()) { + return; + } + exception = arrayList.get(index); + newException = false; } - if (index < 0 || index >= arrayList.size()) { + if (exception == null) { return; } - NotificationsSettingsActivity.NotificationException exception = arrayList.get(index); + AlertsCreator.showCustomNotificationsDialog(NotificationsCustomSettingsActivity.this, exception.did, -1, null, currentAccount, null, param -> { if (param == 0) { + if (newException) { + return; + } if (arrayList != exceptions) { int idx = exceptions.indexOf(exception); if (idx >= 0) { exceptions.remove(idx); + exceptionsDict.remove(exception.did); } } arrayList.remove(exception); if (exceptionsAddRow != -1 && arrayList.isEmpty() && arrayList == exceptions) { listView.getAdapter().notifyItemChanged(exceptionsAddRow); + listView.getAdapter().notifyItemRemoved(deleteAllRow); + listView.getAdapter().notifyItemRemoved(deleteAllSectionRow); } listView.getAdapter().notifyItemRemoved(position); updateRows(); checkRowsEnabled(); + actionBar.closeSearchField(); } else { - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getNotificationsSettings(); exception.hasCustom = preferences.getBoolean("custom_" + exception.did, false); exception.notify = preferences.getInt("notify2_" + exception.did, 0); if (exception.notify != 0) { @@ -246,7 +299,15 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { exception.muteUntil = time; } } - listView.getAdapter().notifyItemChanged(position); + if (newException) { + exceptions.add(exception); + exceptionsDict.put(exception.did, exception); + updateRows(); + adapter.notifyDataSetChanged(); + } else { + listView.getAdapter().notifyItemChanged(position); + } + actionBar.closeSearchField(); } }); return; @@ -276,13 +337,48 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { presentFragment(profileNotificationsActivity, true); }); presentFragment(activity); + } else if (position == deleteAllRow) { + 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) -> { + SharedPreferences preferences = getNotificationsSettings(); + SharedPreferences.Editor editor = preferences.edit(); + for (int a = 0, N = exceptions.size(); a < N; a++) { + NotificationsSettingsActivity.NotificationException exception = exceptions.get(a); + editor.remove("notify2_" + exception.did).remove("custom_" + exception.did); + getMessagesStorage().setDialogFlags(exception.did, 0); + TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(exception.did); + if (dialog != null) { + dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + } + } + editor.commit(); + for (int a = 0, N = exceptions.size(); a < N; a++) { + NotificationsSettingsActivity.NotificationException exception = exceptions.get(a); + getNotificationsController().updateServerNotificationsSettings(exception.did, false); + } + + exceptions.clear(); + exceptionsDict.clear(); + updateRows(); + getNotificationCenter().postNotificationName(NotificationCenter.notificationsSettingsUpdated); + adapter.notifyDataSetChanged(); + }); + 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)); + } } else if (position == alertRow) { - enabled = NotificationsController.getInstance(currentAccount).isGlobalNotificationsEnabled(currentType); + enabled = getNotificationsController().isGlobalNotificationsEnabled(currentType); NotificationsCheckCell checkCell = (NotificationsCheckCell) view; RecyclerView.ViewHolder holder = listView.findViewHolderForAdapterPosition(position); if (!enabled) { - NotificationsController.getInstance(currentAccount).setGlobalNotificationsEnabled(currentType, 0); + getNotificationsController().setGlobalNotificationsEnabled(currentType, 0); checkCell.setChecked(!enabled); if (holder != null) { adapter.onBindViewHolder(holder, position); @@ -291,7 +387,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } else { AlertsCreator.showCustomNotificationsDialog(NotificationsCustomSettingsActivity.this, 0, currentType, exceptions, currentAccount, param -> { int offUntil; - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getNotificationsSettings(); if (currentType == NotificationsController.TYPE_PRIVATE) { offUntil = preferences.getInt("EnableAll2", 0); } else if (currentType == NotificationsController.TYPE_GROUP) { @@ -299,7 +395,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } else { offUntil = preferences.getInt("EnableChannel2", 0); } - int currentTime = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + int currentTime = getConnectionsManager().getCurrentTime(); int iconType; if (offUntil < currentTime) { iconType = 0; @@ -308,7 +404,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } else { iconType = 2; } - checkCell.setChecked(NotificationsController.getInstance(currentAccount).isGlobalNotificationsEnabled(currentType), iconType); + checkCell.setChecked(getNotificationsController().isGlobalNotificationsEnabled(currentType), iconType); if (holder != null) { adapter.onBindViewHolder(holder, position); } @@ -319,7 +415,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { if (!view.isEnabled()) { return; } - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getNotificationsSettings(); SharedPreferences.Editor editor = preferences.edit(); if (currentType == NotificationsController.TYPE_PRIVATE) { enabled = preferences.getBoolean("EnablePreviewAll", true); @@ -332,13 +428,13 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { editor.putBoolean("EnablePreviewChannel", !enabled); } editor.commit(); - NotificationsController.getInstance(currentAccount).updateServerNotificationsSettings(currentType); + getNotificationsController().updateServerNotificationsSettings(currentType); } else if (position == messageSoundRow) { if (!view.isEnabled()) { return; } try { - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getNotificationsSettings(); Intent tmpIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); @@ -451,7 +547,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } int count = listView.getChildCount(); ArrayList animators = new ArrayList<>(); - boolean enabled = NotificationsController.getInstance(currentAccount).isGlobalNotificationsEnabled(currentType); + boolean enabled = getNotificationsController().isGlobalNotificationsEnabled(currentType); for (int a = 0; a < count; a++) { View child = listView.getChildAt(a); RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.getChildViewHolder(child); @@ -500,7 +596,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } private void loadExceptions() { - MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { + getMessagesStorage().getStorageQueue().postRunnable(() -> { ArrayList usersResult = new ArrayList<>(); ArrayList chatsResult = new ArrayList<>(); ArrayList channelsResult = new ArrayList<>(); @@ -513,9 +609,9 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { ArrayList users = new ArrayList<>(); ArrayList chats = new ArrayList<>(); ArrayList encryptedChats = new ArrayList<>(); - int selfId = UserConfig.getInstance(currentAccount).clientUserId; + int selfId = getUserConfig().clientUserId; - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getNotificationsSettings(); Map values = preferences.getAll(); for (Map.Entry entry : values.entrySet()) { String key = entry.getKey(); @@ -539,7 +635,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { int high_id = (int) (did << 32); if (lower_id != 0) { if (lower_id > 0) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(lower_id); + TLRPC.User user = getMessagesController().getUser(lower_id); if (user == null) { usersToLoad.add(lower_id); waitingForLoadExceptions.put(did, exception); @@ -548,7 +644,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } usersResult.add(exception); } else { - TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-lower_id); + TLRPC.Chat chat = getMessagesController().getChat(-lower_id); if (chat == null) { chatsToLoad.add(-lower_id); waitingForLoadExceptions.put(did, exception); @@ -563,12 +659,12 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } } } else if (high_id != 0) { - TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance(currentAccount).getEncryptedChat(high_id); + TLRPC.EncryptedChat encryptedChat = getMessagesController().getEncryptedChat(high_id); if (encryptedChat == null) { encryptedChatsToLoad.add(high_id); waitingForLoadExceptions.put(did, exception); } else { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(encryptedChat.user_id); + TLRPC.User user = getMessagesController().getUser(encryptedChat.user_id); if (user == null) { usersToLoad.add(encryptedChat.user_id); waitingForLoadExceptions.put(encryptedChat.user_id, exception); @@ -584,13 +680,13 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { if (waitingForLoadExceptions.size() != 0) { try { if (!encryptedChatsToLoad.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getEncryptedChatsInternal(TextUtils.join(",", encryptedChatsToLoad), encryptedChats, usersToLoad); + getMessagesStorage().getEncryptedChatsInternal(TextUtils.join(",", encryptedChatsToLoad), encryptedChats, usersToLoad); } if (!usersToLoad.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getUsersInternal(TextUtils.join(",", usersToLoad), users); + getMessagesStorage().getUsersInternal(TextUtils.join(",", usersToLoad), users); } if (!chatsToLoad.isEmpty()) { - MessagesStorage.getInstance(currentAccount).getChatsInternal(TextUtils.join(",", chatsToLoad), chats); + getMessagesStorage().getChatsInternal(TextUtils.join(",", chatsToLoad), chats); } } catch (Exception e) { FileLog.e(e); @@ -633,9 +729,9 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } } AndroidUtilities.runOnUIThread(() -> { - MessagesController.getInstance(currentAccount).putUsers(users, true); - MessagesController.getInstance(currentAccount).putChats(chats, true); - MessagesController.getInstance(currentAccount).putEncryptedChats(encryptedChats, true); + getMessagesController().putUsers(users, true); + getMessagesController().putChats(chats, true); + getMessagesController().putEncryptedChats(encryptedChats, true); if (currentType == NotificationsController.TYPE_PRIVATE) { exceptions = usersResult; } else if (currentType == NotificationsController.TYPE_GROUP) { @@ -697,6 +793,13 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } else { exceptionsSection2Row = -1; } + if (exceptions != null && !exceptions.isEmpty()) { + deleteAllRow = rowCount++; + deleteAllSectionRow = rowCount++; + } else { + deleteAllRow = -1; + deleteAllSectionRow = -1; + } } @Override @@ -716,7 +819,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } } - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getNotificationsSettings(); SharedPreferences.Editor editor = preferences.edit(); if (currentType == NotificationsController.TYPE_PRIVATE) { @@ -745,7 +848,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } } editor.commit(); - NotificationsController.getInstance(currentAccount).updateServerNotificationsSettings(currentType); + getNotificationsController().updateServerNotificationsSettings(currentType); RecyclerView.ViewHolder holder = listView.findViewHolderForAdapterPosition(requestCode); if (holder != null) { adapter.onBindViewHolder(holder, requestCode); @@ -766,48 +869,44 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { private Context mContext; private ArrayList searchResult = new ArrayList<>(); private ArrayList searchResultNames = new ArrayList<>(); - private Timer searchTimer; + private Runnable searchRunnable; + private SearchAdapterHelper searchAdapterHelper; public SearchAdapter(Context context) { mContext = context; + searchAdapterHelper = new SearchAdapterHelper(true); + searchAdapterHelper.setDelegate(() -> { + if (searchRunnable == null && !searchAdapterHelper.isSearchInProgress()) { + emptyView.showTextView(); + } + notifyDataSetChanged(); + }); } public void searchDialogs(final String query) { - try { - if (searchTimer != null) { - searchTimer.cancel(); - } - } catch (Exception e) { - FileLog.e(e); + if (searchRunnable != null) { + Utilities.searchQueue.cancelRunnable(searchRunnable); + searchRunnable = null; } if (query == null) { searchResult.clear(); searchResultNames.clear(); + searchAdapterHelper.mergeResults(null); + searchAdapterHelper.queryServerSearch(null, true, currentType != NotificationsController.TYPE_PRIVATE, true, false, 0, false, 0); notifyDataSetChanged(); } else { - searchTimer = new Timer(); - searchTimer.schedule(new TimerTask() { - @Override - public void run() { - try { - searchTimer.cancel(); - searchTimer = null; - } catch (Exception e) { - FileLog.e(e); - } - processSearch(query); - } - }, 200, 300); + Utilities.searchQueue.postRunnable(searchRunnable = () -> processSearch(query), 300); } } private void processSearch(final String query) { AndroidUtilities.runOnUIThread(() -> { + searchAdapterHelper.queryServerSearch(query, true, currentType != NotificationsController.TYPE_PRIVATE, true, false, 0, false, 0); final ArrayList contactsCopy = new ArrayList<>(exceptions); Utilities.searchQueue.postRunnable(() -> { String search1 = query.trim().toLowerCase(); if (search1.length() == 0) { - updateSearchResults(new ArrayList<>(), new ArrayList<>()); + updateSearchResults(new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); return; } String search2 = LocaleController.getInstance().getTranslitString(search1); @@ -820,7 +919,8 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { search[1] = search2; } - ArrayList resultArray = new ArrayList<>(); + ArrayList resultArray = new ArrayList<>(); + ArrayList exceptionsArray = new ArrayList<>(); ArrayList resultArrayNames = new ArrayList<>(); String[] names = new String[2]; @@ -829,31 +929,34 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { int lower_id = (int) exception.did; int high_id = (int) (exception.did >> 32); + TLObject object = null; if (lower_id != 0) { if (lower_id > 0) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(lower_id); + TLRPC.User user = getMessagesController().getUser(lower_id); if (user.deleted) { continue; } if (user != null) { names[0] = ContactsController.formatName(user.first_name, user.last_name); names[1] = user.username; + object = user; } } else { - TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-lower_id); + TLRPC.Chat chat = getMessagesController().getChat(-lower_id); if (chat != null) { if (chat.left || chat.kicked || chat.migrated_to != null) { continue; } names[0] = chat.title; names[1] = chat.username; + object = chat; } } } else { - TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance(currentAccount).getEncryptedChat(high_id); + TLRPC.EncryptedChat encryptedChat = getMessagesController().getEncryptedChat(high_id); if (encryptedChat != null) { - TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(encryptedChat.user_id); + TLRPC.User user = getMessagesController().getUser(encryptedChat.user_id); if (user != null) { names[0] = ContactsController.formatName(user.first_name, user.last_name); names[1] = user.username; @@ -883,24 +986,45 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } else { resultArrayNames.add(AndroidUtilities.generateSearchName("@" + names[1], null, "@" + q)); } - resultArray.add(exception); + exceptionsArray.add(exception); + if (object != null) { + resultArray.add(object); + } break; } } } - updateSearchResults(resultArray, resultArrayNames); + updateSearchResults(resultArray, exceptionsArray, resultArrayNames); }); }); } - private void updateSearchResults(final ArrayList users, final ArrayList names) { + private void updateSearchResults(final ArrayList result, final ArrayList exceptions, final ArrayList names) { AndroidUtilities.runOnUIThread(() -> { - searchResult = users; + searchRunnable = null; + searchResult = exceptions; searchResultNames = names; + searchAdapterHelper.mergeResults(result); + if (searching && !searchAdapterHelper.isSearchInProgress()) { + emptyView.showTextView(); + } notifyDataSetChanged(); }); } + public Object getObject(int position) { + if (position >= 0 && position < searchResult.size()) { + return searchResult.get(position); + } else { + position -= searchResult.size() + 1; + ArrayList globalSearch = searchAdapterHelper.getGlobalSearch(); + if (position >= 0 && position < globalSearch.size()) { + return searchAdapterHelper.getGlobalSearch().get(position); + } + } + return null; + } + @Override public boolean isEnabled(RecyclerView.ViewHolder holder) { return true; @@ -908,25 +1032,63 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { @Override public int getItemCount() { - return searchResult.size(); + int count = searchResult.size(); + ArrayList globalSearch = searchAdapterHelper.getGlobalSearch(); + if (!globalSearch.isEmpty()) { + count += 1 + globalSearch.size(); + } + return count; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = new UserCell(mContext, 9, 0, false); - view.setPadding(AndroidUtilities.dp(6), 0, AndroidUtilities.dp(6), 0); - view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + View view; + switch (viewType) { + case 0: { + view = new UserCell(mContext, 4, 0, false, true); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + } + case 1: + default: { + view = new GraySectionCell(mContext); + break; + } + } + return new RecyclerListView.Holder(view); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - UserCell cell = (UserCell) holder.itemView; - cell.setException(searchResult.get(position), searchResultNames.get(position), position != searchResult.size() - 1); + switch (holder.getItemViewType()) { + case 0: { + UserCell cell = (UserCell) holder.itemView; + if (position < searchResult.size()) { + cell.setException(searchResult.get(position), searchResultNames.get(position), position != searchResult.size() - 1); + cell.setAddButtonVisible(false); + } else { + position -= searchResult.size() + 1; + ArrayList globalSearch = searchAdapterHelper.getGlobalSearch(); + TLObject object = globalSearch.get(position); + cell.setData(object, null, LocaleController.getString("NotificationsOn", R.string.NotificationsOn), 0, position != globalSearch.size() - 1); + cell.setAddButtonVisible(true); + } + break; + } + case 1: { + GraySectionCell cell = (GraySectionCell) holder.itemView; + cell.setText(LocaleController.getString("AddToExceptions", R.string.AddToExceptions)); + break; + } + } } @Override - public int getItemViewType(int i) { + public int getItemViewType(int position) { + if (position == searchResult.size()) { + return 1; + } return 0; } } @@ -1002,7 +1164,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } case 1: { TextCheckCell checkCell = (TextCheckCell) holder.itemView; - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getNotificationsSettings(); if (position == previewRow) { boolean enabled; if (currentType == NotificationsController.TYPE_PRIVATE) { @@ -1024,7 +1186,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } case 3: { TextColorCell textColorCell = (TextColorCell) holder.itemView; - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getNotificationsSettings(); int color; if (currentType == NotificationsController.TYPE_PRIVATE) { color = preferences.getInt("MessagesLed", 0xff0000ff); @@ -1043,7 +1205,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { break; } case 4: { - if (position == exceptionsSection2Row || position == groupSection2Row && exceptionsSection2Row == -1) { + if (position == deleteAllSectionRow || position == groupSection2Row && exceptionsSection2Row == -1 || position == exceptionsSection2Row && deleteAllRow == -1) { holder.itemView.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); } else { holder.itemView.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); @@ -1052,7 +1214,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } case 5: { TextSettingsCell textCell = (TextSettingsCell) holder.itemView; - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getNotificationsSettings(); if (position == messageSoundRow) { String value; if (currentType == NotificationsController.TYPE_PRIVATE) { @@ -1133,7 +1295,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { String text; StringBuilder builder = new StringBuilder(); int offUntil; - SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + SharedPreferences preferences = getNotificationsSettings(); if (currentType == NotificationsController.TYPE_PRIVATE) { text = LocaleController.getString("NotificationsForPrivateChats", R.string.NotificationsForPrivateChats); @@ -1145,7 +1307,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { text = LocaleController.getString("NotificationsForChannels", R.string.NotificationsForChannels); offUntil = preferences.getInt("EnableChannel2", 0); } - int currentTime = ConnectionsManager.getInstance(currentAccount).getCurrentTime(); + int currentTime = getConnectionsManager().getCurrentTime(); boolean enabled; int iconType; if (enabled = offUntil < currentTime) { @@ -1163,9 +1325,12 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { } case 7: { TextCell textCell = (TextCell) holder.itemView; - textCell.setColors(Theme.key_windowBackgroundWhiteBlueIcon, Theme.key_windowBackgroundWhiteBlueButton); if (position == exceptionsAddRow) { textCell.setTextAndIcon(LocaleController.getString("NotificationsAddAnException", R.string.NotificationsAddAnException), R.drawable.actions_addmember2, exceptionsStartRow != -1); + textCell.setColors(Theme.key_windowBackgroundWhiteBlueIcon, Theme.key_windowBackgroundWhiteBlueButton); + } else if (position == deleteAllRow) { + textCell.setText(LocaleController.getString("NotificationsDeleteAllException", R.string.NotificationsDeleteAllException), false); + textCell.setColors(null, Theme.key_windowBackgroundWhiteRedText5); } break; } @@ -1177,7 +1342,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { if (exceptions == null || !exceptions.isEmpty()) { return; } - boolean enabled = NotificationsController.getInstance(currentAccount).isGlobalNotificationsEnabled(currentType); + boolean enabled = getNotificationsController().isGlobalNotificationsEnabled(currentType); switch (holder.getItemViewType()) { case 0: { HeaderCell headerCell = (HeaderCell) holder.itemView; @@ -1216,11 +1381,11 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { return 2; } else if (position == messageLedRow) { return 3; - } else if (position == groupSection2Row || position == alertSection2Row || position == exceptionsSection2Row) { + } else if (position == groupSection2Row || position == alertSection2Row || position == exceptionsSection2Row || position == deleteAllSectionRow) { return 4; } else if (position == alertRow) { return 6; - } else if (position == exceptionsAddRow) { + } else if (position == exceptionsAddRow || position == deleteAllRow) { return 7; } else { return 5; @@ -1276,6 +1441,9 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundBlue), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundPink), + new ThemeDescription(listView, 0, new Class[]{GraySectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_graySectionText), + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{GraySectionCell.class}, null, null, null, Theme.key_graySection), + new ThemeDescription(listView, 0, new Class[]{NotificationsCheckCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(listView, 0, new Class[]{NotificationsCheckCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), new ThemeDescription(listView, 0, new Class[]{NotificationsCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrack), @@ -1289,6 +1457,7 @@ public class NotificationsCustomSettingsActivity extends BaseFragment { new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueButton), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteRedText5), new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueIcon), }; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PassportActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PassportActivity.java index 0f88c7f21..fb69b3299 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PassportActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PassportActivity.java @@ -807,30 +807,38 @@ public class PassportActivity extends BaseFragment implements NotificationCenter } else { hash = ""; } - if ("data".equals(target)) { - if (field != null) { - vals.put(field, description); - } - } else if ("files".equals(target)) { - if (file_hash != null) { - vals.put("files" + hash, description); - } else { - vals.put("files_all", description); - } - } else if ("selfie".equals(target)) { - vals.put("selfie" + hash, description); - } else if ("translation".equals(target)) { - if (file_hash != null) { - vals.put("translation" + hash, description); - } else { - vals.put("translation_all", description); - } - } else if ("front".equals(target)) { - vals.put("front" + hash, description); - } else if ("reverse".equals(target)) { - vals.put("reverse" + hash, description); - } else if ("error_all".equals(target)) { - vals.put("error_all", description); + switch (target) { + case "data": + if (field != null) { + vals.put(field, description); + } + break; + case "files": + if (file_hash != null) { + vals.put("files" + hash, description); + } else { + vals.put("files_all", description); + } + break; + case "selfie": + vals.put("selfie" + hash, description); + break; + case "translation": + if (file_hash != null) { + vals.put("translation" + hash, description); + } else { + vals.put("translation_all", description); + } + break; + case "front": + vals.put("front" + hash, description); + break; + case "reverse": + vals.put("reverse" + hash, description); + break; + case "error_all": + vals.put("error_all", description); + break; } } } catch (Exception ignore) { @@ -1448,11 +1456,7 @@ public class PassportActivity extends BaseFragment implements NotificationCenter inputFields[a].setInputType(InputType.TYPE_CLASS_PHONE); inputFields[a].setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); - switch (a) { - case FIELD_EMAIL: - inputFields[a].setHint(LocaleController.getString("PassportEmailCode", R.string.PassportEmailCode)); - break; - } + inputFields[a].setHint(LocaleController.getString("PassportEmailCode", R.string.PassportEmailCode)); inputFields[a].setSelection(inputFields[a].length()); inputFields[a].setPadding(0, 0, 0, AndroidUtilities.dp(6)); inputFields[a].setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); @@ -2745,18 +2749,13 @@ public class PassportActivity extends BaseFragment implements NotificationCenter inputFields[a].setCursorSize(AndroidUtilities.dp(20)); inputFields[a].setCursorWidth(1.5f); inputFields[a].setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); - inputFields[a].setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); - switch (a) { - case FIELD_EMAIL: - inputFields[a].setHint(LocaleController.getString("PaymentShippingEmailPlaceholder", R.string.PaymentShippingEmailPlaceholder)); - if (currentTypeValue != null && currentTypeValue.plain_data instanceof TLRPC.TL_securePlainEmail) { - TLRPC.TL_securePlainEmail securePlainEmail = (TLRPC.TL_securePlainEmail) currentTypeValue.plain_data; - if (!TextUtils.isEmpty(securePlainEmail.email)) { - inputFields[a].setText(securePlainEmail.email); - } - } - break; + inputFields[a].setHint(LocaleController.getString("PaymentShippingEmailPlaceholder", R.string.PaymentShippingEmailPlaceholder)); + if (currentTypeValue != null && currentTypeValue.plain_data instanceof TLRPC.TL_securePlainEmail) { + TLRPC.TL_securePlainEmail securePlainEmail = (TLRPC.TL_securePlainEmail) currentTypeValue.plain_data; + if (!TextUtils.isEmpty(securePlainEmail.email)) { + inputFields[a].setText(securePlainEmail.email); + } } inputFields[a].setSelection(inputFields[a].length()); inputFields[a].setPadding(0, 0, 0, AndroidUtilities.dp(6)); @@ -6398,22 +6397,22 @@ public class PassportActivity extends BaseFragment implements NotificationCenter progressView.setVisibility(View.VISIBLE); doneItem.setEnabled(false); doneItemAnimation.playTogether( - ObjectAnimator.ofFloat(doneItem.getImageView(), View.SCALE_X, 0.1f), - ObjectAnimator.ofFloat(doneItem.getImageView(), View.SCALE_Y, 0.1f), - ObjectAnimator.ofFloat(doneItem.getImageView(), View.ALPHA, 0.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), View.SCALE_X, 0.1f), + ObjectAnimator.ofFloat(doneItem.getContentView(), View.SCALE_Y, 0.1f), + ObjectAnimator.ofFloat(doneItem.getContentView(), View.ALPHA, 0.0f), ObjectAnimator.ofFloat(progressView, View.SCALE_X, 1.0f), ObjectAnimator.ofFloat(progressView, View.SCALE_Y, 1.0f), ObjectAnimator.ofFloat(progressView, View.ALPHA, 1.0f)); } else { - doneItem.getImageView().setVisibility(View.VISIBLE); + doneItem.getContentView().setVisibility(View.VISIBLE); doneItem.setEnabled(true); doneItemAnimation.playTogether( ObjectAnimator.ofFloat(progressView, View.SCALE_X, 0.1f), ObjectAnimator.ofFloat(progressView, View.SCALE_Y, 0.1f), ObjectAnimator.ofFloat(progressView, View.ALPHA, 0.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), View.SCALE_X, 1.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), View.SCALE_Y, 1.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), View.ALPHA, 1.0f)); + ObjectAnimator.ofFloat(doneItem.getContentView(), View.SCALE_X, 1.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), View.SCALE_Y, 1.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), View.ALPHA, 1.0f)); } doneItemAnimation.addListener(new AnimatorListenerAdapter() { @Override @@ -6422,7 +6421,7 @@ public class PassportActivity extends BaseFragment implements NotificationCenter if (!show) { progressView.setVisibility(View.INVISIBLE); } else { - doneItem.getImageView().setVisibility(View.INVISIBLE); + doneItem.getContentView().setVisibility(View.INVISIBLE); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PaymentFormActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PaymentFormActivity.java index 70a33d1d5..6264c87c6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PaymentFormActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PaymentFormActivity.java @@ -1020,7 +1020,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen showEditDoneProgress(true, true); progressView.setVisibility(View.VISIBLE); doneItem.setEnabled(false); - doneItem.getImageView().setVisibility(View.INVISIBLE); + doneItem.getContentView().setVisibility(View.INVISIBLE); webView = new WebView(context) { @Override public boolean onTouchEvent(MotionEvent event) { @@ -1823,7 +1823,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen frameLayout.addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.LEFT | Gravity.BOTTOM, 0, 0, 0, 48)); doneItem.setEnabled(false); - doneItem.getImageView().setVisibility(View.INVISIBLE); + doneItem.getContentView().setVisibility(View.INVISIBLE); webView = new WebView(context) { @Override @@ -3042,7 +3042,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen showEditDoneProgress(true, true); progressView.setVisibility(View.VISIBLE); doneItem.setEnabled(false); - doneItem.getImageView().setVisibility(View.INVISIBLE); + doneItem.getContentView().setVisibility(View.INVISIBLE); webView.loadUrl(webViewUrl = ((TLRPC.TL_payments_paymentVerficationNeeded) response).url); }); } @@ -3182,9 +3182,9 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen progressView.setVisibility(View.VISIBLE); doneItem.setEnabled(false); doneItemAnimation.playTogether( - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleX", 0.1f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleY", 0.1f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "alpha", 0.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleX", 0.1f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleY", 0.1f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "alpha", 0.0f), ObjectAnimator.ofFloat(progressView, "scaleX", 1.0f), ObjectAnimator.ofFloat(progressView, "scaleY", 1.0f), ObjectAnimator.ofFloat(progressView, "alpha", 1.0f)); @@ -3195,15 +3195,15 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen ObjectAnimator.ofFloat(progressView, "scaleY", 0.1f), ObjectAnimator.ofFloat(progressView, "alpha", 0.0f)); } else { - doneItem.getImageView().setVisibility(View.VISIBLE); + doneItem.getContentView().setVisibility(View.VISIBLE); doneItem.setEnabled(true); doneItemAnimation.playTogether( ObjectAnimator.ofFloat(progressView, "scaleX", 0.1f), ObjectAnimator.ofFloat(progressView, "scaleY", 0.1f), ObjectAnimator.ofFloat(progressView, "alpha", 0.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleX", 1.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleY", 1.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "alpha", 1.0f)); + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleX", 1.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleY", 1.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "alpha", 1.0f)); } } @@ -3214,7 +3214,7 @@ public class PaymentFormActivity extends BaseFragment implements NotificationCen if (!show) { progressView.setVisibility(View.INVISIBLE); } else { - doneItem.getImageView().setVisibility(View.INVISIBLE); + doneItem.getContentView().setVisibility(View.INVISIBLE); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PeopleNearbyActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PeopleNearbyActivity.java new file mode 100644 index 000000000..b80c970c3 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/PeopleNearbyActivity.java @@ -0,0 +1,809 @@ +/* + * 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; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.location.Location; +import android.os.Bundle; +import android.os.SystemClock; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.BuildVars; +import org.telegram.messenger.ChatObject; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.LocationController; +import org.telegram.messenger.NotificationCenter; +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.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.HeaderCell; +import org.telegram.ui.Cells.ManageChatTextCell; +import org.telegram.ui.Cells.ManageChatUserCell; +import org.telegram.ui.Cells.ShadowSectionCell; +import org.telegram.ui.Components.AlertsCreator; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RadialProgressView; +import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.ShareLocationDrawable; +import org.telegram.ui.Components.UndoView; + +import java.util.ArrayList; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +public class PeopleNearbyActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, LocationController.LocationFetchCallback { + + private ListAdapter listViewAdapter; + private RecyclerListView listView; + private ActionIntroActivity groupCreateActivity; + private UndoView undoView; + + private String currentGroupCreateAddress; + private String currentGroupCreateDisplayAddress; + private Location currentGroupCreateLocation; + + private boolean checkingCanCreate; + private boolean canCreateGroup; + private AlertDialog loadingDialog; + + private Runnable checkExpiredRunnable; + private int reqId; + + private Location lastLoadedLocation; + private long lastLoadedLocationTime; + + private boolean showingLoadingProgress; + private boolean firstLoaded; + private Runnable showProgressRunnable; + private AnimatorSet showProgressAnimation; + private ArrayList animatingViews = new ArrayList<>(); + + private final static int SHORT_POLL_TIMEOUT = 25 * 1000; + + private Runnable shortPollRunnable = new Runnable() { + @Override + public void run() { + if (shortPollRunnable != null) { + sendRequest(true); + AndroidUtilities.cancelRunOnUIThread(shortPollRunnable); + AndroidUtilities.runOnUIThread(shortPollRunnable, SHORT_POLL_TIMEOUT); + } + } + }; + + private ArrayList users; + private ArrayList chats; + + private int currentChatId; + + private int helpRow; + private int usersHeaderRow; + private int usersStartRow; + private int usersEndRow; + private int usersEmptyRow; + private int usersSectionRow; + private int chatsHeaderRow; + private int chatsStartRow; + private int chatsEndRow; + private int chatsCreateRow; + private int chatsSectionRow; + private int rowCount; + + public PeopleNearbyActivity() { + super(); + users = new ArrayList<>(getLocationController().getCachedNearbyUsers()); + chats = new ArrayList<>(getLocationController().getCachedNearbyChats()); + checkForExpiredLocations(false); + updateRows(); + } + + private void updateRows() { + rowCount = 0; + usersStartRow = -1; + usersEndRow = -1; + usersEmptyRow = -1; + chatsStartRow = -1; + chatsEndRow = -1; + chatsCreateRow = -1; + + helpRow = rowCount++; + usersHeaderRow = rowCount++; + if (users.isEmpty()) { + usersEmptyRow = rowCount++; + } else { + usersStartRow = rowCount; + rowCount += users.size(); + usersEndRow = rowCount; + } + usersSectionRow = rowCount++; + + chatsHeaderRow = rowCount++; + chatsCreateRow = rowCount++; + if (!chats.isEmpty()) { + chatsStartRow = rowCount; + rowCount += chats.size(); + chatsEndRow = rowCount; + } + chatsSectionRow = rowCount++; + + if (listViewAdapter != null) { + listViewAdapter.notifyDataSetChanged(); + } + } + + @Override + public boolean onFragmentCreate() { + super.onFragmentCreate(); + NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.newLocationAvailable); + getNotificationCenter().addObserver(this, NotificationCenter.newPeopleNearbyAvailable); + getNotificationCenter().addObserver(this, NotificationCenter.needDeleteDialog); + checkCanCreateGroup(); + sendRequest(false); + AndroidUtilities.runOnUIThread(shortPollRunnable, SHORT_POLL_TIMEOUT); + return true; + } + + @Override + public void onFragmentDestroy() { + super.onFragmentDestroy(); + NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.newLocationAvailable); + getNotificationCenter().removeObserver(this, NotificationCenter.newPeopleNearbyAvailable); + getNotificationCenter().removeObserver(this, NotificationCenter.needDeleteDialog); + if (shortPollRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(shortPollRunnable); + shortPollRunnable = null; + } + if (checkExpiredRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(checkExpiredRunnable); + checkExpiredRunnable = null; + } + if (showProgressRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(showProgressRunnable); + showProgressRunnable = null; + } + if (undoView != null) { + undoView.hide(true, 0); + } + } + + @Override + public View createView(Context context) { + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setAllowOverlayTitle(true); + actionBar.setTitle(LocaleController.getString("PeopleNearby", R.string.PeopleNearby)); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } + } + }); + + fragmentView = new FrameLayout(context); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + fragmentView.setTag(Theme.key_windowBackgroundGray); + FrameLayout frameLayout = (FrameLayout) fragmentView; + + listView = new RecyclerListView(context); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); + 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)); + + listView.setOnItemClickListener((view, position) -> { + if (position >= usersStartRow && position < usersEndRow) { + TLRPC.TL_peerLocated peerLocated = users.get(position - usersStartRow); + Bundle args1 = new Bundle(); + args1.putInt("user_id", peerLocated.peer.user_id); + ChatActivity chatActivity = new ChatActivity(args1); + presentFragment(chatActivity); + } else if (position >= chatsStartRow && position < chatsEndRow) { + TLRPC.TL_peerLocated peerLocated = chats.get(position - chatsStartRow); + Bundle args1 = new Bundle(); + int chatId; + if (peerLocated.peer instanceof TLRPC.TL_peerChat) { + chatId = peerLocated.peer.chat_id; + } else { + chatId = peerLocated.peer.channel_id; + } + args1.putInt("chat_id", chatId); + ChatActivity chatActivity = new ChatActivity(args1); + presentFragment(chatActivity); + } else if (position == chatsCreateRow) { + if (checkingCanCreate || currentGroupCreateAddress == null) { + loadingDialog = new AlertDialog(getParentActivity(), 3); + loadingDialog.setOnCancelListener(dialog -> loadingDialog = null); + loadingDialog.show(); + return; + } + openGroupCreate(); + } + }); + + undoView = new UndoView(context); + frameLayout.addView(undoView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT, 8, 0, 8, 8)); + + updateRows(); + return fragmentView; + } + + private void openGroupCreate() { + if (!canCreateGroup) { + AlertsCreator.showSimpleAlert(PeopleNearbyActivity.this, LocaleController.getString("YourLocatedChannelsTooMuch", R.string.YourLocatedChannelsTooMuch)); + return; + } + groupCreateActivity = new ActionIntroActivity(ActionIntroActivity.ACTION_TYPE_NEARBY_GROUP_CREATE); + groupCreateActivity.setGroupCreateAddress(currentGroupCreateAddress, currentGroupCreateDisplayAddress, currentGroupCreateLocation); + presentFragment(groupCreateActivity); + } + + private void checkCanCreateGroup() { + if (checkingCanCreate) { + return; + } + checkingCanCreate = true; + TLRPC.TL_channels_getAdminedPublicChannels req = new TLRPC.TL_channels_getAdminedPublicChannels(); + req.by_location = true; + req.check_limit = true; + int reqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + canCreateGroup = error == null; + checkingCanCreate = false; + if (loadingDialog != null && currentGroupCreateAddress != null) { + try { + loadingDialog.dismiss(); + } catch (Throwable e) { + FileLog.e(e); + } + loadingDialog = null; + openGroupCreate(); + } + })); + getConnectionsManager().bindRequestToGuid(reqId, classGuid); + } + + private void showLoadingProgress(boolean show) { + if (showingLoadingProgress == show) { + return; + } + showingLoadingProgress = show; + if (showProgressAnimation != null) { + showProgressAnimation.cancel(); + showProgressAnimation = null; + } + if (listView == null) { + return; + } + ArrayList animators = new ArrayList<>(); + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof HeaderCellProgress) { + HeaderCellProgress cell = (HeaderCellProgress) child; + animatingViews.add(cell); + animators.add(ObjectAnimator.ofFloat(cell.progressView, View.ALPHA, show ? 1.0f : 0.0f)); + } + } + if (animators.isEmpty()) { + return; + } + showProgressAnimation = new AnimatorSet(); + showProgressAnimation.playTogether(animators); + showProgressAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + showProgressAnimation = null; + animatingViews.clear(); + } + }); + showProgressAnimation.setDuration(180); + showProgressAnimation.start(); + } + + private void sendRequest(boolean shortpoll) { + if (!firstLoaded) { + AndroidUtilities.runOnUIThread(showProgressRunnable = () -> { + showLoadingProgress(true); + showProgressRunnable = null; + }, 1000); + firstLoaded = true; + } + Location location = getLocationController().getLastKnownLocation(); + if (location == null) { + return; + } + currentGroupCreateLocation = location; + if (!shortpoll && lastLoadedLocation != null) { + float distance = lastLoadedLocation.distanceTo(location); + if (BuildVars.DEBUG_VERSION) { + FileLog.d("located distance = " + distance); + } + if ((SystemClock.uptimeMillis() - lastLoadedLocationTime) >= 3000L && lastLoadedLocation.distanceTo(location) > 20) { + if (reqId != 0) { + getConnectionsManager().cancelRequest(reqId, true); + reqId = 0; + } + } else { + return; + } + } + if (reqId != 0) { + return; + } + lastLoadedLocation = location; + lastLoadedLocationTime = SystemClock.uptimeMillis(); + LocationController.fetchLocationAddress(currentGroupCreateLocation, PeopleNearbyActivity.this); + TLRPC.TL_contacts_getLocated req = new TLRPC.TL_contacts_getLocated(); + req.geo_point = new TLRPC.TL_inputGeoPoint(); + req.geo_point.lat = location.getLatitude(); + req.geo_point._long = location.getLongitude(); + reqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + reqId = 0; + if (showProgressRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(showProgressRunnable); + showProgressRunnable = null; + } + showLoadingProgress(false); + if (response != null) { + TLRPC.Updates updates = (TLRPC.TL_updates) response; + getMessagesController().putUsers(updates.users, false); + getMessagesController().putChats(updates.chats, false); + users.clear(); + chats.clear(); + for (int a = 0, N = updates.updates.size(); a < N; a++) { + TLRPC.Update baseUpdate = updates.updates.get(a); + if (baseUpdate instanceof TLRPC.TL_updatePeerLocated) { + TLRPC.TL_updatePeerLocated update = (TLRPC.TL_updatePeerLocated) baseUpdate; + for (int b = 0, N2 = update.peers.size(); b < N2; b++) { + TLRPC.TL_peerLocated peerLocated = update.peers.get(b); + if (peerLocated.peer instanceof TLRPC.TL_peerUser) { + users.add(peerLocated); + } else { + chats.add(peerLocated); + } + } + } + } + checkForExpiredLocations(true); + updateRows(); + } + if (shortPollRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(shortPollRunnable); + AndroidUtilities.runOnUIThread(shortPollRunnable, SHORT_POLL_TIMEOUT); + } + })); + getConnectionsManager().bindRequestToGuid(reqId, classGuid); + } + + @Override + public void onResume() { + super.onResume(); + if (listViewAdapter != null) { + listViewAdapter.notifyDataSetChanged(); + } + getLocationController().startLocationLookupForPeopleNearby(false); + } + + @Override + public void onPause() { + super.onPause(); + if (undoView != null) { + undoView.hide(true, 0); + } + getLocationController().startLocationLookupForPeopleNearby(true); + } + + @Override + protected void onBecomeFullyHidden() { + super.onBecomeFullyHidden(); + if (undoView != null) { + undoView.hide(true, 0); + } + } + + @Override + public void onLocationAddressAvailable(String address, String displayAddress, Location location) { + currentGroupCreateAddress = address; + currentGroupCreateDisplayAddress = displayAddress; + currentGroupCreateLocation = location; + if (groupCreateActivity != null) { + groupCreateActivity.setGroupCreateAddress(currentGroupCreateAddress, currentGroupCreateDisplayAddress, currentGroupCreateLocation); + } + if (loadingDialog != null && !checkingCanCreate) { + try { + loadingDialog.dismiss(); + } catch (Throwable e) { + FileLog.e(e); + } + loadingDialog = null; + openGroupCreate(); + } + } + + @Override + protected void onBecomeFullyVisible() { + super.onBecomeFullyVisible(); + groupCreateActivity = null; + } + + @Override + public void didReceivedNotification(int id, int account, Object... args) { + if (id == NotificationCenter.newLocationAvailable) { + sendRequest(false); + } else if (id == NotificationCenter.newPeopleNearbyAvailable) { + TLRPC.TL_updatePeerLocated update = (TLRPC.TL_updatePeerLocated) args[0]; + for (int b = 0, N2 = update.peers.size(); b < N2; b++) { + TLRPC.TL_peerLocated peerLocated = update.peers.get(b); + boolean found = false; + ArrayList arrayList; + if (peerLocated.peer instanceof TLRPC.TL_peerUser) { + arrayList = users; + } else { + arrayList = chats; + } + for (int a = 0, N = arrayList.size(); a < N; a++) { + TLRPC.TL_peerLocated old = arrayList.get(a); + if (old.peer.user_id != 0 && old.peer.user_id == peerLocated.peer.user_id || old.peer.chat_id != 0 && old.peer.chat_id == peerLocated.peer.chat_id || old.peer.channel_id != 0 && old.peer.channel_id == peerLocated.peer.channel_id) { + arrayList.set(a, peerLocated); + found = true; + } + } + if (!found) { + arrayList.add(peerLocated); + } + } + checkForExpiredLocations(true); + updateRows(); + } else if (id == NotificationCenter.needDeleteDialog) { + if (fragmentView == null || isPaused) { + return; + } + long dialogId = (Long) args[0]; + TLRPC.User user = (TLRPC.User) args[1]; + TLRPC.Chat chat = (TLRPC.Chat) args[2]; + boolean revoke = (Boolean) args[3]; + Runnable deleteRunnable = () -> { + if (chat != null) { + if (ChatObject.isNotInChat(chat)) { + getMessagesController().deleteDialog(dialogId, 0, revoke); + } else { + getMessagesController().deleteUserFromChat((int) -dialogId, getMessagesController().getUser(getUserConfig().getClientUserId()), null, false, revoke); + } + } else { + getMessagesController().deleteDialog(dialogId, 0, revoke); + } + }; + if (undoView != null) { + undoView.showWithAction(dialogId, UndoView.ACTION_DELETE, deleteRunnable); + } else { + deleteRunnable.run(); + } + } + } + + private void checkForExpiredLocations(boolean cache) { + if (checkExpiredRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(checkExpiredRunnable); + checkExpiredRunnable = null; + } + int currentTime = getConnectionsManager().getCurrentTime(); + int minExpired = Integer.MAX_VALUE; + boolean changed = false; + for (int a = 0; a < 2; a++) { + ArrayList arrayList = a == 0 ? users : chats; + for (int b = 0, N = arrayList.size(); b < N; b++) { + TLRPC.TL_peerLocated peer = arrayList.get(b); + if (peer.expires <= currentTime) { + arrayList.remove(b); + b--; + N--; + changed = true; + } else { + minExpired = Math.min(minExpired, peer.expires); + } + } + } + if (changed && listViewAdapter != null) { + updateRows(); + } + if (changed || cache) { + getLocationController().setCachedNearbyUsersAndChats(users, chats); + } + if (minExpired != Integer.MAX_VALUE) { + AndroidUtilities.runOnUIThread(checkExpiredRunnable = () -> { + checkExpiredRunnable = null; + checkForExpiredLocations(false); + }, (minExpired - currentTime) * 1000); + } + } + + public class HeaderCellProgress extends HeaderCell { + + private RadialProgressView progressView; + + public HeaderCellProgress(Context context) { + super(context); + + setClipChildren(false); + + progressView = new RadialProgressView(context); + progressView.setSize(AndroidUtilities.dp(14)); + progressView.setStrokeWidth(2); + progressView.setAlpha(0.0f); + progressView.setProgressColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueHeader)); + addView(progressView, LayoutHelper.createFrame(50, 40, Gravity.TOP | (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT), LocaleController.isRTL ? 2 : 0, 3, LocaleController.isRTL ? 0 : 2, 0)); + } + } + + public class HintInnerCell extends FrameLayout { + + private ImageView imageView; + private TextView messageTextView; + + public HintInnerCell(Context context) { + super(context); + + imageView = new ImageView(context); + imageView.setBackgroundDrawable(Theme.createCircleDrawable(AndroidUtilities.dp(74), Theme.getColor(Theme.key_chats_archiveBackground))); + imageView.setImageDrawable(new ShareLocationDrawable(context, 2)); + imageView.setScaleType(ImageView.ScaleType.CENTER); + addView(imageView, LayoutHelper.createFrame(74, 74, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 27, 0, 0)); + + messageTextView = new TextView(context); + messageTextView.setTextColor(Theme.getColor(Theme.key_chats_message)); + messageTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + messageTextView.setGravity(Gravity.CENTER); + messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("PeopleNearbyInfo", R.string.PeopleNearbyInfo))); + addView(messageTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 52, 125, 52, 27)); + } + } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { + + private Context mContext; + + public ListAdapter(Context context) { + mContext = context; + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int type = holder.getItemViewType(); + return type == 0 || type == 2; + } + + @Override + public int getItemCount() { + return rowCount; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new ManageChatUserCell(mContext, 6, 2, false); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: + view = new ShadowSectionCell(mContext, 22); + break; + case 2: + view = new ManageChatTextCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 3: + view = new HeaderCellProgress(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 4: + TextView textView = new TextView(mContext) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(67), MeasureSpec.EXACTLY)); + } + }; + textView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + textView.setPadding(0, 0, AndroidUtilities.dp(3), 0); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setGravity(Gravity.CENTER); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3)); + view = textView; + break; + case 5: + default: + view = new HintInnerCell(mContext); + break; + } + view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + return new RecyclerListView.Holder(view); + } + + @Override + public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { + if (holder.getItemViewType() == 3 && !animatingViews.contains(holder.itemView)) { + HeaderCellProgress cell = (HeaderCellProgress) holder.itemView; + cell.progressView.setAlpha(showingLoadingProgress ? 1.0f : 0.0f); + } + } + + private String formatDistance(TLRPC.TL_peerLocated located) { + return LocaleController.formatDistance(located.distance); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: + ManageChatUserCell userCell = (ManageChatUserCell) holder.itemView; + userCell.setTag(position); + if (position >= usersStartRow && position < usersEndRow) { + int index = position - usersStartRow; + TLRPC.TL_peerLocated peerLocated = users.get(index); + TLRPC.User user = getMessagesController().getUser(peerLocated.peer.user_id); + if (user != null) { + userCell.setData(user, null, formatDistance(peerLocated), index != users.size() - 1); + } + } else if (position >= chatsStartRow && position < chatsEndRow) { + int index = position - chatsStartRow; + TLRPC.TL_peerLocated peerLocated = chats.get(index); + int chatId; + if (peerLocated.peer instanceof TLRPC.TL_peerChat) { + chatId = peerLocated.peer.chat_id; + } else { + chatId = peerLocated.peer.channel_id; + } + TLRPC.Chat chat = getMessagesController().getChat(chatId); + if (chat != null) { + String subtitle = formatDistance(peerLocated); + if (chat.participants_count != 0) { + subtitle = String.format("%1$s, %2$s", subtitle, LocaleController.formatPluralString("Members", chat.participants_count)); + } + userCell.setData(chat, null, subtitle, index != chats.size() - 1); + } + } + break; + case 1: + ShadowSectionCell privacyCell = (ShadowSectionCell) holder.itemView; + if (position == usersSectionRow) { + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else if (position == chatsSectionRow) { + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } + break; + case 2: + ManageChatTextCell actionCell = (ManageChatTextCell) holder.itemView; + actionCell.setColors(Theme.key_windowBackgroundWhiteBlueIcon, Theme.key_windowBackgroundWhiteBlueButton); + if (position == chatsCreateRow) { + actionCell.setText(LocaleController.getString("NearbyCreateGroup", R.string.NearbyCreateGroup), null, R.drawable.groups_create, chatsStartRow != -1); + } + break; + case 3: + HeaderCellProgress headerCell = (HeaderCellProgress) holder.itemView; + if (position == usersHeaderRow) { + headerCell.setText(LocaleController.getString("PeopleNearbyHeader", R.string.PeopleNearbyHeader)); + } else if (position == chatsHeaderRow) { + headerCell.setText(LocaleController.getString("ChatsNearbyHeader", R.string.ChatsNearbyHeader)); + } + break; + case 4: + TextView textView = (TextView) holder.itemView; + if (position == usersEmptyRow) { + textView.setText(AndroidUtilities.replaceTags(LocaleController.getString("PeopleNearbyEmpty", R.string.PeopleNearbyEmpty))); + } + break; + } + } + + @Override + public void onViewRecycled(RecyclerView.ViewHolder holder) { + if (holder.itemView instanceof ManageChatUserCell) { + ((ManageChatUserCell) holder.itemView).recycle(); + } + } + + @Override + public int getItemViewType(int position) { + if (position == helpRow) { + return 5; + } else if (position == chatsCreateRow) { + return 2; + } else if (position == usersHeaderRow || position == chatsHeaderRow) { + return 3; + } else if (position == usersSectionRow || position == chatsSectionRow) { + return 1; + } else if (position == usersEmptyRow) { + return 4; + } + return 0; + } + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate cellDelegate = () -> { + if (listView != null) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof ManageChatUserCell) { + ((ManageChatUserCell) child).update(0); + } + } + } + }; + + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{ManageChatUserCell.class, ManageChatTextCell.class, HeaderCell.class, TextView.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundGray), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + new ThemeDescription(listView, ThemeDescription.FLAG_PROGRESSBAR, new Class[]{HeaderCellProgress.class}, new String[]{"progressView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + + new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, new String[]{"statusColor"}, null, null, cellDelegate, Theme.key_windowBackgroundWhiteGrayText), + new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, new String[]{"statusOnlineColor"}, null, null, cellDelegate, Theme.key_windowBackgroundWhiteBlueText), + new ThemeDescription(listView, 0, new Class[]{ManageChatUserCell.class}, null, new Drawable[]{Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundPink), + + new ThemeDescription(listView, ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, new Class[]{HintInnerCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_chats_archiveBackground), + new ThemeDescription(listView, 0, new Class[]{HintInnerCell.class}, new String[]{"messageTextView"}, null, null, null, Theme.key_chats_message), + + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{ManageChatTextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{ManageChatTextCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayIcon), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{ManageChatTextCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueButton), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{ManageChatTextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueIcon), + + new ThemeDescription(undoView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_undo_background), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"undoImageView"}, null, null, null, Theme.key_undo_cancelColor), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"undoTextView"}, null, null, null, Theme.key_undo_cancelColor), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"infoTextView"}, null, null, null, Theme.key_undo_infoColor), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"subinfoTextView"}, null, null, null, Theme.key_undo_infoColor), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"textPaint"}, null, null, null, Theme.key_undo_infoColor), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"progressPaint"}, null, null, null, Theme.key_undo_infoColor), + }; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java index 73e7f8016..c1fb4ea0b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java @@ -405,7 +405,7 @@ public class PhotoCropActivity extends BaseFragment { public void onFragmentDestroy() { super.onFragmentDestroy(); if (bitmapKey != null) { - if (ImageLoader.getInstance().decrementUseCount(bitmapKey) && !ImageLoader.getInstance().isInCache(bitmapKey)) { + if (ImageLoader.getInstance().decrementUseCount(bitmapKey) && !ImageLoader.getInstance().isInMemCache(bitmapKey, false)) { bitmapKey = null; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java index 933d6ae5d..c9a9680e1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java @@ -97,7 +97,7 @@ import org.telegram.messenger.BuildConfig; import org.telegram.messenger.BuildVars; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.Emoji; import org.telegram.messenger.ImageLoader; import org.telegram.messenger.ImageLocation; @@ -1495,7 +1495,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (needSearchImageInArr && isFirstLoading) { isFirstLoading = false; loadingMoreImages = true; - DataQuery.getInstance(currentAccount).loadMedia(currentDialogId, 80, 0, sharedMediaType, 1, classGuid); + MediaDataController.getInstance(currentAccount).loadMedia(currentDialogId, 80, 0, sharedMediaType, 1, classGuid); } else if (!imagesArr.isEmpty()) { if (opennedFromMedia) { actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, currentIndex + 1, totalImagesCount + totalImagesCountMerge)); @@ -1583,9 +1583,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (!endReached[loadIndex]) { loadingMoreImages = true; if (opennedFromMedia) { - DataQuery.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 80, loadFromMaxId, sharedMediaType, 1, classGuid); + MediaDataController.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 80, loadFromMaxId, sharedMediaType, 1, classGuid); } else { - DataQuery.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 80, loadFromMaxId, sharedMediaType, 1, classGuid); + MediaDataController.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 80, loadFromMaxId, sharedMediaType, 1, classGuid); } } } @@ -3171,8 +3171,8 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } else if (object instanceof String) { captionEditText.replaceWithText(start, len, object + " ", false); - } else if (object instanceof DataQuery.KeywordResult) { - String code = ((DataQuery.KeywordResult) object).emoji; + } else if (object instanceof MediaDataController.KeywordResult) { + String code = ((MediaDataController.KeywordResult) object).emoji; captionEditText.addEmojiToRecent(code); captionEditText.replaceWithText(start, len, code, true); } @@ -3592,7 +3592,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat CharSequence caption = captionEditText.getFieldCharSequence(); CharSequence[] result = new CharSequence[] {caption}; - ArrayList entities = DataQuery.getInstance(currentAccount).getEntities(result); + ArrayList entities = MediaDataController.getInstance(currentAccount).getEntities(result); if (object instanceof MediaController.PhotoEntry) { MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) object; photoEntry.caption = result[0]; @@ -4077,6 +4077,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } else { captionEditText.setFieldText(caption); } + captionEditText.setAllowTextEntitiesIntersection(parentChatActivity != null && (parentChatActivity.currentEncryptedChat != null && AndroidUtilities.getPeerLayerVersion(parentChatActivity.currentEncryptedChat.layer) >= 101)); } public void showAlertDialog(AlertDialog.Builder builder) { @@ -5164,7 +5165,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } currentThumb = object != null ? object.thumb : null; isEvent = object != null && object.isEvent; - sharedMediaType = DataQuery.MEDIA_PHOTOVIDEO; + sharedMediaType = MediaDataController.MEDIA_PHOTOVIDEO; allMediaItem.setText(LocaleController.getString("ShowAllMedia", R.string.ShowAllMedia)); menuItem.setVisibility(View.VISIBLE); sendItem.setVisibility(View.GONE); @@ -5275,7 +5276,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } if (messageObject.canPreviewDocument()) { - sharedMediaType = DataQuery.MEDIA_FILE; + sharedMediaType = MediaDataController.MEDIA_FILE; allMediaItem.setText(LocaleController.getString("ShowAllFiles", R.string.ShowAllFiles)); } if (slideshowMessageId == 0) { @@ -5331,7 +5332,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } MessageObject openingObject = imagesArr.get(index); if (openingObject.canPreviewDocument()) { - sharedMediaType = DataQuery.MEDIA_FILE; + sharedMediaType = MediaDataController.MEDIA_FILE; allMediaItem.setText(LocaleController.getString("ShowAllFiles", R.string.ShowAllFiles)); } setImageIndex(index, true); @@ -5403,9 +5404,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (currentAnimation == null && !isEvent) { if (currentDialogId != 0 && totalImagesCount == 0) { - DataQuery.getInstance(currentAccount).getMediaCount(currentDialogId, sharedMediaType, classGuid, true); + MediaDataController.getInstance(currentAccount).getMediaCount(currentDialogId, sharedMediaType, classGuid, true); if (mergeDialogId != 0) { - DataQuery.getInstance(currentAccount).getMediaCount(mergeDialogId, sharedMediaType, classGuid, true); + MediaDataController.getInstance(currentAccount).getMediaCount(mergeDialogId, sharedMediaType, classGuid, true); } } else if (avatarsDialogId != 0) { MessagesController.getInstance(currentAccount).loadDialogPhotos(avatarsDialogId, 80, 0, true, classGuid); @@ -5557,7 +5558,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } - DataQuery.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 80, loadFromMaxId, sharedMediaType, 1, classGuid); + MediaDataController.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 80, loadFromMaxId, sharedMediaType, 1, classGuid); loadingMoreImages = true; } actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, switchingToIndex + 1, totalImagesCount + totalImagesCountMerge)); @@ -5572,7 +5573,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } - DataQuery.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 80, loadFromMaxId, sharedMediaType, 1, classGuid); + MediaDataController.getInstance(currentAccount).loadMedia(loadIndex == 0 ? currentDialogId : mergeDialogId, 80, loadFromMaxId, sharedMediaType, 1, classGuid); loadingMoreImages = true; } actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, (totalImagesCount + totalImagesCountMerge - imagesArr.size()) + switchingToIndex + 1, totalImagesCount + totalImagesCountMerge)); @@ -5807,7 +5808,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat sameImage = init && currentMessageObject != null && currentMessageObject.getId() == newMessageObject.getId(); currentMessageObject = newMessageObject; isVideo = newMessageObject.isVideo(); - if (sharedMediaType == DataQuery.MEDIA_FILE) { + if (sharedMediaType == MediaDataController.MEDIA_FILE) { if (canZoom = newMessageObject.canPreviewDocument()) { menuItem.showSubItem(gallery_menu_save); setDoubleTapEnabled(true); @@ -5910,7 +5911,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat canDragDown = true; changingPage = false; switchImageAfterAnimation = 0; - if (sharedMediaType != DataQuery.MEDIA_FILE) { + if (sharedMediaType != MediaDataController.MEDIA_FILE) { canZoom = !imagesArrLocals.isEmpty() || (currentFileNames[0] != null && /*!isVideo && */photoProgressViews[0].backgroundState != 0); } updateMinMax(scale); @@ -6096,7 +6097,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat return; } MessageObject messageObject = imagesArr.get(index); - if (sharedMediaType == DataQuery.MEDIA_FILE && !messageObject.canPreviewDocument()) { + if (sharedMediaType == MediaDataController.MEDIA_FILE && !messageObject.canPreviewDocument()) { photoProgressViews[a].setBackgroundState(-1, animated); return; } @@ -6356,7 +6357,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat imageReceiver.setImageBitmap(currentAnimation); currentAnimation.setSecondParentView(containerView); return; - } else if (sharedMediaType == DataQuery.MEDIA_FILE) { + } else if (sharedMediaType == MediaDataController.MEDIA_FILE) { if (messageObject.canPreviewDocument()) { TLRPC.Document document = messageObject.getDocument(); imageReceiver.setNeedsQualityThumb(true); @@ -8167,7 +8168,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } } else { - if (sharedMediaType == DataQuery.MEDIA_FILE && !currentMessageObject.canPreviewDocument()) { + if (sharedMediaType == MediaDataController.MEDIA_FILE && !currentMessageObject.canPreviewDocument()) { AndroidUtilities.openDocument(currentMessageObject, parentActivity, null); return; } @@ -8222,7 +8223,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat boolean drawTextureView = aspectRatioFrameLayout != null && aspectRatioFrameLayout.getVisibility() == View.VISIBLE; float x = e.getX(); float y = e.getY(); - if (sharedMediaType == DataQuery.MEDIA_FILE && currentMessageObject != null) { + if (sharedMediaType == MediaDataController.MEDIA_FILE && currentMessageObject != null) { if (!currentMessageObject.canPreviewDocument()) { float vy = (getContainerViewHeight() - AndroidUtilities.dp(360)) / 2.0f; if (y >= vy && y <= vy + AndroidUtilities.dp(360)) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PollCreateActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PollCreateActivity.java index c3df0cd82..f38218fba 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PollCreateActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PollCreateActivity.java @@ -342,22 +342,22 @@ public class PollCreateActivity extends BaseFragment { progressView.setVisibility(View.VISIBLE); doneItem.setEnabled(false); doneItemAnimation.playTogether( - ObjectAnimator.ofFloat(doneItem.getImageView(), View.SCALE_X, 0.1f), - ObjectAnimator.ofFloat(doneItem.getImageView(), View.SCALE_Y, 0.1f), - ObjectAnimator.ofFloat(doneItem.getImageView(), View.ALPHA, 0.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), View.SCALE_X, 0.1f), + ObjectAnimator.ofFloat(doneItem.getContentView(), View.SCALE_Y, 0.1f), + ObjectAnimator.ofFloat(doneItem.getContentView(), View.ALPHA, 0.0f), ObjectAnimator.ofFloat(progressView, View.SCALE_X, 1.0f), ObjectAnimator.ofFloat(progressView, View.SCALE_Y, 1.0f), ObjectAnimator.ofFloat(progressView, View.ALPHA, 1.0f)); } else { - doneItem.getImageView().setVisibility(View.VISIBLE); + doneItem.getContentView().setVisibility(View.VISIBLE); doneItem.setEnabled(true); doneItemAnimation.playTogether( ObjectAnimator.ofFloat(progressView, View.SCALE_X, 0.1f), ObjectAnimator.ofFloat(progressView, View.SCALE_Y, 0.1f), ObjectAnimator.ofFloat(progressView, View.ALPHA, 0.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), View.SCALE_X, 1.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), View.SCALE_Y, 1.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), View.ALPHA, 1.0f)); + ObjectAnimator.ofFloat(doneItem.getContentView(), View.SCALE_X, 1.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), View.SCALE_Y, 1.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), View.ALPHA, 1.0f)); } doneItemAnimation.addListener(new AnimatorListenerAdapter() { @Override @@ -366,7 +366,7 @@ public class PollCreateActivity extends BaseFragment { if (!show) { progressView.setVisibility(View.INVISIBLE); } else { - doneItem.getImageView().setVisibility(View.INVISIBLE); + doneItem.getContentView().setVisibility(View.INVISIBLE); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java index 88b7eb355..ca52fa754 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java @@ -17,7 +17,7 @@ import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ContactsController; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; @@ -128,7 +128,7 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio } if (newSuggest != currentSuggest) { if (!newSuggest) { - DataQuery.getInstance(currentAccount).clearTopPeers(); + MediaDataController.getInstance(currentAccount).clearTopPeers(); } UserConfig.getInstance(currentAccount).suggestContacts = newSuggest; UserConfig.getInstance(currentAccount).saveConfig(false); @@ -188,7 +188,7 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio builder.setMessage(LocaleController.getString("AreYouSureClearDrafts", R.string.AreYouSureClearDrafts)); builder.setPositiveButton(LocaleController.getString("Delete", R.string.Delete), (dialogInterface, i) -> { TLRPC.TL_messages_clearAllDrafts req = new TLRPC.TL_messages_clearAllDrafts(); - ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> DataQuery.getInstance(currentAccount).clearAllDrafts())); + ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> MediaDataController.getInstance(currentAccount).clearAllDrafts())); }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showDialog(builder.create()); @@ -625,7 +625,7 @@ public class PrivacySettingsActivity extends BaseFragment implements Notificatio if (position == blockedRow) { if (!getMessagesController().loadingBlockedUsers) { if (getMessagesController().blockedUsers.size() == 0) { - textCell.setTextAndValue(LocaleController.getString("BlockedUsers", R.string.BlockedUsers), LocaleController.getString("EmptyExceptions", R.string.EmptyExceptions), true); + textCell.setTextAndValue(LocaleController.getString("BlockedUsers", R.string.BlockedUsers), LocaleController.getString("BlockedEmpty", R.string.BlockedEmpty), true); } else { textCell.setTextAndValue(LocaleController.getString("BlockedUsers", R.string.BlockedUsers), String.format("%d", getMessagesController().blockedUsers.size()), true); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java index 07dd389a5..2826f1b29 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java @@ -153,13 +153,7 @@ public class PrivacyUsersActivity extends BaseFragment implements NotificationCe listView.setOnItemClickListener((view, position) -> { if (position == blockUserRow) { if (blockedUsersActivity) { - Bundle args = new Bundle(); - args.putBoolean("onlyUsers", true); - args.putBoolean("destroyAfterSelect", true); - args.putBoolean("returnAsResult", true); - ContactsActivity fragment = new ContactsActivity(args); - fragment.setDelegate(PrivacyUsersActivity.this); - presentFragment(fragment); + presentFragment(new DialogOrContactPickerActivity()); } else { Bundle args = new Bundle(); args.putBoolean(isAlwaysShare ? "isAlwaysShare" : "isNeverShare", true); @@ -406,6 +400,8 @@ public class PrivacyUsersActivity extends BaseFragment implements NotificationCe String subtitle; if (chat.participants_count != 0) { 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)) { subtitle = LocaleController.getString("MegaPrivate", R.string.MegaPrivate); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java index 994237496..a8c15e862 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java @@ -55,7 +55,7 @@ import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.ChatObject; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.FileLoader; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocaleController; @@ -102,6 +102,7 @@ import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.ScamDrawable; +import org.telegram.ui.Components.UndoView; import org.telegram.ui.Components.voip.VoIPHelper; import java.util.ArrayList; @@ -120,6 +121,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private AnimatorSet writeButtonAnimation; private ScamDrawable scamDrawable; private MediaActivity mediaActivity; + private UndoView undoView; private boolean[] isOnline = new boolean[1]; @@ -133,6 +135,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private long dialog_id; private boolean creatingChat; private boolean userBlocked; + private boolean reportSpam; private long mergeDialogId; private int[] mediaCount = new int[]{-1, -1, -1, -1, -1}; @@ -189,6 +192,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private int emptyRow; private int infoHeaderRow; private int phoneRow; + private int locationRow; private int userInfoRow; private int channelInfoRow; private int usernameRow; @@ -314,6 +318,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. user_id = arguments.getInt("user_id", 0); chat_id = arguments.getInt("chat_id", 0); banFromGroup = arguments.getInt("ban_chat_id", 0); + reportSpam = arguments.getBoolean("reportSpam", false); if (user_id != 0) { dialog_id = arguments.getLong("dialog_id", 0); if (dialog_id != 0) { @@ -333,7 +338,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. userBlocked = MessagesController.getInstance(currentAccount).blockedUsers.indexOfKey(user_id) >= 0; if (user.bot) { isBot = true; - DataQuery.getInstance(currentAccount).loadBotInfo(user.id, true, classGuid); + MediaDataController.getInstance(currentAccount).loadBotInfo(user.id, true, classGuid); } userInfo = MessagesController.getInstance(currentAccount).getUserFull(user_id); MessagesController.getInstance(currentAccount).loadFullUser(MessagesController.getInstance(currentAccount).getUser(user_id), classGuid, true); @@ -467,19 +472,32 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. MessagesController.getInstance(currentAccount).unblockUser(user_id); AlertsCreator.showSimpleToast(ProfileActivity.this, LocaleController.getString("UserUnblocked", R.string.UserUnblocked)); } else { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("BlockUser", R.string.BlockUser)); - builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("AreYouSureBlockContact2", R.string.AreYouSureBlockContact2, ContactsController.formatName(user.first_name, user.last_name)))); - builder.setPositiveButton(LocaleController.getString("BlockContact", R.string.BlockContact), (dialogInterface, i) -> { - MessagesController.getInstance(currentAccount).blockUser(user_id); - AlertsCreator.showSimpleToast(ProfileActivity.this, LocaleController.getString("UserBlocked", R.string.UserBlocked)); - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - AlertDialog dialog = builder.create(); - showDialog(dialog); - TextView button = (TextView) dialog.getButton(DialogInterface.BUTTON_POSITIVE); - if (button != null) { - button.setTextColor(Theme.getColor(Theme.key_dialogTextRed2)); + if (reportSpam) { + AlertsCreator.showBlockReportSpamAlert(ProfileActivity.this, user_id, user, null, currentEncryptedChat, false, null, param -> { + if (param == 1) { + NotificationCenter.getInstance(currentAccount).removeObserver(ProfileActivity.this, NotificationCenter.closeChats); + NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.closeChats); + playProfileAnimation = false; + finishFragment(); + } else { + getNotificationCenter().postNotificationName(NotificationCenter.peerSettingsDidLoad, (long) user_id); + } + }); + } else { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("BlockUser", R.string.BlockUser)); + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("AreYouSureBlockContact2", R.string.AreYouSureBlockContact2, ContactsController.formatName(user.first_name, user.last_name)))); + builder.setPositiveButton(LocaleController.getString("BlockContact", R.string.BlockContact), (dialogInterface, i) -> { + MessagesController.getInstance(currentAccount).blockUser(user_id); + AlertsCreator.showSimpleToast(ProfileActivity.this, LocaleController.getString("UserBlocked", R.string.UserBlocked)); + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + AlertDialog dialog = builder.create(); + showDialog(dialog); + TextView button = (TextView) dialog.getButton(DialogInterface.BUTTON_POSITIVE); + if (button != null) { + button.setTextColor(Theme.getColor(Theme.key_dialogTextRed2)); + } } } } else { @@ -592,7 +610,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else { return; } - DataQuery.getInstance(currentAccount).installShortcut(did); + MediaDataController.getInstance(currentAccount).installShortcut(did); } catch (Exception e) { FileLog.e(e); } @@ -682,6 +700,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. holder = findViewHolderForAdapterPosition(sharedSectionRow); } else if (membersSectionRow != -1 && (sharedSectionRow == -1 || membersSectionRow > sharedSectionRow)) { holder = findViewHolderForAdapterPosition(membersSectionRow); + } else if (settingsSectionRow != -1) { + holder = findViewHolderForAdapterPosition(settingsSectionRow); } else if (infoSectionRow != -1) { holder = findViewHolderForAdapterPosition(infoSectionRow); } else { @@ -729,15 +749,15 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (position == photosRow || position == filesRow || position == linksRow || position == audioRow || position == voiceRow) { int tab; if (position == photosRow) { - tab = DataQuery.MEDIA_PHOTOVIDEO; + tab = MediaDataController.MEDIA_PHOTOVIDEO; } else if (position == filesRow) { - tab = DataQuery.MEDIA_FILE; + tab = MediaDataController.MEDIA_FILE; } else if (position == linksRow) { - tab = DataQuery.MEDIA_URL; + tab = MediaDataController.MEDIA_URL; } else if (position == audioRow) { - tab = DataQuery.MEDIA_MUSIC; + tab = MediaDataController.MEDIA_MUSIC; } else { - tab = DataQuery.MEDIA_AUDIO; + tab = MediaDataController.MEDIA_AUDIO; } Bundle args = new Bundle(); if (user_id != 0) { @@ -745,7 +765,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else { args.putLong("dialog_id", -chat_id); } - int[] media = new int[DataQuery.MEDIA_TYPES_COUNT]; + int[] media = new int[MediaDataController.MEDIA_TYPES_COUNT]; System.arraycopy(lastMediaCount, 0, media, 0, media.length); mediaActivity = new MediaActivity(args, media, sharedMediaData, tab); mediaActivity.setChatInfo(chatInfo); @@ -864,6 +884,12 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. FileLog.e(e); } } + } else if (position == locationRow) { + if (chatInfo.location instanceof TLRPC.TL_channelLocation) { + LocationActivity fragment = new LocationActivity(LocationActivity.LOCATION_TYPE_GROUP_VIEW); + fragment.setChatLocation(chat_id, (TLRPC.TL_channelLocation) chatInfo.location); + presentFragment(fragment); + } } else if (position == leaveChannelRow) { leaveChatPressed(); } else if (position == joinRow) { @@ -921,6 +947,9 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. channelParticipant = ((TLRPC.TL_chatChannelParticipant) participant).channelParticipant; TLRPC.User u = MessagesController.getInstance(currentAccount).getUser(participant.user_id); canEditAdmin = ChatObject.canAddAdmins(currentChat); + if (canEditAdmin && (channelParticipant instanceof TLRPC.TL_channelParticipantCreator || channelParticipant instanceof TLRPC.TL_channelParticipantAdmin && !channelParticipant.can_edit)) { + canEditAdmin = false; + } allowKick = canRestrict = ChatObject.canBlockUsers(currentChat) && (!(channelParticipant instanceof TLRPC.TL_channelParticipantAdmin || channelParticipant instanceof TLRPC.TL_channelParticipantCreator) || channelParticipant.can_edit); editingAdmin = channelParticipant instanceof TLRPC.TL_channelParticipantAdmin; } else { @@ -1011,7 +1040,17 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. frameLayout.addView(frameLayout1, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 51, Gravity.LEFT | Gravity.BOTTOM)); frameLayout1.setOnClickListener(v -> { ChatRightsEditActivity fragment = new ChatRightsEditActivity(user_id, banFromGroup, null, chat.default_banned_rights, currentChannelParticipant != null ? currentChannelParticipant.banned_rights : null, ChatRightsEditActivity.TYPE_BANNED, true, false); - fragment.setDelegate((rights, rightsAdmin, rightsBanned) -> removeSelfFromStack()); + fragment.setDelegate(new ChatRightsEditActivity.ChatRightsEditActivityDelegate() { + @Override + public void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned) { + removeSelfFromStack(); + } + + @Override + public void didChangeOwner(TLRPC.User user) { + undoView.showWithAction(-chat_id, currentChat.megagroup ? UndoView.ACTION_OWNER_TRANSFERED_GROUP : UndoView.ACTION_OWNER_TRANSFERED_CHANNEL, user); + } + }); presentFragment(fragment); }); @@ -1154,73 +1193,84 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } }); + undoView = new UndoView(context); + frameLayout.addView(undoView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT, 8, 0, 8, 8)); + return fragmentView; } private void openRightsEdit(int action, int user_id, TLRPC.ChatParticipant participant, TLRPC.TL_chatAdminRights adminRights, TLRPC.TL_chatBannedRights bannedRights) { ChatRightsEditActivity fragment = new ChatRightsEditActivity(user_id, chat_id, adminRights, currentChat.default_banned_rights, bannedRights, action, true, false); - fragment.setDelegate((rights, rightsAdmin, rightsBanned) -> { - if (action == 0) { - if (participant instanceof TLRPC.TL_chatChannelParticipant) { - TLRPC.TL_chatChannelParticipant channelParticipant1 = ((TLRPC.TL_chatChannelParticipant) participant); - if (rights == 1) { - channelParticipant1.channelParticipant = new TLRPC.TL_channelParticipantAdmin(); - } else { - channelParticipant1.channelParticipant = new TLRPC.TL_channelParticipant(); - } - channelParticipant1.channelParticipant.inviter_id = UserConfig.getInstance(currentAccount).getClientUserId(); - channelParticipant1.channelParticipant.user_id = participant.user_id; - channelParticipant1.channelParticipant.date = participant.date; - channelParticipant1.channelParticipant.banned_rights = rightsBanned; - channelParticipant1.channelParticipant.admin_rights = rightsAdmin; - } else if (participant instanceof TLRPC.ChatParticipant) { - TLRPC.ChatParticipant newParticipant; - if (rights == 1) { - newParticipant = new TLRPC.TL_chatParticipantAdmin(); - } else { - newParticipant = new TLRPC.TL_chatParticipant(); - } - newParticipant.user_id = participant.user_id; - newParticipant.date = participant.date; - newParticipant.inviter_id = participant.inviter_id; - int index = chatInfo.participants.participants.indexOf(participant); - if (index >= 0) { - chatInfo.participants.participants.set(index, newParticipant); - } - } - } else if (action == 1) { - if (rights == 0) { - if (currentChat.megagroup && chatInfo != null && chatInfo.participants != null) { - boolean changed = false; - for (int a = 0; a < chatInfo.participants.participants.size(); a++) { - TLRPC.ChannelParticipant p = ((TLRPC.TL_chatChannelParticipant) chatInfo.participants.participants.get(a)).channelParticipant; - if (p.user_id == participant.user_id) { - if (chatInfo != null) { - chatInfo.participants_count--; - } - chatInfo.participants.participants.remove(a); - changed = true; - break; - } + fragment.setDelegate(new ChatRightsEditActivity.ChatRightsEditActivityDelegate() { + @Override + public void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned) { + if (action == 0) { + if (participant instanceof TLRPC.TL_chatChannelParticipant) { + TLRPC.TL_chatChannelParticipant channelParticipant1 = ((TLRPC.TL_chatChannelParticipant) participant); + if (rights == 1) { + channelParticipant1.channelParticipant = new TLRPC.TL_channelParticipantAdmin(); + } else { + channelParticipant1.channelParticipant = new TLRPC.TL_channelParticipant(); } - if (chatInfo != null && chatInfo.participants != null) { + channelParticipant1.channelParticipant.inviter_id = UserConfig.getInstance(currentAccount).getClientUserId(); + channelParticipant1.channelParticipant.user_id = participant.user_id; + channelParticipant1.channelParticipant.date = participant.date; + channelParticipant1.channelParticipant.banned_rights = rightsBanned; + channelParticipant1.channelParticipant.admin_rights = rightsAdmin; + } else if (participant instanceof TLRPC.ChatParticipant) { + TLRPC.ChatParticipant newParticipant; + if (rights == 1) { + newParticipant = new TLRPC.TL_chatParticipantAdmin(); + } else { + newParticipant = new TLRPC.TL_chatParticipant(); + } + newParticipant.user_id = participant.user_id; + newParticipant.date = participant.date; + newParticipant.inviter_id = participant.inviter_id; + int index = chatInfo.participants.participants.indexOf(participant); + if (index >= 0) { + chatInfo.participants.participants.set(index, newParticipant); + } + } + } else if (action == 1) { + if (rights == 0) { + if (currentChat.megagroup && chatInfo != null && chatInfo.participants != null) { + boolean changed = false; for (int a = 0; a < chatInfo.participants.participants.size(); a++) { - TLRPC.ChatParticipant p = chatInfo.participants.participants.get(a); + TLRPC.ChannelParticipant p = ((TLRPC.TL_chatChannelParticipant) chatInfo.participants.participants.get(a)).channelParticipant; if (p.user_id == participant.user_id) { + if (chatInfo != null) { + chatInfo.participants_count--; + } chatInfo.participants.participants.remove(a); changed = true; break; } } - } - if (changed) { - updateOnlineCount(); - updateRowsIds(); - listAdapter.notifyDataSetChanged(); + if (chatInfo != null && chatInfo.participants != null) { + for (int a = 0; a < chatInfo.participants.participants.size(); a++) { + TLRPC.ChatParticipant p = chatInfo.participants.participants.get(a); + if (p.user_id == participant.user_id) { + chatInfo.participants.participants.remove(a); + changed = true; + break; + } + } + } + if (changed) { + updateOnlineCount(); + updateRowsIds(); + listAdapter.notifyDataSetChanged(); + } } } } } + + @Override + public void didChangeOwner(TLRPC.User user) { + undoView.showWithAction(-chat_id, currentChat.megagroup ? UndoView.ACTION_OWNER_TRANSFERED_GROUP : UndoView.ACTION_OWNER_TRANSFERED_CHANNEL, user); + } }); presentFragment(fragment); } @@ -1300,12 +1350,14 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. }); showDialog(builder.create()); return true; - } else if (position == channelInfoRow || position == userInfoRow) { + } else if (position == channelInfoRow || position == userInfoRow || position == locationRow) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setItems(new CharSequence[]{LocaleController.getString("Copy", R.string.Copy)}, (dialogInterface, i) -> { try { String about; - if (position == channelInfoRow) { + if (position == locationRow) { + about = chatInfo != null && chatInfo.location instanceof TLRPC.TL_channelLocation ? ((TLRPC.TL_channelLocation) chatInfo.location).address : null; + } else if (position == channelInfoRow) { about = chatInfo != null ? chatInfo.about : null; } else { about = userInfo != null ? userInfo.about : null; @@ -1387,6 +1439,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. args.putBoolean("addToGroup", true); args.putInt("chatId", currentChat.id); GroupCreateActivity fragment = new GroupCreateActivity(args); + fragment.setInfo(chatInfo); if (chatInfo != null && chatInfo.participants != null) { SparseArray users = new SparseArray<>(); for (int a = 0; a < chatInfo.participants.participants.size(); a++) { @@ -1540,13 +1593,13 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private void loadMediaCounts() { if (dialog_id != 0) { - DataQuery.getInstance(currentAccount).getMediaCounts(dialog_id, classGuid); + MediaDataController.getInstance(currentAccount).getMediaCounts(dialog_id, classGuid); } else if (user_id != 0) { - DataQuery.getInstance(currentAccount).getMediaCounts(user_id, classGuid); + MediaDataController.getInstance(currentAccount).getMediaCounts(user_id, classGuid); } else if (chat_id > 0) { - DataQuery.getInstance(currentAccount).getMediaCounts(-chat_id, classGuid); + MediaDataController.getInstance(currentAccount).getMediaCounts(-chat_id, classGuid); if (mergeDialogId != 0) { - DataQuery.getInstance(currentAccount).getMediaCounts(mergeDialogId, classGuid); + MediaDataController.getInstance(currentAccount).getMediaCounts(mergeDialogId, classGuid); } } } @@ -1679,7 +1732,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. lastMediaCount[a] = 0; } if (uid == did && lastMediaCount[a] != 0) { - DataQuery.getInstance(currentAccount).loadMedia(did, 50, 0, a, 2, classGuid); + MediaDataController.getInstance(currentAccount).loadMedia(did, 50, 0, a, 2, classGuid); } } updateSharedMediaRows(); @@ -1755,7 +1808,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. chatInfo = chatFull; if (mergeDialogId == 0 && chatInfo.migrated_from_chat_id != 0) { mergeDialogId = -chatInfo.migrated_from_chat_id; - DataQuery.getInstance(currentAccount).getMediaCount(mergeDialogId, DataQuery.MEDIA_PHOTOVIDEO, classGuid, true); + MediaDataController.getInstance(currentAccount).getMediaCount(mergeDialogId, MediaDataController.MEDIA_PHOTOVIDEO, classGuid, true); } fetchUsersFromChannelInfo(); updateOnlineCount(); @@ -1818,7 +1871,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } } - int type = DataQuery.getMediaType(obj.messageOwner); + int type = MediaDataController.getMediaType(obj.messageOwner); if (type == -1) { return; } @@ -1865,6 +1918,21 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } } + @Override + public void onPause() { + super.onPause(); + if (undoView != null) { + undoView.hide(true, 0); + } + } + + @Override + protected void onBecomeFullyHidden() { + if (undoView != null) { + undoView.hide(true, 0); + } + } + public void setPlayProfileAnimation(boolean value) { SharedPreferences preferences = MessagesController.getGlobalMainSettings(); if (!AndroidUtilities.isTablet() && preferences.getBoolean("view_animations", true)) { @@ -1907,19 +1975,19 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } listAdapter.notifyItemRangeInserted(sharedHeaderRow, newRowsCount); } else if (sharedHeaderRowPrev != -1 && sharedHeaderRow != -1) { - if (photosRowPrev != -1 && photosRow != -1 && prevMediaCount[DataQuery.MEDIA_PHOTOVIDEO] != lastMediaCount[DataQuery.MEDIA_PHOTOVIDEO]) { + if (photosRowPrev != -1 && photosRow != -1 && prevMediaCount[MediaDataController.MEDIA_PHOTOVIDEO] != lastMediaCount[MediaDataController.MEDIA_PHOTOVIDEO]) { listAdapter.notifyItemChanged(photosRow); } - if (filesRowPrev != -1 && filesRow != -1 && prevMediaCount[DataQuery.MEDIA_FILE] != lastMediaCount[DataQuery.MEDIA_FILE]) { + if (filesRowPrev != -1 && filesRow != -1 && prevMediaCount[MediaDataController.MEDIA_FILE] != lastMediaCount[MediaDataController.MEDIA_FILE]) { listAdapter.notifyItemChanged(filesRow); } - if (linksRowPrev != -1 && linksRow != -1 && prevMediaCount[DataQuery.MEDIA_URL] != lastMediaCount[DataQuery.MEDIA_URL]) { + if (linksRowPrev != -1 && linksRow != -1 && prevMediaCount[MediaDataController.MEDIA_URL] != lastMediaCount[MediaDataController.MEDIA_URL]) { listAdapter.notifyItemChanged(linksRow); } - if (audioRowPrev != -1 && audioRow != -1 && prevMediaCount[DataQuery.MEDIA_MUSIC] != lastMediaCount[DataQuery.MEDIA_MUSIC]) { + if (audioRowPrev != -1 && audioRow != -1 && prevMediaCount[MediaDataController.MEDIA_MUSIC] != lastMediaCount[MediaDataController.MEDIA_MUSIC]) { listAdapter.notifyItemChanged(audioRow); } - if (voiceRowPrev != -1 && voiceRow != -1 && prevMediaCount[DataQuery.MEDIA_AUDIO] != lastMediaCount[DataQuery.MEDIA_AUDIO]) { + if (voiceRowPrev != -1 && voiceRow != -1 && prevMediaCount[MediaDataController.MEDIA_AUDIO] != lastMediaCount[MediaDataController.MEDIA_AUDIO]) { listAdapter.notifyItemChanged(voiceRow); } if (photosRowPrev == -1 && photosRow != -1) { @@ -1957,22 +2025,28 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. @Override protected void onTransitionAnimationStart(boolean isOpen, boolean backward) { - if (!backward && playProfileAnimation && allowProfileAnimation) { - openAnimationInProgress = true; + if (!isOpen || !backward) { + if (!backward && playProfileAnimation && allowProfileAnimation) { + openAnimationInProgress = true; + } + } + if (isOpen) { + NotificationCenter.getInstance(currentAccount).setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.dialogsNeedReload, NotificationCenter.closeChats, NotificationCenter.mediaCountDidLoad, NotificationCenter.mediaCountsDidLoad}); + NotificationCenter.getInstance(currentAccount).setAnimationInProgress(true); } - NotificationCenter.getInstance(currentAccount).setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.dialogsNeedReload, NotificationCenter.closeChats, NotificationCenter.mediaCountDidLoad, NotificationCenter.mediaCountsDidLoad}); - NotificationCenter.getInstance(currentAccount).setAnimationInProgress(true); } @Override protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { - if (isOpen && !backward && playProfileAnimation && allowProfileAnimation) { - openAnimationInProgress = false; - if (recreateMenuAfterAnimation) { - createActionBarMenu(); + if (isOpen) { + if (!backward && playProfileAnimation && allowProfileAnimation) { + openAnimationInProgress = false; + if (recreateMenuAfterAnimation) { + createActionBarMenu(); + } } + NotificationCenter.getInstance(currentAccount).setAnimationInProgress(false); } - NotificationCenter.getInstance(currentAccount).setAnimationInProgress(false); } public float getAnimationProgress() { @@ -2354,7 +2428,7 @@ 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; - DataQuery.getInstance(currentAccount).getMediaCounts(mergeDialogId, classGuid); + MediaDataController.getInstance(currentAccount).getMediaCounts(mergeDialogId, classGuid); } fetchUsersFromChannelInfo(); } @@ -2402,6 +2476,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. infoHeaderRow = -1; phoneRow = -1; userInfoRow = -1; + locationRow = -1; channelInfoRow = -1; usernameRow = -1; settingsTimerRow = -1; @@ -2479,27 +2554,27 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (hasMedia || userInfo != null && userInfo.common_chats_count != 0) { sharedHeaderRow = rowCount++; - if (lastMediaCount[DataQuery.MEDIA_PHOTOVIDEO] > 0) { + if (lastMediaCount[MediaDataController.MEDIA_PHOTOVIDEO] > 0) { photosRow = rowCount++; } else { photosRow = -1; } - if (lastMediaCount[DataQuery.MEDIA_FILE] > 0) { + if (lastMediaCount[MediaDataController.MEDIA_FILE] > 0) { filesRow = rowCount++; } else { filesRow = -1; } - if (lastMediaCount[DataQuery.MEDIA_URL] > 0) { + if (lastMediaCount[MediaDataController.MEDIA_URL] > 0) { linksRow = rowCount++; } else { linksRow = -1; } - if (lastMediaCount[DataQuery.MEDIA_MUSIC] > 0) { + if (lastMediaCount[MediaDataController.MEDIA_MUSIC] > 0) { audioRow = rowCount++; } else { audioRow = -1; } - if (lastMediaCount[DataQuery.MEDIA_AUDIO] > 0) { + if (lastMediaCount[MediaDataController.MEDIA_AUDIO] > 0) { voiceRow = rowCount++; } else { voiceRow = -1; @@ -2520,16 +2595,21 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } } else if (chat_id != 0) { if (chat_id > 0) { - if (chatInfo != null && !TextUtils.isEmpty(chatInfo.about) || !TextUtils.isEmpty(currentChat.username)) { + if (chatInfo != null && (!TextUtils.isEmpty(chatInfo.about) || chatInfo.location instanceof TLRPC.TL_channelLocation) || !TextUtils.isEmpty(currentChat.username)) { infoHeaderRow = rowCount++; - if (chatInfo != null && !TextUtils.isEmpty(chatInfo.about)) { - channelInfoRow = rowCount++; + if (chatInfo != null) { + if (!TextUtils.isEmpty(chatInfo.about)) { + channelInfoRow = rowCount++; + } + if (chatInfo.location instanceof TLRPC.TL_channelLocation) { + locationRow = rowCount++; + } } if (!TextUtils.isEmpty(currentChat.username)) { usernameRow = rowCount++; } } - if (channelInfoRow != -1 || usernameRow != -1) { + if (infoHeaderRow != -1) { notificationsDividerRow = rowCount++; } notificationsRow = rowCount++; @@ -2549,27 +2629,27 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (hasMedia) { sharedHeaderRow = rowCount++; - if (lastMediaCount[DataQuery.MEDIA_PHOTOVIDEO] > 0) { + if (lastMediaCount[MediaDataController.MEDIA_PHOTOVIDEO] > 0) { photosRow = rowCount++; } else { photosRow = -1; } - if (lastMediaCount[DataQuery.MEDIA_FILE] > 0) { + if (lastMediaCount[MediaDataController.MEDIA_FILE] > 0) { filesRow = rowCount++; } else { filesRow = -1; } - if (lastMediaCount[DataQuery.MEDIA_URL] > 0) { + if (lastMediaCount[MediaDataController.MEDIA_URL] > 0) { linksRow = rowCount++; } else { linksRow = -1; } - if (lastMediaCount[DataQuery.MEDIA_MUSIC] > 0) { + if (lastMediaCount[MediaDataController.MEDIA_MUSIC] > 0) { audioRow = rowCount++; } else { audioRow = -1; } - if (lastMediaCount[DataQuery.MEDIA_AUDIO] > 0) { + if (lastMediaCount[MediaDataController.MEDIA_AUDIO] > 0) { voiceRow = rowCount++; } else { voiceRow = -1; @@ -2743,7 +2823,17 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (onlineCount > 1 && chatInfo.participants_count != 0) { newString = String.format("%s, %s", LocaleController.formatPluralString("Members", chatInfo.participants_count), LocaleController.formatPluralString("OnlineCount", Math.min(onlineCount, chatInfo.participants_count))); } else { - newString = LocaleController.formatPluralString("Members", chatInfo.participants_count); + if (chatInfo.participants_count == 0) { + if (chat.has_geo) { + newString = LocaleController.getString("MegaLocation", R.string.MegaLocation).toLowerCase(); + } else if (!TextUtils.isEmpty(chat.username)) { + newString = LocaleController.getString("MegaPublic", R.string.MegaPublic).toLowerCase(); + } else { + newString = LocaleController.getString("MegaPrivate", R.string.MegaPrivate).toLowerCase(); + } + } else { + newString = LocaleController.formatPluralString("Members", chatInfo.participants_count); + } } } else { int[] result = new int[1]; @@ -2804,7 +2894,17 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. int[] result = new int[1]; String shortNumber = LocaleController.formatShortNumber(chatInfo.participants_count, result); if (currentChat.megagroup) { - onlineTextView[a].setText(LocaleController.formatPluralString("Members", result[0]).replace(String.format("%d", result[0]), shortNumber)); + if (chatInfo.participants_count == 0) { + if (chat.has_geo) { + newString = LocaleController.getString("MegaLocation", R.string.MegaLocation).toLowerCase(); + } else if (!TextUtils.isEmpty(chat.username)) { + newString = LocaleController.getString("MegaPublic", R.string.MegaPublic).toLowerCase(); + } else { + newString = LocaleController.getString("MegaPrivate", R.string.MegaPrivate).toLowerCase(); + } + } else { + onlineTextView[a].setText(LocaleController.formatPluralString("Members", result[0]).replace(String.format("%d", result[0]), shortNumber)); + } } else { onlineTextView[a].setText(LocaleController.formatPluralString("Subscribers", result[0]).replace(String.format("%d", result[0]), shortNumber)); } @@ -2849,7 +2949,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. item = menu.addItem(10, R.drawable.ic_ab_other); if (MessagesController.isSupportUser(user)) { if (userBlocked) { - item.addSubItem(block_contact, R.drawable.msg_block, LocaleController.getString("Unblock", R.string.Unblock)); //TODO icon + item.addSubItem(block_contact, R.drawable.msg_block, LocaleController.getString("Unblock", R.string.Unblock)); } } else { if (isBot) { @@ -2857,23 +2957,24 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. item.addSubItem(invite_to_group, R.drawable.msg_addbot, LocaleController.getString("BotInvite", R.string.BotInvite)); } item.addSubItem(share, R.drawable.msg_share, LocaleController.getString("BotShare", R.string.BotShare)); - } - if (user.phone != null && user.phone.length() != 0) { - item.addSubItem(add_contact, R.drawable.msg_addcontact, LocaleController.getString("AddContact", R.string.AddContact)); - item.addSubItem(share_contact, R.drawable.msg_share, LocaleController.getString("ShareContact", R.string.ShareContact)); - item.addSubItem(block_contact, !userBlocked ? R.drawable.msg_block : R.drawable.msg_block, !userBlocked ? LocaleController.getString("BlockContact", R.string.BlockContact) : LocaleController.getString("Unblock", R.string.Unblock)); //TODO icon } else { - if (isBot) { - item.addSubItem(block_contact, !userBlocked ? R.drawable.msg_block : R.drawable.msg_retry, !userBlocked ? LocaleController.getString("BotStop", R.string.BotStop) : LocaleController.getString("BotRestart", R.string.BotRestart)); - } else { - item.addSubItem(block_contact, !userBlocked ? R.drawable.msg_block : R.drawable.msg_block, !userBlocked ? LocaleController.getString("BlockContact", R.string.BlockContact) : LocaleController.getString("Unblock", R.string.Unblock)); //TODO icon - } + item.addSubItem(add_contact, R.drawable.msg_addcontact, LocaleController.getString("AddContact", R.string.AddContact)); + } + if (!TextUtils.isEmpty(user.phone)) { + item.addSubItem(share_contact, R.drawable.msg_share, LocaleController.getString("ShareContact", R.string.ShareContact)); + } + if (isBot) { + item.addSubItem(block_contact, !userBlocked ? R.drawable.msg_block : R.drawable.msg_retry, !userBlocked ? LocaleController.getString("BotStop", R.string.BotStop) : LocaleController.getString("BotRestart", R.string.BotRestart)); + } else { + item.addSubItem(block_contact, !userBlocked ? R.drawable.msg_block : R.drawable.msg_block, !userBlocked ? LocaleController.getString("BlockContact", R.string.BlockContact) : LocaleController.getString("Unblock", R.string.Unblock)); } } } else { item = menu.addItem(10, R.drawable.ic_ab_other); - item.addSubItem(share_contact, R.drawable.msg_share, LocaleController.getString("ShareContact", R.string.ShareContact)); - item.addSubItem(block_contact, !userBlocked ? R.drawable.msg_block : R.drawable.msg_block, !userBlocked ? LocaleController.getString("BlockContact", R.string.BlockContact) : LocaleController.getString("Unblock", R.string.Unblock)); //TODO icon + if (!TextUtils.isEmpty(user.phone)) { + item.addSubItem(share_contact, R.drawable.msg_share, LocaleController.getString("ShareContact", R.string.ShareContact)); + } + item.addSubItem(block_contact, !userBlocked ? R.drawable.msg_block : R.drawable.msg_block, !userBlocked ? LocaleController.getString("BlockContact", R.string.BlockContact) : LocaleController.getString("Unblock", R.string.Unblock)); item.addSubItem(edit_contact, R.drawable.msg_edit, LocaleController.getString("EditContact", R.string.EditContact)); item.addSubItem(delete_contact, R.drawable.msg_delete, LocaleController.getString("DeleteContact", R.string.DeleteContact)); } @@ -3089,6 +3190,11 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(chat_id); detailCell.setTextAndValue(MessagesController.getInstance(currentAccount).linkPrefix + "/" + chat.username, LocaleController.getString("InviteLink", R.string.InviteLink), false); } + } else if (position == locationRow) { + if (chatInfo != null && chatInfo.location instanceof TLRPC.TL_channelLocation) { + TLRPC.TL_channelLocation location = (TLRPC.TL_channelLocation) chatInfo.location; + detailCell.setTextAndValue(location.address, LocaleController.getString("AttachLocation", R.string.AttachLocation), false); + } } break; case 3: @@ -3108,15 +3214,15 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. textCell.setColors(Theme.key_windowBackgroundWhiteGrayIcon, Theme.key_windowBackgroundWhiteBlackText); textCell.setTag(Theme.key_windowBackgroundWhiteBlackText); if (position == photosRow) { - textCell.setTextAndValueAndIcon(LocaleController.getString("SharedPhotosAndVideos", R.string.SharedPhotosAndVideos), String.format("%d", lastMediaCount[DataQuery.MEDIA_PHOTOVIDEO]), R.drawable.profile_photos, position != sharedSectionRow - 1); + textCell.setTextAndValueAndIcon(LocaleController.getString("SharedPhotosAndVideos", R.string.SharedPhotosAndVideos), String.format("%d", lastMediaCount[MediaDataController.MEDIA_PHOTOVIDEO]), R.drawable.profile_photos, position != sharedSectionRow - 1); } else if (position == filesRow) { - textCell.setTextAndValueAndIcon(LocaleController.getString("FilesDataUsage", R.string.FilesDataUsage), String.format("%d", lastMediaCount[DataQuery.MEDIA_FILE]), R.drawable.profile_file, position != sharedSectionRow - 1); + textCell.setTextAndValueAndIcon(LocaleController.getString("FilesDataUsage", R.string.FilesDataUsage), String.format("%d", lastMediaCount[MediaDataController.MEDIA_FILE]), R.drawable.profile_file, position != sharedSectionRow - 1); } else if (position == linksRow) { - textCell.setTextAndValueAndIcon(LocaleController.getString("SharedLinks", R.string.SharedLinks), String.format("%d", lastMediaCount[DataQuery.MEDIA_URL]), R.drawable.profile_link, position != sharedSectionRow - 1); + textCell.setTextAndValueAndIcon(LocaleController.getString("SharedLinks", R.string.SharedLinks), String.format("%d", lastMediaCount[MediaDataController.MEDIA_URL]), R.drawable.profile_link, position != sharedSectionRow - 1); } else if (position == audioRow) { - textCell.setTextAndValueAndIcon(LocaleController.getString("SharedAudioFiles", R.string.SharedAudioFiles), String.format("%d", lastMediaCount[DataQuery.MEDIA_MUSIC]), R.drawable.profile_audio, position != sharedSectionRow - 1); + textCell.setTextAndValueAndIcon(LocaleController.getString("SharedAudioFiles", R.string.SharedAudioFiles), String.format("%d", lastMediaCount[MediaDataController.MEDIA_MUSIC]), R.drawable.profile_audio, position != sharedSectionRow - 1); } else if (position == voiceRow) { - textCell.setTextAndValueAndIcon(LocaleController.getString("AudioAutodownload", R.string.AudioAutodownload), String.format("%d", lastMediaCount[DataQuery.MEDIA_AUDIO]), R.drawable.profile_voice, position != sharedSectionRow - 1); + textCell.setTextAndValueAndIcon(LocaleController.getString("AudioAutodownload", R.string.AudioAutodownload), String.format("%d", lastMediaCount[MediaDataController.MEDIA_AUDIO]), R.drawable.profile_voice, position != sharedSectionRow - 1); } else if (position == groupsInCommonRow) { textCell.setTextAndValueAndIcon(LocaleController.getString("GroupsInCommonTitle", R.string.GroupsInCommonTitle), String.format("%d", userInfo.common_chats_count), R.drawable.actions_viewmembers, position != sharedSectionRow - 1); } else if (position == settingsTimerRow) { @@ -3252,7 +3358,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. View sectionCell = holder.itemView; sectionCell.setTag(position); Drawable drawable; - if (position == infoSectionRow && sharedSectionRow == -1 && lastSectionRow == -1 && settingsSectionRow == -1 || position == sharedSectionRow && lastSectionRow == -1 || position == lastSectionRow || position == membersSectionRow && lastSectionRow == -1 && (sharedSectionRow == -1 || membersSectionRow > sharedSectionRow)) { + if (position == infoSectionRow && sharedSectionRow == -1 && lastSectionRow == -1 && settingsSectionRow == -1 || position == settingsSectionRow && sharedSectionRow == -1 || position == sharedSectionRow && lastSectionRow == -1 || position == lastSectionRow || position == membersSectionRow && lastSectionRow == -1 && (sharedSectionRow == -1 || membersSectionRow > sharedSectionRow)) { drawable = Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow); } else { drawable = Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow); @@ -3309,7 +3415,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. public int getItemViewType(int i) { if (i == infoHeaderRow || i == sharedHeaderRow || i == membersHeaderRow) { return 1; - } else if (i == phoneRow || i == usernameRow) { + } else if (i == phoneRow || i == usernameRow || i == locationRow) { return 2; } else if (i == userInfoRow || i == channelInfoRow) { return 3; @@ -3406,6 +3512,14 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundBlue), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundPink), + new ThemeDescription(undoView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_undo_background), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"undoImageView"}, null, null, null, Theme.key_undo_cancelColor), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"undoTextView"}, null, null, null, Theme.key_undo_cancelColor), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"infoTextView"}, null, null, null, Theme.key_undo_infoColor), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"textPaint"}, null, null, null, Theme.key_undo_infoColor), + new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"progressPaint"}, null, null, null, Theme.key_undo_infoColor), + new ThemeDescription(undoView, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{UndoView.class}, new String[]{"leftImageView"}, null, null, null, Theme.key_undo_infoColor), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{AboutLinkCell.class}, Theme.profile_aboutTextPaint, null, null, Theme.key_windowBackgroundWhiteBlackText), new ThemeDescription(listView, ThemeDescription.FLAG_LINKCOLOR, new Class[]{AboutLinkCell.class}, Theme.profile_aboutTextPaint, null, null, Theme.key_windowBackgroundWhiteLinkText), new ThemeDescription(listView, 0, new Class[]{AboutLinkCell.class}, Theme.linkSelectionPaint, null, null, Theme.key_windowBackgroundWhiteLinkSelection), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java index 61b63c336..03ed553fc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java @@ -55,7 +55,7 @@ import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; -import org.telegram.ui.Cells2.UserCell; +import org.telegram.ui.Cells.UserCell2; import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RecyclerListView; @@ -86,6 +86,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi private int avatarRow; private int avatarSectionRow; private int enableRow; + private int previewRow; private int soundRow; private int vibrateRow; private int smartRow; @@ -136,6 +137,11 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi } else { enableRow = -1; } + if ((int) dialog_id != 0) { + previewRow = rowCount++; + } else { + previewRow = -1; + } soundRow = rowCount++; vibrateRow = rowCount++; if ((int) dialog_id < 0) { @@ -267,7 +273,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi }); if (addingException) { actionBar.setTitle(LocaleController.getString("NotificationsNewException", R.string.NotificationsNewException)); - actionBar.createMenu().addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); + actionBar.createMenu().addItem(done_button, LocaleController.getString("Done", R.string.Done).toUpperCase()); } else { actionBar.setTitle(LocaleController.getString("CustomNotifications", R.string.CustomNotifications)); } @@ -297,55 +303,11 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi preferences.edit().putBoolean("custom_" + dialog_id, customEnabled).commit(); TextCheckBoxCell cell = (TextCheckBoxCell) view; cell.setChecked(customEnabled); - int count = listView.getChildCount(); - ArrayList animators = new ArrayList<>(); - for (int a = 0; a < count; a++) { - View child = listView.getChildAt(a); - RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.getChildViewHolder(child); - int type = holder.getItemViewType(); - if (holder.getAdapterPosition() != customRow && type != 0) { - switch (type) { - case 1: { - TextSettingsCell textCell = (TextSettingsCell) holder.itemView; - textCell.setEnabled(customEnabled, animators); - break; - } - case 2: { - TextInfoPrivacyCell textCell = (TextInfoPrivacyCell) holder.itemView; - textCell.setEnabled(customEnabled, animators); - break; - } - case 3: { - TextColorCell textCell = (TextColorCell) holder.itemView; - textCell.setEnabled(customEnabled, animators); - break; - } - case 4: { - RadioCell radioCell = (RadioCell) holder.itemView; - radioCell.setEnabled(customEnabled, animators); - break; - } - } - } - } - if (!animators.isEmpty()) { - if (animatorSet != null) { - animatorSet.cancel(); - } - animatorSet = new AnimatorSet(); - animatorSet.playTogether(animators); - animatorSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - if (animator.equals(animatorSet)) { - animatorSet = null; - } - } - }); - animatorSet.setDuration(150); - animatorSet.start(); - } + checkRowsEnabled(); } else if (customEnabled) { + if (!view.isEnabled()) { + return; + } if (position == soundRow) { try { Intent tmpIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); @@ -416,6 +378,12 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi TextCheckCell checkCell = (TextCheckCell) view; notificationsEnabled = !checkCell.isChecked(); checkCell.setChecked(notificationsEnabled); + checkRowsEnabled(); + } else if (position == previewRow) { + TextCheckCell checkCell = (TextCheckCell) view; + SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); + preferences.edit().putBoolean("content_preview_" + dialog_id, !checkCell.isChecked()).commit(); + checkCell.setChecked(!checkCell.isChecked()); } else if (position == callsVibrateRow) { showDialog(AlertsCreator.createVibrationSelectDialog(getParentActivity(), dialog_id, "calls_vibrate_", () -> { if (adapter != null) { @@ -609,7 +577,66 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi delegate = profileNotificationsActivityDelegate; } - private class ListAdapter extends RecyclerView.Adapter { + private void checkRowsEnabled() { + int count = listView.getChildCount(); + ArrayList animators = new ArrayList<>(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.getChildViewHolder(child); + int type = holder.getItemViewType(); + int position = holder.getAdapterPosition(); + if (position != customRow && position != enableRow && type != 0) { + switch (type) { + case 1: { + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + textCell.setEnabled(customEnabled && notificationsEnabled, animators); + break; + } + case 2: { + TextInfoPrivacyCell textCell = (TextInfoPrivacyCell) holder.itemView; + textCell.setEnabled(customEnabled && notificationsEnabled, animators); + break; + } + case 3: { + TextColorCell textCell = (TextColorCell) holder.itemView; + textCell.setEnabled(customEnabled && notificationsEnabled, animators); + break; + } + case 4: { + RadioCell radioCell = (RadioCell) holder.itemView; + radioCell.setEnabled(customEnabled && notificationsEnabled, animators); + break; + } + case 8: { + if (position == previewRow) { + TextCheckCell checkCell = (TextCheckCell) holder.itemView; + checkCell.setEnabled(customEnabled && notificationsEnabled, animators); + } + break; + } + } + } + } + if (!animators.isEmpty()) { + if (animatorSet != null) { + animatorSet.cancel(); + } + animatorSet = new AnimatorSet(); + animatorSet.playTogether(animators); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + if (animator.equals(animatorSet)) { + animatorSet = null; + } + } + }); + animatorSet.setDuration(150); + animatorSet.start(); + } + } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { private Context context; @@ -622,6 +649,32 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi return rowCount; } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + switch (holder.getItemViewType()) { + case 1: + case 3: + case 4: { + return customEnabled && notificationsEnabled; + } + case 0: + case 2: + case 6: + case 7: { + return false; + } + case 8: { + TextCheckCell checkCell = (TextCheckCell) holder.itemView; + if (holder.getAdapterPosition() == previewRow) { + return customEnabled && notificationsEnabled; + } else { + return true; + } + } + } + return true; + } + @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view; @@ -650,7 +703,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); break; case 6: - view = new UserCell(context, 4, 0); + view = new UserCell2(context, 4, 0); view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); break; case 7: @@ -818,7 +871,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi break; } case 6: { - UserCell userCell = (UserCell) holder.itemView; + UserCell2 userCell2 = (UserCell2) holder.itemView; int lower_id = (int) dialog_id; TLObject object; if (lower_id > 0) { @@ -826,7 +879,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi } else { object = MessagesController.getInstance(currentAccount).getChat(-lower_id); } - userCell.setData(object, null, null, 0); + userCell2.setData(object, null, null, 0); break; } case 8: { @@ -834,6 +887,8 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); 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_" + dialog_id, true), true); } break; } @@ -864,6 +919,14 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi radioCell.setEnabled(customEnabled && notificationsEnabled, null); break; } + case 8: { + TextCheckCell checkCell = (TextCheckCell) holder.itemView; + if (holder.getAdapterPosition() == previewRow) { + checkCell.setEnabled(customEnabled && notificationsEnabled, null); + } else { + checkCell.setEnabled(true, null); + } + } } } } @@ -886,7 +949,7 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi return 6; } else if (position == avatarSectionRow) { return 7; - } else if (position == enableRow) { + } else if (position == enableRow || position == previewRow) { return 8; } return 0; @@ -900,15 +963,15 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi int count = listView.getChildCount(); for (int a = 0; a < count; a++) { View child = listView.getChildAt(a); - if (child instanceof UserCell) { - ((UserCell) child).update(0); + if (child instanceof UserCell2) { + ((UserCell2) child).update(0); } } } }; return new ThemeDescription[]{ - new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{HeaderCell.class, TextSettingsCell.class, TextColorCell.class, RadioCell.class, UserCell.class, TextCheckCell.class, TextCheckBoxCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{HeaderCell.class, TextSettingsCell.class, TextColorCell.class, RadioCell.class, UserCell2.class, TextCheckCell.class, TextCheckBoxCell.class}, null, null, null, Theme.key_windowBackgroundWhite), new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), @@ -943,10 +1006,10 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrack), new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrackChecked), - new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), - new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusColor"}, null, null, cellDelegate, Theme.key_windowBackgroundWhiteGrayText), - new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusOnlineColor"}, null, null, cellDelegate, Theme.key_windowBackgroundWhiteBlueText), - new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(listView, 0, new Class[]{UserCell2.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{UserCell2.class}, new String[]{"statusColor"}, null, null, cellDelegate, Theme.key_windowBackgroundWhiteGrayText), + new ThemeDescription(listView, 0, new Class[]{UserCell2.class}, new String[]{"statusOnlineColor"}, null, null, cellDelegate, Theme.key_windowBackgroundWhiteBlueText), + new ThemeDescription(listView, 0, new Class[]{UserCell2.class}, null, new Drawable[]{Theme.avatar_broadcastDrawable, Theme.avatar_savedDrawable}, null, Theme.key_avatar_text), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundRed), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundOrange), new ThemeDescription(null, 0, null, null, null, cellDelegate, Theme.key_avatar_backgroundViolet), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProxySettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProxySettingsActivity.java index 4028ad3a6..91e03f714 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProxySettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProxySettingsActivity.java @@ -394,7 +394,7 @@ public class ProxySettingsActivity extends BaseFragment { shareCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)); linearLayout2.addView(shareCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); shareCell.setOnClickListener(v -> { - StringBuilder params = new StringBuilder(""); + StringBuilder params = new StringBuilder(); String address = inputFields[FIELD_IP].getText().toString(); String password = inputFields[FIELD_PASSWORD].getText().toString(); String user = inputFields[FIELD_USER].getText().toString(); @@ -539,6 +539,6 @@ public class ProxySettingsActivity extends BaseFragment { arrayList.add(new ThemeDescription(bottomCell, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4)); arrayList.add(new ThemeDescription(bottomCell, ThemeDescription.FLAG_LINKCOLOR, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteLinkText)); - return arrayList.toArray(new ThemeDescription[arrayList.size()]); + return arrayList.toArray(new ThemeDescription[0]); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java index d49b3e11d..8655c46bc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java @@ -55,7 +55,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.BuildConfig; import org.telegram.messenger.ContactsController; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.ImageLoader; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.NotificationsController; @@ -230,7 +230,7 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter helpRow = rowCount++; versionRow = rowCount++; - DataQuery.getInstance(currentAccount).checkFeaturedStickers(); + MediaDataController.getInstance(currentAccount).checkFeaturedStickers(); userInfo = MessagesController.getInstance(currentAccount).getUserFull(UserConfig.getInstance(currentAccount).getClientUserId()); MessagesController.getInstance(currentAccount).loadUserInfo(UserConfig.getInstance(currentAccount).getCurrentUser(), true, classGuid); @@ -381,7 +381,7 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter presentFragment(new ChangeBioActivity()); } } else if (position == numberRow) { - presentFragment(new ChangePhoneHelpActivity()); + presentFragment(new ActionIntroActivity(ActionIntroActivity.ACTION_TYPE_CHANGE_PHONE_NUMBER)); } } else { if (position < 0) { @@ -1301,7 +1301,7 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter private SearchResult[] searchArray = new SearchResult[]{ new SearchResult(500, LocaleController.getString("EditName", R.string.EditName), 0, () -> presentFragment(new ChangeNameActivity())), - new SearchResult(501, LocaleController.getString("ChangePhoneNumber", R.string.ChangePhoneNumber), 0, () -> presentFragment(new ChangePhoneHelpActivity())), + new SearchResult(501, LocaleController.getString("ChangePhoneNumber", R.string.ChangePhoneNumber), 0, () -> presentFragment(new ActionIntroActivity(ActionIntroActivity.ACTION_TYPE_CHANGE_PHONE_NUMBER))), new SearchResult(502, LocaleController.getString("AddAnotherAccount", R.string.AddAnotherAccount), 0, () -> { int freeAccount = -1; for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { @@ -1389,12 +1389,13 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter new SearchResult(310, LocaleController.getString("RaiseToSpeak", R.string.RaiseToSpeak), "raiseToSpeakRow", LocaleController.getString("ChatSettings", R.string.ChatSettings), R.drawable.menu_chats, () -> presentFragment(new ThemeActivity(ThemeActivity.THEME_TYPE_BASIC))), new SearchResult(311, LocaleController.getString("SendByEnter", R.string.SendByEnter), "sendByEnterRow", LocaleController.getString("ChatSettings", R.string.ChatSettings), R.drawable.menu_chats, () -> presentFragment(new ThemeActivity(ThemeActivity.THEME_TYPE_BASIC))), new SearchResult(312, LocaleController.getString("SaveToGallerySettings", R.string.SaveToGallerySettings), "saveToGalleryRow", LocaleController.getString("ChatSettings", R.string.ChatSettings), R.drawable.menu_chats, () -> presentFragment(new ThemeActivity(ThemeActivity.THEME_TYPE_BASIC))), - new SearchResult(313, LocaleController.getString("StickersAndMasks", R.string.StickersAndMasks), LocaleController.getString("ChatSettings", R.string.ChatSettings), R.drawable.menu_chats, () -> presentFragment(new StickersActivity(DataQuery.TYPE_IMAGE))), - new SearchResult(314, LocaleController.getString("SuggestStickers", R.string.SuggestStickers), "suggestRow", LocaleController.getString("ChatSettings", R.string.ChatSettings), LocaleController.getString("StickersAndMasks", R.string.StickersAndMasks), R.drawable.menu_chats, () -> presentFragment(new StickersActivity(DataQuery.TYPE_IMAGE))), + new SearchResult(312, LocaleController.getString("DistanceUnits", R.string.DistanceUnits), "distanceRow", LocaleController.getString("ChatSettings", R.string.ChatSettings), R.drawable.menu_chats, () -> presentFragment(new ThemeActivity(ThemeActivity.THEME_TYPE_BASIC))), + new SearchResult(313, LocaleController.getString("StickersAndMasks", R.string.StickersAndMasks), LocaleController.getString("ChatSettings", R.string.ChatSettings), R.drawable.menu_chats, () -> presentFragment(new StickersActivity(MediaDataController.TYPE_IMAGE))), + new SearchResult(314, LocaleController.getString("SuggestStickers", R.string.SuggestStickers), "suggestRow", LocaleController.getString("ChatSettings", R.string.ChatSettings), LocaleController.getString("StickersAndMasks", R.string.StickersAndMasks), R.drawable.menu_chats, () -> presentFragment(new StickersActivity(MediaDataController.TYPE_IMAGE))), new SearchResult(315, LocaleController.getString("FeaturedStickers", R.string.FeaturedStickers), null, LocaleController.getString("ChatSettings", R.string.ChatSettings), LocaleController.getString("StickersAndMasks", R.string.StickersAndMasks), R.drawable.menu_chats, () -> presentFragment(new FeaturedStickersActivity())), - new SearchResult(316, LocaleController.getString("Masks", R.string.Masks), null, LocaleController.getString("ChatSettings", R.string.ChatSettings), LocaleController.getString("StickersAndMasks", R.string.StickersAndMasks), R.drawable.menu_chats, () -> presentFragment(new StickersActivity(DataQuery.TYPE_MASK))), - new SearchResult(317, LocaleController.getString("ArchivedStickers", R.string.ArchivedStickers), null, LocaleController.getString("ChatSettings", R.string.ChatSettings), LocaleController.getString("StickersAndMasks", R.string.StickersAndMasks), R.drawable.menu_chats, () -> presentFragment(new ArchivedStickersActivity(DataQuery.TYPE_IMAGE))), - new SearchResult(317, LocaleController.getString("ArchivedMasks", R.string.ArchivedMasks), null, LocaleController.getString("ChatSettings", R.string.ChatSettings), LocaleController.getString("StickersAndMasks", R.string.StickersAndMasks), R.drawable.menu_chats, () -> presentFragment(new ArchivedStickersActivity(DataQuery.TYPE_MASK))), + new SearchResult(316, LocaleController.getString("Masks", R.string.Masks), null, LocaleController.getString("ChatSettings", R.string.ChatSettings), LocaleController.getString("StickersAndMasks", R.string.StickersAndMasks), R.drawable.menu_chats, () -> presentFragment(new StickersActivity(MediaDataController.TYPE_MASK))), + new SearchResult(317, LocaleController.getString("ArchivedStickers", R.string.ArchivedStickers), null, LocaleController.getString("ChatSettings", R.string.ChatSettings), LocaleController.getString("StickersAndMasks", R.string.StickersAndMasks), R.drawable.menu_chats, () -> presentFragment(new ArchivedStickersActivity(MediaDataController.TYPE_IMAGE))), + new SearchResult(317, LocaleController.getString("ArchivedMasks", R.string.ArchivedMasks), null, LocaleController.getString("ChatSettings", R.string.ChatSettings), LocaleController.getString("StickersAndMasks", R.string.StickersAndMasks), R.drawable.menu_chats, () -> presentFragment(new ArchivedStickersActivity(MediaDataController.TYPE_MASK))), new SearchResult(400, LocaleController.getString("Language", R.string.Language), R.drawable.menu_language, () -> presentFragment(new LanguageSelectActivity())), diff --git a/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java index 3777d46df..c3daa0f20 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java @@ -18,7 +18,7 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.Toast; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; @@ -130,9 +130,9 @@ public class StickersActivity extends BaseFragment implements NotificationCenter @Override public boolean onFragmentCreate() { super.onFragmentCreate(); - DataQuery.getInstance(currentAccount).checkStickers(currentType); - if (currentType == DataQuery.TYPE_IMAGE) { - DataQuery.getInstance(currentAccount).checkFeaturedStickers(); + MediaDataController.getInstance(currentAccount).checkStickers(currentType); + if (currentType == MediaDataController.TYPE_IMAGE) { + MediaDataController.getInstance(currentAccount).checkFeaturedStickers(); } NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.stickersDidLoad); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.archivedStickersCountDidLoad); @@ -154,7 +154,7 @@ public class StickersActivity extends BaseFragment implements NotificationCenter public View createView(Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setAllowOverlayTitle(true); - if (currentType == DataQuery.TYPE_IMAGE) { + if (currentType == MediaDataController.TYPE_IMAGE) { actionBar.setTitle(LocaleController.getString("StickersName", R.string.StickersName)); } else { actionBar.setTitle(LocaleController.getString("Masks", R.string.Masks)); @@ -188,7 +188,7 @@ public class StickersActivity extends BaseFragment implements NotificationCenter listView.setOnItemClickListener((view, position) -> { if (position >= stickersStartRow && position < stickersEndRow && getParentActivity() != null) { sendReorder(); - final TLRPC.TL_messages_stickerSet stickerSet = DataQuery.getInstance(currentAccount).getStickerSets(currentType).get(position - stickersStartRow); + final TLRPC.TL_messages_stickerSet stickerSet = MediaDataController.getInstance(currentAccount).getStickerSets(currentType).get(position - stickersStartRow); ArrayList stickers = stickerSet.documents; if (stickers == null || stickers.isEmpty()) { return; @@ -201,7 +201,7 @@ public class StickersActivity extends BaseFragment implements NotificationCenter sendReorder(); presentFragment(new ArchivedStickersActivity(currentType)); } else if (position == masksRow) { - presentFragment(new StickersActivity(DataQuery.TYPE_MASK)); + presentFragment(new StickersActivity(MediaDataController.TYPE_MASK)); } else if (position == suggestRow) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("SuggestStickers", R.string.SuggestStickers)); @@ -242,11 +242,11 @@ public class StickersActivity extends BaseFragment implements NotificationCenter if (!needReorder) { return; } - DataQuery.getInstance(currentAccount).calcNewHash(currentType); + MediaDataController.getInstance(currentAccount).calcNewHash(currentType); needReorder = false; TLRPC.TL_messages_reorderStickerSets req = new TLRPC.TL_messages_reorderStickerSets(); - req.masks = currentType == DataQuery.TYPE_MASK; - ArrayList arrayList = DataQuery.getInstance(currentAccount).getStickerSets(currentType); + req.masks = currentType == MediaDataController.TYPE_MASK; + ArrayList arrayList = MediaDataController.getInstance(currentAccount).getStickerSets(currentType); for (int a = 0; a < arrayList.size(); a++) { req.order.add(arrayList.get(a).set.id); } @@ -258,7 +258,7 @@ public class StickersActivity extends BaseFragment implements NotificationCenter private void updateRows() { rowCount = 0; - if (currentType == DataQuery.TYPE_IMAGE) { + if (currentType == MediaDataController.TYPE_IMAGE) { suggestRow = rowCount++; featuredRow = rowCount++; featuredInfoRow = rowCount++; @@ -270,7 +270,7 @@ public class StickersActivity extends BaseFragment implements NotificationCenter masksRow = -1; masksInfoRow = -1; } - if (DataQuery.getInstance(currentAccount).getArchivedStickersCount(currentType) != 0) { + if (MediaDataController.getInstance(currentAccount).getArchivedStickersCount(currentType) != 0) { archivedRow = rowCount++; archivedInfoRow = rowCount++; } else { @@ -278,7 +278,7 @@ public class StickersActivity extends BaseFragment implements NotificationCenter archivedInfoRow = -1; } - ArrayList stickerSets = DataQuery.getInstance(currentAccount).getStickerSets(currentType); + ArrayList stickerSets = MediaDataController.getInstance(currentAccount).getStickerSets(currentType); if (!stickerSets.isEmpty()) { stickersStartRow = rowCount; stickersEndRow = rowCount + stickerSets.size(); @@ -318,7 +318,7 @@ public class StickersActivity extends BaseFragment implements NotificationCenter @Override public long getItemId(int i) { if (i >= stickersStartRow && i < stickersEndRow) { - ArrayList arrayList = DataQuery.getInstance(currentAccount).getStickerSets(currentType); + ArrayList arrayList = MediaDataController.getInstance(currentAccount).getStickerSets(currentType); return arrayList.get(i - stickersStartRow).set.id; } else if (i == suggestRow || i == suggestInfoRow || i == archivedRow || i == archivedInfoRow || i == featuredRow || i == featuredInfoRow || i == masksRow || i == masksInfoRow) { return Integer.MIN_VALUE; @@ -328,9 +328,9 @@ public class StickersActivity extends BaseFragment implements NotificationCenter private void processSelectionOption(int which, TLRPC.TL_messages_stickerSet stickerSet) { if (which == 0) { - DataQuery.getInstance(currentAccount).removeStickersSet(getParentActivity(), stickerSet.set, !stickerSet.set.archived ? 1 : 2, StickersActivity.this, true); + MediaDataController.getInstance(currentAccount).removeStickersSet(getParentActivity(), stickerSet.set, !stickerSet.set.archived ? 1 : 2, StickersActivity.this, true); } else if (which == 1) { - DataQuery.getInstance(currentAccount).removeStickersSet(getParentActivity(), stickerSet.set, 0, StickersActivity.this, true); + MediaDataController.getInstance(currentAccount).removeStickersSet(getParentActivity(), stickerSet.set, 0, StickersActivity.this, true); } else if (which == 2) { try { Intent intent = new Intent(Intent.ACTION_SEND); @@ -356,7 +356,7 @@ public class StickersActivity extends BaseFragment implements NotificationCenter public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (holder.getItemViewType()) { case 0: - ArrayList arrayList = DataQuery.getInstance(currentAccount).getStickerSets(currentType); + ArrayList arrayList = MediaDataController.getInstance(currentAccount).getStickerSets(currentType); int row = position - stickersStartRow; ((StickerSetCell) holder.itemView).setStickersSet(arrayList.get(row), row != arrayList.size() - 1); break; @@ -384,7 +384,7 @@ public class StickersActivity extends BaseFragment implements NotificationCenter ((TextInfoPrivacyCell) holder.itemView).setText(text); } } else if (position == archivedInfoRow) { - if (currentType == DataQuery.TYPE_IMAGE) { + if (currentType == MediaDataController.TYPE_IMAGE) { ((TextInfoPrivacyCell) holder.itemView).setText(LocaleController.getString("ArchivedStickersInfo", R.string.ArchivedStickersInfo)); } else { ((TextInfoPrivacyCell) holder.itemView).setText(LocaleController.getString("ArchivedMasksInfo", R.string.ArchivedMasksInfo)); @@ -395,10 +395,10 @@ public class StickersActivity extends BaseFragment implements NotificationCenter break; case 2: if (position == featuredRow) { - int count = DataQuery.getInstance(currentAccount).getUnreadStickerSets().size(); + int count = MediaDataController.getInstance(currentAccount).getUnreadStickerSets().size(); ((TextSettingsCell) holder.itemView).setTextAndValue(LocaleController.getString("FeaturedStickers", R.string.FeaturedStickers), count != 0 ? String.format("%d", count) : "", false); } else if (position == archivedRow) { - if (currentType == DataQuery.TYPE_IMAGE) { + if (currentType == MediaDataController.TYPE_IMAGE) { ((TextSettingsCell) holder.itemView).setText(LocaleController.getString("ArchivedStickers", R.string.ArchivedStickers), false); } else { ((TextSettingsCell) holder.itemView).setText(LocaleController.getString("ArchivedMasks", R.string.ArchivedMasks), false); @@ -453,7 +453,7 @@ public class StickersActivity extends BaseFragment implements NotificationCenter builder.setTitle(stickerSet.set.title); CharSequence[] items; final int[] options; - if (currentType == DataQuery.TYPE_IMAGE) { + if (currentType == MediaDataController.TYPE_IMAGE) { if (stickerSet.set.official) { options = new int[]{0}; items = new CharSequence[]{ @@ -523,7 +523,7 @@ public class StickersActivity extends BaseFragment implements NotificationCenter if (fromIndex != toIndex) { needReorder = true; } - ArrayList arrayList = DataQuery.getInstance(currentAccount).getStickerSets(currentType); + ArrayList arrayList = MediaDataController.getInstance(currentAccount).getStickerSets(currentType); TLRPC.TL_messages_stickerSet from = arrayList.get(fromIndex - stickersStartRow); arrayList.set(fromIndex - stickersStartRow, arrayList.get(toIndex - stickersStartRow)); arrayList.set(toIndex - stickersStartRow, from); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ThemeActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ThemeActivity.java index 036d9543b..6682e5403 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ThemeActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ThemeActivity.java @@ -57,7 +57,7 @@ import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.BuildConfig; -import org.telegram.messenger.DataQuery; +import org.telegram.messenger.MediaDataController; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; @@ -132,6 +132,7 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No private int raiseToSpeakRow; private int sendByEnterRow; private int saveToGalleryRow; + private int distanceRow; private int enableAnimationsRow; private int settings2Row; private int stickersRow; @@ -248,10 +249,16 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No editor.putInt("fons_size", SharedConfig.fontSize); editor.commit(); Theme.chat_msgTextPaint.setTextSize(AndroidUtilities.dp(SharedConfig.fontSize)); + int firstVisPos = layoutManager.findFirstVisibleItemPosition(); + View firstVisView = firstVisPos != RecyclerView.NO_POSITION ? layoutManager.findViewByPosition(firstVisPos) : null; + int top = firstVisView != null ? firstVisView.getTop() : 0; for (int a = 0; a < cells.length; a++) { cells[a].getMessageObject().resetLayout(); cells[a].requestLayout(); } + if (firstVisView != null) { + layoutManager.scrollToPositionWithOffset(firstVisPos, top); + } } }); addView(sizeBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 38, Gravity.LEFT | Gravity.TOP, 9, 5, 43, 0)); @@ -484,6 +491,7 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No raiseToSpeakRow = -1; sendByEnterRow = -1; saveToGalleryRow = -1; + distanceRow = -1; settings2Row = -1; stickersRow = -1; stickersSection2Row = -1; @@ -531,6 +539,7 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No raiseToSpeakRow = rowCount++; sendByEnterRow = rowCount++; saveToGalleryRow = rowCount++; + distanceRow = rowCount++; settings2Row = rowCount++; stickersRow = rowCount++; stickersSection2Row = rowCount++; @@ -755,6 +764,25 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No if (view instanceof TextCheckCell) { ((TextCheckCell) view).setChecked(SharedConfig.saveToGallery); } + } else if (position == distanceRow) { + if (getParentActivity() == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("DistanceUnitsTitle", R.string.DistanceUnitsTitle)); + builder.setItems(new CharSequence[]{ + LocaleController.getString("DistanceUnitsAutomatic", R.string.DistanceUnitsAutomatic), + LocaleController.getString("DistanceUnitsKilometers", R.string.DistanceUnitsKilometers), + LocaleController.getString("DistanceUnitsMiles", R.string.DistanceUnitsMiles) + }, (dialog, which) -> { + SharedConfig.setDistanceSystemType(which); + RecyclerView.ViewHolder holder = listView.findViewHolderForAdapterPosition(distanceRow); + if (holder != null) { + listAdapter.onBindViewHolder(holder, distanceRow); + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); } else if (position == customTabsRow) { SharedConfig.toggleCustomTabs(); if (view instanceof TextCheckCell) { @@ -789,7 +817,7 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showDialog(builder.create()); } else if (position == stickersRow) { - presentFragment(new StickersActivity(DataQuery.TYPE_IMAGE)); + presentFragment(new StickersActivity(MediaDataController.TYPE_IMAGE)); } else if (position == showThemesRows) { presentFragment(new ThemeActivity(THEME_TYPE_ALL)); } else if (position == emojiRow) { @@ -1399,7 +1427,7 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No } else { currentFile = new File(themeInfo.pathToFile); } - File finalFile = new File(FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE), currentFile.getName()); + File finalFile = new File(FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE), FileLoader.fixFileName(currentFile.getName())); try { if (!AndroidUtilities.copyFile(currentFile, finalFile)) { return; @@ -1562,6 +1590,13 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No return; } NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.needSetDayNightTheme, themeInfo, false); + int left = view1.getLeft(); + int right = view1.getRight(); + if (left < 0) { + horizontalListView.smoothScrollBy(left - AndroidUtilities.dp(8), 0); + } else if (right > horizontalListView.getMeasuredWidth()) { + horizontalListView.smoothScrollBy(right - horizontalListView.getMeasuredWidth(), 0); + } int count = innerListView.getChildCount(); for (int a = 0; a < count; a++) { @@ -1637,7 +1672,7 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No } cell.setTextAndValue(LocaleController.getString("SortBy", R.string.SortBy), value, true); } else if (position == backgroundRow) { - cell.setText(LocaleController.getString("ChatBackground", R.string.ChatBackground), false); + cell.setText(LocaleController.getString("ChangeChatBackground", R.string.ChangeChatBackground), false); } else if (position == contactsReimportRow) { cell.setText(LocaleController.getString("ImportContacts", R.string.ImportContacts), true); } else if (position == stickersRow) { @@ -1646,6 +1681,16 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No cell.setText(LocaleController.getString("Emoji", R.string.Emoji), true); } else if (position == showThemesRows) { cell.setText(LocaleController.getString("ShowAllThemes", R.string.ShowAllThemes), false); + } else if (position == distanceRow) { + String value; + if (SharedConfig.distanceSystemType == 0) { + value = LocaleController.getString("DistanceUnitsAutomatic", R.string.DistanceUnitsAutomatic); + } else if (SharedConfig.distanceSystemType == 1) { + value = LocaleController.getString("DistanceUnitsKilometers", R.string.DistanceUnitsKilometers); + } else { + value = LocaleController.getString("DistanceUnitsMiles", R.string.DistanceUnitsMiles); + } + cell.setTextAndValue(LocaleController.getString("DistanceUnits", R.string.DistanceUnits), value, false); } break; } @@ -1718,7 +1763,7 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No SharedPreferences preferences = MessagesController.getGlobalMainSettings(); textCheckCell.setTextAndCheck(LocaleController.getString("SendByEnter", R.string.SendByEnter), preferences.getBoolean("send_by_enter", false), true); } else if (position == saveToGalleryRow) { - textCheckCell.setTextAndCheck(LocaleController.getString("SaveToGallerySettings", R.string.SaveToGallerySettings), SharedConfig.saveToGallery, false); + textCheckCell.setTextAndCheck(LocaleController.getString("SaveToGallerySettings", R.string.SaveToGallerySettings), SharedConfig.saveToGallery, true); } else if (position == raiseToSpeakRow) { textCheckCell.setTextAndCheck(LocaleController.getString("RaiseToSpeak", R.string.RaiseToSpeak), SharedConfig.raiseToSpeak, true); } else if (position == customTabsRow) { @@ -1761,7 +1806,8 @@ public class ThemeActivity extends BaseFragment implements NotificationCenter.No public int getItemViewType(int position) { if (position == scheduleFromRow || position == emojiRow || position == showThemesRows || position == scheduleToRow || position == scheduleUpdateLocationRow || position == backgroundRow || - position == contactsReimportRow || position == contactsSortRow || position == stickersRow) { + position == contactsReimportRow || position == contactsSortRow || position == stickersRow || + position == distanceRow) { return 1; } else if (position == automaticBrightnessInfoRow || position == scheduleLocationInfoRow) { return 2; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java index e4d3eee56..c77b5689d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java @@ -119,6 +119,12 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific private int passwordEnabledDetailRow; private int rowCount; + private TwoStepVerificationActivityDelegate delegate; + + public interface TwoStepVerificationActivityDelegate { + void didEnterPassword(TLRPC.InputCheckPasswordSRP password); + } + private final static int done_button = 1; public TwoStepVerificationActivity(int type) { @@ -215,6 +221,7 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific titleTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); titleTextView.setGravity(Gravity.CENTER_HORIZONTAL); + titleTextView.setPadding(AndroidUtilities.dp(40), 0, AndroidUtilities.dp(40), 0); linearLayout.addView(titleTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 0, 38, 0, 0)); passwordEditText = new EditTextBoldCursor(context); @@ -440,7 +447,11 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific updateRows(); actionBar.setTitle(LocaleController.getString("TwoStepVerificationTitle", R.string.TwoStepVerificationTitle)); - titleTextView.setText(LocaleController.getString("PleaseEnterCurrentPassword", R.string.PleaseEnterCurrentPassword)); + if (delegate != null) { + titleTextView.setText(LocaleController.getString("PleaseEnterCurrentPasswordTransfer", R.string.PleaseEnterCurrentPasswordTransfer)); + } else { + titleTextView.setText(LocaleController.getString("PleaseEnterCurrentPassword", R.string.PleaseEnterCurrentPassword)); + } } else if (type == 1) { setPasswordSetState(passwordSetState); } @@ -506,10 +517,16 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific } public void setCurrentPasswordInfo(byte[] hash, TLRPC.TL_account_password password) { - currentPasswordHash = hash; + if (hash != null) { + currentPasswordHash = hash; + } currentPassword = password; } + public void setDelegate(TwoStepVerificationActivityDelegate twoStepVerificationActivityDelegate) { + delegate = twoStepVerificationActivityDelegate; + } + @Override public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen) { @@ -793,22 +810,22 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific progressView.setVisibility(View.VISIBLE); doneItem.setEnabled(false); doneItemAnimation.playTogether( - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleX", 0.1f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleY", 0.1f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "alpha", 0.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleX", 0.1f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleY", 0.1f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "alpha", 0.0f), ObjectAnimator.ofFloat(progressView, "scaleX", 1.0f), ObjectAnimator.ofFloat(progressView, "scaleY", 1.0f), ObjectAnimator.ofFloat(progressView, "alpha", 1.0f)); } else { - doneItem.getImageView().setVisibility(View.VISIBLE); + doneItem.getContentView().setVisibility(View.VISIBLE); doneItem.setEnabled(true); doneItemAnimation.playTogether( ObjectAnimator.ofFloat(progressView, "scaleX", 0.1f), ObjectAnimator.ofFloat(progressView, "scaleY", 0.1f), ObjectAnimator.ofFloat(progressView, "alpha", 0.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleX", 1.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleY", 1.0f), - ObjectAnimator.ofFloat(doneItem.getImageView(), "alpha", 1.0f)); + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleX", 1.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "scaleY", 1.0f), + ObjectAnimator.ofFloat(doneItem.getContentView(), "alpha", 1.0f)); } doneItemAnimation.addListener(new AnimatorListenerAdapter() { @Override @@ -817,7 +834,7 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific if (!show) { progressView.setVisibility(View.INVISIBLE); } else { - doneItem.getImageView().setVisibility(View.INVISIBLE); + doneItem.getContentView().setVisibility(View.INVISIBLE); } } } @@ -842,7 +859,7 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific progressDialog.show(); } - private void needHideProgress() { + protected void needHideProgress() { if (progressDialog == null) { return; } @@ -1068,7 +1085,7 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific }); } - private TLRPC.TL_inputCheckPasswordSRP getNewSrpPassword() { + protected TLRPC.TL_inputCheckPasswordSRP getNewSrpPassword() { if (currentPassword.current_algo instanceof TLRPC.TL_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow) { TLRPC.TL_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow algo = (TLRPC.TL_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow) currentPassword.current_algo; return SRPHelper.startCheck(currentPasswordHash, currentPassword.srp_id, currentPassword.srp_B, algo); @@ -1153,12 +1170,18 @@ public class TwoStepVerificationActivity extends BaseFragment implements Notific Utilities.globalQueue.postRunnable(() -> { boolean secretOk = checkSecretValues(oldPasswordBytes, (TLRPC.TL_account_passwordSettings) response); AndroidUtilities.runOnUIThread(() -> { - needHideProgress(); + if (delegate == null || !secretOk) { + needHideProgress(); + } if (secretOk) { currentPasswordHash = x_bytes; passwordEntered = true; AndroidUtilities.hideKeyboard(passwordEditText); - updateRows(); + if (delegate != null) { + delegate.didEnterPassword(getNewSrpPassword()); + } else { + updateRows(); + } } else { AlertsCreator.showUpdateAppAlert(getParentActivity(), LocaleController.getString("UpdateAppAlert", R.string.UpdateAppAlert), true); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java index 3e58a9aa4..3cfe8c374 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java @@ -131,13 +131,6 @@ public class WebviewActivity extends BaseFragment { type = TYPE_STAT; } - /*@Override - protected void onTransitionAnimationStart(boolean isOpen, boolean backward) { - if (!isOpen) { - - } - }*/ - @Override public void onFragmentDestroy() { super.onFragmentDestroy(); @@ -209,7 +202,7 @@ public class WebviewActivity extends BaseFragment { progressView.setScaleX(1.0f); progressView.setScaleY(1.0f); progressView.setVisibility(View.VISIBLE); - progressItem.getImageView().setVisibility(View.GONE); + progressItem.getContentView().setVisibility(View.GONE); progressItem.setEnabled(false); } @@ -283,15 +276,15 @@ public class WebviewActivity extends BaseFragment { if (progressView != null && progressView.getVisibility() == View.VISIBLE) { AnimatorSet animatorSet = new AnimatorSet(); if (type == TYPE_GAME) { - progressItem.getImageView().setVisibility(View.VISIBLE); + progressItem.getContentView().setVisibility(View.VISIBLE); progressItem.setEnabled(true); animatorSet.playTogether( ObjectAnimator.ofFloat(progressView, "scaleX", 1.0f, 0.1f), ObjectAnimator.ofFloat(progressView, "scaleY", 1.0f, 0.1f), ObjectAnimator.ofFloat(progressView, "alpha", 1.0f, 0.0f), - ObjectAnimator.ofFloat(progressItem.getImageView(), "scaleX", 0.0f, 1.0f), - ObjectAnimator.ofFloat(progressItem.getImageView(), "scaleY", 0.0f, 1.0f), - ObjectAnimator.ofFloat(progressItem.getImageView(), "alpha", 0.0f, 1.0f)); + ObjectAnimator.ofFloat(progressItem.getContentView(), "scaleX", 0.0f, 1.0f), + ObjectAnimator.ofFloat(progressItem.getContentView(), "scaleY", 0.0f, 1.0f), + ObjectAnimator.ofFloat(progressItem.getContentView(), "alpha", 0.0f, 1.0f)); } else { animatorSet.playTogether( ObjectAnimator.ofFloat(progressView, "scaleX", 1.0f, 0.1f), diff --git a/TMessagesProj/src/main/res/drawable-hdpi/channelintro.png b/TMessagesProj/src/main/res/drawable-hdpi/channelintro.png index c46fcb3c6f87dc6a3f535bf86f7404c7d9080c83..85f044e034d5597239502fc2c53ba2b6b2a11d71 100644 GIT binary patch literal 25617 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 uR*yb-mKye`$F_uW=Fe$3c@*;%@N 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 literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/map_pin2.png b/TMessagesProj/src/main/res/drawable-hdpi/map_pin2.png new file mode 100644 index 0000000000000000000000000000000000000000..d96916fda439f19df90d64ce857ed5ca0190eb09 GIT binary patch literal 2311 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 JPDHLkV1nIP3j_cF literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/miniplayer_close.png b/TMessagesProj/src/main/res/drawable-hdpi/miniplayer_close.png old mode 100755 new mode 100644 index 5b43d2c93a4946dc7dc6772796477e6217ac7058..fa21608f9aeb82f4a9a04b9604807acc81af66c1 GIT binary patch delta 402 zcmV;D0d4+-0-*zt8Gi-<002wNzV84400d`2O+f$vv5yPUJ_dJ@5=P@8ICO zW6D?-3f-Rn)ePZPDx+hEsblgi1BK44ltr8Z|1*V-N&z0yWY{P>t!Ba-V*;KmC_46_ z_DEM|;$AHi7=y>Cbiyw9$Q##|t0KV>c<^S#(ni(%mv0I~@Dd%-K1`Jwbq0kE^f~B8 zzV^9!k(^iwHh;h=@VR3VeB{;sKyi{l6P$Siz6Aq=b{}P$MKNA5THxH1@D14WjCRwY zFv_;v4~)NYl#WoM#4uDVhEq( zcss+wcqNo5?4ApmQE>kTWBV6KNPZ6N(c659%+Z<^ez_ zcJu(;sTN4Z9e|!l5|PD9ip#cYgR$<}M`xy{%Mxv_T% z&KWQ@ko&AVTIlFQ%)lSRGP}BW?euMe&wzMor_0LdJD7DSCc9O{Hr}3{=m1>DLc?gT tV9SMx)@e%1#D%RSHVYMN)xnkjf{2PP1EL5n6hvJph$Cu*L_wSAs^7im+|#$Z zUaC5M`qW0vfwTj4>76{bJ3BiE!ZQ(!PeIhrQg~WdSJ$C~|J9)Q2CxCNfX|@P&>YQG zOhd0XgCgdDGvEiX4#g_RoL*-Lq9=f(;3o(*luJ1$^a?`|x)Ee)f2Ns+a&IiG8Hno# z4w_1{1&OO)p$tLLaB$XEk5`m9hZn*Og!Bc~Frnn2xcgS!3`FcLDXQY@-l`fw;cKf{ ze0S=#K4zF#mZ%sDaVr?=BOqYxCvr_=Mf%*5)92jU4PjACQ0>wpO{QV$2)bc9Z1+Ha z6KlMnAEcf^S3Gj1uy({Ah>B){YL_Nya~{qeK{t$m+rqc_yVQQBO=8-acrnxDzbEOQXy%cZ5tlM6`g@`^*6BlY zH9~6$%g`Na>#)W;X>~Q?gRz>y#yEvpW1Y0R8sU7NCk9OOFv1$^q}A1khsJ_d8RHaY zjkRj6ER}265MaGUzo0V1pEKZEZMkkH8i)LuE$|*BcZ}k05SRIUXKp=sY~;G*TDI${ zq5KzINs{^VdDTj>;wQx%Exua7lqA;m#i}to4aVFPm)Zr*sf=gMIA>o713>y zm$@TEm=GVbk_U`aH<(T6s>N3fU7>}Q9 zt6BN)z}>zkbk*@YpM8t2&=UC;m|@Riio8$MER6VRicST2cZ6~!Lbj&z=C!#cgbBgL z(iwUFmg-!{0?Z4loC7cB1*{8T#QI#s#wNjtlOATAj;$}Z{FpETJT#cmO3cXI zgyum+L9@YU2Q$7fcU~Uaa_68h{nuXBu_mM@3{q0EAn9iQ0EyVMAz3K1u|kwz zNmDi!Rt6=_jvBL&m1Hp!3b8N@k|vBW!}WRUHuKJV?|IHS_de&6r@qa3&-0w`^L@^H z&pG#=$)*@vxm<3Cav4|*7J(7)77T%>S(Xj+_un!^Zv$Q6HPCDnXD>Jiq`1Fph}a4q zfO>3%K_@{=)2kq9E9e0h_7ReuO)G+A?ckY(@&d;(p0lKdkYFPiE+n>myb4*GA|PH0 zhAh=OxQ?5@1rp5y4;=|pbnh}P0)Mo>G=;8f_ zUruw?)D;iGG)BJz^Hr8==PSDUzz5I3lC3hvV&YW|z5?C^sw(k;4qgLrmI8StY*35!70|B@&iUk! z+Mj#Yni41Rp*9d2SQW>t?hD;2h)Zz}42HhhD)G|6;Dz892sJ6f@+9u6DJ<9N{oEqx zKJlA!V?vr%--SBZc6&n-I%6o{aOm_K$9;lNPGZ%3i&zF<2cs%W8s{};f6PbiEdUbP z#=3n(#{S>%oe$yk7EmEM8$xCnbBl?45yBT#0A#Wk-wq)(iaBG%GeN_G+HAZ?U387@ zIunwsTV%ZH%4*avXU@9n%tk$suK+)c#MYv@Y$Vl9L)O!^G8^=Lfg}BuJ4sqViYvw= z?rAgjP{4LzX^$ZDp1P)Gkno041#+LccGa+s^c&rq$*Epp;9pYC4`8`{-OZM3kF`~Q!jO8l0M|(!1bZKZ2 kya&7ocfhGP>SG!nu33di3fwx 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 K?C}Nv8~*`W1zza@ literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/sim_old.png b/TMessagesProj/src/main/res/drawable-hdpi/sim_old.png new file mode 100644 index 0000000000000000000000000000000000000000..a24256a05a8724277a99f3b69aee60428c6f0956 GIT binary patch literal 2250 zcmV;*2sQVKP)^Ph|00004XF*Lt006O% z3;baP000PqNklXV%R8 zHZ0D}o;`c5{o8l{?|kf;88e0i0SG_<0uX=z1Rwwb2tdFcfdNku4Gj&Qs(7xK&6iyv zn;|=0Hbpj8)@%Az73`1=4h#(J?zJ-ZhX4c$Bp|`KRJLCBr!0^3ubkXeV3TnT0SJti zfCldz*$1*?vb@t#Ib1c`*x3&P5XhN;2JC=rwaotW7oDC|0Ip8ia_$b?LBKx&4bt(l z%|)w4oBubpezJdhLX% z%KUctPOcW|j?2OPT;d4=Cdb}G25bjtFd=OBp<0+k^ku~;jcmDV6_pCa2R^O)?}nVHS_wLDd358RO; z0D)KpG{_D#Y;l?omAOdfF)eGxt>7+@?U(J6c|S^OhRrVo%0)nfYnH4v9n{KuvS(xu z%NDe@NbB2Vp3;V9dUT-ELIvdS zkM<~Z; z2vmwdOyL+l)DNUJS>-;KeJ%StaGzMk?6&ILBtIf!~{w&u5C~HW^1Ao;rqD3Mb$} zIPAkmcWIEi9GngDSXX#Y5dZ`n5%3@!pKEa1wGGedHP68j8wBzu;6XU-r8yl*kEc(U zeJHa}6eJ~kZQm0eJ9f!X+4E%fF43fPOqsi7_jcJmF~%-s$%a7X33w2Wj_Vp4!1neq zyS}hQHmOT5*lYV9=&~&-`(|Z!`^Jksm^MfDQj&P+h^QmRBnRX{pe_VF2uBj@#mbzK zw4p7}#3MA>w_rokeIbP?1A#gd@G2a$3+$94eHYklTtgt7fLGxtGA`by5&ri>$(j-< za)8zpIn9PZ`3ZOxjy-{B_Xd`;VlnE>;GUuos1AW~PMh*pBd)aT4K{J?-3;-^s566m zib9||1Y8P7V;1SAeqxSMXFubkE(nx~fY(!%GBG4p2-J=M!cn^}r@;{Doq$W>(Cp2$ z>EYTuoH9*0?;RC2KmY=Z(RfOi52hxZ;LJ_tA@;8i%79`5jug^(c7B;a!9 zsDfj9c(aH2g@8i>E`@{X;ST>&2nhl$1iYTAw1mha1R&szfXgEe%?q`c=`53t_;jp} zPJRl38l%n-zm$PM6$!W$4uLk&*cbqOh03IfOKTM|>TIo_WeC)UfY(!%P%)U5p{HWh znZZ3pAy6FxUWKDNglP!`DoDVqa2zX;waZUY7RUqF5a=M_RX9Q?fORye4;e-J?nC%g z0fG1gyb8w_H9h2evPUu?vUik*q~}5kQ3e8aCg4Fh>~oJNB;8`QPcB{~`$+cJ2^){E zm}oqGe09Vnd$Do1x1!tW$&a#C^8IpqO}dra5o3E9)*(M1&P@WY0lZK7OOv*Z zCa*EyBlONnyNkpa+Y7M{fw~azaKy1t|ItIf6I*1T++Cz|d_%w+0T04qZ~JgrlTLo{p#E8ml~ULrxDLBNY}SU4+W-)pcg zmz^y;sS#tYKk_?4cBSkUIo>Lpnri?!gFp!gc(^#)ipj2ZtZ!y$@HQ9ug#ZK`5%6@x z;Rp+%KmY=11Q3oiI&6ag1QH1#9En)Sga8E62p}A3bl3&~2qY3fI1;gt2>}SC5kNT7 z=&%g}5J)6|a3o?O69N!OBY<$E(P0||AdpA^;Yh?nCIldmMj-eZ49()4tcq@tO-rk= zW!t%0p5C%vm&JW*=>CAl+KYvD=yCrq1Ikha0 zXhmHJgr2I{kp`j>=dWTgPPNnufp`Q$g(LLzBwnP|t(m89tJ}AgZCR*rR2BdYg8&4w z6F@k!Gh+`3)SdvsQTsmu06-u+0fZwvGxmT$?Fobmhs`-4#UXGcPPNnufp`Q$g=0s& z5NcoZ7quqVJ}1Gn=O9(h{zxS0SG_<0uX=z1PUYY YKR*J(#ml264gdfE07*qoM6N<$f^MtrN&o-= literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/channelintro.png b/TMessagesProj/src/main/res/drawable-mdpi/channelintro.png index 78a1964b260fd01f93afb56196bbbdcb8de2cf0b..51d0161b175c8ae4e32f6c1026c0eb0aeb6b0625 100644 GIT binary patch literal 15062 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 literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/groupsintro.png b/TMessagesProj/src/main/res/drawable-mdpi/groupsintro.png new file mode 100644 index 0000000000000000000000000000000000000000..284317152aaf61d42120839d08c8497cc96b7f52 GIT binary patch literal 17416 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 u!~&DTZot2XduyP#2L3-xkwQB<7SD130000tD? z0ttc24Gx@mRe~c@??}AVBZo@BrO--ElTty`#BoRnA&Kq0?0EfHd%Zi%?=WVw@s4+u zjGZ)bP*_l!AYvHjBD-!6*cQ%(Uti7`K@A4QZ=k)bMjhVE+-U)`(a{aY=&*Sxj+33)f zj*$PwlZpYS+bw##I{nYjor!#^*Qg2JjYgge`n|7v+@ePhIEn3cioRg8=ZzV{O?hg9 zh)16Uo84g=cvyW)z#SHO^fBeD3f?(0bg8q|H$sEYNK=%BpRT_I|Rp|DDZAp z20XeyR<}$cyV*Q~*|QpGh@xYF7dh^6GGJj*S}LFlshg~}osn2!MBX9dF&ZFBm{b(Z z1Q$vQGY-LG0}PZjqUqtueaA?}XA%aQm`}W#+w5c43((LIx5z6hx>1ysG)pnqVN3~L}oyO`LOY_HnnZf*sU z+!~L;Fcln&$9Kuz@+x^eMMIA15U>SVy9&8xAk#-m6YicJzTMZ=GD*j*er+o~GkQPv zGz()oLz<+P)OSeX-JxT%9xz%-d)F*Z{S5^t;_=x`R-U5?W<6lWUgfGt9xGQAelGi7L z4L(d_M!(tEvf$Xl!q5Ae;<}Ltd=NAG&Bm4mIgZ2RJ6kpq5#OH>Gy2WOmIZ-;oanz( zDwWy>6>RXCk=OIv5)>5fCn;C1ZMeWDz8~umoB3=BLdnXN%C)~oz-E=PdZM-jfq*>2 zbxNeE_#bLp?QIKUO-ohD$uc3kX%GjFhBcYqP<5L~2Mu~4Cp%o+~XkiE?t;SF(CVD1FL)1bN5=nIZyv=+!!@7I7EZ5*9FEjJzz4_km?d`R+ z{O=K+<^tn5zKwnu^qOyxRxDe8| zS2`GD8`!i>U&wch7HI~gQOMI)q%Gtz&B;w{gN{O>4bWUQEh(OBq(3po&;yRZ572#L zNCVd|f&PE_gmVu71g~380t{O6pedN@OO)r5~Jn-sTg*>vQ^0coKtdrEbYFxWUW$NOO4U zAA>RV#1>d#9;;z~g|r7QfL{2Y&RR!V`D}VSPWDkr)ucfyd1J|7V*` zl7A}3IQ@0^&M?g339D~M@+>n;7)rQ5<+;eH$AN}ttZ*YO98g_^qDSH(!$4u1lWP<1XDw^ zN?v!wkis5RN0|6)noXm%nN|yWH%%jDH4l-E6$V0;}&obH2`??-0DMVAK~#9!b+>HyCX3zGYlWlH_A+-unTQ59mIyL87b8` zf)n6ptkbrBSvyg(-_WEy$}TVX+BCa% z76Pr=FZ9lUhd{WGfYV?j2!&{z175=+k_tQAP#&^5`u3QFm4%aqX?Rny7uEcEwdU@?On64F5)O-Vn7-@7t1= zTvvuQk4<@f{ALmENYNv&nqNDT_K8=^i!qiNw9i+2*S0)agLm$(rjovjta1K>00 zHiTm!n~TU*2wmvbt#CxjW^YF<0dpX0%(GDS49M=%Sc%{eSjpg6)(jhTkMk2lp!VlOBrlOBlKgTyeF0M*&5g z%B^nN;&?`Cf#tm^V@!T*F0`3mZbh?_6e$W6a z!3($shhWR|yeE#?4}|-`F0jQD#Z{n8W+xyk0UJOP&oqz4b^u8QNJy0b^)5S`fT#fM z|08rcopLgP(!7hBX6A!jx5xwo_25-Q=9=0w8OSvhCdlU-YJU2L#U?lfAAZts?8JHW zyFhc&a;cBhM*j+N-U3c6GpaQEcgDD;u{MM#L)<4~rUpD%W)J*c>k)z;3rWg0%-oV? zRy{2Pq1ywcfv(>~;&-WC9ci=QNiDC=fbA;79nZvJcUuW-ljL{6iE7AFtnkr>JuNe(a6Bx$N>jV~ky4h)9^s6kJaB1Z739_C zbPGfV1wy*kdy?gD>P#{0fjB=(CY{CoK@7BkcOZ>32EtzHzKs0{(VS3S8IJzFx66Nh z6h#o83rsAAaf<7RcYwDDCQj)o6bnQdfOtB@zzg-}C|piGkmrJJi-~b3V#yHV<={$V zVqL-$s~7Qhpl-JxM=XkCkxGXV90KZ3$1SLn&S#|`;Z{%;hwKyB4!8O(#)#?w0000< KMNUMnLSTZsr4=Ut literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/phone_change.png b/TMessagesProj/src/main/res/drawable-mdpi/phone_change.png deleted file mode 100755 index bb58221c8730de70873ca579e18281df6b04ba2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1088 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 g17bi7OeqHb156MQgm1%&#EP)es#Z{Q3RP z@AJO%+>}(LYy$QN)=K$>&i7y|BE-j(+l2rGco6mk zmRI={ebXR{VU&@s3DPGR>k1Ky0gEZxj*ceK-Y&hlNcEcb(m(@dXKBvgM89P7%5LFNCuS?}xeBVLa!T_-{ zr6LJHRHrSXbKUf)$lMvby$DohGaV>(X{Az`5Fgm@#%3LCeQn#1Z70liIgUA8avg;J zBOm|~pNCz5El|@%-8q=+a#!La$P$>Ge)OvI;4ulLCIA6Fhe?@)J%-(e9fMV1%VDkF z8N`s(Lv7R#Y7!8cmgp9x_HW*fj0sav^UfYW=@d`GX zW2`Rt+5}scqffeuoj{WtC2~9bh)YJ|A0}~Z<$%jM(04!tY$`&VQ!&>uN}MM1prGrp zxV00o2x;6tGT&Cr!}LsvOp$`l7k68Gb9GTD=gcz&qUwX`{?Fn4nNFG=jFE?&)FOWR zl(j;xAd$c*!>IM?}DtpjCuAF{^!A zInLW(E3w(PjS2V?bQ>e!)IuWQOVAGt&_)gQ_3EtS>f^bMjDU|DD;;=zv)Q?_7JJrl zT4$_`f`G4Y=qO+^$>Irk`G&?a+fk&tQF_!1bhiPde8*mhk!3Ze>J3IsyB3JH3`@W_&SG{XD_yB zrt(5l$0`!YOu&nvv63-{8H%xxfKviq1f6x+F6%g_#OOR+0uY?3j3l#0C;4p@U71Su zQ};0-)j0GU(K)Zx@p)(!Y{199E(%S{bEf(_a#OphzCMoIND257^tYV(=It-%>Cs(L z1Y8MPeOu9>A;D(LcBXdKpBm2)j@6+PaCK4G@BltmIR#B!BK**?$c=j-Z}L%^12%F8MJl%If0 zK?@xb+llgHVg_X)z<;XC@=0-?sR&TesW7oqSqMRHHxyeKMD=nU$TH4yBsT+BiZmx{su4A*imF2(ewrqE{M!>dgS9VAu=6arG&xP|R0TKv}z(2-wuLgxhmCyhH002ov JPDHLkV1oEVlnnp? literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/channelintro.png b/TMessagesProj/src/main/res/drawable-xhdpi/channelintro.png index fb7b8df77cae86efe77bebc844ea182f9d2fa72e..59c39b4e78fc33931e8b64567d3c1d2f52ebfaa0 100644 GIT binary patch literal 36747 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

l~##l4C8h#dH799M;Nz!ijkg&D3d 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 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ghost.png b/TMessagesProj/src/main/res/drawable-xhdpi/ghost.png new file mode 100644 index 0000000000000000000000000000000000000000..a241d8026a720178281448fdee436b92f37599c4 GIT binary patch literal 1237 zcmV;`1SO- zagdOR$B7Gxi-X67gG5Cl;U*ChDq7K`H1U2HMO&X=v%A@DXXpReo!Qx#U-Hd==Kuda zzw@7&|IE(j<%JS55Hb)l5Hj%pG7#~j^q!uc0mNAZ)__@H92gJAf^Xn;6s@$k1d&MO z6K!q_h0g^S!AD>se^I^*Hi7({V}Yb+gR7t$q$^Kxuo(oO2$GC|!=MXfS{iXEr%W(~ z`}#_{UFkU%m+z``;?v-^939L&u2}}Et*qyPojhk67gkXOJURuEDxVP+VY-G`N#Wr+0GvH7F zikG@iaWD#P39y<01S}@tJ22QbaE9&bnFZHueA>={x&Rd~b#JpNUTg|14cBYfHKTJm zs<)ALriM+Tii!PNxh63SJ|{ZxY@1+TfScTAaA+(6*#^?5D>n8^WAQv~sX&&zZt{o0 z8OcdVACNJX*xK(*j;XV6a}AiNaNNXHe4eZm?Q2Tzci6YJuXOOG?DB$uS1wW8UB+TV zw)Puh4oq4b0){IBRJe@yJ2f11ZSAcVWHtmiq$by|1~GtD(3LET*nTi%D>-jkm3tbeh7m6+@GqR zRQ-P#n89taEm^^72FPdaw#C22R=(_blN=}m`|?fF2O!VoEc~!?F`~00000NkvXXu0mjfz_&3v literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/groups_create.png b/TMessagesProj/src/main/res/drawable-xhdpi/groups_create.png new file mode 100644 index 0000000000000000000000000000000000000000..b6aeb9e2861d21ae6007d0a7cf73732634410545 GIT binary patch literal 1404 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 literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/map_pin2.png b/TMessagesProj/src/main/res/drawable-xxhdpi/map_pin2.png new file mode 100644 index 0000000000000000000000000000000000000000..c5b1c9cd871c397c22fef342cb85c4f5c056e3b5 GIT binary patch literal 5114 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 cs!~t(|3T)un+a07*qoM6N<$g2i1PKmY&$ literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_location.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_location.png new file mode 100644 index 0000000000000000000000000000000000000000..2b799192cc0996d8aaa938cd295270cb6897877b GIT binary patch literal 2301 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 literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/miniplayer_close.png b/TMessagesProj/src/main/res/drawable-xxhdpi/miniplayer_close.png old mode 100755 new mode 100644 index 20aa1fdde84beafa2e1f04b13c44dc89803f0ad9..b37f8a2d97db7199fc750a23f128d2acd8823953 GIT binary patch delta 639 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 VCNS)goY(*W002ovPDHLkV1g+h!qNZ$ literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/phone_change.png b/TMessagesProj/src/main/res/drawable-xxhdpi/phone_change.png deleted file mode 100755 index 0867a90e6da46abeb2786339bc5bd16964666e58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3535 zcmeHI`9Bm|8=s+=8A~%}rbXOlrtG8T+6LLi$k@in*doH*vXh;VWrjgzi_##nuOVf< zV&XQ+mQa>tGRc||SKO3W`h4E^KX`w-=ZCXA-|so!=RBY1JSUM%G8BP`LjVAPh!K%+ z9smHccIO=+{@p%5EQ1XI@cEHVDF!?Lwtpk=e~bV#67_jE1qTDqlPm!T&R+D7h`#>& zZ=uaIn>#I4G3Z@x@Sa4A4wTnO^#CS`L>NyHRq5+#!196ESYuZ>m7F5uk4GfRStyUarCof3QY7d754b zl^mB<-6gd$ixL|1=@!G>CumE}qQsESi9CIv=A;wGDgw}MOqmr$8yXhfNa=hHHh#;I zbDvS-Sr%%j9oSNr32Fm5iZ|#dqUUFXcrg~-5Tb=cktO4fwe0yyxL8hyuy;4oBw^AL5WnXX8&c=qFVh zl|t`Vk-6$#yHJGdE|iQ+x}-th^q^yW=MgVNt5c_R#tL1%1oBbBh30Fxq=&V9kNd>| z`xt^^yGioRWmVm2yF zFBi2U%9uM}v?=I0m&a-;$7fV`3I!4Ko@r1g{Xx>Q(x0tO|O3@>t{E^2(A= z^dLb*zH5qR&hLEX8*pwLc*%5@vx%i`$~}l;_^x?b4S7MLQJMbuA%4QS+#T^KW3_I_ zo=rrM{KWGZGRVAK_|G9rh(YSggj|E>G8B-G_&`hz1t;<}QAvI~j&AdYAoIbP{!t(n zzFN$(2a&q&KvqyVn3H^4!ZabGyjBaE!#IV^GGv%_WkWQfOokFtTh9yAPuUc%OT<|^ zhoMzrzVbQrK|))(pC}%_TEemcnRPKC?@`LT0Ct5Y{eeV32F+$@B1w8w%xB75I-4+A z&Vys%S4&x645*kNluePC#7#@iiA|YX6wZNbA>ZGvBmcU(-T8Std)g;6?lD~X;z6AL zC%Ycbbmr-*ljV(a*3T50T3ZM9CtP=LpURPn;a<~eZs>{JxwYOdzBL|MLKRuReLKs7 zotc>xDsb8C)j3@9k!X+%_Tl0C`xez<}W#_TC~FVQ~)Nc zHhc?Jxv1(ErjlBFIra|1*re)}9`SK6fPiw2`l?biI9|7?OIwNfEY;n1UsD;n;Ua)y zf!_F*9q(RH9X<}qI0_=ql(FH#R|HTGK%Pv{;9q77G-f3zJtUwDtV0z*#ey2Wq=-Qi z6mX*&yli$kO}c8a4?qxhPG;&h+Yf^d!Ds4pALx$Vp%xG&PpQfSk%iB5BNC!8nM=mujLC0(>hc%Fvgqy>Yn=C>5&zTU+aRR zSQLq;ogns)O`~SW0P9c9*FeT7SPj~CstPt5+EW^7`SrQh^;g|HNfSCj$F{iD7E)Md zp7Qmmq4oMtIU|eVx)Ns_PR24M8iIlrN*A*vqsn6M)y zK`9bu8sRiPl@`O7@Bcn*h7MfXe8Bx^Pr|yM#Rz&MW^XL^G+j=YvPCx(Z-Mf}Fb?=J z_hZkL?Ljn?tl!s$q_|FYDOuM8Zd=Ud9%*7lZA@-W=@LY(NpkV2TloVmnX|SiQz<)} z0=#TXI@YQ|ZHCpoE~K=*^Q2}68?=(hebbP&QDX5n?38+fk8FDzB8J_0&qNj^d4-Pd=n0~094}=a0 z?j8Mo*jrS7r|qNjX?Z4I?<05gwO+FJ36pSnRg?js{ki&8XLvYKn&L!{rQV5X?=KSE z*2oasUhEAt!sW917q|T94(a?@S9*!%6Ob`AXoqA|*I3e3HxSOnLwfXQ<9gjqs^7+( zlENREilj2)xQU1Y8821^5oS&IcYD7);xnM#5JC%g(+S?na+vo zH(Z=W#pXxh^_%#G%I*(BeTMkmKq`|okigP5ExG|(0sz#}qFs_}H4ltd z?sr=JV9eebLbZDY?L9*8vpqud9)ah&=TRtf&!bsg;7>2T_H1{5>FfNWKXu=yOuG>O zz(iLcMC9ifWAmFs&gpO^q~Pwm|*`KPwu-DCOaCF+6FKUjdis~jPW17i<9 z^-H8aQ1)jmHhXbWgLEp^;Sq^lR^`km}? zv^sv{_}H5Y*GAIf;nop7%fqc7!@FXJRDw-}WwI5zZ;3}8TBhab*3UL&EZxuKgxrPQ z-r#|~d9d5!aX0e4Lg#tiJaFZiY% z$+jr&c=bN-_pVQjWykMi&&e>r7iZoDd1 zmchrfV}1*CjBjn#Z~1s>f`2~w3E+HEM|gC)-^(=3wW~i4MzN1=zM<=+(6LmskR2O; zus-#FQ=VYm?h4z*5UU9sVQ6w)2%x|E>6(TEduk; z^pPoHQ6oygCqMU4Lg76fRzeX(%&bfdH`10n=qByM+>|$Z)OK6PX4Wno`21e=hL6*Z g!}Z2@Ezc_rZH^@OTFYwfzFh#o$bdws!C#8~4+j3MIsgCw diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/sim_new.png b/TMessagesProj/src/main/res/drawable-xxhdpi/sim_new.png new file mode 100644 index 0000000000000000000000000000000000000000..2af617503d8f99df976c66d770570d220e1e0f21 GIT binary patch literal 6396 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 literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/sim_old.png b/TMessagesProj/src/main/res/drawable-xxhdpi/sim_old.png new file mode 100644 index 0000000000000000000000000000000000000000..23d68f24afcb1fd542332a4f444dc1bb13372add GIT binary patch literal 5495 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�PN*)plY#2GRqrMR)kR(jP^3AAv0hZXz=M;0i?0JeHcQ*KbvB;7KV}VF z!tX-8`38XI`o%+3+dMwC+IIHtqsFx=;G!W76_sWLT~9r~dvUQ4GE;@Z@hxsz8XGXw zWgjygprL4PObi=7xE&AH`=KP^t$li?41ee7toUY=iwLfC`r$#Dbh$c6MmvI4jJHV) zwB{W`bL8h0p0>qBde*q=OuZ7V>I1>1RAW9G5rm&sUDh z`?FENodct~?f#U3bmLC_EK)!!#W|sP+CZ`?Ov-b!!g6+~qFhbUNfWu`vhrvQN^hE^ zAb|ln%$AY7GDTeec~ZMXydCl{0Jbq_89gULZB7GmK&#}jXoP&Hg{=V&u3c&urnaM2 zpgH;z%gsS90lP>x02p7?D{4kG6@jR|Y)18|eYBE|1y%X}M-sJaNswG72~2bXA$4;GMfe zDVBP$ohd=;(PmWmwwh^n{~BcRlQ^OI&XpQugn?65wC42f1L>639CvWzw1lGzucW45 zJp-8-a0E;wi0X_%O&oX#=XFfiBZ8@*g{c8W@H5EQKMpb*dlMKU|9&v0yIb*+jIR1i zbOV~lk*n;x{c20z&F#3D)DV}Qz1G|~`a(e{k(N=KhQ(&4!mD-6_vjVdr+pf)q0aFFp#wWns{X+*- z)`whI3-r~Z66KA-TX`4|`3V5PK)D~!0zc}QhLjWe`{3sp0OwMr1qrT-C$OpWM$LGq zA4R=Ey>F=dTP>J%rdCzK?{n7fLJ2g&d6#8nB#)6CA0_O1heVAQ zz*#h2@*lgc+bL42GAGxm+sG>8zAot{Nq$FH)RwJF&j|Y#=+q9Kk&?V69{jz3NBmCO zjjTB6>PfZs4Is9&@1x01)|^vswJi{VklNu^YU1O1wXZ$t*`XS~#(-$aQA3U`u7|?;TnfH~D*7Gnc*uHDcY-uY zf>27I+eY6iPgr}MlA%?mLjgM0q}s!u-(z35=qdP?$K3=3mf3Hw^h`Y|dDS|(w^_C@ zU2{uJCfIsoVp(;Z#t4Lm$bx#}coZyUpRSgKwkH>K>uhuFnOYfM5P2h(5GK3l!mBIO zKk-H;IDb~#=ZVlfX#b>)Ac1=yZkT@rix>Y=cbQf2QXs8BS!`HT9JKW@`~;TH!AE)n zbP%GzL)xx!m+^h5J~f$v`czAU+fwV(k&6ZO-+ypfm|$sZ+aYlN=L!4?wVBZlrvVb` zFJ&w*mPYv)6mdA!ost|ceMbX{VfDAk&Ld*n&dinq9l4epGl_YNA6}mfkkQtXH02QM zy07E~)$OuzULfj6-I=3ucfcINt1Zb)7J$?l)~I1(C1T+46n3Cnw2FQ5=lNwh;P+Pc z(y%85y`AOI%Qir&QeZ;lv6bMd^FkNs8h`esc2ege<`e_sq_R&db%Bf$23RuWu_6hU z=zs8F*w1$S3$|k<81#F7I}bD95Zm*3nQ(i9)k7+P|0=KoLE%x!3?=3N{)LHsKcb?i#X={Ig$(?Y PN~3qv@J6+k!^8gpQ)x5- literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/raw/contact_check.json b/TMessagesProj/src/main/res/raw/contact_check.json new file mode 100644 index 000000000..6248347bc --- /dev/null +++ b/TMessagesProj/src/main/res/raw/contact_check.json @@ -0,0 +1 @@ +{"v":"5.5.2","fr":60,"ip":0,"op":45,"w":36,"h":36,"nm":"Lamp 3","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"luc12","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-43.5,75.344,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":-60,"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":"luc12","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[40]},{"t":20,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.527],"y":[0.407]},"t":2,"s":[40]},{"t":5,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":45,"st":-15,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"luc11","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[43.5,75.344,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":60,"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":"luc11","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.526],"y":[0.407]},"t":2,"s":[60]},{"t":5,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[60]},{"i":{"x":[0.201],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[60]},{"t":20,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":45,"st":-15,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"luc10","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,87,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":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":"Преобразовать"}],"nm":"luc10","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.526],"y":[0.407]},"t":2,"s":[60]},{"t":5,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[60]},{"i":{"x":[0.201],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[60]},{"t":20,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":45,"st":-15,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"luc9","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75.344,43.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":30,"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":"luc9","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.526],"y":[0.407]},"t":2,"s":[60]},{"t":5,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[60]},{"i":{"x":[0.201],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[60]},{"t":20,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":45,"st":-15,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"luc8","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[87,0,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[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":"luc8","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.526],"y":[0.407]},"t":2,"s":[60]},{"t":5,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[60]},{"i":{"x":[0.201],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[60]},{"t":20,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":45,"st":-15,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"luc7","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75.344,-43.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":-30,"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":"luc7","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.526],"y":[0.407]},"t":2,"s":[60]},{"t":5,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[60]},{"i":{"x":[0.201],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[60]},{"t":20,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":45,"st":-15,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"luc6","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[43.5,-75.344,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":-60,"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":"luc6","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.526],"y":[0.407]},"t":2,"s":[60]},{"t":5,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[60]},{"i":{"x":[0.201],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[60]},{"t":20,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":45,"st":-15,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"luc5","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-87,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":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":"Преобразовать"}],"nm":"luc5","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[40]},{"t":20,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.527],"y":[0.407]},"t":2,"s":[40]},{"t":5,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":45,"st":-15,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"luc4","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-43.5,-75.344,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":60,"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":"luc4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[40]},{"t":20,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.527],"y":[0.407]},"t":2,"s":[40]},{"t":5,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":45,"st":-15,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"luc3","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-75.344,-43.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":30,"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":"luc3","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[40]},{"t":20,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.527],"y":[0.407]},"t":2,"s":[40]},{"t":5,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":45,"st":-15,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"luc2","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-87,0,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[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":"luc2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[40]},{"t":20,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.527],"y":[0.407]},"t":2,"s":[40]},{"t":5,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":45,"st":-15,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"luc1","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-75.344,43.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":-30,"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":"luc1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[40]},{"t":20,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.527],"y":[0.407]},"t":2,"s":[40]},{"t":5,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":45,"st":-15,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"info1","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.5,3.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-28.5,0.5],[-9.5,19.5],[28.5,-19.5]],"c":false},"ix":2},"nm":"contour","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":12,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"stroke","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":"Преобразовать"}],"nm":"info1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.799],"y":[0]},"t":5,"s":[0]},{"t":15,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":45,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Oval","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[18,18,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.203,0.203,0.667],"y":[1,1,1]},"o":{"x":[0.17,0.17,0.333],"y":[0,0,0]},"t":2,"s":[11.667,11.667,100]},{"t":27,"s":[16.667,16.667,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24],"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","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":45,"st":-15,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/TMessagesProj/src/main/res/values-ar/strings.xml b/TMessagesProj/src/main/res/values-ar/strings.xml index 6ddd4e87a..8696287f0 100644 --- a/TMessagesProj/src/main/res/values-ar/strings.xml +++ b/TMessagesProj/src/main/res/values-ar/strings.xml @@ -202,6 +202,8 @@ غامق مائل رمز + خط متوسط + خط سفلي عادي يحتاج **تيليجرام** إلى الوصول لجهات اتصالك لكي تتمكن من التواصل مع أصدقائك عبر جميع أجهزتك، ستتم مزامنة جهات اتصالك باستمرار مع خوادم تيليجرام السحابية شديدة التشفير. ليس الآن @@ -231,6 +233,9 @@ المعذرة، المجموعة ممتلئة. المعذرة، قرر هذا المستخدم مغادرة المجموعة، لا يمكنك إضافته مرة أخرى إليها. المعذرة، يوجد الكثير من المشرفين في هذه المجموعة. + المعذرة، هذا المستخدم لديه كثيرًا من المجموعات أو القنوات العامة. دعه يجعل بعض مجموعاته أو قنواته خاصة أولًا. + المعذرة، هذا الشخص يملك الكثير من المجموعات المحلية. الرجاء طلب منه حذف بعض منها أولًا. + المعذرة، أنت تملك الكثير من المجموعات المحلية. الرجاء حذف بعض منها أولًا. المعذرة، يوجد الكثير من حسابات البوت في هذه المجموعة. ثبت un1 «%1$s» ثبت un1 رسالة @@ -266,6 +271,7 @@ لقد قمت بتغيير بعض الإعدادات في هذه القناة؛ تطبيق التغييرات؟ قناة عامة مجموعة عامة + مجموعة محلية يمكن العثور على القنوات العامة عبر البحث ويمكن لأي شخص الانضمام لها. يمكن العثور على المجموعات العامة عبر البحث، سيكون محتوى المجموعة متاحًا للجميع، ويمكن لأي شخص الانضمام إليها. قناة خاصة @@ -275,6 +281,7 @@ رابط دائم رابط الدعوة إضافة أعضاء + إضافة أعضاء مغادرة القناة مغادرة القناة الإعدادات @@ -283,7 +290,7 @@ منشور صامت ما هي القنوات؟ القنوات هي أداة جديدة لإيصال رسائلك لجمهور غير محدود. - إنشاء قناة + إنشاء مجموعة عذرًا، هذا الاسم محجوز. المعذرة، الاسم غير مقبول. يجب أن تتكون روابط القنوات من 5 خانات على الأقل. @@ -325,6 +332,7 @@ تم تغيير اسم القناة إلى un2 المعذرة، لقد قمت بحجز أسماء مستخدمين كثيرة. يمكنك تعطيل بعض روابط مجموعاتك وقنواتك القديمة, أو قم بإنشاء واحدة خاصة عوضًا عن ذلك. المالك + مشرف مشرف كتم إلغاء الكتم @@ -348,6 +356,7 @@ المعذرة، يوجد الكثير من المشرفين في هذه القناة. المعذرة، يوجد الكثير من حسابات البوت في هذه القناة. المعذرة، يمكنك إضافة أول 200 عضو للقناة فقط. يمكن لعدد غير محدود من الناس الانضمامُ للقناة عبرَ رابطها. + المعذرة، أنت عضو في الكثير من المجموعات والقنوات. الرجاء مغادرة بعض منها قبل إنشاء واحدة جديدة. un1 أضافك لهذه القناة انضممتَ للقناة قمتَ بالانضمام لهذه المجموعة @@ -388,6 +397,19 @@ حذف الرسائل إضافة مشرفين جدد إزالة المشرف + نقل ملكية المجموعة + نقل ملكية القناة + تأكيد أمني + تستطيع نقل هذه المجموعة إلى **%1$s** فقط إذا: + تستطيع نقل هذه القناة إلى **%1$s** فقط إذا: + فعّلتَ **التحقق بخطوتين** منذ أكثر من **سبعة أيام**. + سجّلتَ دخولك على هذا الجهاز منذ أكثر من **24 ساعة**. + قم المحاولة لاحقًا. + تعيين كلمة مرور + سيؤدي هذا لنقل **جميع حقوق الملكية** الخاصة بـ **%1$s** إلى **%2$s**. + تغيير المالك + أصبح الآن **%1$s** مالك المجموعة. + أصبح الآن **%1$s** مالك القناة. حظر المستخدمين إضافة مستخدمين دعوة المستخدمين عبر الرابط @@ -423,6 +445,7 @@ حظر وإزالة من المجموعة تطبيق التغييرات؟ لقد قمت بتغيير صلاحيات هذا المستخدم في **%1$s**؛ تطبيق التغييرات؟ + Custom إدارة المجموعة إدارة القناة إدارة المجموعة @@ -445,6 +468,8 @@ عامة خاصة عامة + رابط + انقر لإضافة رابط دائم اختر صورة التقاط صورة الاختيار من المعرض @@ -455,27 +480,27 @@ المعذرة، يمكن إضافة البوتات للقنوات كمشرفين فقط. تعيينُه كمشرف سيتم إزالة %1$s من المشرفين إذا قمت بتقييده. - Discussion - Add a group chat for comments - Linked Channel - Select a group chat for discussion that will be displayed in your channel. - Everything you post in the channel will be forwarded to this group. - A link to **%1$s** is shown to all subscribers in the bottom panel. - **%1$s** is linking the group as its discussion board. - All new messages posted in this channel are forwarded to the group. - Create a New Group - Unlink Group - Unlink Channel - Unlink - 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. - \"Chat history for new members\" will be switched to Visible. - Are you sure you want to unlink **%1$s** from this group? - Are you sure you want to unlink **%1$s** from this channel? - DISCUSS - channel + المناقشات + أضف مجموعة للتعليقات + القناة المربوطة + قم باختيار مجموعة نقاشات ليتم عرضها في قناتك. + سيتم تحويل جميع منشورات القناة الجديدة إلى هذه المجموعة. + يظهر رابط **%1$s** لجميع المشتركين في الجزء السفلي من القناة. + تم ربط هذه المجموعة بـ **%1$s** على أنها منصة نقاشاتها. + سيتم تحويل جميع المنشورات الجديدة في هذه القناة للمجموعة. + إنشاء مجموعة جديدة + إلغاء ربط المجموعة + إلغاء ربط القناة + إلغاء الربط + ربط المجموعة + هل ترغب بجعل **%1$s** منصة نقاشات **%2$s**؟ + هل ترغب بجعل **%1$s** منصة نقاشات **%2$s**؟\n\nسيتمكن جميع أعضاء المجموعة من رؤية منشورات القناة. + هل ترغب بجعل **%1$s** منصة نقاشات **%2$s**؟\n\nسيتمكن جميع مشتركي القناة من رؤية الرسائل في هذه المجموعة. + سيتم تغيير \"سجل المحادثة للأعضاء الجدد\" ليصبح ظاهرًا. + هل ترغب حقًا في إلغاء ربط **%1$s** من هذه المجموعة؟ + هل ترغب حقًا بإلغاء ربط **%1$s** من هذه القناة؟ + مناقشة + قناة استفتاء جديد استفتاء @@ -521,7 +546,7 @@ هي قائمة تحوي جميع الأحداث التي قام بها مشرفو القناة خلال آخِر 48 ساعة. غيَّر un1 اسم المجموعة إلى «%1$s» غيَّر un1 اسمَ القناة إلى «%1$s» - un1 غادر المجموعة + غادر un1 المجموعة un1 غادر القناة un1 أضاف un2 un1 انضم للمجموعة @@ -543,12 +568,15 @@ ألغى un1 تثبيت رسالة أوقف un1 الاستفتاء: حذف un1 هذه الرسالة: + غيّرَ un1 موقع المجموعة ليصبح «%1$s» + أزال un1 موقع المجموعة + نقلَ ملكية المجموعة لـ %1$s un1 غير حزمة ملصقات المجموعة \nun1 أزال حزمة ملصقات المجموعة - un1 made un2 the discussion group for this channel - un1 removed the discussion group un2 - un1 linked this group to un2 - un1 unlinked this group from un2 + جعل un1 un2 مجموعة نقاشات هذه القناة + أزال un1 مجموعة النقاشات un2 + ربط un1 هذه المجموعة بـ un2 + ألغى un1 ربط هذه المجموعة بـ un2 غيّرَ un1 رابط المجموعة: غيّرَ un1 رابط القناة: un1 أزال رابط المجموعة @@ -674,8 +702,9 @@ لا توجد رسائل أحدث الرسالة الرسالة + مشاركة رقم هاتفي مشاركة جهة اتصالي - أضف لجهات الاتصال + إضافة لجهات الاتصال %s دعاك للانضمام لمحادثة سرية. لقد قمت بدعوة %s لمحادثة سرية. المحادثات السرية: @@ -705,16 +734,25 @@ رفع %1$s إرسال كملف إرسال كملفات - Open Link - Do you want to open %1$s? - Log in to %1$s as **%2$s** - Allow **%1$s** to send me messages + فتح الرابط + هل ترغب في فتح %1$s؟ + تسجيل الدخول إلى %1$s من حساب **%2$s** + السماح لـ **%1$s** بمراسلتي إلغاء الإرسال هل ترغب في السماح لـ %1$s بإرسال اسمك وهويتك على تيليجرام للصفحات التي تفتحها عبر هذا البوت؟\n\nهويتك ليست رقمَ هاتفك. + المجموعة غير متعلقة بالموقع؟ + مجموعة غير متعلقة بالموقع + يرجى إخبارنا إذا كانت هذه المجموعة غير متعلقة بهذا الموقع:\n\n**%1$s** + يرجى إخبارنا إذا كانت هذه المجموعة غير متعلقة بهذا الموقع. تبليغ عن إزعاج + تبليغ عن إزعاج + حظر %1$s + حظر المستخدم تبليغ عن إزعاج ومغادرة إضافة جهة اتصال + إضافة %1$s لجهات الاتصال عرض جهة الاتصال + هل ترغب بحظر **%1$s** من مراسلتك والاتصال بك على تيليجرام؟ هل ترغب حقًا في التبليغ عن إزعاج من هذا المستخدم؟ هل ترغب حقًا في التبليغ عن إزعاج من هذه المجموعة؟ هل ترغب حقًا في التبليغ عن إزعاج من هذه القناة؟ @@ -763,7 +801,7 @@ مرِّر إلى الأسفل للبوتات %1$s المعذرة، انتهت مهلة تعديل الرسالة. - إضافة إلى الشاشة الرئيسية + إضافة للشاشة الرئيسية البحث عن أعضاء قم بتحويل الرسائل إلى هنا لحفظها الرسائل المحفوظة @@ -820,7 +858,8 @@ بلا معاينات تثبيت خرائط جوجل؟ احتيالي - via + عبر + Message doesn\'t exist عيّن %1$s عداد التدمير الذاتي ليصبح %2$s قمت بتعيين عداد التدمير الذاتي ليصبح %1$s @@ -928,7 +967,7 @@ تيليجرام اختر جهة اتصال - Select Contacts + تحديد جهات اتصال مشاركة جهة الاتصال لا توجد جهات اتصال بعد مرحبا، أنا أستخدم تيليجرام للمحادثة، انضم إلي! قم بتنزيله من هنا: %1$s @@ -956,6 +995,8 @@ اكتشف تيليجرام العديد من جهات الاتصال غير المزامَنة، هل ترغب في مزامنتها الآن؟ اختر «نعم» إذا كنت تستخدم جهازك الخاص وشريحتك وحساب جوجل الخاص بك. مرتبة حسب الاسم مرتبة حسب تاريخ آخر ظهور + إضافة %1$s + رقم الهاتف إضافة أشخاص... ستتمكن من إضافة أعضاء أكثر إذا انتهيت من إنشاء المجموعة وقمت بتحويلها إلى مجموعة خارقة. @@ -981,8 +1022,8 @@ نسخ الرابط مشاركة الرابط يمكن لمستخدمي تيليجرام الانضمام إلى مجموعتك عبر هذا الرابط. - Search for people... - Search for users and groups... + البحث عن أشخاص... + البحث عن مستخدمين ومجموعات... أعضاء الوسائط المتبادلة @@ -1005,15 +1046,15 @@ ** في المجموعات الخارقة:**\n\n• يمكن للأعضاء الجدد مشاهدة محتوى المحادثة كاملًا\n• تختفي الرسائل المحذوفة من أجهزة جميع الأعضاء\n• يمكن للمشرفين إضافة وصفٍ للمجموعة\n• يمكن لمالك المجموعة إنشاء رابط عام لها **ملحوظة:** لا يمكنك التراجع عن هذا القرار. - مشاركة - إضافة + مشاركة جهة الاتصال + إضافة لجهات الاتصال إضافة جهة اتصال لم يقم %1$s بالتسجيل في تيليجرام بعد. هل ترغب في دعوتهم؟ دعوة - حظر - Block user - User blocked - User unblocked + حظر المستخدم + حظر مستخدم جديد + تم حظر المستخدم + تم إلغاء حظر المستخدم تعديل جهة الاتصال حذف الجهة منزل @@ -1025,6 +1066,7 @@ الميلاد اللقب إنشاء جهة اتصال جديدة + جهة اتصال جديدة إضافة إلى جهة اتصال موجودة النبذة التعريفية أضف بضعَ كلمات عن نفسِك @@ -1044,6 +1086,11 @@ https://telegram.org/faq/ar#g غير معروف غير معروف + رقم الهاتف مخفي + سيصبح رقمك ظاهرًا حالما يقوم %1$s بإضافتك كجهة اتصال. + بمجرد ضغطك على **تم** سيصبح رقم هاتفك ظاهرًا لـ %1$s. + مشاركة رقم هاتفي مع %1$s + تمت إضافة %1$s إلى قائمة جهات اتصالك. معلومات هاتف الوسائط المتبادلة @@ -1121,7 +1168,7 @@ معطَّل معطَّل مجدول - Adaptive + تلقائي الجدولة استخدام التوقيت المحلي للشروق والغروب تحديث الموقع @@ -1133,8 +1180,8 @@ قم بالتبديل للنمط الليلي عند انخفاض الإضاءة المحيطة إلى %1$d%% أو أقل. مظلم أزرق غامق - Graphite - Arctic Blue + غرافيت + ثلجي أزرق هل ترغب حقًا في حذف هذا النمط؟ ملف النمط غير صحيح @@ -1143,8 +1190,8 @@ حفظ النمط نمط جديد لون النمط - Built-in themes - Custom themes + الأنماط الافتراضية + الأنماط المخصصة عرض قائمة المحادثات خطين اثنين ثلاثة خطوط @@ -1152,8 +1199,8 @@ تطبيق معاينة النمط اختر لونًا - Color Themes - Show all Themes + الأنماط + عرض جميع الأنماط إنشاء نمط جديد اضغط على أيقونة الألوان لاستعراض قائمة الخيارات في كل شاشة - ثم قم بتعديلها. يمكنك إنشاء النمط الخاص بك عبر تغيير الألوان داخل التطبيق.\n\nيمكنك العودة إلى نمط تيليجرام الافتراضي في أي وقت من هنا. @@ -1213,7 +1260,10 @@ تشغيل إيقاف المستخدمون المحظورون - Blocked users will not be able to contact you and will not see your Last Seen time. + لن يتمكن المستخدمون المحظورون من مراسلتك أو مشاهدة آخر ظهور لك. + حظر المستخدم + المحادثات + جهات الاتصال تسجيل الخروج بدون صوت افتراضي @@ -1221,10 +1271,11 @@ فقط في وضع الصامت تأثير ضبابي تأثير حرَكي - خلفية المحادثة + تغيير خلفية المحادثة + تغيير خلفية المحادثة إعادة تعيين الخلفيات لإزالة جميع الخلفيات المرفوعة واستعادةِ الخلفيات الافتراضية لكل الأنماط. - Reset chat backgrounds + إعادة تعيين الخلفيات هل ترغب حقًا في إعادة تعيين كافة خلفيات المحادثة؟ هل ترغب حقًا في حذف الخلفيات المحددة؟ معاينة الخلفية @@ -1379,7 +1430,8 @@ إعادة الإشعارات يمكنك تغيير رقم تيليجرام الخاص بك هنا، سيتم نقل حسابك وجميع بياناتك التي في سحابة تيليجرام بما فيها رسائلك، الوسائط، جهات الاتصال وغيرها للرقم الجديد.\n\n**هام:** ستحصل جميع جهات اتصالك على **رقمك الجديد**، في حال وجود رقمك القديم عندها وعدم قيامك بحظرها سابقًا في تيليجرام. ستحصل جميع جهات اتصالك على رقمك الجديد، في حال وجود رقمك القديم عندها وعدم قيامك بحظرها سابقًا في تيليجرام. - تغيير الرقم + تغيير الرقم + تغيير الرقم الرقم الجديد سيتم إرسال رسالة SMS قصيرة تحوي رمز التأكيد إلى رقمك الجديد. الرقم %1$s مرتبط بالفعل بحساب آخَر على تيليجرام. يرجى حذف ذلك الحساب قبل محاولة تغيير رقمك. @@ -1395,6 +1447,9 @@ إشعارات ذكية الاستثناءات إضافة استثناء + حذف جميع الاستثناءات + حذف جميع الاستثناءات + هل ترغب حقًا بحذف جميع الاستثناءات؟ استثناء جديد "يحوي هذا القسم على جميع المحادثات التي تملك إعدادات إشعارات مخصصة.\n\nيمكنك تخصيص إشعارات المحادثات عبر فتح صفحاتها واختيار \'الإشعارات\'. " لا شيء @@ -1421,7 +1476,7 @@ استيراد جهات الاتصال إعادة تنزيل جهات الاتصال إعادة تعيين الحوارات - Read all Chats + "قراءة كافة المحادثات " تفعيل الكاميرا الداخلية للتطبيق تعطيل الكاميرا الداخلية للتطبيق إعادة ضبط جهات الاتصال المستوردة @@ -1503,6 +1558,11 @@ لم يتم العثور على نتائج ما من عمليات بحث تمت مؤخرًا الأسئلة الشائعة + Distance Units + Distance units + Automatic + Kilometers + Miles قاعدة البيانات على الجهاز هل ترغب في مسح الرسائل النصية من الذاكرة المؤقتة؟ @@ -1794,8 +1854,10 @@ الخريطة قمر صناعي مختلطة - مترًا من هنا - ك.مترًا من هنا + %1$s m away + %1$s km away + %1$s ft away + %1$s mi away إرسال موقعي الحالي شارك موقعي الحي لمدة... إيقاف مشاركة الموقع @@ -1807,6 +1869,7 @@ الموقع مكان دقيق لدرجة %1$s + على بُعد %1$s أو اختر مكانًا اسحب للأعلى لمشاهدة الأماكن القريبة المواقع الحية @@ -1823,6 +1886,23 @@ اختر المدة التي سيتمكن خلالها %1$s من مشاهدة موقعك الفعلي. اختر المدة التي سيتمكن خلالها أعضاء هذه المحادثة من مشاهدة موقعك الحي. يبدو أن تحديد المواقع معطَّل لديك، يرجى تفعيله لاستخدام ميزات تيليجرام التي تحتاجه. + الأشخاص القريبون + إضافة الأشخاص القريبين + استخدم هذا القسم لتبادل أرقام الهواتف مع الأشخاص القريبون منك.\n\nاسمح بالوصول إلى موقعك لتفعيل هذه الميزة. + الأشخاص القريبون + السماح بالوصول + استخدم هذا القسم لتبادل أرقام الهواتف مع الأشخاص القريبون منك.\n\nقم بتشغيل خدمات الموقع لتفعيل هذه الميزة. + تشغيل + المجموعات القريبة منك + اطلب من أصدقائك فتح هذه الصفحة لتبادل أرقام الهواتف. + يتم البحث عن مستخدمين قريبين منك... + إنشاء مجموعة محلية + إنشاء مجموعة + أي شخص قريب من هذا الموقع (الجيران، زملاء العمل، الطلاب، الزوار، السُيّاح) سيرى مجموعتك في قسم الأشخاص القريبون. + في حال قمت بإنشاء مجموعة غير متعلقة بهذا الموقع، قد يتم منعك من إنشاء مجموعات محلية جديدة. + تعيين موقع + تعيين هذا الموقع + سيكون بإمكان الناس العثور على مجموعتك في قسم «الأشخاص القريبون». عرض كافة الوسائط عرض جميع الملفات @@ -1899,6 +1979,7 @@ يمكنك تعيين كلمة مرور إضافية يتم طلبها عند تسجيل الدخول من جهاز غير معروف، إضافةً إلى الرمز الذي يصلك في رسالة SMS قصيرة. كلمة مرورك قم بإدخال كلمة مرورك + يرجى إدخال كلمة مرورك لإتمام عملية النقل. قم بإدخال كلمة مرور قم بإدخال كلمة مرورك الجديدة قم بإعادة إدخال كلمة مرورك @@ -1979,26 +2060,26 @@ الخصوصية والأمان الخصوصية - آخر ظهور + آخر ظهور ومتصل الصورة الشخصية من يمْكنه رؤية صورتي الشخصية؟ بإمكانك تخصيص من يمْكنه رؤية صورتك الشخصية بدقة انتقائية. - ستتجاوز هذه الإعدادات القيم في الأعلى. - Phone Number - Who can see my phone number? - Users who already have your number saved in their contacts will see it on Telegram. - You can add a user or an entire group as an exception that will override the settings above. + يمكنك إضافة مستخدمين أو مجموعات بأكملها كاستثناءات ليتجاوزوا القيم في الأعلى. + رقم الهاتف + من يمْكنه رؤية رقم هاتفي؟ + المستخدمون الذين يملكون رقمك محفوظًا في دليل عناوينهم سيتمكنون من رؤيته في تيليجرام. + يمكنك إضافة مستخدمين أو مجموعات بأكملها كاستثناءات ليتجاوزوا القيم في الأعلى. الرسائل المحوَّلة رابط إلى حسابك السماح بالرابط حسب الإعدادات أدناه بدون رابط إلى حسابك من يضيف رابطًا إلى حسابي عند تحويل رسائلي؟ لن تملك الرسائل التي تقوم بتحويلها بنفسك إلى محادثاتٍ أخرى رابطًا إلى حسابك. - ستتجاوز هذه الإعدادات القيم في الأعلى. + يمكنك إضافة مستخدمين أو مجموعات بأكملها كاستثناءات ليتجاوزوا القيم في الأعلى. نعم، جاهز للإنطلاق في أي وقت! هل سنخبرهم بقدومنا أم نجعلها مفاجأة؟ - Peer-to-Peer + النِّد للنِّد النِّدُّ للنِّدِّ في المكالمات - Use Peer-to-Peer with + استخدام النِّدِّ للنِّد مع البوتات والمواقع مسح معلومات الدفع والشحن حذفُ معلومات الشحن الخاصة بك، ومطالبةُ موفِّري خدمات الدفع بحذف بطاقاتك الائتمانية المحفوظة؟ اعلم أن تيليجرام لا يحفظ بيانات بطاقتك الائتمانية أبدًا. @@ -2029,11 +2110,12 @@ إذا لم تستخدم حسابَ تيليجرام هذا لمرة واحدة على الأقل خلالَ هذه المدة فسيتم حذفه نهائيًا مع جميع محادثاتك وجهات اتصالك. من يمْكنه رؤية آخر ظهور لك؟ إضافة استثناءات - هام: لن تتمكن من رؤية آخر ظهور للأشخاص الذين اخترت ألا يروا آخر ظهور لك.\nسيتم بدلًا من ذلك عرض آخر ظهور تقريبي (شُوهدَ مؤخرًا، خلال أسبوع، خلال شهر). + إضافة للاستثناءات + لن تتمكن من رؤية آخر ظهور للأشخاص الذين اخترت ألا يروا آخر ظهور لك. سيتم بدلًا من ذلك عرض آخر ظهور تقريبي (شُوهدَ مؤخرًا، خلال أسبوع، خلال شهر). لقد قمت بتغيير بعض إعدادات الخصوصية؛ تطبيق التغييرات؟ المشاركة دائمًا مع عدم المشاركة مع - ستتجاوز هذه الإعدادات القيم في الأعلى. + يمكنك إضافة مستخدمين أو مجموعات بأكملها كاستثناءات ليتجاوزوا القيم في الأعلى. المشاركة دائمًا مع عدم المشاركة مع إضافة مستخدمين @@ -2054,8 +2136,9 @@ اقتراح الجهات المتكررة عرضُ الأشخاص الذين تراسلهم باستمرار في أعلى خانة البحث للوصول إليهم بشكل أسرع. سيؤدي هذا إلى حذف جميع البيانات المتعلقة بالأشخاص الذين تراسلهم بشكل متكرر بالإضافة إلى البوتات الاستعلامية التي قد تستخدمها. - Add Users or Groups - Exceptions + إضافة مستخدمين أو مجموعات + الاستثناءات + لا أحد يتم إرسال المقطع المرئي... يتم إرسال الصورة المتحركة... @@ -2115,7 +2198,7 @@ انضممت للمجموعة عبر رابط دعوة un1 انضم للمجموعة عبر رابط دعوة un1 أزال un2 - un1 غادر المجموعة + غادر un1 المجموعة un1 أضاف un2 أزال un1 صورة المجموعة غيّرَ un1 صورة المجموعة @@ -2172,8 +2255,8 @@ لقد قمت بتصوير الشاشة! صوّر un1 الشاشة! - Warning! This will **delete all messages** in this chat for **both** participants. - Delete All + تحذير! سيؤدي هذا لحذف **جميع رسائل** هذه المحادثة من **كلا** الطرفين. + حذف الكل إيقاف التحميل؟ تحديث تيليجرام عذرًا، تطبيق تيليجرام الذي تملكه قديم ولا يمكنه التعامل مع هذا الطلب، يرجى القيام بتحديثه. @@ -2196,12 +2279,12 @@ هل ترغب بإضافة %1$s للمحادثة %2$s؟ عدد الرسائل اﻷخيرة المراد تحويلها: إضافة %1$s للمجموعة؟ - Add %1$s - Add member - Are you sure you want to add %1$s to **%2$s**? - Are you sure you want to add %1$s to **%2$s**? - Show the last 100 messages to the new members - Show the last 100 messages to **%1$s** + إضافة %1$s + إضافة عضو + هل ترغب حقًا بإضافة %1$s إلى **%2$s**؟ + هل ترغب حقًا بإضافة %1$s إلى **%2$s**؟ + عرض آخر 100 رسالة للأعضاء الجدد + عرض آخر 100 رسالة لـ **%1$s** إرسال الرسائل إلى %1$s؟ هل ترغب في مشاركة اللعبة مع %1$s؟ إرسال جهة الاتصال إلى %1$s؟ @@ -2229,7 +2312,7 @@ سيطلع البوت على رقم هاتفك. ربما يكون هذا مفيدًا للتكامل مع خدمات أخرى. هل ترغب حقًا في مشاركة رقم هاتفك %1$s مع **%2$s**؟ هل ترغب حقًا في مشاركة رقم هاتفك؟ - Are you sure you want to block **%1$s**? + هل ترغب حقًا بحظر **%1$s**؟ هل ترغب حقًا في إزالة الحظر عن جهة الاتصال هذه؟ هل ترغب حقًا في حذف جهة الاتصال هذه؟ محادثة سرية @@ -2267,7 +2350,7 @@ فصلًا قم بالسماح لتيليجرام باستقبال اتصالات ليتمكن من إدخال الرمز لك تلقائيًا. رجاءً قم بالسماح لتيليجرام بتلقي المكالمات للتمكن من تفعيل رقم هاتفك تلقائيًا. رجاءً قم بالسماح لتيليجرام بتلقي المكالمات للتمكن من تفعيل رقم هاتفك تلقائيًا. - المعذرة، لا يمكنك القيام بذلك. + المعذرة، ليس من المسموح لك القيام بهذا. المعذرة، لا يمكنك إضافة هذا المستخدم أو البوت للمجموعات بسبب أنك قمت بحظره. فضلًا قم بإزالة الحظر للاستمرار. انضمام للمجموعة المعذرة، لا يمكنك إضافة هذا المستخدم كمشرف لأنه ليس عضوًا في هذه المجموعة وغير مسموح لك إضافته. @@ -2660,12 +2743,12 @@ %1$d ملفات %1$d ملفًا %1$d ملف - %1$d blocked users - %1$d blocked user - %1$d blocked users - %1$d blocked users - %1$d blocked users - %1$d blocked users + %1$d مستخدم محظور + مستخدم واحد محظور + مستخدمان محظوران + %1$d مستخدمين محظورين + %1$d مستخدمًا محظورًا + %1$d مستخدم محظور %1$d ملف محوَّل ملف واحد محوَّل ملفان محوَّلان diff --git a/TMessagesProj/src/main/res/values-de/strings.xml b/TMessagesProj/src/main/res/values-de/strings.xml index ace510535..6aecac767 100644 --- a/TMessagesProj/src/main/res/values-de/strings.xml +++ b/TMessagesProj/src/main/res/values-de/strings.xml @@ -28,9 +28,9 @@ Prüfe deine Telegram-Nachrichten Code eingeben Wir rufen dich jetzt unter **%1$s** an.\n\nBitte den Anruf nicht annehmen, Telegram kümmert sich um alles. - Wir rufen **%1$s** an, um dir einen Code zu diktieren. + Rufe **%1$s** an, um dir einen Code anzusagen. Telegram ruft dich in %1$d:%2$02d an - Wir senden dir eine SMS in %1$d:%2$02d + Sende dir eine SMS in %1$d:%2$02d Wir rufen dich an… SMS wird gesendet... Code @@ -49,7 +49,7 @@ Dein Anmeldecode lautet **%1$s**. Gebe ihn in der Telegram-App ein, bei der du dich anmelden möchtest.\n\nDen Code niemals weitergeben. Dein Name - Bitte gib deinen vollständigen Namen ein und lade dein Profilbild hoch. + Namen eingeben und ein Profilbild hochladen. Vorname (erforderlich) Nachname (optional) Registrierung abbrechen @@ -202,6 +202,8 @@ Fett Kursiv Mono + Durchstreichen + Unterstreichen Normal **Telegram** benötigt Zugriff auf deine Kontakte, damit du dich mit Freunden auf all deinen Geräten verbinden kannst. Deine Kontakte werden durchgehend mit den stark verschlüsselten Cloud-Servern von Telegram synchronisiert. JETZT NICHT @@ -209,8 +211,8 @@ Deine Kontakte bei Telegram Das ist dein Archiv Chats mit aktivierten Benachrichtigungen landen wieder in der Chatliste, wenn neue Benachrichtigungen eintreffen. - Chats ohne Benachrichtigungen - Stummgeschaltete Chats bleiben im Archiv,\nwenn neue Nachrichten eintreffen. + Stummgeschaltete Chats + Stummgeschaltete Chats\nbleiben im Archiv, wenn\nneue Nachrichten eintreffen. Angeheftete Chats Unbegrenzt viele archivierte Chats\nkannst du oben anheften. @@ -231,6 +233,9 @@ Leider ist diese Gruppe schon voll. Dieser Nutzer hat die Gruppe verlassen, deshalb kannst du ihn nicht wieder hinzufügen. Es gibt bereits zu viele Administratoren. + Der ausgewählte Nutzer hat zu viele öffentliche Gruppen oder Kanäle. Bitte die Person, einfach einen Kanal oder eine Gruppe privat zu stellen. + Der ausgewählte Nutzer hat zu viele lokale Gruppen. Bitte die Person, Gruppen bei sich zu löschen oder Inhaber-Rechte an andere zu vergeben. + Du hast bereits zu viele lokale Gruppen. Bitte lösche zuerst einen deiner bestehenden. Es gibt bereits zu viele Bots. un1 hat \"%1$s\" angeheftet un1 hat eine Nachricht angeheftet @@ -266,6 +271,7 @@ Du hast einige Kanaleinstellungen verändert. Änderungen anwenden? öffentlich Öffentliche Gruppe + Lokale Gruppe Kann jeder über die Suche finden Öffentliche Gruppen kann jeder über die Suche finden, gesamter Chatverlauf ist für alle einsehbar und jeder kann der Gruppe beitreten. privat @@ -275,6 +281,7 @@ Dauerhafter Link Einladungslink Mitglieder hinzufügen + Mitglieder hinzufügen Kanal verlassen Kanal verlassen Einstellungen @@ -283,7 +290,7 @@ Lautloser Broadcast Was ist ein Kanal? In einem Kanal kannst du deine Nachrichten an ein großes Publikum schicken. - KANAL ERSTELLEN + Kanal erstellen Leider ist der Name schon belegt. Der Name ist ungültig. Kanalnamen benötigen mindestens 5 Zeichen. @@ -324,7 +331,8 @@ Bild gelöscht Kanalname zu un2 geändert Du hast leider zu viele öffentliche Benutzernamen erstellt. Du kannst jederzeit den Link einer älteren Gruppe oder eines Kanals entfernen. - Ersteller + Inhaber + Administrator Admin STUMM STUMM AUS @@ -348,6 +356,7 @@ Es gibt bereits zu viele Administratoren in diesem Kanal. Es gibt bereits zu viele Bots. "Du kannst nur die ersten 200 Leute einladen, aber unbegrenzt viele können dem Kanal über den Einladungslink beitreten. " + Du bist Mitglied in zu vielen Gruppen und Kanälen. Bitte verlasse einige, damit du eine neue erstellen kannst. un1 hat dich hinzugefügt Du bist dem Kanal beigetreten Du bist der Gruppe beigetreten @@ -388,6 +397,19 @@ Nachrichten löschen Neue Admins hinzufügen Admin entlassen + Neuen Inhaber ernennen + Neuen Inhaber ernennen + Sicherheitsprüfung + Du kannst die Gruppe **%1$s** nur übertragen, wenn: + Du kannst den Kanal **%1$s** nur übertragen, wenn: + **Zweistufige Bestätigung** aktiv ist und vor über **7 Tagen** eingeschaltet wurde. + Du dich auf auf diesem Gerät vor über **24 Stunden** angemeldet hast. + Bitte komm später wieder. + Passwort festlegen + Das wird die vollständigen **Inhaber-Rechte** von **%1$s** an **%2$s** übertragen. + Inhaber ändern + **%1$s** ist jetzt der Gruppeninhaber. + **%1$s** ist jetzt der Kanalinhaber. Nutzer sperren Nutzer hinzufügen Nutzer per Link einladen @@ -423,6 +445,7 @@ Sperren und aus der Gruppe entfernen Änderungen anwenden Du hast die Rechte des Nutzers bei ** %1$s** geändert. Änderungen anwenden? + Custom Gruppe verwalten Kanal verwalten Gruppe verwalten @@ -445,6 +468,8 @@ Öffentlich Privat Öffentlich + Link + Für dauerhaften Link antippen Bild auswählen Foto aufnehmen Aus der Galerie @@ -543,6 +568,9 @@ un1 hat angeheftete Nachricht entfernt un1 hat Umfrage beendet: un1 hat diese Nachricht gelöscht: + un1 hat Gruppenstandort zu \"%1$s\" geändert + un1 hat Gruppenstandort entfernt + hat Inhaberschaft an %1$s übertragen un1 hat das Sticker-Paket der Gruppe geändert un1 hat das Sticker-Paket der Gruppe entfernt un1 hat un2 als Diskussionsgruppe für diesen Kanal festgelegt @@ -674,6 +702,7 @@ Keine aktuellen Nachricht Nachricht + MEINE NUMMER TEILEN Meine Nummer teilen Zu Kontakten hinzufügen %s hat dich zu einem\nEnde-zu-Ende verschlüsselten\nGeheimen Chat eingeladen. @@ -711,13 +740,22 @@ Erlaube **%1$s**, mir Nachrichten zu senden Versand abbrechen Darf %1$s deinen Anzeigenamen und deine id (nicht deine Telefonnummer) mit Internetseiten teilen, die du mit diesem Bot öffnest? + GRUPPE PASST NICHT ZUM STANDORT? + Unpassende Gruppe melden + Bitte teile uns mit, wenn die Gruppe nicht mit diesem Standort in Verbindung steht:\n\n**%1$s** + Bitte teile uns mit, wenn diese Gruppe nicht diesem Standort in Verbindung steht. SPAM MELDEN + Spam melden + %1$s blockieren + NUTZER BLOCKIEREN SPAM MELDEN UND VERLASSEN KONTAKT HINZUFÜGEN + %1$s ALS KONTAKT HINZUFÜGEN KONTAKT ANZEIGEN + Wirklich **%1$s** blockieren und davon abhalten, dir Nachrichten bei Telegram zu senden oder dich anzurufen? Sicher, dass du Spam von diesem Nutzer melden willst? - Sicher, dass du Spam von dieser Gruppe melden willst? - Sicher. dass du Spam von diesem Kanal melden möchtest? + Möchtest du wirklich Spam aus dieser Gruppe melden? + Möchtest du wirklich Spam aus diesem Kanal melden? Du kannst im Moment nur Kontakten schreiben, die auch deine Nummer haben. Derzeit kannst du nur gemeinsame Kontakte Gruppen hinzufügen. Leider kannst du derzeit nichts in öffentlichen Gruppen veröffentlichen. @@ -821,6 +859,7 @@ Google Maps installieren? BETRUG via + Nachricht existiert nicht %1$s hat den Selbstzerstörungs-Timer auf %2$s gesetzt Du hast den Selbstzerstörungs-Timer auf %1$s gesetzt @@ -956,6 +995,8 @@ Telegram hat viele nicht synchronisierte Kontakte erkannt. Möchtest du diese jetzt synchronisieren? \'OK\' wählen, wenn du dein eigenes Gerät, SIM-Karte und Google-Konto benutzt. Nach Name sortiert Nach zuletzt gesehen sortiert + %1$s hinzufügen + Telefonnummer Leute hinzufügen... Sobald du diese Gruppe zu einer Supergruppe erweitert hast, kannst du mehr Nutzer einladen. @@ -1005,12 +1046,12 @@ **In Supergruppen:**\n\n• Neue Mitglieder sehen gesamten Verlauf\n• Nachrichten sind bei allen löschbar\n• Admins können Beschreibung festlegen\n• Ersteller kann Gruppe öffentlich machen **Wichtig:** Die Änderung kann nicht rückgängig gemacht werden. - Teilen + Kontakt teilen Hinzufügen Kontakt hinzufügen %1$s ist noch nicht bei Telegram. Willst du diesen Nutzer einladen? Einladen - Blockieren + Nutzer blockieren Nutzer blockieren Nutzer blockiert Nutzer freigegeben @@ -1025,6 +1066,7 @@ Geburtstag Position Neuen Kontakt erstellen + Neuer Kontakt Bestehendem Kontakt hinzufügen Bio Ein paar Worte, die dich beschreiben @@ -1044,6 +1086,11 @@ https://telegram.org/faq/de#geheime-chats Unbekannt Unbekannt + Nummer versteckt + Telefonnummer wird sichtbar sein, sobald %1$s dich als Kontakt hinzufügt. + Tippst du auf **FERTIG**, so wird deine Nummer bei %1$s sichtbar sein. + Meine Telefonnummer mit %1$s teilen + %1$s ist jetzt in deiner Kontaktliste. Info Telefon Geteilte Inhalte @@ -1214,6 +1261,9 @@ Ausschalten Blockierte Nutzer Blockierte Nutzer können dich nicht kontaktieren und sehen deine \"zuletzt gesehen\"-Zeit nicht. + Nutzer blockieren + CHATS + KONTAKTE Abmelden Kein Ton Standard @@ -1221,7 +1271,8 @@ Wenn lautlos Unscharf Bewegung - Chat-Hintergrundbild + Chathintergrund ändern + Chat-Hintergrund ändern Chat-Hintergrundbilder zurücksetzen Das löscht alle hochgeladenen Chat-Hintergründe und stellt die vorinstallierten Hintergründe für alle Themen wieder her. Chat-Hintergründe zurücksetzen @@ -1379,7 +1430,8 @@ Erneut benachrichtigen Du kannst deine Telefonnummer hier ändern. Dein Konto und alle Daten in der Telegram-Cloud, also Nachrichten, Medien, Kontakte, etc. werden auf das neue Konto übertragen.\n\n**Wichtig:** Alle deine Kontakte erhalten deine **neue Nummer** ihrem Telefonbuch hinzugefügt, sofern sie deine alte Nummer gespeichert hatten und du sie nicht blockiert hattest. Deinen Kontakten wird deine neue Nummer ihrem Telefonbuch hinzugefügt, sofern sie deine alte Nummer gespeichert hatten und du sie nicht blockiert hattest. - NUMMER ÄNDERN + Nummer ändern + Nummer ändern Neue Nummer Der Bestätigungscode kommt per SMS an deine neue Nummer. Die Telefonnummer %1$s ist bereits ein Telegram Konto. Bitte lösche es, bevor du mit der Übertragung auf das neue Konto startest. @@ -1395,6 +1447,9 @@ Schlaue Benachrichtigungen Ausnahmen Eine Ausnahme hinzufügen + Alle Ausnahmen löschen + Alle Ausnahmen löschen + Wirklich alle Ausnahmen löschen? Neue Ausnahme Deine angepassten Benachrichtigungen werden in diesem Bereich angezeigt.\n\nUm Benachrichtigungen anzupassen, Chat mit dem Gesprächspartner öffnen, Profilbild antippen, dann \'Mitteilungen\' und \'Anpassen\'. Keine @@ -1503,6 +1558,11 @@ Leider nichts gefunden Du hast bisher\nnoch nichts gesucht FAQ + Entfernungseinheiten + Entfernungseinheiten + Automatisch + Kilometer + Meilen Lokale Datenbank Textnachrichten-Cache leeren? @@ -1794,8 +1854,10 @@ Karte Satellit Hybrid - m entfernt - km entfernt + %1$s m entfernt + %1$s km entfernt + %1$s ft entfernt + %1$s mi entfernt Meinen Standort senden Meinen Live-Standort teilen Standortfreigabe beenden @@ -1807,6 +1869,7 @@ Standort Ort Auf %1$s genau + %1$s entfernt Oder wähle einen Ort Hochziehen, für Orte in der Nähe Live-Standorte @@ -1823,6 +1886,23 @@ Wähle, wie lange %1$s deinen Live-Standort sehen darf. Wähle, wie lange Leute in diesem Chat deinen Live-Standort sehen dürfen. Dein GPS scheint deaktiviert zu sein. Aktiviere es, um auf standortbezogene Funktionen zugreifen zu können. + Leute in der Nähe + Leute in der Nähe + Füge schnell Leute aus der Nähe hinzu, \ndie diesen Bereich ebenfalls ansehen,\nund entdecke lokale Gruppen-Chats.\n\nBitte aktiviere den Standortzugriff,\num diese Funktion nutzen zu können. + Leute in der Nähe + Zugriff erlauben + Füge schnell Leute aus der Nähe hinzu, \ndie diesen Bereich ebenfalls ansehen,\nund entdecke lokale Gruppen-Chats.\n\nBitte aktiviere die Standortdienste,\num diese Funktion nutzen zu können. + Einschalten + Gruppen in der Nähe + Bitte deinen Freund in der Nähe, diese Seite zu öffnen, um Nummern auszutauschen. + Prüfe, wer bei dir in der Nähe ist... + Erstelle eine lokale Gruppe + Gruppe erstellen + Jeder, der sich in der Nähe dieses Ortes aufhält (Nachbarn, Arbeitskollegen, Kommilitonen, Besucher einer Veranstaltung), wird deine Gruppe im Bereich \"Leute in der Nähe\" sehen. + Wenn du eine unpassende Gruppe an diesem Standort eröffnest, könnte es sein, dass du beim Anlegen neuer lokaler Gruppen eingeschränkt wirst. + Standort festlegen + Als Standort festlegen + Leute werden deine Gruppe im Bereich \'Leute in der Nähe\" finden können. Zeige alle Medien Zeige alle Dateien @@ -1899,6 +1979,7 @@ Du kannst ein eigenes Passwort festlegen, um dich an einem neuen Gerät anzumelden, zusätzlich zum SMS-Code. Dein Passwort Dein Passwort eingeben + Um die Übertragung abzuschliessen, bitte dein Passwort eingeben. Passwort eingeben Neues Passwort eingeben Passwort erneut eingeben @@ -1983,7 +2064,7 @@ Profilbild Wer darf mein Profilbild sehen? Hier kannst du per Feineinstellung bestimmen, wer dein Profilbild sehen darf. - Das überschreibt die Einstellungen oben. + Du kannst Kontakte oder ganze Gruppen hinzufügen, für die eine Ausnahme gemacht werden soll. Telefonnummer Wer darf meine Nummer sehen? Nutzer, die bereits deine Nummer in ihren Kontakten gespeichert haben, sehen sie auch bei Telegram. @@ -1994,7 +2075,7 @@ Keine Verknüpfung mit deinem Konto Wer darf beim Weiterleiten meiner Nachrichten eine Verknüpfung zu meinem Konto hinzufügen? Leitet man deine Nachrichten an andere Chats weiter, werden sie nicht mit deinem Konto verknüpft. - Das überschreibt die Einstellungen oben. + Du kannst Kontakte oder ganze Gruppen hinzufügen, für die eine Ausnahme gemacht werden soll. Reinhardt, wir brauchen neue Musik für dich 🎶. Peer-to-Peer Peer-to-Peer bei Anrufen @@ -2029,11 +2110,12 @@ Wenn du innerhalb dieser Zeit nicht online bist, wird dein Konto mit allen Nachrichten und Kontakten gelöscht. Wer darf deinen Online-Status sehen? Ausnahmen hinzufügen - Wichtig: Du kannst den \"zuletzt gesehen\" Status nur von Personen sehen, mit denen du auch deinen teilst. Ansonsten wird die ungefähre Zeit angezeigt (kürzlich, innerhalb einer Woche, innerhalb eines Monats). + Zu Ausnahmen hinzufügen + Du kannst den \"zuletzt gesehen\" Status nur von Personen sehen, mit denen du auch deinen teilst. Ansonsten wird die ungefähre Zeit angezeigt (kürzlich, innerhalb einer Woche, innerhalb eines Monats). Du hast einige Änderungen im Bereich Privatsphäre durchgeführt. Möchtest du die Änderungen anwenden? Immer teilen mit Niemals teilen mit - Hier kannst du Kontakte hinzufügen, für die eine Ausnahme gemacht werden soll. + Hier kannst du Kontakte oder ganze Gruppen hinzufügen, für die eine Ausnahme gemacht werden soll. Immer teilen Niemals teilen Hinzufügen @@ -2047,15 +2129,16 @@ Hier kannst du Nutzer hinzufügen, für die eine Ausnahme gemacht werden soll. Ändere, wer dich in Gruppen und Kanäle einladen kann. Du kannst diesen Nutzer nicht hinzufügen, weil er das nicht erlaubt. - Du kannst diesen Nutzer nicht hinzufügen, weil er das nicht erlaubt. + Der Nutzer hat das Hinzufügen zu Kanälen in den Privatsphäre-Einstellungen leider nicht erlaubt. Du kannst mit diesen Nutzern keine Gruppe erstellen, weil sie es nicht erlauben. Deaktivierst du Peer-to-Peer, werden alle Anrufe über die Telegram Server geleitet. Dadurch ist deine IP-Adresse nicht mehr beim Gesprächspartner sichtbar, die Gesprächsqualität wird jedoch leicht abnehmen. Synchronisierte Kontakte löschen Häufige Kontakte vorschlagen Zeigt Leute im oberen Bereich der Suche an, die du häufig kontaktiert hast. Das löscht alle Daten über Personen mit denen du häufig chattest, sowie Vorschläge für Inline-Bots. - Add Users or Groups - Exceptions + Nutzer oder Gruppen hinzufügen + Ausnahmen + Niemand Sende Video... Sende GIF... @@ -2172,8 +2255,8 @@ Du hast ein Bildschirmfoto gemacht! un1 hat ein Bildschirmfoto gemacht! - Warnung! Das wird alle Nachrichten in diesem Chat für beide Teilnehmer löschen. - Delete All + Warnung! Das wird **alle Nachrichten** in diesem Chat **für beide Teilnehmer löschen**. + Alles löschen Ladevorgang stoppen? Aktualisiere Telegram Deine Telegram-App ist leider veraltet und kann diese Anfrage nicht verarbeiten. Bitte aktualisiere Telegram. @@ -2186,7 +2269,7 @@ Ungültiger Code Du hast dein Konto leider zu oft gelöscht. Bitte warte einige Tage, erst dann kannst du dich erneut registrieren. Ungültiger Vorname - Ungültiger Nachname + Dieser Name kann leider nicht benutzt werden Lädt… Du hast keinen Videoplayer. Bitte installiere einen um fortzufahren. Bitte sende eine E-Mail an sms@stel.com mit einer Beschreibung des Problems. @@ -2239,7 +2322,7 @@ Wirklich deinen Verlauf mit **%1$s** leeren? Wirklich den geheimen Verlauf bei dir und **%1$s** leeren? Wirklich den Verlauf in **%1$s** leeren? - Möchtest du wirklich den Verlauf löschen? + Wirklich alle Nachrichten in diesem Chat löschen? Wirklich den Chat **Gespeichertes** leeren? Cache (Texte und Medien) des Kanals wirklich löschen? Cache der Gruppe wirklich löschen? @@ -2290,7 +2373,7 @@ Telegram benötigt Zugriff auf deinen Speicher, damit du Bilder, Videos und Musik senden und speichern kannst. "Telegram benötigt Zugriff auf dein Mikrofon, damit du Sprachnachrichten senden kannst. " Telegram benötigt Zugriff auf dein Mikrofon, damit du Videos aufnehmen kannst. - Telegram benötigt Zugriff auf deine Kamera, damit du Bilder und Videos aufnehmen kannst. + Telegram benötigt Zugriff auf deine Kamera, damit du Bilder und Videos aufnehmen kannst. Bitte aktiviere es in den Einstellungen. "Telegram benötigt Zugriff auf deinen Standort, damit du ihn mit Freunden teilen kannst. " Telegram benötigt Zugriff auf deinen Standort. Telegram braucht Zugriff auf die Funktion \'Über andere Apps einblenden\'. Nur so können Videos im Bild in Bild Modus wiedergegeben werden. diff --git a/TMessagesProj/src/main/res/values-es/strings.xml b/TMessagesProj/src/main/res/values-es/strings.xml index 20663b482..606ff3127 100644 --- a/TMessagesProj/src/main/res/values-es/strings.xml +++ b/TMessagesProj/src/main/res/values-es/strings.xml @@ -28,9 +28,9 @@ Revisa tus mensajes en Telegram Pon el código Estamos llamando al número **%1$s**.\n\nNo contestes. Telegram hará todo el proceso automáticamente. - Estamos llamando al **%1$s** para dictar un código. + Llamando a tu número **%1$s** para dictar el código. Telegram te llamará en %1$d:%2$02d - Te enviaremos un SMS en %1$d:%2$02d + Enviándote un SMS en %1$d:%2$02d Te estamos llamando... Enviando SMS... Código @@ -49,7 +49,7 @@ Tu código de inicio de sesión es **%1$s**. Ponlo en la app de Telegram en la que estás intentando iniciar sesión.\n\nNo le des este código a nadie. Tu nombre - Por favor, pon tu nombre completo y sube tu foto de perfil. + Pon tu nombre completo y sube una foto de perfil. Nombre (requerido) Apellidos (opcional) Cancelar registro @@ -174,7 +174,7 @@ %1$s usa una versión antigua de Telegram, así que las fotos secretas serán mostradas en un modo de compatibilidad.\n\nCuando %2$s actualice Telegram, las fotos con autodestrucción de 1 minuto o menos funcionarán con el modo “Mantén pulsado para ver”, y te notificaremos siempre que la otra parte haga una captura de pantalla. Mensajes Buscar - Silenciar notificaciones + Silenciar Silenciar %1$s Notificar En %1$s @@ -202,6 +202,8 @@ Negrita Cursiva Monoespaciado + Tachado + Subrayar Normal **Telegram** necesita acceso a tus contactos para que puedas comunicarte con tus amigos en todos tus dispositivos. Tus contactos se sincronizarán continuamente con los servidores en la nube fuertemente cifrados de Telegram. AHORA NO @@ -231,6 +233,9 @@ Lo sentimos, el grupo está lleno. Lo sentimos, este usuario decidió salir del grupo, así que no puedes añadirlo otra vez. Hay demasiados administradores en el grupo. + Lo sentimos, el usuario objetivo tiene demasiados grupos o canales públicos. Por favor, pídele que haga privado uno de sus grupos o canales existentes primero. + Lo sentimos, el usuario objetivo tiene demasiados grupos por ubicación. Por favor, pídele eliminar o transferir uno de los existentes primero. + Lo sentimos, tienes demasiados grupos por ubicación. Por favor, elimina uno de los existentes primero. Lo sentimos, hay demasiados bots en el grupo. un1 ancló \"%1$s\" un1 ancló un mensaje @@ -262,10 +267,11 @@ Descripción (opcional) Descripción Puedes poner una descripción para tu canal. - Has cambiado algunos ajustes en este grupo. ¿Aplicar cambios? - Has cambiado algunos ajustes en este canal. ¿Aplicar cambios? + Cambiaste algunos ajustes en este grupo. ¿Aplicar cambios? + Cambiaste algunos ajustes en este canal. ¿Aplicar cambios? Canal público Grupo público + Grupo por ubicación Cualquiera puede unirse a los canales públicos, tras encontrarlos en la búsqueda. Los grupos públicos pueden encontrarse en la búsqueda, su historial está disponible para todos y cualquiera puede unirse. Canal privado @@ -275,6 +281,7 @@ Enlace permanente Enlace de invitación Añadir miembros + Añadir miembros Salir del canal Salir del canal Ajustes @@ -283,7 +290,7 @@ Difusión silenciada ¿Qué es un canal? Los canales son una herramienta para difundir tus mensajes a grandes audiencias. - CREAR CANAL + Crear canal Lo sentimos, este nombre ya está ocupado. Lo sentimos, este nombre no es válido. El nombre del canal debe tener al menos 5 caracteres. @@ -324,7 +331,8 @@ Foto del canal eliminada Nombre del canal cambiado a un2 Lo sentimos, tienes demasiados alias públicos. Puedes anular el enlace de uno de tus grupos o canales anteriores, o crear uno privado. - Creador + Propietario + Administrador Administrador SILENCIAR NOTIFICAR @@ -348,6 +356,7 @@ Lo sentimos, hay demasiados administradores en el canal. Lo sentimos, hay demasiados bots en el canal. Lo sentimos, sólo puedes añadir a los primeros 200 miembros a un canal. Sin embargo, una cantidad ilimitada de personas pueden unirse por el enlace del canal. + Lo sentimos, eres miembro de demasiados grupos y canales. Por favor, sal de algunos antes de crear uno nuevo. un1 te añadió a este canal Te uniste a este canal Te uniste a este grupo @@ -388,6 +397,19 @@ Eliminar mensajes Añadir administradores Eliminar administrador + Transferir propiedad del grupo + Transferir propiedad del canal + Comprobación de seguridad + Puedes transferir este grupo a **%1$s** sólo si: + Puedes transferir este canal a **%1$s** sólo si: + Activaste la **verificación en dos pasos** hace más de **7 días**. + Iniciaste sesión en este dispositivo hace más de **24 horas**. + Por favor, regresa más tarde. + Crear contraseña + Esto transferirá todos los **derechos de creador** de **%1$s** a **%2$s**. + Cambiar propietario + **%1$s** ahora es el propietario del grupo. + **%1$s** ahora es el propietario del canal. Suspender usuarios Añadir usuarios Invitar con un enlace @@ -419,10 +441,11 @@ sin anclar no puede añadir usuarios Duración - Siempre + Para siempre Suspender y eliminar del grupo ¿Aplicar cambios? - Has cambiado los permisos de este usuario en **%1$s**. ¿Aplicar cambios? + Cambiaste los permisos de este usuario en **%1$s**. ¿Aplicar cambios? + Personalizados Administrar grupo Administrar canal Administrar grupo @@ -445,6 +468,8 @@ Público Privado Público + Enlace + Toca para añadir un enlace permanente Elegir foto Tomar foto Subir desde la galería @@ -461,7 +486,7 @@ Elige un grupo de conversación que se mostrará en tu canal. Todo lo que publiques en el canal será reenviado a este grupo. Un enlace a **%1$s** se muestra a todos los suscriptores en el panel inferior. - **%1$s** está vinculando al grupo como su grupo de conversación. + Este grupo está vinculado como grupo de conversación de **%1$s**. Todo lo que publiques en este canal será reenviado a este grupo. Crear un nuevo grupo Desvincular grupo @@ -471,7 +496,7 @@ ¿Quieres convertir a **%1$s** en el grupo de conversación de **%2$s**? ¿Quieres convertir a **%1$s** en el grupo de conversación de **%2$s**?\n\nCualquier miembro de este grupo podrá ver los mensajes del canal. ¿Quieres convertir a **%1$s** en el grupo de conversación de **%2$s**?\n\nCualquier miembro de este canal podrá ver los mensajes del grupo. - El “Historial de chat para nuevos miembros” cambiará a Visible. + El “Historial para nuevos miembros” cambiará a Visible. ¿Quieres desvincular a **%1$s** de este grupo? ¿Quieres desvincular a **%1$s** de este canal? CONVERSAR @@ -543,6 +568,9 @@ un1 desancló un mensaje un1 detuvo la encuesta: un1 eliminó este mensaje: + un1 cambió la ubicación del grupo a “%1$s” + un1 eliminó la ubicación del grupo + transfirió la propiedad a %1$s un1 cambió el pack de stickers de grupo un1 eliminó el pack de stickers de grupo un1 convirtió a un2 en el grupo de conversación para este canal @@ -674,6 +702,7 @@ No hay recientes Mensaje Mensaje + COMPARTIR MI NÚMERO DE TELÉFONO Compartir mi número Añadir a contactos %s te invitó a un chat secreto. @@ -708,13 +737,22 @@ Abrir enlace ¿Quieres abrir %1$s? Iniciar sesión en %1$s como **%2$s** - Permitir a **%1$s** enviarme mensajes + Permitir que **%1$s** me envíe mensajes Cancelar envío ¿Permites a %1$s entregar tu nombre e id de Telegram (no tu número de teléfono) a los sitios web que abres a través de este bot? + ¿NO RELACIONADO CON LA UBICACIÓN? + Reportar grupo no relacionado + Por favor, dinos si este grupo no está relacionado con esta ubicación:\n\n**%1$s** + Por favor, dinos si este grupo no está relacionado con esta ubicación. REPORTAR SPAM + Reportar spam + Bloquear a %1$s + BLOQUEAR REPORTAR SPAM Y SALIR AÑADIR CONTACTO + AÑADIR A %1$s A CONTACTOS VER CONTACTO + ¿Quieres bloquear los mensajes y llamadas de **%1$s** en Telegram? ¿Quieres reportar el spam de este usuario? ¿Quieres reportar el spam en este grupo? ¿Quieres reportar el spam en este canal? @@ -821,6 +859,7 @@ ¿Instalar Google Maps? ESTAFA vía + El mensaje no existe %1$s activó la autodestrucción en %2$s Activaste la autodestrucción en %1$s @@ -956,6 +995,8 @@ Telegram ha detectado muchos contactos sin sincronizar. ¿Quieres sincronizarlos ahora? Elige “OK” si estás usando tu dispositivo, tarjeta SIM y cuenta de Google. Ordenado por nombre Ordenado por última conexión + Añadir a %1$s + Número de teléfono Añadir personas... Podrás añadir más usuarios después de crear el grupo y convertirlo en un supergrupo. @@ -1005,13 +1046,13 @@ **En los supergrupos:**\n\n• Los nuevos miembros ven todo el historial\n• Un mensaje eliminado desaparece para todos\n• Puede añadirse una descripción para el grupo\n• El creador puede generar un enlace público **Importante:** Esta acción no se puede deshacer. - Compartir - Añadir + Compartir contacto + Añadir a contactos Añadir contacto %1$s aún no está en Telegram. ¿Quieres enviarle una invitación? Invitar - Bloquear - Bloquear usuario + Bloquear usuario + Bloquear Usuario bloqueado Usuario desbloqueado Editar contacto @@ -1025,6 +1066,7 @@ Cumpleaños Puesto Crear nuevo contacto + Nuevo contacto Agregar a contacto existente Biografía Añade algunas palabras sobre ti @@ -1044,6 +1086,11 @@ https://telegram.org/faq/es#chats-secretos Desconocido Desconocido + Número oculto + El número de teléfono será visible una vez que %1$s te añada como contacto. + Al tocar **HECHO**, tu número de teléfono será visible para %1$s. + Compartir mi número con %1$s + %1$s ahora está en tu lista de contactos. Información Teléfono Contenido compartido @@ -1070,14 +1117,14 @@ AÑADIR %1$s ELIMINAR %1$s Añadir máscaras - Añadir a stickers - Añadir a favoritos - Sticker añadido a favoritos - El sticker fue eliminado de favoritos + Añadir a Stickers + Añadir a Favoritos + Sticker añadido a Favoritos + Sticker eliminado de Favoritos Recientes Favoritos Stickers de grupo - Eliminar de favoritos + Eliminar de Favoritos Añadir a máscaras Stickers no encontrados Stickers eliminados @@ -1212,8 +1259,11 @@ Por defecto (No) Activar Desactivar - Usuarios bloqueados + Bloqueados Los usuarios bloqueados no podrán contactarte y no verán tu última conexión. + Bloquear + CHATS + CONTACTOS Cerrar sesión Sin sonido Por defecto @@ -1221,7 +1271,8 @@ Sólo si está silenciado Difuminado Movimiento - Fondo de chat + Cambiar fondo de chat + Cambiar fondo de chat Restablecer fondos de chat Elimina todos los fondos de chat subidos y restaura los preinstalados. Restablecer fondos de chat @@ -1364,7 +1415,7 @@ Cuenta oculta para mensajes reenviados Autorreproducir multimedia Elevar para hablar - Guardar en galería + Guardar en Galería Editar nombre Personalizar Personalizadas @@ -1379,7 +1430,8 @@ Repetir notificaciones Puedes cambiar tu número de Telegram aquí. Tu cuenta y todos tus datos de la nube, mensajes, archivos, grupos, contactos, etc., se moverán al nuevo número.\n\n**Importante:** Todos tus contactos de Telegram tendrán tu **nuevo número** añadido a sus agendas de contactos, siempre que hayan tenido tu número viejo y no los hayas bloqueado en Telegram. Todos tus contactos de Telegram tendrán tu número nuevo añadido a sus agendas de contactos, siempre que hayan tenido tu número viejo y no los hayas bloqueado en Telegram. - CAMBIAR NÚMERO + Cambiar número + Cambiar número Nuevo número Enviaremos un SMS con el código de confirmación a tu nuevo número. El número %1$s ya está vinculado a una cuenta de Telegram. Por favor, elimina esa cuenta antes de migrar al nuevo número. @@ -1395,6 +1447,9 @@ Notificaciones inteligentes Excepciones Añadir una excepción + Eliminar todas las excepciones + Eliminar todas las excepciones + ¿Quieres eliminar todas las excepciones? Nueva excepción Aquí aparecerán los chats que no tengan los ajustes por defecto para las notificaciones.\n\nPuedes personalizar las notificaciones para un chat abriendo su información y seleccionando “Notificaciones”. Ninguna @@ -1446,7 +1501,7 @@ Proxy MTProto El proxy que estás usando no está configurado correctamente y será desactivado. Por favor, encuentra otro. Sponsor del proxy - Este canal es mostrado por tu servidor proxy. Para eliminar este canal de tu lista de chats, desactiva el proxy en los Ajustes de Telegram. + Este canal es mostrado por tu servidor proxy. Para eliminar este canal de tu lista de chats, desactiva el proxy en Ajustes de Telegram. Ajustes de proxy SOCKS5. Ajustes de proxy MTProto. Este proxy podría mostrar un canal patrocinado en tu lista de chats. Esto no revela nada de tu tráfico en Telegram. @@ -1503,6 +1558,11 @@ No se encontraron resultados No hay búsquedas recientes FAQ + Unidades de distancia + Unidades de distancia + Automáticas + Kilómetros + Millas Base de datos local ¿Eliminar de la caché los mensajes de texto? @@ -1520,7 +1580,7 @@ Vacía Conservar multimedia Las fotos, videos y otros archivos de los chats en la nube, a los que **no accedas** durante este periodo, se eliminarán del dispositivo para ahorrar espacio.\n\nToda la multimedia estará en la nube de Telegram y podrás descargarla de nuevo si la necesitas. - Siempre + Para siempre Mensajes de voz Videomensajes @@ -1794,8 +1854,10 @@ Mapa Satélite Híbrido - m de distancia - km de distancia + A %1$s m + A %1$s km + A %1$s ft + A %1$s mi Enviar mi ubicación actual Ubicación en tiempo real Dejar de compartir @@ -1807,6 +1869,7 @@ Ubicación Lugar Exacta a %1$s + A %1$s O elige un lugar Levanta para ver lugares cercanos Ubicaciones en tiempo real @@ -1822,7 +1885,24 @@ Enviando tu ubicación en tiempo real a %1$s Elige durante cuánto tiempo %1$s verá tu ubicación exacta. Elige durante cuánto tiempo las personas de este chat verán tu ubicación en tiempo real. - Tu GPS parece estar desactivado. Por favor, actívalo para acceder a las características basadas en la ubicación. + Tu GPS parece estar desactivado. Por favor, actívalo para acceder a las características de ubicación. + Personas cerca + Añadir personas cerca + Añade rápidamente a personas cerca que también están viendo esta sección y descubre grupos locales.\n\nPor favor, activa el acceso a la ubicación para usar esta característica. + Personas cerca + Permitir acceso + Añade rápidamente a personas cerca que también están viendo esta sección y descubre grupos locales.\n\nPor favor, activa los servicios de ubicación para usar esta característica. + Activar + Grupos cerca + Para intercambiar números de teléfono, pide a alguien que esté cerca que abra esta pantalla. + Buscando personas a tu alrededor… + Crear un grupo local + Iniciar grupo + Quienes estén cerca de esta ubicación (vecinos, compañeros de trabajo o de estudio, asistentes a eventos, visitantes de un lugar) verán tu grupo en la sección Personas cerca. + Si inicias un grupo no relacionado en esta ubicación, podrías ser restringido de crear grupos por ubicación. + Establecer ubicación + Establecer esta ubicación + Las personas podrán encontrar tu grupo en la sección “Personas cerca”. Mostrar toda la multimedia Mostrar todos los archivos @@ -1830,7 +1910,7 @@ ABRIR ARCHIVO Mostrar en el chat Detener descarga - Guardar en galería + Guardar en Galería %1$d de %2$d Galería Todas las fotos @@ -1899,6 +1979,7 @@ Puedes poner una contraseña, que será requerida cuando inicies sesión en un nuevo dispositivo, además del código que recibes vía SMS. Tu contraseña Pon tu contraseña + Por favor, pon tu contraseña para completar la transferencia. Pon una contraseña Pon tu nueva contraseña Repite tu contraseña @@ -1983,18 +2064,18 @@ Foto de perfil ¿Quién puede ver mi foto de perfil? Puedes restringir quién puede ver tu foto de perfil con gran precisión. - Estos ajustes anularán los valores de arriba. + Puedes añadir usuarios o grupos como excepciones que anularán los ajustes de arriba. Número de teléfono ¿Quién puede ver mi número de teléfono? Los usuarios que ya tienen tu número guardado en sus contactos también lo verán en Telegram. - Puedes añadir un usuario o un grupo como una excepción que anulará los ajustes de arriba. + Puedes añadir usuarios o grupos como excepciones que anularán los ajustes de arriba. Mensajes reenviados Enlace a tu cuenta Enlace según los ajustes de abajo Sin un enlace a tu cuenta ¿Quién puede añadir un enlace a mi cuenta al reenviar mis mensajes? Los mensajes que envías no enlazarán a tu cuenta al ser reenviados a otros chats. - Estos ajustes anularán los valores de arriba. + Puedes añadir usuarios o grupos como excepciones que anularán los ajustes de arriba. Reinhardt, necesitamos encontrarte algunas nuevas canciones 🎶. Peer-to-peer Peer-to-peer en llamadas @@ -2029,11 +2110,12 @@ Si no estás en línea al menos una vez durante este período, tu cuenta se eliminará junto con todos tus mensajes y contactos. ¿Quién puede ver mi última conexión? Añadir excepciones - Importante: No podrás ver la última conexión de las personas con las que no compartes la tuya. En su lugar, se mostrarán conexiones indeterminadas (recientemente, hace unos días, hace unas semanas). - Has cambiado algunos ajustes de privacidad. ¿Aplicar cambios? + Añadir a excepciones + No verás el estado de última conexión ni el de en línea de las personas con las que no compartes el tuyo. En su lugar, se mostrarán conexiones indeterminadas (recientemente, hace unos días, hace unas semanas). + Cambiaste algunos ajustes de privacidad. ¿Aplicar cambios? Compartir con No compartir con - Estos ajustes anularán los valores de arriba. + Puedes añadir usuarios o grupos como excepciones que anularán los ajustes de arriba. Compartir No compartir Añadir usuarios @@ -2056,6 +2138,7 @@ Esto eliminará todos los datos sobre las personas con las que conversas frecuentemente, así como los bots integrados que podrías utilizar. Añadir usuarios o grupos Excepciones + Ninguno Enviando video... Enviando GIF... @@ -2186,10 +2269,10 @@ Código inválido Has eliminado y vuelto a crear tu cuenta muchas veces recientemente. Por favor, espera algunos días antes de inscribirte de nuevo. Nombre inválido - Apellidos inválidos + Lo sentimos, este apellido no se puede usar Cargando... No tienes reproductor de video. Por favor, instala uno para continuar. - Por favor, envía un correo electrónico a sms@stel.com y cuéntanos tu problema. + Por favor, envía un correo describiendo tu problema a sms@stel.com No tienes aplicaciones que puedan manejar el tipo de archivo “%1$s”. Por favor, instala una para continuar. Este usuario aún no tiene Telegram. ¿Enviarle una invitación? ¿Quieres hacerlo? @@ -2235,11 +2318,11 @@ Chat secreto ¿Quieres iniciar un chat secreto? ¿Quieres cancelar el registro? - ¿Quieres detener el proceso de verificación del número de teléfono? + ¿Quieres detener el proceso de verificación? ¿Quieres eliminar el historial del chat con **%1$s**? ¿Quieres eliminar el historial del chat secreto para ti y para **%1$s**? ¿Quieres eliminar el historial en **%1$s**? - ¿Quieres vaciar el historial? + ¿Quieres eliminar todos los mensajes en este chat? ¿Quieres eliminar el historial de **Mensajes guardados**? ¿Eliminar de la caché todo el texto y la multimedia de este canal? ¿Eliminar de la caché todo el texto y la multimedia de este grupo? @@ -2287,10 +2370,10 @@ Advertencia: Esto eliminará de forma irreversible tu cuenta de Telegram junto a todos los datos que almacenas en la nube de Telegram.\n\nImportante: Puedes Cancelar ahora y exportar tus datos antes de eliminar tu cuenta en vez de perderlo todo. (Para hacerlo, abre la última versión de Telegram Desktop y ve a Ajustes > Exportar datos de Telegram.) Para que puedas comunicarte con tus amigos en todos tus dispositivos, tus contactos se sincronizarán continuamente con los servidores en la nube fuertemente cifrados de Telegram. - Telegram necesita acceso a tu espacio de almacenamiento para que puedas enviar y guardar fotos, videos, música y otros archivos. + Telegram necesita acceso al almacenamiento para que puedas enviar y guardar fotos, videos, música y otros archivos. Telegram necesita acceso a tu micrófono para que puedas enviar mensajes de voz. Telegram necesita acceso a tu micrófono para que puedas grabar videos. - Telegram necesita acceso a tu cámara para que puedas tomar fotos y videos. + Telegram necesita acceso a la cámara para que puedas tomar fotos y videos. Por favor, actívalo en Ajustes. Telegram necesita acceso a tu ubicación para que puedas compartirla con tus amigos. Telegram necesita acceso a tu ubicación. Telegram necesita acceso a mostrarse sobre otras aplicaciones para usar el modo Picture-in-Picture. diff --git a/TMessagesProj/src/main/res/values-it/strings.xml b/TMessagesProj/src/main/res/values-it/strings.xml index 91d1967b3..42240a772 100644 --- a/TMessagesProj/src/main/res/values-it/strings.xml +++ b/TMessagesProj/src/main/res/values-it/strings.xml @@ -10,7 +10,7 @@ Il tuo numero Conferma il prefisso internazionale e inserisci il tuo numero di telefono. - Scegli una nazione + Scegli un paese Prefisso internazionale non valido Questo account è già collegato in questa app. Cambia @@ -28,7 +28,7 @@ Controlla i tuoi messaggi Telegram Inserisci codice " Stiamo chiamando il tuo numero **%1$s**.\n\nNon rispondere alla chiamata, Telegram farà tutto in automatico." - Stiamo chiamando il tuo numero **%1$s** per dettarti un codice. + Chiameremo il tuo numero **%1$s** per dettarti un codice. Telegram ti chiamerà tra %1$d:%2$02d Ti invieremo un SMS tra %1$d:%2$02d Ti stiamo chiamando... @@ -49,7 +49,7 @@ Il tuo codice di accesso è **%1$s**. Inseriscilo nell\'app Telegram in cui stai cercando di accedere.\n\nNon dare questo codice a nessuno. Il tuo nome - Per favore inserisci il tuo nome completo e carica la tua foto profilo. + Inserisci il tuo nome completo e carica una foto profilo. Nome (obbligatorio) Cognome (facoltativo) Annulla iscrizione @@ -65,7 +65,7 @@ Indirizzo 2 (Via) Città Provincia - Nazione + Paese Codice postale Destinatario Nome completo @@ -202,6 +202,8 @@ Grassetto Corsivo Mono + Barrato + Sottolineato Normale **Telegram** ha bisogno di accedere ai tuoi contatti per farti connettere con gli amici su tutti i tuoi dispositivi. I tuoi contatti verranno sincronizzati continuamente con i server cloud fortemente criptati di Telegram. NON ADESSO @@ -231,6 +233,9 @@ Spiacenti, questo gruppo è pieno. Spiacenti, questo utente ha deciso di lasciare il gruppo, quindi non puoi riaggiungerlo. Spiacenti, troppi amministratori in questo gruppo. + Spiacenti, l\'utente in oggetto ha già troppi gruppi o canali pubblici. Per favore chiedigli di rendere privato uno dei suoi gruppi o canali. + Spiacenti, l\'utente in oggetto ha già troppi gruppi basati sulla posizione. Per favore chiedigli di eliminare prima uno di quelli. + Spiacenti, hai già troppi gruppi basati sulla posizione. Per favore eliminane prima uno dei tuoi. Spiacenti, troppi bot in questo gruppo. un1 ha fissato \"%1$s\" un1 ha fissato un messaggio @@ -266,6 +271,7 @@ Hai cambiato qualche impostazione in questo canale. Applicare le modifiche? Canale pubblico Gruppo pubblico + Gruppo basato sulla posizione I canali pubblici possono essere trovati nella ricerca, chiunque può unirsi. I gruppi pubblici possono essere trovati nella ricerca, la cronologia è disponibile per tutti e chiunque può unirsi. Canale privato @@ -275,6 +281,7 @@ Link permanente Link d\'invito Aggiungi membri + Aggiungi membri Lascia il canale Lascia il canale Impostazioni @@ -283,7 +290,7 @@ Post silenzioso Cos\'è un canale? I canali sono uno strumento per diffondere i tuoi messaggi a un pubblico illimitato. - CREA CANALE + Crea canale Spiacenti, questo nome è già stato preso. Spiacenti, questo nome non è valido. I nomi dei canali devono avere almeno 5 caratteri. @@ -324,7 +331,8 @@ Foto del canale rimossa Nome del canale cambiato in un2 Spiacenti, hai riservato troppi username pubblici. Puoi revocare il link da uno dei tuoi gruppi o canali più vecchi, o creare invece delle entità private. - Creatore + Proprietario + Amministratore Amministratore SILENZIA SUONA @@ -348,6 +356,7 @@ Spiacenti, troppi amministratori in questo canale. Spiacenti, troppi bot in questo canale. Spiacenti, puoi aggiungere solo i primi 200 membri a un canale. Ricorda che un numero illimitato di persone potrebbe unirsi tramite il link del canale. + Spiacenti, sei membro di troppi gruppi e canali. Per favore lasciane qualcuno prima di crearne uno nuovo. un1 ti ha aggiunto a questo canale Ti sei unito a questo canale Ti sei unito a questo gruppo @@ -388,6 +397,19 @@ Eliminare messaggi Aggiungere amministratori Rimuovi amministratore + Trasferisci proprietà gruppo + Trasferisci proprietà canale + Controllo di sicurezza + Puoi trasferire questo gruppo a **%1$s** solo se hai: + Puoi trasferire questo canale a **%1$s** solo se lo hai: + La **Verifica in due passaggi** abilitata da più di **7 giorni**. + Connesso questo dispositivo più di **24 ore** fa. + Per favore ritorna più tardi. + Imposta password + Questo trasferirà tutti i **diritti di proprietà** per **%1$s** a **%2$s*. + Cambia proprietario + **%1$s** è ora il proprietario del gruppo. + **%1$s** è ora il proprietario del canale. Bloccare utenti Aggiungere utenti Invitare utenti tramite link @@ -423,6 +445,7 @@ Blocca e rimuovi dal gruppo Applicare le modifiche? Hai cambiato i permessi di questo utente in **%1$s**. Applicare le modifiche? + Personalizzata Gestisci gruppo Gestisci canale Gestisci gruppo @@ -445,6 +468,8 @@ Pubblico Privato Pubblico + Link + Tocca per aggiungere un link permanente Scegli foto Scatta foto Carica dalla galleria @@ -543,6 +568,9 @@ un1 ha tolto un messaggio un1 ha terminato il sondaggio: un1 ha eliminato questo messaggio: + un1 ha cambiato la posizione del gruppo in \"%1$s” + un1 ha rimosso la posizione del gruppo + ha trasferito la proprietà a %1$s un1 ha cambiato il set di sticker del gruppo un1 ha rimosso il set di sticker del gruppo un1 ha reso un2 il gruppo di discussione per questo canale @@ -674,6 +702,7 @@ Nessun recente Messaggio Messaggio + CONDIVIDI IL MIO NUMERO Condividi il mio contatto Aggiungi ai contatti %s ti ha invitato a unirti ad una chat segreta. @@ -711,10 +740,19 @@ Consenti a **%1$s** di inviarmi messaggi Annulla invio Consentire a %1$s di trasmettere il tuo nome e id Telegram (non il tuo numero di telefono) alle pagine che apri tramite questo bot? + GRUPPO NON LEGATO ALLA POSIZIONE? + Segnala gruppo non legato + Per favore dicci se il gruppo non è legato a questa posizione:\n\n**%1$s** + Per favore dicci se il gruppo non è legato a questa posizione. SEGNALA COME SPAM + Segnala come spam + Blocca %1$s + BLOCCA UTENTE SEGNALA COME SPAM E LASCIA AGGIUNGI CONTATTO + AGGIUNGI %1$s AI CONTATTI VISUALIZZA CONTATTO + Vuoi impedire a **%1$s** di scriverti e chiamarti su Telegram? Sei sicuro di voler segnalare questo utente come spam? Sei sicuro di voler segnalare questo gruppo come spam? Sei sicuro di voler segnalare questo canale come spam? @@ -821,6 +859,7 @@ Installare Google Maps? TRUFFA via + Il messaggio non esiste %1$s ha impostato il timer di autodistruzione a %2$s Hai impostato il timer di autodistruzione a %1$s @@ -956,6 +995,8 @@ Telegram ha trovato molti contatti non sincronizzati, vorresti sincronizzarli adesso? Scegli \'OK\' se stai usando il tuo dispositivo, la tua SIM e il tuo account Google. Ordinati per nome Ordinati per ultimo accesso + Aggiungi %1$s + Numero di telefono Aggiungi persone... Potrai aggiungere più utenti dopo aver creato il gruppo e averlo convertito in supergruppo. @@ -1005,12 +1046,12 @@ **Nei supergruppi:**\n\n• I nuovi membri vedono tutta la cronologia\n• I messaggi eliminati scompaiono per tutti\n• Gli admin possono aggiungere una descrizione al gruppo\n• Il creatore può creare un link pubblico per il gruppo **Nota:** questa azione non può essere annullata. - Condividi - Aggiungi + Condividi contatto + Aggiungi ai contatti Aggiungi contatto %1$s non si è ancora unito a Telegram, vuoi invitarlo a unirsi? Invita - Blocca + Blocca utente Blocca utente Utente bloccato Utente sbloccato @@ -1025,6 +1066,7 @@ Compleanno Titolo Crea nuovo contatto + Nuovo contatto Aggiungi a contatto esistente Bio Aggiungi qualche riga su di te @@ -1044,6 +1086,11 @@ https://telegram.org/faq/it#chat-segrete Sconosciuto Sconosciuto + Numero nascosto + Il numero di telefono sarà visibile dopo che %1$s ti avrà aggiunto come contatto. + Quando premerai su **FATTO**, il tuo numero di telefono sarà visibile a %1$s. + Condividi il mio numero con %1$s + %1$s è ora nella tua lista contatti Info Telefono Contenuto condiviso @@ -1121,7 +1168,7 @@ No Disattivata Programmata - Adattativa + Adattiva Programma Usa tramonto e alba locali Aggiorna posizione @@ -1214,6 +1261,9 @@ Disattiva Utenti bloccati Gli utenti bloccati non saranno in grado di contattarti e non vedranno l\'orario del tuo ultimo accesso. + Blocca utente + CHAT + CONTATTI Esci Nessun suono Default @@ -1221,7 +1271,8 @@ Solo se silenzioso Sfocato Prospettiva - Sfondo chat + Cambia sfondo chat + Cambia sfondo chat Ripristina sfondi chat Rimuove tutti gli sfondi chat caricati e ripristina quelli predefiniti. Ripristina sfondi chat @@ -1379,7 +1430,8 @@ Ripeti notifiche Puoi cambiare il tuo numero di telefono qui. Il tuo account e tutti i tuoi dati cloud — messaggi, file, contatti, etc. saranno trasferiti sul nuovo numero.\n\n**Importante:** a tutti i tuoi contatti di Telegram verrà aggiunto il tuo **nuovo numero** ai contatti, purché abbiano il tuo vecchio numero e tu non li abbia bloccati su Telegram. Tutti i tuoi contatti Telegram avranno il tuo nuovo numero tra i loro contatti, purché abbiano il tuo vecchio numero e tu non li abbia bloccati su Telegram. - CAMBIA NUMERO + Cambia numero + Cambia numero Nuovo numero Invieremo un SMS con un codice di conferma al tuo nuovo numero. Il numero %1$s è già connesso a un account Telegram. Per favore elimina quell\'account prima di migrare ad un nuovo numero. @@ -1395,6 +1447,9 @@ Notifiche intelligenti Eccezioni Aggiungi eccezione + Elimina tutte le eccezioni + Elimina tutte le eccezioni + Sei sicuro di voler eliminare tutte le eccezioni? Nuova eccezione Questa sezione mostrerà tutte le chat con impostazioni di notifica non predefinite.\n\nPuoi personalizzare le notifiche per una chat aprendo il suo profilo e scegliendo \'Notifiche\'. Nessuna @@ -1503,6 +1558,11 @@ Nessun risultato trovato Nessuna ricerca recente FAQ + Unità di misura + Unità di misura + Automatica + Chilometri + Miglia Database locale Cancellare i messaggi salvati nella cache? @@ -1564,7 +1624,7 @@ Codice postale Città Provincia - Nazione + Paese Numero di telefono Eliminare il numero di telefono? Inserisci il tuo numero di telefono @@ -1794,8 +1854,10 @@ Mappa Satellite Ibrido - m - km + Lontano %1$s m + Lontano %1$s km + Lontano %1$s piedi + Lontano %1$s miglia Invia la mia posizione corrente Condividi posizione attuale per... Arresta condivisione @@ -1807,6 +1869,7 @@ Posizione Luogo Precisione di %1$s + distante %1$s O scegli un luogo Tira su per vedere i luoghi vicini Posizioni attuali @@ -1823,6 +1886,23 @@ Scegli per quanto tempo %1$s vedrà la tua posizione precisa. Scegli per quanto tempo le persone in questa chat vedranno la tua posizione attuale. Sembra che il tuo GPS sia disattivato, per favore attivalo per utilizzare le funzioni che necessitano della posizione. + Persone vicine + Aggiungi persone vicine + Aggiungi rapidamente persone vicine che stanno guardando questa sezione e scopri chat di gruppo locali.\n\nPer favore concedi l\'accesso alla posizione per abilitare questa funzione. + Persone vicine + Consenti accesso + Aggiungi rapidamente persone vicine che stanno guardando questa sezione e scopri chat di gruppo locali.\n\nPer favore attiva i servizi di localizzazione per abilitare questa funzione. + Attiva + Gruppi vicini + Chiedi al tuo amico vicino di aprire questa pagina per scambiare i numeri di telefono. + Cerco utenti attorno a te... + Crea un gruppo locale + Avvia gruppo + Chiunque si trovi in ​​questa posizione (vicini, colleghi, studenti, partecipanti ad un evento, visitatori) vedrà il tuo gruppo nella sezione Persone vicine. + Se avvii un gruppo non legato a questa posizione, potresti essere limitato nella creazione di nuovi gruppi basati sulla posizione. + Imposta posizione + Imposta questa posizione + Le persone potranno trovare il tuo gruppo nella sezione \"Persone vicine\" Mostra tutti i media Mostra tutti i file @@ -1899,6 +1979,7 @@ Puoi impostare una password che ti verrà richiesta quando ti connetti da un nuovo dispositivo in aggiunta al codice che riceverai via SMS. La tua password Inserisci la tua password + Per favore inserisci la password per completare il trasferimento. Inserisci una password Inserisci la tua nuova password Reinserisci la tua password @@ -1945,7 +2026,7 @@ Questa azione non può essere annullata.\n\nSe ripristini il tuo account, tutti i tuoi messaggi e le tue chat saranno eliminati. Ripristina account Password - Hai attivato la verifica in due passaggi, quindi il tuo account è protetto con una password aggiuntiva. + Hai attivato la verifica in due passaggi, così il tuo account è protetto con una password aggiuntiva. Password dimenticata? Recupero password Codice @@ -1979,22 +2060,22 @@ Privacy e sicurezza Privacy - Ultimo accesso + Ultimo accesso e in linea Foto profilo Chi può vedere la mia foto profilo? Puoi decidere chi può vedere la tua foto profilo con precisione granulare. - Queste impostazioni annulleranno i valori precedenti. + Puoi aggiungere utenti o interi gruppi come eccezioni che annulleranno le impostazioni precedenti. Numero di telefono Chi può vedere il mio numero di telefono? Gli utenti che hanno già il tuo numero salvato in rubrica lo vedranno anche su Telegram. - Puoi aggiungere un utente o un intero gruppo come eccezione che sovrascriverà le impostazioni precedenti. + Puoi aggiungere utenti o interi gruppi come eccezioni che annulleranno le impostazioni precedenti. Messaggi inoltrati Collegato al tuo account Collegato se permesso dalle impostazioni Non collegato al tuo account Chi può aggiungere un collegamento al mio account quando inoltra i miei messaggi? Quando inoltrati in altre chat, i tuoi messaggi non saranno collegati al tuo account. - Queste impostazioni annulleranno i valori precedenti. + Puoi aggiungere utenti o interi gruppi come eccezioni che annulleranno le impostazioni precedenti. Reinhardt, dobbiamo trovarti qualche nuova canzone 🎶. Peer-to-peer Peer-to-peer nelle chiamate @@ -2029,11 +2110,12 @@ Se non ti connetti almeno una volta in questo periodo, il tuo account verrà eliminato insieme a tutti i messaggi e i contatti. Chi può vedere il tuo ultimo accesso? Aggiungi eccezioni - Importante: non potrai vedere l\'ultimo accesso delle persone con cui non condividi l\'ultimo accesso. Verrà mostrato un orario approssimativo (di recente, entro una settimana, entro un mese). + "Aggiungi alle eccezioni " + Non vedrai l\'ultimo accesso e lo stato in linea delle persone con cui non condividi l\'ultimo accesso. Verrà mostrato un orario approssimativo (di recente, entro una settimana, entro un mese). Hai modificato alcune impostazioni della privacy. Applicare le modifiche? Condividi con Non condividere con - Queste impostazioni annulleranno i valori precedenti. + Puoi aggiungere utenti o interi gruppi come eccezioni che annulleranno le impostazioni precedenti. Condividi Non condividere Aggiungi utenti @@ -2056,6 +2138,7 @@ Questo eliminerà tutti i dati delle persone che contatti di frequente così come i bot inline che usi di solito. Aggiungi utenti o gruppi Eccezioni + Nessuno Invio video... Invio GIF... @@ -2186,10 +2269,10 @@ Codice non valido Spiacenti, hai eliminato e ricreato il tuo account troppe volte di recente. Per favore attendi alcuni giorni prima di iscriverti di nuovo. Nome non valido - Cognome non valido + Spiacenti, questo cognome non può essere usato Carico... Non hai un lettore video, per favore installane uno per continuare - Per favore invia un’email a sms@stel.com spiegandoci il problema. + Per favore invia un’email descrivendo il problema a sms@stel.com Non hai applicazioni che possono gestire il tipo di file \'%1$s\': installane una per proseguire Questo utente non ha ancora Telegram, vuoi invitarlo? Sei sicuro? @@ -2227,19 +2310,19 @@ Questo bot vorrebbe sapere la tua posizione ogni volta che invii una richiesta. Questo può essere usato per fornire risultati specifici in base alla posizione. Condividere il tuo numero di telefono? Il bot saprà il tuo numero di telefono. Questo può essere utile per l\'integrazione con altri servizi. - Sicuro di voler condividere il tuo numero di telefono %1$s con **%2$s**? - Sei sicuro di voler condividere il tuo numero di telefono? + Sicuro di voler condividere il tuo numero %1$s con **%2$s**? + Sei sicuro di voler condividere il tuo numero? Sei sicuro di voler bloccare **%1$s**? - Vuoi sbloccare questo contatto? + Sei sicuro di voler sbloccare questo contatto? Sei sicuro di voler eliminare questo contatto? Chat segreta - Iniziare una chat segreta? + Sei sicuro di voler iniziare una chat segreta? Sei sicuro di voler annullare la registrazione? - Vuoi arrestare il processo di verifica del numero di telefono? + Vuoi arrestare il processo di verifica? Sei sicuro di voler eliminare la tua cronologia della chat con **%1$s**? Sei sicuro di voler eliminare la tua cronologia della chat segreta con **%1$s**? Sei sicuro di voler cancellare la cronologia chat di **%1$s**? - Sei sicuro di voler eliminare la cronologia? + Sei sicuro di voler eliminare tutti i messaggi in questa chat? Sei sicuro di voler pulire i **Messaggi salvati**? Eliminare tutti i testi e i media di questo canale dalla cache? Eliminare tutti i testi e i media di questo gruppo dalla cache? @@ -2290,7 +2373,7 @@ Telegram deve accedere alla tua memoria per poter inviare e salvare foto, video, musica e altri media. Telegram deve accedere al microfono per poter inviare messaggi vocali. Telegram deve accedere al microfono per poter registrare video. - Telegram deve accedere alla tua fotocamera per scattare foto e registrare video. + Telegram deve avere accesso alla fotocamera per scattare foto e registrare video. Per favore attivalo nelle Impostazioni. Telegram deve accedere alla tua posizione per poterla condividere con i tuoi amici. Telegram deve accedere alla tua posizione. Telegram deve accedere allo spostamento su altre app per riprodurre i video in modalità PiP. diff --git a/TMessagesProj/src/main/res/values-ko/strings.xml b/TMessagesProj/src/main/res/values-ko/strings.xml index c20ade5c8..591dee824 100644 --- a/TMessagesProj/src/main/res/values-ko/strings.xml +++ b/TMessagesProj/src/main/res/values-ko/strings.xml @@ -202,6 +202,8 @@ 굵게 기울임꼴 고정폭 + Strikethrough + Underline 일반 모든 기기에 걸쳐 친구들과 소통하려면 **텔레그램**의 연락처 접근을 허용해 주세요. 단단히 암호화된 텔레그램 서버로 회원님의 연락처가 지속적으로 동기화될 것입니다. 지금 안 함 @@ -231,6 +233,9 @@ 죄송합니다, 그룹의 인원이 최대치입니다. 죄송합니다. 이 사용자는 스스로 그룹에서 나갔기에, 다시 초대하실 수 없습니다. 죄송합니다, 그룹에 너무 많은 관리자가 있습니다. + Sorry, the target user has too many public groups or channels already. Please ask them to make one of their existing groups or channels private first. + Sorry, the target user has too many location-based groups already. Please ask them to delete one of their existing ones first. + Sorry, you have too many location-based groups already. Please delete one of your existing ones first. 죄송합니다. 이 그룹에는 봇이 너무 많습니다. un1 님이 \"%1$s\"을(를) 고정했습니다 un1 님이 메시지를 고정했습니다 @@ -266,6 +271,7 @@ 몇 가지 채널 설정을 바꾸셨습니다. 변경 사항을 적용할까요? 공개 채널 공개 그룹 + Location-based Group 공개 채널은 검색으로 찾을 수 있으며, 누구나 들어올 수 있습니다. 공개 그룹은 검색으로 찾을 수 있고, 모두에게 대화 내용이 공개되며, 누구나 들어갈 수 있습니다. 비공개 채널 @@ -275,6 +281,7 @@ 영구 링크 초대 링크 참가자 추가 + 참가자 추가 채널 나가기 채널 나가기 설정 @@ -283,7 +290,7 @@ 조용히 알리기 채널이란 무엇인가요? 채널은 회원님의 메시지를 수많은 사람에게 알리기 위한 도구입니다. - 채널 만들기 + Create Channel 죄송합니다. 이미 사용되고 있는 이름입니다. 올바르지 않은 이름입니다. 채널명은 5자 이상이어야 합니다. @@ -325,6 +332,7 @@ 채널명이 un2(으)로 바뀌었습니다 죄송하지만, 공개 사용자명을 너무 많이 가지셨습니다. 다른 그룹이나 채널의 링크를 하나 폐기하거나, 비공개 그룹으로 바꾸세요. 생성자 + Administrator 관리자 음소거 음소거 취소 @@ -348,6 +356,7 @@ 죄송합니다, 채널에 너무 많은 관리자가 있습니다. 죄송합니다. 이 채널에는 봇이 너무 많습니다. 죄송합니다. 처음 200명까지만 채널에 추가하실 수 있습니다. 채널 링크로는 사람들을 무한정 들이실 수 있는 점 알아두세요. + Sorry, you are a member of too many groups and channels. Please leave some before creating a new one. un1 님이 회원님을 이 채널에 추가했습니다 채널에 들어오셨습니다 그룹에 들어오셨습니다 @@ -388,6 +397,19 @@ 메시지 삭제 새로운 관리자 추가 관리자 권한 회수 + 그룹 소유권 이전 + 채널 소유권 이전 + 보안 확인 사항 + You can transfer this group to **%1$s** only if you have: + You can transfer this channel to **%1$s** only if you have: + Enabled **2-Step Verification** more than **7 days** ago. + Logged in on this device more than **24 hours** ago. + Please come back later. + 비밀번호 설정 + This will transfer the **full owner** rights for **%1$s** to **%2$s**. + 소유자 바꾸기 + **%1$s** is now the owner of the group. + **%1$s** is now the owner of the channel. 사용자 차단 사용자 추가 링크로 사용자 초대 @@ -423,6 +445,7 @@ 차단하고 그룹에서 추방 변경 사항을 적용할까요? **%1$s**에서 사용자의 권한을 수정하셨습니다. 변경 사항을 적용할까요? + Custom 그룹 관리 채널 관리 그룹 관리 @@ -445,6 +468,8 @@ 공개 비공개 공개 + Link + Tap to add a permanent link 사진 선택 카메라 갤러리 @@ -455,27 +480,27 @@ 죄송합니다. 봇에게 관리자 권한을 부여해야만 채널에 추가하실 수 있습니다. 관리자 세우기 %1$s 님을 제한하시면 이 사용자가 관리자에서 제명됩니다. - Discussion - Add a group chat for comments - Linked Channel - Select a group chat for discussion that will be displayed in your channel. - Everything you post in the channel will be forwarded to this group. - A link to **%1$s** is shown to all subscribers in the bottom panel. + 토론 + 논평을 위한 그룹 대화방을 추가하세요. + 연결된 채널 + 채널 안에 표시될 토론 그룹 대화방을 선택하세요. + 채널에 게시하시는 모든 게시물이 이 그룹으로 전달됩니다. + **%1$s** 링크는 모든 구독자에게 바닥 패널에서 보입니다. **%1$s** is linking the group as its discussion board. - All new messages posted in this channel are forwarded to the group. - Create a New Group - Unlink Group - Unlink Channel - Unlink - 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. \"Chat history for new members\" will be switched to Visible. - Are you sure you want to unlink **%1$s** from this group? + **%1$s** 채널과 이 그룹의 연결을 해제하시겠습니가? Are you sure you want to unlink **%1$s** from this channel? - DISCUSS - channel + 토론 + 채널 새로운 설문 설문 @@ -543,6 +568,9 @@ un1 님이 메시지를 고정했습니다. un1 님이 설문을 마감했습니다: un1 님이 아래 메시지를 삭제했습니다: + un1 님이 그룹 위치를 \"%1$s\"로 바꿨습니다 + un1 removed group location + transferred ownership to %1$s un1 님이 그룹 스티커 묶음을 변경했습니다 un1 님이 그룹 스티커 묶음을 제거했습니다 un1 made un2 the discussion group for this channel @@ -674,6 +702,7 @@ 최근 이모지 없음 메시지 메시지 + SHARE MY PHONE NUMBER 내 연락처 공유 연락처에 추가 %s 님이 회원님을 비밀 대화에 초대했습니다. @@ -705,16 +734,25 @@ %1$s 업로드 파일로 보내기 파일로 보내기 - Open Link - Do you want to open %1$s? + 링크 열기 + %1$s을(를) 여시겠습니까? Log in to %1$s as **%2$s** - Allow **%1$s** to send me messages + **%1$s**에서 나에게 메시지를 보내도록 허용합니다 보내기 취소 %1$s이(가) 앞으로 여실 페이지로 회원님의 텔레그램 이름과 아이디(전화번호 아님)를 전달할 수 있도록 허락할까요? + GROUP UNRELATED TO LOCATION? + Report unrelated group + Please let us know if this group is not related to this location:\n\n**%1$s** + Please let us know if this group is not related to this location. 스팸 신고 + Report spam + Block %1$s + BLOCK USER 스팸 신고하고 나가기 연락처에 추가 + ADD %1$s TO CONTACTS 연락처 보기 + Do you want to block **%1$s** from messaging and calling you on Telegram? 정말 이 사용자가 보낸 스팸을 신고하시겠습니까? 이 그룹 메시지를 스팸신고 하시겠습니까? 이 채널을 스팸 신고하시겠습니까? @@ -820,7 +858,8 @@ 미리보기 없음 Google 지도를 설치할까요? 신용 사기 - via + "다음을 통해 보냄: " + Message doesn\'t exist %1$s 님이 자동 삭제 타이머를 %2$s(으)로 맞췄습니다 자동 삭제 타이머를 %1$s(으)로 맞추셨습니다 @@ -928,7 +967,7 @@ 텔레그램 연락처 선택 - Select Contacts + 연락처 선택 연락처 공유 연락처 없음 안녕하세요, 전 텔레그램이라는 메신저를 사용하고 있어요. 함께 사용해요! 여기서 다운로드하실 수 있어요. %1$s @@ -956,6 +995,8 @@ 텔레그램이 아직 동기화되지 않은 여러 연락처를 찾아냈습니다. 연락처를 동기화하시겠습니까? 본인의 기기, SIM 카드와 Google 계정을 사용하는 중이라면 \'확인\'을 누르십시오. 이름순으로 정렬 마지막 접속순으로 정렬 + Add %1$s + Phone number 사람들을 추가하세요... 이 그룹을 마저 만들고 슈퍼그룹으로 변환한 뒤에 사용자를 더 추가하실 수 있습니다. @@ -981,8 +1022,8 @@ 링크 복사 링크 공유 누구나 텔레그램을 설치했다면 위 링크를 따라 그룹에 들어올 수 있습니다. - Search for people... - Search for users and groups... + 사람을 검색하세요... + 사용자나 그룹을 검색하세요... 참가자 공유된 미디어 @@ -1011,9 +1052,9 @@ %1$s 님은 아직 텔레그램에 가입하지 않았습니다. 초대하시겠습니까? 초대 차단 - Block user - User blocked - User unblocked + 사용자 차단 + 사용자가 차단되었습니다 + 사용자가 차단 해제되었습니다 연락처 수정 연락처 삭제 @@ -1025,6 +1066,7 @@ 생일 제목 새 연락처 등록 + New Contact 기존 연락처에 추가 자기소개 본인에 대해 몇 마디 적어 보세요 @@ -1044,6 +1086,11 @@ https://telegram.org/faq#secret-chats 알 수 없음 없음 + Mobile hidden + Phone number will be visible once %1$s adds you as a contact. + When you tap **DONE**, your phone number will become visible to %1$s. + Share my phone number with %1$s + %1$s is now in your contact list. 정보 전화번호 공유하는 콘텐츠 @@ -1088,7 +1135,7 @@ 공유 링크 복사 삭제 - 스티커 없음 + 스티커가 없습니다 스티커를 찾지 못했습니다 GIF를 찾지 못했습니다 이모지 없음 @@ -1121,7 +1168,7 @@ 끄기 끄기 예약 - Adaptive + 조정 예약 지역 일몰 및 일출 시간 사용 위치 갱신 @@ -1133,8 +1180,8 @@ 주변 밝기가 %1$d%% 아래일 때 선호하시는 야간 테마로 전환합니다. 다크 검푸른색 - Graphite - Arctic Blue + 흑연색 + 북극 파랑 파랑 이 테마를 삭제하시겠습니까? 올바르지 않은 테마 파일입니다 @@ -1143,8 +1190,8 @@ 테마 저장 새로운 색 테마 색 테마 - Built-in themes - Custom themes + 내장 테마 + 맞춤 테마 대화방 목록 뷰 두 줄 세 줄 @@ -1152,13 +1199,13 @@ 적용 테마 미리보기 색 선택 - Color Themes - Show all Themes + 색 테마 + 모든 테마 보기 새로운 테마 만들기 각 화면의 구성 요소 목록을 보려면 팔레트 아이콘을 누르세요. 이들을 수정하실 수 있습니다. 앱 안에서 색들을 바꾸어 회원님만의 테마를 만드실 수 있습니다.\n\n여기서 언제든지 기본 텔레그램 테마로 돌리실 수 있습니다. - 모든 알림 설정이 초기화됐습니다 + 모든 알림 설정이 기본으로 설정되었습니다 정말 모든 알림 설정을 기본으로 초기화하시겠습니까? 메시지 글자 크기 질문하기 @@ -1213,7 +1260,10 @@ 켜기 끄기 차단된 사용자 - Blocked users will not be able to contact you and will not see your Last Seen time. + 차단된 사용자는 회원님에게 연락하거나 회원님의 마지막 접속 시간을 볼 수 없습니다. + 사용자 차단 + 대화방 + 연락처 로그아웃 무음 기본 @@ -1221,7 +1271,8 @@ 음소거일 때만 흐림 모션 효과 - 대화방 배경 + 대화 배경 바꾸기 + 대화방 배경 바꾸기 대화방 배경 초기화 업로드된 대화방 배경을 모두 제거하고 기본으로 설치된 배경을 복구합니다. 대화방 배경 재설정 @@ -1232,10 +1283,10 @@ 야호! 고마워요 좌우로 넘겨 다양한 색을 입혀 보세요 Salmon is a fish, not a color. - 루시우 - 라인하르트, 새 음악도 좀 들어보시지 그래요🎶? - 요즘 젊은이들은 테크노인지 뭔지 하는 것만 좋아한다니까! 핫셀호프 같은 고전 음악을 들어 보라고! - 이게 진심으로 하는 말이라니. 아으! + Bob Harris + 좋은 아침이에요! 👋 + 지금 몇 시인지 아세요? + 도쿄는 아침이에요 😎 \'설정\'을 눌러 배경을 적용하세요. 경치가 장관이로구나! 배경으로 설정 @@ -1379,7 +1430,8 @@ 알림 반복 텔레그램 번호를 여기서 바꾸실 수 있습니다. 회원님의 계정과 함께 메시지와 미디어, 연락처 따위의 모든 클라우드 데이터가 새 번호로 옮겨집니다.\n\n**중요:** 텔레그램 연락 상대 중에서 회원님의 옛 전화번호가 있으며 차단되지 않은 모두에게 회원님의 **새 전화번호**가 등록됩니다. 텔레그램 연락 상대 중에서 회원님의 옛 전화번호가 있으며 차단되지 않은 모두에게 회원님의 새 전화번호가 등록됩니다. - 번호 바꾸기 + 번호 바꾸기 + Change number 새 번호 회원님의 새 번호로 인증 코드가 담긴 SMS를 보내겠습니다. 그 번호는 이미 텔레그램 계정에 연결되어 있습니다. 새 번호로 이동하기 전에 %1$s 계정에서 탈퇴해 주세요. @@ -1395,6 +1447,9 @@ 스마트 알림 예외 예외 추가 + 예외 모두 삭제 + 예외 모두 삭제 + 정말 모든 예외를 삭제하시겠습니까? 새로운 예외 기본과 다르게 알림 설정이 된 대화방이 이 부분에 모두 나열됩니다.\n\n대화방의 프로필을 열고 \'알림\'을 선택하여 알림 설정을 맞춤하실 수 있습니다. 없음 @@ -1503,6 +1558,11 @@ 결과 없음 최근 검색 없음 자주 묻는 질문 + Distance Units + Distance units + Automatic + Kilometers + Miles 로컬 데이터베이스 캐시가 된 문자 메시지를 비울까요? @@ -1794,8 +1854,10 @@ 지도 위성 혼합 - m 떨어짐 - km 떨어짐 + %1$s m away + %1$s km away + %1$s ft away + %1$s mi away 내 현재 위치 보내기 내 실시간 위치 공유... 위치 공유 중단 @@ -1807,6 +1869,7 @@ 위치 장소 반경 %1$s 내로 정확함 + %1$s away 아니면 장소 선택 근처 장소를 보려면 쓸어 올리세요 실시간 위치 @@ -1823,6 +1886,23 @@ %1$s 님에게 회원님의 정확한 위치를 얼마 동안 보일지 선택하세요. 이 대화방 사람들에게 회원님의 실시간 위치를 얼마 동안 보일지 선택하세요. GPS가 꺼져 있습니다. 위치 기반 서비스를 사용하려면 GPS를 켜 주세요. + People Nearby + Add People Nearby + Quickly add people nearby who are viewing this section and discover local group chats. \n\nPlease switch on location access to enable this feature. + People nearby + Allow Access + Quickly add people nearby who are also viewing this section and discover local group chats.\n\nPlease turn on location services to enable this feature. + Turn On + Groups nearby + Ask your friend nearby to open this page to exchange phone numbers. + Looking for users around you... + 지역 그룹 만들기 + 그룹하기 + Anyone close to this location (neighbors, co-workers, fellow students, event attendees, visitors of a venue) will be able to see your group in the People Nearby section. + If you start an unrelated group at this location, you may lose the ability to create location-based groups. + Set Location + 이 위치 설정 + People will be able to find your group in the \"People Nearby\" section. 모든 미디어 보기 모든 파일 보기 @@ -1899,6 +1979,7 @@ 새로운 기기에 로그인할 때 SMS로 받는 코드와 별개로 입력해야 하는 비밀번호를 설정하실 수 있습니다. 비밀번호 비밀번호를 입력하세요 + 이전 작업을 완료하려면 비밀번호를 입력해 주세요. 비밀번호 입력 새 비밀번호를 입력해 주세요 비밀번호를 다시 입력해 주세요 @@ -1979,15 +2060,15 @@ 개인 정보 및 보안 개인 정보 - 마지막 접속 + 마지막 접속 및 온라인 프로필 사진 내 프로필 사진을 볼 수 있는 사람 회원님의 프로필 사진을 볼 수 있는 사람을 세세하게 제한하실 수 있습니다. 이 설정은 앞선 설정보다 우선합니다. - Phone Number - Who can see my phone number? + 전화번호 + 내 전화번호를 볼 수 있는 사람 Users who already have your number saved in their contacts will see it on Telegram. - You can add a user or an entire group as an exception that will override the settings above. + You can add users or entire groups as exceptions that will override the settings above. 전달된 메시지 회원님 계정으로 연결합니다 아래 설정이 허용할 시 연결합니다 @@ -1996,9 +2077,9 @@ 회원님이 보낸 메시지가 다른 대화방으로 전달되었을 때 회원님의 계정으로 연결되지 않습니다. 이 설정은 앞선 설정보다 우선합니다. 라인하르트, 새 음악도 좀 들어보시지 그래요 🎶? - Peer-to-Peer + 피어투피어 단대단 통화 - Use Peer-to-Peer with + 단대단 사용할 사람 봇 및 웹 사이트 결제 및 배송 정보 초기화 배송 정보를 삭제하고 결제 제공 업체에 저장된 신용카드 정보를 삭제하도록 요청하시겠습니까? 텔레그램은 절대로 신용카드 정보를 저장하지 않습니다. @@ -2009,7 +2090,7 @@ 모두 내 연락 상대 없음 - 전체 공개 (-%1$d) + 모두 (-%1$d) 내 연락 상대만 (+%1$d) 내 연락 상대만 (-%1$d) 내 연락 상대만 (-%1$d, +%2$d) @@ -2029,11 +2110,12 @@ 이 기간 안에 한 번이라도 접속하시지 않으면, 모든 그룹, 메시지, 연락처와 더불어 회원님의 계정이 사라집니다. 내 마지막 접속 시간을 볼 수 있는 사람 예외 추가 + Add to exceptions 중요: 회원님의 마지막 접속 시간을 공유받지 않는 사람의 마지막 접속 시간은 보실 수 없습니다. 대신 최근, 일주일 이내, 한달 이내와 같이 간략하게 보일 것입니다. 일부 개인 정보 설정을 바꾸셨습니다. 변경 사항을 적용할까요? - 항상 공유 + 항상 공유할 사람 절대 공유하지 않음 - 이는 앞선 설정을 무시하고 작동합니다. + 앞선 설정을 무시하고 사용자나 그룹 전체를 예외로 추가하실 수 있습니다. 항상 공유 절대 공유하지 않음 사용자 추가 @@ -2054,8 +2136,9 @@ 자주 이용하는 연락처 추천 "신속한 연락을 위해 검색 구역 위쪽에 자주 대화하는 사람을 내보이세요. " 즐겨 사용하시는 인라인 봇과 자주 메시지하시는 대화 상대들의 데이터가 삭제됩니다. - Add Users or Groups - Exceptions + 사용자 또는 그룹 추가 + 예외 + 없음 동영상 보내는 중... GIF 보내는 중... @@ -2173,7 +2256,7 @@ un1 님이 스크린샷을 찍었습니다! Warning! This will **delete all messages** in this chat for **both** participants. - Delete All + 모두 삭제 불러오기를 중단할까요? 텔레그램을 업데이트하세요 죄송합니다. 텔레그램 앱이 최신 버전이 아니기에 이 요청을 받을 수 없습니다. 텔레그램을 업데이트해 주세요. @@ -2196,11 +2279,11 @@ %2$s 대화방에 %1$s 님을 추가할까요? 전달할 최근 메시지 개수: %1$s 님을 그룹에 추가할까요? - Add %1$s - Add member - Are you sure you want to add %1$s to **%2$s**? - Are you sure you want to add %1$s to **%2$s**? - Show the last 100 messages to the new members + %1$s 추가 + 참가자 추가 + 정말 %1$s 님을 **%2$s**에 초대하시겠습니까? + 정말 %1$s 님을 **%2$s**에 추가하시겠습니까? + 새로운 참가자에게 최근 메시지 100개를 보입니다 Show the last 100 messages to **%1$s** %1$s 님에게 메시지를 보낼까요? %1$s 님에게 게임을 공유할까요? @@ -2229,7 +2312,7 @@ 봇이 회원님의 전화번호를 감지할 것입니다. 이는 다른 서비스와 통합하는 데 도움이 됩니다. " **%2$s**님에게 %1$s번호를 공유하겠습니까?" 정말 전화번호를 공유하시겠습니까? - Are you sure you want to block **%1$s**? + 정말 **%1$s** 님을 차단하시겠습니까? 차단을 해제할까요? 이 연락처를 정말 삭제하시겠습니까? 비밀 대화 @@ -2660,12 +2743,12 @@ 미디어 %1$d개 미디어 %1$d개 미디어 %1$d개 - %1$d blocked users - %1$d blocked user - %1$d blocked users - %1$d blocked users - %1$d blocked users - %1$d blocked users + 차단된 사용자 %1$d명 + 차단된 사용자 %1$d명 + 차단된 사용자 %1$d명 + 차단된 사용자 %1$d명 + 차단된 사용자 %1$d명 + 차단된 사용자 %1$d명 전달할 파일 %1$d개 전달할 파일 %1$d개 전달할 파일 %1$d개 diff --git a/TMessagesProj/src/main/res/values-nl/strings.xml b/TMessagesProj/src/main/res/values-nl/strings.xml index cf45ba30e..7c5f0f7d4 100644 --- a/TMessagesProj/src/main/res/values-nl/strings.xml +++ b/TMessagesProj/src/main/res/values-nl/strings.xml @@ -202,6 +202,8 @@ Vet Cursief Mono + Doorstrepen + Onderstrepen Normaal **Telegram** heeft toegang tot je contacten nodig zodat je makkelijk contact kunt opnemen met je vrienden vanaf al je apparaten. Telegram synchroniseert je contacten continu naar onze zwaar versleutelde Cloud-servers. NU NIET @@ -231,6 +233,9 @@ Sorry, deze groep is vol. Deze gebruiker heeft de groep verlaten. Je kunt hem/haar niet meer uitnodigen. Maximaal aantal beheerders bereikt. + Sorry, de gebruiker heeft al teveel locatiegebonden groepen of kanalen. Vraag of ze er eerst 1 willen overdragen of verwijderen. + Sorry, de gebruiker heeft al teveel locatiegebonden groepen. Vraag of ze er eerst 1 willen overdragen of verwijderen. + Sorry, je hebt teveel locatiegebonden groepen. Verwijder eerst een bestaande. Maximaal aantal bots bereikt. un1 heeft \"%1$s\" vastgezet un1 heeft bericht vastgezet @@ -266,6 +271,7 @@ Je hebt instellingen voor dit kanaal gewijzigd, wijzigingen toepassen? Publiek kanaal Publieke groep + Locatiegebonden groep Publieke kanalen zijn te vinden via de zoekfunctie, iedereen kan er lid van worden. Publieke groepen zijn te vinden via de zoekfunctie, iedereen kan er lid van worden en de geschiedenis zien. Privé-kanaal @@ -275,6 +281,7 @@ Permanente link Uitnodigingslink Leden toevoegen + Leden toevoegen Kanaal verlaten Kanaal verlaten Instellingen @@ -283,7 +290,7 @@ Stil massabericht Wat is een kanaal? Kanalen zijn een functie om berichten naar een ongelimiteerd publiek te sturen. - MAAK KANAAL + Kanaal maken Deze naam is bezet. Deze naam is ongeldig. Een kanaalnaam moet minimaal 5 tekens hebben. @@ -324,7 +331,8 @@ Kanaalfoto verwijderd Kanaalnaam gewijzigd naar un2 Het maximale aantal publieke gebruikersnamen is bereikt. Trek eerst een link van één van je oudere groepen of kanalen in, of gebruik de privé-variant. - Maker + Eigenaar + Beheerder Beheerder GELUID UIT GELUID AAN @@ -348,6 +356,7 @@ Maximaal aantal beheerders bereikt. Maximaal aantal bots bereikt. Je kunt 200 leden handmatig toevoegen aan een kanaal. Een ongelimiteerd aantal mensen kan lid worden via de link van het kanaal. + Sorry, je bent lid van teveel groepen en kanalen, verlaat er wat voordat je een nieuwe maakt. un1 heeft je toegevoegd aan dit kanaal Je bent nu lid van dit kanaal Je bent nu lid van deze groep @@ -388,6 +397,19 @@ Verwijder berichten Beheerders toevoegen Beheerder ontslaan + Eigenaarschap overdragen + Eigenaarschap overdragen + Veiligheidscontrole + Je kunt de groep alleen overdragen aan **%1$s** indien: + Je kunt het kanaal alleen overdragen aan **%1$s** indien: + **Twee-staps-verificatie** langer dan **7 dagen** geleden is ingeschakeld. + Je langer dan **24 uur** geleden bent ingelogd op dit apparaat. + Probeer het later opnieuw + Wachtwoord instellen + Hiermee draag je alle **eigenaarsrechten** van **%1$s** over naar **%2$s** + Eigenaar wijzigen + **%1$s** is nu eigenaar van de groep. + **%1$s** is nu eigenaar van het kanaal. Leden blokkeren Leden toevoegen Uitnodigen via link @@ -423,6 +445,7 @@ Blokkeer en verwijder uit groep Wijzigingen toepassen? Je hebt de rechten van deze gebruiker in **%1$s** aangepast. Wijzigingen toepassen? + Custom Groep beheren Kanaal beheren Groep beheren @@ -445,6 +468,8 @@ Publiek Privé Publiek + Link + Tik om een permanente link toe te voegen Afbeelding kiezen Foto maken Uploaden uit galerij @@ -455,27 +480,27 @@ Bots kunnen alleen als beheerder aan kanalen worden toegevoegd. BEHEERDER MAKEN %1$s zal verwijderd worden als beheerder als je een beperking oplegt. - Discussion - Add a group chat for comments - Linked Channel - Select a group chat for discussion that will be displayed in your channel. - Everything you post in the channel will be forwarded to this group. + Discussie + Voeg een groepschat toe voor opmerkingen. + Gekoppeld kanaal + Kies een groepschat voor discussie die zal worden weergegeven in je kanaal. + Alles wat je plaatst in het kanaal zal worden doorgestuurd naar deze groep. Een link naar **%1$s** zal worden weergegeven aan alle abonnees in het onderpaneel. - **%1$s** is linking the group as its discussion board. - All new messages posted in this channel are forwarded to the group. - Create a New Group + **%1$s** koppelt de groep als discussiegroep + Alle nieuwe berichten die je plaatst in dit kanaal worden doorgestuurd naar de groep. + Maak een nieuwe groep Groep ontkoppelen Kanaal ontkoppelen Ontkoppel Groep koppelen - 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. - \"Chat history for new members\" will be switched to Visible. - Are you sure you want to unlink **%1$s** from this group? - Are you sure you want to unlink **%1$s** from this channel? - DISCUSS - channel + **%1$s** echt de discussiegroep voor **%2$s** maken? + **%1$s** echt de discussiegroep voor **%2$s** maken?\n\nAlle leden van deze groep kunnen berichten in het kanaal zien. + **%1$s** echt de discussiegroep voor **%2$s** maken?\n\nAlle abonnees van het kanaal kunnen de berichten in deze groep zien. + \"Chatgeschiedenis voor nieuwe leden\" zal op zichtbaar worden ingesteld. + **%1$s** echt ontkoppelen van deze groep? + **%1$s** echt ontkoppelen van dit kanaal? + Discussiëren + kanaal Nieuwe poll Poll @@ -543,12 +568,15 @@ bericht door un1 losgemaakt un1 heeft de poll gestopt: bericht door un1 verwijderd: + un1 wijzigde de groepslocatie naar \"%1$s\" + un1 verwijderde de groepslocatie + Eigenaarschap overgedragen aan %1$s groepsstickers door un1 gewijzigd groepsstickers door un1 verwijderd - un1 made un2 the discussion group for this channel - un1 removed the discussion group un2 - un1 linked this group to un2 - un1 unlinked this group from un2 + un1 heeft un2 de discussiegroep van dit kanaal gemaakt. + un1 heeft de discussiegroep verwijderd un2 + un1 heeft deze groep aan un2 gekoppeld + un1 heeft deze groep van un2 ontkoppeld link door un1 gewijzigd: link door un1 gewijzigd: groepslink door un1 verwijderd @@ -674,6 +702,7 @@ Niets recents Bericht Bericht + DEEL MIJN NUMMER Mijn contactgegevens delen Toevoegen aan contacten %s heeft je uitgenodigd voor een geheime chat. @@ -705,16 +734,25 @@ Upload %1$s Als bestand Als bestand - Open Link - Do you want to open %1$s? - Log in to %1$s as **%2$s** - Allow **%1$s** to send me messages + Link openen + Wil je %1$s openen? + Log in op %1$s als %2$s + Sta %1$s toe om me berichten te sturen Versturen annuleren Wil je %1$s toestaan om je naam en ID (niet je telefoonnummer) door te geven aan webpagina\'s die je opent via deze bot? + GROEP NIET LOCATIEGEBONDEN? + Ongerelateerde groep melden + Vertel het ons als deze groep niet gerelateerd is aan deze locatie.\n\n**%1$s** + Vertel het ons als deze groep niet gerelateerd is aan deze locatie. SPAM MELDEN + Spam melden + Blokkeer %1$s + BLOKKEER GEBRUIKER SPAM MELDEN EN VERLATEN CONTACT TOEVOEGEN + VOEG %1$s TOE AAN CONTACTEN CONTACT BEKIJKEN + **%1$s** echt blokkeren om je berichten te sturen en je te bellen via Telegram? "Spam van deze gebruiker echt melden? " "Spam van deze groep echt melden? " "Spam van dit kanaal echt melden? " @@ -821,6 +859,7 @@ Google Maps installeren? OPLICHTING via + Message doesn\'t exist %1$s heeft de zelfvernietigingstimer ingesteld op %2$s Je hebt de zelfvernietigingstimer ingesteld op %1$s @@ -928,7 +967,7 @@ Telegram Contact kiezen - Select Contacts + Kies contacten Contact delen Nog geen contacten Ik gebruik Telegram om te chatten. Download de app hier: %1$s @@ -956,6 +995,8 @@ Veel van je contacten zijn niet gesynchroniseerd, wil je deze synchroniseren? Kies \'OK\' als je je eigen apparaat, SIM en Google-account gebruikt. Sorteren op naam Sorteren op laatst-gezien + Voeg %1$s toe + Telefoonnummer Mensen toevoegen Nadat je de groep hebt aangemaakt kun je meer gebruikers toevoegen en omzetten naar een supergroep. @@ -981,8 +1022,8 @@ Link kopiëren Link delen Andere Telegram-gebruikers kunnen lid worden van je groep door deze link te openen. - Search for people... - Search for users and groups... + Zoek mensen + Zoek naar gebruikers en groepen Leden Gedeelde media @@ -1011,9 +1052,9 @@ %1$s is nog geen lid, uitnodigen? Nodig uit Blokkeer - Block user - User blocked - User unblocked + Blokkeer gebruiker + Gebruiker geblokkeerd + Gebruiker gedeblokkeerd Wijzig Verwijder Thuis @@ -1025,6 +1066,7 @@ Verjaardag Titel Maak nieuw contact + Nieuw contact Toevoegen aan Contact Bio Voeg wat informatie over jezelf toe @@ -1044,6 +1086,11 @@ https://telegram.org/faq#secret-chats Onbekend Onbekend + Verborgen + Telefoonnummer is zichtbaar zodra %1$s je als contact heeft toegevoegd. + Als je op **GEREED** tik, zal je nummer zichtbaar zijn voor %1$s. + Deel mijn nummer met %1$s + %1$s staat nu in je contacten Info Telefoon Gedeelde inhoud @@ -1121,7 +1168,7 @@ Uit Uitgeschakeld Geplande tijd - Adaptive + Adaptief Planning Gebruik zonsopkomst & -ondergang Locatie bijwerken @@ -1133,8 +1180,8 @@ Schakel om naar je nachtthema als het omgevingslicht minder dan %1$d%% is. Donker Donkerblauw - Graphite - Arctic Blue + Grafiet + Arctisch Blauw Blauw Thema echt verwijderen? Themabestand is ongeldig @@ -1143,8 +1190,8 @@ THEME OPSLAAN Nieuw kleurenthema Kleurenthema - Built-in themes - Custom themes + Ingebouwde thema\'s + Aangepaste thema\'s Weergave chatoverzicht Twee regels Drie regels @@ -1152,13 +1199,13 @@ TOEPASSEN Thema-voorbeeld Selecteer kleur - Color Themes - Show all Themes + Kleurenthema\'s + Alle thema\'s weergeven Nieuw thema maken Tik op het palet-icoon om een lijst met elementen voor elk scherm te zien en deze te bewerken. Je kunt je eigen thema maken door kleuren in de app aan te passen.\n\nJe kunt hier altijd het standaardthema weer instellen. - Meldingen gereset + Alle meldingen gereset Alle meldingsinstellingen herstellen naar de standaardwaarden? Tekstgrootte berichten Een vraag stellen @@ -1213,7 +1260,10 @@ Inschakelen Uitschakelen Geblokkeerd - Blocked users will not be able to contact you and will not see your Last Seen time. + Geblokkeerde gebruikers kunnen geen contact met je opnemen en zien je laatst-gezien-tijd niet. + Blokkeer gebruiker + CHATS + CONTACTEN Uitloggen Geen geluid Standaard @@ -1221,7 +1271,8 @@ Alleen indien stil Vervaagd Beweging - Achtergrond kiezen + Chatachtergrond wijzigen + Achtergrond wijzigen Chatachtergronden resetten Verwijder alle geüploade chatachtergronden en herstel de voorgeïnstalleerde achtergronden. Chat achtergrond resetten @@ -1379,7 +1430,8 @@ Meldingen herhalen Je kan je telefoonnummer hier wijzigen. Al je clouddata — berichten, bestanden, groepen, contacten, etc. zullen worden gekopieërd naar je nieuwe nummer.\n\n**Let op:** al je Telegram-contacten krijgen je **nieuwe nummer** in hun adresboek, ervan uitgaande dat ze je oude nummer hadden en je hen niet had geblokkeerd in Telegram. Al je Telegram-contacten krijgen je nieuwe nummer in hun adresboek, ervan uitgaande dat ze je oude nummer hadden en je hen niet had geblokkeerd in Telegram. - NUMMER WIJZIGEN + Nummer wijzigen + Nummer wijzigen Nieuw nummer We sturen een sms-bericht met verificatiecode naar je nieuwe nummer. Aan telefoonnummer %1$s is al een Telegram-account gekoppeld. Verwijder het account om te kunnen migreren naar het nieuwe nummer. @@ -1395,6 +1447,9 @@ Slimme meldingen Uitzonderingen Uitzondering toevoegen + Alle uitzondering wissen + Alle uitzonderingen wissen + Echt alle uitzonderingen wissen? Nieuwe uitzondering Hier vind je alle chats met niet-standaard berichtgevingsinstellingen.\n\nJe kunt de instellingen van een chat wijzigen door het profiel te openen en naar ‘Berichtgeving’ te gaan. Geen @@ -1503,6 +1558,11 @@ Geen resultaten Geen recente zoekopdrachten Veelgestelde vragen + Distance Units + Distance units + Automatic + Kilometers + Miles Lokale database Gecachet tekstberichten wissen? @@ -1794,8 +1854,10 @@ Kaart Satelliet Hybride - m - km + %1$s m away + %1$s km away + %1$s ft away + %1$s mi away Huidige locatie sturen Deel mijn live-locatie Delen van locatie stoppen @@ -1807,6 +1869,7 @@ Locatie Plek Nauwkeurig tot op %1$s + %1$s van jou Of kies een plek Veeg omhoog voor plekken dichtbij Live-locaties @@ -1823,6 +1886,23 @@ Kies hoelang je je nauwkeurige locatie met %1$s wilt delen. Kies hoelang je je Live-locatie met mensen in deze chat wilt delen. GPS lijkt uitgeschakeld, schakel GPS in om locatiegebaseerde functies te gebruiken. + Mensen dichtbij + Voeg mensen dichtbij toe + Gebruik deze sectie om makkelijk mensen dichtbij toe te voegen die deze sectie open hebben en lokale groepschats te ontdekken.\n\nSta locatie-toegang toe om deze functie in te schakelen. + Mensen dichtbij + Sta toegang toe + Voeg snel mensen dichtbij toe die deze sectie open hebben en ontdek lokale groepschats.\n\nSta locatietoegang toe om deze functie te gebruiken. + Inschakelen + Groepen dichtbij + Vraag je vriend die bij je is om deze pagina te openen en zo telefoonnummers uit te wisselen. + Zoeken naar gebruikers in de buurt + Maak een lokale groep + Groep starten + Iedereen die hier dichtbijt is (buren, collega\'s, medestudenten, etc.) zien je groep in de \"Mensen dichtbij\"-sectie. + Als je een ongerelateerde groep op deze plek start kun je beperkt worden in het maken van nieuwe lokatie-afhankelijke groepen. + Locatie instellen + Stel deze locatie in + Mensen kunnen je groep vinden in de \"Mensen Dichtbij\"-sectie Alle media weergeven Alle bestanden @@ -1899,6 +1979,7 @@ Naast de code die je per SMS ontvangt kun je een extra wachtwoord instellen voor als je inlogt op een nieuw apparaat. Je wachtwoord Voer je wachtwoord in + Voer je wachtwoord in om de overdracht af te ronden Wachtwoord invoeren Nieuw wachtwoord invoeren Wachtwoord opnieuw invoeren @@ -1979,26 +2060,26 @@ Privacy en veiligheid Privacy - Laatst gezien + Laatst gezien & online Profielfoto Wie kan mijn profielfoto zien Je kunt nauwkeurig beperken wie je profielfoto kan zien. - Deze instelling overschrijft de bovenstaande. - Phone Number + Je kunt gebruikers of groepen toevoegen als uitzondering op de instelling hierboven. + Telefoonnummer Wie kan mijn telefoonnummer zien? - Users who already have your number saved in their contacts will see it on Telegram. - You can add a user or an entire group as an exception that will override the settings above. + Gebruikers die je nummer al hebben opgeslagen in contacten zien je nummer op ook Telegram. + Je kunt gebruikers of groepen toevoegen als uitzondering op de instelling hierboven. Doorgestuurde berichten Link naar je account Link indien hieronder toegestaan Geen link naar je account Wie voegt een link naar mijn account toe bij het doorsturen van mijn berichten Berichten van je die worden doorgestuurd linken niet terug naar je account. - Deze instelling overschrijft de bovenstaande. + Je kunt gebruikers of groepen toevoegen als uitzondering op de instelling hierboven. Godfried, verveel je je? Peer-to-Peer Peer-to-Peer in oproepen - Use Peer-to-Peer with + Gebruik Peer-to-Peer met Bots en websites Betaal- en verzendinfo wissen Je verzendinformatie wissen en alle betalingsproviders opdracht geven om je bewaarde creditcards te verwijderen? Je creditcardgegevens worden nooit opgeslagen door Telegram. @@ -2029,11 +2110,12 @@ Als je binnen deze periode niet minimaal één keer ingelogd bent geweest zal je account worden verwijderd, inclusief alle berichten en contacten. Wie kan mijn laatst gezien tijd zien? Uitzonderingen toevoegen + Aan uitzonderingen toevoegen Let op: van mensen waarmee je je laatst gezien tijd niet deelt is deze voor jou ook niet zichtbaar. In plaats daarvan krijg je tijden bij benadering te zien (recent, afgelopen week, afgelopen maand). Je hebt privacyinstellingen aangepast. Wijzigingen toepassen? Altijd delen met Nooit delen met - Deze instelling overschrijft de bovenstaande. + Je kunt gebruikers of groepen toevoegen als uitzondering op de instelling hierboven. Altijd delen Nooit delen Toevoegen @@ -2054,8 +2136,9 @@ Veelgebruikte contacten voorstellen Mensen die je vaak een bericht stuurt bovenaan de zoeksectie weergeven voor snelle toegang. Hiermee verwijder je alle gegevens van mensen die je vaak berichten stuurt, inclusief inline-bots die je veel gebruikt. - Add Users or Groups - Exceptions + Voeg gebruikers of groepen toe. + Uitzonderingen + Geen Video versturen GIF versturen... @@ -2172,8 +2255,8 @@ Schermafdruk gemaakt! un1 heeft een schermafdruk gemaakt! - Warning! This will **delete all messages** in this chat for **both** participants. - Delete All + Let op! Hiermee verwijder je **alle berichten** in deze chat voor **beide** deelnemers. + Alles verwijderen Stopped met laden? Telegram bijwerken Je gebruikt een verouderde versie van Telegram, deze kan dit verzoek niet verwerken. Werk Telegram bij. @@ -2189,19 +2272,19 @@ Ongeldige achternaam Bezig met laden Je hebt geen mediaspeler. Installeer een mediaspeler om door te gaan. - E-mail ons op sms@stel.com en vertel ons over je probleem. + E-mail ons op sms@stel.com en vertel ons wat het probleem is. Je hebt geen apps die bestandstype \'%1$s\' kunnen verwerken, gelieve een compatibele app te installeren Deze gebruiker heeft nog geen Telegram. Wil je een uitnodiging sturen? Weet je het zeker? %1$s toevoegen aan de groep %2$s? Aantal recente berichten om door te sturen: %1$s toevoegen aan de groep? - Add %1$s - Add member - Are you sure you want to add %1$s to **%2$s**? - Are you sure you want to add %1$s to **%2$s**? - Show the last 100 messages to the new members - Show the last 100 messages to **%1$s** + Voeg %1$s toe + Lid toevoegen + %1$s echt toevoegen aan **%2$s**? + %1$s echt toevoegen aan **%2$s**? + Nieuwe leden krijgen de laatste 100 berichten te zien. + Laat de laatste 100 berichten aan **%1$s** zien. Berichten naar %1$s versturen? Spel delen met %1$s? Contact delen met %1$s? @@ -2229,13 +2312,13 @@ De bot krijgt je telefoonnummer, dit kan handig zijn voor de integratie met andere diensten. Telefoonnummer %1$s delen met **%2$s**? Telefoonnummer echt delen? - Are you sure you want to block **%1$s**? + **%1$s** echt blokkeren? Weet je zeker dat je deze persoon wilt deblokkeren? Contact echt verwijderen? Geheime chat Weet je zeker dat je een geheime chat wilt starten? Weet je zeker dat je de registratie wilt annuleren? - Wil je telefoonnummerverificatieproces stoppen? + Nummerverificatie stopped? Chatgeschiedenis met **%1$s** echt wissen? Geheime chatgeschiedenis met **%1$s** echt wissen? Chatgeschiedenis in **%1$s** echt wissen? @@ -2290,7 +2373,7 @@ Telegram heeft toegang tot je opslaggeheugen nodig zodat je foto\'s, video\'s, muziek en andere media kunt opslaan en versturen. Telegram heeft toegang tot je microfoon nodig om spraakberichten te kunnen verzenden. Telegram heeft toegang tot je microfoon nodig om video op te nemen. - Telegram heeft toegang tot je camera nodig zodat je foto\'s en video\'s kunt maken. + Telegram heeft toegang tot je camera nodig zodat je foto\'s en video\'s kunt maken. Schakel dit in via instellingen. Telegram heeft toegang tot je locatie nodig om deze te kunnen delen met je vrienden. Telegram heeft toegang tot je locatie nodig. Telegram heeft de toestemming \'over andere apps tekenen\' nodig om video\'s af te spelen in Picture-in-Picture-modus. @@ -2660,12 +2743,12 @@ %1$d media %1$d media %1$d media - %1$d blocked users - %1$d blocked user - %1$d blocked users - %1$d blocked users - %1$d blocked users - %1$d blocked users + %1$d geblokkeerde gebruikers + %1$d geblokkeerde gebruiker + %1$d geblokkeerde gebruikers + %1$d geblokkeerde gebruikers + %1$d geblokkeerde gebruikers + %1$d geblokkeerde gebruikers Bijlage: %1$d bestanden Bijlage: 1 bestand Bijlage: %1$d bestanden diff --git a/TMessagesProj/src/main/res/values-pt-rBR/strings.xml b/TMessagesProj/src/main/res/values-pt-rBR/strings.xml index c530004cb..a8ede29e1 100644 --- a/TMessagesProj/src/main/res/values-pt-rBR/strings.xml +++ b/TMessagesProj/src/main/res/values-pt-rBR/strings.xml @@ -28,7 +28,7 @@ Confira suas mensagens no Telegram Inserir código Estamos ligando para o seu telefone **%1$s**.\n\nNão atenda, o Telegram processará tudo automaticamente. - Nós te ligaremos em **%1$s** para ditar o código. + Te ligaremos no **%1$s** para ditar o código. Telegram irá te ligar em %1$d:%2$02d Vamos te enviar um SMS em %1$d:%2$02d Estamos te ligando... @@ -49,7 +49,7 @@ Seu código de login é **%1$s**. Digite ele no app do Telegram no qual você está tentando fazer o login.\n\nNão informe esse código para ninguém. Seu nome - Por favor, digite seu nome completo e coloque uma foto de perfil. + Digite seu nome completo e coloque uma foto de perfil. Nome (obrigatório) Sobrenome (opcional) Cancelar registro @@ -183,7 +183,7 @@ Recente Pessoas Remover sugestão - Tem certeza de que você deseja remover **%1$s** das sugestões? + Tem certeza de que você deseja remover **%1$s** das sugestões? Remover %1$s das sugestões? Prévia do link Rascunho @@ -202,6 +202,8 @@ Negrito Itálico Mono + Tachado + Sublinhado Regular O **Telegram** precisa acessar seus contatos para que você possa se comunicar com seus amigos em todos os seus dispositivos. Seus contatos serão continuamente sincronizados na nuvem fortemente criptografada do Telegram. AGORA NÃO @@ -225,12 +227,15 @@ Espere! Apagar este grupo removerá todos os membros e todas as mensagens serão perdidas. Apagar o grupo mesmo assim? Grupo criado un1 adicionou você ao grupo - Você tem certeza de que deseja sair do grupo? + Tem certeza de que deseja sair do grupo? Tem certeza de que deseja sair de **%1$s**? Desculpe, você não pode adicionar esse usuário a grupos. Desculpe, este grupo está lotado. Desculpe, esse usuário decidiu sair deste grupo. Por isso, você não pode convidá-lo de volta. - Desculpe, muitos administradores neste canal. + Desculpe, há muitos administradores neste grupo. + Desculpe, o usuário já tem muitos grupos ou canais públicos. Por favor, peça que ele apague ou transfira um dos grupos ou canais existentes primeiro. + Desculpe, o usuário já tem muitos grupos baseados em localização. Por favor, peça que ele apague um dos grupos existentes primeiro. + Desculpe, você já tem muitos grupos com base em localização. Por favor, apague um dos seus grupos existentes primeiro. Desculpe, há bots demais neste grupo. un1 fixou \"%1$s\" un1 fixou uma mensagem @@ -266,6 +271,7 @@ Você alterou algumas configurações neste canal. Aplicar alterações? Canal Público Grupo Público + Grupo Baseado em Localização Canais públicos podem ser encontrados na busca. Qualquer um pode entrar. Grupos públicos podem ser encontrados na busca, o histórico é disponível para todos e qualquer um pode entrar. Canal Privado @@ -275,6 +281,7 @@ Link permanente Link de Convite Adicionar membros + Adicionar Membros Sair do Canal Sair do Canal Configurações @@ -283,7 +290,7 @@ Mensagem Silenciosa O que é um Canal? Canais são uma ferramenta de transmissão de suas mensagens para audiências ilimitadas. - CRIAR CANAL + Criar Canal Desculpe, esse nome já está em uso. Desculpe, esse nome é inválido. Nome do canal deve ter pelo menos 5 caracteres. @@ -314,7 +321,7 @@ Apagar Canal Apagar Canal Espere! Apagar esse canal removerá todos os membros e todas as mensagens serão perdidas. Apagar assim mesmo? - Você tem certeza de que deseja sair do canal? + Tem certeza de que deseja sair do canal? Tem certeza de que deseja sair de **%1$s**? Você perderá todas as mensagens desse canal. Editar @@ -324,7 +331,8 @@ Foto do canal removida Nome do canal alterado para un2 Desculpe, você reservou muitos nomes públicos. Você pode remover o link público de um de seus grupos ou canais, ou criar de forma privada. - Criador + Dono + Administrador Admin SILENCIAR RESTAURAR SOM @@ -348,6 +356,7 @@ Desculpe, muitos administradores neste canal. Desculpe, há bots demais neste canal. Desculpe, você só pode adicionar os primeiros 200 membros ao canal. Note que um número ilimitado de pessoas podem entrar via link do canal. + Desculpe, você é um membro de muitos grupos e canais. Por favor, saia de alguns antes de criar um novo. un1 adicionou você ao canal Você entrou neste canal Você entrou neste grupo @@ -360,7 +369,7 @@ %1$s postou um vídeo %1$s postou um contato %2$s %1$s postou uma enquete %2$s - %1$s postou uma foto + %1$s postou uma localização %1$@ postou uma localização em tempo real %1$s postou um arquivo %1$s postou um GIF @@ -388,6 +397,19 @@ Apagar Mensagens Adicionar Novos Admins Dispensar admin + Transferir Posse do Grupo + Transferir Posse do Canal + Verificação de Segurança + Você pode transferir este grupo para **%1$s** somente se tiver: + Você pode transferir este canal para **%1$s** somente se tiver: + Ativado a **Verificação em Duas Etapas** há mais de **7 dias**. + Conectado a este dispositivo há mais de **24 horas**. + Por favor, volte mais tarde. + Definir Senha + Isso transferirá os direitos de **dono** de **%1$s** para **%2$s**. + Alterar Dono + **%1$s** agora é o dono do grupo. + **%1$s** agora é o dono do canal. Banir Usuários Adicionar Usuários Convidar Usuários via Link @@ -423,6 +445,7 @@ Banir e remover do grupo Aplicar Alterações? Você alterou as permissões deste usuário em **%1$s**. Aplicar alterações? + Personalizado Gerenciar Grupo Gerenciar Canal Gerenciar grupo @@ -445,6 +468,8 @@ Público Privado Público + Link + Toque para adicionar um link permanente Escolher Foto Tirar foto Enviar da galeria @@ -460,10 +485,10 @@ Canal Vinculado Escolha um grupo para conversas que será exibido em seu canal. Tudo que você postar no canal será encaminhado para este grupo. - Um link para **%1$s** é exibido no rodapé para todos os inscritos. + Um link para **%1$s** é exibido no painel inferior para todos os inscritos. O canal **%1$s** está vinculado a este grupo de conversas. Todas as novas mensagens postadas neste canal serão encaminhadas ao grupo. - Criar Novo Grupo + Criar Grupo Desvincular Grupo Desvincular Canal Desvincular @@ -543,6 +568,9 @@ un1 desafixou essa mensagem: un1 encerrou a enquete: un1 apagou esta mensagem: + un1 alterou o local do grupo para \"%1$s\" + un1 removeu a localização do grupo + transferiu a posse para %1$s un1 alterou o pacote de sticker do grupo un1 removeu o pacote de sticker do grupo un1 definiu un2 como o grupo de conversas deste canal. @@ -674,8 +702,9 @@ Nada recente Mensagem Mensagem + COMPARTILHAR MEU NÚMERO Compartilhar meu contato - Adicionar aos contatos + Add Contato %s te convidou para um chat secreto. Você convidou %s para um chat secreto. Chats Secretos: @@ -711,13 +740,22 @@ Permitir que **%1$s** me envie mensagens Cancelar o envio Permitir que %1$s passe seu nome no Telegram e ID (não o seu número de telefone) para páginas que você abrir com esse bot? + GRUPO SEM RELAÇÃO COM O LOCAL? + Denunciar grupo sem relação + Por favor, nos diga se este grupo não está relacionado com este local:\n\n**%1$s** + Por favor, nos diga se este grupo não está relacionado com este local. DENUNCIAR SPAM + Denunciar spam + Bloquear %1$s + BLOQUEAR DENUNCIAR SPAM E SAIR ADICIONAR CONTATO + ADD %1$s A CONTATOS VER CONTATO - Você tem certeza de que deseja denunciar este usuário por spam? - Você tem certeza de que deseja denunciar este grupo por spam? - Você tem certeza de que deseja denunciar este canal por spam? + Deseja bloquear as mensagens e chamadas de **%1$s** no Telegram? + Tem certeza de que deseja denunciar este usuário por spam? + Tem certeza de que deseja denunciar este grupo por spam? + Tem certeza de que deseja denunciar este canal por spam? Desculpe, você só pode enviar mensagens para contatos mútuos no momento. Desculpe. No momento, você só pode adicionar contatos mútuos a grupos. Desculpe, no momento você está restringido de postar em grupos públicos. @@ -787,9 +825,9 @@ Segure para gravar áudio. Toque para vídeo. Segure para gravar vídeo. Toque para áudio. Descartar Mensagem de Voz - Você tem certeza de que deseja parar de gravar e descartar sua mensagem de voz? + Tem certeza de que deseja parar de gravar e descartar sua mensagem de voz? Descartar Mensagem de Vídeo - Você tem certeza de que deseja parar de gravar e descartar sua mensagem de vídeo? + Tem certeza de que deseja parar de gravar e descartar sua mensagem de vídeo? Descartar Os admins do grupo restringiram você de enviar mídias aqui até %1$s Os admins do grupo restringiram você de usar conteúdo inline aqui até %1$s. @@ -821,6 +859,7 @@ Instalar Google Maps? FRAUDE via + A mensagem não existe. "%1$s alterou o timer de autodestruição para %2$s " Você alterou o timer de autodestruição para %1$s @@ -956,6 +995,8 @@ O Telegram detectou muitos contatos não sincronizados, você gostaria de sincronizá-los agora? Escolha \'OK\' se você está usando seu próprio celular, cartão SIM e conta do Google. Listado por Nome Listado por Visto por Último + Adicionar %1$s + Número de telefone Adicionar pessoas... Você poderá adicionar mais usuários após finalizar a criação do grupo e convertê-lo em supergrupo. @@ -972,12 +1013,12 @@ Email copiado para a área de transferência Convidar para o Grupo via Link Link de Convite - Você tem certeza de que deseja desativar o link de convite? Se fizer isso, ninguém conseguirá usá-lo. + Tem certeza de que deseja desativar o link de convite? Se fizer isso, ninguém conseguirá usá-lo. O link de convite anterior foi desativado. Um novo link foi gerado. Desativar Desativar Link - Você tem certeza de que deseja desativar o link **%1$s**?\n\nO grupo \"**%2$s**\" se tornará privado. - Você tem certeza de que deseja desativar o link **%1$s**?\n\nO canal \"**%2$s**\" se tornará privado. + Tem certeza de que deseja desativar o link **%1$s**?\n\nO grupo \"**%2$s**\" se tornará privado. + Tem certeza de que deseja desativar o link **%1$s**?\n\nO canal \"**%2$s**\" se tornará privado. Copiar Link Compartilhar Link Qualquer pessoa que tenha o Telegram instalado poderá participar do seu grupo seguindo esse link. @@ -1005,12 +1046,12 @@ **Em supergrupos:**\n\n• Novos membros podem ver todo o histórico do chat\n• Mensagens apagadas somem para todos os membros\n• Admins podem adicionar uma descrição no grupo\n• O criador pode definir um link público para o grupo **Nota:** essa ação não pode ser desfeita. - Compartilhar - Adicionar + Compartilhar contato + Adicionar contato Adicionar contato %1$s ainda não está no Telegram. Gostaria de fazer um convite? Convidar - Bloquear + Bloquear usuário Bloquear usuário Usuário bloqueado Usuário desbloqueado @@ -1024,7 +1065,8 @@ Trabalho Nascimento Cargo - Criar Novo Contato + Novo Contato + Novo Contato Adicionar ao Contato Existente Bio Adicione algumas palavras sobre você @@ -1044,6 +1086,11 @@ https://telegram.org/faq/br#chats-secretos Desconhecido Desconhecido + Número oculto + O número de telefone ficará visível assim que %1$s te adicionar como contato. + Quando você tocar em **CONCLUÍDO**, o seu número ficará visível para %1$s. + Compartilhar meu número com %1$s + %1$s agora está em seus Contatos. Info Telefone Conteúdo compartilhado @@ -1136,7 +1183,7 @@ Grafite Azul Ártico Azul - Você tem certeza de que deseja apagar este tema? + Tem certeza de que deseja apagar este tema? Arquivo de tema incorreto Insira o nome do tema FECHAR EDITOR @@ -1154,7 +1201,7 @@ Selecionar cor Tema de Cores Mostrar Todos os Temas - Criar novo tema + Criar tema Toque no ícone da paleta para visualizar a lista de elementos de cada tela - e editá-los. Você pode criar seu próprio tema alterando as cores dentro do aplicativo.\n\nVocê sempre pode voltar para o tema padrão do Telegram aqui. @@ -1214,6 +1261,9 @@ Desativar Usuários Bloqueados Usuários bloqueados não conseguem entrar em contato com você e não veem o seu Visto por Último. + Bloquear Usuário + CHATS + CONTATOS Sair Sem som Padrão @@ -1221,7 +1271,8 @@ "Somente no silencioso " Desfoque Movimento - Papel de Parede + Alterar Papel de Parede + Alterar Papel de Parede Redefinir Papéis de Parede Remove todos os papéis de parede enviados e restaura os pré-instalados. Redefinir papéis de parede @@ -1379,7 +1430,8 @@ Repetir Notificações Você pode alterar seu número do Telegram aqui. Sua conta e todos os seus dados — mensagens, mídia, contatos, etc. serão movidos para o novo número.\n\n**Importante:** todos os seus contatos do Telegram terão seu **novo número** adicionado às listas de contatos deles, desde que tenham seu número antigo e você não os tenha bloqueado no Telegram. Todos os seus contatos do Telegram terão seu novo número adicionado às listas de contatos deles, desde que tenham seu número antigo e você não os tenha bloqueado no Telegram. - ALTERAR NÚMERO + Alterar Número + Alterar número Novo número Vamos enviar um SMS com um código de confirmação para o seu novo número. O número %1$s já possui uma conta do Telegram. Por favor, exclua esta conta antes de migrar para o novo número. @@ -1395,6 +1447,9 @@ Notificações Inteligentes Exceções Adicionar Exceção + Apagar Todas as Exceções + Apagar todas as exceções + Tem certeza de que deseja apagar todas as exceções? Nova Exceção Esta seção irá listar todos os chats que possuem configurações não-padrão.\n\nVocê pode personalizar notificações para um chat abrindo o perfil do mesmo e escolhendo \'Notificações\'. Nenhum @@ -1503,6 +1558,11 @@ Nenhum resultado encontrado Nenhuma busca recente FAQ + Unidades de Distância + Unidades de distância + Automático + Quilômetros + Milhas Banco de Dados Local Limpar todos os textos em cache? @@ -1711,7 +1771,7 @@ Vietnamita Sessões Ativas - Sessão atual + Sessão Atual Nenhuma outra sessão ativa Você pode entrar no Telegram em outros celulares, tablets e computadores usando o mesmo número de telefone. Todos os seus dados serão sincronizados. Sessões Ativas @@ -1719,11 +1779,11 @@ Toque em uma sessão para terminá-la. Encerrar essa sessão? Sai de todos os dispositivos, exceto este. - Você tem certeza de que deseja terminar todas as outras sessões? + Tem certeza de que deseja terminar todas as outras sessões? Terminar todas as outras sessões aplicativo não oficial Nenhum login ativo. - Você pode entrar em sites que são compatíveis com o login com o Telegram. + Em sites compatíveis, você pode fazer o login com o Telegram. Sites conectados Entrou com o Telegram Toque para desconectar de sua conta do Telegram. @@ -1731,7 +1791,7 @@ Bloquear %1$s Desconectar Todos os Sites Tem certeza de que deseja desconectar de todos os sites em que você logou usando o Telegram? - Você pode entrar em sites que são compatíveis com o login com o Telegram. + Em sites compatíveis, você pode fazer o login com o Telegram. Tentativas incompletas de login Os dispositivos acima não possuem acesso às suas mensagens. O código foi inserido corretamente, mas a senha não. @@ -1794,19 +1854,22 @@ Mapa Satélite Híbrido - m de distância - km de distância + %1$s metros de distância + %1$s km de distância + %1$s pés de distância + %1$s milhas de distância Enviar minha localização atual - Compartilhar Localização em Tempo Real... + Compartilhar em Tempo Real... Parar Compartilhamento Tem certeza de que deseja parar de compartilhar sua localização com %1$s? Tem certeza de que deseja parar de compartilhar sua localização com %1$s? Tem certeza de que deseja parar de compartilhar sua localização em tempo real? - Atualizada em tempo real enquanto você se move + Atualizada enquanto você se move Enviar localização selecionada Localização Local Precisão de %1$s + %1$s de distância Ou escolha um lugar Deslize para ver lugares próximos Localizações em Tempo Real @@ -1823,6 +1886,23 @@ Escolha por quanto tempo %1$s verá sua localização precisa. Escolha por quanto tempo as pessoas nesse chat irão ver a sua localização em tempo real. Seu GPS parece estar desativado, ative-o para acessar as funções de localização. + Pessoas Próximas + Adicionar Pessoas Próximas + Adicione rapidamente pessoas próximas que estejam visualizando esta seção e descubra grupos locais.\n\nPor favor, ative o acesso à localização para ativar esse recurso. + Pessoas próximas + Permitir Acesso + Adicione rapidamente pessoas próximas que estejam visualizando esta seção e descubra grupos locais.\n\nPor favor, ative os serviços de localização para ativar este recurso. + Ativar + Grupos próximos + Peça ao seu amigo por perto para abrir esta página para trocar números de telefone. + Buscando por pessoas nas proximidades... + Criar um Grupo Local + Iniciar Grupo + Qualquer pessoa próxima a este local (vizinhos, colegas de trabalho ou de classe, participantes do evento, visitantes de um local) verá seu grupo na seção Pessoas Próximas. + Se você iniciar um grupo não relacionado com este local, poderá ficar restrito e não poderá criar novos grupos baseados em localização. + Definir Localização + Definir esta localização + As pessoas poderão encontrar seu grupo na seção \"Pessoas Próximas\". Mostrar todas as mídias Mostrar todos os arquivos @@ -1869,8 +1949,8 @@ Desativado Linear Radial - Você tem certeza de que deseja apagar esta foto? - Você tem certeza de que deseja apagar este vídeo? + Tem certeza de que deseja apagar esta foto? + Tem certeza de que deseja apagar este vídeo? Tem certeza de que deseja apagar esse GIF? Descartar mudanças? Limpar histórico de busca? @@ -1899,6 +1979,7 @@ "Você pode configurar uma senha que será requisitada quando você entrar em um novo aparelho, além do código que você receberá por SMS. " Sua senha Insira sua senha + Por favor, digite sua senha para finalizar a transferência. Insira uma senha Digite a sua nova senha Digite a sua senha novamente @@ -1911,7 +1992,7 @@ Atenção É sério!\n\nSe você esquecer a sua senha, você perderá o acesso à sua conta do Telegram. Não há nenhuma forma de recuperá-la. Quase lá! - Por favor, verifique o seu email (não esqueça da pasta spam) para completar a configuração da verificação em duas etapas. + Por favor, verifique o seu email para completar a configuração da Verificação em Duas Etapas (não esqueça da pasta spam). Tudo certo! A sua senha para a verificação em duas etapas está ativada. Seu email de recuperação para a Verificação em Duas Etapas está ativo. @@ -1919,7 +2000,7 @@ Desativar senha Definir Email de Recuperação Alterar email de recuperação - Você tem certeza de que deseja desativar a sua senha? + Tem certeza de que deseja desativar a sua senha? Deseja mesmo desistir de configurar a verificação em duas etapas? Deseja mesmo desistir de configurar o email de recuperação? Atenção! Todos os dados salvos no seu Telegram Passport serão perdidos! @@ -1951,7 +2032,7 @@ Código Senha desativada Reenviar código - O código de verficação foi enviado para seu email. + O código de verificação foi enviado para o seu email. Você ativou a Verificação em Duas Etapas.\nToda vez que você entrar na sua conta em um novo aparelho, será preciso digitar a senha que você configurar aqui. Dados e Armazenamento @@ -1979,22 +2060,22 @@ Privacidade e Segurança Privacidade - Último Acesso + Último Acesso e Online Foto de Perfil Quem pode ver minha foto de perfil? Você pode restringir quem pode ver sua foto de perfil com precisão granular. - Essas configurações substituirão os valores acima. + Você pode adicionar usuários ou grupos inteiros como exceções que substituirão as configurações acima. Número de Telefone Quem pode ver meu número? Usuários que já tiverem o seu número salvo nos contatos também irão ver no Telegram. - Você pode adicionar um usuário ou um grupo inteiro como uma exceção que substituirá as configurações acima. + Você pode adicionar usuários ou grupos inteiros como exceções que substituirão as configurações acima. Mensagens Encaminhadas Link para sua conta É um link se permitido pelas configurações abaixo Não é um link para sua conta Quem pode adicionar um link para minha conta ao encaminhar minhas mensagens? Quando encaminhadas para outros chats, as mensagens enviadas não terão um link para sua conta. - Essas configurações substituirão os valores acima. + Você pode adicionar usuários ou grupos inteiros como exceções que substituirão as configurações acima. Reinhardt, precisamos encontrar músicas novas. Ponta a Ponta Ponta a Ponta em Chamadas @@ -2029,11 +2110,12 @@ Se você não acessar sua conta ao menos uma vez nesse período, sua conta será apagada junto com todas as suas mensagens e contatos. Quem pode ver o seu Último Acesso? Definir exceções - Importante: você não poderá ver quando foi o Último Acesso das pessoas com quem você não compartilha o seu Último Acesso. Em vez disso, você verá o último acesso aproximado (recentemente; há uma semana; há um mês). + Adicionar às exceções + Você não poderá ver o Último Acesso e status online das pessoas com quem você não compartilha o seu. Em vez disso, você verá o último acesso aproximado (recentemente; há uma semana; há um mês). Você alterou algumas configurações de privacidade. Aplicar mudanças? Sempre Mostrar Para Nunca Mostrar Para - Essas configurações irão sobrepor os valores acima. + Você pode adicionar usuários ou grupos inteiros como exceções que substituirão as configurações acima. Sempre Mostrar Nunca Mostrar Adicionar Usuários @@ -2056,6 +2138,7 @@ Isso apagará todos os dados sobre as pessoas com quem você conversa frequentemente, e também os bots inline que você provavelmente usaria. Adicionar Usuários ou Grupos Exceções + Ninguém Enviando vídeo... Enviando GIF... @@ -2186,13 +2269,13 @@ Código inválido Desculpe, você apagou e recriou sua conta muitas vezes recentemente. Aguarde alguns dias antes de tentar novamente. Nome inválido - Sobrenome inválido + Desculpe, este sobrenome não pode ser usado Carregando... Você não possui um reprodutor de vídeo, instale um para continuar - Por favor, envie um email para sms@stel.com e conte-nos sobre seu problema. + Por favor, envie um email descrevendo o problema para sms@stel.com. Você não possui um aplicativo que suporte o tipo de arquivo \'%1$s\', por favor instale um para continuar Esse usuário ainda não tem o Telegram, deseja enviar um convite? - Você tem certeza? + Tem certeza? Adicionar %1$s ao chat %2$s? Número de mensagens antigas para encaminhar: Adicionar %1$s ao grupo? @@ -2205,16 +2288,16 @@ Enviar mensagens para %1$s? Compartilhar jogo com %1$s? Enviar contato para %1$s? - Você tem certeza de que desejar sair?\n\nNote que você pode usar o Telegram em todos os seus dispositivos ao mesmo tempo.\n\nLembre-se, sair apaga todos os seus Chats Secretos. + Tem certeza de que desejar sair?\n\nNote que você pode usar o Telegram em todos os seus dispositivos ao mesmo tempo.\n\nLembre-se, sair apaga todos os seus Chats Secretos. Apagar %1$s Limpar %1$s Limpar cache para %1$s Tem certeza de que deseja apagar os chats selecionados? Tem certeza de que deseja limpar o histórico dos chats selecionados? Apagar todo os textos e mídias armazenadas em cache dos chats selecionados? - Você tem certeza de que deseja apagar e sair do grupo? + Tem certeza de que deseja apagar e sair do grupo? Tem certeza de que deseja apagar e sair do grupo **%1$s**? - Você tem certeza de que deseja apagar esse chat? + Tem certeza de que deseja apagar esse chat? Tem certeza de que deseja apagar o chat com **%1$s**? Tem certeza que deseja apagar as **Mensagens Salvas**? Tem certeza de que deseja apagar o histórico do chat secreto com **%1$s**? @@ -2230,16 +2313,16 @@ Tem certeza de que deseja compartilhar seu número de telefone %1$s com **%2$s**? Tem certeza de que deseja compartilhar seu número de telefone? Deseja mesmo bloquear **%1$s**? - Você tem certeza de que deseja desbloquear este contato? - Você tem certeza de que deseja apagar este contato? + Tem certeza de que deseja desbloquear este contato? + Tem certeza de que deseja apagar este contato? Chat secreto - Você tem certeza de que deseja iniciar um chat secreto? - Você tem certeza de que deseja cancelar o registro? - Deseja interromper o processo de verificação do número de telefone? + Tem certeza de que deseja iniciar um chat secreto? + Tem certeza de que deseja cancelar o registro? + Deseja interromper o processo de verificação? Tem certeza de que deseja limpar o histórico do chat com **%1$s**? Tem certeza de que deseja limpar o histórico do chat secreto com **%1$s**? Tem certeza de que deseja limpar o histórico do chat em **%1$s**? - Você tem certeza de que deseja limpar o histórico? + Tem certeza de que deseja apagar todas as mensagens neste chat? Tem certeza que deseja limpar as **Mensagens Salvas**? Apagar todos os textos e mídias em cache desse canal? Apagar todo o texto e mídia em cache desse grupo? @@ -2287,13 +2370,13 @@ Atenção, isso apagará irreversivelmente sua conta do Telegram, juntamente com todos os dados armazenados na nuvem do Telegram.\n\nImportante: em vez de perder tudo, você pode Cancelar agora e exportar seus dados antes de apagar a conta. Para fazer isso, abra o Telegram Desktop atualizado e vá em Configurações > Exportar Dados do Telegram. Para que você se conecte aos seus amigos em todos os dispositivos, seus contatos serão sincronizados continuamente com os servidores fortemente criptografados do Telegram. - Telegram precisa acessar seu armazenamento para que você possa enviar e salvar fotos, vídeos, músicas e outras mídias. + O Telegram precisa de acesso ao armazenamento para que você possa enviar e salvar fotos, vídeos, músicas e outras mídias. Telegram precisa acessar seu microfone para que você possa enviar mensagens de voz. Telegram precisa acessar seu microfone para que você possa gravar vídeos. - Telegram precisa acessar sua câmera para que você possa capturar fotos e vídeos. + O Telegram precisa de acesso à câmera para que você possa tirar fotos e vídeos. Por favor, ative a permissão nas Configurações. Telegram precisa acessar sua localização para que você possa compartilhar com seus amigos. O Telegram precisa acessar sua localização - O Telegram precisa de acesso para aparecer sobre outros apps a fim de reproduzir vídeos no modo PiP. + Para reproduzir vídeos no modo PiP, o Telegram precisa de acesso para aparecer sobre outros apps. CONFIGURAÇÕES Por favor, permita que o Telegram seja mostrado na tela de bloqueio para que as chamadas possam funcionar corretamente. @@ -2363,7 +2446,7 @@ Ligar de Volta Ligar Novamente Padrão - Você tem certeza de que deseja apagar essa entrada do registro de chamadas? + Tem certeza de que deseja apagar essa entrada do registro de chamadas? Chamada do Telegram Auricular Alto-falante diff --git a/TMessagesProj/src/main/res/values/ids.xml b/TMessagesProj/src/main/res/values/ids.xml index 870eed74e..1a03de773 100644 --- a/TMessagesProj/src/main/res/values/ids.xml +++ b/TMessagesProj/src/main/res/values/ids.xml @@ -4,6 +4,8 @@ + + diff --git a/TMessagesProj/src/main/res/values/strings.xml b/TMessagesProj/src/main/res/values/strings.xml index de2da5449..b43e7c4f7 100644 --- a/TMessagesProj/src/main/res/values/strings.xml +++ b/TMessagesProj/src/main/res/values/strings.xml @@ -202,6 +202,8 @@ Bold Italic Mono + Strike + 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 @@ -231,6 +233,9 @@ Sorry, this group is full. Sorry, this user decided to leave this group, so you cannot add them back here. Sorry, too many administrators in this group. + Sorry, the target user has too many public groups or channels already. Please ask them to make one of their existing groups or channels private first. + Sorry, the target user has too many location-based groups already. Please ask them to delete one of their existing ones first. + Sorry, you have too many location-based groups already. Please delete one of your existing ones first. Sorry, too many bots in this group. un1 pinned \"%1$s\" un1 pinned a message @@ -266,6 +271,7 @@ You have changed some settings in this channel. Apply changes? Public Channel Public Group + Location-based Group Public channels can be found in search, anyone can join them. Public groups can be found in search, chat history is available to everyone and anyone can join. Private Channel @@ -275,6 +281,7 @@ Permanent link Invite Link Add members + Add Members Leave Channel Leave channel Settings @@ -283,7 +290,7 @@ Silent Broadcast What is a Channel? Channels are a tool for broadcasting your messages to unlimited audiences. - CREATE CHANNEL + Create Channel Sorry, this name is already taken. Sorry, this name is invalid. Channel names must have at least 5 characters. @@ -325,6 +332,7 @@ Channel name changed to un2 Sorry, you have reserved too many public usernames. You can revoke the link from one of your older groups or channels, or create a private entity instead. Creator + Administrator Admin MUTE UNMUTE @@ -348,6 +356,7 @@ Sorry, too many admins in this channel. Sorry, too many bots in this channel. Sorry, you can only add the first 200 members to a channel. Note that an unlimited number of people may join via the channel\'s link. + Sorry, you are a member of too many groups and channels. Please leave some before creating a new one. un1 added you to this channel You joined this channel You joined this group @@ -388,6 +397,19 @@ Delete Messages Add New Admins Dismiss Admin + Transfer Group Ownership + Transfer Channel Ownership + Security Check + You can transfer this group to **%1$s** only if you have: + You can transfer this channel to **%1$s** only if you have: + Enabled **2-Step Verification** more than **7 days** ago. + Logged in on this device more than **24 hours** ago. + Please come back later. + Set Password + This will transfer the **full owner** rights for **%1$s** to **%2$s**. + Change Owner + **%1$s** is now the owner of the group. + **%1$s** is now the owner of the channel. Ban Users Add Users Invite Users via Link @@ -423,6 +445,7 @@ Ban and remove from group Apply Changes? You have changed this user\'s rights in **%1$s**. Apply changes? + Custom Manage Group Manage Channel Manage group @@ -445,6 +468,8 @@ Public Private Public + Link + Tap to add a permanent link Choose photo Take photo Upload from gallery @@ -543,6 +568,9 @@ un1 unpinned message un1 stopped the poll: un1 deleted this message: + un1 changed group location to \"%1$s\" + un1 removed group location + transferred ownership to %1$s un1 changed the group sticker set un1 removed the group sticker set un1 made un2 the discussion group for this channel @@ -674,7 +702,8 @@ No recent Message Message - Share my contact + SHARE MY PHONE NUMBER + Share my phone Add to contacts %s invited you to join a secret chat. You have invited %s to join a secret chat. @@ -711,10 +740,19 @@ Allow **%1$s** to send me messages Cancel sending Allow %1$s to pass your Telegram name and id (not your phone number) to pages you open with this bot? + GROUP UNRELATED TO LOCATION? + Report unrelated group + Please let us know if this group is not related to this location:\n\n**%1$s** + Please let us know if this group is not related to this location. REPORT SPAM + Report spam + Block %1$s + BLOCK USER REPORT SPAM AND LEAVE ADD CONTACT + ADD %1$s TO CONTACTS VIEW CONTACT + Do you want to block **%1$s** from messaging and calling you on Telegram? Are you sure you want to report spam from this user? Are you sure you want to report spam from this group? Are you sure you want to report spam from this channel? @@ -821,6 +859,7 @@ Install Google Maps? SCAM via + Message doesn\'t exist %1$s set the self-destruct timer to %2$s You set the self-destruct timer to %1$s @@ -956,6 +995,8 @@ Telegram has detected many unsynced contacts, would you like to sync them now? Choose \'OK\' if you\'re using your own device, SIM card and Google account. Sorted by name Sorted by last seen time + Add %1$s + Phone number Add people... You will be able to add more users after you finish creating the group and convert it to a supergroup. @@ -1025,6 +1066,7 @@ Birthday Title Create New Contact + New Contact Add to Existing Contact Bio Add a few words about yourself @@ -1044,6 +1086,11 @@ https://telegram.org/faq#secret-chats Unknown Unknown + Mobile hidden + Phone number will be visible once %1$s adds you as a contact. + When you tap **DONE**, your phone number will become visible to %1$s. + Share my phone number with %1$s + %1$s is now in your contact list. Info Phone Shared content @@ -1056,7 +1103,7 @@ Sorry, this username is already taken. Sorry, this username is invalid. A username must have at least 5 characters. - The username must not exceed 32 characters. + The username mustto not exceed 32 characters. Sorry, a username can\'t start with a number. You can choose a username on **Telegram**. If you do, other people will be able to find you by this username and contact you without knowing your phone number.\n\nYou can use **a–z**, **0–9** and underscores. Minimum length is **5** characters. This link opens a chat with you:\n%1$s @@ -1214,6 +1261,9 @@ Turn Off Blocked Users Blocked users will not be able to contact you and will not see your Last Seen time. + Block User + CHATS + CONTACTS Log out No sound Default @@ -1221,6 +1271,7 @@ Only if silent Blurred Motion + Change Chat Background Chat Background Reset Chat Backgrounds Remove all uploaded chat backgrounds and restore the pre-installed ones. @@ -1379,7 +1430,8 @@ Repeat Notifications You can change your Telegram number here. Your account and all your cloud data — messages, media, contacts, etc. will be moved to the new number.\n\n**Important:** all your Telegram contacts will get your **new number** added to their address book, provided they had your old number and you haven\'t blocked them in Telegram. All your Telegram contacts will get your new number added to their address book, provided they had your old number and you haven\'t blocked them in Telegram. - CHANGE NUMBER + Change Number + Change number New number We will send an SMS with a confirmation code to your new number. The number %1$s is already connected to a Telegram account. Please delete that account before migrating to the new number. @@ -1395,6 +1447,9 @@ Smart Notifications Exceptions Add an Exception + Delete All Exceptions + Delete all exceptions + Are you sure you want to delete all exceptions? New Exception This section will list all chats with non-default notification settings.\n\nYou can customize notifications for a chat by opening its profile and choosing \'Notifications\'. None @@ -1503,6 +1558,11 @@ No results found No recent searches FAQ + Distance Units + Distance units + Automatic + Kilometers + Miles Local Database Clear cached text messages? @@ -1794,8 +1854,10 @@ Map Satellite Hybrid - m away - km away + %1$s m away + %1$s km away + %1$s ft away + %1$s mi away Send my current location Share My Live Location for... Stop Sharing Location @@ -1807,6 +1869,7 @@ Location Place Accurate to %1$s + %1$s away Or choose a place Pull up to see places nearby Live locations @@ -1823,6 +1886,23 @@ Choose for how long %1$s will see your accurate location. Choose for how long people in this chat will see your live location. Your GPS seems to be disabled, please enable it to access location-based features. + People Nearby + Add People Nearby + Use this section to quickly exchange phone numbers with people around you.\n\nPlease switch on location access to enable this feature. + People nearby + Allow Access + Use this section to quickly exchange phone numbers with people around you.\n\nPlease turn location services on to enable this feature. + Turn On + Groups nearby + Ask your friend nearby to open this page to exchange phone numbers. + Looking for users around you... + Create a Local Group + Start Group + Anyone close to this location (neighbors, co-workers, fellow students, event attendees, visitors of a venue) will be able to see your group in the People Nearby section. + If you start an unrelated group at this location, you may lose the ability to create location-based groups. + Set Location + Set this location + People will be able to find your group in the People Nearby section. Show all media Show all files @@ -1899,6 +1979,7 @@ You can set a password that will be required when you log in on a new device in addition to the code you get in the SMS. Your Password Enter your password + Please enter your password to complete the transfer. Enter a password Enter your new password Re-enter your password @@ -2029,6 +2110,7 @@ If you do not come online at least once within this period, your account will be deleted along with all messages and contacts. Who can see your Last Seen time? Add exceptions + Add to exceptions Important: you won\'t be able to see Last Seen times for people with whom you don\'t share your Last Seen time. Approximate last seen will be shown instead (recently, within a week, within a month). You have changed some privacy settings. Apply changes? Always Share With @@ -2056,6 +2138,7 @@ This will delete all data about the people you message frequently as well as the inline bots you are likely to use. Add Users or Groups Exceptions + None Sending video... Sending GIF... diff --git a/build.gradle b/build.gradle index a955ca89b..e05d52f9a 100644 --- a/build.gradle +++ b/build.gradle @@ -6,10 +6,10 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.0' - classpath 'com.google.gms:google-services:4.2.0' + classpath 'com.android.tools.build:gradle:3.4.1' + classpath 'com.google.gms:google-services:4.3.0' } } repositories { google() -} \ No newline at end of file +}

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 a6!`!4-*kqDEsh#+XE7A(s3t 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 m4DQ97r8AZwv2Wp~wjA!LrAm z>Zyo%GKdJG9`daRK_~;GDDjK%t&%{E!d@yh(;7=ii6$ybdx^W? z&t7}&wbp;_eb2|<=QbJx8LYry1$wyx&6y%^wOX^_x*D{=M6=oaYbLm-Wr9rR5{kbQ zJOu6sOTkq@cF6^B2AlxFs{ zat+ln(aM)7g4Vgg|29?`!iCa6O=@F7|c7%KLFcC8kJPe)< zkUlSG>|T7WY|ax`$_JMHhUstgRauD`UkLOH_yc&6O=AnYqNQVSEG}H~wj=d`7zDI$(lT!-6b-I~F&;)|RF}$bh~6-%E3(0G%54fIRO*`RoZX zX(=t2YEUh?q{syQDu0y0@ET)_&G;nx7BCthUA8FlRZN~CdHNmqs%$`GQ52K7W5KScx4jRywF$c@MSem1i)0;G$KbS^_;EJOU-&wL zZQR%uqu36?SBK*XGhk~m=1#T91TQn;=Z)@F|7*SL9ZcVk`B>xHu$;CQ>(ikZnP5EX zD2a|cX`0L*Q~E_FXbYXoJu!buri-1k7uCib{Fa#h z6g7;^F-9BPHV3Y`Z9}7BYcb|dwa5g2aN%i%`}-|xEBBbo>0{%t=km2gzw#}sxDi-M z+%n;i^b}p~nekf<@;*hMw#4WgeZIbw7vzb3y|(l@Y^%;?Zn?o z?x$#4&O|#5M+}P&xQU-5urC7N8{clKd>18=V%KL4aYZ?jqCqi`nM|%`&zIyB>9)Ov4m^lGKO|{RyKTens^iV%TeL7X{_66 zVz-=)tjXf^V|K^k#}fMx9b({?FsRS)Bdq?>MCa)A-`Q^+-DSaajP_&V&V_zr1D8I7 zd<9~ihl#m0(YY=oq?^oYO5IvlZl6cF9oLiIr}jRCv%!QR)}?lR-9*X>?4>Qv*>&?I z=7VS)SlN;2Siwm09hLyyZpu64P^HJmHBE++_|azz}Z zU-Y%pQI2we&s^@rT~kMJorX}zVOPj=RXNC~FK?tE!Lbk%n>T#^ zp7*ItJDfwY!qD!cO}!r((<{MmruEdcpD}tqw{`&(b|bK7H@dA?+YU})>=ii}(FuQr zbL+1z1wLX5j_M{~B?C{yLDN3!{ra*`!7rJjrRtxgdt$NZI#=3_G~L$AalG$aifw}T zRke{b=r@4tz)!%+hje94rH(bgBAFnufRVr$qRex Sj_U&e0000`FQ-+_CF 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 Ef(;9PW&i*H literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/phone_change.png b/TMessagesProj/src/main/res/drawable-xhdpi/phone_change.png deleted file mode 100755 index 9fe6bd8df16a204c5c5df97f76829ab905ee42c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2409 zcmeHI`#aN%AD_!i8{!-`VazR>5i2K`a}ZX@t+`E;#*CPd%usTTMsv%MPSMHTI6fSg z?R1dKFmw^?LXqQER!VM(eAgfF{SVIjhu8CZUeD`!JY~g9r!+hPr!kGD)bXdSEMioC$|%7J$%mjXk#zXV617}v;_hZJ#SKq_x(Kfe8Cx1ttb z=w14{Y<%`Y$@)Ik>R-anz1Utm$YtLKfu!sm?T>rLiA_E6ibaEDB%h$t5jbb58? zp6NvAMeJe)=Tyf=tviNg!gZPmtea4E$!T2gl?vNqgKSD0&UNkORRq=VBFObSu(6uMk!vTDosbwZ(gouG<|2|l-4#-rzI-^M=>N5Or_TM`uV0~VJA7waqN*E$41OuezSPED2W~-l8pG!CQ*{4Q8eW>#3N#PO;UgKT=RLz~E-9&UCgZ6&{7+IREHr}zA_R@ZGB zY7h(6owkg*1XM8Sb~0+!WsC9YkhZ#X5;<{!V$^FO=t zVG4Xn)m*XrvriTM&1CfLK9AsFUexTk>sgN$S;d|azu=5s?2)9d0y(u+t0BSQvnA{C zgC@dAUPr?ASD-+l)=!11pv5{xt~AT%R{|)dA2=reLstGFGi@uPL)mLGI#K;5g4q#v zxR0E@{s8L^i(>{SG(qxtx;gV3b2+7*2(7>9+ckc%&hnXJ#_+$iZIb|N`=Ss7KEWEKzMozul zn6LbaENU(8h1>IDTp#ygt`~%@+D3T!7S%?;kSHyCNIG3@<%{tYYR&jTJoQ~<7k&4$ zUbC=vJCHd$U`t{)_j7e}XrbkLTUGvUpENflHB!aC8LX_ld=@bcTu$x!dt{?~g+YGZ z(vR%}WLAlpJ1ddM)q9cVn?BQo#rRM1hfL77C64t2&O}T(`RQT1CBgk`xq8+^Ht;)H zaWh&3`F6s;`PwH;;{a$}t&~ZTl43PJtN+{@!T%!K0^S-ozliSyz6ZHLdI#&Ylry_>E<3vVQiwu@Q zXlCm&0?Xo^ajC}VR&J-QxmvKk3j!b_2dhAoEpNd}z)a|2f}hlmTR*JlmD*L05s2?R zRj4qAlUPyeHO-&2Nw;PRRjIls!+?*Xm2YxO4Ap@X3^KET>x@+gB0$UL$m~vMJW1?h zD2c+;E`kt7W7xr@cpak;ZCR`Ug`(K9_o#qth95e|?~%^i7{X)4sV5*ACpA>^usN$* zR=^c_lZw9;{Z)W@SywoysFgTcesf1h#-jDK1TKOBc?e;g7%y!*?ii0fd5=8Olh2eg4*-fz~^S3jwo zB8Os_mB7&=-WcK1oUfr2coY}P2`KrMy;)uk=r@EI+o=m=+@ClXdLMIk%cHQ2k%}=d z83ob)QYB~@6|Ent;uNL=QvP|KkZ5e+{vD#mH~ci~me9 zP2@F|bfyt@uJU5s3HZ$cH|1U{>*Y?F>1}A;T&hE~_gh)fLj*bETmsgQh diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/sim_new.png b/TMessagesProj/src/main/res/drawable-xhdpi/sim_new.png new file mode 100644 index 0000000000000000000000000000000000000000..088e1b3cc98d61948ea6dbf093c322ff4bb62e76 GIT binary patch literal 3976 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 WJGKfo=E5AUAtnZv`enL~5&r?!q7!HU literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/sim_old.png b/TMessagesProj/src/main/res/drawable-xhdpi/sim_old.png new file mode 100644 index 0000000000000000000000000000000000000000..b136bbd9a241eda666cabe2e705f7e1ee1d53f0f GIT binary patch literal 3106 zcmcJRc{r5q9>-^<2Q%5mPPP$a&GMFQWEsXXlqC__BU@Q2s(EQJ=t6`Nsl>GK<}I?9 zl%`i@GG(n8V=3E=@J763iE}@lbFS-L=l$yo)*XRBMu2e&njc)%&oAbJ8x(HsY>hzF<_T^b;X@z<*^ai>9u(x<&17ak zs9gJKzjIt?)U?}GyE@GM-hmUe+le+S*{Z!48z#a#|;* zflTsAbZ=VG-2dnefX6Qse% z0eysjkT4S6 zU_&p~5~=(aZu^PA##H4;#JJt?+RCjkABm13+ovKRb8$@@I==HvTzlW{Sg+#~A2-gP z8p2&_T8)Ior?o;(*V0-K^qjUUslv`2O@1%hH@!^b2SuSP)7+so*X@5+B_a7o!mf_8 zLn&tnO0d!l1(Y4SVe@`$T$eo8RwlX4sXW4@!B4@;b9Xuda>q!jbW1nbu9^637#PtI z>-t_I&xP^@v_PFz)!kkyWA2y4t4CVNa#@F1opZX;7T^5lj!Zr}WaZX%%n_E)z?tqz zE5dOU&*Zr{>?Lso`PI8kc|e%gM#6;1dtq8Ch~DEZT{9JMrmk{;o(L(t6;| z?A}uMo$VXsFN=J2WgWSqJ_)@*0VfR{a_#AZV|yFFWu1ThfEsvDK~X=@YgQ+0>Ot4c z(b(lvYtM?5OW0jzupstm^0}B9%z;MJ+l9%@B)t~Lut(HD?HhZx9z@HBj&P**SnFJi zWr=h-eIZaS?O%qk)rbJKf_6b_=6l1kdgyt}tHE}lbT7OftyXoBhW8UDW?aLJXAU_xzUAT82 zj7JC__kyjnFWT5Dv5>D~@^BsdAgZ$cc*53JB*S%OZ0qsIvv_E}X!%F4clIAIQ~*=5 z$gGPurKTFR2+xxHZx!WDDA4W7ZX`Byh7vj@FHUayw_3w?dhJBC2}zcG8$H$H*rdLK z(Ea|yH$<;sSVLa@tRVINY%y!`wa+dQkRdQyWAa4BZcF&n^dD#{lo3DgMjs1!uyANd5zuNL-S2fvy zDhG?tpX2xUoQSTXP#4b@lK$)+={Qx|dHXC^31J27=Z7loqU-$bFBQ&8u8kt`rQ#tR zHk%01ay>6CMIEYW`GiEGDYz73;yrh2PETN+Z<)n2EP6kJCgSJk4g#E$AkwM*c|5=? z1H$qcukc1_BEtj;4W9jx3Gn}4U#$rzP-54wFkILHY$UrT@0SlMuX|@-c(6j3lV0eA zhO>Tx+MycMm8MkpY}eL9rcdI|Kt0dwW|@{rgD6g^yg(AH{hGL}d*Q5qlJaz7kb}Ig zz~PQW%=!@$UGYPSn@YY7V_acL$7%kQ#Wr?PwXc2#t1x}t&rc@xlZu-rG7t5PkZS5R znh~IP<9^pJxZ)nrQ_PYj-zHh=9KC$9LEp{tE((dxNl7uGja2UGbbr{()N|0+Xc0dT z(;ajj%s1_u<~RPq7o9b$#ExNKc~Cv)#SPsiYV4Y^+A%A8`dx61cN4SdZ5(rOXlkKc z9wwiMeqZV1Jjz)4dJWMLaBpF*ztPG>RWZu)WU0l-;-G=s-4BnBG_|3u;-nm6#1_(s zpP{EWt^1v0hg3e^m-sc=@Z{FDmfzM&b-0{xrJ#=q{xS7uCRHI^Hy>&QdQz?K_5QSU zZ}?ekFWeY*WfIkBmf_ZNk6H>cWI6bkJg@^p8W}aGA|v{XOESS5SZxrW64byd>wz^8 zQcEF<+L8odkk4Rj#d%kEyG?!fXq)&BuH&`f=VWG&yG)FApL2)0myODXJ}yz3PmT#7 z(WdyCfykk_KR(X&x*i*EpNh|kB=wD7)DjjEM4~14<9g-dUle10wiz=dWOh?d2`=Fd zl@y{$>NRV4vv(!Xdjz5@XFl5;Z}8@sFD1QGwJOLJ5{3DFhefM)i*uC)+3&OlCKe^K zmR8))4amSv>man#`J~@fq;9)Lh^*f~7s^!Y-q+Vx#$0$K%P#|eKMn1~#3w9Mz!0lh zj6nIHIEe;(;Ta>LC!D`D;2sx*LdU;&rD6z%9Pswir5yLE??;8w1Ys@WQJ0`K{po4f ziT{{7660~;i~P|Wp@OXiZtF3HJ#cVIM_dkZCYRA~Tik-m-ncF%33oGGucU*^=J%iB zi$M6ONVE_LZ$>jXsBnl`Q5M#}MDgFMyKDtLu?OFq{4i{(XrCFBDL4n<%gQ1jR3Nxk zuofy?O$e<7%MTP_SX!VI0Q>!4 z$uWydw%Wt0M4Qt_T&jz7Ig{ z5xVRajNN-NB7KI}4KO0o?SK!n=jC?%zg|C#s?uop*Wu{ixE$Ij9hYmow3dTRRJh7vpO7}^GTlEw{$fbGdR2YTE&@wVUk@-YO!S~A{lL8#C8L+f#QEVJa1qrqAS0x&W|p-_JKn`!qLvfw%R5r^&eEQD@wT`G2v*!3f)gBCpjd$-#oZl>7MJ4gEv2}-dvSMncmG1)-}grj z=j8A_yE{8ObMKwmgeWOUp`#L_!ok6zL#4%4;NSoPRMa43Edevr*33Fd_@2cJ}3mThhaI^`*DN89So5-@7d#7t)L3sNo@ zr-&T?!sq(8K%g(JXe)fg95qrLKV1K5L(ak|aC60Hf!c)6e^-o^X_`EUes!eWTTm5O zVm$t*KMLTcE*6AbN#bjcE5uR`pS;DJOniP4YQe<8X;jsuzAJ)-1$${Lb}Qao8+Fk7 zI6jR>a$MBSt$WEs9_`7L$_K1cDM)aPAQJ^T96VTxop2$-oj4-DgoBgw5+yU_BQ_8S zFOr#qWg^OWr7$7cwENxp!F;ymGtam(cl^CL9zZlMd*8MC-r`T)ddfFKqP-)L@KM1+ zUc#j#?l>Z3fS(U&L=s4h!2t(%Gl+5`^Wn=DKfUgwm=RG!+?Eb9Jh_a{j(&~NuX8*h zfu+%j6Lz2cfG_G3xZu2E5D3|U8X%qgy>0Zu(9Z5IW7+%mS!ZXkthb5s73{(2(^Njo zxM`0|q7OubL|z#3Hk25mLd;%^3zTc1ct7q_&^*860?1(}> zdyHNRh;gM99edT*G~};Fnz%bi_456QfUOmRh0F~kjzIOmW~z5s@S(wD)SSSw>WDpc z5Q43h@0q}V5*e4tgFZjk^p>(vb>8jzM@fioaKD@mQ3(fOiCla(_APeD0to|k93R2Z z8i^{F!{(z0fy4YEe#o89Hx`^gM0|v3!!_lTlcg4FK=`Z5PAuhWkcar*fH-279{0Ja zR?wQ#GYAu4>2?}8%+c-zE^e#Kt7rT%@5g%G?M~fQ`$fny?M>g*GHT1;R+0?VOIuUT zgsYHJQH)qiJ%6dB}dH>nDd(;=ue8)$`g{!DxI)U1^eMc}?E<$bTE%D*gAkNFCy z`Am)WO+=~}3(z*Do;KBwf}?Ax*R5KEAupqF3-C@fszKym?kC+ZUibqpR2=k2Y+U&J zsij7lQ^AW~ChMQO6^n!Nvrx5FC9t$U_{|mEsa0D^I0hN){8s(d)>Vax zLN4xu57c#9v?J)z4mxlCqS9ArwAH97Y1-s*FhfAM=yt6c9sx!=8LCV)yG8SQU8N?M zO=$2Ti^~&kBL6a=yVg)!;X{>_@5Q(sNk&H;0Cu#nfO5zjtC&^YHx7l0d+_qCWmq2~ z2OMfGrZM!z|K5&WtRp!wSqC-e4X}tpf>q|p5Sw;y%lE?v60r+iW%hHs73XvXtAzJt z4~JQuwia5l4%iw27!z6K_`q~D#-Ec8L;y#+rqL_fB`uwwh2WI?@^^b(*`{MzQLwXt zT=@?EfpZ!^1~u8)^O&guOZ@a-j*e|N#?75fX_3x`X_YG--zQl zR$F$Q)(IFNB|(Lb1aJ8X2J4^9uj{l~y-&45UJotKT?KPH*JmR3DSN8mRvR8F+Z#=B zxYk?7r!>@L1Pb9-nybulg;G)dJF!a0BW&6pw;I94OsKz_N+fS|RB_h7H@Msq4X0hG zN^L}k`!t3FyuBFpjhNKWQ?2OoM{jpJkVGdZ^HES!=SPn3_D6)+ zUfJ*&)UW?i59}a__4lr2a-b&A(w&Z4vvH$E;)@5?G@uX?5oNUO1J&^r~Ef|or>qHnwF{@<&X1QjK$xiTHkq$ zGmWdbc4%!`Fnu^rtqwgjoZ#&vF(FQLxL$NtJX&hHAN2S4H!UkG%SluCiu=Sm=3BK8 zzju$qibXcy->t|gEFu19Z*PCKJ%EiTS_`g0;a%GwgSyh=PTs;_=B@F$-QBBt8|30k z0++idxq``7gltDa>(Sh{?c)pMkyF7>tU$t7NaXTi{x87=ivbW`OIgEa{x&A>P3;FJ zj)>}0ceA_Gm_V}CL@&I9+FYF*ip<8ccIT)x4mR(ZLL(>Bc;y3n`Ek22V&1Xat9fh1 zqve+8OX=@_f+HizLe9kVNG4wwWnvcwN`e&wN=={XJ~iC)97vo^6h;#cI|%0X@;frB z=<9uv7+2XZ9N)P6H5iNAZrCLbhD~Qg26)2#N%R&P+(lxSh3pb+r5ZcV>2|r+l7=DS zJtlPnTDiA;D`ar66MfSm&bVHw^ShtcviEqQ&VDb7TyU4}UP~OM`jONzGYs1O{qQ?~x0zx6Q>@_A&H65; zz)9;N9UYynMEIzoM_Y}he3%>h`G`-o5|YAJTR&rv-fg~^P1Y|&se=p-6n)h5p!Xeb zfZ1<-YrorL>DqE}RM_+3{r>!LotL#M=Z1m=ypbToFxU-ay-8`&>^5ykH1rrHuDp1- zJ=bAwx)`$*biaDQn8TMp>Hpa%pM_}oO#xR8q98)Bk&sF5q+0Bxs;3=jm|I^>8l20) zk=<6^fK5n5+bt$`(u>7$*G-S8o!Gr-6ZIDFH68TSg$knig*can1~O~~9chtzk9H7^ z*ms)6s!wS-URHr{a<^xB!Wu)(Mr}`KkU(;OjHF|$cvyo97d;jb2u6UpzA7>} zF0IX|_Cc+xtT;?6^|$83we5}PgoU1|ma>1epU-iPLhc~w#`Z3`;r(_cVaj;ibjf0a zv#wvmm{;(CnR>Q>Nxp4!CE-;ngU`3G-&a0aVW%?I!b#M!7iX8R3vAKQP>Wt)eB4>2 zBniCQ#mKpH46^;^2FVE9$W$)SaZa&yJ^JxZOK+e20ihtPFPVDK<$THCc~;>~d9S;5 z$)GVWr9sLsRf*}Axm!2vA6=ZIy!Q!H2uhm6dYua}d0QhC=lwf=>rEeFUUseB_ za^`dBIv6PM$1`e8S$BsK2zTaHm1K2OS5@Ww`f2XZC^g(xP3c;j!+@!<@y>~s-0aXL z7Uzwg-Z29{dm*l1--n^GSnXAElCvpws_W)(1}#=74J6cNQrVqOExY)as}#6 zJ=~qKb5LmDWx(t@VryQ`y(k0IIRc@H>5&JNx3SiNva!aBnHR@o3T#@)R{1@Z?`xS8 zrI=((RTLEoD)a?f!6EYynSttd;M^EZ=bhP-`L!FqoGx#=#KN&)UcbMxPOu?!-0#^o zPydxVmbzU@m4EoKKUM6GU zO;>Z2(ov)m=rRyVK11;)w{6oKlW~z5$DOwm+)=UJf8O=_cr1*-+bmv0+bqVl6`GH1@Rh?UmdnyiwXo=wztwojk-bPi2Ne@0l89 zeie~}Mb} zICyh&b1_JA&byPUx;Eabd0(7dpvB^~g|H1^VGf&=u`0@r9Z<<}-Rc;tzttlM}Cld+a!dOYsY+1yGzYNa}P*%CbLFdw&(U$9F=exQ-k^gEwG7c%p@(<})LlUg(V zowrH$58-_T;>er|)b4SmB5nITYdl`bwtT#gb1)>=^E4Nqx-qS6-fFeu%&&AaCCqg{ zZsJ~L_P!m+=);W3c-r}VGMi59Lh$%R%$HQOxtTFS$x^DQSbO}dFZr~lWvQ>YwO}5j z-TTqt-N&3ByGSQwvXZM#!AK1nrHN>2ZZO(ns8b(YkRi+raM&XjKfaHs(e?T)i|pH8 z&*zD7B^c}b_&n_)e=7sB0~=0@*ktbOIbs~nHu~o8uaBQOm@265h+4}JX+}~moOo=x zJ_ga;HKfF013cZGg7`}k(P{?qx2Qy}YA3RyugR8f-Y#i{zPEc&8|#aXUlFM4iu8Nh z9eER|5WqmN5)}sXP!Iqr9rtCVmcMqzq}PVpkm2)#TuSu_g0r>hJ5=DoGH%vR-Q#^J z5&ytM;+Ti&M(`QE+HZW1+s%X)-vJAQM|8)bCH9jIcir~c2QK`J+Xmmbs$huIwbjWG zk#TjQKA(Lv(Cg&F-G=QMa0LaTkTqOEPXwNf0eMR*MVY7%F>GQIeJ>F$0RsBNMz%IE z3=~u^=&)VvuhG@UbwC;`{eX=;_*1?BW7N)mSZkPK$`L{PPq%o~MNw_-O~HD1xI4O_ z*;lQGQ|Z4tg?_pZ>+CMO>z7JPzS~8&go^6%>T1)ic>IyATRGeM?NOgyT&x=ziB0=L zN^OqBrJ=zA;r9xrjyqzR-fSFaxyY5GREkh*tRWmq@-mopIJ!E;w6!n>9eIXHms<>~ zKm}k%~(pMX{ z0&r%BfKLfPaDheqy;+Bm~#>ZM{xI_M{^KLaZc&TD`U7W{+l zMB5LnUnQb3&`6Qm2r|uMX$GE_mG^?%S%T z6koR|*Gt}X(d)LF({nrab(rxVQ{vt6bFZCJ&!3E}lF)?ePtCwbVVOUEDkuQA%U5)_ zu8t!_X=yJF1~rmV;2>+DK2}7NBKVq!v|_IVoJZ*%!YAJz#Hr<^jkW0!$u|5IYrT`) zZ7fSXmh0>x{v=s;`=xzD!29Xp8ph1-D9qq2B0;=yT7c!J^Pn}=`7kU0?Q>Kaz1gW8 z7Gou{9m|ThQr*kv)aU^>pO6lCzB|cs=pNJbzgfitQEQfK;S8zxdn*~<_(7DAc|d>i zk@w7PxuIC_I>6t2)!s%3;wA0DIW(cv#*)^ z`!~ZFxG6n8-U+F{u6YTG#P+lzK{$w8-8xXRMk_&<;tngbBcz5*2z_4hZ5vEhuuND( zyzCdxkQ3Cuj4&vK_LQ(3OG);sTzHZiO8P+xu|U}$RS6gBtZx>}NkhhxmBJH{_lxv&T!wwyX+ZS?Tm>7E@N$e2yonXLrAJJ$SXU{tMB6U~hsPYyG=r zM=CL$r3r&PKen$8#mi!O{w;RAcK7>j+aptk)Gr&gLmK^F=8 zZ1}@ohWV&)j0KaD$d5`SUnAWkKe7kMcN01BeZ*^DbWR4Zn*rb4tJ4`mnqmm#`(D3@ z!&oH94qVD?IWrW{?9J+;KlNsUG2qZhu-mOCrmi177vi?@k^p|66b*m~MhL8hs(`=D zijlOl196@ndevpujTSv}7iG2u>`87a;ZeC-m;=x+TNy`SIGi zdK|1eHPz7~SO?@f7HofnG9cfG>n)EGyR1YWze+p*ic$6l-{tC_TSW6qY7`YaPYmA6 zk~2feM>NDkpx7NluKY3LY}=FSx+K>(>yahSb=n#W!Rzg3gg19&OF@r4_MgCIpb-iH z&G1&obcPF_0$#kKncn$7{A4_BM(;o`;2nB1G4%Y#sf_b;Ll%m5LjzwY3*OR-LvI@`4`seUU2;=}jfXe`v};dO@wX**G^Cq+i7h z=ZV?IV^(*itUA+}J}1CSMzJ#MkB>M4jhic_(#-k2t* zdh_1i`#+OFu!0$Ye917!`Vu!GkawGHYpM=~_K}UQUvVz@7`S0S?%Yn=tlSXb!fXr- zhcg9}-43wLFWRh+e7hb#Y8;&BHjQjdrW#|3-nYUr6;X7tYZTvR{2~26Cp*L1k{9BxYn18{HX#wQ(qfP3INd8(bzCJP@DRj4E=kSoA7wEJbzkYIXpOMl9 zGrVX7oU2no1z@Nx0E6!N;&A`8L&iyFXE7yysnHO6@MF*f`uW9xzP(bJO(8Kd-;o*r zveSJb*yOZS5fN#lSXkwXN1Z4YF2MJ?o_o*RAdet1cKE%O{n!(JA9G9dXtcKZA zVVNjq8*(G)8ipLijM2y^UkGun@wwgWHlFcr(Ns$vQZ9vUgN-R64nnDo?y|>WIuG zGl-Yy;om>T_(6IoPmYooS*mprn3X!K@p)Wt5x@XFk*(5i!S+bLS$cwAmGeYs zkvJh83M?4>s|H0v+!>l?o8VyxV-*H`Cs{5ME-2YQa$bg?+;Rr=J`^|57;-)p0VNZ8gDP%5cupg*5a84vJC^Iyy~oeOG$hm7E;qVc2E zLMr7XBMeQM|C8~7z9$l(3GqIw{A$=6`69@Ky)c>T(Al}&ya(uJ+)m;&7+gg%$FX@l z?uZ*7X2}P5%Evye6xsKfZuEA9H68>bzd>MRW1}8@@0uC?FEwHze+7y{;|4X!IjyN> zBZ5fXXiZmIeV@M~7L+#PK3(=HRD3$~gUopDtW>ejfjpRtgMfP{pLC)-r~ z4C^A+XD8?U+h&?n-=?jEWo@!m%UfyxTWC9~uM##DwdCH$$1l10X&K&6w=)wH6DgDZ zaZ7fUaMfgdmjB+y4~*E@=r~LJ%CMfy@|!t~EcNimxC#+-)6W4bv$?T=7i#`e|7b^k zd7q<<@tC-4J*I910lRTyh^ttWKNAtQ`bpHGXynpEtP|D^^H4webM zGTR%<@13miy;wIo2^>xOfi_=K$E_kU8;G;SK+pNtI;XO zCYn@3rCJt081Vl$9cf5rF>E?HGRcF6eIZA1IOF5v$I3)XZbQEIrPnta|7Oz(vUy*E zB?MX4-D0s{(32K(hS!7y;LKBm6vq(I{SRL>BUc8{{_G6?+6xRhOPyLprUZK@7=HPPaNB zT(V{$tvsZXq_xTSbVjfgpI-l6+mhkGSpj`vy!TVK;S}gpJdwfnfLPeeL+<)$v1Q;L z&y$BP(*~Z-%5T`I?u4CcWrSmEA=9KD&T%P`)PehgeA6ESozj&uxP|`<5i{gi49aYZ z{vp_Xyv1EG72?KOV2-?PVWrqHTb|8;;|7Us^>{GBD@saQd_9pT<=sq0~NRwooc&WN`t2MBclT(<|Zu z0jp#|Qf<~HVUi6ft?O#xfcXgI|D z1xku`>CTneZT7D+B@`79G*35US;i2&>60P@gtk`9>KU6R#jI-<0&4_jCE-TN)}{U^c1myZPdbYr32}F*LV>D}Q4xD&0ST zY*KJiCTK(g%f!OsX3mIvL7%s~C{#{3yI__uyL0)7Lto*8MV-^(oFf9EElqOttiwvB z2G~^uWQILsd%-Y~62U2=PQYTlre}FOq*r<d3}uUXS6Nh?B{OPBcE?Uh4G%|1m1K=L_q!h86Spb47&o z6LFAIb}kM@GRme#w<)97Ss%@DyAiioi^H$GBZDd3sa2GaFc~al59TvpO`MX?<<%oxFdY5#YR$xvbzln4rr6hbu>vADQ#38s6)6k5F(q8l`3) zUA87Iu)&x+GU&*As8ac4Z%^lL7+v!VB|t)Q5o4@(TsNNEhF}}E%`YGh&atoQ%p7B| zE9rOG!>sr<+MH-yT!~#|;4Vxqm5ucG2d)#rpWaUx-)2?llkl^=4!k%(oL!6|r)R{L zp*LTZQ|8@Rb3F5)dz&y_$$Zir9I0Cg!#_V*Fvlgu%X?H| z*MU}E`&Mj+u6Ewq;ismV6_)GW(Yjde;tr|xiFk8eB!~@IBVNM?3e$+W|5GnJDMx75 zzK$SJ>y=WSvFhqBPk^^Wllkf$_l-}}DlP6!O&rddjmeyM;A~|?86K_$?N^rZ1Ty?Tl*IM{KXq9|#1S-j62 zXiDjJUrZC=2a!cS;MITR-$_bB3^^^Fm_){j1}_WKCW==%kkS2PlrWUa2q@NY`_P9S z-kvG%c33sdR5O2wi-EM_b=*xiQqW*Kn^XE5RcOWq7?G_rdTNj7BdvTq__o$;Ot04B z0q#}*@_qlunFA(rZ=-504oh zb!MgKalLPo`OP~Pzg#)jTqPI(_rELyBfEA$O$wQ%jN15nHj!)qV|c^jR-(D3kcEyF z%e3jRz?@Y_lX@8ULUwL8qSsv{v(Fb@{qZdA8^dX+7;AKKXYCa23fd4AC35Q(h z**7;jdMj!b!+z8k{o@7gl0-sSti7le<>HCCms|`yEG&L^!R@r}o|6buZbld&UigH? z0VM#X4g<=CuOS0f7W=lfU)n)UmT2XFV1?G!_ztyVpU-_MAW=>{&)X5)%T-S{kPsK5 z0$yC(&vT;_a;EDaYLm$+Gg~MtqpwE7;p;*8b3`Sd3FdHC>taxQ>Hqkr^_=T3KCxF@ zYPjs@QF2}*rRp}FgNX+k;GiZ%_PdE*clhHmYvf2h((4a4)5_BOjDE~~pWW(K5xO{}9giq&LV>3%N$H5lNhEbY z1-eIsIaMi5+A+{A>aMhpm)$(o$zcma(8Fuh>dC?kvSq%-Z1jpEfl6n@hiRP(juiSa z#HzQ!J09zMAyF$|38K;F`+Od0V@lZrHoY3qjJ$QF{2a~)qrHx$0v<<^rpQQ>krcfv zhvEmTW<+SzGFx7=D<|{0`2^9r_H-pv4$0O8aq5RWZBK>dBxwCp#6~V*$4U8%!wykO zOI4MFon3jCkm>bLia*Pyw$x_i6H1@v>UGSuEG=Xvo(4@PL#SNugp#$;Or^t2b{Qmj zpP0D1QJi&zFrI<~*S8?ag}Te^^GX?ese{Y`LqYbO86Ve2if&vctZYHWc+n+fLM?;| z?-m)V!)e|J9vamGr2`K+x|BniRxmnMlPy2p*YTn-=t7|Pzk0!COpqWyK~P1~Se_4g6Z?lhDpDS4k$ z>6GO;hE;`=7nypfoh{DnITlvv`?>azq#7sBmAxkGM39u#F9Q&cbQn}72?4y(WL#=%g zLA!vzzjIiB$p(%~U5)mB5F|A_V6LocvCuwi@P3jA)WY(m3}3n(+xjYbey<4jw{VX4 zpKG2s4);q?JLIZF-yRGR*k6`-&%}Opn3RW2Rq&<{2ZxG6j!%Q49V||lN>&tB9161O z-#q>?D*@T@8@Es+ze#VDi73Q<11|20RTN4J(A^1C#AGDdz~y*@u&h&~i_#)D$^W*d zf-Lv-56+Vr;b}9kq>1~jm9t6uj00r}=|JK+UVB7xW67(1D=g3Q{kQ$0Q@F-F|-FRp59QN|G`M?pe`%n@3^k{s<_4yDlqNq{(p_hWYg?0;$~ z`G0RG9CM(p-~ANxJ*cCgdaFYl^xi=#3$dM-bLsqR_Yr)dwtUeg72^v|g|#HuBI6@R zWXu!xM%ScMH=Om;Gqcw4>wF49?C4JLty6H{HqcSSm>HRAzAv68<9S`z<R3s_;pgac5 z&n#nH1y-YK#*K|;Ohn%+r>JO*dvM-pT>TOuBIN|%uu`b+-^$=65OIF|INW$YsIJBD z3VOuu%ObHfoAI^6dtGfT}EDL08Wxh6skT^RfJ z|C+$uD_;nwjMMd$^oK8B*1ERZ*G*g&X50Q2&RSGVL>pAPkCrWll-Q0O)eSHcB2(TO zn>9!64AzImiQ9C!xcqt@?kz?nT5M}x*Fw4&!|);VRmgTu3Y>0RBeBWfdow(yvo|}K z{lsjs@7(#AdDr-|CBA^cFq8>Xx7V>@p=PW~nnzBMgp5phP~d<#1@GU^$&QfrJ^1y> zS$@tpJHD^U^BzN66msXl(wA;q|B%h=z|9NHtHa^7kPqevA0u%1ppS8pDrl%V3Chx? zD#5O)YuLYpua^IUPZr!yQofe#zJv%^3y`dP(~1?`%qb0?(k&)h~PkeWOL@tM~g+%t~ZMqeYexf2r`6;sgv zLK|>~$O}pb%~Qq&2FijxD}#u^GFwscD`TZ6Gw+jRjkZ(XVMqTL9G;LX18K2EzQp9_ z!NeOzB>OZ*tE=3Fr;clHq%q>OUhew$iSDCtAjR1yzt`viA4{e`CLH)-%-;~4O)HuY z++CMO3>*M_?!iGnwk$8TaiOfYA#C?C6ea77b$s}VX^0)C(y`L$u2fSkv6bRvsGn1G zY@+$B32OcsJUS3!9vow2<>CO7PGmo zD^Gzq^~&#@MVF!~aDa(FmW& zj8d=ToR;utT8$~`{$G0!Tt_?YuUwFK%V8x&K7l>ISC(D*2?yj1{J~$xqFGs7-Hv`l zXS;?dupV|&L{2`lkchN<2C(JZj+~P0DO9_c;3HL{-B7vZGPRRfuOnTKpw;#*^%D=S zb0q|sd%=+NjL+wxuVGeOi|joY;|BvU*9P{sfz!C9dK$vI&F>0@?#g$v8;m%6L0_c$;!RYXcL@q8tC2Gc5p;DGsHPTJklMwT9^+4X!JjT+DFxX%T;0 zYRwX=-49hfZtXG;XV#+}MmCw_)@Mx|c+7KlPc*$P{TOqm*I&~+wK-d-W;M&Ff3l^)ivtBc&WTRQWfGWSK+QNnWPT+@49!MSo%-ah z)VXfUfb&0Fw!ikBVrn8leq&`?jUV!HrV)m+Py0=AHIGil2gr6TBN+g+Jlpj%D zfza^N+8S%Zn*Q&eGZ_wbI47i8yQZtK42gzc=t%afSw%D^M`#o7s-R~2DA416MbikG z4vDtosa{i})2=+>+#8`~d(8w16~9VnZx`n`zq`J;iO~tBEA+F<*c&s{$QXj_7mX9&lfZ8pv%n`R25#xlRAY8h%qmO zZO#+XYkK97SiRkeTC9-Rgc#C|iK%=6G@h;_1e#*;x>*`9@tmRw7rbE(Bf+?s2?x&j z%9d(|nWDzU|086ft@TF7t$)!o5FE$5yAe$ax#@9;OGLX=&d zhx-!A&O6ewiDc4)R;tNmIF6AW118^*CJidXl2$z}z!!@uVyfD;O9)hMp})#-YAIAXu9%Gr*EU#!1Yc8%4RiL&DI{hrrj3Xjz*THy=!>$!_(Z zGM-6^ee!jXWP-;APh06QHySiDiItgSGDwc`OkohsFlx);z)RaL(3RV2M8(vzbrloI z1Pqp)=E}Tct%;5P-0d9^ zQ1@0X&KR-uOD8Xgu?>YCk-Bvzy^K-yhuupqhJUe&i}!0~yV*uKQ5J<=QLNziK=GWn zs*x2RsM&wJGsqp{P19Y+r=S9KP<*+tsU8q4c#kQC6udSa@-U+YIeCCxu_JWyNSy>AL;Tyo)0E2GvvzHCX1$JPE zXqFM1f02Zq`GQy|=yBO_UfsUO%;o+`2|FXkz`2X*fEFLwA(DJ#Wi~MiSCLjfh@GDa zJVRzM$OPE=na*S% zj~E2@rE*_Wxr+E;?W7S>LSD`;KS-~N_R{lB8p!rXeSN)s>J|?@V{|Mw8Rk(MN^Icu z^j5T%3q046@Vis{yi#hLdN^7#I>wcN4r?f((O;ldg{T{sd zZv!&j%S{OSyc`5^Ags}sygVw7Zum1hd(FnlsT$tLD+wCtLCfC6Hm*0f+#|9=Otfqu|Ryu$rATpIILzE(*UdDwB)6wj3Bl7T@Pp)bv1$5tqFw;zpNY0q})P>PT8tDCXko9{h*R;?O3 z%3Ls^QE|vBpS+Gn^n;jV;YS%!$y8oQiih;YBwNWZtJIiN=s7#-O^b^f!&(AxUvPP~ zvreUna-Tt@;8r)YLz>NKg1eg?MO<7^x4(?moBoTGC9RVj{X9nWGHtkd?Dhy6goa3Pl98s(hAM*QK5YD8gBF`*wDR< zMfAX{zOim+9L|rt+sr@?HNJ_9Kew^ultTt_S}s|Dr5i(jj_Ot!(q?^Xuv;3s(Bogi zL;*WEIf7ZTJHx#7nAawnS{Uhpocxiu zVAb`Y3ttU9^uT6~MGzAgBC3r=<-Wz{);Fb3W)m0ktxwx-A#1oCKFjWF#M;6JXlRgE zH%Gg4N-kN%uqz53Jw(^$0|^_hk9(#$`bs5eJClMW?~BZ_D{yX19)%8to>~WF6ax|1 zdqlk}Ib|h>CtM}+Zg}ZhZAp9($!haxs9K4Mmv6|Mvb1DyK2A^RH?R?ZCH+0`xnRft zZh3p@8Rt(T8-N<)@YSM9>`GT8cU#+s^kE}3wgTC1w6TL}H^W?z0#mKh=e*U6zUmHm zZw!W4$Pk#YyXqV;1@N}n8L(;&j$(F&tBHI6Z}X7>A{1E2%4-?3xq2)?`M-Ys(t$#u zh}j&u>UO`nMrH2=!Sv#OE*{{) zZYKxqXsw4^h1%=ApAi3`SG@Qn?1gw}#|h}(Zu{&!k-39R7b0U<-{!YP8|}IY78Cxo zH8pe$3~-r>%UPxb$`&uz8F3kc`c@$ZL{r?d4c#j-bgQ3rrY#Ke*W8CyKlsF}^46xN zzxzUwy4>s<&qc$OlXxUHUE9{Aj}X(Zs)hNpi(D4-AjxXC2v%f4jq2#mq$j4);h9Lp zFIPXyrOOWm^rUMKJgexM?kq7VEF4s##ZUa}5^44KVa^uc2yTw&<>EA-Vt9v#XFb^8 zUoTb6QXaY8Rn$M19O1}IPPb$*EkX}=)@DV~jTclJSkZ{cfnD>sO7(&CVT2d(H=p#l z_bITNy}4S$y@IrdS9I@#Ly+fQ%W@-D$wo-4Fqx@FY~zaIn&k(BqVvD`v=Hf@z3pAv zgEJGMr526Z>C$^&5B*fJ&yy;qj!aA)xlYHnK#c7VOz>uS>)DGO1FV#zi0NNRh;a}!-RNqR5%hk3 zp9m4(x}2Q9%oqRI?7mMds2iHQc{(jL3Jrg~Bv6O*|Gx#Go88iQOen1oHr_4x+uAve zHadRW2`oaK{-{Ti`j>gMT$cMFh<{5L4Uba)yJJ`D10Ch+BD0gNsvvdD1p|}%smGGm zYeEfWlpZ{jj>~9Kxh)W>KYzgLquY+>EY)Y7ItfaBZAmMl;96zS^QWR3lCetrxaaKFuOoHtD2m47b z&*sWp=tkH!KQ5>2p-5c!Ma|vi`(I|QqaeC=EC%c`!u#8T(qAZ3sZJf6-%_!d=ct6G zp)c3LkO4!;F3m|P=T9o{ul3p|9EJt2Q`;pGCB-$P59IbsjJ?xpK1%WjNXv@)hFMJ? z^M`PW@UJv+HYhiYJ``nCG8tZvtANVeo_+*_EKpnbxn|E7R;Umrp0#Sj9Ct2m{1%y zF#fU4U^Ad*k#adgPIWaGw~)}1y;?v`cd1ZPlsfEs)C`wdUQ{Ju94vY;=Y} zti2RH!9tWInD(;OUAYi4Ph$^?X(cEBb9;(@!sM$wJ6eG-LS*@ULP=}-|9JWeM>^mC zeayu4u}yPuj_w$SVe;tObT`x8Z90xN-7!q(bk3MI-8nJc{2qIMzP~@f<34x1;<~Tv zy3z6tC`od-CP(l(6_fKOn=Li#_)5zwXOa-6PI9XmqB`R0Biv1a4a z-|qxUtiE(hjlyhnhb8Wh4d5iXzpH)OFL9Ns8|cwS&>w7mWrLmnJRd5+0WY$9be=F+ zUy3J2EP#q|xbTp)ez`T5@xdkD{PGM>af4OY8XbXeQvO7Zx)6NX)EqqPb#vk7r#_ ztxU__xcs>gmjIn`u`nb#LuQtix|ajl8dzAmMh+idpExltnLM*2ApXo~ZxO|!=ifXm z7O8rWSlL{%AZguyKY0&i8GI$*r3{d%=E$3J8EMi@%+gB zxE-pN^2K8RIp?#;U%yf--#tr=Im|LC(Nxzhvx;ZR9}r7piVDP?q+%=+-Tb6&bv7un zAer_L%)-S&L%#Vim_Hz3CKoN4`jw49TVS&Ndw7$N*6D^$3o=$UUon4;dLR32_csGx zX0lhQ^*{Ey9`XF@%Lq2o;tcZpBTxJZD&15t+;UM&jibR)ZH(dn;wpMN zd@?!R#h_Ol@~?;h7(4^>@PXKyayj=Gq}`6d@g7uB5Hu;CVa!NUfREA}bGjdMVI1nU zAQlV8bqg=yNjS_28WR+g6B#>#Z!?D!8S%DHIl0X|?A%u7&DD2buuSL{wiN$$??~R9 zILUK~Rr$9kQ#~OI$vHVf2SIlrSLG0fp8meBowh!x(jI>UW>#>ZlwnO2Xz*YlE+cZU zo+dvxEFnfHX4;9``5%D1146r+pxYIb5(zMQ`DbQAK}aVgn)XYSSu0jZ-8T&B-G<(c zZk~}K+P3>w2Zq$2>c)f@3@eC1S*-PQMT*`W1EyMp9_SQ8TKO*PoEd@67N6%@^!EG8 zPp32mPb_NnC2q~h1)f#Wdb$MuWVH#&@~k4(ji=L=J|8cM`=>& z(cWjljz6?La_`|Ju&DN?*eHc;|IFE*Rqwh8FHxoJ?I!dLYxoz`IY@^=t)ccvb$!pX zF};M;9j@&8&!rwXA{@zeo4OBk*u&=CfoC&SKtu4>DIbo1B*Wf|qh=!cJo1t+*1sx4ggx;WC#l+awKeEcBRsPsOk1(N_Dgs-lhZ3< zAMdyP`#=XpfkaeV<6q0tgM3X!^*L^XDm(9)!Q{-J-|!>35gN&}OQx}L)ZOCbT6j4w z$)EVG!!14(-$G5tv~C?W`Drg1dtd(^i@{^N%S4shKMz)gxu(5R#BMxf=i>a#qC_-U zbq|6sKe+A3pWUvwg|e|*tf{lw8X4O0(4Hx9jxK|c)rR< zH~Otj6e+Nh3;Eo=D%~Gf`o2;rbBxJ6iB`+~sndi)8XgdFR9Bpll z?NV>|c^kk;6R%tv)~srHxBPs$<$%Mwb>or#S#tlI-09D)*_WbGM9jVyeR;wx#B&5Y zbcEwq&q2`{;#tz>za)lgH$4ByL$vrupLg*s$nh4N(GYj?+#>V)NArmiZADtpoG#cd z1#3#lfNA69&jz9h-jPUK8HX7K|9mc*Zl21_yoBhjOgfr3JIIG4V)$?VkP(HR+H@rz z2932=iS*aZnzd&cu1l*92T2xTRmI{1LFsq(?REn>D}TxwR$UHdxNgDY@+sUeomA$k z+hzE2MM*+0DL&xW`7EPnZAX+?dle+xETB@OoQ(>|#^e13veWA3W)74h+LV(`ubXXP zSv*VG*j99@IxHo2#u5k!SIuvsJR@=kd9wPl!O*p>T+(d*?=NIbDdz#{%U++-LCn!8dw8h-g}jbltkQEI2z# zcCE0NwHG3?XlNImDMKwQmi7{!52xm~-jRJWCU`1T4g!R;Wo-8t;S<1#Z#iny(os=y zoD=wgF8Xx{YwNB57EZ2XzfkK#SRr2yK_+o1LTj@_*;bfFqGOuWD}HuJexoE$hZ2zKD29cD^#U#I8$2it4CO?9O?8XayPD|s=_XikcEAwLp&u*Hj z0&+b@X#y=zyzV#Vtv>0ziuL{a==sNSIERjGwaY&`iZ39Il0#FkX%igrEPAlFC*IZ< zu|+9DJf+0nMWJHB0WI@ZUvTodyHEBUMUkXujKr!yulY<7Yt8fben=&G7p(tB-mIs_ z4Xr|&$w^_QQr{DoU}e9)zJAQe$hbe+`z8+%1*w0rsEJd;;;mscJ_mDl{LsBme(AzHjt(48J91s?ZO!kAr|teZHNWah;H#FoF=yY5AH|$2U096Sjt-5Hy_l`N3S^q z^!oqWuVT*n{`xAmpb(6!fu_wdcXM52!h%{h%i_z zEG*VNmYDrLWxwiD5P9?CR-%$FM(wTjWXC9xvtv7eXm^%)J4tDnE@mx*)moG998yz&af{I-b>c!fU%ut*LJ|c2 zW*@7uN1u;zJxQi~J0_Js0icEMqG|J3Ba`pQj3A~xV{RD}pQE~H8=ixd78EumnkW7L zX(L6BM0xqpFAR~o-zpM#4&OzF$>k{&5O`i5-s_b1td^qRuMBfrC@L!VQH24V;P+rC zJ)+Hb<8LPN&wHVYeoMV|&+IZXjbuodp4lCQ;yZ)I29b<{&`EK(9o{>5F+uw3w*I{7 z0%XUpkaG$B^&cHRjQ3b@YB&hp{q1h|1+-`FQLHH4-Zp9Mpv*@%nU~!RDt~^UX>&Rw zXTr=>r<)gTcvJNPdOnXhSs~Htq)elM;LvFr`=3|nN`??BewK=2eG&a8#!Xrn4257|pQLsbB!S7gJAEpQ{ za6AO8VT|rv5ZcG1grS29=y%*@WbnjDkZd5s&R8MKQDbk!Q)9rm&S*gabh99cSolUQ z^7h01k?*6--3K`f!NyURz)yU@P0d(9hY5bj+}o6&d`Dw6?3Clli*{?JD1&NmHUGxB zyWxVznVv4fsZjm(L*X}S6@?blz|U0>I2IcW#vn*aGtl5QLW9uKq&(4?sj&TdlQ9n4 zeoEg($PDFlmdQEL4cTysWbnFk%i!@?5ZNiB^)yGy)QC7!MLoruZd|kwx>>B~LA*qJ zP0pQ*t;JvOZuft+T9oDmyVY(sx=$01NHd{{F(IkRk)bcb*D%*<=kq77#ZlhpTgA7^ z*ewW8(NxK#EQLyckD~_x&n`!=ye*YJ79vmkOiZ>E9ceYtuM^LhBt(4U^ZB4`PzsZhY|!9w zxve+jcHtMB8_7!D?}z=ZISoF55AkRZ1#3Sb`X)}~I z+h~w*7@vnPNtKlbM(T)~kc=ycHaZd6z4?ZNGnSS_@Ye^^@j6^{3?=13yu3b0SQ~x{ zodpE_RI^pTGa$=)q4D@l@=#(|6y3u#_qzTzfWm7qW6!E;L&)0DdU~5z;-1GdJ^S?O zQ&32=CXUkk05Qp=VhHTCulzS%$%d4i*4x+rsow&GKU3rG74T#t#6zObp^et_^-~9c zf0dSY+|iEybWM8!gpm5zXDj<$kgOB;{B9fI_Lw{)aH(B>xao5)k5C(2UIry9FJ; zjzFy}E&WEEYpA7w^~Y|34f_2rF*2dmCeo$ytA^pnQ%7r*iB;V;ZD+RNVCm9Zqs1)P zvG)vIXgs5VqYEKF9L$9!{h}wbTgO4jfLv?YuIL$@`tU@f@D!EdG#7F!&*Qu`mis<+K?qqW|#~Jbq zg;doKd{xT*AsfP5|D@!|#zLE%ocDg!kRxjGq;n;H`l2b_uy6Uh!Euvhor^y=H#f?T zsLRbBn3xGrUQQ-B;B99D+9bZw0FOKxX$v3knCMlX#00(coQTD1(swn%J{LKhIIE-sFo|zM?e`!l=}@z{ zf9f{ei2ekD=D#58vIciHH?bIbxZ7Y<7+(K^L?|9Lr9Bc(>~nj9c(p|3!=8S)K~HOCTIk1J^u)0_tcc=A)WrDVy}W z2sC9j-t*Rw!BN|J>ceFvz+ko2)=pn#isv)(=-zc#(f_krGx-t7*cNDjnL_!&4=x;p z_;k{y{zvX_F1`VyW8>FpQGkK6|C&GqYjJCyTSuMe}s5kgTQwXCnTg=^wTuF zuq6KY*CqM+Vy<*B$UxS|!3FvnOu*d4aOZhnBA*FIzdNg9BB%X2UU|a#DIxM+w>_x8^wruBCS`w1Tf)IQtNbm0Q zX5P4r9&Eg^76$Z)VIhW>v@#qm5$O26a@3O1ceHI@Oql$FV~BY*MkT<#tng6^gS~ zF8vm7IRsD_f+eTO#Rf5HeQ{Ihl=zDHrqL&yXLMZkL`6lbn&R;^sz!&lf=w!Dfoe__ z%^31_HCNX@>2QFhq`G>1n}vse7%-s&{)@Hn3hANU?V1}_!qmx(70KK9Lb7PTiB+$) z2xz76n~p|B#M6L&aVYvYafY{mD~E zFtumIt@xqHfz^>2D4RQ}aX)L3RuE^Ru}iu9Ts78ZPiL*1!fMPIA@sf;UUjnE2!(l%C>R!Ou1 z_k@X^iIEPP{zRoj-V_2&6e*qD90Xf6>m+(CPxA5EJ)=%$T%0`_0ifbba~H+e-;A>? zj*lW|;DTc#BC#y2No29|!=38Ckxr8r8A+%6-Jzi?;};&k57-!Eat{T)sQX2a)h`?Q z^BNaZqxq>iM-%QK!mqiTxD1AfcjQ%LR(+qTQdJh?QG3!7w<1eQn_T)KK5L8KHao}< zvCK~X-I^07%*qYysbbvhC~L% z9Bx#;(b8J_`rVRurmECin4#gu7%q1_q^&{2(RjpB@W1u|P?nPTM}wth!wJzxIAI+9 zvPrdW{n2F3mrI^Uo6gS8;tPoVgU957T3&KhQb5^Fhs=@_g%tLY2Z6ip{G0JMpLy=) zo90kJNGOMoZb;Q*uNW9B@xO-L;IptKE;NlRo*KpP)CCe|j8YxcvhoA4#^pl2pH_81 zC7l?uzVYL|7w>fdYG`4+lu6px9&?SE=H^b_lKc)iIp~5m)O@*69%|SLE+mTdno{O{ zCXzQu`JWWr|F0;Zs|O9I1DQN~>qD0@7^bc<^mz36aCDul=i^R(=ux(g&0Cnfzyy)0 z#_SK3B1J>W4@(b8%ST-NIwHGh!^1Rd|JssxR~=61JtzkiPx}r6L5vmND)GbvrdP4{ zFtYzp75~2g`7sQ>_%#)*L{!8_6Ch@h!7F$+DoUHgqT}(btw+ifEp?Ec%{BBs$>yJT zQYb>)9$(;kc0&{m0JkwMCt_^w{8Wf+*j#^-2>$b%$O{AEQM7bygc<>rt^~yDNgwKx zt@4AP-Flbtm32}DX&i$6M-}`Q&+xAiRM|}k!phcW@nVqOQ*U=g)y;Y+j2tUYEl_L@ z0(c_0FIhX@UqBwzi4U^I+|{Tt^$s`QwW&BiYgepP zBpPJ33bJ88KcWCC-(p#IM;S@r-s_Q~h3Ogs>*g~6kA++}{)cP|qB*@*sumrMX2OF} zH8E@4{7`GFKIkRT%U`tnu!X&A!tE-BboQ-EgncH_>x^3140+^o)n03UvHk#)9M7N@ zHtr2gT|_{Gg+Jg#{`}8`gcT_RmXJbNf)i-+@^mwq$$On-nfKziQH-3exjEfO{8)Rel(01sdkwR=`j!*Sv)XAaPmwUe2c&;z{owHe6W=5rWWbZ?mrdlWYQ% zC361}h%}cap!LR7?-L6%^ffL4Ovmo6AG|LYdFtxxFKXxTiX=_?JNG+}9X8OPjf)%3 z%}PW=XwSlGGVGswEcn=BG$WY*og>fLDrSZZMu*NWssEoY2`^CC3?GUV9v1f^jlrmE zBZ4We^|)?Em+{Lx?;BZx3*#8zK!k>BY#_zaN5}x^1qs7Z3agfFM z5gSimPx8q;PS`Z0N)9fOq*=8fagPq8dijr^2FCS93&OBc42uir7YCBcZ08AlwRqkxJi_$dfa`7$<@A=FR+^@b5+BoS;t^jG zat3UzWC;wR@1khN)cc2CxV#5zhl1o{V0a3NrvMP+n+AO=HIo4Z;hCq1jN!^*uXU&r z0ZdRAsQ^Oa-L6z9IdUk$a7RIXgvTsv-PpzBLH|hi-m~5v3lemA=X(8RC}<2K{tNZJ z>3?Sl4Hu0zS8ReEp9Jt7)itdaV|j)-^LMp7%N;O{-Oh<~C360RO|(?dD60|9DT*I= zJ?TcP9VKHu`_6@LdU|!We%?XfR<~}O7gT8rsfV4UubZC4PX?^kGc|~BNQ$HUlPw6B z;iB~)=!=D_e!z)`!tK*&V8&s8>}4GUnY0`2mI3e4erf{`YHn`l5Zx8IQRRhC+Tr|; zIe0D^Wq~N3&l8$bH@eXfm0v{vd4&mUXR4a4nUpy+kssM8(?;wdgK(%F>O{3!C2+pCXn);8aG&7v;FPrgKcA0x~Q zf~FOuGL>*7GR38%;al|4eGL3)d%idI!NS6VXH|Kj9gB#=7_V)GNwd~x(JPm49j>eZQl*j1nyh3KQlpgaDY6lfW>k(EaH$YxBR~Sh?xO$fs zF6(77SuP4*5b~FeoYbh2g-WN>IEYzrS^zRx00wV*oU^L@j^^6yUE)$)xQod)$Ie~l z`qibYRg-3SMBFcdKW_{-E1Hs$gi|l4wDd)I#F#z_2^-OxI#fnByfN~0vXYFlI{unC zZ~(y8vJcRDT1e+mqB@CIo3F+$_@r@GV}u)l;2R4ZC7PiWa0z=nq@^(~Y0XY;ff=xW zyG5}iW0K7JD~FOhJQSc5HcFE03UOjV%3mImj`sg^>>jJRqy|1vGBBWA8IgamS4zS9Yn^x#%C`X4~Me=_PIxw z_Sc0U547d_^ZZw##RknTVn}Cl)Lr>W(#^wY;LO_isAzIZLuuqGwQQ66+*~R;Mn+Di znrkX4rtK7T(D_jW<~975IR4*qd*kiIZv}7(38l|n9*%peiG#;OL4vfvtaAdXa5KS7 zxrcqoDImxhbN6_?zr78>6O_!iPY{!47eC+3ME9kPv+CBQ5{1%ELlDMGw427p&Bh0f)U*m|t>+Q_r6AJCfzpwx>M0Aw>@y}^#?e^D7S|`6iGm(yZdd0sdhMi9 z0j?fL1xum|@sF8dZXAL{-V;@H`H08&;WJ-gT@k`ahvq~}iv@V{RGDofrg?j3y%H_6 z=0ifv4ae{_X`sR12sNY;+)BAIt`@VvcHv-&e?rOMQXr1MMFn}qrX395HlwvIQdP==avI1Gr=Y09` z;Sz6W_4aaS_7?QhQA_p>sz=kfNQ={l8J9fAB*#z zsob8L=4tW}e*{S5w=Sg^I`8_|R(d-)({B=)Od&^jAS)-^Gc zcg$}c)n)mLAv+Dd334C;geB%zGQ8W66LY%L;bLlWfX_d7)M4>&~8L2020I|SW3p=URP0zxp2@( zlX4;|8*yGR3%qx92HLm)VOmPzWh_#8z$?*3@=LLEBTMh=rg4Iqi9I$9!<+`@#IZ!j zGlY#S!IA_N_{NObL^M9Ct9K-`RL6Vy`OW*meVeZrz19bNVP6x`#7LBKcW-a}uHauN zYhoc^vxnY2-kpZiX1cA3p3R@0D!ywqV$K%EQtd{^eP+BgK}(AZn;^Ez*vaZfW|& z%iHEq1p8ELkBx!23Z7-J)7Jtrhs&{CQRmY*xIVvFGinNK2FlF+)+~7@bEnZCou1h& zYrH~#`BpbuD_5n4k7_uzvfIKtDz43M)Hl&IUv48b6a$YUeAuEzU`EBLnP78DeTeSS zu89hs|Foshk;o0#JlRy+PVBfGMB$+K743Oo0k55Urephe&YT6 zyG0b$8oOJYV*-*9DB{b4obTgZx9__(=9Libm{ZdD);bGEl0&Visdpcm^6!Bgl)iUF zZ~rG-A;N3yv+=_jW{H-@j~34O>6M@Sx)^v=C-L0h597zc8J+bb7sThQvjSu+Qqkql zoHxV*Ubb?;(?#Knc)efosO0eQ$$f#sE5#@v(3o_RYl!Xws`f|f>7wi{IUZf`lvXJ^Z_>?gPBgi{^z6Fkz6*U zft7>+W14d2%XC=C!-`^7ydk+u9al3zxwObs1Y0Y11>jUe>4v?Oyp4d}zP3`81&Ta@>-`HQqdt zjk;H5qu&l_^B=o~?z%5r+fFmumlqfB+tJX?)fv;SM9H(bO`YBt_iZH&Cum=ZzXGAW zf(ywqZ8Ezz2M`V98KV&LCX`o^U=bvY<(Mzdfyb#R#T%#m3@WMBTYxLLJ*8R5kPBL$0g(9rqheLFq)s}L>gc_bvKqSt;;tfrT|%!TdiEfckC zUB+#VHB|`nL7rAqLq~*P#`oS5m^3vZJHGpyZ&8|#Q!@k!LS45X1dYRWc97lI z4Mp#Jien`yqcCJVTGm2V#1-q)$@X3Af7s5pna1hXC-J=Aydy=nIFweu>)Y6}Sh(;7 zv@H`#w~Xw&@FYa`al7Ck9@pvAD6%>#^yDb+>JnuBZKNyRSV~rfL1$eb^}}R2W}!v^ zUGW7=S{u;TgNWuEy8XtZt$xe9c89i%RBUf0TDOuerwwj>DzYMMBbl!KaIJT@M!@rZ zTky!eov`li!L85vGaUO73+)qH(F|IUx4SU{kgA$ zDnuGDccVajPP|b<7Ij~JM*mIijQcnhQ{JoN7U9BL0jBXHH8JAHmF2tP<-7Z$cjxr( zx7TngzcPdtgN_P!wC<*3JIkwORd<)BYBWZ4x`;u|cv)^+eueJ8i*}`4!&+nQKxn9J zJD=8Crk8PY9dvi}6knS(kK!EdY`T8V$@|V|Bu4hjLmVIH8BGgzQ-!r_P?ok16i z!8q4>k=W|}c2RQcrbK>IMW|l*p{fIDxus(KlkH4DAFa+eB`Vy{ev+nZ%7%ErzpOtZ zc(4efqr_3GWXd~GUk10>SP6RPf_zX1oRU$uwznw`j*cGZ59S+$Sfm(A{3{6%T)NXt zZ*2(e=Pl-4G-rc{?yXZTA6zm+te3H`T8Z$I^vg4|iD6^g^X1-_OOWa*Rw=Wz!7kT}Q52(Oy%#IH# z>5>*=O|eSd-A77x(om+@#VRE$$cUX3wOn1NAEWYdt3x9E8DcDoux3^qjS&2l_;jss^5Uu*ph}zvj$L*2(>w4>X@>j?&?Vzx|XQaT{_`6zb z!~6QtwzTyM4kcv#Rm6BKQP|ZvGVv`dhKpZkeJ@W9tL&(7)*j3Ap&z)ub5PuM<=5M zH!9e2Hdul{Tv%T-Gd80~$OG_fdfaEWbsypz?35eDc+UNmM{+$4l|ufsyXwkUWwSS# zDTh!m@tBA66}d|$$#mlZb-D#kEThbFn>I{v=t1WTglL?r4C8flrH_$s?TQi;exm zrUCDONMJR;5z6{`&!irl3{ z+sGt|*V1fm2SiB;(zA{VE%@YS8Yf@F1Aa4T7qt3)n`M!3_$raFoD}|Ly3uYx2qI--BhQW<*5Zis~Z}Xe>xw*ME z2=f{=+&0WRhq@KAZ;RPwL;Rx?Duc6~)X@%+-dz^+Hot4*Nc%hu7vwq&EZp3`;QHED zUz8VF=y{TF&XjJA^PchQS}{oVRE`DReqB0rG&+L-<1;E=gw`e#$Mb4q{+8B^iq*uE z%koM#W<%b?-=U-g`We^x=ezyX2+usfQ^A_+QD6UFKP99_N~Ow19w;x`t*+x2p<35##EpTazi_4nPX=}HH zRqn%I^Vq~wP09|$Q1I(rO2g=y*L?w*;BEk<&=K9j72hDikD8L=Wdkt2> z8GO#cd#tGezr}A-lZ+wdJ9$g=`U;Wdj}bnW(WKPpi90){i^{Ej{(W@5BNUECsu4dCam_~=waxlUt=NFvQfW+GHHIT21 z`JuhUMudtUqH!%=;7t$F$beyr<0vo(=U0m^M63cx4ly@?IpH}5yYBUYg4Y^8uQJAV z9D_x*Hx#(LpKPFAO*Y*o5Kj@K7it)U3J!WW4_RmrS!sLS5=G&(ds+;Of?@l-sRT2jNq!>ZHuB23YC!8@_vPgx(dXMVUO<2CZ# z-t|i(5KtnE2MBY(gH{`ZKZ=?f$|NVeY^G@?Gn=mQWLN!@H9=4RWVqKYDi zBH#_MB1wnt>v9iY;C(dAH&`5#(1DPAuST007-^)4ii+|UF)c$zZv#Q*QNhHLzg@&d zlB5#$wLXd&xGNnmy&Ah;Qv^I>@f4|yW8@}Q)|DNQ_13{scCl3UCBqVxU%Orzy6#?X zZ*OPam$l!Nv81{;2Ms=_|7#pZp>>(`T)=1Bn_|{w`#Z&5y*Xy>bFX4ja$qG9C4|xA z1jXb6k96M{bk+MZe3~Cfv{es1$y~l)Tn>)=!Gr$djmG=mNf3HUQlzZ#=&NOliO#bfE}4}ug1s%=&Lx?LA<{+Bq88%9*Js!$R?YD zV>QJ5qJhu8!W&3{?>YJb~wrjLa}MKQF|Bx_|gu1?f=&B1`8-z$u?- zM>Z}Jxj@jMm!`#YWDhE670q=2e3D!02GkOFsP6evaH_w9E%TVNKU1o9`Hl$ewu-A! z@aTk)wc+{p^OA0}j7GpcS~5@==P1hIa2DMR(|2-p{pcE;<`^d`_KNJ&%!18!OR(=eOpLjD!;b!Qd@w z)O?!^JSi!;HDsA&E3VWe5drKL&^0?OXl_=|cT?l2UW(Ju4`^6#OGfCe`2K{U1 zc7FrQ+Aj+VQYhzkt+5XassBs1W-)~ z!6y|}tSj1bf2oRi2}=U-5Y2Y-Fss5y8fP9QBlqi<>j__kZfAdy-}UjdRp}}pBlzDH z$9&TTMZ#%Ep1r=Y64p{a{_$FE7SZf!PFoEpBz`T za~r9k%*xTTzXzI(3`lJL z$QDXXo-G+pZJoanFFn%dAi9HgwxhBalpp6wY+DZM&9F&%?P|EPA+Y;guna^RX6tRo zu>mW^7%_h#1eeZ~(7pQ7frm`)Zw!HBdE_IssCWg{|SB`O`K*jK1Birhy5psMmcksNokUm%xTpBBh)F zzGiB04uzCdwq!s`g0iBt94$Y-9aMqmKS&#+Uq5$* zNIwtEB&&SwU*Gq3HtmO`Yu#0qt_AfsO*nE!J|i8B;ovhhP7O(lCPz$G8Ydg&$~uF1 z_1P%!#8{)s2{0xK_3EC=s9zbz%HQycL7U@P2XYXZ&Newx9bq6PCrHCpSzG3d<6p*- zd_?Ldd{u)OlbOanaT<1$uO8w#KfiR*Gt+ia=gjc|Xthw`Ok*p?=mH6wyup3VmNj)n zQ<34FgH6HWRX;PXP&J|(?M61E{NPmLBJ1`LSkgjL5`#b<{HmrTq5P4b=fa^$P9!hu z6>?#ZS4SR|MzfJshQ-&Y!A0JQs5Fj!-!{_chVLG_}E7aNfQj(>|V({9Rf^cNX_}bdqSO~rOSxtDy zSEhDB`nNK+3!1ijrPk!eJ#W-L%$U79O{MJ(zr+%{8H>@zSb9TL-K{~U*BsS5(duMX zMG8bbAVwjhAVS3t{kU`nL7{PEHa0diFb5GMfikoXHWQBLw(QdGy-#qHWOGZ4^RAqC zEcJIb@*wN&zJu1UpJ55r*z_d-^1{p4h)^lQU=a2Bx!-(YAq_jtpn|Z$k*fO{c1D5O z$jEgXHg0w85%AX7Ba_r`$RIW&y|SK&<9b@AbCxeu<8uNQkn@C*Pd@v}g^!l?B@&iY z0`Z?zJG>y~4ht@rD2VhM>oVZC#`yLGN`6(*>0lDLw|;oi_62&(-V^< z`CsACFKjtst+7;OWe4$ev*v8FPicw{gbd`??m8GeY%D0YKS$7+>6bYC@C2$b!q~l;5iGsN&Gde`tN+iY^ z$lYM1=fVhL11>JwPf;TY49h*Kk0S047F#KJaOY%CHh+{)Xt|7zjP#P|E9@Eg_3O_l zEaBt@4?p$OIvW!pLSX<2RV@HH%L6vsn_gZ{<}bc_^DFZUzES6KJ_U0_L&GBlYunYD zHRvp!$zr?W$xXP z5opPfM8sBp?ieb27YJR)3tg)b)nuun<2WsmTY?I}@AydZpLVN05CS3e?8t#D5w(Qn(+8+S4tq z5Hfh76n*h;aDj@~SE~GY%QqgfIIpbe+A6OawCn(!jj_c zU1uK%H(4+JhpgQk-7g(2TAK)80|LAX3Eq@qmuqvDjVfk_5)gv;oiw ziLTG}W=lo;!%fzps&*J0M3qe1bo?Q&qoCn~Xn%O{djxns1~mj0+?93z`ZeMgF7Yt9 zR6Lh9rBMEDGye;IsSA1Jk6|4?aSYcfn8l4-X;y9Zw#)C>=#uFyNCm0Voh5S zF}~ifex`U(NyT=HfK#8d%Lu_Yl0&MA_&^1bD~79JFu+89OQnd7)(9D-ks|)M%=uhV z&rFl}&k!QE`;3r0m=-umUkF~y^Opsday{N@cM5Y0rw#ON8>s^h$=fb>j7W~=pAXOp4UZl6(q^dci*iUqU}Ke*$mTiAWV&Nx}8fCM-|r z!jv68rQjQNOkPczReyTIaO~GRRLj4Q_eLdLl5_s}M2u#EvSMAN*PUDat)j_LNJ*MAij=S^6 z{Ec;#vpAspjjXPEK5WsNIhBiIpgK8(_UCD329N0a#8%heM)$Q9 z3r^1Dt2oS!6~5whN;^Yd<32NdSH8=OiwCpa$wUSNQt7k>_VP4dd#vtY_;Idqwlewk zxph=wOF`#P_oOEL$JlT~DpCmEUy%+PNf!3;nuQhC@K=XPg(xavLZMWyV9EOKR#r6k zxwf1MdDF(9xPJM^J1YTA7Aki*Mtqq?+gHcy?bX1<`^Hhxnc_oqDn-*mpp9=Ob8Fu& zs564mR0mN5-pU|05s^xK#I=Kgh(A;t$LF&tmA=fKOiA>?Sd_}zQ704RAtVdgx8opb z*37d<`9a`+x*lN~Lz*_PP0)^34=^Ae8H(=|S)StON_TlGBq#1lH|{;q>pul$e-U9A z{7)d=)bw;xB%hyib-K^#;V?>n<)?icPTPf+DXz(r1}}Z`9weL%?l^`f`OL>CR2=eF znejy^FI2i1&J@{NOPbYHt8e&UsyM{^RsgfHWl?&sT`%3z8~!G`k|=4maq?qr16$EE zA^v)Emr5;;DDLBvj0}F)hvo6(D2G+Nahc>bwnA>1#5GT`c&Dd8i%6wthnzrY04DyZ zR{%>)^a>`pldwl{7eV;6oua<)k|CAoB5=E3(;(dphnz3w5=;6>ME^UJ2LDo)51yjK z#!YYnjok3a7Yj2e?9j#1#6%KH9qIk9j%Fg;)tzHaeUdd6IumVHHpOWnfbu}UJ3j8 z)G^NvKv)k{-F0bFg$E&NN=q^-23-v0?vBIsHfl1~W@) zw;Afsss|BH0!lV}EGguTjry|6zfgLxSxl=NQ^gN0+ux}P2!gI>=}lZ5M9hzjmF+*c zpp=FRtsZlcT0M4`h2%B1W_q8GJ>6*FN9=@j1Z1gPyK(Z$UF;a#j`wm0U9hqz={SnBX@*&s{tr!_y z6u@DbT3#uq1s#9dv4rumJlDEbX*v}>o`2Np#U{>wSLPgRF31GgUMY!y!9a5%#@j`| z+P8>moNkfAEbSs^u%qRgdRwmoq`Xli)=zIx*SGUu3BnS^;jwLPH5xU76zje#soh(z zh=kBoI6nK}q_^t(oSm&A)9Y5hElBr~-e(x5)%;U|TQW{-vLp?EiIm7dnaT9kB>o0y zI&ut9{tfjJ%@4Dr1|3fJdZ=1XoJ<{S4M7xMKM#L@W1M`7kteAB zmpmi=ct~EVdz>^5P7b5(Xhbn(V zJc(?78n4`WwPmDi#{Rry{6S_GRq`#p@t`vRD74RwjUmeC8NG%B}2Sh>zDW!X(8wBZ&F}jh40V3Ukv~)ANm6R9V-8oW0ViM9h zKmqCaJ@x&0{Jwv{wrAY;z3ZIooO21wJXelJ)s#qQVN%M5dFfZ!3zc%=D@LG;Qz}{2 zbpG@tjbQE+M(!E;{y2QAV39~O|IXXlHl|r_cE#AarpU_`)<^Z)^;=NN7g3j!0wl*q zRl~1?7X`2>#!R`gpGlFQC&ucYVcR8^%WgKIcQP9V9xPma#)L-{w^nviYqC;$RMvdQ zUS{`XlzEmg%FC_qXjlrd-peO6DPArphlv_SSA76FB0pP!X)4$t6GYPe=YatKh1BV1 z+65_08XOA$?n4MeIo_fB>zGqCI*Mj?cHm*Ii-+Xqi@osWo$T+OCTU2!b*dtPwBjym zOS9dlzOs3yW3TPd#vi6)p!Mi?P*;*Fo3uoHOjXLE#;{j_@=hX1+HzZ@$m}eM4Nqj6 zKk@Zqs}}pFY#_okI#g%x+`frIpL`i?;nluHlxJ!nC_BR|?~{80B>6lD;Vyz8QSgJp z=n}g>MG{e3T5m&=>2=;qye?c*`b+=3z@U@cU*cK6++Cy%;55Ra)go82b=Vbo3Y@R9uu(wh~QenT^qeW`Fur0aYMq_O3x5{t?|I zIHfiXW{Fc7;FfI~!55O$H_^4DYyBvZZu-@*fPWkHmH{6R53f03laaP}u1^QXYIUiU z!WUkF_9Bgay*Zl190vslc$L5ZstjjAEhTnjU2Lw{VJsFEmywJ3Mu*Fs_h08-Aw%1F z@9&t}<8&-`cXB8r)7642EDrqncJ}FdfrJh{_~ineZ7HjJn_@?=uy&v zOUfMO{<^oLg`u;T#J3ZHA)n=QyzL{SbGho~$duU0jMEKzL%qf%QZF4%&8si~75;oG zp`1d(X5(P3!1iSF zImeu;FocFqiWb=WQlmbuDP^++JLux_&Qn{{anS|R1rsO25ykj&So7|RuO5pSZ15Jq z2*J4|ty(*GNR`gHVu*-W zJi9oi6w&reK&6KK!!MZIVhlHB@8z;iW{lN|N)mskT3&t8yzh|CDYpQYsCiZ7D?kTo z+Ok)#wo4sz%Af>EBVC#>pB-8Vgn8Tqt1gt#*h-v>@1rXp~Ke`-O-iB`Q3g?=EIaGVi@W21p=hXtCBDNT0@Q@5U|p( zweQ~jrZ)ctDy3+BCQj^-yql3U`R|LsgQ=5y2bQ_iQ2MdODo(KG0jX)%jcwqSqPbLx znQXj-%)Me#H%%%PQli2Y`ySklGKnG`EUonUQ~qO0^?h^QrB`pud>UWru2eO(SN&Y0#x%C& zeEg35PwmX{8fiRvcA}lB@J&n1S6w#~D0B=Pq#8w3mkL7Ky-__C$Xmre<>t1?P`TWC ze*b$=^!TRdLs~j}7r0y0XpB4wVHj9E>^4;NeWwcq!qp1W6TbP6B=>Hu36T_Di+TwH zJ6+K9yx(NEEL5WQ>Xx3~q=0o;n%v?GaOPJf)UO$1Tia%TX}t$!C_O$@RO2d4~^cufoL$?pj4lF?hdl;LAXgmWxUr4|>7H1x24>z=**H zwOEK2F$z&qyfGun`5ILC{hMr}1Tl1B*UHdEDb`lX_k=c@*vT;lxgI zWoCwfXEcbLrSx0@dtawnjhzi*{p#dqtq2#BC=2{tC{|tj6Dz_<_HBZ9AqNEkj ze4jM%5^aj6+|^1GeO7KjI(OD^veDQ1hyH;dP6p4ZYn=&>on!~3CfaZtTC{&Ko8OOB zd3Upay!dVBP;2J3AHj1Lv71&q&ANcgL96W%B>Ge{BEd)^RV|5AeA@SQ^kXe9$sITs z=gbzW>X90@`rui6L(X-&;s)3zQJ__E_$|`y54Sq!khpV?bbztW!&#|h=Zjte7l>iF z94j8CA;H6E*tv-9M@xC8Df2ZxwnJiarFoqw;u% zlC`)SEy@n(U$5%2^fz`*q;>clD1%}J4&Yr@xo+R|DzU>ok6sxeugTtms~H(Ib+c%*tW$oW zPmby3&o`18`J=R2D~+00*fUlq)=Z!1zHxpL$X22Hl^FwF{*$U{+WWG&$Ud;c;esL- zrpE}CUO0}_6HV@m7O2`s#g>#DBKmG&I~qUZL75aDQnLJp7cHIvMsqETs*9Qr|8X8{ zCjN+eeK0rDZ_#=b-1-JvLdm<%eXW5C2mdV60UM&t$0`^(&Aao-q}vLLI(K@x%;<2@ ztcok8&zjiVok8{^atHsas8gun-#Zy{3(XZat#Vn znO1l|`~^;Q`Nrm4KO9L9jiS7w)aIa+nZ}Gtra;PFr4~uN#=Mxg@p}Jq(1aiD%kd5! zr2X`VBQ6up?P!|Sk6!|3DeDuMk$KZcI4_mczMEYI*Gh3z=G)B;ko_ZqF{#nPiXv)p za9a4`r{)stB6y%(%5Oyq7!J+x|$* znzt=dY=AO$hjWunevWyQbZYO3*qOv0DRznExOO-6MT zo+_FffRwMjtx~_-9eH=RPehkKVe(gI)l_~=dHN5a3oD>;d*3{+wz!lQdb)}|K{-gb z{%%Q7nBb*;34Z7Xeo{k^g+3+zWLVzwcyjdG5f5Ut$f@qujUTb$=nNm(wXrdx+<&_C z2F#=^sAVP9>g($HS`A+Uh9hC^*lAAgl9cg*+d8|-m>p4}Wse!rBD|YKV}U3cJ7}s> z3L5!8p!TT;XCV~QUo)bbE`edNZDeQ1c{g`rL^S#f;q`UP8}N)SMk=MgIanthePd+w zlaBv7+IaL+3aB+;I%>E%qk)Y7-<`Gd1Vi?NN9t9K;3GBPrYx^yG z#KE8Fc#aq~iOL)fNxLuQ8@>L6u@=%Nvsh!2J}r$2S_lK&bBq-I!(N=|%IJmg>bQPF&zVet90WgY#i?-@&M4&c0uTFt*9eSET+*{X zuPEGFm|Y+C-HHl=GcGD-03ST{S*@54?`a^*^~fc2S_awC$^8YEMq>181wf5StoKpM z+p4&Evni{sLKbrr*esB(_(wbHm~+uV`Ero7TG6>_uBhoZU#);>j+eaTfiW5AL3@EB z03)NPAQc7h>5p7HSv0kDc8P<}2s6%+f0zQHuMQ6ne<%&nvJrv9;_v?>Txjo_`>tbs zi>hps)B|A@$D={C%6?i*a_i%abC1$HoqX(feUX2ER_dDg>E^TBhFYUbiX4LUC6i5v zl$GO~sW(ijbsU5&KA21Ct<=n#Y*&j@xZ4EiiQsuaSn1rEn)XdGW-*y#lgyYJgF7!@ zsQtq`x&Ip4u4hwUlqj%>rXAJz?o_R=4fhsJrA^KL83K~IYjAd_v#y~~s6zGNpnN@u z6tm%5SV$j!dM9ykfvkCeEAl%6LC|z?c=%XTgeBdkN1Z?%=_#O6P^9$L>=i~oKXe8kES$zQfEwK@kW3S>R&l)7=>F;>_0>1Es-2!#mxxL=4C3sJ}M1=1L%?D zdgRR!568ny%17LULNxQBC@m_gSc1PTh$jhB^2HC?UB4JNa3ZCC5h^v{(`kJMQio^b zb4HiS<>UYS+vWChJ!uS|S#^I^DVMpo4XHiS6K`mN*^tWENY6pHWn)gH%Xu1Sb?fZQ zM!%2hy`iYXTfk!vS>w3o0rR$6KvQ^PQnWxHvVhhH#s9TFf~4ee$F8NruOpZzz7E8a zrvWW;3D)&l-PDr8))kLNfsDL4g&9u$&&vfUwVa$xq54z$83Qw`HxzoDUs%p`l$!#( zi-m-T z4V82r)R6P3%BVC|*(aATCyQLoB6NRzy=)Nnb?N~qYT{)4T2ahB{ZfJ!@$3)d*YRm7 z%e2Cx=I8k>1e`NEX7)#|p6UWeq*7**AFf)+)9xL;`%@_=CcC&qqtKRbRoJQf%P!O1ZKHDHOEZ|LdJ5NM%8_ zuV@W?#U~^0C$$wPGc}W#DmO*4D96L&nP%p6ocHB@C_!(1xnNjw7@iieKJ62=Ijpm6 z8cB~iRA<33YWGk{&tZ0tc*cFZ2NC1?hj-Gx`A=4Sn!?9htGkceOFae+M(Ljg4kYhupH1?!g7is>YDsj8u-dA(88J>r#+hwx0NjsqK{6-!zTttJsQSTaiM%|Kb`g&xTm=ho|g zr15W@(CM!xQPqU83oS|pd9VEf6 zS)V<*jx)tY!?@?mKkL(qa2!#~Z4%1x235mpULh1JCix_Xn(W28V7WZsiR}*QxK(w| z8OZ((Ar#`=VpGnnIW2IFApBMDz%2)9TuY@Y%kTKCgda5>O;G!{y&y*?gtN+MFUrjY zz(qDep;yi3G?IRk+D>Y(&f`otw-vszyZuq3BL@PnuF&Rt#d;jug);ve{hrT(`cL$| zO;2Ulf0thfav$4sBr-UnoK79fyPAd^_M@iUU)1=si$o=wil2`H8eZebv0%07>-g;! z0R=dt2Ib4k!50}>S#Ms&Y)>0erTp&%bBjQWOtgnhS2tfVY(O$*+=sc|H-+797ux-4 zoZa@U;~)2;GcXXLOw2XWK#S;y&sBA))jRrsXkbCHn$#CBvQw&_;Zhyao})M`)4{ck zEt#$FZ`2k>^G7WVCrtx@G)Y6rF~?&772%B&EI7@VIDIs6^4Me4IB@@RHZn4@b!HUF zotc$4oNgWacbiFJx`hUDnq)mM?qo|W{05st5z;gB(6qwal_jpv>qb^DV|^~SlD7*S zD)r%^f^?cbdz;{Io+7mKo9BP1OCUdQmRBHUmvx%A2pRXlVwYlE}FKcm;bg9Opv(VEuLNPD{h`;81^*; z5~RAO8@}si_+CBM*kI(EP-b=r#n~iBM;+x8mwl};X5@EEGYlsx5QYjdP03ERwD-%v z9T=k#6uTtij4Zb0qKwhg>`Bj402X}`ueS2x;0MLv>gMPGDkm{vnX(;7ar+Ln5uurP z_KJcN$Pm=DQ9MkQu)_`P=JU_7i;qk3bLYP7%p-`lsOO!a8dM4F1CiVqo(z|#^yy=P z_d9_!^=kxXGo7BlTZvA)H*g0tq%i9u&`1ueP*uE?M&&9WY5z=_>N`Z?kWiuT9{(i~ zv>EB$Z3Hwmkv-fH)Vd}v)6(JD7j=%FBzi$(0x=KR3LI-X~nlj6+#k^*~;5J35M4ykMMw^jti2ubcgYjm_Z)D-LfD?XLxn8iCo>%!)NX~K^CHsR%L@?`1SVZKqAeJDk|HOF|Ts9jAx@mMNQW6>6+eJ1y@^@#@ z*vXlI*>Uu%Yfka3ze{WSaXRe`9kuM&Odn=F>*4BT#?Rv`w3Hn6dNup|0y#PJD~D`* z0KL6R*64=XD%pvA=#KDh1?0Q>8v_HfbJpAxvQ}$WO*cXdF=m`qu#c=+CGz`_A{R4S z{WqYs+E_Kc6d<4LQx-UxLv0kxOs>&WyVRk?iYf-Isyxyt4twYSpko*p6^R&&jY4xD zus46$1z79~BFAj`Ep1s$B8rX%Mq z|KI$1)g#uAd_of#FfZ)8NO;bv*PI>L5gzKxu5DWCsSwrkj^O>W0=#H;bx5Q*0!`vp z$f<(gzwV5qps0vACsW;Q9o~>621B_PaUt5M2*;4;^JUfbNS!uIru;v6sdpaZ*gYgNOil@jeCcG8?w8|P-pAH_ z;aV&Q&1Zs=l7YtC;lon_n7k%J@eP=OZ~-X0>*9KRYkD0yjYTiEx4;onmE_3yMR$^Ou|8w~8}?{8XW__?LPWFY_}cOJ zACw>;uCw91(~9p#>d^9iwPO&fmu^#!*Th^j$0EIIk=<8)-uWlp%PyfI-C(CVcg3l} z;>Ywvnu|~RG>C@2Zq}a(^76V4uw}dyt{c@ZfPL(BIVXIZ7~z%9;xt1DD1Q?G+1LE} zetgCIzEF7oOjH~DZ%#~REa)P_>m#yIY%_zfXL>50@^7?E*@EU z+kNF7`Lg9mJH#f+PsiqE(#_+A>O3tm*^YCgJA9>IA>?mTX=CJE>a$#>x}9fqp205v zpG~qO(we)IrQH9TH|sdx%yvRSS-mJH110Go8`T< z+MQfnT)MNZ&=xt+ABmj~O)ja6E)`Gd(ACx2NSsw0DQpO_XG7 zK=YD(_UDeQ@d()y_x`3o+k1sO+Evy;n>if<6t=D(%9R{yIAg8J^YI6 zlVcy&8c$0UBdz$#=9KSXqLL&UW4{+P@wEBljqZW)nUjv9wJB&Z#A|^yk4&}5G|D3)Q4U? zs@+YkA3q;KZ=q8Q0PV5XPK&SA%Z@wYjQKb()U&4Ur<;h`TV!C>Onq5on=VP6ESB9l zznklo;`0XN0Th4y3~&SYzh=JQdT1fKio?_`z-;%cG6KThZWg=mjBbLp>x$%;TS^q~ z2J{LIpAiH%(tl;_PAi|$m@_uwu298{rVReWpy?L)=m5JRgTrHkYt^Bt)5uqAg@8p; z_QXTUfvC7{v@$LhA>Aq&xYa;zV1cMC>l*6#{ojNu)bob|*Ii6Mv1O*17Xi0N+MKW8 zqC&02QB$z2mzj9Ip#282&%(w+-YKcFitinEyRr#$3Hqc`mU7}!u)VQ=a~o;GUCQqX z8wNrHaSX>lM+bp_9BY{fS#&YYlW0UeOSaP|_|ldc8gQFZN^E3HnhgJBL_Z2I?uG#g!KFL6?EIJ8#Gz?^-_3%+felx7C%8ftqO4xn0TvyIr4Z__9 zTuWCD%-0*0p=O*SDv1u2vum-P#f{(!_{9n52bM3Sv!D&}KsHI{OA(*b- z5X4aFhB-1*VDzXk1+nyOWGGYmxbNj{TNWEJk&*mUa+Y3W*5PbpMlFs(K0`Bm)rifv z4!(2z1j6)MTuSfF^$}h*)tHM=qzlgrm-1b9@ssJ_yKu@PSM^Tcp^npS6FbgBYb&Y% zN0rIJsl{p)`e_kLfSD*PEL0yckP1RQ?LP&}kGPmvmOJ4NMfI=|e%V4gmdnM@j-sRA z^wZoM;X=(1&$MNEAFo;V7pXqe?E2GInP=H(PdnnwMTbtOwg(5XIR0$(jzNAHY|BXo z#R38Dq!WLeKY%odVJ}mO4wCKrV?1}>5n9u_DKzgf(!04-yoS#{BWg!9#c+`n=}Eir2()@1_&$1kWXwVd)W^NXBwH62 z_WH|FPy6%8(YvYNw+_Md&XW1WH-+b|WNvcf`ckNvqnGNtPer?DxDjlsGPt6s@4ph@ zELR(SI{&Uln&8(6ype^cy#|C`!~~GQd@Z_O(T^N25b-Vc^4E`Y3*BWmuwFLRX0uaA zFshjA+m#a(>EV)2_ik%{Ok{MJkLc2(OPF&w6 z{`1DCG8I0F#(mEJ4?A2*({a2a zvP+&fLAe<3vN~3nAeqzy6k3~1&0)mTVB-wtrkjK(7FjRKmlsT*uVb1Nzxk#cf(T-w zH!xw44#6vwX&0?p-liTg+OneJU#?xtATrJrVTpKH6s?|Uc%wG@T;*{xi|5rYbCmp% zA_oB=d9F5eGM*^}pfAZ2tbgRel_DA@tCJlb1Ey2s z%@q@IAjtrUP6UIfy9F{o&FBPuu7fg|580%Td!RY8^tN^0G=tjwPn`pA7A+{GAk+UH z(|LDRQiuTj6Tp?go}=H$S5Jd{ZnD9xB|9Hmmy$Y>^~IJ_tEjguiT$D9^yXF7)VfxS zSS_>=N!Mz%1Ay#}fk}n_?}s-@(6dR9)bhCSbg~)kHXadsQmYJ{O=@&hmzWTZNwF32j5@0lpsOjR2JUG#pq`k!6S{jeP z)DRV|vXe!phLucr_9D?m5nw7Zxt&h$65v8A$Lmp8D)-41G!XfX0~RFU@^JyZr5%~X zef#U7t!A-hEUyhZ9*(ADER2>#Jlm>^#%r#2f7ufAWDp)Z;48^23k1kGTGEn#pxzCT ztk zz234Vz$Zt?P-B9TJCY$x!fY;v&P%~rbVX@GNTKCr|ZkRh{m#p`$VM_ z60YliZwZ4A?Ziryn?^MzoyDgJauUw8bIA6L7*GTA2sFOM$HVkSR4N#Z&Be60f6UR3lL}+ofla6s^8~m4CnP7dC^SVO>A# zH-`UrM7AnQMG^W&ho^#~Fd@Kx-YM44+EI_&4lqZ3<{&UgTuxEX%Y4NzlNN$%Lcp{0 z!AB7^zPrSHPS0recYYm03+w@xh<$%T+x?Io4{ecD;=s*8&K)+))&UobUujP-Cy$tX$B*J+i6n$OaEVBCUj1}6 zgE1z+LJF7ZfA)XR{a2)Bk!ZJzP92G1^vB|(c`ntlE9dXJqd!g9a`VDVNyXs;)W5a_ z>F|9U>e)4gAd{Y==S|I>hw_MXBezpVwYOYW0BYfdRY?7lbv?&m(~Tx7ja(s0``JBur{Q!% z!`(pMOx0+}-vx%wkjRc%u&E@16Ja&xd>I!faC`CYp~%qS*%#a`nT}E+# zuSu$uLde_zmEP>lKAW_VoxXR0)AZ8PQs$y__Z_d@Yyw?zmg#?DKc#{H%NhXEu#ks=c zhKpJx*g5lX@cv3_QR(Dkby-uNzK~r&!2LR&(9$ofvgBAg!kJ3{npG!V~xR$r1p6HhrLtU@AKw;f}L# z@M}CP?4_ibewiZtROZ4WtYEI>RZmU%Br}G15SoalX$DO-oHtGlN;6oy zzsg=YD=7stl1tYeEd0B2y=TcRl{kb?f$sxyzn-86-TjYetI7&# zdaBX4Lkb0(5l?0!r3e{#DEVwuDh}4O65Tm>whTfZ`^0gKJcs%j)wVY&3@mt_=>OEy zw|H~V-!)aW$dKc|_gt8|Z2A&aAk6u)z4=@qRf5i1tUr;X5nq)v6FrFrPmMFH7Flr; z62jPCS~@gO)q_Z8emFI|-!ki?!NP&lLh6Q{K3YZ`^4Wz!Ql>+q!hog-0UEA|P?rPO zC|BCkWIDyn?x(+gA3E^Z$hsFBzp#;gDws3#7#-aR4vXLw9Ec;_W`EF+6u@m$PGr*^ zAzHFh#F`k2_tn;>?sh;oVOpE?kPkvTJCP2TS>K=%%<08q|B-yUKE>%1cu?%iZnUYm zDU7&t8&{48eSjVlVg3{68O2;s?x0b&l)}M(FljZ*!VKm{ip5A)QXCSK8iHRq$}A9qSEkkar;(ZNL*p)Dj-J}-tz}=6YSj)L>5EdUL`5gT-97E!;OFX6SnFKLOJ#j zjizZ^PRj2 z9k8GPo~Ky9`!X2N%O(!KrE_ufK;BA?AX;5JNS_>kx(NZd1}5?1O~AI6wJ#wr_He}D zB;+4r>B2ly#o(B@aK#+9iEm@|+kHG;yuYgq<3kskKMP7E@iJ)8VVXAAPI{+jFQ{B2 zXZ6kQ@W}PpLN?*xT=_XuAg1RDt3Lw)8s?|$yUkYl)3F(P=VmBcIEfG@s^Y`yLV>Ut z$ukU6p%$Wepl~SNE0zaA`eOTtz`HC_0=Od3ye|0V^KOy1fJwgL&G(-RdWU=L^2B6+ zO=GLd$hB=-12+o`i?ik9fD1*Kk|1Q7$1k1EI*zQBCy}K!l1B~mNhn&=&BNJ1fiQ&& zQAnA$+cyJ(b0cB(5YVDiI-AGY^TvDpD+#hP^v4hb4MS&ZMW3@T9#Ipz=D zY*5#PJj1hqxff`4bq7H0ueKD=SfT$Osj385P7P&=iJF{7J3B#Lg{_z{ktF3X`K${;J=mkhZoPDJ4O$Ft4Aj!fl7@JoZ11fIa(b z%X15D39na+giqwkv6AztFk|>gt-2-+8K?1QM=x8D$3I!QYK?K-WSK558N|_gPnyGQ zf?+`f-`rzIWHLYRLRD8c)RzyT&V)r6BVWxZHRg7;&&_r7k4ho7=SG^3^x!_3?B* z;3!L_Y7G#KXtFfAwKpJScu9wWR9yXC|M?Z|+w42d%1-$OL*F%q6<3c|1t~<*`39*z zg6QOX7kr;x+{bb$C;fEQIN}kcmKfit8E}vZ5*W^y9Ibe&J7>ot_2|~EXGy)_M^WS^76diL$`&E8 z1NN}gHtDoa-Dkf()APR;Oy$v_a(6pO6Pb~rgyJsn(DP&dIAEWc$Ucx$3yqiQEV&@}!&o(Ke1-rsm^X%^JuU)JK9f@ovZ92F^YNCf5}62K(`35wlZ zZ@AN^jGy|eCntfOjq?JCB=jID?O`lo0}egi9WE;zp+t@xVF)d8&$K80$uVC7cR`(N z(IRGWk|$v$k`Gr?Kd*q9Gxo+MMMWd$j&4O3<11i!Cox-`B$lmbZv<5^*8ywcd&GE{ z+eP#8vVq--w`ZKsvFj%d;; z5gkdb{VW;0pmG_IGuOqTA}&aQdh2=@+mR51IU9#M9F0<2U1d|NF|4_USDXA*D!gX4 zumN}WkL)3FN#&Hj_MeF_1r^Bk_RpVbMNduxsxzO}F!wGP?vs^fhnJ$zLl88Y_wGHW zIrE+Za(TP71rL7K^XU1~U2TFts^xo~C)~I%wB;^l8h_Y3qm(R4D=)}KXe+Mg%kJp* zHAgeW&W-0`oAPNqwXRGaktZfFJyq?ACflVEmsv8Nl%#UJ2Mfgo7+N#OnVyBGQ76EG zy&Ld=tatkkDVQYs(fYZ1(irk|3+q7g@P6lm}K=BWMHnl&#P)T#yQ zo{unlqjvl#N=t3cdP9ybR_Zf{OSSWVJl$>WZRSwd`nvD$Ef|iE8|wm4DO9XD6zU-8 zZ<|L2PgZ2|n8FzSk6TY|Z1CHfqK@9xu6Cx|_Jc9B!&xuMy*`q$s@k|xMla^yp=_P6 zb`>RkwW6<+Kpk|brz}h#?0^W8+#JAinj<|H9wqfGSOy*@k|glLjr9n_yL*r(6eH{w zp`~JmNaN^rK&vwRW_W#6e`m8a?15HND?1t9?xS6~TR`$w3)QrW^L?~^>rX+#rneU* z7B(>&?ftI^=jdG!m=V(Uy@|+>4F}REb{YBTF_2g#i}`Z%6+tNf*BX)wHN$B8(NnFZ z$5SfG_~xHv4lb^HRHrgfX@X&&TGnqC9`4_8mS6j;A`nb|kC+JES@BQ6Z_*b0*> zb)f0(G)tH3HA69W`rRBh|72=kG{5hB3n4mU%Rf5tZ4zF@pmXtghwQR(rpV{9?RixxCW6@>E>im$C|l-KO%v} zjw6IuLySm)Pg}6)Py7S7z+%vuu`aN_1Nd&QRF}}Z^$zUB%ag1j)Yg4 zRSq|lb#fBE%Mr|B!TKjZU)Xl?`8r9oTtsn;{a^`xnqAhWxlY@<+^6CynMwO{e*I#D zcf|fhW*r;U$1Ugc0f+PW2oCAk6sj_yS;8%Ifx#2Nt<)`$!Wqf9#f^hPHRKSmyp-SW ztYgO~DlyA36_|O7^jGKPux{>j^9`sI)=va`BUNU^%tT_oQQXF-Ip-vT$M{pnUaY+$ z`AsV#;NW$kl4;~sc>&TmGN@(TpQz*CU(gt#qBc%c#RD1@A0yFJBv40Xzl%Zw^qP2W zI2x&i-8K`d+xGu^pw|S;RwanW3tS$7A-&8HGT_*k0!Wojg5bxuRcu7q1s*#u;-ii^ zVml2+EMJyTTrvT%_ht0Tjnzd^H#R#FJ|5ey3lup&|ABK3kz)R8;Y}L#9~*yuNnz!_mkE&$$!_-)Kju%iF0*c6!~!~rX-d_} zThlPFqy|mHcQ;7!i>1>f=6pY*AL;KqTL+ZDO8bSeU05b&*(q?P7kQT8QZH$z>H9XrjIIb4bzrkTZ1<;dZRlo z2 zCEO7|CTT@HcD3hCKO8=!>?&`5fhiGfs|mAcd}729miuI&)CcB`z4{0!GJKvKx1icU zUVC+mxJF}S=mZQv{?iV$>nNySE8BaJ)o0jiU{7o5_h;eL31;!I2sug?v`m*q%V@Lc$C}^46^+P7! z8mtJHYM~3X&;II(n0|>IZgc#<lm1sFy(rUMt8NA3OEqr*FV1aY@srIPG5DF6azra zZ^AQmdnnG+MtutIqe{{>6&wEL*I|s#Ini&%ORWYpG>%q?oV1PzhV+kby1C!$n$DSQ z8%-|%VpT$V2sUVfN$&k;JLCRecU%Gy9+K%C%he9O(@H5%u*5~VNP#jdjy&VZwnMGs z!#Dnzl0b~(j-}R@567@05!e_xJpouwk5l#I^9XK0x&4c6mwhm89R^XGWB$|(iiM1Z zr*A6^#`bvU9SD8!J(y4xozG??IdP3E==SI|IA7s}N)3m<94|{sG~|d@|1t%`$O_GM zSR81Yji02@ZVsU-;HVFar^#- zVk82IroLuWK)5#z-{t*$y9RaNzAV<>ju&6H(e{o>x49@>^A)b~sb(QDV-4LK_!VMI zPdv&QF<%9p8?I$s45R8rdmG2RtR_5b&0_zsMti^J9Qdc)Y;Y}rPCyxc+o$Bt?GYsK z`cHrDuWmrD9kBX$kFd}f7q#P6r0xT|oTrv5bh#5bG(m=H8KwUh!Up#PC zaG|ceds+rrB&l4%VhC&z5WGYY|9N)akf!^v?m;rw@Mkk6G4o9Vqwn-4eek7Czi<9# zjyR_@PN}qSjee9*yr>(;%B@KSs7_$Y7p?!9WXIj1bD-YnH6$J-uoMgA^lf?f-%dC9 znfw3$&o9Wa;8TkqXCv}-@M(LVGX*8QCjtl}c{JO=k_lJwuVY-oJ#am_iiM;Xv_at| zW3C_h*S1=^n%=mN#dFQf89RFulZInr0$oBz{+MmlZh=a={=y1zL#8f9j1Oh)*%fu#)X$4CHl@Ej}Sy?OH#$$Wpjw@F?^!$ z>ca4~dR;Ls{YmhhIP!xEOjuMC$8ujydQ{B>B| z@_vuVBi)jSiRHtAoAJYC$kobIQZa4x<|h{W@9gW^m2tcUbjXcAe6Sz47S5n4+AFx+ zayq0ex9jB8%{%*2iaXueaKDmPT2c}(0nFsd514OX`BM}WXSAmryYzY#)P5E~|I;(T z&M8xB7?OhF|KW9umbCdt0}xF%=hD>bT{=uZ+qm_|Y}&qr#gWHgR8W9N|Fu%B@Ou!e zOj=?zw*~4nHnB*~Grfj`lo6_N9{wHDwVNkNx6Kl8d16g3ZJBs7$~D6n3b~PAtm+p$ zUrBp+{DvUTSt&y*E8O;%2{0~q*_6l_d-6P-vJ7Zti$kf*YHMGe z<2Ig>anby(fEe}o9uaMAK}*uHh&Ed@j**#~y#oCC^$i$jiuhzrXnN{=D>W zoD&V zrm6|{%aJfd$toG~m9@%4vM!YTZJQz-6wl4_rM83-+(yMb{mI(dmP)w?OPj$WE3rE% zpNdK#?v?EJewR(XyKHt@d1cZ`88u1I=j@M9Hyw2%=f9r8$Y z@$#+UtnR&H;lk+4LFivAMhG0sKbiBFIyH{$z zp2o$--ri=~=3SjEd;h#^Xb9F1QH@+`$&T>5=+j6nnZ&oJxP|R`WfnY5W_hv9xe*le zB6xW@b$e9`_+bD*^~D#+`y8rbul8uN)Hpjt`Lo6R>2h6)!g&v4X&ZPsHt%uia{118 zP08?mq#|yzQi@dUr(bc?II^Rd*(x*^GEd=Hh_Br7&$6Q#Wo@9gxvjod<$HE!K6dz4 z8WFNSu8wni*0Qqv(FWBN5e%V=z4TBW#ozU1FO< z*n^$p@JEr@3DlzJsn7jZAN>?|lbd9C(x1S(dcr6hVE4wfG;iEHqI_%V3?7-xk~-kH z35rG!W-=6C_-072_-gYgmybRz@#A`xN%IN!!>8EPjS;F~7IKW6+MHY(JZP6s{-F2rfw*`bqdA=xyt|;=o4>U_w_79_;yS&XV*+m!dzIUF8$ zd+bdM-J_=NQQZ%BKOgQ|HZ7zOI2v^x-@iSbXtw~e9CtLjiU30=l)Qh6-!Q^??YF&Gmk#vaDr0 z9`O3?UB*K*6OFOCRkASGIQLX7X)Z*554wf*vLYaTg>_$LZB$3TF}N3` z+yQf>b^t_YcG-6!e}NOh$Xcr@Ni9=kJv%J5RD+Md7rRM_ zmqZAn4N*sL(TQH7_ZEzV=urkCBwj{@2+=#EMf4sKqK@7|biubL@vigt{5or$wLbsm zvCDm5*S)X3pZ)A&rR;2Pzp@MtogIC4`(*IN_3j%fgPdBW3$%~Qo^wrD^y%L|#-2f~ zbm-YNeAb;RM++dEQc%x%1tu$v9kMkjp^`Z!vsDF^!%qJyhnm3Xj_&8mDMiMtZ#38& z5HS^@lq{3TC)t+C*o9E-U!I7tyYEJ(FU{58TJ<&Yiu=sppYf2!R?|2Y9cw6%iVmDy zQ`7hS=ML9<=EP!lLA&6+l>DM^DCA<7=S3{%gDparNO#N9%*<{J)EpsiF z<=eE;*A#nrEM)uc+}%~uVU^}KjK;_>_(&Z+WR(&)zd7XGn&*&`G&7{@{GINDnxfT5 z2j`-@*t2c^t$Lu(N@Q*C118Ru`t`&)p07MtiiyvZ@ZUv)eQzCU!Mn+CRTbT|1uc`a z?vU=Fz6hwTA1yj$ymifI+IYZ_{Mw8f9JpCl)0b&INE-U|-yc8pmcBf8;A{C&RnYzA zAD;)zvtS%3OV(=HW{EWvMo1B)VTpmK4@Op8<*c_gt1^rR5Y^{TSHJF}L@`)+^h5UU*thUw=K$Jvg|fSAh3b?XiFP zCsM(CBlm3myn2b_;xov06g$~t6LK!Lqi%2Dj(sxd4q{I8{B@%CUIg)3 z<=4+C?Yy@ek77?X4wWmI+7ex+y8(4s{U9XTWMNRXmlie&E+ng}T%eFw1T$%ob1JGMOn13(kpc#tqp9nR6QDoGq&>nZGg6q zCZ)FwaZVFW=lGwjURj#;d>P)8|Ena{*fhLAHu^)qZYr>$jZVleX$FP9zw5vlJOTBH)br`6ce2XWv7QSD1E`;89ZG@vUPmuH>ao0<8+u z+w;&j6f7EM0!JLG6%Cp}qDhp+I#IvYE2R5&4k^0z0%G}fa-M%qL8I0uy}jIw^cSk$ zau4w9SAoHMS>t}DpC+k&iO7=kxvV` z6OpE+DI-_RA&=BK+UD$aXtgM5sQB149yh9Ecj)?}?Rd^3g^CgGaEovLX8~Lc-{8A; z&U0IP@#IG9{kVj@hy6lMRYB4$i{PUGyL2m*Sm?Gznvk+B(q6uYjY)%1&FA+0 zDA~!iir8^8`j}zz^Co*e`Fp8t-MUpfihZ74F$~phoZFw1^Bg8uF_Sho4`f2+x~@{P zlObf8aI*3O!+9k~nkkh=aP@K!!Q~rYC;7yuz}K8&5c>?PN?p;D`KI{(gzbb5kMivO zQ*MP%B#s5gPPG??8>7pND}s6NDRVYueTq)9W20N#WO6vGL^;r{omS@g@7{OK}#hwWn<)jjj*Y6me~BbU%@xv8;N`;57l&+ z*HJ_o_}5QsC#LKA2b)?OO_z|o%R}HqY11;65|D9HLPWPP?7ZGxGIdw^1u_=eJTmI` zRYcMjATq7EO*QSJcNxeCgdqH@T7no6`|2WNh4s3{R-|IoOJkmoM~+>6j_Y~CoYe3* zP@-edD-|dN0gIu(wu>)z%X%ij>iR!?=@cv~w(#rcz2WOg41VHrhNs>A@GChAiqL2> z+%(KLPjD7={=^*0`yLI)MBh7#{=VDZ4!o2wJ8by9!H z_xf70imj8+{`NTB|M(fbGila)=gk!S`fb1}7fS+?;Nu;KQDbM*p)lF?4;K$jEu4D$ z86d3i+O||a3PdLJAWu6!4RJ;9^VnL6N+;~yJ2eUK+{G3sKJ(DDH~a{D|1w86j`wZL zl2#*cLFLz*Baif%Q14s!g!Bffwbi#N>A(`J>{n!rTHH(Zouw2A0tm@&T2MG=h+=Rh zISqC_4fwu94jF}gAy06M_`AdmJmQoDF2T3ocqsf`&jZrIQUS-^|C{Q6k@&xg34B|H zePJrZ&1+X>p<%J;)?tr?7oDYGr@jK{>5E)Lw%D5+%fEXGKUyrlQl$!o6Cft=$n(hS zvNGXOOt2hXOA#T05r+>YLgPgxgsqyQ_{^k!P;iKMA2B$5ew*T|K@-?}pR(OJ(AE24 zF{%!HIw>fcEi+cfvCUrGDN`&Fk3tvx(~1k`x}YdRS)*ZGTs?UJwnvnq?glT zbPJZyrCh);M!{;C(E>)b)5r9^flm}GC%zeFJxiTnCF4}Xp5NU&G1M`fNpB|)g+pLI z+e58F1(woYTbhUFDdz=*h?NWj$?m%KJ4R-aIlsF;^(59``={h`jp-167 zrF0qFtD}1EwR|(L%A4mO;#4K|$MFSdqb5izlcKk;gKH;af&PijobmRa&0})J1V1r8 zs^KBw(xJw&pAT@kJ^feQ!-w;A2zuv!QUfDF+xrzx?`Tr{2KRCZl{2xYaQtS5>lRTmeqgWBTz0Zq^y#|-Ug!mr(vKmN&e3EGD;LTb+ofz$w14nM5p4~c=E7l>e%!) zfwzs4l$SHz_N7oV0v1g}EsL+!y8h!GQ3?K~A=AAAg zTVV>fuEbyTOB~p2)BeV3cEh14dK{@;B!mzm%26Q2%X5fP)`jPZ63Q0rDxNFlRcTua zJHD2J;C}erf=_{N_ha&be`>j3ML0)Q7RA?mTf%8!JmAcz#MjQ(Zt1`zpLH^ze7bRY zN_=_hl!4GcJss@D_;uXZdw$UWRA`*%xR;7VNcloB{6gljHH6~zwdH9=zLT?d5?ts! zRkl|2c-qPpDDB?4qDb(28HKT(GCFkYO9DA^`{C`}pJ$^6D8p#z7e;6V7uvPRLUu>! z9^z;2Z;G@rm2bT64{>nCA#*MNydB-<@!2U~P^N_9Q)Fw=s)nByT{8)Gm*F24cl89R z>LGk2Vzsi(%{5G+FbT--rRdI-_gA2;Z5MW`oY!k!+$Mve-=W7&&h%zSrr5bkNsqE{ zTtCie{d{Nwp-@;J$hyr{eAF(_xDN3?DM?h>JB_7-5PYM_)+%#_w_kAFQNBIg`^TZ# zr_?0^L*+cMT;z}M996ri0cN&Q7WcGl@%-M}RPFIf*y091>mXM3OT@o!zGM>$+ki9- zSaR5g#S#s2+En~h=C6-;ole5o7RM4jKjp%DcnT4MAE-q|;y?%(LvbKnWZ7D9WhO)X zzd!%8LJmp_!@-4;Dn;SMX~T)S1poeop__1)8>@kp&!OFViC3}^nQK(5;L?n~wHcu~ zsirx-C60LQ6Z4B*4l7VXb}Fc>a@20?3xp#;F4((9&uF4L|C6t1UY#<}Ao;cf+ z&5tN+CSfCmBPtW=2;pHnBPi)lwNQnw>gdFh*!l173MFAVQpE8+-8aYtZ{R`&SpoF_ zTISmnEYOXVC4Daf8>uQ0c8@?VZIG*{%tm~*fDisz*dsC!sV~EnZ@mPaKG52@@eBtt zK{-ot@3r?~@K}fh8;6dO-*G%Dq0bxcl9@4LZW?b*IUo<5F5^QJa+I5fxaymaN)8uA z!HaLcP19lP@0HYLk`}$^TQ0>TGC3EKG=CR(IU5B0_@!3U$%0TgBXXfr0jD;J8XOB#xwv+l>7`AqWuGiX$oR_t+ z@&Vu~M1L{;H6~O97&DEGmLvNURxm~f+~WOI@P9*^_xQ|Xj$;8P5a!3c*+@zclNW>E z5^)GdHA58&B92s@*#{>^I5_Iy!{!BW(iN8|I>>b*jCby+t~rvBPfbYe`qvjo(wYRE zxZ&{UepAW_t1L;6U8kvY(+}5<8Sfy0Q<@JX zmQXMitE}hEZZ%A^uhAgOR zGGwM^vS!=Jmf)NG`jJz^%;=lVBihPe=Q42OlAcg08%mYG^WY5WhIf0=Q?2lM!k*>k zG4Tz6N+krcdsfXwN;#7=#LUo(w28(6fE)+pu#VcJ`6Q5AWlTwnf25Nd(_Pv@`7tt& zakdJk=sUIv7Leg;U<=^O>~YX=?CQ%C#&Nl%rc{UraTLhUa#a{}@jgbTe(MMn)&88)5N;O}m$zplSTaZ8AhK&Z}g{t-}XeIiXV zW=+xrx|U-jvCiGCkr2#5_}jI2BK&QSxxe`RQGysqkhvvUvgUs(!Vf~kYb<#T+t+|+ zq4GdwVqQI$y;Kn|Ali8!-W1_qiV`m<+_0UM@=`@mD!_NseQV*%;vQ=O@R`~F{VxDb z0jaBn93QZ~2liPE_)UG6F#IwgS3whl>q{DvToz|Q2|$We=DgG_r8FRMb!%OYm&KuT z0A=#3vX`0#tApAjhxIfb0WJSiKQ)LkUQ2SKOIwH8fW#FCl+j)m_X$Lg_5VZH!@c-k zj1eo&7P{l5OO=Xd=I_#DEkRsReG`jzzGfJ77mNWuttDst^5`(2%_B~I!^RRCih!v| z#wFptt$tWgSDnY=erbH9F+keIVxHxar)sQTr^dJcr`Ilk$C~h{mzPjX1D^biACN0v z@>m0)nWtu#x&+z}j94(vt4j2jP|btBCffMb(It(u9|6$+KUZ#42Sbqy?>kn~dUGyd zG4CbPjg=DD!whWnhKZNC(XuHLfvW+LU(yrfk7ti1^M{D z3CTrfXFwX-0B!vw2u;mqbzy{;tyP%Pu-YKA*H_c|K>byJKejP4dO|ik;}y-y1&^p1 zcTvO@%pg9)OYvop(NmlBqFy!lg!kC66_vp9v=bjHzLYl$mBQiJwe!I!-?WzpEse;E zBn;}PF>3e^y#)ECr>aEsF53y845I^dYFFM)0An*5K${kik5VM`DC1c}%~9u_pL>kX z#e%^mRFLE6R@0Tl@gij5l7K_>?@nh-)PH5~%6Q~3i@+_CE1n)sW0GwZuwLmP)Qo)< z`XvZ3jbp!-4;8MX(HS7t#*oq{7%LRNM=oW5orB7r$kV-|n^7PbkGMWL^u&LOTKD9< z`P5AD%`3zs0BF%2_S>jRZ<#bHd32zb-T8j1v(^j2>mVAxAYq+5$z>PWT{3qZRo@ER%koFp|xCV!PWPEvz#Z`-FnPlcSV!9^*jPgMQ z%t8WZeh6%D1%~{*`og_LyG5YQlr3UM_s#r!&@6JpZ>k$fKQZ;wl-s=3?|C(_zokua zF|MlbnPmIZKkdh8HhtMl$YU@hn%4PPBqOusug72cmDKSroCQyH2F`n}MXf-~MT`>B zOMU033*LzsI@#qu%+$icA2$KxswK8ND%pk6FJH@jt6gSrnCvBUV~>cH$3b`z@fw%H z3-zA;pg?}#w_ImwR?S!Uc_g2U@&7npDb*k!C3tea@j}|j*2^(?fKd51fK*h2aY|%0 zeBbu;@w!gUjWeb|GwmrY3LqF6pN!uK*&Wyh`CiBvmO5rFq6o0V$s;b(TbSM19FE>9 z_9&@G3RG|QbV7@+0-8xif9@@VEv9Vn{nx0A+_@ipCRS>kxxI}4G{qZz{pZNo?yvC~L6SCX(?jzh z+?r9_0tU>d*XafO#S^BQZ(L;xz04><*0}KaXh}#kb-!9HXd5A4=I~^X$L4j(a{@px ztPFDPwlO=Md8Hd`v-ae86~9rU(?KtFKIp76>j*ogd<$`Ge4r+;JVI)DXO63n%T6~B z$<1&_u-#VIwdWkuXI;OZY^oNG&a9S9$6h?HOeWTH0w7IjdT}bqbXD)Y-a|RSe@L<& ze{Bd!>ofMvh_IBv>Oc{iMdfWGn{-vB>pP>`;{C+sEK}YndGarcv>l9gcn2;sqfZxm zUwW?z%u3m1Sl8G%voUQ|dun|{q% z)~0j*M<{FLB?M;7A)d=L!B1xm?7nf{^c5L%)H4g-<&G#RpQUrkVAtvVA7uk z*$eH;#|MYEkmJ)QagBPI(dzL^e%mKWl3I4mL!4qdM1fIGQ#E&M*6P;H#}(a!3>Epl zM>g1Wx=b`U=l7m`Q+053svt|cfkk#6;_CdJJLKZWsr5xm+t;P9_2Lcwi{Dzu!4O+=%Ob|mbiBQ(Ps{X0E`t04)6XEjswP&+Zt~?(O;um&XRZ5hs9zHXXJo9)e=vKu) zQO0&1LeS3Rw&s=JYyKN0@%zYQbyHYeV;HU4_Ea{;+wq{RliJha8R8DtN&1Wz7x&U* zmGq~bQylwLFSckm?k1lW*I8!#-kfSqdLr#nseI!z_T{aVXzwu1W?w~Gy1LD0e8Oej zb8)v;+PwUCV#?Pm6oWT`+}gp(G?+b?f}ZYmW$*F);O&dFig0VrtKX_IExuUk6>X)U z0O&=slDY^J(V_D7i&Nd+qC{Q9@`67tr7JeyRCKbe!@duy^I7&_zKWUG!ncuUg;J%R zZ0>4;XS+R^)PX5Zk&u&Qj~#id`HYAftZ*ej0;eZ9o`^JYukVx9=Zg;zADiS}O$j*H zD9$7Ko=g9RN zEc_5V9|4^wT^XX;n=bt4{sd~^_&{OOcA`YUk?vHjV4SX#+E)7TXWKz@PmH8L`;7wd zKBUwk5q!?)8?>k)U-)5?U1YxE^PXD1t!t;`*hWFUuyYQz* z=6k7G)yX;~tExh9;YE$$-3i~IeF3CyaHe%R@;7%n89?`wKz4}57MBpl)vVEL=ky?# zVhGGnu<7$qSeaE%i}_YbacvScK092wkIVYNz|F?)14#&bk!{CizC58ek0LNe&l8Xv?cBO>Ua*tG%~H~^-uRHxJ5 zd6!aS_0Lw3z0|yr=u-^xa~Cz$@a%z;oWLkZ0xTuy<2kyHl8wF)+3GLGx|USx1kS1A&-B*YT@ zj1gc7zi+ygG@*BGYhzl3%bXpr5;Ii>xQsn?=zm;r!55; ztSu-r@Ydq5SNxqLa58PHu|a#Q-uUg<=@tyrRyhz!j2#dDi|6I3J2vnw`=wccggyP~ zMh3_EuZ=+#KT5<57#y5UhrLzcbeqhq6+wPjbIA*n9yt|MyEW(bCLZ*U?Oddh+OPN2 zziZH}jcFGTP;+?KS&eY!)i*_bR2NaZ*G)cE1o1AP9`;I}o)?lT?Mf#AI1WNiXukKu z3igjPi(DP;r)uVRE*@MbhP(s4)w{6R+q$y{Vu$Lh$-A+Iy0$BE)A#nwee~Bk;zz{8 zMb^(U71nJF4YyS%de7CL?YeV72#84sjl%=3u-WYi6x};1Ux+2b#BN1j$0Oeumfr5B sA*$E?*J6Av@0a*h;sr%$5y9_&$Z3~{9N8smaKN9kyt-Vm?BlTi0dii`)c^nh literal 41823 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 Xk(B55pWWvH@PpL5pj&a?{{H^}W8vBe diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ghost.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ghost.png new file mode 100644 index 0000000000000000000000000000000000000000..9d7624fbf77a6b31cc9c4dd07688f1d531786998 GIT binary patch literal 1953 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 literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/groupsintro.png b/TMessagesProj/src/main/res/drawable-xxhdpi/groupsintro.png new file mode 100644 index 0000000000000000000000000000000000000000..9efcf21874ac393e7b8bff2fd689110cf9a59320 GIT binary patch literal 67015 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